"""S2-06: Iron Condor simulato + Variance Risk Premium harvesting. Vendi un range: se il prezzo sta dentro il range a scadenza → profitto. Più sofisticato del vol selling puro: - Calcolo IV vs RV (variance risk premium) - Selezione larghezza condor in base a IV/RV ratio - Dynamic position sizing: più capital quando IV/RV ratio è alto - Ingresso giornaliero, scadenze 24h e 48h - Include: tail risk protection (chiudi se move > 2 ATR) """ 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 = 0.001 INITIAL = 1000 def realized_vol_ann(close: np.ndarray, window: int) -> np.ndarray: log_ret = np.diff(np.log(np.where(close == 0, 1e-10, close))) result = np.full(len(close), 0.5) for i in range(window, len(log_ret)): result[i + 1] = np.std(log_ret[i - window : i]) * np.sqrt(24 * 365) return result def run_iron_condor(asset, tf="1h"): print(f"\n{'#'*60}") print(f" {asset} {tf} — IRON CONDOR / VARIANCE PREMIUM") print(f"{'#'*60}") df = load_data(asset, tf) close = df["close"].values high = df["high"].values low = df["low"].values n = len(close) split = int(n * 0.7) timestamps = pd.to_datetime(df["timestamp"], unit="ms", utc=True) rv_24 = realized_vol_ann(close, 24) rv_48 = realized_vol_ann(close, 48) rv_168 = realized_vol_ann(close, 168) # 1 week IV_PREMIUM = 1.25 # IV typically 1.2-1.3x RV in crypto configs = [ # (dte_hours, condor_width_mult, max_loss_pct, vrp_min, pos_pct, name) (24, 1.0, 0.03, 1.10, 0.15, "24h_1x_std"), (24, 1.5, 0.04, 1.10, 0.12, "24h_1.5x_safe"), (24, 0.8, 0.025, 1.15, 0.18, "24h_0.8x_aggr"), (48, 1.0, 0.035, 1.10, 0.15, "48h_1x_std"), (48, 1.5, 0.05, 1.10, 0.12, "48h_1.5x_safe"), (48, 0.7, 0.025, 1.20, 0.20, "48h_0.7x_highVRP"), (72, 1.2, 0.04, 1.10, 0.12, "72h_1.2x"), (24, 1.0, 0.03, 1.30, 0.20, "24h_veryHighVRP"), (24, 1.2, 0.035, 1.10, 0.15, "24h_1.2x_balanced"), ] for dte, width_mult, max_loss, vrp_min, pos_pct, name in configs: capital = float(INITIAL) correct = 0 total = 0 daily_trades = {} max_dd = 0 peak = capital for i in range(max(split, 170), n - dte): day = timestamps.iloc[i].strftime("%Y-%m-%d") if daily_trades.get(day, 0) >= 1: continue hour = timestamps.iloc[i].hour if hour != 8: continue rv_short = rv_24[i] rv_long = rv_168[i] if rv_short <= 0 or rv_long <= 0: continue iv_est = rv_long * IV_PREMIUM vrp_ratio = iv_est / rv_short if vrp_ratio < vrp_min: continue spot = close[i] t_years = dte / (24 * 365) # Condor range: spot ± width * daily_std * sqrt(t) daily_std = rv_short / np.sqrt(365) range_width = width_mult * daily_std * np.sqrt(dte / 24) * spot upper_strike = spot + range_width lower_strike = spot - range_width # Premium collected (simplified BS for condor) # Premium ≈ IV * sqrt(t) * (width factor) premium_pct = iv_est * np.sqrt(t_years) * 0.4 * (1 / width_mult) # Check if price stays in range exit_idx = min(i + dte, n - 1) price_path = close[i : exit_idx + 1] max_move = max(np.max(price_path) - spot, spot - np.min(price_path)) final_price = close[exit_idx] in_range = lower_strike <= final_price <= upper_strike breached_hard = max_move > spot * max_loss if breached_hard: pnl_pct = -max_loss * pos_pct elif in_range: pnl_pct = premium_pct * pos_pct else: # Partial loss: exceeded range but not catastrophic excess = max(0, final_price - upper_strike, lower_strike - final_price) loss = min(excess / spot, max_loss) pnl_pct = (premium_pct - loss) * pos_pct fee_cost = FEE * 2 * pos_pct net_pnl = pnl_pct - fee_cost capital += capital * net_pnl capital = max(capital, 0) if capital > peak: peak = capital dd = (peak - capital) / peak if peak > 0 else 0 max_dd = max(max_dd, dd) total += 1 if net_pnl > 0: correct += 1 daily_trades[day] = daily_trades.get(day, 0) + 1 if total < 20: continue acc = correct / total * 100 ret = (capital - INITIAL) / INITIAL * 100 test_days = (n - split) / 24 test_years = test_days / 365.25 ann = ((capital / INITIAL) ** (1 / test_years) - 1) * 100 if test_years > 0 and capital > 0 else -100 dpnl = (capital - INITIAL) / test_days if test_days > 0 else 0 days_active = len(daily_trades) tag = "✅✅" if acc >= 70 and ann >= 50 else "✅" if acc >= 65 and ann >= 30 else "" print(f" {name:22s}: trades={total:4d} acc={acc:.1f}% ret={ret:+.1f}% ann={ann:+.1f}% dd={max_dd*100:.1f}% €/day={dpnl:.2f} active={days_active} {tag}") for asset in ["ETH", "BTC"]: run_iron_condor(asset) # === COMBINAZIONE: Iron Condor + Funding + Gap Fade === print(f"\n{'#'*60}") print(f" COMBINAZIONE: MULTI-STRATEGY PORTFOLIO") print(f"{'#'*60}") # Simula portafoglio: 50% iron condor ETH, 25% iron condor BTC, 25% gap fade ETH print(" (Dettagli nel prossimo script con backtest combinato)")