QTQuant Terminal
D1-09D1·advanced·~23 min

Cointegration and pairs trading — mean reversion you can hunt for

time-seriescointegrationpairs-tradingengle-grangerspreadmean-reversionhedge-ratio

▸ Pretest — guess, even if you don't know

Two oil majors have a daily RETURN correlation of 0.9. Does that guarantee the gap between their PRICES stays bounded over the next decade?

Correlated returns, drifting prices

Correlation (A2-08) lives in return space: do the two series zig together day by day? Cointegration lives in price space: is there a combination of the two prices that never wanders off? The two properties are genuinely independent:

If you trade the gap between two prices, return correlation is the wrong safety check. You need the stronger property.

Cointegration: a stationary combination of non-stationary things

From D1-03: individual prices are non-stationary — unit roots, no fixed mean to revert to, ADF fails to reject. Two price series yty_t and xtx_t are cointegrated if some linear combination

zt=ytγxtz_t = y_t - \gamma\, x_t

is stationary. Each series wanders like a drunk, but they're two drunks handcuffed together: each path is unpredictable, the distance between them is not. The spread ztz_t has a fixed mean, finite variance, and mean-reverts — everything the D1-03 toolkit certifies, and everything the D1-04 machinery can then exploit. Economically the handcuff is usually an arbitrage force: dual listings, share classes, an ETF versus its basket, refiner versus crude — someone with size gets paid to close the gap, so the gap closes.

Engle–Granger in two steps

The classic test for cointegration is almost embarrassingly simple:

  1. Regress yy on xx by OLS (A2-06). The fitted slope γ^\hat\gamma is the hedge ratio — how many units of xx offset one unit of yy.
  2. ADF-test the residuals (D1-03). The residuals are the spread zt=ytγ^xtz_t = y_t - \hat\gamma x_t. If ADF rejects a unit root in zz, the pair is cointegrated: you found a stationary combination.

One honest technicality: because γ^\hat\gamma was fitted — OLS chose the combination that looks most stationary — the ADF critical values must be stricter than the off-the-shelf ones (statsmodels' coint handles this). The two-step structure, though, is exactly regression plus the stationarity test you already know.

Trading the spread

Once ztz_t is certified stationary, it's an AR(1)-style mean reverter and all of D1-04 applies to the spread: estimate its ϕ\phi, get a half-life — the practical timescale of the trade. Half-life of 8 days: tradeable. Half-life of 8 months: your capital ages while the spread thinks about it.

The playbook: compute the spread's z-score (ztzˉ)/σz(z_t - \bar z)/\sigma_z. When it stretches beyond about ±2, short the rich side and buy the cheap side in the ratio γ^\hat\gamma; exit near zero. The position is (roughly) market-neutral — you're long and short similar exposure — so the P&L comes from convergence, not direction. This is the cleanest expression in the whole curriculum of trading a statistical property rather than an opinion.

Honest warnings

Try it

Build the two objects everything above rests on — the hedge ratio (a regression slope via the covariance formula from A2-06) and the spread:

▮ EXERCISE · d1-09-ex1

Implement hedge_ratio(y, x): the OLS slope of y on x, computed as np.cov(y, x)[0, 1] / np.var(x, ddof=1). And spread(y, x): the array y - hedge_ratio(y, x) * x.

⧉ Review card
Why does high return correlation NOT protect a pairs trade?
Correlation measures daily co-movement of returns; the trade's risk lives in price levels. A small persistent drift difference, invisible in correlation, compounds into an unbounded price gap. Only cointegration bounds the spread.
⧉ Review card
Define cointegration.
Two non-stationary (unit-root) series are cointegrated if some linear combination z = y - gamma*x is stationary. Each series wanders; the spread between them mean-reverts — two drunks handcuffed together.
⧉ Review card
Describe the Engle-Granger two-step procedure.
Step 1: OLS-regress y on x; the slope is the hedge ratio. Step 2: ADF-test the residuals (the spread). Rejecting a unit root in the residuals means cointegrated. Caveat: fitted residuals need stricter ADF critical values.
⧉ Review card
How do you turn a cointegrated pair into a trade?
Treat the spread as an AR(1): estimate its half-life for the trade's timescale, z-score it, short the rich leg and buy the cheap leg in hedge-ratio proportion beyond about 2 sigma, exit near zero. Market-neutral; P&L comes from convergence.
⧉ Review card
Three honest failure modes of pairs trading?
1) Cointegration breaks with regime change — a 6-sigma spread may be a dead pair, not a bargain. 2) In-sample hedge ratios overfit; re-estimate rolling. 3) Scanning many pairs is multiple testing (HLZ) — demand an economic link before the statistical test.

Map it

On paper, draw a concept map linking: unit root (D1-03), return correlation (A2-08), OLS slope (A2-06), hedge ratio, spread, ADF test, stationarity, AR(1) half-life (D1-04), z-score entry, and regime break. Arrows should say things like "tested by", "computed as", "applies to", "kills".

Predict before the next lesson

Next is D1-10: regimes and structural breaks — the lesson every warning above has been pointing at. Predict: you fit one GARCH persistence and one pairs hedge ratio on 2005–2024 pooled data. Name two historical events inside that window that plausibly changed the true parameters mid-sample. What would the pooled estimate be a blend of?

◈ Calibration check

Could you explain to a skeptic why cointegration, not correlation, justifies a pairs trade — and walk through Engle-Granger plus its three failure modes from memory?

1 = guessing · 5 = could teach it

⏻ End of lesson

Mark it read to book its 5 review cards into your deck.

Sources & further reading