"""EXIT-16 — close_confirm_sl: STOP-LOSS confermato in CHIUSURA (immune ai wick). IDEA. Lo SL intrabar fisso stoppa anche su un wick transitorio che buca sl0 e poi rientra. Le fade sono mean-reversion: il movimento contrario e' spesso un overshoot momentaneo. Qui lo SL NON e' piu' un livello toccato intrabar (disattivato: sl=None nei livelli, l'engine non lo testa MAI sui low/high) ma una CONFERMA sul CLOSE del bar: si esce solo se il prezzo CHIUDE oltre sl0. in levels(j): sl = None (no stop intrabar -> immune ai wick), TP fisso tp0. in after_bar(j): long (d=1): esci al close[j] se close[j] < sl0 - buffer*atr14[j] short (d=-1): esci al close[j] se close[j] > sl0 + buffer*atr14[j] ONESTO. L'uscita avviene al CLOSE[j], che puo' essere PEGGIO di sl0 quando il bar gappa o sfonda di slancio (il backtest paga il close reale, non sl0). Questo e' il costo del "confermare": si rinuncia a fermarsi esatti a sl0 in cambio di non farsi cacciare dai wick. Il buffer*atr richiede uno sfondamento ulteriore in chiusura -> meno uscite ma piu' lontane da sl0 quando scattano. ANTI-LOOK-AHEAD. levels(j) ritorna sl=None e tp0 (nessun dato del bar j). La decisione di uscita e' in after_bar(j), che PER CONTRATTO puo' leggere il bar j: close[j] e atr14[j] sono entrambi noti al close del bar j (atr14[j] = rolling mean di TR fino a j; TR[j] usa high/low/close[j] e close[j-1], tutti chiusi a j). Nessun dato > j entra nella decisione. -> contratto rispettato. NB il HARD_CAP/horizon=max_bars resta: se ne' TP ne' close-confirm scattano, il trade scade al close a max_bars come la base. Quindi un trade puo' restare in posizione PIU' a lungo della base (la base poteva stopparlo intrabar prima): l'avg_bars puo' SALIRE -> il ret va pesato per l'esposizione. GRID: buffer in {0.0, 0.25, 0.5} ATR (3 celle). """ import sys from pathlib import Path import numpy as np sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from exit_lab import ExitPolicy, evaluate # noqa: E402 class CloseConfirmSl(ExitPolicy): name = "close_confirm_sl" def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params): super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params) self.buffer = float(params.get("buffer", 0.0)) self.close = ctx["close"] self.atr = ctx["atr14"] def levels(self, j: int): # SL intrabar DISATTIVATO (immune ai wick): l'engine non testa low/high # contro lo stop. TP fisso tp0 intrabar invariato. return self.tp0, None, 1.0 def after_bar(self, j: int) -> bool: # Decisione sul CLOSE del bar j -> per contratto j e' leggibile. a = self.atr[j] if not np.isfinite(a): a = 0.0 cj = self.close[j] if self.d == 1: # long: stop confermato se chiude SOTTO sl0 - buffer*atr return cj < self.sl0 - self.buffer * a else: # short: stop confermato se chiude SOPRA sl0 + buffer*atr return cj > self.sl0 + self.buffer * a GRID = [ {"buffer": 0.0}, {"buffer": 0.25}, {"buffer": 0.5}, ] if __name__ == "__main__": evaluate(CloseConfirmSl, GRID)