feat(strategy4): MT01 squeeze+MTF 82.7% acc — batte SQ02, 6 strategie scartate

Nuova strategia MT01: squeeze 15m + momentum EMA 1h
  BTC 15m: 82.7% acc, 503 trades, DD 5.9%, 9/9 anni, worst 72%
  ETH 15m: 81.2% acc, 404 trades, DD 2.9%, 9/9 anni, worst 73%

Strategie testate e scartate (waste W23-W28):
  IB01 inside bar (58.7%, no edge)
  DC01 donchian (48%, sotto random)
  SB01 retest (52%, no edge)
  MR01 mean reversion RSI (62.9%, DD 29%)
  VO01 volume spike (64.2%, DD 34%)
  HY01 squeeze+MR (64.6%, DD 14.5%)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 00:38:11 +02:00
parent 56bad4741e
commit f42fec9fac
21 changed files with 1230 additions and 0 deletions
+148
View File
@@ -0,0 +1,148 @@
"""MR01 — Mean Reversion da estremi RSI.
Approccio opposto allo squeeze: quando il prezzo va troppo lontano troppo veloce,
scommetti che torni indietro. Autocorrelazione lag-1 negativa (-0.21 BTC, -0.35 ETH)
conferma che il mercato a 15m è mean-reverting.
IN:
- OHLCV DataFrame
- Parametri: rsi_period, rsi_oversold, rsi_overbought, hold_bars,
volume_filter (volume > N× media), atr_filter (move > N×ATR)
OUT:
- Signal: long quando RSI < oversold, short quando RSI > overbought
- BacktestResult con metriche
Logica:
1. RSI scende sotto soglia oversold → LONG (prezzo tornerà su)
2. RSI sale sopra soglia overbought → SHORT (prezzo tornerà giù)
3. Filtro opzionale: volume spike conferma l'eccesso
4. Filtro opzionale: move recente > N×ATR (eccesso di prezzo)
5. Hold fisso, poi chiudi
"""
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
def rsi(close, period=14):
delta = np.diff(close)
gain = np.where(delta > 0, delta, 0)
loss = np.where(delta < 0, -delta, 0)
result = np.full(len(close), 50.0)
if len(gain) < period:
return result
ag = np.mean(gain[:period])
al = np.mean(loss[:period])
for i in range(period, len(delta)):
ag = (ag * (period - 1) + gain[i]) / period
al = (al * (period - 1) + loss[i]) / period
result[i + 1] = 100 if al == 0 else 100 - 100 / (1 + ag / al)
return result
class MeanReversionRSI(Strategy):
name = "MR01_mean_reversion_rsi"
description = "Mean reversion da estremi RSI — fade eccessi direzionali"
default_assets = ["BTC", "ETH"]
default_timeframes = ["15m", "1h"]
fee_rt = 0.002
def generate_signals(self, df, ts, **params):
c = df["close"].values
h = df["high"].values
l = df["low"].values
v = df["volume"].values
n = len(c)
rsi_period = params.get("rsi_period", 14)
oversold = params.get("rsi_oversold", 25)
overbought = params.get("rsi_overbought", 75)
use_vol_filter = params.get("vol_filter", False)
use_atr_filter = params.get("atr_filter", False)
cooldown = params.get("cooldown", 4)
rsi_vals = rsi(c, rsi_period)
# Volume media rolling
vol_ma = np.full(n, np.nan)
for i in range(20, n):
vol_ma[i] = np.mean(v[i - 20:i])
# ATR
tr = np.maximum(h[1:] - l[1:],
np.maximum(np.abs(h[1:] - c[:-1]), np.abs(l[1:] - c[:-1])))
atr_vals = np.full(n, np.nan)
for i in range(15, len(tr)):
atr_vals[i + 1] = np.mean(tr[i - 14:i])
signals = []
last_signal_idx = -cooldown
for i in range(20, n):
if i - last_signal_idx < cooldown:
continue
direction = 0
if rsi_vals[i] < oversold:
direction = 1 # oversold → long
elif rsi_vals[i] > overbought:
direction = -1 # overbought → short
if direction == 0:
continue
# Volume filter
if use_vol_filter and not np.isnan(vol_ma[i]):
if v[i] < vol_ma[i] * 1.5:
continue
# ATR filter: il move recente deve essere > 1.5× ATR
if use_atr_filter and not np.isnan(atr_vals[i]):
recent_move = abs(c[i] - c[max(0, i - 3)]) / c[max(0, i - 3)]
if recent_move < atr_vals[i] / c[i] * 1.5:
continue
signals.append(Signal(
idx=i, direction=direction, entry_price=c[i],
metadata={"rsi": float(rsi_vals[i])},
))
last_signal_idx = i
return signals
if __name__ == "__main__":
strategy = MeanReversionRSI()
configs = [
("RSI25/75", {}),
("RSI20/80", {"rsi_oversold": 20, "rsi_overbought": 80}),
("RSI25/75+vol", {"vol_filter": True}),
("RSI20/80+vol", {"rsi_oversold": 20, "rsi_overbought": 80, "vol_filter": True}),
("RSI25/75+atr", {"atr_filter": True}),
("RSI20/80+vol+atr", {"rsi_oversold": 20, "rsi_overbought": 80, "vol_filter": True, "atr_filter": True}),
]
all_results = []
for label, params in configs:
for asset in ["BTC", "ETH"]:
for tf in ["15m", "1h"]:
for hold in [3, 6]:
r = strategy.backtest(asset, tf, hold=hold, **params)
if r and r.trades >= 30:
r.strategy_name = f"MR01 {label} h={hold}"
all_results.append(r)
all_results.sort(key=lambda r: r.accuracy, reverse=True)
print(f"\n{'=' * 120}")
print(f" MR01 MEAN REVERSION RSI — TOP 20")
print(f"{'=' * 120}")
for r in all_results[:20]:
r.print_summary()
if all_results:
all_results[0].print_yearly()