Files
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

206 lines
7.2 KiB
Python

"""AD01 — Adaptive Squeeze Threshold.
Problema SQ02: sq_threshold fisso (0.8) non si adatta al regime di volatilità.
Soluzione: threshold adattivo basato su volatilità recente.
Logica:
- Calcola volatilità rolling (std dei rendimenti su finestra 100 barre)
- Confronta con percentile storico (rolling 500 barre)
- Alta vol (>70° percentile) → soglia BASSA (0.65) — squeeze più "lenti"
- Bassa vol (<30° percentile) → soglia ALTA (0.90) — squeeze "stretti"
- Vol media → soglia standard (0.80)
Razionale: in mercati calmi, il BB si stringe molto → sq_threshold alto cattura
segnali migliori. In mercati volatili, bastano squeeze minori per essere significativi.
Anti-overfitting: solo 3 parametri (low_thr, mid_thr, high_thr), logica deterministica.
Eredita antifakeout + volume da SQ02.
"""
from __future__ import annotations
import sys
sys.path.insert(0, ".")
import numpy as np
import pandas as pd
from src.strategies.base import Strategy, Signal, BacktestResult, YearlyStats, TF_MINUTES
from src.strategies.indicators import keltner_ratio, ema
from src.data.downloader import load_data
def _adaptive_sq_threshold(close: np.ndarray,
vol_window: int = 100,
regime_window: int = 500,
low_thr: float = 0.65,
mid_thr: float = 0.80,
high_thr: float = 0.90) -> np.ndarray:
"""Calcola sq_threshold adattivo per ogni barra."""
n = len(close)
lr = np.diff(np.log(np.where(close <= 0, 1e-10, close)))
vol = np.full(n, np.nan)
for i in range(vol_window, n):
vol[i] = np.std(lr[i - vol_window:i])
# Percentile rolling della volatilità
thresh = np.full(n, mid_thr)
for i in range(regime_window, n):
if np.isnan(vol[i]):
continue
hist = vol[i - regime_window:i]
hist = hist[~np.isnan(hist)]
if len(hist) < 10:
continue
p30 = np.percentile(hist, 30)
p70 = np.percentile(hist, 70)
if vol[i] < p30:
thresh[i] = high_thr # vol bassa → soglia alta
elif vol[i] > p70:
thresh[i] = low_thr # vol alta → soglia bassa
else:
thresh[i] = mid_thr
return thresh
def _detect_adaptive_squeezes(close, high, low, kcr, adaptive_thr,
min_dur: int = 5) -> list[dict]:
"""Squeeze con threshold adattivo per ogni barra."""
events = []
in_sq = False
sq_start = 0
for i in range(1, len(close)):
if np.isnan(kcr[i]) or np.isnan(adaptive_thr[i]):
continue
thr = adaptive_thr[i]
is_sq = kcr[i] < thr
if is_sq and not in_sq:
in_sq = True
sq_start = i
elif not is_sq and in_sq:
in_sq = False
dur = i - sq_start
if dur < min_dur:
continue
events.append({
"idx": i, "dur": dur, "sq_start": sq_start,
"kcr_at_release": kcr[i],
"thr_used": adaptive_thr[i],
})
return events
class AdaptiveSqueeze(Strategy):
name = "AD01_adaptive_squeeze"
description = "Squeeze con threshold adattivo a regime volatilità"
default_assets = ["BTC", "ETH"]
default_timeframes = ["15m", "1h"]
fee_rt = 0.002
leverage = 3.0
position_size = 0.15
initial_capital = 1000.0
def generate_signals(self, df: pd.DataFrame, ts: pd.DatetimeIndex,
**params) -> list[Signal]:
c = df["close"].values
h = df["high"].values
l = df["low"].values
v = df["volume"].values
n = len(c)
bb_w = params.get("bb_window", 14)
low_thr = params.get("low_thr", 0.65)
mid_thr = params.get("mid_thr", 0.80)
high_thr = params.get("high_thr", 0.90)
retrace_limit = params.get("retrace_limit", 0.6)
vol_mult = params.get("vol_multiplier", 1.3)
use_vol = params.get("use_vol", True)
vol_window = params.get("vol_window", 100)
regime_window = params.get("regime_window", 500)
kcr = keltner_ratio(c, h, l, bb_w)
adaptive_thr = _adaptive_sq_threshold(
c, vol_window, regime_window, low_thr, mid_thr, high_thr
)
events = _detect_adaptive_squeezes(c, h, l, kcr, adaptive_thr)
signals = []
for ev in events:
i = ev["idx"]
if i < 1 or i >= n:
continue
first_ret = (c[i] - c[i - 1]) / c[i - 1] if c[i - 1] > 0 else 0
if abs(first_ret) < 0.001:
continue
direction = 1 if first_ret > 0 else -1
# Anti-fakeout
br = h[i] - l[i]
if br > 0:
if direction == 1 and (h[i] - c[i]) / br > retrace_limit:
continue
elif direction == -1 and (c[i] - l[i]) / br > retrace_limit:
continue
# Volume confirm
if use_vol:
sq_start = ev["sq_start"]
avg_sq_v = np.mean(v[sq_start:i])
if avg_sq_v > 0 and v[i] <= avg_sq_v * vol_mult:
continue
signals.append(Signal(
idx=i,
direction=direction,
entry_price=c[i - 1],
metadata={
"dur": ev["dur"],
"thr_used": ev.get("thr_used", mid_thr),
},
))
return signals
if __name__ == "__main__":
strategy = AdaptiveSqueeze()
configs = [
# low_thr, mid_thr, high_thr, use_vol
{"low_thr": 0.65, "mid_thr": 0.80, "high_thr": 0.90, "use_vol": True},
{"low_thr": 0.65, "mid_thr": 0.80, "high_thr": 0.90, "use_vol": False},
{"low_thr": 0.60, "mid_thr": 0.78, "high_thr": 0.92, "use_vol": True},
{"low_thr": 0.70, "mid_thr": 0.82, "high_thr": 0.90, "use_vol": True},
{"low_thr": 0.65, "mid_thr": 0.80, "high_thr": 0.95, "use_vol": True},
{"low_thr": 0.65, "mid_thr": 0.80, "high_thr": 0.90,
"use_vol": True, "vol_multiplier": 1.2},
]
all_results = []
for cfg in configs:
for asset in ["BTC", "ETH"]:
for tf in ["15m", "1h"]:
for hold in [3, 6]:
r = strategy.backtest(asset, tf, hold=hold, **cfg)
if r and r.trades >= 20:
lbl = (f"AD01 lt={cfg['low_thr']} ht={cfg['high_thr']} "
f"v={cfg['use_vol']} h={hold}")
r.strategy_name = lbl
all_results.append(r)
all_results.sort(key=lambda r: r.accuracy, reverse=True)
print(f"\n{'=' * 130}")
print(" AD01 ADAPTIVE SQUEEZE THRESHOLD — TOP 20")
print(f"{'=' * 130}")
print(f" {'Nome':<50s} {'A/T':>7s} {'Trades':>6s} {'Acc':>6s} "
f"{'PnL€':>10s} {'DD%':>6s} {'€/day':>7s} "
f"{'Mkt%':>5s} {'Dur':>5s} {'Anni':>4s}")
print(f" {'─' * 120}")
for r in all_results[:20]:
r.print_summary()
if all_results:
all_results[0].print_yearly()
print(f"\n BENCHMARK SQ02: 79.7% acc, 1250t, DD 6.5%, €5.23/day, 9 anni")
print(f" BENCHMARK MT01: 82.7% acc, 503t, DD 5.9%")