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.

Next