Skip to main content
Pro Feature. Requires a Pro or Ultra subscription. Get started at api.mathematicalcompany.com
What is this? A Kalman filter tracks a hidden ‘true’ value (like fair price) from noisy observations. It smooths out market noise in real time, giving you a cleaner signal than raw prices. Use it for spread trading, hedge ratio estimation, or any time you need to filter noise from a live feed.

Kalman Filters

Horizon provides two Kalman filter implementations in Rust: a standard linear KalmanFilter and an UnscentedKF for nonlinear dynamics. Both support online single-step updates for live trading and batch smoothing for offline analysis.

Linear Kalman Filter

Standard Kalman filter for linear state-space models. Optimal for Gaussian noise.

Unscented Kalman Filter

Sigma-point filter for nonlinear dynamics. No Jacobians required.

Hedge Ratio Tracker

Online hedge ratio estimation via time-varying regression coefficients.

Spread Trading

Kalman-filtered spread for pairs/stat-arb strategies on correlated markets.

KalmanFilter

The linear Kalman filter tracks a hidden state vector through noisy observations. It is optimal (minimum variance) when the system is linear and noise is Gaussian.

Constructor

import horizon as hz

kf = hz.KalmanFilter(
    state_dim=2,        # Dimension of the state vector
    obs_dim=1,          # Dimension of the observation vector
)
ParameterTypeDescription
state_dimintDimension of the hidden state vector
obs_dimintDimension of the observation vector

Configuration Methods

Set the system matrices before running the filter. All matrices are provided as flat row-major lists.
# State transition: x_{t+1} = F * x_t
kf.set_transition([[1.0, 1.0], [0.0, 1.0]])

# Observation model: z_t = H * x_t
kf.set_observation([[1.0, 0.0]])

# Process noise covariance Q
kf.set_process_noise([[0.01, 0.0], [0.0, 0.01]])

# Measurement noise covariance R
kf.set_measurement_noise([[0.1]])
MethodParameterTypeDescription
set_transitionmatrixlist[list[float]]State transition matrix F (state_dim x state_dim)
set_observationmatrixlist[list[float]]Observation matrix H (obs_dim x state_dim)
set_process_noisematrixlist[list[float]]Process noise Q (state_dim x state_dim)
set_measurement_noisematrixlist[list[float]]Measurement noise R (obs_dim x obs_dim)

predict()

Propagate the state forward one time step using the transition model.
kf.predict()

update()

Incorporate a new observation and correct the state estimate.
kf.update([0.55])  # observation vector
ParameterTypeDescription
observationlist[float]Observation vector (length obs_dim)

State Access

state = kf.state()           # Current state estimate [x1, x2, ...]
cov = kf.covariance()        # State covariance matrix
ll = kf.log_likelihood()     # Cumulative log-likelihood of observations
MethodReturnsDescription
state()list[float]Current state estimate vector
covariance()list[list[float]]State covariance matrix P
log_likelihood()floatCumulative log-likelihood of all processed observations

UnscentedKF

The Unscented Kalman Filter handles nonlinear state transitions and observation models using sigma-point propagation. No Jacobian computation is needed.

Constructor

ukf = hz.UnscentedKF(
    state_dim=2,
    obs_dim=1,
    alpha=1e-3,       # Sigma point spread (default: 1e-3)
    beta=2.0,         # Prior distribution parameter (default: 2.0)
    kappa=0.0,        # Secondary scaling parameter (default: 0.0)
)
ParameterTypeDefaultDescription
state_dimintrequiredDimension of the state vector
obs_dimintrequiredDimension of the observation vector
alphafloat1e-3Controls spread of sigma points around the mean
betafloat2.0Incorporates prior distribution knowledge (2.0 is optimal for Gaussian)
kappafloat0.0Secondary scaling parameter
The UKF supports the same set_process_noise, set_measurement_noise, predict, update, state, covariance, and log_likelihood methods as KalmanFilter. The transition and observation models are specified as nonlinear functions during construction or via configuration.

Pipeline Integration

hz.kalman_tracker

Tracks a filtered price estimate using a constant-velocity Kalman model. Injects the smoothed state into ctx.params.
import horizon as hz

def spread_strategy(ctx):
    kf_state = ctx.params.get("kalman")
    if kf_state is None:
        return []
    filtered_price = kf_state["price"]
    velocity = kf_state["velocity"]
    raw = ctx.feed.price

    # Trade mean reversion when price deviates from filter
    deviation = raw - filtered_price
    if abs(deviation) > 0.03:
        side = "sell" if deviation > 0 else "buy"
        return hz.order(side=side, price=raw, size=10)
    return []

hz.run(
    name="kalman_mm",
    markets=["election-winner"],
    feeds={"book": hz.PolymarketBook("election-winner")},
    pipeline=[
        hz.kalman_tracker(feed="book", process_noise=0.001, measurement_noise=0.01),
        spread_strategy,
    ],
    risk=hz.Risk(max_position=100),
)
ParameterTypeDefaultDescription
feedstrNoneFeed name to read prices from
process_noisefloat0.001Process noise variance Q
measurement_noisefloat0.01Measurement noise variance R
param_namestr"kalman"Key in ctx.params

hz.kalman_hedge_ratio

Estimates a time-varying hedge ratio between two feeds using a Kalman regression model. The state tracks the intercept and slope (hedge ratio) of the linear relationship.
hz.run(
    name="pairs_trader",
    markets=["market-a", "market-b"],
    feeds={
        "a": hz.PolymarketBook("token-a"),
        "b": hz.PolymarketBook("token-b"),
    },
    pipeline=[
        hz.kalman_hedge_ratio(feed_x="a", feed_y="b", process_noise=1e-4),
        pairs_strategy,  # ctx.params["hedge_ratio"], ctx.params["hedge_intercept"]
    ],
)
ParameterTypeDefaultDescription
feed_xstrrequiredFeed name for the independent variable
feed_ystrrequiredFeed name for the dependent variable
process_noisefloat1e-4Process noise for coefficient drift
measurement_noisefloat0.01Observation noise variance

hz.kalman_spread

Computes a Kalman-filtered spread between two markets and injects spread statistics for stat-arb strategies.
import horizon as hz

def stat_arb(ctx):
    spread = ctx.params.get("kalman_spread")
    if spread is None:
        return []
    z_score = spread["z_score"]
    if z_score > 2.0:
        return hz.order(side="sell", price=ctx.feed.price, size=5)
    elif z_score < -2.0:
        return hz.order(side="buy", price=ctx.feed.price, size=5)
    return []

hz.run(
    name="spread_arb",
    markets=["market-a", "market-b"],
    feeds={
        "a": hz.PolymarketBook("token-a"),
        "b": hz.PolymarketBook("token-b"),
    },
    pipeline=[
        hz.kalman_spread(feed_x="a", feed_y="b", lookback=200),
        stat_arb,
    ],
)
KeyTypeDescription
ctx.params["kalman_spread"]["spread"]floatCurrent filtered spread value
ctx.params["kalman_spread"]["z_score"]floatZ-score of the spread relative to its filtered mean and variance
ctx.params["kalman_spread"]["hedge_ratio"]floatCurrent hedge ratio estimate

Example: Hedge Ratio Spread Trading

import horizon as hz

model = hz.KalmanFilter(state_dim=2, obs_dim=1)
model.set_transition([[1.0, 0.0], [0.0, 1.0]])   # random walk coefficients
model.set_observation([[1.0, 0.0]])                 # observe intercept + beta * x
model.set_process_noise([[1e-5, 0.0], [0.0, 1e-5]])
model.set_measurement_noise([[1e-2]])

# Online hedge ratio estimation
for price_x, price_y in zip(series_x, series_y):
    model.set_observation([[1.0, price_x]])
    model.predict()
    model.update([price_y])
    intercept, beta = model.state()
    spread = price_y - beta * price_x - intercept
    print(f"beta={beta:.4f}, spread={spread:.4f}")

Mathematical Background

The Kalman filter recursively estimates the state of a linear system:
  • Predict: x_hat = F * x + B * u, P = F * P * F’ + Q
  • Update: K = P * H’ * (H * P * H’ + R)^(-1), x = x + K * (z - H * x), P = (I - K * H) * P
where F is the transition matrix, H the observation matrix, Q the process noise, R the measurement noise, and K the Kalman gain.
The UKF propagates 2n+1 sigma points (where n is the state dimension) through the nonlinear function, then recovers the mean and covariance from the transformed points. This avoids computing Jacobians and provides second-order accuracy for Gaussian inputs.
Modeling the hedge ratio as a Kalman state allows it to drift over time, adapting to structural changes in the relationship between two markets. This is superior to rolling OLS because the Kalman filter optimally weights old vs. new information based on the noise model.