Kalman-Filter im Trading: dynamische Hedge-Ratios.
Statische Hedge-Ratios sind die häufigste Schwachstelle in Pairs-Strategien. Märkte ändern sich — und mit ihnen die Beziehung zwischen zwei Assets. Der Kalman-Filter schätzt diese Beziehung in jedem Tick neu, ohne dass Sie ein Fenster wählen müssen. Wer ihn beherrscht, bekommt deutlich robustere Spreads.
Was der Kalman-Filter eigentlich tut.
Der Kalman-Filter ist ein rekursiver Schätzer für State-Space-Modelle. Sie haben einen versteckten Zustand (z. B. den wahren Hedge-Ratio zwischen zwei Aktien), den Sie nicht direkt beobachten können — Sie sehen nur verrauschte Messungen (die Preise). Der Filter kombiniert eine A-priori-Vorhersage mit der aktuellen Messung und liefert eine bayesianisch-optimale Schätzung.
Das Konzept stammt aus der Navigation der 1960er — Apollo-Missionen tracken damit die Position eines Raumschiffs aus verrauschten Sensordaten. Im Trading übertragen wir: Das Raumschiff ist der Hedge-Ratio, die Sensoren sind die täglichen Preise.
Der entscheidende Vorteil gegenüber Rolling-OLS: Sie müssen keine Fenstergröße festlegen. Stattdessen kodieren Sie über zwei Parameter (Prozess-Varianz und Mess-Varianz), wie schnell sich der Zustand ändern darf und wie verrauscht die Beobachtungen sind.
Das Modell für Pairs-Trading.
Für ein Pair (y, x) modellieren wir y als linearen Funktion von x mit zeitvariablem Slope (Hedge-Ratio) und Intercept:
y_t = α_t + β_t · x_t + ε_t
α_t = α_{t-1} + η_α
β_t = β_{t-1} + η_β
Der Zustandsvektor ist θ_t = [α_t, β_t] und entwickelt sich als Random-
Walk. Die Beobachtungsgleichung ist die Regression. Der Filter aktualisiert die
Schätzung von θ_t mit jedem neuen Datenpunkt — ohne irgendetwas zu
„vergessen“, aber auch ohne an alten Daten festzuhalten.
Implementierung in Python.
Hier eine kompakte Implementierung ohne externe Filter-Bibliothek — Sie sehen jeden Schritt:
import numpy as np
import pandas as pd
def kalman_pairs(y, x, delta=1e-4, r=1e-3):
"""
Kalman-Filter für dynamischen Hedge-Ratio.
delta: Prozess-Varianz (wie schnell darf sich beta ändern?)
r: Mess-Varianz (wie verrauscht sind die Preise?)
"""
n = len(y)
theta = np.zeros((n, 2))
P = np.zeros((n, 2, 2))
theta[0] = [0.0, 1.0]
P[0] = np.eye(2) * 100
Q = np.eye(2) * delta
e = np.zeros(n)
s = np.zeros(n)
for t in range(1, n):
theta_pred = theta[t-1]
P_pred = P[t-1] + Q
H = np.array([1.0, x.iloc[t]])
y_pred = H @ theta_pred
e[t] = y.iloc[t] - y_pred
s[t] = H @ P_pred @ H + r
K = P_pred @ H / s[t]
theta[t] = theta_pred + K * e[t]
P[t] = P_pred - np.outer(K, H) @ P_pred
return pd.DataFrame({
'alpha': theta[:, 0],
'beta': theta[:, 1],
'spread': e,
'spread_var': s
}, index=y.index)
Der Spread fällt als Innovation e_t direkt mit aus dem Filter — Sie
müssen keinen separaten Regressionsschritt rechnen. Die Innovationsvarianz
s_t gibt Ihnen einen natürlichen Z-Score:
z_t = e_t / √s_t.
Die zwei Parameter: delta und r.
Der ganze Filter lebt von zwei Hyperparametern. Sie müssen wissen, was sie tun:
- delta (Prozess-Varianz): Klein → β ändert sich kaum, fast wie statisches OLS auf langem Fenster. Groß → β passt sich schnell an, ähnlich Rolling-OLS auf kurzem Fenster. Typische Werte: 10⁻⁵ bis 10⁻³.
- r (Mess-Varianz): Skaliert die Empfindlichkeit gegenüber Ausreißern. Typischerweise an der empirischen Varianz der Residuen kalibriert.
In der Praxis kalibrieren wir delta per Maximum-Likelihood über das gesamte Modell. Praktischer Trick: Cross-Validation über mehrere Pairs hinweg liefert oft eine globale delta-Wahl, die für alle Pairs derselben Klasse gut funktioniert.
Vergleich gegen Rolling-OLS.
In einem typischen Backtest auf 50 cointegrierten Pairs (US-Versorger, 2010–2024) sehen wir konsistente Muster:
- Rolling-OLS mit 60-Tages-Fenster: Sharpe 0,9, max. DD −12 %, Whipsaws an Strukturbrüchen.
- Kalman-Filter (delta = 1e-4): Sharpe 1,2, max. DD −8 %, schnelle Anpassung an Spin-offs.
- Statisches OLS: Sharpe 0,6, max. DD −22 %, viele Pairs brechen out-of-sample.
Der Kalman-Filter glänzt vor allem in zwei Situationen: bei langsamen, kontinuierlichen Drifts des Hedge-Ratios und bei plötzlichen Sprüngen (Spin-off, Earnings-Schock). Bei sehr stabilen Pairs ist der Vorsprung gegenüber Rolling-OLS kleiner — dort lohnt der Aufwand kaum.
Erweiterungen: Multi-Asset und nicht-lineare Modelle.
Der Filter funktioniert nicht nur für zwei Assets. Für ein Triplet oder Quartett (Index-Replikation aus 3–5 Aktien) erweitern Sie den Zustandsvektor um weitere Betas. Die Mathematik bleibt linear, der Rechenaufwand wächst nur linear mit der Anzahl Assets.
Wenn die Beziehung nicht-linear wird — etwa bei Volatilitäts-Spreads oder Optionspositionen — kommt der Extended Kalman Filter (EKF) oder Unscented Kalman Filter (UKF) ins Spiel. Für die meisten Statistical-Arbitrage-Anwendungen reicht aber der Standard-Filter aus.
# Multi-Asset-Variante mit pykalman
from pykalman import KalmanFilter
def kalman_basket(y, X, delta=1e-4):
"""X: DataFrame mit N Faktor-Assets, y: Series."""
n_features = X.shape[1] + 1
obs_mat = np.hstack([np.ones((len(X), 1)), X.values])
obs_mat = obs_mat[:, np.newaxis, :]
kf = KalmanFilter(
n_dim_obs=1,
n_dim_state=n_features,
initial_state_mean=np.zeros(n_features),
initial_state_covariance=np.eye(n_features),
transition_matrices=np.eye(n_features),
observation_matrices=obs_mat,
observation_covariance=1.0,
transition_covariance=delta * np.eye(n_features)
)
state_means, state_covs = kf.filter(y.values)
return pd.DataFrame(state_means, index=y.index)
Fallstricke in der Praxis.
- Initialisierung: Die ersten 30–60 Beobachtungen sollten Sie aus dem Signal-Set ausschließen — der Filter braucht Zeit zum Einschwingen.
- Look-Ahead: Niemals den geglätteten (Smoother) Output für Backtests nutzen — nur die rein gefilterten Werte. Smoother sieht in die Zukunft.
- Numerische Stabilität: Bei langen Zeitreihen kann die Kovarianzmatrix degenerieren. Square-Root-Form oder Joseph-Form-Update verwenden.
- Strukturbrüche: Auch der Filter kann an echten Regime-Wechseln zu langsam sein. Ergänzen Sie ihn mit einem Change-Point-Detector.
Was bleibt vom Kalman im Quant-Setup.
Der Kalman-Filter ist kein Allheilmittel — er ersetzt nicht ökonomisches Verständnis. Aber er ist eine der wenigen Techniken, die quasi „kostenlos“ Strategien robuster machen: gleicher Backtest-Code, gleiches Pair-Universum, nur ersetzen Sie statisches Beta durch einen Filter und gewinnen oft 30–50 % Sharpe-Punkte.
Bei uns läuft inzwischen praktisch jede Pairs-Strategie auf Kalman-basiertem Spread. Der Aufwand bei der Erstimplementierung ist ein Nachmittag, der Wartungsaufwand danach geringer als bei Rolling-OLS, weil weniger Parameter zu tunen sind.
Sie wollen Kalman-Filter in Ihren Quant-Stack integrieren oder einen bestehenden State-Space-Aufbau überprüfen? Unverbindlich anfragen — wir setzen die Implementierung sauber auf.