HJB Market Making
Classical market making (post symmetric quotes around a fair value) ignores a critical variable: your current inventory. As inventory grows, your risk increases, and optimal quotes should shift to encourage mean reversion. The Avellaneda-Stoikov (2008) and Gueant-Lehalle-Fernandez-Tapia (2012) frameworks solve this rigorously using the Hamilton-Jacobi-Bellman equation. Horizon implements the full finite-difference backward induction solver in Rust.HJB Solver
hz.solve_hjb() solves the HJB PDE on a discretized grid via backward induction, producing the full value function and optimal quote schedule.Quote Generation
hz.hjb_quote() evaluates the solution at a given inventory and time to produce optimal bid/ask spreads.Arrival Rate Model
hz.hjb_arrival_rate() computes the expected fill rate as a function of quote depth, calibrated to market microstructure.Pipeline Integration
hz.hjb_market_maker() runs the full HJB market maker each cycle: solve, quote, and manage inventory.hz.solve_hjb
Solve the HJB partial differential equation for optimal market making on a discretized (inventory, time) grid. The solver uses finite differences with backward induction from the terminal condition.| Parameter | Type | Description |
|---|---|---|
config | HJBConfig | Configuration object with all model parameters |
HJBConfig Type
| Field | Type | Description |
|---|---|---|
sigma | float | Mid-price volatility (annualized). Controls how fast the mid-price diffuses |
gamma | float | Risk aversion coefficient. Higher gamma produces tighter inventory control |
kappa | float | Order arrival rate decay parameter. Controls how quickly fill probability decreases with quote depth |
alpha | float | Order arrival rate baseline shift. Additive constant in the arrival rate model |
max_inventory | int | Maximum absolute inventory the solver considers. Grid spans [-max_inventory, +max_inventory] |
t_horizon | float | Trading horizon in days. The terminal condition penalizes open inventory at this time |
n_time_steps | int | Number of time steps in the finite-difference grid. More steps = higher accuracy but slower |
terminal_penalty | float | Per-unit penalty for inventory remaining at the horizon. Models liquidation cost |
HJBSolution Type
| Field | Type | Description |
|---|---|---|
value_function | list[list[float]] | Value function V(q, t) on the discretized grid |
optimal_bid_depth | list[list[float]] | Optimal bid depth delta_b(q, t) on the grid |
optimal_ask_depth | list[list[float]] | Optimal ask depth delta_a(q, t) on the grid |
n_inventory | int | Number of inventory grid points (2 * max_inventory + 1) |
n_time | int | Number of time grid points |
config | HJBConfig | Configuration used to produce this solution |
The solver runs in O(max_inventory * n_time_steps) time. For max_inventory=10 and n_time_steps=100, this is ~2000 grid points and completes in under 1ms. Larger grids (max_inventory=50, n_time_steps=1000) still solve in under 50ms.
hz.hjb_quote
Evaluate the HJB solution at a specific inventory level and time fraction to produce optimal bid and ask quotes.| Parameter | Type | Description |
|---|---|---|
solution | HJBSolution | Pre-computed HJB solution from solve_hjb() |
inventory | int | Current net inventory (positive = long, negative = short) |
time_fraction | float | Fraction of the trading horizon elapsed (0.0 = start, 1.0 = end) |
HJBQuote Type
| Field | Type | Description |
|---|---|---|
bid_depth | float | Optimal distance of the bid below the mid-price |
ask_depth | float | Optimal distance of the ask above the mid-price |
bid_skew | float | Inventory-induced shift to the bid. Positive when long (bid moves down to discourage further buying) |
ask_skew | float | Inventory-induced shift to the ask. Positive when long (ask moves down to encourage selling) |
reservation_price | float | The inventory-adjusted fair value: mid - gamma * sigma^2 * inventory * (T - t). When long, this is below mid; when short, above |
spread | float | Total quoted spread (bid_depth + ask_depth) |
hz.hjb_arrival_rate
Compute the expected order arrival rate (fill probability per unit time) as a function of quote depth. This is the Poisson intensity model used internally by the HJB solver.| Parameter | Type | Description |
|---|---|---|
delta | float | Quote depth (distance from mid-price) |
kappa | float | Decay parameter (higher = faster decay with depth) |
alpha | float | Baseline shift (additive constant) |
float: the Poisson arrival intensity lambda(delta) = alpha + exp(-kappa * delta). Higher depth means lower arrival rate (quotes farther from mid fill less frequently).
Inventory Dynamics
The key insight of HJB market making is that optimal quotes depend on inventory:Pipeline Integration
Thehz.hjb_market_maker() pipeline function runs the full HJB market maker: solve the PDE (once or periodically), evaluate quotes each cycle based on current inventory and time, and submit orders.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
feed | str | required | Feed name to read mid-price from |
config | HJBConfig | required | HJB solver configuration |
size | float | 10.0 | Base order size in contracts |
recompute_every | int | 1000 | Re-solve the HJB PDE every N cycles (to incorporate updated volatility) |
ctx.params["hjb_quotes"] (an HJBQuote object) and ctx.params["hjb_orders"] (a list of OrderRequest objects) each cycle.
Finite-Difference Backward Induction
The HJB solver discretizes the (inventory, time) space and works backward from the terminal condition. Terminal condition at t = T:V(q, T) = -terminal_penalty * abs(q)
This penalizes open inventory at the end of the horizon.
Backward step from t+dt to t:
At each (q, t), the solver computes the optimal bid depth delta_b and ask depth delta_a that maximize the expected infinitesimal gain:
max [lambda_b * (delta_b + V(q+1, t) - V(q, t)) + lambda_a * (delta_a + V(q-1, t) - V(q, t)) - 0.5 * gamma * sigma^2 * q^2 * dt]
where lambda_b = alpha * exp(-kappa * delta_b) and lambda_a = alpha * exp(-kappa * delta_a).
The first-order conditions yield closed-form expressions for the optimal depths at each grid point, which are then used to update the value function.
Mathematical Background
Avellaneda-Stoikov Framework
Avellaneda-Stoikov Framework
Avellaneda and Stoikov (2008) formulated optimal market making as a stochastic control problem. The market maker maximizes expected utility of terminal wealth:
max E[-exp(-gamma * W_T)], where W_T is terminal wealth. The mid-price follows a Brownian motion: dS = sigma * dW. The market maker controls bid and ask depths, which determine fill rates via a Poisson process. The resulting HJB equation is a PDE in (inventory, time) that can be solved analytically in simple cases or numerically via finite differences.Gueant-Lehalle-Fernandez-Tapia Extension
Gueant-Lehalle-Fernandez-Tapia Extension
Gueant, Lehalle, and Fernandez-Tapia (2012) extended the framework to include a terminal penalty for open inventory and an exponential arrival rate model. They showed that the optimal bid and ask depths are:
delta_b = (1/gamma) * ln(1 + gamma/kappa) + (gamma * sigma^2 * (T-t) * (2q + 1)) / 2delta_a = (1/gamma) * ln(1 + gamma/kappa) - (gamma * sigma^2 * (T-t) * (2q - 1)) / 2The first term is the “base spread” (independent of inventory), and the second term is the “skew” (linear in inventory). The finite-difference solver in Horizon generalizes this to handle the terminal penalty and boundary conditions exactly.Arrival Rate Model
Arrival Rate Model
Order arrivals follow a Poisson process with intensity
lambda(delta) = alpha + exp(-kappa * delta), where delta is the distance from mid. This captures the empirical observation that quotes closer to mid fill more frequently. The parameter kappa controls the sensitivity: higher kappa means the arrival rate drops faster with depth. The parameter alpha provides a floor to prevent zero arrival rates at wide spreads.Risk Aversion and Inventory Control
Risk Aversion and Inventory Control
The parameter gamma controls the trade-off between profit and risk. At gamma = 0, the market maker is risk-neutral and posts quotes to maximize expected profit without regard to inventory. As gamma increases, the market maker becomes more aggressive in mean-reverting inventory, widening the spread on the side that would increase inventory and tightening it on the side that reduces inventory. In practice, gamma should be calibrated to match the desired maximum inventory holding period.