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