"""EXIT-09 — tp_extend_momentum: cavalca il momentum OLTRE il TP.

Idea: le fade escono al TP fisso (alla media). Ma quando il movimento NON e'
esaurito e sfonda il TP in chiusura, vogliamo provare a cavalcarlo invece di
prendere solo il TP. State machine a 2 fasi:

  FASE A (default, TP intatto): tp = tp0, sl = sl0. Exit standard alla media.
    Finche' close[j-1] NON ha superato tp0 in chiusura, ci si comporta come base.

  FASE B (armata quando close[j-1] supera tp0 a favore):
    - TP RIMOSSO (None): non si esce piu' al livello fisso;
    - attiva un trail "chandelier" a k*ATR ancorato all'ESTREMO favorevole visto
      DAL bar di superamento in poi (high per long, low per short);
    - FLOOR a tp0: lo stop trailing non scende mai sotto tp0 (long) / non sale mai
      sopra tp0 (short), cosi' NON si esce MAI peggio del TP originale. Lo stop e'
      inoltre solo-stringente (cricchetto): puo' solo avvicinarsi al prezzo.

  Trigger di arma (su close[j-1], a favore):
    long :  close[j-1] > tp0   ->   arma
    short:  close[j-1] < tp0   ->   arma

  Trail attivo (FASE B), con floor a tp0:
    long :  stop = max( tp0, fav_high_post - k*atr14[j-1] )   (cricchetto: solo sale)
    short:  stop = min( tp0, fav_low_post  + k*atr14[j-1] )   (cricchetto: solo scende)

Orizzonte esteso a 3*mb (cap HARD_CAP=240) per dare spazio al runner post-TP.

ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1:
  - il trigger di arma legge close[j-1] (gia' chiuso al poll del bar j);
  - l'estremo favorevole post-superamento e' sullo slice [arm_idx .. j-1]
    (mantenuto incrementalmente, incorporando i bar fino a j-1);
  - atr14[j-1] (indice causale).
Nessun fill parziale (tp_frac sempre 1.0). after_bar non usato.

GRID: k in {1.0, 2.0, 3.0} (3 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 TpExtendMomentum(ExitPolicy):
    name = "tp_extend_momentum"

    def __init__(self, ctx, i, d, entry, tp0, sl0, mb, k=2.0, **params):
        super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
        self.k = float(k)
        self.horizon = min(3 * mb, HARD_CAP)
        self.close = ctx["close"]
        self.high = ctx["high"]
        self.low = ctx["low"]
        self.atr = ctx["atr14"]
        # FASE A finche' armed=False. Si arma quando close[j-1] supera tp0 a favore.
        self.armed = False
        self.arm_idx = None              # bar dal quale si ancora l'estremo favorevole
        self.fav_high = None             # estremo favorevole post-superamento (long)
        self.fav_low = None              # estremo favorevole post-superamento (short)
        self._last_seen = i              # ultimo close gia' esaminato per il trigger
        self._fav_seen = None            # ultimo bar gia' incorporato nell'estremo
        # stop trailing monotono in FASE B: parte dal floor tp0, solo si stringe
        self.cur_stop = None

    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:
                    # arma: ancora l'estremo favorevole DAL bar di superamento
                    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       # floor di partenza = tp0
                    break
            if not self.armed:
                return self.tp0, self.sl0, 1.0     # ancora FASE A: TP/SL fissi

        # ---- FASE B -> TP rimosso, trail chandelier ancorato post-superamento -----
        # incorpora i bar [arm_idx .. j-1] nell'estremo favorevole (dati causali)
        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 = self.fav_high - self.k * a
                cand = max(cand, self.tp0)         # floor: mai sotto il TP
                if cand > self.cur_stop:           # cricchetto: solo sale
                    self.cur_stop = cand
            else:
                cand = self.fav_low + self.k * a
                cand = min(cand, self.tp0)         # floor: mai sopra il TP
                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} for k in (1.0, 2.0, 3.0)]


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