b59dcc3629
Set up the directory structure and wrote placeholder logic for: - Order Book Imbalance: trades on L2 bid/ask skew - Iceberg/TWAP detection: follows whale accumulation patterns - Funding rate arbitrage: delta-neutral carry on perp funding - Pairs trading: BTC/ETH spread mean reversion - Avellaneda-Stoikov market making: optimal bid/ask quoting Also added shared risk manager, portfolio tracker, and a plain-language strategy walkthrough in docs/.
88 lines
2.8 KiB
Python
88 lines
2.8 KiB
Python
"""
|
|
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,
|
|
))
|