Cointegration pairs trading
CompleteEngle-Granger pair selection within one sector, walk-forward re-selection, and an honest comparison against buy-and-hold — including the finding that the edge is thin and cost-sensitive.
Motivation
Pairs trading is one of the most-studied statistical arbitrage strategies and a fair test of whether a spread's mean reversion holds up once selection bias and costs are accounted for.
Data
Daily closing prices for 10 liquid large-cap US banks (JPM, BAC, WFC, C, GS, MS, USB, PNC, TFC, COF), 2015-2024, via Yahoo Finance. One sector, so any cointegration reflects shared rate/macro exposure rather than a coincidence.
Methodology
Engle-Granger two-step cointegration test on log prices, run on every pair in the universe (45 combinations) at each re-selection point. The 5 pairs with the lowest p-value below 0.05 are traded; the hedge ratio is fit by OLS on the same formation-window data. Positions are sized on the spread's z-score: enter beyond ±2.0, exit inside ±0.5, stop out beyond ±4.0.
Backtest design
Walk-forward: pairs are re-selected and the hedge ratio re-fit every 63 trading days (one quarter), using only the prior 252 days (one year) — nothing in a trading window ever influences the pair selection or hedge ratio applied to it. Positions are force-closed at the end of each quarter before re-selection. Costs are modeled explicitly: 5bps per leg per trade, plus a 30bps/year borrow cost on the short leg while a position is held — not assumed away.
Results
| Point | Strategy | Buy & hold benchmark |
|---|---|---|
| 2016 | 0.9984 | 0.8834 |
| 1.0066 | 0.8368 | |
| 0.9991 | 0.8783 | |
| 0.994 | 0.934 | |
| 0.991 | 0.9476 | |
| 0.9893 | 0.8793 | |
| 0.992 | 0.9252 | |
| 0.9943 | 0.9975 | |
| 0.9786 | 0.9701 | |
| 0.9786 | 1.0208 | |
| 0.9779 | 1.2031 | |
| 0.9669 | 1.2643 | |
| 2017 | 0.959 | 1.2622 |
| 0.962 | 1.3456 | |
| 0.9741 | 1.2771 | |
| 0.9709 | 1.2548 | |
| 0.9636 | 1.219 | |
| 0.953 | 1.3097 | |
| 0.9536 | 1.3384 | |
| 0.9612 | 1.306 | |
| 0.9623 | 1.3829 | |
| 0.9697 | 1.4394 | |
| 0.9763 | 1.4725 | |
| 0.9708 | 1.5117 | |
| 2018 | 0.9808 | 1.6277 |
| 0.9912 | 1.5824 | |
| 0.9897 | 1.4925 | |
| 0.9872 | 1.4674 | |
| 0.9712 | 1.4568 | |
| 0.964 | 1.4254 | |
| 0.9664 | 1.516 | |
| 0.9579 | 1.529 | |
| 0.9468 | 1.4636 | |
| 0.9333 | 1.4226 | |
| 0.9424 | 1.4289 | |
| 0.9243 | 1.2247 | |
| 2019 | 0.9357 | 1.3669 |
| 0.9222 | 1.3915 | |
| 0.9348 | 1.342 | |
| 0.9432 | 1.4884 | |
| 0.9227 | 1.3463 | |
| 0.9501 | 1.4513 | |
| 0.968 | 1.5173 | |
| 0.974 | 1.4097 | |
| 0.9716 | 1.502 | |
| 0.9668 | 1.5662 | |
| 0.9693 | 1.6557 | |
| 0.9587 | 1.7117 | |
| 2020 | 0.9517 | 1.6222 |
| 0.9599 | 1.4123 | |
| 0.9472 | 1.0146 | |
| 0.9481 | 1.1592 | |
| 0.9498 | 1.1878 | |
| 0.9956 | 1.188 | |
| 1.0282 | 1.1994 | |
| 1.0324 | 1.2435 | |
| 1.0136 | 1.1976 | |
| 1.0101 | 1.2066 | |
| 1.0203 | 1.4636 | |
| 1.0115 | 1.6098 | |
| 2021 | 1.0054 | 1.5935 |
| 1.0111 | 1.8627 | |
| 1.0054 | 1.9778 | |
| 0.9947 | 2.1092 | |
| 0.9917 | 2.2415 | |
| 0.9796 | 2.1526 | |
| 0.9658 | 2.1297 | |
| 1.0054 | 2.2519 | |
| 1.0054 | 2.2411 | |
| 1.0088 | 2.3608 | |
| 1.0052 | 2.1953 | |
| 1.0097 | 2.2086 | |
| 2022 | 0.9905 | 2.2783 |
| 0.9861 | 2.1994 | |
| 0.9803 | 2.0432 | |
| 0.9814 | 1.8479 | |
| 0.9915 | 1.9742 | |
| 0.9898 | 1.7269 | |
| 0.9755 | 1.8701 | |
| 0.9748 | 1.8263 | |
| 0.964 | 1.652 | |
| 0.9701 | 1.8504 | |
| 0.9731 | 1.9754 | |
| 0.9861 | 1.8135 | |
| 2023 | 1.023 | 2.0421 |
| 1.023 | 1.9858 | |
| 1.0745 | 1.6871 | |
| 1.0745 | 1.7241 | |
| 1.0745 | 1.6438 | |
| 1.0745 | 1.7291 | |
| 1.0745 | 1.8991 | |
| 1.0745 | 1.7254 | |
| 1.0745 | 1.6739 | |
| 1.0745 | 1.6123 | |
| 1.0745 | 1.8523 | |
| 1.0758 | 2.1023 | |
| 2024 | 1.0858 | 2.1184 |
| 1.0805 | 2.1541 | |
| 1.0962 | 2.3485 | |
| 1.0972 | 2.2842 | |
| 1.093 | 2.3612 | |
| 1.0881 | 2.3642 | |
| 1.083 | 2.5617 | |
| 1.0614 | 2.5889 | |
| 1.0763 | 2.5437 | |
| 1.0829 | 2.7061 | |
| 1.0829 | 3.0889 | |
| 1.0829 | 2.8867 |
Robustness checks
Entry threshold sensitivity (z = 1.5 / 2.0 / 2.5) keeps Sharpe in a 0.20-0.36 range — thin but not a knife-edge parameter choice. A 5x cost stress test flips the strategy negative (Sharpe -1.04, CAGR -5.5%), which says the edge is real but genuinely thin relative to trading frictions, not robust to them. Splitting by regime: the strategy lost money 2015-2019 (Sharpe -0.24), did well through the 2020 volatility spike (Sharpe 0.78), and was moderately positive after (Sharpe 0.33) — performance is concentrated in high-dispersion periods, not a steady year-round edge. As a leakage check, re-running the same pipeline but letting pair selection and the hedge ratio see the trading window itself (a deliberate look-ahead leak) pushes Sharpe to 1.91 — confirming the walk-forward split is doing real work, and that 0.20 is the honest number, not a bug suppressing it.
Limitations
Sharpe 0.20 is materially below the buy-and-hold benchmark's 0.55 over this window, on both an absolute and risk-adjusted basis — this strategy did not beat a naive long position here, even though its volatility (5.2%) and max drawdown (-9.2%) are far smaller than buy-and-hold's (28.8% / -50.2%). The universe is small and hand-picked (10 names, one sector), so there's real selection bias and correlated tail risk — the 2023 regional-banking stress is exactly the kind of event that hits every pair in this universe at once. Execution is modeled as same-day with flat per-trade costs; real slippage, short-borrow availability during stress, and wider bid-ask names aren't captured. Only 3 threshold values were checked, with no correction for testing multiple parameters.