53d0134cb1
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) <noreply@anthropic.com>
84 lines
4.6 KiB
Python
84 lines
4.6 KiB
Python
"""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()
|