Kalman Filters
Linear and unscented Kalman filters for state estimation, hedge ratios, and spread trading
Kalman filters estimate hidden state from noisy observations. In trading, the “hidden state” is the true fair price, the true hedge ratio, or the true spread level — and the observations are noisy market data. Horizon ships both a linear Kalman filter (for Gaussian, linear systems) and an unscented variant (for nonlinear dynamics).
API
Linear Kalman Filter
kf = hz.KalmanFilter(state_dim=2, obs_dim=1)
# Configure system matrices
kf.set_transition([[1, 1], [0, 1]]) # state transition (F)
kf.set_observation([[1, 0]]) # observation model (H)
kf.set_process_noise([[0.01, 0], [0, 0.01]]) # process noise (Q)
kf.set_measurement_noise([[0.1]]) # measurement noise (R)
# Run the filter
kf.predict()
kf.update([observed_price])
state = kf.state() # current state estimate
cov = kf.covariance() # state uncertainty
Unscented Kalman Filter
ukf = hz.UnscentedKF(state_dim=2, obs_dim=1)
# Same configuration interface
ukf.set_process_noise([[0.01, 0], [0, 0.01]])
ukf.set_measurement_noise([[0.1]])
ukf.predict()
ukf.update([observed_price])
The UKF handles nonlinear state transitions and observation models by propagating sigma points through the nonlinearity, avoiding the Jacobian computation required by the extended Kalman filter.
Online hedge ratio estimation
The most common use in stat-arb: estimate the time-varying hedge ratio between two assets.
kf = hz.KalmanFilter(state_dim=2, obs_dim=1)
kf.set_transition([[1, 0], [0, 1]]) # random walk on coefficients
kf.set_process_noise([[1e-5, 0], [0, 1e-5]]) # slow-moving hedge ratio
kf.set_measurement_noise([[1e-3]])
for price_a, price_b in zip(series_a, series_b):
kf.set_observation([[price_b, 1.0]]) # y = beta * x + alpha
kf.predict()
kf.update([price_a])
beta, alpha = kf.state()
spread = price_a - beta * price_b - alpha
The hedge ratio beta adapts over time as the relationship between the two assets shifts. The Kalman filter tracks this drift without a fixed lookback window.
Kalman-filtered fair price
For noisy prediction markets where the displayed price jumps on low liquidity:
kf = hz.KalmanFilter(state_dim=1, obs_dim=1)
kf.set_transition([[1.0]])
kf.set_observation([[1.0]])
kf.set_process_noise([[0.001]]) # fair price moves slowly
kf.set_measurement_noise([[0.05]]) # observed price is noisy
for tick in market_ticks:
kf.predict()
kf.update([tick.price])
fair_price = kf.state()[0]
# Trade when observed price deviates from fair_price
When to use
- Pairs trading: online hedge ratio that adapts to structural shifts without window selection.
- Noisy markets: extract a smooth fair-price estimate from jumpy prediction market quotes.
- Spread tracking: maintain a filtered spread for mean-reversion signals.
- Use the UKF when the relationship between state and observation is nonlinear (e.g., volatility models, option-implied quantities).