Skip to content

Building an ML Pairs Strategy Against a Cointegration Baseline

Original question

Can you show me how to implement a pairs trading strategy using machine learning instead of traditional cointegration methods?
Define a pair and hedge rule using available information, then train a model to forecast the pair's future relative return. Use trailing spread, volatility and liquidity features, and validate predictions on later dates before turning them into long–short positions. Compare with a simple spread z-score rule under the same costs and borrow assumptions.

Make the two strategies trade the same exposure

For a simple dollar-neutral demonstration, allocate half a dollar to the long leg and short half a dollar of the other per dollar of equity allocated to the strategy. Let rA,tr_{A,t} and rB,tr_{B,t} be the executable forward returns, from next open to following open. The gross target for long A and short B is

yt=12(rA,t−rB,t).y_t=\tfrac12(r_{A,t}-r_{B,t}).

An alternative hedge can use trailing market betas or risk estimates. Recompute the target and position accounting to match that hedge. Build causal features such as the latest five-session relative return and recent dispersion, then train with earlier outcomes. Choose a forecast threshold on validation data. Small differences in predicted relative returns can fall below the total cost of two legs.

Turn a spread forecast into a two-leg order

A model forecasts 0.25% for a dollar-neutral long A and short B pair. Starting from cash, it buys 50% of account equity in A and shorts 50% in B. At 10 basis points per dollar traded, that costs 10 basis points of equity on entry and approximately the same on exit. If the realised return matches the forecast, the trade earns roughly 5 basis points before borrow and any extra slippage. A forecast of 0.10% would give little room for the assumed round trip.
A fair comparison changes the signal and holds execution fixed. Use the same pair universe and formation dates, gross exposure limits, fill clock and borrow assumptions for the cointegration rule and the learned model. Reconcile both legs separately when one is hard to borrow or fills later than the other. Compare gross and net results. If the learned model makes roughly the same directional decisions but trades more often, its small forecast advantage can disappear in the cost of changing both legs.

A walk-forward model for executable spread returns

This fits one first-pass model and simulates a long–short rule. Supply a CSV with consecutive sessions for both assets, with columns date, a_open, b_open, a_close and b_close.

import numpy as np import pandas as pd from sklearn.ensemble import HistGradientBoostingRegressor data = pd.read_csv("pair.csv", parse_dates=["date"]).set_index("date").sort_index() relative = data["a_close"].pct_change() - data["b_close"].pct_change() data["recent_spread"] = relative.rolling(5).sum() data["spread_volatility"] = relative.rolling(20).std() opening = data[["a_open", "b_open"]] forward = opening.shift(-2).div(opening.shift(-1)).sub(1) data["target"] = 0.5 * (forward["a_open"] - forward["b_open"]) data["end_date"] = data.index.to_series().shift(-2) data = data.dropna() features = ["recent_spread", "spread_volatility"] cutoff = data.index[int(len(data) * 0.75)] train = data[data["end_date"] < cutoff] test = data.loc[cutoff:] model = HistGradientBoostingRegressor(max_depth=2, early_stopping=False) model.fit(train[features], train["target"]) prediction = model.predict(test[features]) direction = np.where(abs(prediction) > 0.002, np.sign(prediction), 0) asset_returns = forward.loc[test.index].to_numpy() equity = 1.0 weights = np.zeros(2) for side, returns in zip(direction, asset_returns): target = 0.5 * side * np.array([1.0, -1.0]) turnover = np.abs(target - weights).sum() borrow = 0.0001 * np.maximum(-target, 0).sum() gross_growth = 1 + target @ returns - borrow equity *= (1 - 0.001 * turnover) * gross_growth weights = target * (1 + returns) / gross_growth equity *= 1 - 0.001 * np.abs(weights).sum() # exit both legs print("Net test return:", equity - 1)

The fixed 20-basis-point threshold is illustrative. The simulation assumes both legs fill at the opening prices, cash earns zero, and trading costs reduce equity before new target weights take effect. Estimate borrow and execution separately with historical data before interpreting test returns.

Browse the runnable strategies in Strategy Lab for baselines you can examine before selecting a more flexible signal.

References and next steps