"""OPTIONS VRP LAB — sleeve income: vendita put settimanali (CSP) che incassa il VRP (IV>RV). Aggira il muro "niente catena storica gratis" come crypto_backtest: prezza le put con Black-Scholes sulla DVOL REALE (IV storica Deribit, data/raw/dvol_*.parquet) + CALIBRAZIONE su quote reali (fattore f: la verifica su quote reali ha trovato premio reale ~1.29x il modellato a IV-ATM per via dello skew, al netto dello spread). Payoff sul path REALIZZATO dei prezzi certificati. Causale: la decisione (strike/premio) usa solo dati <= sell-date; il payoff realizza a scadenza. Onesto: e' SHORT-VOL, il rischio vero e' la CODA (crash). Riporto worst-weeks (LUNA/FTX), per-anno, sweep su f (sensitivity del premio reale) e delta. NON e' un deploy: e' la prima validazione del lead. uv run python scripts/research/options_vrp_lab.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 scipy.stats import norm from scripts.analysis.research_lab import load_tf HOLDOUT = pd.Timestamp("2025-01-01", tz="UTC") WK_PER_YEAR = 365.25 / 7.0 def bs_put(S, K, T, sig): if T <= 0 or sig <= 0: return max(K - S, 0.0) d1 = (np.log(S / K) + 0.5 * sig ** 2 * T) / (sig * np.sqrt(T)) d2 = d1 - sig * np.sqrt(T) return K * norm.cdf(-d2) - S * norm.cdf(-d1) # r=0 def strike_from_delta(S, T, sig, target_delta=-0.28): # delta_put = -N(-d1) = target -> d1 = -N^{-1}(-target) d1 = -norm.ppf(-target_delta) return S * np.exp(0.5 * sig ** 2 * T - d1 * sig * np.sqrt(T)) def load_series(asset): px = load_tf(asset, "1d") s = pd.Series(px["close"].values.astype(float), index=pd.to_datetime(px["timestamp"], unit="ms", utc=True)) dv = pd.read_parquet(PROJECT_ROOT / "data" / "raw" / f"dvol_{asset.lower()}.parquet") d = pd.Series(dv["close"].values.astype(float), index=pd.to_datetime(dv["timestamp"], unit="ms", utc=True)) J = pd.concat({"px": s, "dvol": d}, axis=1, join="inner").sort_index().dropna() return J def put_sell_weekly(asset, delta=-0.28, f=1.0, tenor_d=7): """Vendita CSP settimanale. Ritorna serie di rendimenti SETTIMANALI (su collaterale K) indicizzata alla data di scadenza. Causale: strike/premio da DVOL e prezzo a sell-date; payoff a scadenza.""" J = load_series(asset) px = J["px"].values; dv = J["dvol"].values / 100.0; idx = J.index n = len(px); T = tenor_d / 365.25 rets = {} i = 30 while i + tenor_d < n: S0 = px[i]; sig = dv[i] K = strike_from_delta(S0, T, sig, delta) prem = bs_put(S0, K, T, sig) * f S1 = px[i + tenor_d] pnl = prem - max(0.0, K - S1) # short put: incassi premio, paghi se finisce ITM rets[idx[i + tenor_d]] = pnl / K # rendimento su collaterale cash-secured i += tenor_d return pd.Series(rets) def m_weekly(r): r = r.dropna() if len(r) < 3 or r.std() == 0: return dict(sh=0, cagr=0, dd=0, n=len(r)) eq = np.cumprod(1 + r.values); pk = np.maximum.accumulate(eq) yrs = len(r) / WK_PER_YEAR return dict(sh=float(r.mean() / r.std() * np.sqrt(WK_PER_YEAR)), cagr=float(eq[-1] ** (1 / yrs) - 1) if yrs > 0 and eq[-1] > 0 else 0, dd=float(np.max((pk - eq) / pk)), n=len(r)) def per_year(r): out = {} for y, g in r.groupby(r.index.year): eq = np.cumprod(1 + g.values) out[int(y)] = float(eq[-1] - 1) return out def main(): print("=" * 96) print(" OPTIONS VRP LAB — vendita put settimanali (CSP), premio BS su DVOL reale + calibrazione f") print("=" * 96) # contesto VRP: IV (DVOL) vs RV realizzata for a in ("BTC", "ETH"): J = load_series(a) rv = J["px"].pct_change().rolling(30).std() * np.sqrt(365.25) * 100 vrp = (J["dvol"] - rv).dropna() print(f" {a}: DVOL media {J['dvol'].mean():.0f}% | RV30 media {rv.mean():.0f}% | VRP media {vrp.mean():+.1f} pt, >0 nel {100*(vrp>0).mean():.0f}% del tempo") print("\n (1) SWEEP CALIBRAZIONE f (delta -0.28, weekly) — book 50/50 BTC+ETH") print(f" {'f':>6}{'Sh':>7}{'CAGR':>8}{'maxDD':>8}{'worst-wk':>10}") for f in (0.70, 0.85, 1.0, 1.15, 1.29): rB = put_sell_weekly("BTC", f=f); rE = put_sell_weekly("ETH", f=f) book = pd.concat({"B": rB, "E": rE}, axis=1, join="inner").mean(axis=1) mm = m_weekly(book); worst = book.min() tag = " <- reale(calm)" if f == 1.29 else (" <- conservativo" if f == 1.0 else "") print(f" {f:>6.2f}{mm['sh']:>7.2f}{mm['cagr']*100:>+7.0f}%{mm['dd']*100:>7.1f}%{worst*100:>+9.1f}%{tag}") print("\n (2) SWEEP DELTA (f=1.0 conservativo) — book 50/50") print(f" {'delta':>7}{'Sh':>7}{'CAGR':>8}{'maxDD':>8}") for dl in (-0.15, -0.28, -0.40): rB = put_sell_weekly("BTC", delta=dl); rE = put_sell_weekly("ETH", delta=dl) book = pd.concat({"B": rB, "E": rE}, axis=1, join="inner").mean(axis=1) mm = m_weekly(book) print(f" {dl:>7.2f}{mm['sh']:>7.2f}{mm['cagr']*100:>+7.0f}%{mm['dd']*100:>7.1f}%") # config centrale: delta -0.28, f=1.0 (conservativo) e f=1.29 (reale misurato) print("\n (3) PER ANNO + WORST WEEKS (delta -0.28, book 50/50) — il rischio e' la CODA") for f in (1.0, 1.29): rB = put_sell_weekly("BTC", f=f); rE = put_sell_weekly("ETH", f=f) book = pd.concat({"B": rB, "E": rE}, axis=1, join="inner").mean(axis=1) py = per_year(book) worst = book.nsmallest(5) print(f"\n f={f}: per-anno " + " ".join(f"{y}:{v*100:+.0f}%" for y, v in py.items())) print(f" worst weeks: " + " ".join(f"{d.date()}:{v*100:.0f}%" for d, v in worst.items())) full = m_weekly(book); ho = m_weekly(book[book.index >= HOLDOUT]) print(f" FULL Sh {full['sh']:.2f} CAGR {full['cagr']*100:+.0f}% DD {full['dd']*100:.0f}% | HOLD-OUT Sh {ho['sh']:.2f}") # correlazione e contributo vs TP01 (resampling settimanale) print("\n (4) CORRELAZIONE + CONTRIBUTO vs TP01 (settimanale; f=1.0 conservativo)") from src.portfolio.sleeves import tp01_sleeve tp = tp01_sleeve().daily() tp_wk = (1 + tp).resample("7D").prod() - 1 rB = put_sell_weekly("BTC"); rE = put_sell_weekly("ETH") opt = pd.concat({"B": rB, "E": rE}, axis=1, join="inner").mean(axis=1) opt_wk = opt.copy(); opt_wk.index = opt_wk.index.to_period("W").to_timestamp() tp_wk2 = tp_wk.copy(); tp_wk2.index = tp_wk2.index.to_period("W").to_timestamp() Jc = pd.concat({"tp": tp_wk2, "opt": opt_wk}, axis=1, join="inner").dropna() corr = float(Jc["tp"].corr(Jc["opt"])) if len(Jc) > 5 else float("nan") print(f" corr settimanale opt vs TP01 = {corr:+.2f} (atteso ~0.2)") for w in (0.3, 0.5): comb = (1 - w) * Jc["tp"] + w * Jc["opt"] mt = m_weekly(Jc["tp"]); mc = m_weekly(comb) print(f" TP01 {1-w:.0%} + OPT {w:.0%}: Sh {mc['sh']:.2f} (TP01-solo {mt['sh']:.2f}) DD {mc['dd']*100:.0f}%") print("\n NB onesto: short-vol -> guarda i worst-weeks e gli anni di crash. Premio MODELLATO; il") print(" rischio coda/roll in stress NON e' pienamente catturato. Lead, non deploy.") if __name__ == "__main__": main()