"""EXIT-19 — TP RIMOSSO, exit al CANALE DONCHIAN OPPOSTO (donchian_trail).

Idea: la fade attuale esce al TP (sulla media) + SL fisso (ATR dall'entrata) +
max_bars. Qui TOGLIAMO il TP e usiamo come unica uscita di prezzo uno STOP
DINAMICO ancorato al canale Donchian OPPOSTO alla direzione del trade:

  long  (d=1):  stop(j) = min(low[j-n .. j-1])     (canale inferiore)
  short (d=-1): stop(j) = max(high[j-n .. j-1])    (canale superiore)

Per un long che funziona (il prezzo risale verso la media) il canale inferiore
SALE bar dopo bar -> lo stop segue e blocca profitto; usciamo quando il prezzo
ritraccia sotto il minimo recente. Simmetrico short.

FLOOR a sl0 (mai PEGGIO dello SL originale): il livello attivo e' floorato alla
protezione iniziale -> non si allenta mai oltre sl0.
  long :  stop = max(channel_low, sl0)
  short:  stop = min(channel_high, sl0)

HORIZON = 4*mb (cap HARD_CAP=240): senza TP la posizione puo' restare a lungo,
quindi diamo molto piu' respiro al time-stop; l'uscita "naturale" e' il canale.

DIFFERENZA da EXIT-18 (swing_stop): qui (a) NON c'e' TP affatto (li' tp0 restava),
(b) niente cricchetto persistente: lo stop e' il canale RICALCOLATO ogni bar (puo'
anche allentarsi rispetto al bar prima, ma mai sotto sl0 grazie al floor),
(c) horizon esteso 4x. E' una uscita puramente trend-following/Donchian innestata
su un ingresso mean-reversion.

ANTI-LOOK-AHEAD (contratto): levels(j) usa SOLO dati con indice <= j-1:
  - min/max sullo slice [j-n .. j-1] (lookback chiuso a j-1, lo[lo:hi] con hi=j);
  - nessun dato del bar j entra nel livello attivo nel bar j;
  - non si guarda mai high/low[j] per decidere lo stop attivo nel bar j.

PRIOR dal repo (ladder scartato): il TP della fade sta alla MEDIA, dove il
movimento e' esaurito; "il runner non corre". Quindi togliendo il TP rischiamo di
restare in posizione MENTRE il prezzo ristagna/rientra, pagando giveback e fee.
Il canale opposto dovrebbe limitare il giveback, ma la mean-reversion fa rientrare
il prezzo prima che il canale si stringa -> probabile uscita PEGGIORE del TP.
Lo misuriamo senza pregiudizio.

GRID: n in {10, 20, 30} (3 celle).
"""
import sys
from pathlib import Path

import numpy as np

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from exit_lab import ExitPolicy, evaluate  # noqa: E402


class DonchianTrail(ExitPolicy):
    name = "donchian_trail"

    def __init__(self, ctx, i, d, entry, tp0, sl0, mb, n=20, **params):
        super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
        self.n = int(n)
        self.low = ctx["low"]
        self.high = ctx["high"]
        # TP rimosso, horizon esteso 4x (il cap a HARD_CAP lo applica l'engine)
        self.horizon = 4 * mb

    def levels(self, j: int):
        d = self.d
        # canale opposto causale: slice [lo : j] => indici <= j-1
        lo = max(self.i, j - self.n)
        hi = j
        if hi <= lo:
            # primo bar dopo l'entrata: nessuna finestra -> usa solo sl0 (no TP)
            return None, self.sl0, 1.0
        if d == 1:
            ch = float(np.min(self.low[lo:hi]))
            stop = max(ch, self.sl0)             # floor: mai sotto sl0
        else:
            ch = float(np.max(self.high[lo:hi]))
            stop = min(ch, self.sl0)             # floor: mai sopra sl0
        return None, stop, 1.0                   # TP = None (rimosso)


GRID = [
    {"n": 10},
    {"n": 20},
    {"n": 30},
]


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