KF Kalman Filtering
Adaptive hedge ratios — because static betas are indefensible in production
Learning Objectives
- •Understand the Kalman filter conceptually and mathematically
- •Implement a Kalman filter for dynamic hedge ratio estimation
- •Compare static vs. dynamic hedge ratios in practice
Explain Like I'm 5
The hedge ratio between two assets isn't fixed — it drifts over time. A static hedge ratio from OLS gets stale fast. The Kalman filter continuously updates the hedge ratio as new data arrives, adapting to changing relationships. Think of it as GPS — constantly recalculating your position based on new information.
Think of It This Way
Imagine tracking a moving target with a rifle scope. OLS regression is like taking a photo and aiming where the target WAS 5 seconds ago. Kalman filtering is like using a tracking scope that continuously adjusts your aim as the target moves. The accuracy difference is not subtle.
1Why Static Hedge Ratios Fail
2Kalman Filter Mechanics
3Static vs. Dynamic Hedge Ratio
Static OLS vs Kalman-Filtered Hedge Ratio
4Tuning Q: The Key Decision
5When Kalman Is Not Worth It
Key Formulas
Kalman Gain
K determines how much to trust new data vs. the old estimate. High K means fast adaptation but noisy estimates. Low K means smooth but slow.
State Update
The hedge ratio estimate is corrected by the Kalman gain times the prediction error, continuously adapting beta to new data.
Hands-On Code
Kalman Filter for Dynamic Hedge Ratio
import numpy as np
class KalmanHedgeRatio:
"""Kalman filter for dynamic hedge ratio estimation."""
def __init__(self, Q=1e-4, R=1.0):
self.Q = Q # process noise
self.R = R # observation noise
self.beta = 0.0 # initial hedge ratio
self.P = 1.0 # initial uncertainty
def update(self, x, y):
"""Update hedge ratio with new observation."""
# Predict
beta_pred = self.beta
P_pred = self.P + self.Q
# Update
innovation = y - x * beta_pred
S = x * P_pred * x + self.R
K = P_pred * x / S # Kalman gain
self.beta = beta_pred + K * innovation
self.P = (1 - K * x) * P_pred
return self.beta, self.P
def run(self, price_a, price_b):
"""Run Kalman filter on price series."""
betas = []
spreads = []
for a, b in zip(price_a, price_b):
beta, P = self.update(np.log(b), np.log(a))
betas.append(beta)
spread = np.log(a) - beta * np.log(b)
spreads.append(spread)
spreads = np.array(spreads)
betas = np.array(betas)
# Z-score of spread
lookback = 50
z = (spreads - np.mean(spreads[-lookback:])) / np.std(spreads[-lookback:])
print(f"Current beta: {betas[-1]:.4f}")
print(f"Current z: {z[-1]:.2f}")
return betas, zImplements a Kalman filter that continuously adapts the hedge ratio between two price series, producing more stationary spreads and more reliable z-scores for trading signals.
Knowledge Check
Q1.The Kalman gain K is very high. What does this mean?
Assignment
Implement a Kalman filter for the hedge ratio between a cointegrated pair. Compare the Kalman-filtered spread's stationarity (ADF test) vs. the OLS spread. Which produces better pairs trading signals?