Skip to main content

Automated Market Making

Horizon includes a complete Avellaneda-Stoikov market making engine. All math is implemented in Rust for maximum performance and exposed to Python via PyO3. The hz.market_maker() pipeline factory handles everything from feed ingestion to multi-level quote generation in a single call.
Market making in prediction markets involves continuously quoting bid and ask prices. The Avellaneda-Stoikov model adjusts quotes based on inventory risk, volatility, and competitive dynamics. Horizon’s implementation adds competitive spread blending and multi-level quoting on top of the base model.

Overview

Reservation Price

hz.reservation_price() computes the inventory-skewed fair value using Avellaneda-Stoikov.

Optimal Spread

hz.optimal_spread() computes the theoretically optimal bid-ask width.

Competitive Spread

hz.competitive_spread() blends model spread with live orderbook spread.

Inventory-Aware Sizing

hz.mm_size() skews bid/ask sizes based on current inventory.

Core Functions

hz.reservation_price

Compute the inventory-skewed fair value (Avellaneda-Stoikov reservation price).
Formula: r = mid - inventory * gamma * volatility^2 * time_horizon
  • Long inventory skews fair value down (encourages selling)
  • Short inventory skews fair value up (encourages buying)
  • NaN/Inf inventory defaults to zero (returns mid)

hz.optimal_spread

Compute the theoretically optimal bid-ask spread.

hz.competitive_spread

Blend the model-optimal spread with the live orderbook spread.
The result is clamped to [0.001, 1.0].

hz.mm_size

Compute inventory-skewed bid and ask sizes.
Returns (bid_size, ask_size):
  • Long inventory → smaller bids, larger asks (reduces inventory)
  • Short inventory → larger bids, smaller asks (builds inventory)
  • Returns (0.0, 0.0) if base_size is zero or NaN

hz.estimate_volatility

Estimate price volatility from a series of prices using log-return standard deviation.
Returns 0.0 for fewer than 2 prices or constant prices.

Pipeline Factory: hz.market_maker

The market_maker() factory returns a pipeline function that generates multi-level quotes automatically.

Parameters

How It Works

On each cycle, the market maker:
  1. Gets the mid price from the feed (bid/ask midpoint or price field)
  2. Estimates volatility from the rolling price history
  3. Computes reservation_price() with current inventory
  4. Computes optimal_spread() based on volatility and risk aversion
  5. Blends with live spread via competitive_spread() if bid/ask data is available
  6. Computes inventory-skewed mm_size()
  7. Generates num_levels quote levels with 0.8x size decay per level
  8. Clamps all prices to [0.01, 0.99] and skips crossed quotes
Returns list[Quote] compatible with the pipeline system.

Signal Chaining

When placed after hz.signal_combiner() in a pipeline, the market maker receives the combined signal value as its fair value estimate. This replaces the feed mid price for the reservation price calculation:
The signal value is only used when it falls in the valid range (0, 1). Otherwise the feed mid price is used as the fallback.

Examples

Single-Level Market Maker

Multi-Level with Aggressive Spread

Combined with Signal Combiner


Mathematical Background

The model computes the dealer’s reservation price as:r = s - q * gamma * sigma^2 * TWhere s is the mid price, q is inventory, gamma is risk aversion, sigma is volatility, and T is the time horizon. This skews the fair value against inventory to encourage mean reversion.
The optimal spread balances adverse selection risk against the probability of getting filled. Higher volatility and lower order arrival rates lead to wider spreads.
Pure model spreads can be too wide or too narrow relative to the live orderbook. The aggression parameter lets you blend toward the market spread when you want to be competitive, or toward the model spread when you want to protect against adverse selection.
Market making in prediction markets carries inventory risk. Always use risk limits (max_position, max_drawdown_pct) and start with paper trading before deploying live. The gamma parameter controls how aggressively the model penalizes inventory, start with higher values (0.5-1.0) to be conservative.