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