14522262e6
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>
159 lines
5.7 KiB
Python
159 lines
5.7 KiB
Python
"""PD01 — Price-Volume Divergence Squeeze.
|
|
|
|
Estende SQ02 con volume TREND come filtro:
|
|
- Breakout UP con volume CRESCENTE (ultimi 3 bar vs media squeeze) → ENTRA
|
|
- Breakout UP con volume CALANTE → SALTA (divergenza bearish)
|
|
- Viceversa per short
|
|
|
|
Logica anti-fakeout:
|
|
1. Squeeze rilascio (come SQ02)
|
|
2. Anti-fakeout candela (come SQ02)
|
|
3. Volume al breakout > media squeeze (come SQ02)
|
|
4. NUOVO: volume trending UP nelle ultime 3 barre prima del breakout
|
|
|
|
Parametri semplici, nessun overfitting.
|
|
"""
|
|
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 PriceVolumeDivergence(Strategy):
|
|
name = "PD01_price_vol_div"
|
|
description = "Squeeze + antifakeout + volume trend confirmation"
|
|
default_assets = ["BTC", "ETH"]
|
|
default_timeframes = ["15m", "1h"]
|
|
fee_rt = 0.002
|
|
leverage = 3.0
|
|
position_size = 0.15
|
|
initial_capital = 1000.0
|
|
|
|
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
|
|
v = df["volume"].values
|
|
n = len(c)
|
|
|
|
bb_w = params.get("bb_window", 14)
|
|
sq_thr = params.get("sq_threshold", 0.8)
|
|
retrace_limit = params.get("retrace_limit", 0.6)
|
|
vol_mult = params.get("vol_multiplier", 1.3)
|
|
vol_trend_bars = params.get("vol_trend_bars", 3) # barre per trend volume
|
|
|
|
kcr = keltner_ratio(c, h, l, bb_w)
|
|
events = detect_squeezes(c, h, l, kcr, sq_thr)
|
|
|
|
signals = []
|
|
for ev in events:
|
|
i = ev["idx"]
|
|
if i < vol_trend_bars + 1 or i >= n:
|
|
continue
|
|
|
|
# Direzione breakout
|
|
first_ret = (c[i] - c[i - 1]) / c[i - 1] if c[i - 1] > 0 else 0
|
|
if abs(first_ret) < 0.001:
|
|
continue
|
|
direction = 1 if first_ret > 0 else -1
|
|
|
|
# Anti-fakeout
|
|
br = h[i] - l[i]
|
|
if br > 0:
|
|
if direction == 1 and (h[i] - c[i]) / br > retrace_limit:
|
|
continue
|
|
elif direction == -1 and (c[i] - l[i]) / br > retrace_limit:
|
|
continue
|
|
|
|
# Volume al breakout > media squeeze
|
|
sq_start = ev["sq_start"]
|
|
avg_sq_v = np.mean(v[sq_start:i])
|
|
if avg_sq_v <= 0 or v[i] <= avg_sq_v * vol_mult:
|
|
continue
|
|
|
|
# Volume TREND: slope delle ultime vol_trend_bars barre
|
|
# Usa regressione lineare semplice (rank correlation del volume)
|
|
recent_v = v[i - vol_trend_bars:i + 1] # include breakout bar
|
|
if len(recent_v) < vol_trend_bars:
|
|
continue
|
|
# slope: media seconda metà vs prima metà
|
|
mid = len(recent_v) // 2
|
|
v_early = np.mean(recent_v[:mid])
|
|
v_late = np.mean(recent_v[mid:])
|
|
vol_trending_up = v_late > v_early
|
|
vol_trending_down = v_early > v_late
|
|
|
|
# Concordanza: long richiede volume trending up, short trending down
|
|
if direction == 1 and not vol_trending_up:
|
|
continue
|
|
if direction == -1 and not vol_trending_down:
|
|
continue
|
|
|
|
signals.append(Signal(
|
|
idx=i,
|
|
direction=direction,
|
|
entry_price=c[i - 1],
|
|
metadata={
|
|
"dur": ev["dur"],
|
|
"vol_ratio": v[i] / avg_sq_v if avg_sq_v > 0 else 0,
|
|
"vol_trend": v_late / v_early if v_early > 0 else 1,
|
|
},
|
|
))
|
|
return signals
|
|
|
|
|
|
if __name__ == "__main__":
|
|
strategy = PriceVolumeDivergence()
|
|
|
|
configs = [
|
|
{"bb_window": 14, "sq_threshold": 0.8, "retrace_limit": 0.6,
|
|
"vol_multiplier": 1.3, "vol_trend_bars": 3},
|
|
{"bb_window": 14, "sq_threshold": 0.8, "retrace_limit": 0.6,
|
|
"vol_multiplier": 1.2, "vol_trend_bars": 3},
|
|
{"bb_window": 14, "sq_threshold": 0.8, "retrace_limit": 0.6,
|
|
"vol_multiplier": 1.3, "vol_trend_bars": 5},
|
|
{"bb_window": 14, "sq_threshold": 0.8, "retrace_limit": 0.5,
|
|
"vol_multiplier": 1.3, "vol_trend_bars": 3},
|
|
{"bb_window": 14, "sq_threshold": 0.75, "retrace_limit": 0.6,
|
|
"vol_multiplier": 1.3, "vol_trend_bars": 3},
|
|
{"bb_window": 20, "sq_threshold": 0.8, "retrace_limit": 0.6,
|
|
"vol_multiplier": 1.3, "vol_trend_bars": 3},
|
|
]
|
|
|
|
all_results = []
|
|
for cfg in configs:
|
|
for asset in ["BTC", "ETH"]:
|
|
for tf in ["15m", "1h"]:
|
|
for hold in [3, 6]:
|
|
r = strategy.backtest(asset, tf, hold=hold, **cfg)
|
|
if r and r.trades >= 20:
|
|
lbl = (f"PD01 vtb={cfg['vol_trend_bars']} "
|
|
f"vm={cfg['vol_multiplier']} "
|
|
f"sq={cfg['sq_threshold']} h={hold}")
|
|
r.strategy_name = lbl
|
|
all_results.append(r)
|
|
|
|
all_results.sort(key=lambda r: r.accuracy, reverse=True)
|
|
|
|
print(f"\n{'=' * 130}")
|
|
print(" PD01 PRICE-VOLUME DIVERGENCE — TOP 20")
|
|
print(f"{'=' * 130}")
|
|
print(f" {'Nome':<50s} {'A/T':>7s} {'Trades':>6s} {'Acc':>6s} "
|
|
f"{'PnL€':>10s} {'DD%':>6s} {'€/day':>7s} "
|
|
f"{'Mkt%':>5s} {'Dur':>5s} {'Anni':>4s}")
|
|
print(f" {'─' * 120}")
|
|
for r in all_results[:20]:
|
|
r.print_summary()
|
|
|
|
if all_results:
|
|
all_results[0].print_yearly()
|
|
|
|
print(f"\n BENCHMARK SQ02: 79.7% acc, 1250t, DD 6.5%, €5.23/day, 9 anni")
|
|
print(f" BENCHMARK MT01: 82.7% acc, 503t, DD 5.9%")
|