"""
Hawkes Process Order Flow Imbalance Strategy.

Models trade arrivals as self-exciting point processes.
Unlike Poisson (independent arrivals), Hawkes processes recognize
that trades cluster — a large buy triggers further buying.

λ(t) = μ + Σ α·e^(-β(t-t_i))   for t_i < t

where μ = baseline intensity, α = self-excitation, β = decay rate.

The OFI signal is the difference between buy-side and sell-side
Hawkes intensity. When OFI crosses a threshold, it predicts
short-term price direction.

Usage:
    from strategies.hawkes_ofi import HawkesOFI
    model = HawkesOFI(alpha=0.3, beta=0.5)
    signal = model.update(trade_side, trade_size, trade_price)
"""
import math
import time
from collections import deque

class HawkesOFI:
    """Single-asset Hawkes process for OFI signal generation."""

    def __init__(self, alpha: float = 0.3, beta: float = 0.5, mu: float = 0.05):
        self.alpha = alpha    # Self-excitation strength
        self.beta = beta      # Decay rate
        self.mu = mu          # Baseline intensity

        # Track recent trade events: (timestamp, side_multiplier)
        # side_multiplier = +1 for buys, -1 for sells
        self.events: deque = deque(maxlen=200)
        self._last_update = time.time()

        # Rolling statistics
        self.buy_intensity = mu
        self.sell_intensity = mu
        self.ofi_history: deque = deque(maxlen=50)
        self.price_history: deque = deque(maxlen=100)

    def compute_intensity(self, t: float, side_filter: int) -> float:
        """Compute Hawkes intensity at time t for a given side filter.
        
        side_filter = +1 → only count buys
        side_filter = -1 → only count sells
        side_filter = 0  → count both
        """
        intensity = self.mu
        for ev_t, ev_side in self.events:
            dt = t - ev_t
            if dt > 10.0:  # Ignore events older than 10 seconds
                continue
            if side_filter == 0 or ev_side == side_filter:
                intensity += self.alpha * math.exp(-self.beta * dt)
        return intensity

    def update(self, side: str, size: float, price: float) -> float:
        """Process a new trade and return the OFI signal.
        
        Args:
            side: 'B' for buy, 'S' for sell
            size: trade size
            price: trade price

        Returns:
            ofi_signal: positive → bullish, negative → bearish, near 0 → neutral
        """
        t = time.time()
        side_mult = +1 if side in ('B', 'BUY', 'b') else -1

        # Add event
        self.events.append((t, side_mult))
        self.price_history.append(price)

        # Compute intensities
        buy_intensity = self.compute_intensity(t, +1)
        sell_intensity = self.compute_intensity(t, -1)

        self.buy_intensity = buy_intensity
        self.sell_intensity = sell_intensity

        # OFI = normalized difference in intensities
        total = buy_intensity + sell_intensity
        if total > 0:
            ofi = (buy_intensity - sell_intensity) / total
        else:
            ofi = 0.0

        self.ofi_history.append(ofi)
        self._last_update = t

        return ofi

    def get_signal(self) -> dict:
        """Get current trading signal based on OFI."""
        if len(self.ofi_history) < 10:
            return {"signal": None, "strength": 0.0, "confidence": 0.0}

        # Current OFI
        current_ofi = self.ofi_history[-1]

        # Trend in OFI (momentum of the imbalance)
        if len(self.ofi_history) >= 5:
            recent = list(self.ofi_history)[-5:]
            ofi_trend = sum(recent) / len(recent)
        else:
            ofi_trend = current_ofi

        # Z-score of current OFI
        ofi_list = list(self.ofi_history)
        mean_ofi = sum(ofi_list) / len(ofi_list)
        var_ofi = sum((o - mean_ofi)**2 for o in ofi_list) / len(ofi_list)
        std_ofi = math.sqrt(var_ofi) if var_ofi > 0 else 0.01

        z_score = (current_ofi - mean_ofi) / std_ofi if std_ofi > 0 else 0.0

        # Signal logic
        threshold = 0.15
        z_threshold = 1.5

        if current_ofi > threshold and z_score > z_threshold:
            signal = "BUY"
            strength = min(abs(z_score) / 3, 1.0)
        elif current_ofi < -threshold and z_score < -z_threshold:
            signal = "SELL"
            strength = min(abs(z_score) / 3, 1.0)
        elif abs(ofi_trend) > threshold * 0.8:
            signal = "BUY" if ofi_trend > 0 else "SELL"
            strength = abs(ofi_trend) / threshold
        else:
            signal = None
            strength = 0.0

        # Confidence based on how extreme the deviation is
        confidence = min(abs(z_score) / 3, 1.0)

        return {
            "signal": signal,
            "strength": round(strength, 4),
            "confidence": round(confidence, 4),
            "ofi": round(current_ofi, 4),
            "buy_intensity": round(self.buy_intensity, 4),
            "sell_intensity": round(self.sell_intensity, 4),
        }
