research: calibra VRP su quote REALI cerbero-bite — f≈1.0 (non 1.29), lead DEBOLE confermato non-deploy

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>
This commit is contained in:
Adriano Dal Pastro
2026-06-19 20:38:54 +00:00
parent 8f9ce89039
commit 53d0134cb1
2 changed files with 104 additions and 0 deletions
+21
View File
@@ -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.
+83
View File
@@ -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()