"""Gaussian Cycle Tracker — cycle-filtered trend detection.

Uses a 4-pole Gaussian filter (Ehlers) to extract the dominant cycle
from price data, combined with an EMA(50) for trend direction.

Signal logic:
  - Gaussian filter (period=40, poles=4) extracts smooth cycle
  - EMA(50) provides trend bias
  - When cycle slope and EMA trend agree → full signal
  - When they disagree → reduced signal (cycle turning against trend)

The Gaussian filter has much better frequency response than simple
moving averages — it passes the dominant cycle cleanly while rejecting
noise, without the ringing artifacts of a Butterworth filter.

Reference: John Ehlers, "Cybernetic Analysis for Stocks and Futures"
"""

from __future__ import annotations

from vanta.tracker import TrackerBase
from vanta.indicators import GaussianFilter, EMA, ATR, SMA


class GaussianCycleTracker(TrackerBase):
    """Gaussian-filtered cycle + EMA trend model."""

    def __init__(
        self,
        gauss_period: int = 40,
        gauss_poles: int = 4,
        ema_period: int = 50,
        atr_period: int = 14,
        cycle_lookback: int = 3,
    ):
        super().__init__()

        # Core indicators
        self.gauss = GaussianFilter(period=gauss_period, poles=gauss_poles)
        self.ema = EMA(period=ema_period)
        self.atr = ATR(period=atr_period)
        self.atr_sma = SMA(period=50)

        self._cycle_lookback = cycle_lookback
        self._gauss_history: list[float] = []
        self._last_close: float = 0.0

    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"])

            self.gauss.update(p)
            self.ema.update(p)
            self.atr.update(h, l, p)
            if self.atr.ready:
                self.atr_sma.update(self.atr.value)

            if self.gauss.ready:
                self._gauss_history.append(self.gauss.value)
                if len(self._gauss_history) > 100:
                    self._gauss_history = self._gauss_history[-100:]

            self._last_close = p

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

        # Cycle slope as percentage of price (return-scale)
        cycle_slope = self._cycle_slope() / self._last_close if self._last_close > 0 else 0.0

        # Trend direction from EMA (already return-scale)
        ema_trend = (self._last_close - self.ema.value) / self.ema.value

        # Regime: high vol → reduce conviction
        vol_regime = 1.0
        if self.atr_sma.ready and self.atr.ready:
            vol_ratio = self.atr.value / self.atr_sma.value
            if vol_ratio > 1.5:
                vol_regime = 0.6  # reduce in high vol
            elif vol_ratio < 0.7:
                vol_regime = 1.2  # increase in low vol

        # Combine: cycle and trend must agree
        cycle_dir = 1.0 if cycle_slope > 0 else -1.0
        trend_dir = 1.0 if ema_trend > 0 else -1.0

        if cycle_dir == trend_dir:
            # Agreement: full signal
            signal = cycle_slope * 0.5 + ema_trend * 0.5
        else:
            # Disagreement: cycle might be turning — reduce signal
            signal = cycle_slope * 0.3 + ema_trend * 0.2

        signal *= vol_regime

        # Horizon scaling
        hours = horizon_seconds / 3600
        if hours > 24:
            signal *= min(1.5, hours / 24)

        return {"expected_return": signal}

    def _cycle_slope(self) -> float:
        """Slope of the Gaussian filter over recent bars."""
        if len(self._gauss_history) < self._cycle_lookback + 1:
            return 0.0
        recent = self._gauss_history[-(self._cycle_lookback + 1):]
        return (recent[-1] - recent[0]) / self._cycle_lookback

    def _ready(self) -> bool:
        return (
            self.gauss.ready
            and self.ema.ready
            and self.atr.ready
            and len(self._gauss_history) >= self._cycle_lookback + 1
        )
