"""r0909 — IL CROLLO CATTURATO: il `f` di stress delle opzioni, misurato su quote VERE. PERCHE' ESISTE. La decisione vincolante del 19/06 ("niente short-vol da modello in deploy", e la gemella "niente long-vol scalp") dichiara UNA condizione che la riapre: *un `f` di stress reale misurato su un crash catturato*. COLLAR01 (§71) e PAVIMENTO-LEVA (§72) chiudono con la stessa condizione. Il 22/08 (§11) il campione con quote vere era "la strategia mentre STA FERMA": 19 settimane di rally, "l'unico episodio ad alta vol del campione e' un rally, non un crash". FATTO NUOVO (2026-09-09): l'archivio ereditato `bite_archive.parquet` copre con bid E ask il crollo del 1-5 giugno 2026 — BTC −25% e ETH −30% in 5 giorni (peggior giorno ETH −10,6% il 05/06), su UNA scadenza (19 giugno, 14-21 DTE agli ingressi). Il campione del 22/08 partiva DOPO, dal fondo. Nessuno lo aveva usato: e' il crash catturato che la regola aspettava. COSA MISURA (a ogni ora d'ingresso dal 30/05 al 04/06, BTC e ETH — l'ora e' un'ancora, si riportano mediana e banda, mai il massimo): A. la geometria di VRP01 (vendi put δ−0,28, compra put δ−0,10, stessa scadenza) a quote REALI: credito al bid/ask, mark-to-market ora per ora dalle standing quotes, regolamento a scadenza sullo spot certificato. Contro lo STESSO trade prezzato dal modello del sleeve (BS su DVOL, `sleeves._bs_put`). f_stress = perdita reale / perdita modellata, a scadenza e al peggior MTM. B. lo specchio LONG (compra la put δ−0,10 all'ask; e il debit spread −0,28/−0,10): quanto del payoff modellato si incassa davvero al bid — a scadenza (uscita passiva) e al miglior punto del percorso (limite superiore con look-ahead, dichiarato). C. il contesto: DVOL, RV realizzata, IV-rank causale (il gate di VRP01 era aperto o chiuso?), e cosa dice il modello del sleeve (`_vrp_weekly_asset`) per le settimane di giugno. LIMITI DICHIARATI PRIMA DEI NUMERI: n = 1 crollo, 1 scadenza per asset, tenor 14-21 giorni (VRP01 e' a 7), 2-5 mila righe/giorno (40-125 strumenti/ora), `underlying_price` assente nell'archivio (spot dal feed 1h certificato, D1), regolamento approssimato allo spot dell'ora di scadenza (Deribit usa una TWAP di 30 min). La famiglia dell'archivio e' INVERSE (premi in BTC/ETH; `ETH-19JUN26-1900-P`): VRP01 opererebbe sulla USDC-lineare, che il conto puo' marginare e che NON e' nell'archivio (§11) — il f qui misurato e' della famiglia che il conto non negozia. Un episodio e' un episodio: da' la TAGLIA di f nello stress, non la sua distribuzione. DUE CORREZIONI DELLA REVISIONE (fable, 09/09), applicate: * il feed 1h e' etichettato all'APERTURA (la barra 20:00 chiude alle 21:00): `S.asof(ts)` guardava un'ora AVANTI. Qui lo spot e' `spot_causale` = chiusura dell'ultima barra chiusa a ts. ⚠️ `cblib.spot_series` ha lo stesso difetto (e quindi il f 0,714 di §11): registrato, non toccato. * il costo di chiusura `(ask corta − bid lunga)·spot` puo' superare la LARGHEZZA dello spread (una quota da 158% di spread relativo a T→0): uno spread a rischio definito non costa mai piu' della larghezza per chiudere (si lascia regolare). Il MTM e' tagliato alla larghezza e le ore tagliate sono contate. Senza il taglio il "peggior MTM" era un artefatto di quote (2,6×). * `f_exp` e' TAUTOLOGICO quando entrambe le gambe finiscono ITM (payoff = larghezza per reale e modello): si riduce a una funzione di f_net. Si stampa la quota di trade in cui succede. Nessun file di produzione toccato. Nessun ordine. """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "scripts" / "research")) import cblib as CB # noqa: E402 from src.portfolio.sleeves import VRP_CFG, _vrp_weekly_asset # noqa: E402 ARCHIVIO = ROOT / "data" / "raw" / "cb_chain" / "bite_archive.parquet" ASSETS = ("BTC", "ETH") EXP = pd.Timestamp("2026-06-19 08:00", tz="UTC") INGRESSI = (pd.Timestamp("2026-05-30", tz="UTC"), pd.Timestamp("2026-06-05", tz="UTC")) # [da, a) CROLLO = (pd.Timestamp("2026-06-01", tz="UTC"), pd.Timestamp("2026-06-06", tz="UTC")) PRE = pd.Timestamp("2026-06-02", tz="UTC") # ingressi "prima del crollo" = prima del primo giorno a −7%/−8% (il 01/06 BTC fece −3,1%: e' DENTRO "pre", dichiarato) TOL_D = (0.08, 0.06) # geometria VRP01: |δ corta + 0,28| ≤ 0,08 e |δ lunga + 0,10| ≤ 0,06, altrimenti "fuori geometria" SHORT_D, LONG_D = VRP_CFG["short_delta"], VRP_CFG["long_delta"] # P1: dal sleeve, non ridichiarati FEE_FRAC = VRP_CFG["fee_frac"] # listino Deribit opzioni (stesse costanti di r0822_vrp_real_quotes, verificate al venue il 22/08) FEE_UNDERLYING, FEE_PREMIUM_CAP, FEE_DELIVERY = 0.0003, 0.125, 0.00015 def leg_fee(prem_base: float, spot: float) -> float: return min(FEE_UNDERLYING * spot, FEE_PREMIUM_CAP * prem_base * spot) def spot_causale(S: pd.Series) -> pd.Series: """Il feed 1h e' etichettato all'apertura: la chiusura della barra T e' nota a T+1h. Spostando l'indice di +1h, `asof(ts)` restituisce l'ultima chiusura CONOSCIUTA a ts, mai una futura.""" out = S.copy(); out.index = out.index + pd.Timedelta(hours=1) return out def carica() -> pd.DataFrame: a = pd.read_parquet(ARCHIVIO) a["ts"] = pd.to_datetime(a["ts"], utc=True) a["exp"] = pd.to_datetime(a["exp"], utc=True) a = a[(a["option_type"] == "P") & (a["exp"] == EXP) & (a["ts"] >= INGRESSI[0]) & (a["ts"] <= EXP)] a = a.drop_duplicates(subset=["ts", "instrument_name"], keep="last") a["dte"] = (a["exp"] - a["ts"]).dt.total_seconds() / 86400.0 return a.reset_index(drop=True) def bs_spread(S: float, Ks: float, Kl: float, T: float, sig: float) -> float: return CB.bs_put(S, Ks, T, sig) - CB.bs_put(S, Kl, T, sig) def bs_put_vec(S, K, T, sig): """`cblib.bs_put` vettorizzata (stessa formula, r=0); T≤0 → intrinseco.""" from scipy.stats import norm S = np.asarray(S, float); T = np.asarray(T, float); sig = np.asarray(sig, float) out = np.maximum(K - S, 0.0) ok = (T > 0) & (sig > 0) d1 = (np.log(S[ok] / K) + 0.5 * sig[ok] ** 2 * T[ok]) / (sig[ok] * np.sqrt(T[ok])) out[ok] = K * norm.cdf(-(d1 - sig[ok] * np.sqrt(T[ok]))) - S[ok] * norm.cdf(-d1) return out class Quote: """Tabella ora × strumento (bid, ask, iv) costruita UNA volta per asset, con spot e DVOL per ora.""" def __init__(self, seg: pd.DataFrame, S: pd.Series, V: pd.Series): q = seg.dropna(subset=["bid", "ask"]); q = q[(q["bid"] > 0) & (q["ask"] > 0)] self.bid = q.pivot_table(index="ts", columns="instrument_name", values="bid") self.ask = q.pivot_table(index="ts", columns="instrument_name", values="ask") self.iv = q.pivot_table(index="ts", columns="instrument_name", values="iv") idx = self.bid.index self.spot = pd.Series([float(S.asof(t)) for t in idx], index=idx) self.dvol = pd.Series([float(V.asof(t)) for t in idx], index=idx) / 100.0 self.T = pd.Series(np.maximum((EXP - idx).total_seconds().values / 86400.0, 0.0) / 365.25, index=idx) def percorso(Q: Quote, dopo: pd.Timestamp, inst_s: str, inst_l: str, Ks: float, Kl: float) -> pd.DataFrame: """Ora per ora dopo l'ingresso: costo REALE di chiudere lo short spread (ricompra la corta all'ask, rivende la lunga al bid), valore REALE di liquidare il debit spread (vende la corta al bid, ricompra la lunga all'ask) e la put lunga (bid); piu' il valore MODELLATO (BS a DVOL_t).""" if inst_s not in Q.bid.columns or inst_l not in Q.bid.columns: return pd.DataFrame() m = (Q.bid.index > dopo) & Q.bid[inst_s].notna() & Q.bid[inst_l].notna() if not m.any(): return pd.DataFrame() idx = Q.bid.index[m] sp = Q.spot.loc[idx].values; dv = Q.dvol.loc[idx].values; T = Q.T.loc[idx].values bs_, as_ = Q.bid.loc[idx, inst_s].values, Q.ask.loc[idx, inst_s].values bl, al = Q.bid.loc[idx, inst_l].values, Q.ask.loc[idx, inst_l].values mod_s, mod_l = bs_put_vec(sp, Ks, T, dv), bs_put_vec(sp, Kl, T, dv) grezzo = (as_ - bl) * sp return pd.DataFrame(dict(ts=idx, spot=sp, dvol=dv, chiudi_short=np.minimum(grezzo, Ks - Kl), chiudi_grezzo=grezzo, liquida_long=np.clip((bs_ - al) * sp, 0.0, Ks - Kl), liquida_put=bl * sp, mod_spread=mod_s - mod_l, mod_put=mod_l, iv_s=Q.iv.loc[idx, inst_s].values, iv_l=Q.iv.loc[idx, inst_l].values)) def trade(asset: str, ts: pd.Timestamp, snap: pd.DataFrame, Q: Quote, S: pd.Series, V: pd.Series) -> dict | None: legs = CB.pick_legs(snap, SHORT_D, LONG_D) if legs is None: return None spot = float(S.asof(ts)); dvol = float(V.asof(ts)); T = (EXP - ts).total_seconds() / 86400.0 / 365.25 hist = V[V.index < ts]; ivr = float((hist < dvol).mean()) if len(hist) else np.nan Ks, Kl = legs["k_short"], legs["k_long"] # --- A. short spread (VRP01) — reale al bid/ask, modello a DVOL cred = (legs["bid_short"] - legs["ask_long"]) * spot if cred <= 0: return None cred_mod = bs_spread(spot, Ks, Kl, T, dvol / 100.0) ST = float(S.asof(EXP)) payoff = max(Ks - ST, 0.0) - max(Kl - ST, 0.0) fee_in = leg_fee(legs["bid_short"], spot) + leg_fee(legs["ask_long"], spot) fee_out = sum(min(FEE_DELIVERY * ST, FEE_PREMIUM_CAP * max(K - ST, 0.0)) for K in (Ks, Kl) if K > ST) pnl_real = cred - payoff - fee_in - fee_out pnl_mod = cred_mod - payoff - FEE_FRAC * cred_mod p = percorso(Q, ts, legs["inst_short"], legs["inst_long"], Ks, Kl) if p.empty: return None mtm_real = cred - p["chiudi_short"] # P&L aperto se si chiude ORA a quote vere (tagliato alla larghezza) mtm_mod = cred_mod - p["mod_spread"] # lo stesso trade riprezzato a BS/DVOL_t (costruzione nostra: il sleeve tiene a scadenza) i_min = int(mtm_real.idxmin()) n_tagli = int((p["chiudi_grezzo"] > Ks - Kl).sum()) # --- B. specchio long: put δ−0,10 comprata all'ask; debit spread −0,28/−0,10 costo_put = legs["ask_long"] * spot mod_put = CB.bs_put(spot, Kl, T, dvol / 100.0) costo_deb = (legs["ask_short"] - legs["bid_long"]) * spot mod_deb = cred_mod pay_put_exp = max(Kl - ST, 0.0) pay_deb_exp = payoff # il picco del percorso e' un limite SUPERIORE con look-ahead: si dichiara, non si vende i_put = int(p["liquida_put"].idxmax()); i_deb = int(p["liquida_long"].idxmax()) risk = (Ks - Kl) - cred in_geom = abs(legs["d_short"] - SHORT_D) <= TOL_D[0] and abs(legs["d_long"] - LONG_D) <= TOL_D[1] return dict( in_geom=bool(in_geom), asset=asset, ts=ts, dte=(EXP - ts).total_seconds() / 86400.0, spot=spot, dvol=dvol, ivrank=ivr, k_short=Ks, k_long=Kl, d_short=legs["d_short"], d_long=legs["d_long"], width=Ks - Kl, iv_s0=legs["iv_short"], iv_l0=legs["iv_long"], ST=ST, und_ret=ST / spot - 1.0, cred_real=cred, cred_mod=cred_mod, f_net=cred / cred_mod if cred_mod > 0 else np.nan, risk=risk, pnl_real=pnl_real, pnl_mod=pnl_mod, perdita_su_risk=-pnl_real / risk, perdita_su_coll=-pnl_real / Ks, f_exp=(-pnl_real / -pnl_mod) if pnl_mod < 0 and pnl_real < 0 else np.nan, mtm_real_min=float(mtm_real.min()), mtm_mod_min=float(mtm_mod.min()), mtm_min_ts=p["ts"].iloc[i_min], n_tagli=n_tagli, n_ore=int(len(p)), iv_s_at_min=float(p["iv_s"].iloc[i_min]), iv_l_at_min=float(p["iv_l"].iloc[i_min]), dvol_at_min=float(p["dvol"].iloc[i_min]) * 100, itm_pieno=bool(ST <= Kl), # entrambe le gambe ITM: payoff = larghezza, f_exp si riduce a f_net f_mtm=(mtm_real.min() / mtm_mod.min()) if mtm_mod.min() < 0 and mtm_real.min() < 0 else np.nan, spot_min=float(p["spot"].min()), iv_s_max=float(p["iv_s"].max()), iv_l_max=float(p["iv_l"].max()), n_ore_percorso=int(len(p)), # long costo_put=costo_put, mod_put=mod_put, f_put_in=costo_put / mod_put if mod_put > 0 else np.nan, put_exp=pay_put_exp - costo_put, put_exp_mod=pay_put_exp - mod_put, put_best=float(p["liquida_put"].iloc[i_put]) - costo_put, put_best_mod=float(p["mod_put"].iloc[i_put]) - mod_put, put_best_ts=p["ts"].iloc[i_put], bleed_put=costo_put / spot, costo_deb=costo_deb, mod_deb=mod_deb, deb_exp=pay_deb_exp - costo_deb, deb_exp_mod=pay_deb_exp - mod_deb, deb_best=float(p["liquida_long"].iloc[i_deb]) - costo_deb, deb_best_mod=float(p["mod_spread"].iloc[i_deb]) - mod_deb, ) def misura() -> dict: a = carica() out = {"trades": {}, "contesto": {}, "sleeve": {}} for asset in ASSETS: seg = a[a["asset"] == asset] S, V = spot_causale(CB.spot_series(asset)), CB.dvol_series(asset) Q = Quote(seg, S, V) rows = [] for ts in sorted(seg["ts"].unique()): ts = pd.Timestamp(ts) if not (INGRESSI[0] <= ts < INGRESSI[1]): continue t = trade(asset, ts, seg[seg["ts"] == ts], Q, S, V) if t: rows.append(t) out["trades"][asset] = pd.DataFrame(rows) # contesto: DVOL, RV5 e RV1 sul crollo px = S[(S.index >= pd.Timestamp("2026-05-20", tz="UTC")) & (S.index <= EXP)] pd_ = px.resample("1D").last().dropna() # chiusura del giorno = ultima chiusura nota entro le 24:00 hist_all = V[V.index < CROLLO[0]] lr = np.log(pd_).diff() rv5 = lr.rolling(5).std() * np.sqrt(365.25) * 100 dv = V[(V.index >= pd.Timestamp("2026-05-20", tz="UTC")) & (V.index <= EXP)] hist = V[V.index < CROLLO[0]] out["contesto"][asset] = dict( spot=pd_, ret=lr, rv5=rv5, dvol=dv, dvol_max_crollo=float(dv[(dv.index >= CROLLO[0]) & (dv.index <= CROLLO[1])].max()), dvol_pre=float(V.asof(CROLLO[0])), pctl_picco=float((hist_all < V[(V.index >= CROLLO[0]) & (V.index <= CROLLO[1])].max()).mean()), ivr_pre=float((hist < V.asof(CROLLO[0])).mean()), rv5_max=float(rv5[(rv5.index >= CROLLO[0]) & (rv5.index <= CROLLO[1] + pd.Timedelta(days=3))].max()), caduta=float(pd_[CROLLO[0]:CROLLO[1]].min() / pd_[:CROLLO[0]].iloc[-1] - 1.0), ) wk = _vrp_weekly_asset(asset) out["sleeve"][asset] = wk[(wk.index >= pd.Timestamp("2026-05-10", tz="UTC")) & (wk.index <= pd.Timestamp("2026-07-05", tz="UTC"))] return out def sintesi(m: dict) -> dict: s = {} for asset in ASSETS: t = m["trades"][asset] if t.empty: s[asset] = None; continue n_fuori = int((~t["in_geom"]).sum()) t = t[t["in_geom"]] # la griglia di strike dell'archivio e' rada: fuori geometria = altra struttura if t.empty: s[asset] = dict(n=0, n_fuori=n_fuori); continue pre = t[t["ts"] < PRE] # ingressi PRIMA del crollo: il caso della regola dur = t[(t["ts"] >= PRE) & (t["ts"] < CROLLO[1] - pd.Timedelta(days=1))] # DURANTE (02-04/06): lente secondaria med = lambda c, d=t: float(np.nanmedian(d[c])) if len(d) else np.nan b = lambda c, d=t: (float(np.nanpercentile(d[c], 10)), float(np.nanpercentile(d[c], 90))) if len(d) else (np.nan, np.nan) s[asset] = dict( n=int(len(t)), n_pre=int(len(pre)), n_fuori=n_fuori, d_short=med("d_short", pre), d_long=med("d_long", pre), width=med("width", pre), ST=float(t["ST"].iloc[0]), spot_pre=med("spot", pre), dte=(float(t["dte"].min()), float(t["dte"].max())), f_net=med("f_net"), f_net_b=b("f_net"), ivrank=(float(t["ivrank"].min()), float(t["ivrank"].max())), gate_aperto=float((t["ivrank"] >= VRP_CFG["gate_ivr"]).mean()), perdita_su_risk=med("perdita_su_risk", pre), perdita_su_risk_b=b("perdita_su_risk", pre), perdita_su_coll=med("perdita_su_coll", pre), pnl_real=med("pnl_real", pre), pnl_mod=med("pnl_mod", pre), f_exp=med("f_exp", pre), f_exp_b=b("f_exp", pre), n_f_exp=int(pre["f_exp"].notna().sum()), f_mtm=med("f_mtm", pre), f_mtm_b=b("f_mtm", pre), mtm_real_min=med("mtm_real_min", pre), mtm_mod_min=med("mtm_mod_min", pre), n_tagli=int(pre["n_tagli"].sum()) if len(pre) else 0, n_ore=int(pre["n_ore"].sum()) if len(pre) else 0, iv_s_at_min=med("iv_s_at_min", pre), dvol_at_min=med("dvol_at_min", pre), q_itm_pieno=float(pre["itm_pieno"].mean()) if len(pre) else np.nan, n_strutture=int(pre.groupby(["k_short", "k_long"]).ngroups) if len(pre) else 0, f_put_in=med("f_put_in", pre), bleed_put=med("bleed_put", pre), put_exp=med("put_exp", pre), put_exp_mod=med("put_exp_mod", pre), put_best=med("put_best", pre), put_best_mod=med("put_best_mod", pre), f_put_exp=med_ratio(pre, "put_exp", "put_exp_mod"), f_put_best=med_ratio(pre, "put_best", "put_best_mod"), deb_exp=med("deb_exp", pre), deb_exp_mod=med("deb_exp_mod", pre), deb_best=med("deb_best", pre), deb_best_mod=med("deb_best_mod", pre), f_deb_exp=med_ratio(pre, "deb_exp", "deb_exp_mod"), f_deb_best=med_ratio(pre, "deb_best", "deb_best_mod"), iv_s0=med("iv_s0", pre), iv_s_max=med("iv_s_max", pre), iv_l0=med("iv_l0", pre), iv_l_max=med("iv_l_max", pre), und_ret=med("und_ret", pre), spot_min=med("spot_min", pre), durante=dict(n=int(len(dur)), f_exp=med("f_exp", dur), f_mtm=med("f_mtm", dur), perdita_su_risk=med("perdita_su_risk", dur), f_net=med("f_net", dur), q_perde=float((dur["pnl_real"] < 0).mean()) if len(dur) else np.nan, pnl_p10=float(np.nanpercentile(dur["pnl_real"], 10)) if len(dur) else np.nan, pnl_p90=float(np.nanpercentile(dur["pnl_real"], 90)) if len(dur) else np.nan, d_short=med("d_short", dur), d_long=med("d_long", dur), width=med("width", dur), pnl_real=med("pnl_real", dur), pnl_mod=med("pnl_mod", dur)), ) return s def med_ratio(d: pd.DataFrame, a: str, b: str, minimo: float = 5.0) -> float: """Mediana del rapporto reale/modello sulle righe in cui il modello vale almeno `minimo` $ (un payoff modellato ≈ 0 non da' un rapporto, da' un'altra domanda: si stampano i dollari).""" x = d[(d[b] >= minimo)] return float(np.nanmedian(x[a] / x[b])) if len(x) else np.nan def verdetto(s: dict) -> str: parti = [] for asset in ASSETS: r = s.get(asset) if not r or r.get("n", 0) == 0: parti.append(f"{asset}: NON misurabile — {r.get('n_fuori', 0) if r else 0} snapshot tutti FUORI geometria (griglia di strike rada)"); continue if not np.isfinite(r["f_exp"]): parti.append(f"{asset}: nessun ingresso pre-crollo in geometria (solo DURANTE: f_net {r['durante']['f_net']:.2f}, perde il {r['durante']['q_perde']*100:.0f}%)"); continue parti.append(f"{asset}: f_net {r['f_net']:.2f} (22/08 in rally 0,714); a scadenza f {r['f_exp']:.2f} ma TAUTOLOGICO nel " f"{r['q_itm_pieno']*100:.0f}% dei trade (entrambe le gambe ITM ⇒ dipende solo da f_net; {r['n_strutture']} strutture, non {r['n_pre']} osservazioni); " f"peggior MTM tagliato alla larghezza f {r['f_mtm']:.2f} (quote oltre la larghezza nel {r['n_tagli']/max(1, r['n_ore'])*100:.1f}% delle ore); " f"gate IV-rank {'APERTO' if r['gate_aperto'] > 0.5 else 'CHIUSO'} nel {r['gate_aperto']*100:.0f}% delle ore") esito = ("IL CROLLO E' CATTURATO MA A VOL BASSA E FUORI DAL GATE DI VRP01: il f di stress che la regola del 19/06 aspettava " "NON e' misurato nella sostanza — cio' che e' misurato e' che il rapporto di credito f_net non cambia col regime") return esito + " — " + "; ".join(parti) + " — n = 1 crollo, 1 scadenza per asset, famiglia INVERSE" def pct(x): return f"{x*100:+.1f}%" def main() -> None: print("=" * 104) print(" r0909 — IL CROLLO CATTURATO (1-5 giugno 2026): f di stress su quote VERE, scadenza 19/06") print("=" * 104) m = misura(); s = sintesi(m) for asset in ASSETS: c = m["contesto"][asset]; r = s[asset] print(f"\n [{asset}] CONTESTO — caduta {pct(c['caduta'])} dal 31/05 al minimo di 1-5/06; " f"DVOL pre {c['dvol_pre']:.1f} (IV-rank causale {c['ivr_pre']:.2f}) → max {c['dvol_max_crollo']:.1f} " f"(= {c['pctl_picco']*100:.0f}° percentile storico: il PICCO del crollo sta sotto la mediana); RV5 max {c['rv5_max']:.0f}") print(" giorno spot ret DVOL RV5") for d in pd.date_range("2026-05-28", "2026-06-08", tz="UTC"): if d in c["spot"].index: dv = c["dvol"].asof(d) print(f" {d.date()} {c['spot'][d]:>8.0f} {pct(c['ret'].get(d, np.nan)):>7} {dv:>5.1f} {c['rv5'].get(d, np.nan):>5.0f}") if r is None or r.get("n", 0) == 0: print(f" nessun trade in geometria VRP01: {r.get('n_fuori', 0) if r else 0} snapshot fuori geometria " f"(la griglia di strike dell'archivio in quei giorni non ha le δ−0,28/−0,10)"); continue t = m["trades"][asset]; t = t[t["in_geom"]] print(f"\n [{asset}] A. VRP01 a quote vere — {r['n']} snapshot d'ingresso in geometria ({r['n_pre']} prima del {PRE.date()}; " f"{r['n_fuori']} scartati fuori geometria), DTE {r['dte'][0]:.0f}-{r['dte'][1]:.0f}, IV-rank {r['ivrank'][0]:.2f}-{r['ivrank'][1]:.2f}") print(f" geometria mediana pre-crollo: δ corta {r['d_short']:+.2f} / δ lunga {r['d_long']:+.2f}, larghezza {r['width']:.0f}, " f"spot d'ingresso {r['spot_pre']:.0f}, regolamento {EXP.date()} a {r['ST']:.0f}") print(f" credito reale/modello f_net {r['f_net']:.2f} [{r['f_net_b'][0]:.2f}, {r['f_net_b'][1]:.2f}] " f"(22/08: 0,714 in rally)") print(f" ingressi PRE-crollo, mediana: P&L reale ${r['pnl_real']:+.0f} vs modello ${r['pnl_mod']:+.0f} per unita' " f"→ perdita {r['perdita_su_risk']*100:.0f}% del rischio [{r['perdita_su_risk_b'][0]*100:.0f}, {r['perdita_su_risk_b'][1]*100:.0f}] " f"(100% = perdita MASSIMA dello spread; oltre 100 sono le fee), " f"{r['perdita_su_coll']*100:.1f}% del collaterale") print(f" f a SCADENZA {r['f_exp']:.2f} [{r['f_exp_b'][0]:.2f}, {r['f_exp_b'][1]:.2f}] su {r['n_f_exp']} snapshot = {r['n_strutture']} strutture " f"— TAUTOLOGICO nel {r['q_itm_pieno']*100:.0f}% (entrambe le gambe ITM: payoff = larghezza per reale e modello, resta solo f_net)") print(f" peggior MTM (chiusura tagliata alla larghezza; quote oltre la larghezza nel {r['n_tagli']/max(1, r['n_ore'])*100:.1f}% delle ore-percorso): " f"f {r['f_mtm']:.2f} [{r['f_mtm_b'][0]:.2f}, {r['f_mtm_b'][1]:.2f}] (reale ${r['mtm_real_min']:+.0f} / modello ${r['mtm_mod_min']:+.0f}); " f"a quell'ora IV corta {r['iv_s_at_min']:.0f} vs DVOL {r['dvol_at_min']:.0f}") print(f" IV gamba corta {r['iv_s0']:.0f} → max {r['iv_s_max']:.0f} (il max e' a T→0, non nel crollo); gamba lunga {r['iv_l0']:.0f} → max {r['iv_l_max']:.0f}; " f"sottostante a scadenza {pct(r['und_ret'])}, minimo {r['spot_min']:.0f}") du = r["durante"] print(f" lente secondaria — ingressi DURANTE il crollo (02-04/06, {du['n']} snapshot): δ {du['d_short']:+.2f}/{du['d_long']:+.2f}, " f"larghezza {du['width']:.0f}, f_net {du['f_net']:.2f}, P&L reale ${du['pnl_real']:+.0f} vs modello ${du['pnl_mod']:+.0f}, " f"perde nel {du['q_perde']*100:.0f}% (p10 ${du['pnl_p10']:+.0f} / p90 ${du['pnl_p90']:+.0f}: bimodale, la mediana nasconde le perdite), " f"f scadenza {du['f_exp']:.2f} / peggior MTM {du['f_mtm']:.2f}") print(f"\n [{asset}] B. LO SPECCHIO LONG (ingressi PRE-crollo, mediana per unita')") print(f" put δ−0,10 all'ask: costo reale/modello {r['f_put_in']:.2f}, bleed {r['bleed_put']*100:.2f}% dello spot") print(f" a scadenza: reale ${r['put_exp']:+.0f} vs modello ${r['put_exp_mod']:+.0f} → f_payoff {r['f_put_exp']:.2f}") print(f" al PICCO del percorso (look-ahead, limite sup.): reale ${r['put_best']:+.0f} vs modello ${r['put_best_mod']:+.0f} → f {r['f_put_best']:.2f}") print(f" debit spread −0,28/−0,10: a scadenza reale ${r['deb_exp']:+.0f} vs modello ${r['deb_exp_mod']:+.0f} → f {r['f_deb_exp']:.2f}; " f"al picco ${r['deb_best']:+.0f} vs ${r['deb_best_mod']:+.0f} → f {r['f_deb_best']:.2f}") print(f"\n [{asset}] C. IL MODELLO DEL SLEEVE (`_vrp_weekly_asset`, 0.0 = settimana saltata dai gate) — rendimento settimanale su collaterale:") print(" " + " ".join(f"{d.date()} {v*100:+.2f}%" for d, v in m["sleeve"][asset].items())) print(f"\n snapshot d'ingresso (primi 6): " + " | ".join( f"{row.ts.strftime('%m-%d %H')}h K{row.k_short:.0f}/{row.k_long:.0f} cred ${row.cred_real:.0f} pnl ${row.pnl_real:+.0f} mtm ${row.mtm_real_min:+.0f}" for row in t.head(6).itertuples())) print("\n VERDETTO: " + verdetto(s)) print("=" * 104) if __name__ == "__main__": main()