"""EXIT-04 — breakeven (protezione). Quando il profitto su CLOSE supera k*ATR(entry) (ATR fissato all'entrata, atr14[i]), sposta lo SL a BREAKEVEN = entry +/- buffer. Il buffer e' a favore (0.2% = 2*fee_rt*entry) cosi' l'uscita a BE non e' in perdita per le fee. Il TP fisso tp0 RESTA invariato. horizon = max_bars (invariato). Una volta armato il breakeven, lo SL non torna mai indietro (cliquet): protegge il profitto gia' maturato senza allentare il rischio. Stop attivo nel bar j (solo dati <= j-1, anti-look-ahead): - trigger: si arma quando close[j-1] e' a favore di >= k*atr14[i] dall'entrata (atr14[i] = ATR all'entrata, costante; close[j-1] = ultimo close noto). - prima dell'arm: sl = sl0 (protezione iniziale invariata). - dopo l'arm: long : be = entry + buffer; sl = max(sl0, be) short: be = entry - buffer; sl = min(sl0, be) NB max/min con sl0 fa si' che lo SL non venga mai ALLENTATO: se sl0 fosse gia' oltre il BE (raro), resta sl0. Tipicamente sl0 e' sotto entry (long) e il BE lo alza -> stop piu' protettivo. GRID: k in {0.5, 1.0, 1.5} x buffer_frac in {0.0, 0.002} (6 celle). buffer = buffer_frac * entry. buffer_frac 0.002 = 0.2% = 2*fee_rt a favore. """ 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 Breakeven(ExitPolicy): name = "breakeven" def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params): super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params) self.k = float(params.get("k", 1.0)) self.buffer_frac = float(params.get("buffer_frac", 0.002)) self.close = ctx["close"] self.atr = ctx["atr14"] self.atr_entry = self.atr[i] # ATR all'entrata, costante self.buffer = self.buffer_frac * entry self.armed = False def levels(self, j: int): # arma il breakeven usando SOLO close[j-1] (ultimo close noto) e atr14[i] if not self.armed and self.atr_entry is not None and self.atr_entry == self.atr_entry: cprev = self.close[j - 1] profit = (cprev - self.entry) * self.d # >0 = a favore if profit >= self.k * self.atr_entry: self.armed = True if not self.armed: return self.tp0, self.sl0, 1.0 if self.d == 1: be = self.entry + self.buffer sl = max(self.sl0, be) # non allenta mai else: be = self.entry - self.buffer sl = min(self.sl0, be) return self.tp0, sl, 1.0 GRID = [ {"k": 0.5, "buffer_frac": 0.0}, {"k": 0.5, "buffer_frac": 0.002}, {"k": 1.0, "buffer_frac": 0.0}, {"k": 1.0, "buffer_frac": 0.002}, {"k": 1.5, "buffer_frac": 0.0}, {"k": 1.5, "buffer_frac": 0.002}, ] if __name__ == "__main__": evaluate(Breakeven, GRID)