Saltar al contenido principal

Portfolio options market making — Lucic-Tse 2024-2026

Audience: options market-makers, quant developers writing spread-prediction models, and agents that need to reason about portfolio-level risk skew.

The single-asset Avellaneda-Stoikov framework breaks down for options portfolios. An options book carries simultaneous Δ / Γ / ν / ρ exposures across hundreds of strikes; the spread the dealer should quote at strike K is no longer independent of the inventory at strike K′.

The breakthrough closed-form solution for portfolio-level options MM landed in 2024-2026: V. Lucic and A. Tse, "Optimal option market making and volatility arbitrage". AlphaSwarm implements that framework in alphaswarm/options/portfolio_mm.py.

Two equations

1. Per-strike vol-arb alpha.

α(K, T) = ½ · S² · Γ(K, T) · (σ_real² − σ_imp²)

This is the option-equivalent of the spot vol-arb edge: when realised volatility exceeds implied volatility, the dealer collects vega exposure at a positive expected value.

2. Inventory-skewed bid/ask quote.

bid(K, T)  = mid(K, T) − δ(K, T) − skew(K, T)
ask(K, T) = mid(K, T) + δ(K, T) − skew(K, T)
δ(K, T) = base_spread + ½ · hedge_cost · |Γ(K, T)|
skew(K, T) = γ_inv · ν(K, T) · (Σ_vol · q_per_expiry)

where Σ_vol is the (rank-reducible) covariance of the implied-vol factors across maturities and q is the inventory matrix.

The Riccati system the linear-quadratic ansatz produces is closed-form in steady state — no PDE solver required. AlphaSwarm implements that closed form in pure JAX with jnp.einsum for the matrix contractions.

Calling the solver

import numpy as np
from alphaswarm.analysis.pricing import greeks_grid
from alphaswarm.options.portfolio_mm import LucicTseParams, compute_lucic_tse_quotes

strikes = np.array([95., 100., 105.])
expiries = np.array([0.05, 0.1, 0.25])

grid = greeks_grid(spot=100., strikes=strikes, expiries=expiries, vol=0.2)
quotes = compute_lucic_tse_quotes(
spot=100.0,
mid_quotes=grid["price"],
gamma_surface=grid["gamma"],
vega_surface=grid["vega"],
realized_vol=0.22, # the dealer's view
implied_vol=np.full_like(grid["price"], 0.20), # market quote
inventory=np.zeros_like(grid["price"]),
params=LucicTseParams(gamma_inv=0.05, base_spread=0.05, hedge_cost=0.001),
)
print(quotes.bid) # (n_expiries, n_strikes)
print(quotes.ask)
print(quotes.expected_pnl)

JAX optionality: when the [optimal-control] extra is missing, the module degrades to NumPy. Numerical results are identical, just slower.

Analysis-flow surface

For UI / agent flows, use optimal_control.lucic_tse_portfolio_quotes or the namespace alias derivatives.lucic_tse_quotes. Both wrap compute_lucic_tse_quotes and persist a row-per-cell table to alphaswarm_gold_analysis_optimal_control.lucic_tse_portfolio_quotes.

from alphaswarm.analysis import run_flow

out = run_flow(
"optimal_control.lucic_tse_portfolio_quotes",
None,
{
"spot": 100.0,
"strikes": [90, 95, 100, 105, 110],
"expiries": [0.05, 0.1, 0.25, 0.5],
"realized_vol": 0.22,
"implied_vol": 0.20,
"gamma_inv": 0.05,
"base_spread": 0.05,
"hedge_cost": 0.001,
},
)

Pairing with the JAX/fast-vollib Greek path

Building the Greek surface dominates the per-step cost. AlphaSwarm ships a JAX/vmap drop-in path in alphaswarm/options/greeks_jax.py that auto-detects fast_vollib (Triton-fused on H100) when the extra is installed, otherwise JIT-compiles a hand-rolled BSM kernel. The legacy alphaswarm.analysis.pricing.greeks_grid routes through this fast path automatically.

RL pairing

alphaswarm.rl.envs.LucicTsePortfolioEnv exposes the framework as a Gym environment so PPO/SAC can learn to adapt γ_inv / base_spread as a function of the realised vs implied gap. Sample config: configs/rl/lucic_tse_options.yaml.

See also