Particle Filter
Sequential Monte Carlo for nonlinear, non-Gaussian dynamics
The Kalman filter assumes linear dynamics and Gaussian noise. Real markets have jumps, fat tails, and multimodal distributions. The particle filter drops those assumptions: it represents the posterior as a set of weighted particles, each a hypothesis about the current state.
API
pf = hz.ParticleFilter(
n_particles=1000,
initial_state=0.5,
process_noise=0.01,
measurement_noise=0.05,
)
pf.update(observation)
mean = pf.state_mean() # weighted mean of particles
particles = pf.particles() # list of (state, weight) tuples
Parameters
| Parameter | Meaning | Guidance |
|---|---|---|
n_particles | Number of particles | 500-5000. More particles = better approximation, higher cost |
initial_state | Starting state for all particles | Set to your prior estimate |
process_noise | Std dev of state transition noise | Controls how fast the state can drift |
measurement_noise | Std dev of observation noise | How noisy are your observations |
Methods
pf.update(observation) # incorporate new observation, resample
pf.state_mean() # weighted average state
pf.state_std() # weighted std dev (uncertainty)
pf.particles() # raw particles and weights
pf.effective_sample_size() # ESS -- below n/2 means particle degeneracy
Tracking a jumpy fair value
Prediction markets with low liquidity exhibit price jumps that a Kalman filter smooths away too aggressively. A particle filter can maintain multiple hypotheses — some particles track the old regime, others jump to the new one:
pf = hz.ParticleFilter(
n_particles=2000,
initial_state=0.50,
process_noise=0.02, # allows moderate jumps
measurement_noise=0.03,
)
for tick in market_ticks:
pf.update(tick.price)
fair = pf.state_mean()
uncertainty = pf.state_std()
if uncertainty > 0.10:
# High uncertainty = multimodal posterior = possible regime transition
print(f"Uncertain regime: fair={fair:.3f} +/- {uncertainty:.3f}")
Stochastic volatility estimation
Track time-varying volatility where the vol process itself is stochastic:
pf = hz.ParticleFilter(
n_particles=3000,
initial_state=0.20, # initial vol estimate (20%)
process_noise=0.005, # vol-of-vol
measurement_noise=0.10, # return observations are noisy
)
for ret in daily_returns:
pf.update(abs(ret)) # absolute returns as vol proxy
vol_estimate = pf.state_mean()
# Use vol_estimate for position sizing
When to use
- Jump-diffusion models: when fair values can jump discontinuously (event-driven markets, earnings, rulings).
- Multimodal distributions: when there are two or more plausible states and you need to track all of them.
- Stochastic vol: when volatility itself is a random process and you want to track it online.
- Non-Gaussian noise: when observation noise has fat tails or skew that violate Kalman assumptions.
For purely linear/Gaussian systems, the Kalman filter is cheaper and exact. Use the particle filter when those assumptions break down.