Ornstein-Uhlenbeck Mean Reversion: vom Modell zur Strategie.
Der Ornstein-Uhlenbeck-Prozess ist das mathematische Rückgrat fast jeder Mean-Reversion- Strategie. Wer ihn versteht, kann Halbwertszeiten korrekt schätzen, optimale Trading- Schwellen ableiten und vor allem erkennen, wann ein Spread kein stationärer OU-Prozess mehr ist — bevor das die Strategie kostet.
Das Modell in einer Zeile.
Die stochastische Differentialgleichung des Ornstein-Uhlenbeck-Prozesses lautet:
dX_t = θ · (μ − X_t) · dt + σ · dW_t
Drei Parameter, jeder mit konkreter Bedeutung. μ ist der langfristige
Mittelwert, zu dem der Prozess zurückkehrt. θ ist die Geschwindigkeit
dieser Rückkehr (mean-reversion rate). σ ist die Volatilität der
Schocks. dW_t ist Brownsche Bewegung — reines Rauschen.
Das ist das einfachste stationäre stochastische Modell, das Sie überhaupt finden. Genau deshalb passt es als Erstes auf praktisch jeden Spread, der mean-revertet: Pairs-Spread, PCA-Residuum, Basis zwischen Future und Underlying, Term-Structure- Spread.
Wichtige Eigenschaften, die Sie kennen müssen.
- Stationarität: Bei θ > 0 ist der Prozess stationär. Sein langfristiger Mittelwert ist μ, seine langfristige Varianz
σ² / (2θ). - Half-Life: Die erwartete Zeit, um eine Abweichung um die Hälfte zu reduzieren, ist
ln(2) / θ. Bei θ = 0,05 pro Tag sind das etwa 14 Handelstage. - Verteilung: Die stationäre Verteilung ist normal mit Mittelwert μ und Standardabweichung
σ / √(2θ). Daraus leiten Sie Z-Scores ab. - Übergangsverteilung: Konditional auf
X_0istX_tnormal verteilt — analytisch handhabbar, ideal für Maximum-Likelihood.
Kalibrierung aus Daten.
Die Standardmethode ist die exakte Diskretisierung. Der OU-Prozess lässt sich an Beobachtungszeitpunkten als AR(1)-Modell schreiben:
X_{t+Δt} = X_t · e^{−θΔt} + μ · (1 − e^{−θΔt}) + ε
Eine einfache lineare Regression von X_{t+1} auf X_t
liefert Steigung a = e^{−θΔt} und Intercept b = μ(1−a).
Daraus folgen direkt μ und θ:
import numpy as np
import pandas as pd
from scipy.stats import linregress
def fit_ou(series, dt=1.0):
"""Kalibriere OU-Parameter aus Zeitreihe."""
x = series.values
x_t = x[:-1]
x_tp1 = x[1:]
slope, intercept, r, p, se = linregress(x_t, x_tp1)
if slope <= 0 or slope >= 1:
return None # kein gültiger OU
theta = -np.log(slope) / dt
mu = intercept / (1 - slope)
# Residuen-Varianz
resid = x_tp1 - (slope * x_t + intercept)
sigma2 = np.var(resid) * 2 * theta / (1 - slope**2)
sigma = np.sqrt(sigma2)
half_life = np.log(2) / theta
return {
'mu': mu,
'theta': theta,
'sigma': sigma,
'half_life': half_life,
'r_squared': r**2
}
Die Maximum-Likelihood-Variante liefert nahezu identische Werte, ist aber numerisch stabiler bei sehr kurzen Half-Lives oder hochfrequenten Daten. Für tägliche Bars reicht die OLS-Variante in 99 % der Fälle.
Optimale Trading-Schwellen — analytisch.
Im Gegensatz zu reinem Z-Score-Trading lässt sich für OU-Prozesse die optimale
Entry/Exit-Schwelle analytisch herleiten. Bertram (2009) und nachfolgende Arbeiten
zeigen: Wenn Sie pro Trade einen festen Kostenanteil c haben und Long/
Short-symmetrisch handeln, maximieren Sie den erwarteten Gewinn pro Zeiteinheit über
eine Entry-Schwelle a und Exit auf Mittelwert.
Das Optimum hängt von der Sharpe-pro-Trade a / σ_stat und der erwarteten
Tradezeit ab. Für typische Kosten von 5–10 bps pro Trade-Seite und Half-Lives um
15 Tage ergeben sich Entry-Schwellen zwischen 1,2 und 1,8 Standardabweichungen — und
interessanterweise oft nicht symmetrisch (asymmetrische Kosten, asymmetrische Drift-
Risiken).
In der Praxis lohnt sich eine Grid-Search über mögliche Schwellen, weil das analytische Optimum unter idealen Annahmen gilt, die Realität aber regelmäßig verletzt sind (Kosten variabel, Fills nicht garantiert, Strategie nur Quasi-OU).
Multi-dimensionale OU: Faktor-Spreads gleichzeitig handeln.
Wenn Sie nicht einen Spread haben, sondern 50, lohnt sich eine multivariate OU-Sicht. Der Vektor-Prozess folgt:
dX_t = Θ · (μ − X_t) · dt + Σ^{1/2} · dW_t
Θ ist jetzt eine Matrix, die nicht nur Eigen-Reversion, sondern auch Cross-Reversion modelliert: Wenn Spread A heute zu weit oben ist, kann sich das auch auf die Geschwindigkeit von Spread B auswirken. Für korrelierte Pair-Cluster (etwa innerhalb eines Sektors) bringt das oft 10–20 % zusätzliche Performance.
def fit_multivariate_ou(X, dt=1.0):
"""X: DataFrame mit Spreads in Spalten. Vector-OU via VAR(1)."""
X_t = X.iloc[:-1].values
X_tp1 = X.iloc[1:].values
# OLS pro Spalte (oder geschlossene Matrix-Form)
A, _, _, _ = np.linalg.lstsq(
np.hstack([X_t, np.ones((len(X_t), 1))]),
X_tp1, rcond=None
)
# A ist (k+1, k); Steigungs-Block ist obere k x k
k = X.shape[1]
slope_matrix = A[:k, :]
intercept = A[k, :]
# Theta-Matrix aus Matrix-Logarithmus
from scipy.linalg import logm
Theta = -logm(slope_matrix) / dt
# Equilibrium-Mittelwert
mu = np.linalg.solve(np.eye(k) - slope_matrix, intercept)
# Residuen-Kovarianz
resid = X_tp1 - X_t @ slope_matrix - intercept
Sigma = np.cov(resid.T)
return Theta, mu, Sigma
Wann der OU-Annahme nicht mehr passt.
OU ist ein lineares, gauss'sches Modell mit konstanten Parametern. Die Realität ist keines davon. Vier typische Verletzungen:
- Regime-Wechsel: μ und θ ändern sich strukturell. Lösung: Markov-Switching-OU oder rollende Re-Kalibrierung.
- Volatilitäts-Clustering: σ ist nicht konstant. Lösung: OU mit GARCH-Volatilität oder Heston-Style stochastische Vola.
- Fat Tails: Renditen sind nicht normal verteilt. Lösung: OU mit t-verteilten Schocks oder Jumps (CIR, Merton-Jump-OU).
- Drift: μ selbst driftet. Lösung: Modell mit zeitvariablem Mittelwert oder Differenzieren vor OU-Fit.
Pragmatisch: Wenn der Spread auf dem Q-Q-Plot grob normal aussieht und Half-Life stabil bleibt, ist OU ein vernünftiges Modell. Wenn nicht, wechseln Sie auf nicht- parametrische Schwellen oder ein robustes Z-Score-Setup.
Was wir aus OU für jede Strategie mitnehmen.
Auch wenn die Strategie am Ende keine analytischen OU-Schwellen nutzt, ist die OU-Kalibrierung ein hervorragendes Diagnose-Werkzeug. Sie sagt Ihnen:
- Wie schnell der Spread mean-revertet (Half-Life) — entscheidend für Trade-Frequenz und Kostenrelevanz.
- Wie volatil der Spread im stationären Zustand ist — entscheidend für Z-Score-Skalen.
- Wie weit der aktuelle Wert vom Mittelwert entfernt ist — entscheidend für Entry-Timing.
Bei uns läuft jedes neue Stat-Arb-Setup zuerst durch eine OU-Kalibrierung. Pairs mit instabilen θ-Schätzern oder R² unter 0,05 werden gar nicht erst weiterverfolgt — egal wie schön der Z-Score-Chart aussieht. Diese Vorab-Filterung allein hat uns über die Jahre eine zweistellige Anzahl Fehl-Strategien erspart.
Sie wollen ein Mean-Reversion-Setup auf OU-Basis aufbauen oder ein bestehendes Modell stresstesten? Erstgespräch buchen — wir machen die Kalibrierung, die Schwellen-Optimierung und die Live-Implementierung.