refactor(strategie): tieni solo MR01 mean-reversion, squeeze -> waste
L'analisi out-of-sample fee-aware ha dimostrato che l'intera famiglia squeeze-breakout (SQ01-04, MT01, ML01, AD01, CM01, PD01) non ha edge: le accuratezze storiche 76-82% erano un artefatto di look-ahead (ingresso a close[i-1] con direzione decisa da close[i]). Sotto ingresso onesto a close[i] e fee reali tutte perdono, anche a fee zero. - nuova MR01_bollinger_fade (mean-reversion): edge netto validato OOS, robusto su griglia parametri e fino a 0.20% fee RT. BTC 1h n50 k2.5: +201% OOS, DD 15% - 9 strategie squeeze spostate in scripts/waste/ - strategy_loader + strategies.yml: solo MR01 (BTC/ETH 1h) - signal_engine.train: validazione OOS (accuratezza test + signal precision) - scripts/analysis/strategy_research.py: harness di ricerca fee-aware NOTA: lo StrategyWorker va aggiornato per usare gli exit TP/SL passati in metadata prima di tradare MR01 dal vivo (ora esce solo a hold_bars/stop fisso). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""SQ01 — Squeeze Breakout Base.
|
||||
|
||||
Strategia strutturale: rileva compressione di volatilità (Bollinger dentro
|
||||
Keltner Channel) e segue la direzione del breakout al rilascio.
|
||||
|
||||
IN:
|
||||
- OHLCV DataFrame (da load_data)
|
||||
- Parametri: bb_window (14), sq_threshold (0.8), min_squeeze_dur (5)
|
||||
|
||||
OUT:
|
||||
- Lista di Signal con direzione breakout (+1/-1)
|
||||
- BacktestResult con equity, yearly breakdown, metriche
|
||||
|
||||
Risultati tipici:
|
||||
BTC 15m: 76.7% acc, 4062 trades, DD 6.7%, €9.32/day
|
||||
ETH 15m: 76.4% acc, 2948 trades, DD 6.2%, €10.31/day
|
||||
"""
|
||||
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
|
||||
from src.strategies.indicators import keltner_ratio, detect_squeezes
|
||||
|
||||
|
||||
class SqueezeBase(Strategy):
|
||||
name = "SQ01_squeeze_base"
|
||||
description = "Squeeze breakout puro — segui direzione al rilascio"
|
||||
default_assets = ["BTC", "ETH"]
|
||||
default_timeframes = ["15m", "1h"]
|
||||
|
||||
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
|
||||
n = len(c)
|
||||
|
||||
bb_w = params.get("bb_window", 14)
|
||||
sq_thr = params.get("sq_threshold", 0.8)
|
||||
min_dur = params.get("min_dur", 5)
|
||||
|
||||
kcr = keltner_ratio(c, h, l, bb_w)
|
||||
events = detect_squeezes(c, h, l, kcr, sq_thr, min_dur)
|
||||
|
||||
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
|
||||
signals.append(Signal(
|
||||
idx=i,
|
||||
direction=1 if first_ret > 0 else -1,
|
||||
entry_price=c[i - 1],
|
||||
metadata={"dur": ev["dur"], "kcr": ev["kcr_at_release"]},
|
||||
))
|
||||
return signals
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
strategy = SqueezeBase()
|
||||
strategy.report()
|
||||
Reference in New Issue
Block a user