0e47956f7a
- src/strategies/base.py: Strategy ABC con Signal, BacktestResult, YearlyStats - src/strategies/indicators.py: keltner_ratio, detect_squeezes, ema, atr, rv, corr - scripts/strategies/: SQ01-SQ04 (squeeze puro/filtri), ML01 (squeeze+GBM) - scripts/waste/: W01-W22 script scartati + REF originali - scripts/analysis/: compare, best_yearly, final_report, paper_status - CLAUDE.md aggiornato con nuova struttura e tabella strategie Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
2.0 KiB
Python
69 lines
2.0 KiB
Python
"""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()
|