← Alle Insights

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:

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:

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.

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.