"""S2-09: VRP test per-anno — verità nuda. Test su OGNI anno separatamente per vedere performance durante crash. Niente compounding — PnL medio per trade in punti percentuali. Costi realistici Deribit options. """ 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 # 0.52% roundtrip (4 legs × 0.13%) 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, dte_h): if iv <= 0 or dte_h <= 0: return 0 return iv * np.sqrt(dte_h / (24 * 365)) * 0.8 def run_per_year(asset, dte=24): print(f"\n{'='*70}") print(f" {asset} — VRP PER ANNO (DTE={dte}h, NO compounding)") print(f" Fee roundtrip: {FEE_ROUNDTRIP*100:.2f}%") print(f"{'='*70}") df = load_data(asset, "1h") close = df["close"].values n = len(close) ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) rv_24 = rv_ann(close, 24) rv_168 = rv_ann(close, 168) # IV/RV premium: conservative estimate per regime # Storicamene crypto VRP ≈ 15-30% (IV/RV ≈ 1.15-1.30) # Ma durante crash VRP va NEGATIVO (RV > IV) years = sorted(set(ts.dt.year)) print(f"\n {'Year':>6s} {'Trades':>7s} {'Wins':>5s} {'Acc%':>6s} {'AvgPnL%':>9s} {'TotPnL€':>9s} {'Worst%':>8s} {'MaxMove%':>9s}") print(f" {'-'*70}") all_pnls = [] yearly_stats = [] for year in years: year_mask = ts.dt.year == year year_indices = np.where(year_mask.values)[0] if len(year_indices) < 200: continue trades_pnl = [] trades_detail = [] for i in year_indices: if i < 170 or i + dte >= n: continue if ts.iloc[i].hour != 8: continue rv_s = rv_24[i] rv_l = rv_168[i] if np.isnan(rv_s) or np.isnan(rv_l) or rv_s < 0.05 or rv_l < 0.05: continue # IV estimate: regime-dependent regime = rv_s / rv_l if rv_l > 0 else 1.0 if regime > 2.0: # CRASH: RV esplosa, IV probabilmente = RV o meno iv_premium_factor = 0.9 elif regime > 1.5: iv_premium_factor = 1.0 elif regime > 1.0: iv_premium_factor = 1.1 else: # Calm: VRP positivo iv_premium_factor = 1.2 iv = rv_l * iv_premium_factor prem = straddle_prem(iv, dte) spot = close[i] exit_idx = min(i + dte, n - 1) actual_move = abs(close[exit_idx] - spot) / spot # P&L (senza compounding — flat € su €1000) pos_size = INITIAL * 0.10 # 10% fisso, no leverage if actual_move <= prem: raw_pnl = (prem - actual_move) * pos_size else: raw_pnl = -(actual_move - prem) * pos_size raw_pnl = max(raw_pnl, -pos_size * 0.05) # cap loss cost = FEE_ROUNDTRIP * pos_size net_pnl = raw_pnl - cost trades_pnl.append(net_pnl) trades_detail.append({ "prem": prem, "move": actual_move, "regime": regime, "rv_s": rv_s, "iv": iv, }) all_pnls.append(net_pnl) if not trades_pnl: continue wins = sum(1 for p in trades_pnl if p > 0) acc = wins / len(trades_pnl) * 100 avg_pnl = np.mean(trades_pnl) tot_pnl = np.sum(trades_pnl) worst = np.min(trades_pnl) max_move = max(t["move"] for t in trades_detail) * 100 tag = "" if year in [2020, 2021, 2022]: tag = " ← CRASH YEAR" if acc >= 70 and avg_pnl > 0: tag += " ✅" print(f" {year:>6d} {len(trades_pnl):>7d} {wins:>5d} {acc:>5.1f}% {avg_pnl:>+8.2f}€ {tot_pnl:>+8.0f}€ {worst:>+7.2f}€ {max_move:>8.1f}% {tag}") yearly_stats.append({ "year": year, "trades": len(trades_pnl), "acc": acc, "avg_pnl": avg_pnl, "tot_pnl": tot_pnl, "worst": worst, }) # Summary if all_pnls: total_trades = len(all_pnls) total_wins = sum(1 for p in all_pnls if p > 0) print(f"\n {'TOTALE':>6s} {total_trades:>7d} {total_wins:>5d} {total_wins/total_trades*100:>5.1f}% {np.mean(all_pnls):>+8.2f}€ {np.sum(all_pnls):>+8.0f}€ {np.min(all_pnls):>+7.2f}€") # Con compounding realistico capital = float(INITIAL) peak = capital max_dd = 0 for pnl in all_pnls: capital += pnl * (capital / INITIAL) # scala con capitale capital = max(capital, 10) if capital > peak: peak = capital dd = (peak - capital) / peak max_dd = max(max_dd, dd) years_total = (yearly_stats[-1]["year"] - yearly_stats[0]["year"] + 1) ann = ((capital / INITIAL) ** (1 / years_total) - 1) * 100 if capital > 0 else -100 daily_avg = (capital - INITIAL) / (total_trades) # approx 1 trade/day print(f"\n CON COMPOUNDING:") print(f" Capitale finale: €{capital:,.0f}") print(f" ROI annualizzato: {ann:+.1f}%") print(f" Max Drawdown: {max_dd*100:.1f}%") print(f" €/trade medio: €{daily_avg:.2f}") # Worst year worst_year = min(yearly_stats, key=lambda x: x["tot_pnl"]) best_year = max(yearly_stats, key=lambda x: x["tot_pnl"]) print(f"\n Anno peggiore: {worst_year['year']} → {worst_year['tot_pnl']:+.0f}€ ({worst_year['acc']:.0f}% acc)") print(f" Anno migliore: {best_year['year']} → {best_year['tot_pnl']:+.0f}€ ({best_year['acc']:.0f}% acc)") for asset in ["ETH", "BTC"]: for dte in [24, 48]: run_per_year(asset, dte)