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

124 lines
5.6 KiB
Python

"""FR01 — Hurst-Calm Fade (FRATTALE x REGIME). Esito della ricerca a 100 agenti (2026-06-02).
Fade della banda di Bollinger (k=2.5 su SMA50, TP=media, SL=2*ATR, max_bars=24) ATTIVATO
SOLO quando coincidono due condizioni di regime:
- FRATTALE: rolling Hurst < 0.55 (regime anti-persistente -> la mean-reversion ha senso
fratalmente; con Hurst>0.55 il fade peggiora, il momentum perde comunque).
- VOLATILITA: dvol_pct < 0.40 (DVOL nel terzile basso del suo storico -> regime calmo/range).
Doppio gate frattale x regime: l'INTERAZIONE e' l'ingrediente attivo, non il fade di per se'
(ablation: senza gate Sharpe ~0.8 e muore a fee 0.2% RT; col doppio gate OOS Sharpe ~3.7).
VALIDAZIONE (netto fee 0.10% RT, leva 3x, OOS ultimo 30%, ricerca fractal_argo_workflow):
BTC 1h: 198 trade, FULL +100% / OOS +54% / Sharpe OOS 3.73 / DD OOS 5.1% / 6/6 anni positivi,
regge fee 0.2% RT. Confermato avversarialmente (no look-ahead, split alternativo).
Generalizza a ETH 1h (Sharpe ~2.6, secondario). 4h/1d = rumore (pochi trade).
Correlazione coi fade esistenti BASSA: MR01 +0.17, MR02 +0.08, MR07 -0.03 -> DIVERSIFICATORE
quasi-ortogonale (profilo SH01/pairs), NON ridondante. Esposizione ~1-9% -> low-frequency.
RUOLO: diversificatore a basso DD per il MASTER/PORT06, NON motore standalone (non batte il
portafoglio da solo). Coerente coi priori: i frattali da soli sono rumore; il valore e' nel
gating del regime. NB il prior ARGO "VRP>0=range=fade" e' SMENTITO: l'edge robusto e' su VRP<0
e su DVOL bassa (questo gate dvol_pct<0.4), non su vol alta.
DIPENDENZA REGIME (caveat deploy): il gate usa DVOL/dvol_pct. Per il BACKTEST le feature
arrivano da regime_lab (cache da Deribit mainnet). Per il LIVE serve un feed DVOL in produzione
(regime_fetcher + allineamento causale nel runner) -> wiring NON ancora fatto. Finche' manca,
FR01 e' validata-in-ricerca ma non deployabile live.
Run backtest: uv run python scripts/strategies/FR01_hurst_calm_fade.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.strategies.base import Strategy, Signal # noqa: E402
from src.fractal.indicators import rolling_hurst # noqa: E402
def _atr(df: pd.DataFrame, n: int = 14) -> np.ndarray:
h, l, c = df["high"].values, df["low"].values, df["close"].values
pc = np.roll(c, 1); pc[0] = c[0]
tr = np.maximum(h - l, np.maximum(np.abs(h - pc), np.abs(l - pc)))
return pd.Series(tr).rolling(n).mean().values
class HurstCalmFade(Strategy):
name = "FR01_hurst_calm_fade"
description = "Fade Bollinger gateato da Hurst<0.55 (anti-persistente) + DVOL bassa (calm)"
default_assets = ["BTC", "ETH"]
default_timeframes = ["1h"]
fee_rt = 0.001
leverage = 3.0
position_size = 0.15
initial_capital = 1000.0
def generate_signals(self, df: pd.DataFrame, ts: pd.DatetimeIndex, **params) -> list[Signal]:
bb_w = params.get("bb_window", 50)
k = params.get("k", 2.5)
sl_atr = params.get("sl_atr", 2.0)
max_bars = params.get("max_bars", 24)
hurst_thr = params.get("hurst_thr", 0.55)
hurst_win = params.get("hurst_win", 100)
dvol_pct_thr = params.get("dvol_pct_thr", 0.40)
c = df["close"].values.astype(float)
n = len(c)
ma = pd.Series(c).rolling(bb_w).mean().values
sd = pd.Series(c).rolling(bb_w).std().values
a = _atr(df, 14)
hurst = rolling_hurst(c, window=hurst_win) # causale (returns[i-win:i])
# dvol_pct: dalla colonna se presente (regime_lab.load_features), altrimenti gate OFF
dvol_pct = df["dvol_pct"].values if "dvol_pct" in df.columns else np.full(n, np.nan)
signals: list[Signal] = []
for i in range(bb_w + 14, n):
if np.isnan(sd[i]) or np.isnan(a[i]) or sd[i] == 0:
continue
# GATE FRATTALE x REGIME (tutto noto a i)
if hurst[i] >= hurst_thr:
continue
if "dvol_pct" in df.columns:
if np.isnan(dvol_pct[i]) or dvol_pct[i] >= dvol_pct_thr:
continue
up, lo = ma[i] + k * sd[i], ma[i] - k * sd[i]
if c[i] < lo:
d, sl = 1, c[i] - sl_atr * a[i]
elif c[i] > up:
d, sl = -1, c[i] + sl_atr * a[i]
else:
continue
signals.append(Signal(
idx=i, direction=d, entry_price=float(c[i]),
metadata={"tp": float(ma[i]), "sl": float(sl), "max_bars": int(max_bars)},
))
return signals
if __name__ == "__main__":
# backtest via l'harness onesto + feature di regime_lab (DVOL reale)
from scripts.analysis.regime_lab import load_features, report
from scripts.analysis.explore_lab import robust
strat = HurstCalmFade()
print(f"{'=' * 100}")
print(f" FR01 HURST-CALM FADE — netto fee {strat.fee_rt*100:.2f}% RT, leva {strat.leverage:.0f}x")
print(f" gate: hurst<0.55 (anti-persistente) + dvol_pct<0.40 (DVOL bassa)")
print(f"{'=' * 100}")
for asset in ("BTC", "ETH"):
df = load_features(asset, "1h")
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
sigs = strat.generate_signals(df, ts)
ent = [{"i": s.idx, "d": s.direction, "tp": s.metadata["tp"],
"sl": s.metadata["sl"], "max_bars": s.metadata["max_bars"]} for s in sigs]
res = report(f"FR01_{asset}_1h", ent, df)
print(f" -> {asset}: robust={robust(res)} OOS Sharpe={res['oos']['sharpe']:.2f} "
f"OOS ret={res['oos']['ret']:+.0f}% DD={res['full']['dd']:.0f}%")