OU Pairs Trading with Half-Life Sizing
Pairs trading begins with a relative price: long one asset, short another in a ratio that makes their combined exposure plausibly stationary. The most useful first distinction is between a correlated pair and a mean-reverting spread. Correlation describes contemporaneous movement; it does not ensure a relative mispricing will close. See pairs trading in crypto and stocks for the market-structure differences that make this distinction especially important.
For a log-price spread xt = log(At) - beta log(B_t), the Ornstein-Uhlenbeck process writes:
dx_t = kappa (mu - x_t) dt + sigma dW_t
half-life = ln(2) / kappa
kappa is the speed of pull toward equilibrium. A half-life is operationally useful: it informs signal horizon, position scale, holding-period expectations, and when a position has failed its premise. It is an estimate, not a deadline at which an open trade is guaranteed to converge.
Estimating the process discretely
At daily frequency, fit the equivalent AR(1) regression:
x_t = a + b x_(t-1) + epsilon_t
kappa = -ln(b) / Delta_t, mu = a / (1 - b)
Only use estimates with 0 < b < 1; a negative coefficient can look strongly mean-reverting at one sampling interval while representing oscillation, stale prices, or mismatched trading hours. Estimate on an expanding or rolling window entirely before the trade date, then inspect confidence intervals. Short samples make half-lives highly unstable near b = 1.
| Estimate | Trading interpretation | Caution |
|---|---|---|
b near 1 | slow convergence | estimates are noisy and capital is tied up |
| low half-life | frequent opportunities | costs can dominate small moves |
| high residual sigma | larger raw swings | not necessarily more alpha |
changing mu | likely regime shift | a static z-score is misleading |
import numpy as np
import statsmodels.api as sm
def ou_parameters(spread, periods_per_year=252):
y = spread.iloc[1:].to_numpy()
lag = spread.iloc[:-1].to_numpy()
fit = sm.OLS(y, sm.add_constant(lag)).fit()
a, b = fit.params
if not 0 < b < 1:
return {"valid": False}
kappa = -np.log(b) * periods_per_year
half_life_days = np.log(2) / (-np.log(b))
mu = a / (1 - b)
sigma_eps = fit.resid.std(ddof=1)
return {"valid": True, "beta_ar": b, "mu": mu,
"half_life_days": half_life_days, "sigma_eps": sigma_eps}
The hedge ratio is a separate estimation problem. A regression of one log price on another can produce a useful spread, but cointegration tests and residual diagnostics should decide whether it is stable. For multiple related assets, a Johansen test and VECM is usually more coherent than selecting pairwise regressions after searching many candidates.
Signal and half-life-aware sizing
Define a standardized displacement using an OU equilibrium variance or a rolling residual scale:
z_t = (x_t - mu) / sigma_x
Enter at |z| > 1.5--2.5, reduce near zero, and place a hard risk limit beyond the ordinary signal threshold. A compact sizing rule combines conviction and speed:
gross_t = risk_budget * min(|z_t| / z_max, 1) * f(half_life)
where f declines for very slow spreads. A two-standard-deviation deviation with a 120-day half-life has much less attractive annualized opportunity than the same deviation with a 10-day half-life, all else equal. Do not simply allocate inversely to half-life: extremely fast apparent reversion can be bid-ask bounce that disappears after executable prices and a one-bar delay.
| Decision | Half-life input | Better implementation |
|---|---|---|
| Entry threshold | expected speed | require net expected edge after costs |
| Position size | convergence rate and volatility | cap gross and single-name risk |
| Time stop | several estimated half-lives | reassess model, do not average blindly |
| Rebalance cadence | avoid overreacting to noise | match data frequency and liquidity |
Map the spread position into shares so the hedge ratio, beta, and dollar exposure are all controlled. If the pair uses securities with different volatilities, a unit spread may still have concentrated leg risk. Recalculate target shares at controlled intervals; continuous hedge-ratio adjustment creates hidden turnover.
Model risk is trade risk
The OU model assumes a fixed equilibrium and Gaussian diffusion. Real spreads jump on earnings, regulation, index changes, takeover rumors, borrow recalls, and capital structure events. A statistically attractive short can become impossible or expensive to maintain. Test signals excluding the estimation period, reserve a separate validation set, and correct for the many pair and threshold combinations considered.
Use executable bid/ask prices, financing, dividends, borrow fees, and locate failures. For ETFs and futures, account for roll and creation mechanics; for international shares, synchronize closes. A close-to-close backtest that flips at the same close systematically overstates a fast strategy.
Monitor rolling beta, half-life, residual volatility, and realized correlation. Suspend entry when stationarity tests fail persistently or the equilibrium moves beyond a predefined band. A stop is not a claim that mean reversion is false; it recognizes that the distribution has changed faster than capital can safely wait.
Key takeaways
- Half-life translates a mean-reversion estimate into a holding-period and sizing input.
- Estimate the hedge ratio and OU dynamics out of sample and with lagged data.
- Fast apparent reversion can be microstructure noise; slow reversion has opportunity cost.
- Stops, borrow constraints, and regime monitoring are core parts of a pairs strategy.
