"""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()