"""EXIT-05 — ratchet: stop a cricchetto sul profitto su close (high-water-mark).

Idea: una volta che il trade ha messo a segno profitto (su CLOSE), proteggiamo
una FRAZIONE f del profitto migliore raggiunto. hwm = miglior close a favore visto
finora; quando hwm e' a favore dell'entrata, lo stop si porta a
  long :  stop = entry + f*(hwm - entry)
  short:  stop = entry - f*(entry - hwm)
Lo stop e' SOLO-STRINGENTE (cricchetto): puo' solo avvicinarsi al prezzo, mai
allentarsi. Parte da sl0 (protezione iniziale invariata finche' il ratchet non
supera sl0).

Differenza dal trailing chandelier (EXIT-01): qui lo stop e' ancorato all'ENTRY
+ frazione del profitto su close (non al massimo high - k*ATR). Cattura profitto
in % del guadagno realizzato; con f<1 lascia un cuscinetto sotto l'hwm.

VARIANTI:
  - keep_tp=True : il TP fisso tp0 resta (exit standard alla media) + ratchet di
    backup sullo SL. horizon = max_bars.
  - keep_tp=False: TP RIMOSSO -> ride puro protetto dal cricchetto, horizon=2*mb
    (cap HARD_CAP). Qui il ratchet e' l'unica uscita oltre l'orizzonte.

ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1:
  - hwm calcolato sui close[i .. j-1] (mantenuto incrementalmente, aggiornato col
    bar j-1 prima di calcolare lo stop attivo nel bar j; close[i]=entry e' incluso
    cosi' hwm>=entry sempre, lo stop non scende mai sotto entry una volta armato).
Nessun TP nella variante ride -> nessun fill parziale. after_bar non usato.

GRID: f in {0.3, 0.5, 0.7} 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 Ratchet(ExitPolicy):
    name = "ratchet"

    def __init__(self, ctx, i, d, entry, tp0, sl0, mb, f=0.5, keep_tp=True, **params):
        super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
        self.f = float(f)
        self.keep_tp = bool(keep_tp)
        self.close = ctx["close"]
        if not self.keep_tp:
            self.horizon = min(2 * mb, HARD_CAP)
        # high-water-mark del close a favore sullo slice [i..j-1]; il primo bar
        # valutato e' j=i+1 -> slice [i..i] = close[i] = entry (gia' incorporato).
        self.hwm = self.close[i]                # = entry
        self._last_seen = i
        self.armed = False                      # diventa True quando hwm > entry (a favore)
        # stop a cricchetto: parte da sl0, puo' solo stringersi
        self.cur_stop = sl0

    def levels(self, j: int):
        c = self.close
        d = self.d
        # incorpora i close fino a j-1 (causali, gia' chiusi al poll del bar j)
        while self._last_seen < j - 1:
            self._last_seen += 1
            cv = c[self._last_seen]
            if d == 1:
                if cv > self.hwm:
                    self.hwm = cv
            else:
                if cv < self.hwm:
                    self.hwm = cv
        tp = self.tp0 if self.keep_tp else None
        # profitto su close del miglior bar a favore (>=0 perche' hwm parte da entry)
        prof = (self.hwm - self.entry) * d
        if prof > 0:                            # il trade e' andato a favore: arma il ratchet
            self.armed = True
            if d == 1:
                cand = self.entry + self.f * (self.hwm - self.entry)
                if cand > self.cur_stop:        # cricchetto: lo stop long solo SALE
                    self.cur_stop = cand
            else:
                cand = self.entry - self.f * (self.entry - self.hwm)
                if cand < self.cur_stop:        # cricchetto: lo stop short solo SCENDE
                    self.cur_stop = cand
        return tp, self.cur_stop, 1.0


GRID = [
    {"f": f, "keep_tp": keep_tp}
    for f in (0.3, 0.5, 0.7)
    for keep_tp in (True, False)
]


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