"""EXIT-01 — trail_atr_ride: TP RIMOSSO, cavalcata pura con SL trailing chandelier. Idea: le fade mean-reversion escono oggi al TP fisso (alla media) + SL + max_bars. Qui togliamo il TP e lasciamo correre il trade, proteggendolo con un SL trailing "chandelier" a k*ATR dal massimo favorevole raggiunto. Lo stop puo' solo stringersi (mai allargarsi). Orizzonte esteso (cap HARD_CAP=240) per dare spazio al runner. Long: stop(j) = max( sl0, max(high[i..j-1]) - k*atr14[j-1] ) (sale, mai scende) Short: stop(j) = min( sl0, min(low[i..j-1]) + k*atr14[j-1] ) (scende, mai sale) ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1: - massimo/minimo favorevole sullo slice [i .. j-1] (mantenuto incrementalmente, aggiornato col bar j-1 prima di calcolare lo stop attivo nel bar j); - atr14[j-1] (indice causale). Nessun TP -> nessun fill parziale. after_bar non usato (chiusura solo a orizzonte/SL). GRID: k in {2.0, 3.0, 4.0} x horizon_mult in {2, 4} (6 celle). horizon = mult*mb cap 240. """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from exit_lab import ExitPolicy, evaluate, HARD_CAP class TrailAtrRide(ExitPolicy): name = "trail_atr_ride" def __init__(self, ctx, i, d, entry, tp0, sl0, mb, k=3.0, horizon_mult=4, **params): super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params) self.k = float(k) self.horizon = min(int(horizon_mult) * mb, HARD_CAP) # estremo favorevole sullo slice [i..j-1]; inizializzato al bar di entrata i # (il primo bar valutato e' j=i+1, dove lo slice [i..j-1]=[i..i] e' noto). self.fav_high = ctx["high"][i] self.fav_low = ctx["low"][i] self._last_seen = i # ultimo indice gia' incorporato nell'estremo # stop trailing monotono: parte da sl0 e puo' solo stringersi self.cur_stop = sl0 def levels(self, j): h = self.ctx["high"] l = self.ctx["low"] atr = self.ctx["atr14"] # incorpora i bar fino a j-1 (dati causali, gia' chiusi al poll del bar j) while self._last_seen < j - 1: self._last_seen += 1 if h[self._last_seen] > self.fav_high: self.fav_high = h[self._last_seen] if l[self._last_seen] < self.fav_low: self.fav_low = l[self._last_seen] a = atr[j - 1] if a != a: # NaN nei primi 14 bar -> resta sullo stop corrente return None, self.cur_stop, 1.0 if self.d == 1: cand = self.fav_high - self.k * a if cand > self.cur_stop: # lo stop long puo' solo SALIRE (stringersi) self.cur_stop = cand else: cand = self.fav_low + self.k * a if cand < self.cur_stop: # lo stop short puo' solo SCENDERE (stringersi) self.cur_stop = cand return None, self.cur_stop, 1.0 # TP rimosso GRID = [ {"k": k, "horizon_mult": m} for k in (2.0, 3.0, 4.0) for m in (2, 4) ] if __name__ == "__main__": evaluate(TrailAtrRide, GRID)