"""S2-03: Volatility Selling — Straddle/Strangle corto simulato. La IV crypto è cronicamente sopra la realized vol → vendere premium è profittevole. Simulazione: vendi straddle ATM → profitto = max(0, premium - |move|). Premium stimato da IV storica. Ingresso giornaliero. """ from __future__ import annotations import sys sys.path.insert(0, ".") import numpy as np import pandas as pd from scipy.stats import norm from src.data.downloader import load_data FEE = 0.001 INITIAL = 1000 def realized_vol(close: np.ndarray, window: int = 24) -> np.ndarray: """Annualized realized volatility rolling.""" 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)): rv = np.std(log_ret[i - window : i]) * np.sqrt(24 * 365) result[i + 1] = rv return result def implied_vol_proxy(close: np.ndarray, window: int = 48) -> np.ndarray: """IV proxy: realized vol * premium factor. Storicamente IV crypto ≈ 1.2-1.5x realized vol (variance risk premium). """ rv = realized_vol(close, window) # Premium factor varia: alto in panic, basso in calma result = np.full(len(close), 0.5) for i in range(window, len(close)): short_rv = realized_vol(close[max(0, i-12):i+1], min(12, i))[-1] if i >= 12 else rv[i] if rv[i] > 0: regime = short_rv / rv[i] premium = 1.15 + 0.3 * max(0, regime - 1) # più alto in regime volatile else: premium = 1.2 result[i] = rv[i] * premium return result def bs_straddle_price(spot: float, iv: float, dte_hours: float) -> float: """Black-Scholes straddle price (call + put ATM).""" if dte_hours <= 0 or iv <= 0: return 0 t = dte_hours / (24 * 365) d1 = (0.5 * iv * iv * t) / (iv * np.sqrt(t)) call = spot * (2 * norm.cdf(d1) - 1) return call * 2 # straddle = 2 * ATM call (approx for ATM) def run_vol_selling(asset): print(f"\n{'#'*60}") print(f" {asset} — VOLATILITY SELLING (SHORT STRADDLE)") print(f"{'#'*60}") df = load_data(asset, "1h") close = df["close"].values n = len(close) split = int(n * 0.7) timestamps = pd.to_datetime(df["timestamp"], unit="ms", utc=True) rv = realized_vol(close, 24) iv_proxy = implied_vol_proxy(close) configs = [ # (dte_hours, iv_floor, iv_rv_ratio_min, position_pct, name) (24, 0.3, 1.15, 0.1, "daily_24h"), (12, 0.3, 1.15, 0.08, "half_day_12h"), (48, 0.3, 1.10, 0.12, "2day_48h"), (24, 0.4, 1.20, 0.1, "daily_highIV"), (8, 0.25, 1.10, 0.06, "ultra_short_8h"), (24, 0.3, 1.30, 0.15, "daily_bigPremium"), ] for dte, iv_floor, ratio_min, pos_pct, name in configs: capital = float(INITIAL) correct = 0 total = 0 daily_trades = {} for i in range(max(split, 50), n - dte): day = timestamps[i].strftime("%Y-%m-%d") if daily_trades.get(day, 0) >= 1: continue hour = timestamps[i].dt.hour if hasattr(timestamps[i], 'dt') else timestamps.iloc[i].hour if hour != 8: # entrata alle 08 UTC ogni giorno continue current_iv = iv_proxy[i] current_rv = rv[i] if current_iv < iv_floor: continue if current_rv > 0 and current_iv / current_rv < ratio_min: continue spot = close[i] premium = bs_straddle_price(spot, current_iv, dte) premium_pct = premium / spot # Actual move during holding period exit_idx = min(i + dte, n - 1) actual_move = abs(close[exit_idx] - spot) actual_move_pct = actual_move / spot # P&L: premium received - actual move (capped at max loss) max_loss = spot * 0.05 # cap loss at 5% of spot pnl = premium - min(actual_move, max_loss + premium) pnl_on_capital = pnl / spot * pos_pct fee_cost = FEE * 4 * pos_pct # 4 legs: sell call, sell put, buy back net_pnl = pnl_on_capital - fee_cost capital += capital * net_pnl capital = max(capital, 0) total += 1 if 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 >= 60 and ann >= 30 else "" print(f" {name:20s}: trades={total:4d} acc={acc:.1f}% ret={ret:+.1f}% ann={ann:+.1f}% €/day={dpnl:.2f} active={days_active} {tag}") for asset in ["ETH", "BTC"]: run_vol_selling(asset)