The geometric Brownian motion from the previous post is a reasonable model for a single trending asset, but pairs of cointegrated assets — think a stock and its future, or two closely related equities — are often better described by an Ornstein-Uhlenbeck process on their spread:
$$ dX_t = \theta(\mu - X_t), dt + \sigma, dW_t $$
Here $\theta$ controls the speed of mean reversion, $\mu$ is the long-run mean of the spread, and $\sigma$ is the diffusion coefficient. Unlike GBM, the OU process has a well-defined stationary distribution, which is precisely what makes a pairs-trading strategy tractable: you can compute the half-life of a deviation directly from $\theta$,
$$ t_{1/2} = \frac{\ln 2}{\theta} $$
and size a mean-reversion trade around it. Estimating $\theta$, $\mu$, and $\sigma$ from data is usually done with a discretized AR(1) regression:
import numpy as np def fit_ou(spread: np.ndarray, dt: float = 1.0) -> tuple[float, float, float]: x, y = spread[:-1], spread[1:] beta, alpha = np.polyfit(x, y, 1) theta = -np.log(beta) / dt mu = alpha / (1 - beta) resid = y - (alpha + beta * x) sigma = resid.std() * np.sqrt(2 * theta / (1 - beta ** 2)) return theta, mu, sigma
Typical fitted half-lives for liquid equity pairs:
| Pair | θ | Half-life |
|---|---|---|
| XOM / CVX spread | 0.084 | 8.3 days |
| KO / PEP spread | 0.041 | 16.9 days |
Full position-sizing and Kelly-fraction derivation is a longer post on its own — this one is mostly here to set up the SDE so the fitting code above has somewhere to live.