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>
77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
"""INVERTED_TP_SKIP (2026-06-16): un wick transitorio puo' far calcolare alla strategia
|
|
un tp dal lato SBAGLIATO dell'entry (es. donchian: segnale su barra wickata, entry al
|
|
prezzo recuperato gia' oltre il proprio tp). L'exit intrabar `bar_high>=tp` (long) /
|
|
`bar_low<=tp` (short) scatterebbe a bars_held=0 in PERDITA, con churn di fee e TP
|
|
reduce-only respinti. Verita' d'esecuzione, zero parametri: se il TP e' gia' sfondato
|
|
all'ingresso il segnale e' malformato -> NON si apre. TP dal lato giusto (o assente)
|
|
-> comportamento storico."""
|
|
from src.live.strategy_worker import StrategyWorker
|
|
from src.live.strategy_loader import load_strategy
|
|
from src.strategies.base import Signal
|
|
|
|
|
|
def _worker(tmp):
|
|
w = StrategyWorker(strategy=load_strategy("MR02_donchian_fade"), asset="BTC", tf="15m",
|
|
capital=1000.0, data_dir=tmp)
|
|
w._notify = lambda *a, **k: None
|
|
return w
|
|
|
|
|
|
def test_long_inverted_tp_skipped(tmp_path):
|
|
# long con tp SOTTO l'entry (caso reale 16-06: entry 66780, tp 66189) -> no entry
|
|
w = _worker(tmp_path)
|
|
sig = Signal(idx=119, direction=1, entry_price=66780.0,
|
|
metadata={"tp": 66189.25, "sl": 64218.21, "max_bars": 24})
|
|
w._open_position(sig, 66780.0, current_ts=1)
|
|
assert not w.in_position
|
|
assert w.tp == 0.0
|
|
|
|
|
|
def test_short_inverted_tp_skipped(tmp_path):
|
|
# short con tp SOPRA l'entry -> il TP intrabar `bar_low<=tp` scatterebbe subito -> no entry
|
|
w = _worker(tmp_path)
|
|
sig = Signal(idx=119, direction=-1, entry_price=100.0,
|
|
metadata={"tp": 105.0, "sl": 110.0, "max_bars": 24})
|
|
w._open_position(sig, 100.0, current_ts=1)
|
|
assert not w.in_position
|
|
|
|
|
|
def test_long_valid_tp_opens(tmp_path):
|
|
# controllo positivo: long con tp SOPRA l'entry -> apre normalmente
|
|
w = _worker(tmp_path)
|
|
sig = Signal(idx=119, direction=1, entry_price=100.0,
|
|
metadata={"tp": 103.0, "sl": 98.0, "max_bars": 24})
|
|
w._open_position(sig, 100.0, current_ts=1)
|
|
assert w.in_position
|
|
assert w.direction == 1
|
|
assert w.tp == 103.0
|
|
|
|
|
|
def test_short_valid_tp_opens(tmp_path):
|
|
# controllo positivo: short con tp SOTTO l'entry -> apre normalmente
|
|
w = _worker(tmp_path)
|
|
sig = Signal(idx=119, direction=-1, entry_price=100.0,
|
|
metadata={"tp": 97.0, "sl": 102.0, "max_bars": 24})
|
|
w._open_position(sig, 100.0, current_ts=1)
|
|
assert w.in_position
|
|
assert w.direction == -1
|
|
|
|
|
|
def test_no_tp_opens(tmp_path):
|
|
# segnale senza tp (strategie a orizzonte puro, es. SH01) -> il guard non si applica
|
|
w = _worker(tmp_path)
|
|
sig = Signal(idx=119, direction=1, entry_price=100.0, metadata={"max_bars": 12})
|
|
w._open_position(sig, 100.0, current_ts=1)
|
|
assert w.in_position
|
|
|
|
|
|
def test_inverted_skip_deduped_per_bar(tmp_path):
|
|
# stesso bar_ts -> un solo log INVERTED_TP_SKIP; non blocca un bar successivo valido
|
|
w = _worker(tmp_path)
|
|
sig = Signal(idx=119, direction=1, entry_price=66780.0,
|
|
metadata={"tp": 66189.25, "max_bars": 24})
|
|
w._open_position(sig, 66780.0, current_ts=1)
|
|
w._open_position(sig, 66780.0, current_ts=1)
|
|
assert not w.in_position
|
|
assert w._inverted_tp_ts == 1
|