"""EXIT-17 — wide_sl_trail: RESPIRA POI PROTEGGI. Idea: l'SL fisso all'entrata (sl0) puo' essere troppo stretto -> stoppa fade che poi sarebbero rientrate alla media. Qui ALLARGHIAMO l'SL iniziale a sl_w = entry - w*(entry - sl0) (long, w>1 -> stop piu' lontano = piu' respiro) sl_w = entry + w*(sl0 - entry) (short) ma attiviamo SUBITO un trailing chandelier a k*ATR dall'estremo favorevole. Lo stop attivo nel bar j e' il PIU' PROTETTIVO fra l'SL allargato e il chandelier: long : sl = max(sl_w, max(high[i..j-1]) - k*ATR[j-1]) short: sl = min(sl_w, min(low[i..j-1]) + k*ATR[j-1]) TP fisso tp0 invariato; horizon = max_bars invariato. Logica: finche' il prezzo NON corre a favore, il rischio e' l'SL allargato (respiro: si tollera un ritracciamento iniziale piu' ampio nella speranza che la fade rientri). Appena il prezzo corre a favore, il chandelier sale sopra sl_w e protegge il profitto come un trail normale. Differenza da EXIT-02 (che usa max(sl0, chand)): qui il floor iniziale e' PIU' LARGO di sl0, quindi early il rischio per-trade e' maggiore (peggiora i loser veloci) ma si salvano fade che con sl0 sarebbero state stoppate giusto prima del rientro. ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1 (estremo favorevole su [i..j-1] mantenuto incrementalmente fino a j-1; atr14[j-1]). after_bar non usato. GRID: w in {1.5, 2.0} x k in {2.0, 3.0} (4 celle). """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from exit_lab import ExitPolicy, evaluate # noqa: E402 class WideSLTrail(ExitPolicy): name = "wide_sl_trail" def __init__(self, ctx, i, d, entry, tp0, sl0, mb, w=1.5, k=2.0, **params): super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params) self.w = float(w) self.k = float(k) self.high = ctx["high"] self.low = ctx["low"] self.atr = ctx["atr14"] # SL iniziale ALLARGATO: piu' lontano dall'entrata (w>1) if d == 1: self.sl_w = entry - self.w * (entry - sl0) else: self.sl_w = entry + self.w * (sl0 - entry) # estremo favorevole running (solo barre <= j-1); init = barra d'entrata i self.run_hi = self.high[i] self.run_lo = self.low[i] self.last_seen = i def _update_running(self, upto: int) -> None: """Incorpora le barre (last_seen, upto] nell'estremo favorevole. upto = j-1 -> NON tocca il bar j (anti-look-ahead).""" while self.last_seen < upto: self.last_seen += 1 if self.high[self.last_seen] > self.run_hi: self.run_hi = self.high[self.last_seen] if self.low[self.last_seen] < self.run_lo: self.run_lo = self.low[self.last_seen] def levels(self, j: int): self._update_running(j - 1) # solo dati <= j-1 a = self.atr[j - 1] if a is None or a != a: # NaN nei primi 14 bar -> usa l'SL allargato return self.tp0, self.sl_w, 1.0 if self.d == 1: chand = self.run_hi - self.k * a sl = max(self.sl_w, chand) # il piu' protettivo (stop piu' alto) else: chand = self.run_lo + self.k * a sl = min(self.sl_w, chand) # il piu' protettivo (stop piu' basso) return self.tp0, sl, 1.0 GRID = [ {"w": w, "k": k} for w in (1.5, 2.0) for k in (2.0, 3.0) ] if __name__ == "__main__": evaluate(WideSLTrail, GRID)