A spread between cointegrated assets does not wander freely; it is pulled toward a long-run level at a rate proportional to its displacement. The diffusion that captures exactly this behaviour is the Ornstein-Uhlenbeck process, and it is tractable enough that its transition law, stationary law, and discrete-time estimation all follow in closed form.
#The mean-reverting equation
The Ornstein-Uhlenbeck process solves the linear stochastic differential equation
with mean-reversion speed , long-run level , volatility , and a Brownian motion.
The affine drift is globally Lipschitz with linear growth, so Equation (1) has a unique strong solution for every square-integrable initial condition [1].
#Closed form
The solution of Equation (1) is
Apply Ito's formula to . Since has no second-order contribution from the finite-variation factor,
after substituting Equation (1) and cancelling the terms. Integrating gives , and multiplying by returns Equation (2).
#Transition and stationary laws
Conditional on , the value is Gaussian with
As grows the law converges to the stationary distribution , which is invariant under Equation (1).
The stochastic integral in Equation (2) is a Wiener integral of a deterministic function, hence Gaussian with zero mean, so the conditional mean is the deterministic part of Equation (2). The Ito isometry gives the variance,
Letting grow, the mean tends to and the variance to . The solution is a time-homogeneous Markov process, so the law of given is governed by a transition semigroup , and is the marginal of when . If is drawn from independently of , the summands in Equation (2) are independent Gaussians, so has mean and variance , hence for every . Thus for all , so is invariant.
A deviation from the mean decays in conditional expectation as , so it loses half its size over the half-life
a single scalar summarizing the reversion timescale.
#Exact reduction to an autoregression
Sampled at a fixed spacing , the process obeys the exact recursion
where the are independent and .
Write Equation (2) from time to ,
The deterministic part rearranges to . The stochastic term is a Wiener integral over , hence Gaussian with mean zero, independent of and so of the past increments, and its variance is by the Ito isometry.
Equation Equation (7) is a first-order autoregression. Because the innovations are i.i.d. with constant in (homoskedastic), the conditional log-likelihood is . For fixed the maximiser over minimises the residual sum of squares, independent of the value of , and so is exactly the ordinary least-squares regression of on (the profile over being the residual variance). The slope estimates , from which and the half-life of Equation (6) follow.
#Illustration
Simulating the exact recursion Equation (7), regressing successive samples, and recovering the half-life shows the estimator at work.
import numpy as np
from numpy.random import Generator
def simulate_ou(
kappa: float, theta: float, sigma: float, dt: float, n: int, rng: Generator
) -> np.ndarray:
"""Simulate an Ornstein-Uhlenbeck path by its exact discretization.
Args:
kappa: Mean-reversion speed.
theta: Long-run level.
sigma: Volatility.
dt: Sampling spacing.
n: Number of steps.
rng: Seeded generator for reproducibility.
Returns:
The sampled path of length n + 1. It starts at path[0] = theta, the
stationary mean, and converges to the stationary law N(theta,
sigma^2 / (2 kappa)) after a transient initial segment.
"""
phi = np.exp(-kappa * dt)
innovation_sd = sigma * np.sqrt((1.0 - phi**2) / (2.0 * kappa))
noise = rng.normal(0.0, innovation_sd, size=n)
path = np.empty(n + 1)
path[0] = theta
for k in range(n):
path[k + 1] = theta * (1.0 - phi) + phi * path[k] + noise[k]
return path
def estimate_half_life(path: np.ndarray, dt: float) -> float:
"""Estimate the mean-reversion half-life by least squares on the AR(1) form.
Args:
path: A sampled Ornstein-Uhlenbeck trajectory.
dt: The sampling spacing used to generate the path.
Returns:
The estimated half-life ln(2) / kappa.
"""
lagged = path[:-1]
nextval = path[1:]
design = np.vstack([np.ones_like(lagged), lagged]).T
slope = np.linalg.lstsq(design, nextval, rcond=None)[0][1]
kappa = -np.log(slope) / dt
return float(np.log(2.0) / kappa)
rng = np.random.default_rng(0)
path = simulate_ou(kappa=1.5, theta=0.0, sigma=0.3, dt=1.0 / 252.0, n=50_000, rng=rng)
half_life = estimate_half_life(path, dt=1.0 / 252.0)
The closed form Equation (2), the stationary law of Proposition 3, and the autoregressive reduction Equation (7) give the Ornstein-Uhlenbeck process closed-form transition, stationary, and estimation theory.