← Back to Learn
IV ExpertWeek 19 • Lesson 57Duration: 50 min

OU Spread Modeling

Ornstein-Uhlenbeck — the mathematics of rubber bands snapping back

Learning Objectives

  • Model spreads as Ornstein-Uhlenbeck (OU) processes
  • Estimate mean-reversion speed, equilibrium, and volatility from data
  • Derive optimal entry/exit thresholds from OU parameters

Explain Like I'm 5

A mean-reverting spread can be modeled as an Ornstein-Uhlenbeck process — basically a spring that pulls back to center with random kicks. The OU model gives you three numbers: how fast it reverts (kappa), what level it reverts to (mu), and how volatile it is (sigma). From these, you can calculate mathematically optimal entry and exit levels instead of guessing at z-score thresholds.

Think of It This Way

Imagine a rubber ball on a spring. The spring (mean-reversion force) pulls it to the center. Random kicks (market noise) push it away. The OU model describes exactly this: spring strength (kappa), center position (mu), and kick strength (sigma). Stronger spring = faster reversion = better pairs trade.

1The OU Process

The Ornstein-Uhlenbeck process is the standard mathematical model for mean-reverting phenomena:
dSt=κ(μSt)dt+σdWtdS_t = \kappa(\mu - S_t)\,dt + \sigma\,dW_t
Where: - StS_t = spread at time t - κ\kappa = mean-reversion speed (higher = faster reversion, better) - μ\mu = long-run equilibrium level (where the spread "wants" to be) - σ\sigma = volatility of the spread (the random kicks) - dWtdW_t = Brownian motion (standard random noise) From these three parameters, you get everything you need: - Half-life = ln(2)/κ\ln(2)/\kappa = time for spread to revert halfway to mean - Stationary variance = σ2/(2κ)\sigma^2/(2\kappa) - Optimal entry typically at 1.5-2 standard deviations from mean The beauty of OU is that it turns pairs trading from "eyeballing z-scores" into a proper mathematical framework with optimal solutions.

2Estimating OU Parameters From Data

Estimating κ\kappa, μ\mu, and σ\sigma from a real spread is surprisingly easy. Fit an AR(1) model to the spread:
ΔSt=a+bSt1+εt\Delta S_t = a + b \cdot S_{t-1} + \varepsilon_t
Then map the coefficients: - κ=b\kappa = -b (reversion speed) - μ=a/b\mu = -a/b (equilibrium) - σ=std(ε)×2κ\sigma = \text{std}(\varepsilon) \times \sqrt{2\kappa} (volatility) That's a linear regression. You already know how to do this. Important sanity checks: - bb MUST be negative — if b0b \geq 0, the spread is not mean-reverting and you should not be trading it - κ\kappa should give you a half-life that makes sense for your timeframe - μ\mu should be stable over time (if it's drifting, your equilibrium is shifting)

3From OU Parameters to Optimal Thresholds

This is where it gets powerful. Given OU parameters, you can compute mathematically optimal trading thresholds instead of arbitrarily picking z=2z = 2 because a textbook said so. Entry threshold: Where expected profit from convergence exceeds expected cost of adverse moves. Typically 1.5σstationary1.5\sigma_{\text{stationary}} to 2σstationary2\sigma_{\text{stationary}} from the mean. Exit threshold: Where the expected future profit from holding is zero. Typically ±0.5σstationary\pm 0.5\sigma_{\text{stationary}}. Stop-loss: Where the probability of convergence becomes too low. Typically 3-4σstationary3\text{-}4\sigma_{\text{stationary}} from the mean. The key insight: faster mean-reversion (higher κ\kappa) allows tighter entry thresholds because you're more confident the spread will actually revert. Two pairs might both have z=2.0z = 2.0 right now, but the one with κ=0.1\kappa = 0.1 is a far better trade than the one with κ=0.01\kappa = 0.01 because it'll revert 10x faster.

OU Spread with Optimal Trading Bands

4OU in Practice: What Textbooks Miss

Several important gotchas with OU modeling: 1. OU assumes constant parameters. In reality, κ\kappa, μ\mu, and σ\sigma all change over time. Use a rolling window (100-200 bars) and re-estimate frequently. 2. Estimation is noisy on short windows. You need at least 50-100 observations for decent parameter estimates. Less than that and your confidence intervals are enormous. 3. κ\kappa and half-life are inversely related. A small error in κ\kappa translates to a large error in half-life when κ\kappa is small. This means slow-reverting spreads have very uncertain half-lives. 4. The spread might not actually be OU. OU is a model, not reality. Real spreads have fat tails, jumps, and regime shifts that OU doesn't capture. Use it as a guide, not gospel. 5. Multiple equilibria. Sometimes μ\mu shifts permanently (structural break). Your OU model keeps pulling toward the old μ\mu while the spread has moved to a new regime. Always sanity-check μ\mu stability. The practical approach: estimate OU parameters, use them for threshold guidance, but always have hard stop-losses that don't depend on the model being correct.

Key Formulas

OU Process

The Ornstein-Uhlenbeck process: spread pulled toward mu with force kappa, disturbed by noise sigma. The standard model for mean-reverting processes.

OU Half-Life

Time for the spread to revert halfway to its mean. Shorter half-life = more tradeable pair.

Hands-On Code

OU Parameter Estimation

python
import numpy as np
from sklearn.linear_model import LinearRegression

def estimate_ou_params(spread):
    """Estimate OU process parameters from spread time series."""
    S = spread[:-1]
    dS = np.diff(spread)
    
    model = LinearRegression()
    model.fit(S.reshape(-1, 1), dS)
    
    b = model.coef_[0]
    a = model.intercept_
    residuals = dS - model.predict(S.reshape(-1, 1))
    
    if b >= 0:
        print("[WARN] Spread is NOT mean-reverting (b >= 0)")
        return None
    
    kappa = -b
    mu = -a / b
    sigma = np.std(residuals) * np.sqrt(2 * kappa)
    half_life = np.log(2) / kappa
    stationary_std = sigma / np.sqrt(2 * kappa)
    
    print(f"=== OU PARAMETERS ===")
    print(f"kappa (reversion speed): {kappa:.4f}")
    print(f"mu    (equilibrium):     {mu:.4f}")
    print(f"sigma (volatility):      {sigma:.4f}")
    print(f"Half-life:               {half_life:.1f} periods")
    print(f"Stationary std:          {stationary_std:.4f}")
    print(f"\n=== OPTIMAL THRESHOLDS ===")
    print(f"Entry:    +/-{2*stationary_std:.4f} from mu")
    print(f"Exit:     +/-{0.5*stationary_std:.4f} from mu")
    print(f"Stop:     +/-{4*stationary_std:.4f} from mu")
    
    return {'kappa': kappa, 'mu': mu, 'sigma': sigma, 'half_life': half_life}

Estimates the three OU parameters (kappa, mu, sigma) from a spread time series using AR(1) regression, then computes optimal entry/exit and stop-loss thresholds.

Knowledge Check

Q1.Two pairs have the same spread volatility. Pair A has kappa=0.1 (half-life 7 days). Pair B has kappa=0.01 (half-life 69 days). Which is better for short-term pairs trading?

Assignment

Estimate OU parameters for your best cointegrated pair. Compute optimal entry/exit thresholds. Backtest using OU-optimal thresholds vs. simple z-score thresholds. Which performs better?