Belief-Volatility Surface
Streaming EM decomposition of diffusion and jump components in prediction markets
Prediction market prices move for two reasons: gradual belief drift (diffusion) and sudden information shocks (jumps). BeliefVolSurface decomposes price changes into these two components using a streaming EM algorithm that operates in logit space, treating the bid-ask spread as microstructure noise rather than signal.
API
python
surface = hz.BeliefVolSurface(
decay=0.97, # exponential decay for weighting recent observations
min_obs=20, # minimum observations before estimates are valid
)
surface.update(price=0.62, spread=0.03, timestamp=1710000000.0)
Methods
python
surface.update(price, spread, timestamp) # ingest a new tick
surface.diffusion_vol() # float -- belief volatility (continuous component)
surface.jump_intensity() # float -- estimated jump arrival rate
surface.jump_mean() # float -- average jump size (in logit space)
surface.is_ready() # bool -- True after min_obs updates
Parameters
| Parameter | Meaning | Typical values |
|---|---|---|
decay | Exponential weighting factor | 0.95-0.99; lower = faster adaptation |
min_obs | Burn-in period before output is valid | 10-50 |
Separating noise from signal
When a prediction market ticks from 0.55 to 0.62, was that a meaningful information event or a noisy fill in a thin book? The belief-vol surface answers this by maintaining running estimates of both components:
python
surface = hz.BeliefVolSurface(decay=0.97, min_obs=30)
for tick in market_ticks:
surface.update(tick.price, tick.spread, tick.timestamp)
if not surface.is_ready():
continue
diffusion = surface.diffusion_vol()
jump_rate = surface.jump_intensity()
# High jump intensity = information-driven move, tighten quotes
# High diffusion vol = noisy market, widen quotes
if jump_rate > 0.3:
spread_multiplier = 1.5
elif diffusion > 0.10:
spread_multiplier = 1.2
else:
spread_multiplier = 1.0
Position sizing with decomposed vol
Use the diffusion component for Kelly sizing. The jump component represents tail risk that Kelly underestimates:
python
total_vol = surface.diffusion_vol()
jump_adj = surface.jump_intensity() * abs(surface.jump_mean())
# Kelly using diffusion vol, then discount by jump risk
kelly_size = edge / (total_vol ** 2)
adjusted_size = kelly_size * max(0.3, 1.0 - jump_adj)
When to use
- Market making: separate microstructure noise from real belief shifts to set appropriate spreads.
- Event detection: a spike in jump intensity signals that new information just hit the market.
- Position sizing: use diffusion vol for Kelly and jump intensity for tail-risk discounting.
- Cross-market comparison: compare belief volatility across correlated prediction markets to find which one leads.