"""EXIT-02 — trail_atr_keep_tp.

Chandelier trailing stop a k*ATR dall'estremo favorevole RAGGIUNTO dall'entrata,
MA il TP fisso tp0 dall'entrata RESTA attivo: si esce al PRIMO dei due (il TP al
livello, oppure il trail). horizon = max_bars (invariato).

Stop attivo nel bar j (solo dati <= j-1, anti-look-ahead):
  long : chand = max(high[i..j-1]) - k*atr14[j-1];  sl = max(sl0, chand)
  short: chand = min(low[i..j-1])  + k*atr14[j-1];  sl = min(sl0, chand)

Il max(sl0, chand) (per il long) tiene la protezione iniziale a sl0 e lascia che
il trail TIGHTEN solo quando il prezzo corre a favore -> "ride" controllato che
non allenta mai il rischio iniziale. Il TP non viene toccato: una fade che torna
alla media esce comunque al TP come la base; il trail morde solo quando il TP non
viene raggiunto e il prezzo ha prima corso a favore e poi ritracciato.

GRID: k in {1.5, 2.0, 3.0, 4.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 TrailATRKeepTP(ExitPolicy):
    name = "trail_atr_keep_tp"

    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", 2.0))
        self.high = ctx["high"]
        self.low = ctx["low"]
        self.atr = ctx["atr14"]
        # 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  # ultimo indice gia' incorporato nel running extremum

    def _update_running(self, upto: int) -> None:
        """Incorpora le barre (last_seen, upto] nell'estremo running. upto = j-1,
        quindi 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 -> nessun trail, usa sl0
            return self.tp0, self.sl0, 1.0
        if self.d == 1:
            chand = self.run_hi - self.k * a
            sl = max(self.sl0, chand)          # piu' protettivo (stop piu' alto)
        else:
            chand = self.run_lo + self.k * a
            sl = min(self.sl0, chand)          # piu' protettivo (stop piu' basso)
        return self.tp0, sl, 1.0


GRID = [{"k": 1.5}, {"k": 2.0}, {"k": 3.0}, {"k": 4.0}]


if __name__ == "__main__":
    evaluate(TrailATRKeepTP, GRID)
