"""EXIT-23 — sl_tp_ride: LOCK AL TP E CAVALCA. Idea (variante stretta di EXIT-09). Le fade escono al TP fisso (alla media). Qui si tiene il TP fisso ATTIVO come exit normale finche' il prezzo non lo SUPERA in chiusura. Quando close[j-1] supera tp0 a favore: - TP RIMOSSO (None): non si esce piu' al livello fisso; - SL spostato esattamente a tp0 (il profitto del TP e' LOCKATO: non si esce mai peggio del TP originale); - da tp0 in poi un trail chandelier k*ATR ancorato all'estremo favorevole visto dal bar di superamento in poi, SOLO-STRINGENTE (cricchetto, mai si allenta). Differenza dal 09: qui il floor e' ESATTAMENTE tp0 (cosi' come in 09, ma 09 ha horizon 3*mb fisso e griglia su k 1/2/3); qui il lock scatta subito al primo superamento in chiusura e la griglia esplora ANCHE l'horizon. State machine: FASE A (armed=False): tp=tp0, sl=sl0 -> identica a base. FASE B (armed): tp=None, sl = max(tp0, fav_high - k*atr) (long, cricchetto) = min(tp0, fav_low + k*atr) (short, cricchetto) ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1: - trigger di arma su close[j-1] (gia' chiuso al poll del bar j); - estremo favorevole post-superamento su slice [arm_idx .. j-1] (incrementale); - atr14[j-1] (indice causale). Nessun fill parziale (tp_frac sempre 1.0). after_bar non usato. GRID: k in {1.5, 2.5} x horizon in {2*mb, 4*mb} (4 celle). """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from exit_lab import ExitPolicy, evaluate, HARD_CAP # noqa: E402 class SlTpRide(ExitPolicy): name = "sl_tp_ride" def __init__(self, ctx, i, d, entry, tp0, sl0, mb, k=2.5, hmult=4, **params): super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params) self.k = float(k) self.horizon = min(int(hmult) * mb, HARD_CAP) self.close = ctx["close"] self.high = ctx["high"] self.low = ctx["low"] self.atr = ctx["atr14"] self.armed = False self.arm_idx = None self.fav_high = None self.fav_low = None self._last_seen = i # ultimo close esaminato per il trigger di arma self._fav_seen = None # ultimo bar incorporato nell'estremo favorevole self.cur_stop = None # stop trailing monotono in FASE B (floor tp0) def levels(self, j: int): c = self.close d = self.d # ---- FASE A: controlla l'arma sui close fino a j-1 (causali) ------------- if not self.armed: while self._last_seen < j - 1: self._last_seen += 1 cv = c[self._last_seen] crossed = (cv > self.tp0) if d == 1 else (cv < self.tp0) if crossed: self.armed = True self.arm_idx = self._last_seen self.fav_high = self.high[self.arm_idx] self.fav_low = self.low[self.arm_idx] self._fav_seen = self.arm_idx self.cur_stop = self.tp0 # SL spostato a tp0: profitto lockato break if not self.armed: return self.tp0, self.sl0, 1.0 # FASE A: TP/SL fissi # ---- FASE B: TP rimosso, trail chandelier con floor a tp0 ---------------- while self._fav_seen < j - 1: self._fav_seen += 1 if self.high[self._fav_seen] > self.fav_high: self.fav_high = self.high[self._fav_seen] if self.low[self._fav_seen] < self.fav_low: self.fav_low = self.low[self._fav_seen] a = self.atr[j - 1] if a == a: # non-NaN if d == 1: cand = max(self.fav_high - self.k * a, self.tp0) if cand > self.cur_stop: # cricchetto: solo sale self.cur_stop = cand else: cand = min(self.fav_low + self.k * a, self.tp0) if cand < self.cur_stop: # cricchetto: solo scende self.cur_stop = cand return None, self.cur_stop, 1.0 # TP rimosso in FASE B GRID = [{"k": k, "hmult": h} for k in (1.5, 2.5) for h in (2, 4)] if __name__ == "__main__": evaluate(SlTpRide, GRID)