"""SH01 EXIT POLICY 07 — LOSER time-stop condizionale (after_bar).

Idea: SH01 esce a orizzonte fisso H=12. Se a un check-point intermedio j=i+m il
trade e' GIA' in perdita oltre una soglia, esci subito al close invece di tenere
fino a H. L'ipotesi (lezione exit-lab fade: tagliare i loser presto rischia di
tagliare anche i winner in drawdown temporaneo): su un orizzonte FISSO di 12 bar
forse un loser conclamato a meta' corsa raramente recupera, mentre i winner del
modello partono subito (asimmetria). Il time-stop e' UNA volta sola (al bar m),
non un trailing: non insegue il prezzo, condiziona solo l'uscita a un istante.

Regola (after_bar):
    al bar j == i + m:  se  (close[j]-entry)/entry * d  <  -x * ATR14[i] / entry
                        esci al close del bar j.
Equivalente: directional_move[j] < -x*ATR14[i].  x=0.0 => esci se in QUALSIASI
perdita direzionale al bar m.

Griglia m in {2, 3, 4, 6} x x in {0.0, 0.5, 1.0}.

ANTI-LOOK-AHEAD: ATR14[i] e entry=close[i] fissati all'ingresso (dati <= i);
after_bar decide sul close del bar j (dati <= j, eseguibile al poll del tick).
Nessun indicatore al bar j stesso.

    cd /opt/docker/PythagorasGoal && uv run python scripts/analysis/sh01_exit_policies/07_loser_timestop.py
"""
from __future__ import annotations

import sys

sys.path.insert(0, "/opt/docker/PythagorasGoal")

from scripts.analysis.sh01_exit_lab import (  # noqa: E402
    ExitPolicy, evaluate, load_sleeves, simulate, OOS_START_MS,
)


class LoserTimestop(ExitPolicy):
    def __init__(self, m: int, x: float):
        self.m = int(m)
        self.x = float(x)
        self.name = f"loser_timestop m={m} x={x:g}"

    def open_trade(self, ctx: dict, i: int, d: int) -> dict:
        return {
            "entry": float(ctx["close"][i]),
            "atr": float(ctx["atr14"][i]),
        }

    def after_bar(self, ctx: dict, i: int, d: int, j: int, st: dict) -> bool:
        if j != i + self.m:
            return False
        move = (ctx["close"][j] - st["entry"]) * d          # directional, in price
        thresh = -self.x * st["atr"]
        return move < thresh


GRID_M = [2, 3, 4, 6]
GRID_X = [0.0, 0.5, 1.0]


def _fmt(m):
    return (f"ret={m['ret']:>+7.0f}% dd={m['dd']:>4.0f}% shrp={m['sharpe']:>5.2f} "
            f"worst={m['worst']:>+5.1f}% stop={m['stop_rate']:>4.1f}% n={m['trades']}")


def main():
    sleeves = load_sleeves()

    base = {}
    for a in ("BTC", "ETH"):
        ctx = sleeves[a]
        base[a] = {
            "train": simulate(ctx, ExitPolicy(), t_hi=OOS_START_MS),
            "oos": simulate(ctx, ExitPolicy(), t_lo=OOS_START_MS),
        }

    print("=" * 110)
    print("BASELINE (exit orizzonte puro):")
    for a in ("BTC", "ETH"):
        print(f"  {a} TRAIN {_fmt(base[a]['train'])}")
        print(f"  {a} OOS   {_fmt(base[a]['oos'])}")

    print("=" * 110)
    print("GRID — TRAIN ONLY (selezione parametri):  m x rows")
    train_res = {}
    for mm in GRID_M:
        for xx in GRID_X:
            pol = LoserTimestop(mm, xx)
            row = {}
            for a in ("BTC", "ETH"):
                row[a] = simulate(sleeves[a], pol, t_hi=OOS_START_MS)
            train_res[(mm, xx)] = row
            print(f"  m={mm} x={xx:g} | BTC {_fmt(row['BTC'])}")
            print(f"           | ETH {_fmt(row['ETH'])}")

    print("=" * 110)
    print("OOS (verdetto, intera griglia per ispezione plateau):")
    for mm in GRID_M:
        for xx in GRID_X:
            pol = LoserTimestop(mm, xx)
            line_b = simulate(sleeves["BTC"], pol, t_lo=OOS_START_MS)
            line_e = simulate(sleeves["ETH"], pol, t_lo=OOS_START_MS)
            print(f"  m={mm} x={xx:g} | BTC OOS {_fmt(line_b)}")
            print(f"           | ETH OOS {_fmt(line_e)}")


if __name__ == "__main__":
    main()
