"""
Avellaneda-Stoikov Market Making strategy.

A mathematical model for optimal market making based on
stochastic optimal control. Computes optimal bid/ask quotes
considering current inventory, risk aversion, volatility,
and time horizon.

Key formulas:
  Reservation price:  r = s - q * gamma * sigma^2 * tau
  Optimal spread:     delta = gamma * sigma^2 * tau + (2/gamma) * ln(1 + gamma/k)

where:
  s = mid price, q = inventory, gamma = risk aversion
  sigma = volatility, tau = remaining time, k = order intensity
"""
import math
from nautilus_trader.trading.strategy import Strategy
from nautilus_trader.config import StrategyConfig
from datetime import datetime, timezone


class AvellanedaStoikovConfig(StrategyConfig, frozen=True):
    instrument_id: str
    gamma: float = 0.1
    sigma: float = 0.02
    T: float = 1.0
    k: float = 1.5
    min_spread: float = 0.0001
    max_inventory: float = 0.01


class AvellanedaStoikov(Strategy):
    """
    A-S optimal market making.

    Instead of predicting direction, this strategy provides
    liquidity by continuously quoting bid/ask prices at an
    optimal distance from the mid price. The spread widens
    as inventory builds up (to discourage further accumulation)
    and tightens as the time horizon approaches.
    """

    def __init__(self, config: AvellanedaStoikovConfig) -> None:
        super().__init__(config)
        self.config = config
        self.start_time: datetime | None = None

    def on_start(self) -> None:
        self.start_time = self.clock.utc_now()
        self.subscribe_quote_ticks(self.config.instrument_id)
        self.log.info(
            f"A-S MM on {self.config.instrument_id} "
            f"(gamma={self.config.gamma})"
        )

    def on_quote_tick(self, tick) -> None:
        self.cancel_all_orders(self.config.instrument_id)

        elapsed = (self.clock.utc_now() - self.start_time).total_seconds() / 3600
        tau = max(self.config.T - elapsed, 0.01)

        q = float(self.portfolio.net_position(self.config.instrument_id))
        if abs(q) >= self.config.max_inventory:
            return

        g = self.config.gamma
        s = self.config.sigma
        k = self.config.k

        mid = (tick.bid + tick.ask) / 2
        reservation = mid - q * g * s**2 * tau
        spread = g * s**2 * tau + (2 / g) * math.log(1 + g / k)
        spread = max(spread, self.config.min_spread)

        self.submit_order(self.order_factory.limit(
            instrument_id=self.config.instrument_id,
            order_side="BUY",
            quantity=self.config.max_inventory / 10,
            price=reservation - spread / 2,
        ))
        self.submit_order(self.order_factory.limit(
            instrument_id=self.config.instrument_id,
            order_side="SELL",
            quantity=self.config.max_inventory / 10,
            price=reservation + spread / 2,
        ))
