What cointegration actually tests (and what most people get wrong)
Most pair trading tutorials hand-wave around the cointegration test. We walk through what it really checks, why correlation alone isn't enough, and how to spot a 'spurious' cointegration in real data.
By Li Tan
Quick test: which of these statements is correct about pair trading?
- If two assets have correlation > 0.9, they're a good pair trading candidate.
- Cointegration is just a fancy word for high correlation.
- Pair trading works because price ratios always come back.
- None of the above.
Answer: D. None of those is correct, and confusing them is the most common reason naive pair trading strategies blow up.
Correlation vs cointegration: a simple example
Imagine two random walks: each day, asset A goes up by a random small amount, and asset B goes up by a different random amount. Over time, both will trend upward (because both have positive expected drift), and their daily changes will be uncorrelated. Their correlation could easily be zero — but their levels might look strikingly similar over a long sample.
This is the classic 'spurious regression' problem. Two completely independent random walks can produce a high R² in a regression of one on the other, with no real economic relationship. If you use this to build a pair trading strategy, you'll get whipsawed mercilessly the moment the next 'shock' hits one of them.
Correlation tells you about CHANGES moving together. Cointegration tells you about LEVELS staying tied together. They're completely different statistical claims.
What the Engle-Granger test actually does
The two-step Engle-Granger test (1987) — which we use in OpenAlpha's pair_trading.py — runs a regression of asset A on asset B, computes the residuals, and then tests whether those residuals are stationary using an Augmented Dickey-Fuller (ADF) test. If the residuals ARE stationary (the ADF p-value is below 0.05), we conclude that asset A and asset B are cointegrated.
What does 'residuals stationary' mean in plain English? It means: when you compute the spread between A and beta*B, that spread oscillates around a constant mean instead of drifting indefinitely. That oscillation is the only reason mean-reversion trading can work.
Why you need rolling cointegration
Here's the trap most amateur backtests fall into: they run the cointegration test once on the full historical dataset, find pairs that 'pass', and then backtest a strategy on those pairs. This is a look-ahead bias — you're using future information to select pairs you would never have known about in real-time.
OpenAlpha's pair trading strategy re-tests cointegration every recalc_period bars (default 63 = ~3 months), using only data available up to that point. If the pair stops being cointegrated, we close the position. This is more conservative and less profitable in backtest — but it's also the only honest way to do it.
Read the source code
If you want to see exactly how this is implemented, check strategies/pair_trading.py in our GitHub repo. The relevant function is generate_signals() — look for the loop where 'is_cointegrated' gets re-evaluated.
Want to test this yourself? Open the Lab, pick pair_trading strategy with EUR_USD and GBP_USD, and try changing the coint_window parameter. You'll see how the entry/exit signals change as you give the model more or less history to work with.
Like this kind of analysis? Upgrade to Learner for weekly articles, real-time signals, and full educational content.
See Learner plan