Cointegrated Basket Trading
A cointegrated basket extends pairs trading to several assets whose individual prices may wander but whose particular linear combination is stationary. The advantage is economic breadth: a sector, curve, or capital-structure relationship is rarely fully described by two securities. The cost is selection risk. Every extra instrument gives an optimizer another way to manufacture an attractive in-sample spread.
For log-price vector p_t, a cointegrating vector w defines:
s_t = w' p_t
If st is stationary while the components of pt are integrated, deviations from the basket equilibrium can support a relative-value trade. This is not ordinary correlation. Cointegration explained covers the statistical distinction; in trading, the important implication is that a common trend can be neutralized without requiring every constituent to be individually mean reverting.
Finding a vector without data mining
The Johansen procedure fits a vector error-correction model (VECM) and estimates both the cointegration rank and candidate vectors:
Delta p_t = alpha beta' p_(t-1) + Gamma Delta p_(t-1) + epsilon_t
Columns of beta generate stationary combinations, while alpha measures which assets respond when the equilibrium is displaced. Read the detailed mechanics in Johansen tests and VECM. Test rank on a formation sample, select a vector using a predeclared normalization, then freeze it for the next holding interval. Re-selecting the vector daily uses future information unless the entire selection schedule is replicated in the backtest.
| Choice | Why it matters | Conservative practice |
|---|---|---|
| Asset universe | determines search breadth | use economically motivated groups |
| Lag order | controls autocorrelated changes | choose before inspecting P&L |
| Deterministic terms | affects equilibrium trend | test intercept/trend explicitly |
| Rank | number of independent spreads | require stability across windows |
| Normalization | changes share interpretation | normalize to investable gross exposure |
import numpy as np
from statsmodels.tsa.vector_ar.vecm import coint_johansen
def basket_weights(log_prices, det_order=0, k_ar_diff=1):
result = coint_johansen(log_prices, det_order, k_ar_diff)
# Choose rank through pre-specified trace-test logic in production.
w = result.evec[:, 0]
# Normalize for comparability; actual shares also need price and multiplier scaling.
return w / np.sum(np.abs(w))
def basket_zscore(log_prices, weights, lookback=60):
spread = log_prices @ weights
mu = spread.rolling(lookback).mean()
sd = spread.rolling(lookback).std()
return (spread - mu) / sd
The example intentionally does not infer rank from one test result or trade the last observation at its own close. A production implementation must define missing-price handling, point-in-time constituents, trading calendars, and what happens when a constituent is suspended or delisted.
Turning a spread into an order
When the basket z-score is positive, short positive-weight legs and buy negative-weight legs in proportions implied by the vector; reverse when it is negative. Transform mathematical weights into shares using current prices, contract multipliers, beta, and volatility. Dollar neutrality is often desirable but is not automatic from sum(w)=0, especially for log-price vectors.
| Portfolio constraint | Purpose |
|---|---|
| Gross and net exposure | bound leverage and funding use |
| Per-name/issuer cap | prevent one illiquid leg dominating |
| Sector and market beta | prevent the basket becoming a directional bet |
| Turnover penalty | stop unstable vectors from consuming alpha |
| Borrow and liquidity bounds | ensure the theoretical short is executable |
Estimate residual half-life and size slower spreads less aggressively, as in OU mean-reversion trading. However, an OU fit is a diagnostic, not proof that a VECM equilibrium is stable. Consider entry bands, a time stop after several expected half-lives, and a re-estimation schedule that is slower than the trade horizon.
Economic and execution failures
Cointegration can break for a valid reason: an acquisition changes a firm’s capital structure, a commodity producer changes hedge policy, or an index provider changes membership. A basket can also be statistically stationary because prices were stale or share a common recording convention. Plot the spread, inspect component weights, and require an economic explanation for each family of candidates.
Costs compound across legs. Crossing four bid-ask spreads, financing longs, paying hard-to-borrow fees, and trading at different exchange closes can turn a smooth backtest into a negative strategy. Backtest with leg-level execution assumptions and partial-fill logic. Capacity is governed by the least liquid leg and may shrink precisely when residual dispersion rises.
Validate using walk-forward formation and trading periods, not a single full-sample fit. Report gross/net returns, turnover, borrow availability, factor exposures, and performance by volatility regime. Correct for the number of baskets, lag orders, and thresholds tested. A surviving basket should be robust to modest changes in window, normalization, and entry rule.
Key takeaways
- Cointegration identifies stationary combinations of nonstationary assets, not merely correlated ones.
- Johansen vectors require point-in-time, walk-forward rank and vector selection.
- Convert mathematical weights into a constrained, executable multi-leg portfolio.
- Economic review, borrow, and leg-level costs are as important as the stationarity test.
