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

ParameterMeaningTypical values
decayExponential weighting factor0.95-0.99; lower = faster adaptation
min_obsBurn-in period before output is valid10-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.

Next