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

python
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

python
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.

python
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:

python
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).

Next