"""
exit_fast_slope_experimental.py

[2026-09-18] NOT wired into monitor.py's live loop -- backtest-only.
User's proposed design, deliberately simpler than
exit_giveback_experimental.py's multi-vote sequence: sell the instant
price's own SHORT-TERM trajectory turns down (with real volume behind
it, not a one-print wiggle), and trust the existing entry gate
(smart_engine.evaluate() -- already allows immediate same-symbol
re-entry, no cooldown, per the 2026-09-18 GLOO decision) to buy back in
once the stock shows renewed strength. No new logic on the entry side
at all -- Stage 1's constructive check + Stage 2's 18s persistence hold
+ Stage 3's imbalance/resistance confirmation already ARE "volume and
all the enter indicators needed to allow entry."

Uses stream.py's SymbolBuffer.get_bars_sub() -- the 30-second-bucket
rolling buffer that's been built and fed by every trade tick since
2026-09-05 but never actually consumed by anything (its own docstring:
"inert until a stream-features consumer is built"). This is that
consumer. 30s buckets react roughly 2x faster than the 1-min bars
exit.py's existing deterioration layer reads, without going all the way
down to raw-tick noise.

Single-read trigger, no confirm-read wait -- deliberately, since the
whole point is speed. The regression slope over several sub-bars is
already a smoothed measure (unlike a raw two-tick delta), so it doesn't
need an additional multi-poll hysteresis layer on top the way the
imbalance-decline gate does.
"""

from dataclasses import dataclass, field

from indicators import normalized_slope_pct, classify_slope, atr as calc_atr

STATE_HOLD = "HOLD"
STATE_EXIT = "EXIT"

DEFAULT_CONFIG = {
    "sub_bars_lookback": 4,            # ~2 min at 30s buckets
    "min_flat_threshold_pct": 0.05,
    "flat_threshold_atr_fraction": 0.25,   # same ATR-scaling formula smart_engine/exit already use
    "volume_floor_ratio": 0.5,         # recent sub-bar volume must be >= this fraction of the trailing average
}


@dataclass
class FastSlopeDecision:
    symbol: str
    state: str = STATE_HOLD
    should_exit: bool = False
    reason: str = ""
    metrics: dict = field(default_factory=dict)


def _merge_cfg(cfg):
    return {**DEFAULT_CONFIG, **(cfg or {})}


def evaluate(symbol: str, bars: list, sub_bars: list, cfg: dict = None) -> FastSlopeDecision:
    """
    bars: standard 1-min rolling window (only used for ATR, to scale the
        flat threshold the same way the rest of the codebase does).
    sub_bars: stream.SymbolBuffer.get_bars_sub() -- 30s-bucket bars,
        oldest first, each {"t","o","h","l","c","v","tick_count",
        "upticks","downticks"}.
    """
    cfg = _merge_cfg(cfg)

    if len(bars) < 3 or len(sub_bars) < cfg["sub_bars_lookback"] + 2:
        return FastSlopeDecision(symbol=symbol, reason="insufficient sub-bar history")

    price = bars[-1]["c"]
    a = calc_atr(bars, period=min(14, max(2, len(bars) - 1)))
    atr_pct = (a / price * 100.0) if price else 0.0
    flat_thresh = max(cfg["min_flat_threshold_pct"], atr_pct * cfg["flat_threshold_atr_fraction"])

    window = sub_bars[-cfg["sub_bars_lookback"]:]
    closes = [b["c"] for b in window]
    fast_slope_pct = normalized_slope_pct(closes)
    fast_trend = classify_slope(fast_slope_pct, flat_thresh)

    recent_vol = sum(b["v"] for b in window)
    trailing = sub_bars[-(cfg["sub_bars_lookback"] * 3):]
    trailing_avg = (sum(b["v"] for b in trailing) / len(trailing)) * cfg["sub_bars_lookback"] if trailing else 0
    volume_confirmed = trailing_avg <= 0 or recent_vol >= cfg["volume_floor_ratio"] * trailing_avg

    metrics = {"price": price, "atr_pct": round(atr_pct, 3), "flat_thresh": round(flat_thresh, 4),
               "fast_slope_pct": round(fast_slope_pct, 4), "fast_trend": fast_trend,
               "recent_sub_vol": round(recent_vol, 1), "trailing_avg_vol": round(trailing_avg, 1),
               "volume_confirmed": volume_confirmed}

    if fast_trend == "negative" and volume_confirmed:
        return FastSlopeDecision(
            symbol=symbol, state=STATE_EXIT, should_exit=True,
            reason=(f"fast slope down: {fast_slope_pct:.3f}% over last {cfg['sub_bars_lookback']} "
                    f"sub-bars (flat band {flat_thresh:.3f}%), volume confirmed "
                    f"({recent_vol:.0f} vs {trailing_avg:.0f} trailing avg)"),
            metrics=metrics)

    if fast_trend == "negative":
        return FastSlopeDecision(
            symbol=symbol, state=STATE_HOLD,
            reason=f"slope down ({fast_slope_pct:.3f}%) but volume too thin to confirm -- holding",
            metrics=metrics)

    return FastSlopeDecision(symbol=symbol, state=STATE_HOLD,
                              reason=f"healthy (fast slope {fast_slope_pct:.3f}%, {fast_trend})",
                              metrics=metrics)
