"""
Guéant Closed-Form Market Making Model.

Extends Avellaneda-Stoikov with closed-form asymptotic solutions
that are computationally efficient and embed asymmetric information.

Reference: Guéant, Lehalle, Fernandez-Tapia — "Dealing with the
Inventory Risk: A solution to the market making problem" (2012)

Key improvements over standard A-S:
  1. Closed-form solutions (no PDE solving needed)
  2. Explicit handling of asymmetric information (adverse selection)
  3. Explicit dependence on order book shape
  4. Better terminal condition handling

Optimal quotes:
  δ_a(t,q) = σ²γ(T-t)/2 + (1/γ)log(1 + γ/k)
  δ_b(t,q) = δ_a(t,q)
  r(t,q)  = s - q·σ²γ(T-t)   [reservation price]

where:
  s = mid price
  q = inventory
  γ = risk aversion
  σ = volatility
  k = order arrival intensity
  T-t = remaining time

Bid = r(t,q) - δ_b
Ask = r(t,q) + δ_a

The Guéant extension adds:
  - Asymmetric spreads when adverse selection detected
  - Queue-position dependent fill probabilities
  - Better parameter estimation from LOB data

Usage:
    from strategies.gueant import GueantMM
    mm = GueantMM(gamma=0.1, sigma=0.01)
    bid, ask = mm.optimal_quotes(mid, inventory, elapsed, adverse)
"""
import math

class GueantMM:
    """Closed-form market making with asymmetric information handling."""

    def __init__(self, gamma: float = 0.1, sigma: float = 0.01,
                 k: float = 1.5, T: float = 60.0, max_pos: float = 0.005):
        """
        Args:
            gamma: risk aversion (0.01=v.aggressive, 1.0=v.conservative)
            sigma: volatility (annualized)
            k: baseline order arrival intensity
            T: trading session length in seconds
            max_pos: max absolute position
        """
        self.gamma = gamma
        self.sigma = sigma
        self.k = k
        self.T = T
        self.max_pos = max_pos

    def optimal_spread(self, tau: float, adverse_prob: float = 0) -> float:
        """Compute optimal half-spread.

        Args:
            tau: time remaining (T - elapsed)
            adverse_prob: estimated adverse selection probability (0-1)

        Returns half-spread δ in price units.
        """
        if tau < 0.01:
            tau = 0.01

        sig2 = self.sigma**2
        gamma = self.gamma

        # Base Guéant spread: σ²γτ/2 + (1/γ)log(1+γ/k)
        base_spread = gamma * sig2 * tau / 2.0

        if gamma > 0 and self.k > 0:
            log_term = (1.0 / gamma) * math.log(1.0 + gamma / self.k)
        else:
            log_term = 0.001

        half_spread = base_spread + log_term

        # Asymmetric information adjustment
        # When adverse selection is high, widen spread proportionally
        if adverse_prob > 0:
            # Guéant extension: adverse selection increases effective spread
            # δ_effective = δ_base · (1 + φ·P(adverse))
            phi = 2.0  # Sensitivity to adverse selection
            half_spread *= (1.0 + phi * adverse_prob)

        return max(half_spread, 0.01)  # Minimum 1 cent spread

    def reservation_price(self, mid_price: float, inventory: float,
                          tau: float) -> float:
        """Compute reservation price adjusted for inventory risk.

        r = s - q·γ·σ²·τ

        Long inventory (q > 0): reservation shifts DOWN (want to sell)
        Short inventory (q < 0): reservation shifts UP (want to buy)
        """
        r = mid_price - inventory * self.gamma * self.sigma**2 * tau
        return r

    def optimal_quotes(self, mid_price: float, inventory: float,
                       elapsed: float, adverse_prob: float = 0,
                       bid_depth: float = 1.0, ask_depth: float = 1.0) -> dict:
        """Compute optimal bid and ask quotes.

        Args:
            mid_price: current mid price
            inventory: current net position
            elapsed: time elapsed this session
            adverse_prob: estimated probability of adverse selection
            bid_depth: relative bid depth (1.0 = normal, >1 = deeper book)
            ask_depth: relative ask depth (1.0 = normal, >1 = deeper book)

        Returns dict with bid, ask, reservation, half_spread, skew.
        """
        tau = self.T - elapsed
        if tau < 0.01:
            tau = 0.01

        r = self.reservation_price(mid_price, inventory, tau)
        spread = self.optimal_spread(tau, adverse_prob)

        # Adjust spread based on book depth
        # Deeper book → tighter spreads (more competition)
        # Thinner book → wider spreads (less competition)
        bid_spread = spread / max(bid_depth, 0.5)
        ask_spread = spread / max(ask_depth, 0.5)

        bid = r - bid_spread
        ask = r + ask_spread

        # Enforce inventory limits
        if inventory >= self.max_pos:
            bid = 0  # Don't buy more
        if inventory <= -self.max_pos:
            ask = float('inf')  # Don't sell more

        return {
            "bid": round(bid, 1),
            "ask": round(ask, 1),
            "reservation": round(r, 1),
            "half_spread": round(spread, 2),
            "bid_spread": round(bid_spread, 2),
            "ask_spread": round(ask_spread, 2),
            "skew": round(r - mid_price, 2),
        }

    def estimated_fill_probability(self, our_price: float,
                                   best_price: float,
                                   is_bid: bool) -> float:
        """Estimate probability our quote gets filled.

        Based on distance from best and queue position.
        At best (matching): high fill rate
        1 tick away: moderate
        >2 ticks away: low
        """
        dist = abs(our_price - best_price) / best_price if best_price > 0 else 0

        if dist < 0.0001:   # At the best price level
            return 0.30  # ~30% chance per tick
        elif dist < 0.0005:  # Within 1 tick
            return 0.10
        elif dist < 0.002:   # Within 2 ticks
            return 0.03
        return 0.01
