"""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()