From 53d0134cb150b61014a7f4bbfcabd84cb0a30718 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Fri, 19 Jun 2026 20:38:54 +0000 Subject: [PATCH] =?UTF-8?q?research:=20calibra=20VRP=20su=20quote=20REALI?= =?UTF-8?q?=20cerbero-bite=20=E2=80=94=20f=E2=89=881.0=20(non=201.29),=20l?= =?UTF-8?q?ead=20DEBOLE=20confermato=20non-deploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cerbero-bite GIA' accumula la catena reale mainnet (option_chain_snapshots, 2026-05->oggi) -> uso quella (niente nuovo snapshotter). options_vrp_calibrate.py misura il fattore f reale su 223 snapshot/asset (put weekly delta-0.28, BID): BTC f median 1.03, ETH 0.97, skew reale +1.5..1.9 pt. Il f reale e' ~1.0 NON 1.29 (lo snapshot singolo del branch era outlier ad alto skew). -> VRP sleeve = punto f≈1.0 = Sharpe ~0.71 (conservativo), DD 33%, hold-out piatto: diversificatore DEBOLE (corr +0.07) sotto TP01, coda severa. Calibrazione su ~10g densi, 1 regime calmo; f di stress non misurato. Verdetto: la decorrelazione modesta NON giustifica il rischio di coda short-vol senza dato reale multi-regime (serve che cerbero-bite copra un crash). Confermato NON-deploy. Portafoglio invariato TP01 70% + XS01 30%. Diario 2026-06-19-options-vrp-lab.md aggiornato. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/diary/2026-06-19-options-vrp-lab.md | 21 ++++++ scripts/research/options_vrp_calibrate.py | 83 +++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 scripts/research/options_vrp_calibrate.py diff --git a/docs/diary/2026-06-19-options-vrp-lab.md b/docs/diary/2026-06-19-options-vrp-lab.md index e97f2fb..e34bc1e 100644 --- a/docs/diary/2026-06-19-options-vrp-lab.md +++ b/docs/diary/2026-06-19-options-vrp-lab.md @@ -51,6 +51,27 @@ migliora il portafoglio per pura decorrelazione. - Regola del progetto: **mai deployare uno short-vol prezzato da un modello.** → NON aggiunto al portafoglio. Portafoglio attivo invariato: TP01 70% + XS01 30%. +## CALIBRAZIONE su quote REALI cerbero-bite (`options_vrp_calibrate.py`) — corregge l'ottimismo +cerbero-bite GIA' accumula la catena reale mainnet (option_chain_snapshots, BTC 224k / ETH 237k +righe, 2026-05→oggi). Usandola (non un nuovo snapshotter), misurato il fattore f reale su 223 +snapshot/asset (put weekly ~delta -0.28, vendita al BID): +- **BTC: f mediano 1.03** (IQR 0.89-1.21), skew reale **+1.9 pt** (IV put 43.5% vs DVOL 41.6%). +- **ETH: f mediano 0.97** (IQR 0.88-1.11), skew **+1.5 pt**. +- **Il f reale e' ~1.0, NON 1.29.** Lo snapshot singolo del branch (skew +4.8 → f 1.29) era un + OUTLIER; sulla media lo skew e' modesto e il bid/ask lo compensa → premio reale ≈ modellato. +→ Il VRP sleeve sta sul punto **f≈1.0 dello sweep = Sharpe ~0.71** (caso CONSERVATIVO), DD 33%, +hold-out ~piatto (0.04). Non il 1.70 ottimistico. Resta un diversificatore modesto (corr +0.07, +migliora il portafoglio settimanale 0.71→0.97 a 30%), ma standalone SOTTO TP01 e con coda severa. +**CAVEAT:** la finestra di calibrazione reale e' ~10 giorni densi (06-09→06-19, cerbero-bite ruota +le scadenze → i weekly compaiono sparsi) e UN regime calmo. Il f di STRESS resta non misurato. + +## Verdetto aggiornato +Al premio REALE (f≈1.0), il VRP sleeve e' un diversificatore DEBOLE (Sharpe ~0.71 < TP01, DD 33%, +hold-out piatto): la modesta decorrelazione NON giustifica il rischio di coda short-vol senza molto +piu' dato reale multi-regime. **Confermato NON-deploy.** Il valore vero arriva solo se cerbero-bite, +continuando ad accumulare, copre un CRASH: lì si misura il f reale di stress e si fa un backtest su +catena reale. Fino ad allora, lead quantificato ma in attesa. Portafoglio invariato TP01 70%+XS01 30%. + ## Prossimi passi per graduare il lead a sleeve deployabile 1. **Accumulo forward di quote reali** (bid/ask + skew della put settimanale delta-0.28, ogni giorno, su più regimi) → sostituire il premio modellato con quello reale e misurare f nello stress. diff --git a/scripts/research/options_vrp_calibrate.py b/scripts/research/options_vrp_calibrate.py new file mode 100644 index 0000000..f5ebae8 --- /dev/null +++ b/scripts/research/options_vrp_calibrate.py @@ -0,0 +1,83 @@ +"""CALIBRAZIONE VRP su quote REALI cerbero-bite — misura f e skew, non li assume. + +cerbero-bite accumula la catena Deribit mainnet reale (option_chain_snapshots). Qui, per ogni +snapshot, prendo la put piu' vicina a delta -0.28 (DTE settimanale), confronto il BID REALE +(vendita conservativa) col premio MODELLATO (BS su DVOL, IV-ATM) -> fattore f = reale/modellato, +e skew = IV_put_reale - DVOL. Pinna empiricamente dove sta il VRP sleeve sullo sweep f. + +Input: /tmp/cb_puts.csv (export da cerbero-bite). Finestra ~2026-05 -> oggi (un regime, mainnet). + + uv run python scripts/research/options_vrp_calibrate.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)) +import numpy as np, pandas as pd +from scripts.research.options_vrp_lab import bs_put +from scripts.analysis.research_lab import load_tf + +CSV = "/tmp/cb_puts.csv" + + +def spot_series(asset): + px = load_tf(asset, "1h") + return pd.Series(px["close"].values.astype(float), + index=pd.to_datetime(px["timestamp"], unit="ms", utc=True)).sort_index() + + +def dvol_series(asset): + d = pd.read_parquet(PROJECT_ROOT / "data" / "raw" / f"dvol_{asset.lower()}.parquet") + return pd.Series(d["close"].values.astype(float), + index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)).sort_index() + + +def main(): + df = pd.read_csv(CSV, names=["ts", "asset", "strike", "expiry", "bid", "mid", "iv", "delta"]) + df["ts"] = pd.to_datetime(df["ts"], utc=True, errors="coerce") + df["expiry"] = pd.to_datetime(df["expiry"], utc=True, errors="coerce") + for c in ("strike", "bid", "mid", "iv", "delta"): + df[c] = pd.to_numeric(df[c], errors="coerce") + df = df.dropna(subset=["ts", "expiry", "strike", "bid", "iv", "delta"]) + df["dte"] = (df["expiry"] - df["ts"]).dt.total_seconds() / 86400.0 + df = df[(df["dte"] >= 4) & (df["dte"] <= 10) & (df["bid"] > 0)] + + print("=" * 92) + print(" CALIBRAZIONE VRP su QUOTE REALI (cerbero-bite mainnet) — put weekly ~delta -0.28") + print("=" * 92) + for asset in ("BTC", "ETH"): + d = df[df["asset"] == asset].copy() + if d.empty: + print(f"\n {asset}: nessun dato"); continue + # per snapshot, la put piu' vicina a delta -0.28 + d["dd"] = (d["delta"] - (-0.28)).abs() + pick = d.sort_values("dd").groupby("ts").first().reset_index().sort_values("ts") + S = spot_series(asset); V = dvol_series(asset) + Sdf = pd.DataFrame({"ts": S.index.as_unit("ns"), "spot": S.values}).sort_values("ts") + Vdf = pd.DataFrame({"ts": V.index.as_unit("ns"), "dvol": V.values}).sort_values("ts") + pick = pick.sort_values("ts").reset_index(drop=True) + pts = pick[["ts"]].copy() + pts["ts"] = pts["ts"].dt.as_unit("ns") + pick["spot"] = pd.merge_asof(pts, Sdf, on="ts", direction="nearest", tolerance=pd.Timedelta("2h"))["spot"].values + pick["dvol"] = pd.merge_asof(pts, Vdf, on="ts", direction="nearest", tolerance=pd.Timedelta("2D"))["dvol"].values + pick = pick.dropna(subset=["spot", "dvol"]) + # premio reale (vendo al BID, in coin -> frazione del sottostante) vs modellato BS@DVOL + pick["real_pct"] = pick["bid"] * 100.0 + pick["model_pct"] = pick.apply(lambda r: bs_put(r["spot"], r["strike"], r["dte"] / 365.25, r["dvol"] / 100.0) / r["spot"] * 100.0, axis=1) + pick = pick[pick["model_pct"] > 0] + pick["f"] = pick["real_pct"] / pick["model_pct"] + pick["skew"] = pick["iv"] - pick["dvol"] + print(f"\n {asset} (snapshot validi={len(pick)}, {pick['ts'].iloc[0].date()} -> {pick['ts'].iloc[-1].date()})") + print(f" delta medio {pick['delta'].mean():+.2f} | DTE medio {pick['dte'].mean():.1f}g | moneyness medio {(pick['strike']/pick['spot']).mean():.3f}") + print(f" IV put reale {pick['iv'].mean():.1f}% vs DVOL {pick['dvol'].mean():.1f}% -> SKEW medio {pick['skew'].mean():+.1f} pt") + print(f" premio reale(BID) {pick['real_pct'].mean():.2f}% vs modellato(IV-ATM) {pick['model_pct'].mean():.2f}%") + print(f" FATTORE f = reale/modellato: mediana {pick['f'].median():.2f} IQR [{pick['f'].quantile(.25):.2f}, {pick['f'].quantile(.75):.2f}] (range {pick['f'].min():.2f}-{pick['f'].max():.2f})") + + print("\n -> f e' il punto reale sullo sweep di options_vrp_lab (Sh: f1.0=0.71, f1.29=1.70).") + print(" CAVEAT: finestra mag-giu 2026 = UN regime (niente crash) -> f calmo. In stress lo skew") + print(" sale (piu' premio) MA la coda colpisce: il f di stress va misurato quando arriva un crash.") + + +if __name__ == "__main__": + main()