refactor: riorganizzazione script — Strategy ABC, folder strategies/waste/analysis

- src/strategies/base.py: Strategy ABC con Signal, BacktestResult, YearlyStats
- src/strategies/indicators.py: keltner_ratio, detect_squeezes, ema, atr, rv, corr
- scripts/strategies/: SQ01-SQ04 (squeeze puro/filtri), ML01 (squeeze+GBM)
- scripts/waste/: W01-W22 script scartati + REF originali
- scripts/analysis/: compare, best_yearly, final_report, paper_status
- CLAUDE.md aggiornato con nuova struttura e tabella strategie

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 23:01:36 +02:00
parent fa2d74be77
commit 0e47956f7a
40 changed files with 1747 additions and 12 deletions
+152
View File
@@ -0,0 +1,152 @@
"""S2-11: VRP con DVOL REALE — unico test valido.
Solo 90 giorni di dati, ma REALI.
Confronta DVOL (IV reale Deribit) vs RV realizzata.
"""
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
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_pct, dte_h):
"""iv_pct è la IV in decimale (es 0.50 = 50%)."""
if iv_pct <= 0 or dte_h <= 0:
return 0
return iv_pct * np.sqrt(dte_h / (24 * 365)) * 0.8
for asset in ["ETH", "BTC"]:
print(f"\n{'='*70}")
print(f" {asset} — VRP CON DVOL REALE (90 giorni)")
print(f"{'='*70}")
df_price = load_data(asset, "1h")
df_dvol = pd.read_parquet(f"data/raw/{asset.lower()}_dvol.parquet")
close = df_price["close"].values
ts_price = df_price["timestamp"].values
n = len(close)
dvol_ts = df_dvol["timestamp"].values
dvol_vals = df_dvol["dvol"].values / 100 # converti a decimale
rv_24 = rv_ann(close, 24)
rv_48 = rv_ann(close, 48)
# Allinea DVOL ai candles 1h (DVOL è giornaliero)
dvol_aligned = np.full(n, np.nan)
for j in range(len(dvol_ts)):
mask = (ts_price >= dvol_ts[j]) & (ts_price < dvol_ts[j] + 86400000)
dvol_aligned[mask] = dvol_vals[j]
valid_count = np.sum(~np.isnan(dvol_aligned))
print(f" Candele con DVOL reale: {valid_count}")
print(f" DVOL range: {np.nanmin(dvol_aligned)*100:.1f}% — {np.nanmax(dvol_aligned)*100:.1f}%")
# Analisi IV vs RV reale
iv_rv_ratios = []
for i in range(n):
if np.isnan(dvol_aligned[i]) or np.isnan(rv_24[i]) or rv_24[i] <= 0:
continue
iv_rv_ratios.append(dvol_aligned[i] / rv_24[i])
if iv_rv_ratios:
print(f"\n IV/RV ratio REALE:")
print(f" Mean: {np.mean(iv_rv_ratios):.3f}")
print(f" Median: {np.median(iv_rv_ratios):.3f}")
print(f" Min: {np.min(iv_rv_ratios):.3f}")
print(f" Max: {np.max(iv_rv_ratios):.3f}")
print(f" >1 (VRP+): {sum(1 for r in iv_rv_ratios if r > 1)/len(iv_rv_ratios)*100:.0f}% del tempo")
# Backtest VRP reale
for dte in [24, 48]:
print(f"\n --- DTE={dte}h ---")
capital = float(INITIAL)
trades = []
daily_done = set()
for i in range(100, n - dte):
if np.isnan(dvol_aligned[i]) or np.isnan(rv_24[i]):
continue
ts_dt = pd.Timestamp(ts_price[i], unit="ms", tz="UTC")
if ts_dt.hour != 8:
continue
day = ts_dt.strftime("%Y-%m-%d")
if day in daily_done:
continue
iv = dvol_aligned[i]
rv = rv_24[i]
# Filtro regime: skip se RV > IV (no premium)
if rv > iv:
continue
prem = straddle_prem(iv, dte)
spot = close[i]
exit_idx = min(i + dte, n - 1)
actual_move = abs(close[exit_idx] - spot) / spot
pos_pct = 0.10
if actual_move <= prem:
raw = (prem - actual_move) * pos_pct
else:
raw = -(actual_move - prem) * pos_pct
raw = max(raw, -pos_pct * 0.05)
net = raw - FEE_ROUNDTRIP * pos_pct
capital += capital * net
capital = max(capital, 10)
trades.append({
"day": day,
"iv": iv * 100,
"rv": rv * 100,
"premium": prem * 100,
"move": actual_move * 100,
"pnl": net * capital,
"win": raw > 0,
})
daily_done.add(day)
if not trades:
print(" Nessun trade!")
continue
wins = sum(1 for t in trades if t["win"])
acc = wins / len(trades) * 100
ret = (capital - INITIAL) / INITIAL * 100
avg_iv = np.mean([t["iv"] for t in trades])
avg_rv = np.mean([t["rv"] for t in trades])
avg_prem = np.mean([t["premium"] for t in trades])
avg_move = np.mean([t["move"] for t in trades])
print(f" Trades: {len(trades)}")
print(f" Accuracy: {acc:.1f}%")
print(f" Return: {ret:+.1f}%")
print(f" Capital: €{capital:.0f}")
print(f" Avg IV: {avg_iv:.1f}%")
print(f" Avg RV: {avg_rv:.1f}%")
print(f" Avg Prem: {avg_prem:.2f}%")
print(f" Avg Move: {avg_move:.2f}%")
print(f" IV > Move (win condition): {sum(1 for t in trades if t['premium'] > t['move'])/len(trades)*100:.0f}%")
# Worst trade
worst = min(trades, key=lambda t: t["pnl"])
print(f" Worst: day={worst['day']} iv={worst['iv']:.1f}% move={worst['move']:.2f}%")