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>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""MR02 — Donchian Fade (mean-reversion sugli estremi del canale).
|
||||
|
||||
L'opposto esatto del trend-following Donchian (che PERDE netto: vedi
|
||||
scripts/analysis/strategy_research.py). Coerente con la lezione squeeze:
|
||||
i breakout RIENTRANO, quindi si fada la rottura del canale verso il centro.
|
||||
|
||||
Logica:
|
||||
1. Canale Donchian: massimo/minimo delle ultime n barre (escludendo la corrente)
|
||||
2. ENTRY: close rompe SOPRA il massimo del canale -> SHORT (fade);
|
||||
close rompe SOTTO il minimo -> LONG. Ingresso a close[i] (eseguibile).
|
||||
3. EXIT: take-profit al centro del canale (il rientro atteso),
|
||||
stop-loss a sl_atr*ATR oltre l'estremo, time-limit max_bars.
|
||||
|
||||
Validazione (netto, fee 0.10% RT reale Deribit, leva 3x, OOS = ultimo 30%):
|
||||
BTC 1h n=20: +879% FULL / +171% OOS, DD 30%, 8/9 anni positivi
|
||||
ETH 1h n=20: enorme FULL / +8452% OOS, DD 42%
|
||||
Robusto su TUTTA la griglia n in {10,20,30,50} x sl_atr in {1.5,2.0,3.0}
|
||||
(BTC+ETH 1h sempre positivo OOS) e su tutte le fee 0.00-0.20% RT.
|
||||
Ricerca completa: scripts/analysis/strategy_research_v2.py.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from src.strategies.base import Signal
|
||||
from src.strategies.fade_base import FadeStrategy, atr, trend_distance, hurst_skip_mask
|
||||
|
||||
|
||||
class DonchianFade(FadeStrategy):
|
||||
name = "MR02_donchian_fade"
|
||||
description = "Mean-reversion: fada la rottura del canale Donchian, TP al centro"
|
||||
default_assets = ["BTC", "ETH"]
|
||||
default_timeframes = ["1h"]
|
||||
|
||||
def generate_signals(self, df: pd.DataFrame, ts: pd.DatetimeIndex,
|
||||
**params) -> list[Signal]:
|
||||
n = params.get("n", 20)
|
||||
sl_atr = params.get("sl_atr", 2.0)
|
||||
max_bars = params.get("max_bars", 24)
|
||||
trend_max = params.get("trend_max") # None = filtro disattivo
|
||||
ema_long = params.get("ema_long", 200)
|
||||
# Edge minimo: salta i fade il cui TP (midpoint canale) è entro il costo RT. 0 = off.
|
||||
min_tp_frac = params.get("min_tp_frac", 0.0)
|
||||
# Loss-guard Hurst: salta in regime persistente/trending (hurst >= soglia). None = off.
|
||||
hurst_max = params.get("hurst_max")
|
||||
|
||||
h, l, c = df["high"].values, df["low"].values, df["close"].values
|
||||
hh = pd.Series(h).rolling(n).max().shift(1).values
|
||||
ll = pd.Series(l).rolling(n).min().shift(1).values
|
||||
a = atr(df, 14)
|
||||
td = trend_distance(df, ema_long) if trend_max is not None else None
|
||||
skip = hurst_skip_mask(df, hurst_max, params.get("hurst_win", 100))
|
||||
|
||||
signals: list[Signal] = []
|
||||
for i in range(n + 14, len(c)):
|
||||
if np.isnan(hh[i]) or np.isnan(a[i]):
|
||||
continue
|
||||
if skip[i]:
|
||||
continue # loss-guard: regime persistente
|
||||
if td is not None and (np.isnan(td[i]) or td[i] > trend_max):
|
||||
continue
|
||||
mid = (hh[i] + ll[i]) / 2.0
|
||||
if c[i] > hh[i] and c[i - 1] <= hh[i - 1]: # rottura rialzista -> fade short
|
||||
d, sl = -1, c[i] + sl_atr * a[i]
|
||||
elif c[i] < ll[i] and c[i - 1] >= ll[i - 1]: # rottura ribassista -> fade long
|
||||
d, sl = 1, c[i] - sl_atr * a[i]
|
||||
else:
|
||||
continue
|
||||
if min_tp_frac > 0 and abs(mid - c[i]) / c[i] <= min_tp_frac:
|
||||
continue # TP entro le fee -> non eseguibile in utile
|
||||
signals.append(Signal(
|
||||
idx=i, direction=d, entry_price=c[i],
|
||||
metadata={"tp": float(mid), "sl": float(sl), "max_bars": max_bars},
|
||||
))
|
||||
return signals
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
strat = DonchianFade()
|
||||
print("=" * 110)
|
||||
print(f" MR02 DONCHIAN FADE — netto fee {strat.fee_rt*100:.2f}% RT, leva {strat.leverage:.0f}x")
|
||||
print("=" * 110)
|
||||
for asset in ["BTC", "ETH"]:
|
||||
r = strat.backtest(asset, "1h", n=20, sl_atr=2.0, max_bars=24)
|
||||
if r:
|
||||
r.strategy_name = f"MR02 {asset} 1h n20"
|
||||
r.print_summary()
|
||||
r.print_yearly()
|
||||
Reference in New Issue
Block a user