14522262e6
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
165 lines
5.6 KiB
Python
165 lines
5.6 KiB
Python
"""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)")
|