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:
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user