"""S2-11: VRP con DVOL REALE — unico test valido. Solo 90 giorni di dati, ma REALI. Confronta DVOL (IV reale Deribit) vs RV realizzata. """ from __future__ import annotations import sys sys.path.insert(0, ".") import numpy as np import pandas as pd from src.data.downloader import load_data FEE_ROUNDTRIP = 0.0052 INITIAL = 1000 def rv_ann(close, window): lr = np.diff(np.log(np.where(close == 0, 1e-10, close))) r = np.full(len(close), np.nan) for i in range(window, len(lr)): r[i + 1] = np.std(lr[i - window : i]) * np.sqrt(24 * 365) return r def straddle_prem(iv_pct, dte_h): """iv_pct è la IV in decimale (es 0.50 = 50%).""" if iv_pct <= 0 or dte_h <= 0: return 0 return iv_pct * np.sqrt(dte_h / (24 * 365)) * 0.8 for asset in ["ETH", "BTC"]: print(f"\n{'='*70}") print(f" {asset} — VRP CON DVOL REALE (90 giorni)") print(f"{'='*70}") df_price = load_data(asset, "1h") df_dvol = pd.read_parquet(f"data/raw/{asset.lower()}_dvol.parquet") close = df_price["close"].values ts_price = df_price["timestamp"].values n = len(close) dvol_ts = df_dvol["timestamp"].values dvol_vals = df_dvol["dvol"].values / 100 # converti a decimale rv_24 = rv_ann(close, 24) rv_48 = rv_ann(close, 48) # Allinea DVOL ai candles 1h (DVOL è giornaliero) dvol_aligned = np.full(n, np.nan) for j in range(len(dvol_ts)): mask = (ts_price >= dvol_ts[j]) & (ts_price < dvol_ts[j] + 86400000) dvol_aligned[mask] = dvol_vals[j] valid_count = np.sum(~np.isnan(dvol_aligned)) print(f" Candele con DVOL reale: {valid_count}") print(f" DVOL range: {np.nanmin(dvol_aligned)*100:.1f}% — {np.nanmax(dvol_aligned)*100:.1f}%") # Analisi IV vs RV reale iv_rv_ratios = [] for i in range(n): if np.isnan(dvol_aligned[i]) or np.isnan(rv_24[i]) or rv_24[i] <= 0: continue iv_rv_ratios.append(dvol_aligned[i] / rv_24[i]) if iv_rv_ratios: print(f"\n IV/RV ratio REALE:") print(f" Mean: {np.mean(iv_rv_ratios):.3f}") print(f" Median: {np.median(iv_rv_ratios):.3f}") print(f" Min: {np.min(iv_rv_ratios):.3f}") print(f" Max: {np.max(iv_rv_ratios):.3f}") print(f" >1 (VRP+): {sum(1 for r in iv_rv_ratios if r > 1)/len(iv_rv_ratios)*100:.0f}% del tempo") # Backtest VRP reale for dte in [24, 48]: print(f"\n --- DTE={dte}h ---") capital = float(INITIAL) trades = [] daily_done = set() for i in range(100, n - dte): if np.isnan(dvol_aligned[i]) or np.isnan(rv_24[i]): continue ts_dt = pd.Timestamp(ts_price[i], unit="ms", tz="UTC") if ts_dt.hour != 8: continue day = ts_dt.strftime("%Y-%m-%d") if day in daily_done: continue iv = dvol_aligned[i] rv = rv_24[i] # Filtro regime: skip se RV > IV (no premium) if rv > iv: continue prem = straddle_prem(iv, dte) spot = close[i] exit_idx = min(i + dte, n - 1) actual_move = abs(close[exit_idx] - spot) / spot pos_pct = 0.10 if actual_move <= prem: raw = (prem - actual_move) * pos_pct else: raw = -(actual_move - prem) * pos_pct raw = max(raw, -pos_pct * 0.05) net = raw - FEE_ROUNDTRIP * pos_pct capital += capital * net capital = max(capital, 10) trades.append({ "day": day, "iv": iv * 100, "rv": rv * 100, "premium": prem * 100, "move": actual_move * 100, "pnl": net * capital, "win": raw > 0, }) daily_done.add(day) if not trades: print(" Nessun trade!") continue wins = sum(1 for t in trades if t["win"]) acc = wins / len(trades) * 100 ret = (capital - INITIAL) / INITIAL * 100 avg_iv = np.mean([t["iv"] for t in trades]) avg_rv = np.mean([t["rv"] for t in trades]) avg_prem = np.mean([t["premium"] for t in trades]) avg_move = np.mean([t["move"] for t in trades]) print(f" Trades: {len(trades)}") print(f" Accuracy: {acc:.1f}%") print(f" Return: {ret:+.1f}%") print(f" Capital: €{capital:.0f}") print(f" Avg IV: {avg_iv:.1f}%") print(f" Avg RV: {avg_rv:.1f}%") print(f" Avg Prem: {avg_prem:.2f}%") print(f" Avg Move: {avg_move:.2f}%") print(f" IV > Move (win condition): {sum(1 for t in trades if t['premium'] > t['move'])/len(trades)*100:.0f}%") # Worst trade worst = min(trades, key=lambda t: t["pnl"]) print(f" Worst: day={worst['day']} iv={worst['iv']:.1f}% move={worst['move']:.2f}%")