"""EXIT-03 — trail_pct: trailing percentuale dall'high-water-mark favorevole (sui CLOSE).

Idea: invece di un trail ATR (EXIT-01/02), lo stop segue l'estremo favorevole
RAGGIUNTO dai CLOSE a una distanza percentuale fissa p. Lo stop puo' solo
stringersi (mai allentarsi sotto sl0 lato rischio).

  Long : hwm = max(close[i..j-1]);  stop(j) = max(sl0, hwm*(1-p))   (sale, mai scende)
  Short: lwm = min(close[i..j-1]);  stop(j) = min(sl0, lwm*(1+p))   (scende, mai sale)

Due varianti:
  keep_tp=True  -> il TP fisso tp0 dall'entrata RESTA attivo (esci al primo dei due),
                   horizon = max_bars (invariato). Il trail morde solo se il TP non
                   viene raggiunto e il prezzo ha prima corso a favore e poi ritracciato.
  keep_tp=False -> TP RIMOSSO (cavalcata pura), horizon = 2*max_bars (cap HARD_CAP).
                   Si esce solo sul trailing-stop o a orizzonte.

ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1:
  - hwm/lwm sullo slice [i..j-1] dei CLOSE (mantenuto incrementalmente col bar j-1);
  - p e' una costante -> nessuna dipendenza da indicatori del bar j.
  L'hwm sui close (non sugli high) e' deliberato: il close e' il dato su cui il
  worker live decide al poll del tick; un hwm sugli high anticiperebbe un livello
  che il close non ha ancora confermato.

GRID: p in {0.005, 0.01, 0.02} x keep_tp in {True, False}  (6 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 TrailPct(ExitPolicy):
    name = "trail_pct"

    def __init__(self, ctx, i, d, entry, tp0, sl0, mb,
                 p=0.01, keep_tp=True, **params):
        super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
        self.p = float(p)
        self.keep_tp = bool(keep_tp)
        if not self.keep_tp:
            self.horizon = min(2 * mb, HARD_CAP)
        self.close = ctx["close"]
        # high/low-water-mark sui CLOSE, solo barre <= j-1; init = close d'entrata i
        self.hwm = self.close[i]
        self.lwm = self.close[i]
        self.last_seen = i           # ultimo indice gia' incorporato
        # stop trailing monotono: parte da sl0 e puo' solo stringersi
        self.cur_stop = sl0

    def _update_wm(self, upto: int) -> None:
        """Incorpora le barre (last_seen, upto] nell'estremo sui close. upto = j-1
        -> NON tocca il bar j (anti-look-ahead)."""
        while self.last_seen < upto:
            self.last_seen += 1
            cv = self.close[self.last_seen]
            if cv > self.hwm:
                self.hwm = cv
            if cv < self.lwm:
                self.lwm = cv

    def levels(self, j: int):
        self._update_wm(j - 1)                  # solo dati <= j-1
        if self.d == 1:
            cand = self.hwm * (1.0 - self.p)
            if cand > self.cur_stop:            # lo stop long puo' solo SALIRE
                self.cur_stop = cand
        else:
            cand = self.lwm * (1.0 + self.p)
            if cand < self.cur_stop:            # lo stop short puo' solo SCENDERE
                self.cur_stop = cand
        tp = self.tp0 if self.keep_tp else None
        return tp, self.cur_stop, 1.0


GRID = [
    {"p": p, "keep_tp": kt}
    for p in (0.005, 0.01, 0.02)
    for kt in (True, False)
]


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