Automated Market Making
Avellaneda-Stoikov with inventory skew, competitive spread blending, and multi-level quoting
Market making means continuously quoting bid and ask prices, earning the spread while managing inventory risk. Horizon implements the Avellaneda-Stoikov framework: the reservation price shifts based on inventory, and the optimal spread adjusts with volatility and competition.
API
Reservation price
python
# Where you'd be willing to trade given your inventory
r_price = hz.reservation_price(
mid=0.55, # current mid price
inventory=3, # your net position (positive = long)
gamma=0.1, # risk aversion (higher = more inventory penalty)
volatility=0.02, # estimated price volatility
time_horizon=1.0, # remaining time (normalized, 1.0 = full session)
)
# r_price < mid when inventory is positive (want to sell), > mid when negative
Optimal spread
python
spread = hz.optimal_spread(
gamma=0.1, # risk aversion
volatility=0.02, # estimated volatility
time_horizon=1.0, # remaining time
arrival_rate=5.0, # expected order arrivals per unit time
)
# spread: float -- total spread (divide by 2 for half-spread)
Competitive spread blending
python
# Blend your model spread with the current book spread
final_spread = hz.competitive_spread(
model_spread=0.04, # Avellaneda-Stoikov output
book_spread=0.02, # observed best bid-ask spread
blend=0.3, # 0 = pure model, 1 = pure book
)
Inventory-adjusted sizing
python
size = hz.mm_size(
base_size=10, # default quote size
inventory=3, # current net position
max_inventory=20, # hard inventory limit
skew=0.5, # how aggressively to skew sizes (0 = no skew)
)
# size: float -- reduced on the side that would increase inventory
Full quoting loop
python
surface = hz.BeliefVolSurface(decay=0.97, min_obs=20)
def market_make(ctx):
tick = ctx.feeds["orderbook"]
mid = (tick.bid + tick.ask) / 2
surface.update(mid, tick.ask - tick.bid, tick.timestamp)
if not surface.is_ready():
return None
vol = surface.diffusion_vol()
inv = ctx.inventory("market-id").net
r = hz.reservation_price(mid, inv, gamma=0.1, volatility=vol, time_horizon=1.0)
model_spread = hz.optimal_spread(0.1, vol, 1.0, arrival_rate=5.0)
spread = hz.competitive_spread(model_spread, tick.ask - tick.bid, blend=0.3)
bid = r - spread / 2
ask = r + spread / 2
bid_sz = hz.mm_size(10, inv, max_inventory=50, skew=0.5)
ask_sz = hz.mm_size(10, -inv, max_inventory=50, skew=0.5)
return [hz.Order(side="buy", price=bid, size=bid_sz),
hz.Order(side="sell", price=ask, size=ask_sz)]
When to use
- Prediction markets: quote both sides of a binary market with inventory-aware pricing.
- Crypto perps: provide liquidity on Hyperliquid with volatility-adjusted spreads.
- Competitive venues: blend your theoretical spread with the observed book to avoid being stale.