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