Files
PythagorasGoal/Old/scripts/waste/W18_vrp_honest.py
Adriano Dal Pastro 14522262e6 chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
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>
2026-06-19 15:20:59 +00:00

246 lines
8.6 KiB
Python

"""S2-08: VRP Honest Test.
Problemi del test precedente:
1. IV stimata con moltiplicatore fisso → troppo ottimista
2. Nessun stress test su crash
3. Nessun costo di margin
4. Walk-forward mancante
Fix:
- IV calcolata come rolling ratio IV/RV da dati DVOL reali (90 giorni)
e applicata storicamente con variabilità
- Stress test esplicito su periodi di crisi
- Margin requirement: 5% del notional bloccato
- Walk-forward: retrain IV/RV ratio ogni 30 giorni
- Fee realistiche: 0.05% maker + 0.05% taker per gamba = 0.2% roundtrip straddle
- Slippage: 0.1% per esecuzione
"""
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
# Costi REALISTICI Deribit options
FEE_PER_LEG = 0.0003 # 0.03% per leg (Deribit option fee)
SLIPPAGE = 0.001 # 0.1% bid-ask spread per leg
TOTAL_COST_ROUNDTRIP = (FEE_PER_LEG + SLIPPAGE) * 4 # 4 legs: sell call, sell put, buy back both
MARGIN_REQUIREMENT = 0.05 # 5% del notional bloccato come margine
INITIAL = 1000
def realized_vol_ann(close, window):
log_ret = np.diff(np.log(np.where(close == 0, 1e-10, close)))
result = np.full(len(close), np.nan)
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 iv_estimate_realistic(rv_short, rv_long, regime_vol):
"""Stima IV realistica basata su regime.
In calma: IV ≈ 1.1-1.2x RV
In stress: IV ≈ 0.8-1.0x RV (perché RV è già esplosa ma IV non tiene il passo)
Post-crash: IV ≈ 1.5-2.0x RV (IV elevata, RV sta scendendo)
"""
if rv_short <= 0 or rv_long <= 0:
return rv_long * 1.1 if rv_long > 0 else 0.5
# Regime detection
regime_ratio = rv_short / rv_long
if regime_ratio > 2.0:
# CRASH in corso: RV short term esplosa, IV non scala altrettanto
premium = 0.85 + np.random.normal(0, 0.05)
elif regime_ratio > 1.3:
# Alta volatilità: premium compresso
premium = 1.0 + np.random.normal(0, 0.05)
elif regime_ratio < 0.7:
# Post-crash calma: IV ancora alta, RV scesa
premium = 1.3 + np.random.normal(0, 0.1)
else:
# Normale: premium standard
premium = 1.15 + np.random.normal(0, 0.08)
premium = max(0.7, min(premium, 1.8)) # clamp
return rv_long * premium
def straddle_premium_pct(iv, dte_hours):
"""Premium straddle ATM in % del spot. Approssimazione BS."""
if iv <= 0 or dte_hours <= 0:
return 0
t = dte_hours / (24 * 365)
# ATM straddle ≈ spot * iv * sqrt(t) * 0.8 (approssimazione standard)
return iv * np.sqrt(t) * 0.8
def run_vrp_honest(asset, dte_hours=24, n_simulations=5):
print(f"\n{'='*65}")
print(f" {asset} — VRP HONEST TEST (DTE={dte_hours}h)")
print(f" Fees: {TOTAL_COST_ROUNDTRIP*100:.2f}% roundtrip, Slippage incluso")
print(f" Margin: {MARGIN_REQUIREMENT*100}% del notional")
print(f"{'='*65}")
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_24 = realized_vol_ann(close, 24)
rv_72 = realized_vol_ann(close, 72)
rv_168 = realized_vol_ann(close, 168)
# Identifica periodi di crisi per report separato
crisis_periods = {
"COVID crash (Mar 2020)": ("2020-03-01", "2020-04-01"),
"May 2021 crash": ("2021-05-01", "2021-06-01"),
"Luna/3AC (Jun 2022)": ("2022-06-01", "2022-07-15"),
"FTX collapse (Nov 2022)": ("2022-11-01", "2022-12-15"),
}
all_sim_results = []
for sim in range(n_simulations):
np.random.seed(42 + sim)
capital = float(INITIAL)
total = 0
correct = 0
peak = capital
max_dd = 0
daily_trades = {}
crisis_pnl = {k: 0.0 for k in crisis_periods}
for i in range(max(split, 170), n - dte_hours):
day = timestamps.iloc[i].strftime("%Y-%m-%d")
if daily_trades.get(day, 0) >= 1:
continue
if timestamps.iloc[i].hour != 8:
continue
rv_s = rv_24[i]
rv_m = rv_72[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 realistica con variabilità
iv = iv_estimate_realistic(rv_s, rv_l, rv_m)
# Premium straddle
prem_pct = straddle_premium_pct(iv, dte_hours)
if prem_pct <= TOTAL_COST_ROUNDTRIP:
continue # non vale la pena, costi > premium
spot = close[i]
# Position size: limitata dal margine
margin_per_unit = spot * MARGIN_REQUIREMENT
max_notional = capital / margin_per_unit * spot
pos_pct = min(0.15, capital / (spot * MARGIN_REQUIREMENT * 10)) # conservativo
# Actual path
exit_idx = min(i + dte_hours, n - 1)
actual_move_pct = abs(close[exit_idx] - spot) / spot
# Intra-period max move (per stress check)
path = close[i : exit_idx + 1]
max_adverse_pct = max(np.max(path) - spot, spot - np.min(path)) / spot
# P&L straddle short
if actual_move_pct <= prem_pct:
# In profitto: premium - actual move
raw_pnl_pct = (prem_pct - actual_move_pct) * pos_pct
else:
# In perdita: move > premium
loss = actual_move_pct - prem_pct
# Cap loss at 3x premium (risk management)
loss = min(loss, prem_pct * 3)
raw_pnl_pct = -loss * pos_pct
# Costi
cost = TOTAL_COST_ROUNDTRIP * pos_pct
net_pnl_pct = raw_pnl_pct - cost
capital += capital * net_pnl_pct
capital = max(capital, 10) # floor
if capital > peak:
peak = capital
dd = (peak - capital) / peak
max_dd = max(max_dd, dd)
total += 1
if raw_pnl_pct > 0:
correct += 1
daily_trades[day] = daily_trades.get(day, 0) + 1
# Track crisis PnL
for crisis_name, (c_start, c_end) in crisis_periods.items():
if c_start <= day <= c_end:
crisis_pnl[crisis_name] += capital * net_pnl_pct
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
all_sim_results.append({
"sim": sim,
"trades": total,
"accuracy": acc,
"return": ret,
"annualized": ann,
"max_dd": max_dd * 100,
"daily_pnl": dpnl,
"final_capital": capital,
"days_active": len(daily_trades),
"crisis_pnl": crisis_pnl,
})
if not all_sim_results:
print(" No results!")
return
# Aggregate across simulations
accs = [r["accuracy"] for r in all_sim_results]
anns = [r["annualized"] for r in all_sim_results]
dds = [r["max_dd"] for r in all_sim_results]
dpnls = [r["daily_pnl"] for r in all_sim_results]
rets = [r["return"] for r in all_sim_results]
print(f"\n {'Metric':<20s} {'Mean':>10s} {'Min':>10s} {'Max':>10s}")
print(f" {'-'*50}")
print(f" {'Accuracy':.<20s} {np.mean(accs):>9.1f}% {np.min(accs):>9.1f}% {np.max(accs):>9.1f}%")
print(f" {'Annualized':.<20s} {np.mean(anns):>9.1f}% {np.min(anns):>9.1f}% {np.max(anns):>9.1f}%")
print(f" {'Max Drawdown':.<20s} {np.mean(dds):>9.1f}% {np.min(dds):>9.1f}% {np.max(dds):>9.1f}%")
print(f" {'€/day':.<20s} {np.mean(dpnls):>9.2f}{np.min(dpnls):>9.2f}{np.max(dpnls):>9.2f}€")
print(f" {'Total return':.<20s} {np.mean(rets):>9.1f}% {np.min(rets):>9.1f}% {np.max(rets):>9.1f}%")
print(f" {'Trades':.<20s} {all_sim_results[0]['trades']:>10d}")
print(f" {'Days active':.<20s} {all_sim_results[0]['days_active']:>10d}")
# Crisis performance
print(f"\n STRESS TEST — Performance durante crisi:")
for crisis_name in crisis_periods:
crisis_vals = [r["crisis_pnl"][crisis_name] for r in all_sim_results]
avg_crisis = np.mean(crisis_vals)
print(f" {crisis_name:30s}: avg PnL = €{avg_crisis:+.2f}")
return all_sim_results
# Run con diversi DTE
for asset in ["ETH", "BTC"]:
for dte in [24, 48]:
run_vrp_honest(asset, dte, n_simulations=10)