chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""Validazione dell'edge del credit-spread di cerbero-bite sui PREZZI REALI.
|
||||
|
||||
cerbero-bite (container accanto) vende credit spread su ETH (bull-put primario,
|
||||
short delta ~0.18, DTE 18, PT 50% / stop 2.5x credito / delta-breach 0.30 / vol-stop
|
||||
+10 DVOL / time-stop 7 DTE). Domanda: l'edge regge su un CICLO ETH completo, o e'
|
||||
profittevole solo nei campioni calmi?
|
||||
|
||||
Tre analisi (riprendibili):
|
||||
1) entry_economics() -> economia d'ingresso REALE dalla chain (data/options/eth_chain.parquet):
|
||||
credit/width effettivo a delta 0.18 dai bid/ask veri, eleggibilita' sotto i gate liquidita'.
|
||||
2) tail_model_free() -> esito terminale dai prezzi ETH reali (2018-2026), cw reale 0.106,
|
||||
NESSUN modello opzioni (niente errore BS): win-rate, EV, frequenza max-loss.
|
||||
3) managed_backtest() -> lifecycle CON management; mark con skew calibrato sulle IV reali.
|
||||
|
||||
ESITO (2026-06-09):
|
||||
- cw reale a delta 0.18 = 0.106 (short ~9.4% OTM, NON 18%), max-loss/credito = 8.4x, eleggibilita' 65%.
|
||||
- hold-to-expiry @0.106: EV -1.0 crediti/trade, 7/9 anni NEGATIVI, max-loss 17.8% delle volte.
|
||||
- managed (skew): EV -0.02 cr/trade, win-rate 37% (delta-breach esce sul 62% dei trade a piccola perdita).
|
||||
- VERDETTO: NON edge robusto su ciclo completo. Il "+0.48%/mese" era artefatto di finestra calma
|
||||
(mag-giu 2026, no crash). Premium-selling a skew negativo: vince nei campioni calmi, restituisce
|
||||
tutto (o piu') nei crash. Tune "Profilo B" (vendere a 9.4% OTM) PEGGIORA la frequenza di max-loss.
|
||||
Coda CONCENTRATA col fade ETH di PythagorasGoal (stesso crash colpisce entrambi).
|
||||
|
||||
TODO APERTO (per nail-are l'EV managed esatto): la calibrazione non e' ancora perfetta
|
||||
(mark mid+skew da cw 0.228 vs 0.106 reale -> sovrastima il credito ~2x). Manca: modellare
|
||||
bid/ask reale incrociato sulle 2 gambe + griglia strike reale (entrambi nella chain) cosi'
|
||||
l'entry cw scende a 0.106 e l'EV managed diventa esatto. Allora chiudere il sì/no definitivo.
|
||||
|
||||
uv run python scripts/analysis/cerbero_bite_credit_spread.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys, math, collections
|
||||
from pathlib import Path
|
||||
import numpy as np, pandas as pd
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
from scripts.analysis.options_chain import OptionChain, load_market
|
||||
from scripts.analysis.explore_lab import get_df
|
||||
from scripts.analysis.option_overlay_lab import bs_put, _ncdf, dvol_for
|
||||
|
||||
SHORT_OTM, LONG_OTM, DTE = 0.094, 0.134, 17 # da chain reale (delta 0.18, width 4%)
|
||||
CW_REAL = 0.106
|
||||
|
||||
|
||||
def entry_economics():
|
||||
oc = OptionChain("ETH"); ch = oc.df
|
||||
mk = load_market("ETH")[["ts_ms", "spot"]].dropna().sort_values("ts_ms")
|
||||
p = ch[ch["option_type"] == "P"].copy()
|
||||
p = pd.merge_asof(p.sort_values("ts_ms"), mk, on="ts_ms", direction="backward")
|
||||
cand = p[(p["tenor_d"] >= 14) & (p["tenor_d"] <= 21)].dropna(subset=["delta", "bid", "ask", "strike", "spot"])
|
||||
rows = []
|
||||
for (ts, exp), g in cand.groupby(["timestamp", "expiry"]):
|
||||
spot = g["spot"].iloc[0]
|
||||
sc = g[(g["delta"] <= -0.12) & (g["delta"] >= -0.22)]
|
||||
if sc.empty: continue
|
||||
short = sc.iloc[(sc["delta"] + 0.18).abs().argmin()]
|
||||
Ks = short["strike"]; longc = g[g["strike"] < Ks]
|
||||
if longc.empty: continue
|
||||
longp = longc.iloc[(longc["strike"] - (Ks - spot * 0.04)).abs().argmin()]
|
||||
W = Ks - longp["strike"]
|
||||
if W <= 0: continue
|
||||
credit = short["bid"] - longp["ask"]
|
||||
def ok(o):
|
||||
sp = (o["ask"] - o["bid"]) / ((o["ask"] + o["bid"]) / 2) if (o["ask"] + o["bid"]) > 0 else 9
|
||||
return (o["open_interest"] or 0) >= 100 and sp <= 0.15 and o["bid"] > 0
|
||||
cw = credit / (W / spot)
|
||||
rows.append(dict(cw=cw, credit=credit, elig=ok(short) and ok(longp) and cw >= 0.08 and credit > 0,
|
||||
short_otm=(spot - Ks) / spot, delta=short["delta"]))
|
||||
r = pd.DataFrame(rows)
|
||||
print(f"[ENTRY] {len(r)} spread | eleggibili {r['elig'].mean()*100:.0f}% | cw mediano {r['cw'].median():.3f} "
|
||||
f"| short OTM {r['short_otm'].median()*100:.1f}% | max-loss/credito {((1-r['cw'].median())/r['cw'].median()):.1f}x")
|
||||
|
||||
|
||||
def tail_model_free():
|
||||
df = get_df("ETH", "1h"); c = df["close"].values; n = len(c)
|
||||
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True); H = DTE * 24
|
||||
res = []
|
||||
for i in range(200, n - H - 1, 24 * 2):
|
||||
S0 = c[i]; Ks = S0 * (1 - SHORT_OTM); Kl = S0 * (1 - LONG_OTM); W = Ks - Kl
|
||||
Sx = c[i + H]; intr = min(max(Ks - Sx, 0.0), W); credit = CW_REAL * W
|
||||
res.append((ts.iloc[i].year, 1 - intr / credit, Sx < Kl))
|
||||
R = pd.DataFrame(res, columns=["y", "pnl", "maxloss"]); P = R["pnl"].values
|
||||
print(f"[TAIL model-free @cw0.106] win {(P>0).mean()*100:.0f}% | EV {P.mean():+.2f}cr | max-loss {R['maxloss'].mean()*100:.0f}% "
|
||||
f"| anni neg {(R.groupby('y')['pnl'].mean()<0).sum()}/{R['y'].nunique()}")
|
||||
|
||||
|
||||
def _skew_fit():
|
||||
oc = OptionChain("ETH"); ch = oc.df
|
||||
mk = load_market("ETH")[["ts_ms", "spot"]].dropna().sort_values("ts_ms")
|
||||
p = ch[ch["option_type"] == "P"].copy()
|
||||
p = pd.merge_asof(p.sort_values("ts_ms"), mk, on="ts_ms", direction="backward")
|
||||
p = p.dropna(subset=["iv", "strike", "spot", "delta", "tenor_d"])
|
||||
p = p[(p["tenor_d"] >= 7) & (p["tenor_d"] <= 35) & (p["iv"] > 0)]
|
||||
p["dd"] = (p["delta"] + 0.5).abs()
|
||||
atm = p.sort_values("dd").groupby("timestamp")["iv"].first()
|
||||
p["atm_iv"] = p["timestamp"].map(atm); p = p.dropna(subset=["atm_iv"])
|
||||
p["k"] = np.log(p["strike"] / p["spot"]); p["ratio"] = p["iv"] / p["atm_iv"]
|
||||
p = p[(p["k"] > -0.35) & (p["k"] < 0.15) & (p["ratio"] > 0.5) & (p["ratio"] < 3)]
|
||||
coef, *_ = np.linalg.lstsq(np.c_[p["k"], p["k"]**2], p["ratio"] - 1.0, rcond=None)
|
||||
return coef # a, b
|
||||
|
||||
|
||||
def managed_backtest():
|
||||
a, b = _skew_fit()
|
||||
def ivol(S, K, atm):
|
||||
k = math.log(K / S); return max(atm * (1 + a * k + b * k * k), 0.05)
|
||||
def put_delta(S, K, T, sig):
|
||||
if T <= 0 or sig <= 0: return -1.0 if S < K else 0.0
|
||||
return _ncdf((math.log(S / K) + 0.5 * sig * sig * T) / (sig * math.sqrt(T))) - 1.0
|
||||
def mark(S, Ks, Kl, T, atm):
|
||||
return bs_put(S, Ks, T, ivol(S, Ks, atm)) - bs_put(S, Kl, T, ivol(S, Kl, atm))
|
||||
df = get_df("ETH", "1h"); c = df["close"].values; n = len(c)
|
||||
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True); dvol = dvol_for(df, "ETH")
|
||||
H = DTE * 24; STEP = 6; cw = []; tr = []
|
||||
for i in range(200, n - H - 1, 24 * 2):
|
||||
S0 = c[i]; atm0 = dvol[i] if not np.isnan(dvol[i]) else 0.6
|
||||
Ks = S0 * (1 - SHORT_OTM); Kl = S0 * (1 - LONG_OTM); W = Ks - Kl
|
||||
credit = mark(S0, Ks, Kl, DTE / 365.0, atm0)
|
||||
if credit <= 0: continue
|
||||
cw.append(credit / W); pnl = why = None
|
||||
for k in range(STEP, H + 1, STEP):
|
||||
j = i + k; Trem = max((H - k) / (24 * 365.0), 1e-6); Sj = c[j]
|
||||
atmj = dvol[j] if not np.isnan(dvol[j]) else atm0; mk = mark(Sj, Ks, Kl, Trem, atmj)
|
||||
if mk <= 0.5 * credit: pnl, why = 1 - mk / credit, "PT"; break
|
||||
if mk >= 2.5 * credit: pnl, why = 1 - mk / credit, "stop"; break
|
||||
if put_delta(Sj, Ks, Trem, ivol(Sj, Ks, atmj)) <= -0.30: pnl, why = 1 - mk / credit, "delta"; break
|
||||
if atmj - atm0 >= 0.10: pnl, why = 1 - mk / credit, "vol"; break
|
||||
if k >= (DTE - 7) * 24: pnl, why = 1 - mk / credit, "time"; break
|
||||
if pnl is None:
|
||||
Sx = c[i + H]; intr = min(max(Ks - Sx, 0), W); pnl, why = 1 - intr / credit, "expiry"
|
||||
tr.append((ts.iloc[i].year, pnl, why))
|
||||
P = np.array([t[1] for t in tr])
|
||||
print(f"[MANAGED skew] cw@entry {np.median(cw):.3f} (vs 0.106 reale: sovrastima ~2x, EV vero <=) | "
|
||||
f"win {(P>0).mean()*100:.0f}% | EV {P.mean():+.3f}cr | worst {P.min():.1f} | "
|
||||
f"uscite {dict(collections.Counter(t[2] for t in tr))}")
|
||||
R = pd.DataFrame({"y": [t[0] for t in tr], "p": P})
|
||||
print(f" 2021+: EV {R[R.y>=2021]['p'].mean():+.3f}cr/trade")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
entry_economics()
|
||||
tail_model_free()
|
||||
managed_backtest()
|
||||
Reference in New Issue
Block a user