"""Enhanced Mean Reversion — multi-scale overextension detection.

The basic MeanReversionTracker uses a 10-bar SMA on 1m candles, which is
too fast for 1d prediction horizons. This enhanced version uses:

1. **Multi-scale deviation** — measures overextension from SMAs at 1h and
   4h/1d timeframes. Only signals when BOTH scales agree (price is
   extended at multiple resolutions).

2. **RSI extremes** — adds RSI on 1h candles as a second overextension
   measure. Only contributes when RSI is extreme (<25 or >75).

3. **Volatility-adjusted thresholds** — the size of "overextended" changes
   with volatility. Uses ATR to normalize: 2-ATR deviation in low-vol
   is more meaningful than 2-ATR in high-vol.

4. **Volume exhaustion** — extreme moves on declining volume suggest the
   move is running out of steam. Volume divergence boosts the
   mean-reversion signal.

Designed for sideways/ranging markets where EnhancedTracker struggles.
"""

from __future__ import annotations

from vanta.tracker import TrackerBase
from vanta.indicators import EMA, SMA, ATR, RSI, VolumeRatio


class MeanReversionEnhancedTracker(TrackerBase):
    """Multi-scale mean reversion with volume exhaustion."""

    def __init__(self):
        super().__init__()

        # ── 1h price levels ───────────────────────────────────────
        self.sma_1h_20 = SMA(period=20)
        self.sma_1h_50 = SMA(period=50)
        self.atr_1h = ATR(period=14)
        self.rsi_1h = RSI(period=14)
        self._last_1h_close: float = 0.0

        # ── 1m feed for continuous price tracking ─────────────────
        self.sma_1m_fast = SMA(period=60)  # 1-hour SMA on 1m
        self._last_close: float = 0.0

        # ── Volume ────────────────────────────────────────────────
        self.vol_ratio = VolumeRatio(period=20)

        # ── Signal scaling ────────────────────────────────────────
        self.price_sma = SMA(period=14)
        self.atr_1m = ATR(period=14)
        self.atr_sma_1m = SMA(period=60)

    # ── tick ──────────────────────────────────────────────────────

    def tick(self, data: dict) -> None:
        super().tick(data)

        for c in data.get("candles_1m", []):
            p = float(c["close"])
            h, l = float(c["high"]), float(c["low"])
            v = float(c.get("volume", 0))
            self.sma_1m_fast.update(p)
            self.atr_1m.update(h, l, p)
            if self.atr_1m.ready:
                self.atr_sma_1m.update(self.atr_1m.value)
            self.price_sma.update(p)
            self.vol_ratio.update(v)
            self._last_close = p

        for c in data.get("candles_1h", []):
            p = float(c["close"])
            h = float(c["high"])
            l = float(c["low"])
            self.sma_1h_20.update(p)
            self.sma_1h_50.update(p)
            self.atr_1h.update(h, l, p)
            self.rsi_1h.update(p)
            self._last_1h_close = p

    # ── predict ───────────────────────────────────────────────────

    def predict(self, symbol: str, horizon_seconds: int, step_seconds: int) -> dict:
        if not self._ready():
            return {"expected_return": 0.0}

        # Sub-signals
        dev_short = self._deviation_signal(self.sma_1h_20)
        dev_long = self._deviation_signal(self.sma_1h_50)
        rsi_sig = self._rsi_signal()

        # Multi-scale agreement: both scales must agree for strong signal
        if dev_short * dev_long > 0:
            # Same direction — strong mean-reversion signal
            # Weight more towards the shorter-term deviation
            raw = 0.50 * dev_short + 0.30 * dev_long + 0.20 * rsi_sig
        else:
            # Disagreement — only use the more extreme one, reduced
            if abs(dev_short) > abs(dev_long):
                raw = 0.30 * dev_short + 0.20 * rsi_sig
            else:
                raw = 0.30 * dev_long + 0.20 * rsi_sig

        # Volume exhaustion: boost signal when volume is declining
        # (move running out of steam → reversion more likely)
        if self.vol_ratio.ready:
            vr = self.vol_ratio.value or 1.0
            if vr < 0.7 and abs(raw) > 0.2:
                raw *= 1.3  # low volume + extended → boost reversion
            elif vr > 2.0 and abs(raw) > 0.2:
                raw *= 0.5  # high volume extension → momentum, not reversion

        raw = max(-1.0, min(1.0, raw))
        scaled = self._scale(raw)
        return {"expected_return": scaled}

    # ── Sub-signals ───────────────────────────────────────────────

    def _deviation_signal(self, sma: SMA) -> float:
        """Mean-reversion signal from price deviation. ±1 range.

        Normalised by ATR so thresholds adapt to volatility.
        Negative when price is above SMA (expect reversion down).
        """
        if not sma.ready or not self.atr_1h.ready:
            return 0.0

        distance = self._last_1h_close - sma.value
        atr = max(self.atr_1h.value, 1e-9)
        norm = distance / atr

        # Only signal when extended beyond 0.5 ATR
        if abs(norm) < 0.5:
            return 0.0

        # Contrarian: -(distance/atr), clipped. 1.5 ATR → ±1
        return max(-1.0, min(1.0, -norm / 1.5))

    def _rsi_signal(self) -> float:
        """RSI extreme signal. ±1 range. Only active at extremes."""
        if not self.rsi_1h.ready:
            return 0.0

        rsi = self.rsi_1h.value

        if rsi > 75:
            # Overbought → expect down
            return -(rsi - 75) / 25.0  # RSI=75→0, RSI=100→-1
        elif rsi < 25:
            # Oversold → expect up
            return (25 - rsi) / 25.0  # RSI=25→0, RSI=0→+1
        return 0.0

    # ── Helpers ───────────────────────────────────────────────────

    def _ready(self) -> bool:
        return (self.sma_1h_20.ready and self.atr_1h.ready
                and self.rsi_1h.ready and self._last_1h_close > 0
                and self.price_sma.ready)

    def _scale(self, raw: float, max_abs: float = 0.02) -> float:
        target = 0.004
        if not (self.atr_1m.ready and self.price_sma.ready and self.atr_sma_1m.ready):
            return max(-max_abs, min(max_abs, raw * target))
        norm_atr = self.atr_1m.value / max(self.price_sma.value, 1e-9)
        avg_atr = self.atr_sma_1m.value / max(self.price_sma.value, 1e-9)
        if avg_atr < 1e-9:
            return max(-max_abs, min(max_abs, raw * target))
        vol_ratio = max(0.3, min(3.0, avg_atr / max(norm_atr, 1e-9)))
        return max(-max_abs, min(max_abs, raw * target * vol_ratio))
