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.

ChoiceWhy it mattersConservative practice
Asset universedetermines search breadthuse economically motivated groups
Lag ordercontrols autocorrelated changeschoose before inspecting P&L
Deterministic termsaffects equilibrium trendtest intercept/trend explicitly
Ranknumber of independent spreadsrequire stability across windows
Normalizationchanges share interpretationnormalize 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 constraintPurpose
Gross and net exposurebound leverage and funding use
Per-name/issuer capprevent one illiquid leg dominating
Sector and market betaprevent the basket becoming a directional bet
Turnover penaltystop unstable vectors from consuming alpha
Borrow and liquidity boundsensure 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.
#cointegration #basket trading #VECM #relative value #mean reversion