"""
exit_v2.py -- [2026-09-29] Exits the user asked for on 9/29 ("add the smooth exit and add
the breakeven rule stop trail"), ported from trade1/exit_rules.py modes "step_r" and
"smooth" (tested there on fixed entries: 21 days smooth break 0.6 +$1,213, 0.8 +$970 vs
exit B +$407; step_r + reversal breakeven -$547 vs hold -$1,275 on the first entries).

One managed stop + one trend check, evaluated every poll:
  STOP TRAIL (trail: true)
    start   the entry's stop, at least min_stop_pct below the buy price; R = buy - start
    steps   every time price has risen another R above the buy, the stop rises by R
            (after the first step it sits at the buy price = BREAKEVEN)
    trail   from breakeven on, the stop also trails atr_mult x the 1-min ATR (Wilder,
            atr_period bars) under the high since the buy; it never moves down
    reversal breakeven (reversal_breakeven_atr > 0): at the first 1-min bar after the
            buy that closes >= 1 ATR under the high since the buy, if that high was
            < reversal_breakeven_atr x ATR above the buy, the stop moves to the buy price
    sells on a touch of the stop
  SMOOTH TREND (smooth: true)
    smoothness = R^2 of the last smooth_bars finished 1-min closes, signed by the slope;
    armed once it reaches smooth_arm after the buy; armed -> sell when it drops under
    smooth_break (the steady climb broke)
replace_j: true -> these are the ONLY exits besides the 15:55 sell-all (J's giveback,
stall, imbalance, weakening and hard-stop layers are skipped); false -> they run first,
then J's layers as before.
"""
from dataclasses import dataclass, field
from datetime import datetime

DEFAULTS = {
    "enabled": False,
    "replace_j": True,
    "trail": True,
    "min_stop_pct": 1.0,
    "atr_mult": 2.0,
    "atr_period": 14,
    "reversal_breakeven_atr": 2.0,
    "smooth": True,
    "smooth_bars": 30,
    "smooth_arm": 0.8,
    "smooth_break": 0.6,
}


@dataclass
class ExitV2Decision:
    should_exit: bool = False
    state: str = "HOLD"
    reason: str = ""
    metrics: dict = field(default_factory=dict)


def _atr_1m(done, period):
    atr = None
    for prev, b in zip(done, done[1:]):
        tr = max(b["h"] - b["l"], abs(b["h"] - prev["c"]), abs(b["l"] - prev["c"]))
        atr = tr if atr is None else atr + (tr - atr) / period
    return atr


def _smoothness(closes):
    n = len(closes)
    if n < 5:
        return None
    xm, ym = (n - 1) / 2, sum(closes) / n
    sxx = sum((k - xm) ** 2 for k in range(n))
    slope = sum((k - xm) * (y - ym) for k, y in enumerate(closes)) / sxx
    sst = sum((y - ym) ** 2 for y in closes)
    if sst == 0:
        return 0.0
    ssr = sum((y - (ym + slope * (k - xm))) ** 2 for k, y in enumerate(closes))
    return (1 - ssr / sst) * (1 if slope > 0 else -1)


def evaluate(bars, position, state, cfg=None) -> ExitV2Decision:
    """bars: today's 1-min bars incl. the forming one; position: {"entry_price",
    "stop_price", "entry_time" (iso)}; state: per-position dict kept by the caller."""
    c = {**DEFAULTS, **(cfg or {})}
    price = bars[-1]["c"]
    entry = position["entry_price"]
    done = bars[:-1]
    t_buy = datetime.fromisoformat(position["entry_time"]).replace(second=0, microsecond=0)
    if "stop" not in state:
        stop0 = min(position["stop_price"], entry * (1 - c["min_stop_pct"] / 100.0))
        state.update(stop0=stop0, stop=stop0, steps=0, high=entry, how="entry stop", armed=False,
                     peak_r2=None, reversal_seen=False, bar_high=entry)
    state["high"] = max(state["high"], price)
    R = entry - state["stop0"]
    atr = _atr_1m(done[-(int(c["atr_period"]) * 3):], int(c["atr_period"])) if len(done) >= 2 else None

    if c["trail"]:
        if R > 0:
            steps = int((state["high"] - entry) // R)
            if steps > state["steps"]:
                state["steps"] = steps
                new = state["stop0"] + steps * R
                if new > state["stop"]:
                    state["stop"], state["how"] = new, ("breakeven (+1R)" if steps == 1 else f"step +{steps - 1}R")
        k_rev = c.get("reversal_breakeven_atr") or 0
        if k_rev and atr and not state["reversal_seen"] and done and done[-1]["t"] >= t_buy:
            last = done[-1]
            state["bar_high"] = max(state["bar_high"], last["h"])
            if state["bar_high"] > entry and last["c"] <= state["bar_high"] - atr:
                state["reversal_seen"] = True
                run = state["bar_high"] - entry
                if run < k_rev * atr and entry > state["stop"]:
                    state["stop"], state["how"] = entry, f"first reversal after only {run / atr:.1f} ATR -> buy price"
        if state["steps"] >= 1 and atr:
            trail = state["high"] - c["atr_mult"] * atr
            if trail > state["stop"]:
                state["stop"], state["how"] = trail, f"trail {c['atr_mult']}x1m-ATR ({atr:.3f})"

    r2 = None
    if c["smooth"]:
        n = int(c["smooth_bars"])
        if len(done) >= n:
            r2 = _smoothness([b["c"] for b in done[-n:]])
        if r2 is not None and done and done[-1]["t"] >= t_buy and r2 >= c["smooth_arm"]:
            state["armed"] = True
        if r2 is not None:
            state["peak_r2"] = r2 if state["peak_r2"] is None else max(state["peak_r2"], r2)

    stop = state["stop"]
    m = {"stop": round(stop, 4), "stop_from": state["how"], "R": round(R, 4), "steps": state["steps"],
         "atr_1m": round(atr, 4) if atr else None, "high_since_entry": state["high"],
         "smoothness": round(r2, 3) if r2 is not None else None, "armed": state["armed"],
         "peak_smoothness": round(state["peak_r2"], 3) if state["peak_r2"] is not None else None}
    if price <= stop:
        return ExitV2Decision(True, "STOP", f"exit_v2 stop: price {price:.2f} <= {stop:.2f} ({state['how']})", m)
    if c["smooth"] and state["armed"] and r2 is not None and r2 < c["smooth_break"]:
        return ExitV2Decision(True, "SMOOTH_BREAK",
                              f"exit_v2 smooth break: smoothness {r2:.2f} < {c['smooth_break']} (trend broke)", m)
    return ExitV2Decision(False, "HOLD", f"exit_v2 hold: stop {stop:.2f} ({state['how']})"
                          + (f", smoothness {r2:.2f}{' armed' if state['armed'] else ''}" if r2 is not None else ""), m)
