"""R0702 ALB-STRUCTURE — double diagonal deep-OTM "Albimarini" vs struttura VRP01. FILONE: video didattici claimano su SPY una vendita sistematica di DOUBLE DIAGONAL a credito (2 short scadenza T a ~9% di distanza su ~6 giorni + 2 long scadenza T+1g, entrambi i lati) con 82% win, PF 5.16, "420% annuo". Qui la STRUTTURA (non il gate — lezione 2026-07-01: il gate IV-rank>0.30 canonico NON si riottimizza) viene portata su BTC/ETH Deribit (che ha scadenze giornaliere) e modellata onestamente sul nostro stack DVOL, contro il VRP01 canonico (put credit spread settimanale -0.28/-0.10, gate vrp>0 + ivr>=0.30 + crash-skip 0.90). ⚠️ CAVEAT SKEW — IN TESTA, NON IN FONDO: il pricing e' Black-Scholes FLAT sulla DVOL (IV ATM 30g). Il deep-OTM (qui 1.5-3.0 sigma ~ 8-25% di distanza) e' ESATTAMENTE dove il flat-vol sbaglia di piu': su crypto lo smile e' ripido su ENTRAMBE le ali (su equity solo put). Il premio reale delle ali e' probabilmente > modello (f>1) per le put e variabile per le call; la calibrazione reale che abbiamo (f~1.0) e' ATM-ish delta -0.28 su finestra calma, NON copre il deep-OTM. Quindi OGNI numero va letto come BANDA sul fattore premio f in {0.6, 0.8, 1.0, 1.3} + uno scenario skew asimmetrico (f_put=1.3 / f_call=0.7), MAI come stima puntuale. In piu' la DVOL e' IV a 30g usata per tenor 3-6g (term structure ignorata; snapshot vol_term 2026-06: iv_7d vs iv_30d entro ~±3pt in calma, ma in stress il front-end esplode -> il modello SOTTOSTIMA il mark-to-market avverso in crash). Distanza in unita' di VOL, non il 9% fisso di SPY: 9%/6g su SPY (IV~16%) = ~3.5-4 sigma; BTC si muove del 9% in 6 giorni spesso. Cella centrale dichiarata A PRIORI: z=2.0 sigma, ali dz=+1.0 sigma, tenor 5g (centro del range 3-6g del video). z=3.0 in griglia = cella "fedele al video". Riusa: options_vrp_lab (bs_put, load_series, per_year), options_vrp_v2 (vrp_spread_weekly = VRP01, _ivrank, _rv30), altlib.marginal_vs_tp01. Fee Deribit opzioni per gamba: 0.03% del notional cap 12.5% del premio (+ delivery 0.015% cap 12.5% sull'ITM a scadenza; il diagonale paga anche la fee di USCITA per vendere le long residue a T). NON deploy: regola standing "niente short-vol da modello in deploy" — l'esito massimo e' conoscenza sulla struttura. uv run python scripts/research/r0702_alb_structure.py """ from __future__ import annotations import sys from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "alt")) import numpy as np, pandas as pd from scipy.stats import norm from scripts.research.options_vrp_lab import bs_put, load_series, per_year from scripts.research.options_vrp_v2 import vrp_spread_weekly, _ivrank, _rv30 HOLDOUT = pd.Timestamp("2025-01-01", tz="UTC") DAY = 1.0 / 365.25 # cella centrale DICHIARATA A PRIORI (prima di guardare qualsiasi risultato) CENTRAL = dict(z=2.0, dz=1.0, tenor_d=5) F_SWEEP = (0.6, 0.8, 1.0, 1.3) # banda skew simmetrica F_SKEW = dict(f_put=1.3, f_call=0.7) # scenario skew crypto-shaped (put ricche, call povere) def bs_call(S, K, T, sig): if T <= 0 or sig <= 0: return max(S - K, 0.0) d1 = (np.log(S / K) + 0.5 * sig ** 2 * T) / (sig * np.sqrt(T)) return S * norm.cdf(d1) - K * norm.cdf(d1 - sig * np.sqrt(T)) # r=0 def _fee_frac(prem_frac, notional_ratio=1.0, rate=0.0003): """Fee Deribit per gamba come frazione di S0: rate*notional cap 12.5% del premio.""" return min(rate * notional_ratio, 0.125 * max(prem_frac, 0.0)) def run_structure(asset, kind, z=2.0, dz=1.0, tenor_d=5, f_put=1.0, f_call=1.0, gated=False, collect=None): """Vendita sistematica non-overlapping della struttura, cadenza = tenor_d. kind: 'diag' = double diagonal Albimarini (short T entrambi i lati, long T+1g piu' OTM) 'condor' = iron condor STESSA scadenza (controllo del claim '+1 giorno') 'vert' = vertical put credit spread deep-OTM (solo lato put, stessa scadenza) Strike: K = S0*exp(±z·σ√T) (z in sigma dell'orizzonte SHORT); ali a z+dz. gated=True -> gate CANONICO VRP01 (vrp>0 AND ivr>=0.30 AND ivr<=0.90), NON riottimizzato. Ritorna Series di rendimenti per-periodo su capitale = S0 (spot a entry), indice = scadenza. collect (dict) accumula diagnostica per la decomposizione diag-vs-condor.""" J = load_series(asset) px = J["px"].values; dv = J["dvol"].values / 100.0; idx = J.index n = len(px); T = tenor_d / 365.25 T_long = T + DAY if kind == "diag" else T has_call = kind in ("diag", "condor") rets = {} i = 60 while i + tenor_d < n: S0 = px[i]; sig = dv[i] if gated: rv = _rv30(px, i); ivr = _ivrank(dv, i) skip = ((not np.isnan(rv) and (sig - rv) <= 0) or (not np.isnan(ivr) and (ivr < 0.30 or ivr > 0.90))) if skip: rets[idx[i + tenor_d]] = 0.0 i += tenor_d continue m = sig * np.sqrt(T) Kp_s = S0 * np.exp(-z * m); Kp_l = S0 * np.exp(-(z + dz) * m) Kc_s = S0 * np.exp(+z * m); Kc_l = S0 * np.exp(+(z + dz) * m) # premi a entry (frazione di S0), f per lato ps = bs_put(S0, Kp_s, T, sig) / S0 * f_put # short put pl = bs_put(S0, Kp_l, T_long, sig) / S0 * f_put # long put (T o T+1g) legs = [ps, pl] cs = cl = 0.0 if has_call: cs = bs_call(S0, Kc_s, T, sig) / S0 * f_call cl = bs_call(S0, Kc_l, T_long, sig) / S0 * f_call legs += [cs, cl] credit = (ps + cs) - (pl + cl) # exit a scadenza degli short j = i + tenor_d S1 = px[j]; sig1 = dv[j] short_pay = max(0.0, Kp_s - S1) / S0 + (max(0.0, S1 - Kc_s) / S0 if has_call else 0.0) if kind == "diag": # long con 1 giorno residuo: mark BS alla DVOL di uscita (vega!) lp = bs_put(S1, Kp_l, DAY, sig1) / S0 * f_put lc = bs_call(S1, Kc_l, DAY, sig1) / S0 * f_call long_val = lp + lc exit_fee = sum(_fee_frac(v, notional_ratio=S1 / S0) for v in (lp, lc)) else: # stessa scadenza: valore = solo intrinseco long_val = max(0.0, Kp_l - S1) / S0 + (max(0.0, S1 - Kc_l) / S0 if has_call else 0.0) exit_fee = 0.0 entry_fee = sum(_fee_frac(v) for v in legs) # delivery fee su gambe short ITM a scadenza (0.015% cap 12.5%) deliv = _fee_frac(max(0.0, Kp_s - S1) / S0, rate=0.00015) if has_call: deliv += _fee_frac(max(0.0, S1 - Kc_s) / S0, rate=0.00015) pnl = credit - short_pay + long_val - entry_fee - exit_fee - deliv rets[idx[j]] = pnl if collect is not None: # costo extra del T+1 a entry e valore residuo recuperato a exit (vs intrinseco) pl_T = bs_put(S0, Kp_l, T, sig) / S0 * f_put cl_T = (bs_call(S0, Kc_l, T, sig) / S0 * f_call) if has_call else 0.0 intr = max(0.0, Kp_l - S1) / S0 + (max(0.0, S1 - Kc_l) / S0 if has_call else 0.0) collect.setdefault("extra_cost", []).append((pl + cl) - (pl_T + cl_T)) collect.setdefault("recovered", []).append((long_val - intr) if kind == "diag" else 0.0) collect.setdefault("short_pay", []).append(short_pay) collect.setdefault("credit", []).append(credit) i += tenor_d return pd.Series(rets) def book(kind, **kw): rB = run_structure("BTC", kind, **kw); rE = run_structure("ETH", kind, **kw) return pd.concat({"B": rB, "E": rE}, axis=1, join="inner").mean(axis=1) def metrics(r, tenor_d): """Metriche su rendimenti per-periodo (cadenza tenor_d). win/PF solo sui periodi ATTIVI.""" r = r.dropna() ppy = 365.25 / tenor_d if len(r) < 3 or r.std() == 0: return dict(sh=0.0, sh_h=0.0, cagr=0.0, dd=0.0, worst=0.0, win=0.0, pf=0.0, act=0.0) def _sh(x): return float(x.mean() / x.std() * np.sqrt(ppy)) if len(x) > 2 and x.std() > 0 else 0.0 eq = np.cumprod(1 + r.values); pk = np.maximum.accumulate(eq) yrs = len(r) / ppy act = r[r != 0.0] pos = act[act > 0].sum(); neg = -act[act < 0].sum() return dict( sh=_sh(r), sh_h=_sh(r[r.index >= HOLDOUT]), cagr=float(eq[-1] ** (1 / yrs) - 1) if yrs > 0 and eq[-1] > 0 else -1.0, dd=float(np.max((pk - eq) / pk)), worst=float(r.min()), win=float((act > 0).mean()) if len(act) else 0.0, pf=float(pos / neg) if neg > 0 else float("inf"), act=float((r != 0.0).mean()), n_act=int(len(act))) def row(label, r, tenor_d): mm = metrics(r, tenor_d) pf = f"{mm['pf']:5.2f}" if np.isfinite(mm["pf"]) else " inf" print(f" {label:<40} {mm['sh']:>6.2f} {mm['sh_h']:>6.2f} {mm['cagr']*100:>+6.1f}% " f"{mm['dd']*100:>5.1f}% {mm['worst']*100:>+6.2f}% {pf} {mm['win']*100:>4.0f}% {mm['act']*100:>4.0f}%") return mm def to_daily_lumped(wk): """Rendimenti per-periodo -> griglia giornaliera con lump alla scadenza (convenzione _vrp_combo_returns: preserva lo Sharpe annualizzato, niente smoothing).""" wk = wk.sort_index() days = pd.date_range(wk.index.min().normalize(), wk.index.max().normalize(), freq="1D", tz="UTC") daily = pd.Series(0.0, index=days) daily.loc[wk.index.normalize()] = wk.values return daily HDR = f" {'struttura':<40} {'ShF':>6} {'ShH':>6} {'CAGR':>7} {'maxDD':>6} {'worst':>7} {'PF':>5} {'win%':>5} {'att%':>5}" def main(): print("=" * 110) print(" R0702 ALB-STRUCTURE — double diagonal deep-OTM (Albimarini) vs vertical vs condor vs VRP01") print(" Capitale = SPOT a entry (S0) per le strutture nuove; VRP01 canonico = strike corto (sua convenzione).") print(" ⚠️ SKEW: pricing BS FLAT su DVOL-30g; il deep-OTM e' banda-f, non stima puntuale. Term structure ignorata.") print("=" * 110) z, dz, tn = CENTRAL["z"], CENTRAL["dz"], CENTRAL["tenor_d"] for a in ("BTC", "ETH"): J = load_series(a) sig = J["dvol"].mean() / 100.0 d5 = sig * np.sqrt(tn / 365.25) print(f" {a}: DVOL media {sig*100:.0f}% -> 1σ su {tn}g = {d5*100:.1f}% | z=2.0 = {2*d5*100:.1f}% " f"| z=3.0 = {3*d5*100:.1f}% (il '9% su SPY/6g' ≈ 3.5-4σ equity)") # ------------------------------------------------------------------ (1) VRP01 canonico print(f"\n (1) VRP01 CANONICO (riproduzione options_vrp_v2 COMBO: spread -0.28/-0.10 7g, vrp>0+ivr30+cs90)") print(HDR) vrp = {} for f in F_SWEEP: vrp[f] = pd.concat({"B": vrp_spread_weekly("BTC", defined_risk=True, f=f, gate_vrp=True, gate_ivr=0.30, crash_skip=0.90), "E": vrp_spread_weekly("ETH", defined_risk=True, f=f, gate_vrp=True, gate_ivr=0.30, crash_skip=0.90)}, axis=1, join="inner").mean(axis=1) row(f"VRP01 gated f={f}", vrp[f], 7) vrp_nog = pd.concat({"B": vrp_spread_weekly("BTC", defined_risk=True, f=1.0), "E": vrp_spread_weekly("ETH", defined_risk=True, f=1.0)}, axis=1, join="inner").mean(axis=1) row("VRP01 NO-gate f=1.0", vrp_nog, 7) # bridge di validazione del motore: vertical -0.28-equivalente (z~0.58, dz~0.70, 7g) ~ VRP01 zb = float(-norm.ppf(0.28)); dzb = float(-norm.ppf(0.10)) - zb br = book("vert", z=zb, dz=dzb, tenor_d=7, f_put=1.0, f_call=1.0, gated=True) row(f"[bridge] vert z={zb:.2f} dz={dzb:.2f} 7g gated", br, 7) print(" (bridge ~ VRP01 a meno di convenzione strike/capitale: valida il motore nuovo)") # ------------------------------------------------------------------ (2) tabella principale print(f"\n (2) STRUTTURE ALLA CELLA CENTRALE A PRIORI (z={z}σ, ali +{dz}σ, tenor {tn}g) — banda f") scen = [(f, f, f"f={f}") for f in F_SWEEP] + [(F_SKEW["f_put"], F_SKEW["f_call"], "SKEW fp=1.3/fc=0.7")] streams = {} for gated, gtag in ((False, "NO-GATE"), (True, "GATE canonico (vrp>0+ivr30+cs90)")): print(f"\n --- {gtag} ---") print(HDR) for kind, ktag in (("diag", "DIAG double-diagonal T+1g"), ("condor", "CONDOR iron condor stessa T"), ("vert", "VERT put spread stessa T")): for fp, fc, ftag in scen: r = book(kind, z=z, dz=dz, tenor_d=tn, f_put=fp, f_call=fc, gated=gated) row(f"{ktag} {ftag}", r, tn) streams[(kind, gated, ftag)] = r # distanze alternative (sweep trasparente, selezione SOLO in-sample; f=1.0 gated) print(f"\n (3) SWEEP DISTANZA/TENOR (gated, f=1.0) — selezione cella SOLO su Sharpe PRE-holdout") print(f" {'cella':<40} {'ShF-IS':>7} {'ShH':>6} {'DD':>6} {'worst':>7} {'PF':>5} {'win%':>5}") best = None for kind in ("diag", "condor", "vert"): for zz in (1.5, 2.0, 2.5, 3.0): for tt in (3, 5): r = book(kind, z=zz, dz=dz, tenor_d=tt, f_put=1.0, f_call=1.0, gated=True) ris = r[r.index < HOLDOUT] mm = metrics(r, tt); mi = metrics(ris, tt) pf = f"{mm['pf']:5.2f}" if np.isfinite(mm["pf"]) else " inf" act = r[r != 0.0] nloss = int((act < 0).sum()) tag = " <- video ~3.5σ" if zz == 3.0 and tt == 5 and kind == "diag" else "" if nloss == 0: tag += " ⚠️ 0 perdite su tutta la storia = coda MAI campionata (lezione CC01: Sharpe implausibile -> rischio nascosto)" print(f" {kind:<7} z={zz} dz={dz} tenor={tt}g{'':<14} {mi['sh']:>7.2f} {mm['sh_h']:>6.2f} " f"{mm['dd']*100:>5.1f}% {mm['worst']*100:>+6.2f}% {pf} {mm['win']*100:>4.0f}% n={len(act):>3}{tag}") if best is None or mi["sh"] > best[1]: best = ((kind, zz, tt), mi["sh"], mm["sh_h"]) print(f" -> cella best IN-SAMPLE: {best[0]} (ShF-IS {best[1]:.2f}) | suo hold-out ShH {best[2]:.2f}") print(" Le celle z>=2.5/5g vendono un evento ~1% mai occorso nel subsample gated (~140 trade):") print(" Sharpe 'inf/5.9' = premio senza coda osservata, NON edge. E' il punto cieco CC01 in forma opzioni.") # ------------------------------------------------------------------ (4) claim del video print(f"\n (4) TEST CLAIM VIDEO: la long a T+1g domina la long a STESSA T? (z={z}, dz={dz}, {tn}g, f=1.0)") for gated in (False, True): colD, colC = {}, {} rD = pd.concat({a[0]: run_structure(a, "diag", z=z, dz=dz, tenor_d=tn, gated=gated, collect=colD) for a in ("BTC", "ETH")}, axis=1, join="inner").mean(axis=1) rC = pd.concat({a[0]: run_structure(a, "condor", z=z, dz=dz, tenor_d=tn, gated=gated, collect=colC) for a in ("BTC", "ETH")}, axis=1, join="inner").mean(axis=1) mD, mC = metrics(rD, tn), metrics(rC, tn) ec = np.array(colD["extra_cost"]); rec = np.array(colD["recovered"]) sp = np.array(colD["short_pay"]); crd = np.array(colD["credit"]) crash = sp > np.quantile(sp, 0.95) gt = "GATE" if gated else "NO-GATE" print(f" [{gt}] DIAG ShF {mD['sh']:+.2f}/ShH {mD['sh_h']:+.2f} worst {mD['worst']*100:+.2f}% | " f"CONDOR ShF {mC['sh']:+.2f}/ShH {mC['sh_h']:+.2f} worst {mC['worst']*100:+.2f}%") print(f" costo extra T+1 a entry: {ec.mean()*1e4:+.1f} bps/trade | residuo recuperato a exit: " f"{rec.mean()*1e4:+.1f} bps (nei 5% peggiori: {rec[crash].mean()*1e4:+.1f} bps vs extra {ec[crash].mean()*1e4:+.1f})") print(f" trade a CREDITO netto: {(crd>0).mean()*100:.0f}% (credito medio {crd.mean()*1e4:+.1f} bps di S0)") dY = per_year(rD); cY = per_year(rC) print(" Δ(diag-condor) per anno: " + " ".join(f"{y}:{(dY[y]-cY.get(y,0))*100:+.2f}%" for y in sorted(dY))) # ------------------------------------------------------------------ (5) per-anno print(f"\n (5) PER-ANNO (gated, f=1.0) — 2022 = LUNA+FTX e' il banco di prova") for tag, r, tt in (("DIAG", streams[("diag", True, "f=1.0")], tn), ("CONDOR", streams[("condor", True, "f=1.0")], tn), ("VERT", streams[("vert", True, "f=1.0")], tn), ("VRP01", vrp[1.0], 7)): py = per_year(r) print(f" {tag:<7} " + " ".join(f"{y}:{v*100:+5.1f}%" for y, v in sorted(py.items()))) # ------------------------------------------------------------------ (6) marginale print(f"\n (6) MARGINALE vs TP01 e vs VRP01 (daily-lumped; corr su griglia settimanale)") import altlib as al tp = al.tp01_baseline_daily() dv_daily = to_daily_lumped(streams[("diag", True, "f=1.0")]) vr_daily = to_daily_lumped(vrp[1.0]) tp_w = (1 + tp).resample("W").prod() - 1 di_w = (1 + dv_daily).resample("W").prod() - 1 vr_w = (1 + vr_daily).resample("W").prod() - 1 Jw = pd.concat({"tp": tp_w, "di": di_w, "vr": vr_w}, axis=1, join="inner").dropna() print(f" corr settimanale: DIAG~TP01 {Jw['tp'].corr(Jw['di']):+.2f} | DIAG~VRP01 {Jw['di'].corr(Jw['vr']):+.2f} " f"| VRP01~TP01 {Jw['tp'].corr(Jw['vr']):+.2f}") for name, dd in (("DIAG gated f=1.0", dv_daily), ("VRP01 gated f=1.0 (riferimento)", vr_daily)): mv = al.marginal_vs_tp01(dd) print(f" marginal_vs_tp01[{name}]: verdict={mv.get('marginal_verdict')} corr={mv.get('corr_full')} " f"uplift w25 full/hold={mv['blends']['w25']['uplift_full']}/{mv['blends']['w25']['uplift_hold']} " f"IS-Sh={mv.get('cand_insample_sharpe')} insample_edge={mv.get('has_insample_edge')} " f"hedge={mv.get('is_hedge')} robust_oos={mv.get('robust_oos')} multicut={mv.get('multicut_uplift')}") print(" ⚠️ Un verdetto ADDS qui NON promuove: lo stream vende coda che nel subsample gated non ha mai") print(" colpito (hold-out Sh 3+ = assenza di eventi, non alpha) — vale la lezione CC01, e vale la regola") print(" standing 'niente short-vol da modello in deploy'.") # ------------------------------------------------------------------ (7) eseguibilita' print(f"\n (7) ESEGUIBILITA' DERIBIT (min 0.1 BTC / 1 ETH per gamba; diag = 4 gambe/asset, book = 8)") for a, minc in (("BTC", 0.1), ("ETH", 1.0)): J = load_series(a); S = float(J["px"].iloc[-1]); sig = float(J["dvol"].iloc[-1]) / 100.0 w = dz * sig * np.sqrt(tn / 365.25) # larghezza ala in frazione di S notional = minc * S maxloss = w * notional # margine ~ max loss defined-risk (per lato) col = {} run_structure(a, "diag", z=z, dz=dz, tenor_d=tn, gated=True, collect=col) cbps = np.mean(col["credit"]) * 1e4 if col.get("credit") else float("nan") print(f" {a}: spot ~${S:,.0f} -> notional min {minc} = ${notional:,.0f}/gamba | ala {w*100:.1f}% " f"-> margine/max-loss min ~${maxloss:,.0f} | credito tipico {cbps:+.0f} bps = ${notional*cbps/1e4:,.0f}/trade") print(" -> a $600: UN diagonale BTC min-size impegna >50% del capitale su un trade 5g = NON eseguibile.") print(" Scala minima: sleeve opzioni al ~12% con margine <= peso richiede >~$3-5k per il solo BTC") print(" min-size; book 50/50 con granularita' (>=3-5 step di size) ~= $15-25k. STAT-MODE, come VRP01.") print("\n NB ONESTO: win-rate alto e' STRUTTURALE nel deep-OTM (vendi eventi rari), non e' edge. Il verdetto") print(" sta in Sharpe/PF/coda attraverso la banda f e il 2022. Regola standing: niente short-vol da modello") print(" in deploy — esito massimo = aggiornamento di conoscenza sulla STRUTTURA.") if __name__ == "__main__": main()