Mean Reversion
Everything about this chapter is the mirror image of the last one, including the psychology, and that symmetry is worth holding in mind: trend following has a low win rate with positive skew; mean reversion has a high win rate with negative skew. One feels bad and works; the other feels good and occasionally destroys you.
The effect
Prices overshoot and return. At short horizons (intraday to a few days) and in relative-value contexts (one asset against a related one), returns exhibit negative autocorrelation.
Two distinct families:
Short-term reversal - an asset that dropped sharply over one to five days tends to bounce. Documented in Lehmann (1990) and Lo and MacKinlay (1990). Substantially arbitraged at this point, particularly in liquid large caps.
Relative value / statistical arbitrage - two economically related instruments diverge, and you bet on convergence. Pairs trading is the simplest instance. Gatev, Goetzmann and Rouwenhorst documented meaningful returns to a simple distance-based pairs rule over 1962-2002, with clear decay in later periods as the strategy became widely known.
The mechanism
This is the most important paragraph in the chapter.
Mean reversion profits are compensation for supplying liquidity. When a large holder needs to exit a position quickly, they consume liquidity and push the price beyond fair value. Someone must take the other side. That someone earns a premium for absorbing the imbalance and bearing the risk that the move was information rather than impatience.
You are being paid to be the counterparty to urgency.
This framing tells you exactly when the strategy works and when it doesn't:
- It works when the move is driven by forced or impatient flow - margin liquidations, redemption-driven selling, index rebalancing, month-end flows, someone needing to be flat by the close.
- It fails catastrophically when the move is driven by information. If a stock dropped 15% because the FDA rejected its drug, it is not going to revert, and every mean-reversion signal you own will scream buy the whole way down.
And you cannot distinguish the two in real time from price alone. This is not a solvable problem. It is the fundamental risk of the strategy, and it is what "cheap versus falling" means.
The payoff distribution
- High win rate, often 60-80%. This feels excellent.
- Negative skew. Many small wins, occasional very large losses.
- The Sharpe ratio is misleading. Sharpe assumes roughly symmetric returns. A strategy with 75% winners and rare 10R losses can post an excellent Sharpe right up until the loss arrives. For negative-skew strategies, Sharpe systematically overstates quality.
The psychological trap is specific and well-documented: the high win rate builds confidence, confidence encourages leverage, and leverage converts the eventual tail event from painful into terminal. LTCM is the canonical instance of exactly this shape.
Cointegration, correctly
Correlation is not the right tool for pairs. Two series can be highly correlated while drifting apart indefinitely. What you need is cointegration: a linear combination of two non-stationary series that is itself stationary.
The standard workflow:
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller, coint
def test_cointegration(y: pd.Series, x: pd.Series) -> dict:
"""
Engle-Granger two-step. Note the asymmetry: coint(y, x) and coint(x, y)
give different results, because the regression is not symmetric.
Test both orderings and be suspicious if they disagree.
"""
score, pvalue, _ = coint(y, x)
x_with_const = sm.add_constant(x)
model = sm.OLS(y, x_with_const).fit()
hedge_ratio = model.params.iloc[1]
spread = y - hedge_ratio * x
adf_stat, adf_p, *_ = adfuller(spread.dropna(), maxlag=1)
return {
"coint_pvalue": pvalue,
"hedge_ratio": hedge_ratio,
"adf_pvalue": adf_p,
"spread": spread,
}
def half_life(spread: pd.Series) -> float:
"""
Fit an Ornstein-Uhlenbeck process and return the mean-reversion half-life
in periods. This is the single most useful number for a pairs strategy:
it tells you your expected holding period and therefore whether the
trade survives costs.
d(spread) = lambda * (spread_lagged) + noise
half_life = -ln(2) / lambda
"""
lagged = spread.shift(1)
delta = spread - lagged
df = pd.concat([delta, lagged], axis=1).dropna()
df.columns = ["delta", "lagged"]
model = sm.OLS(df["delta"], sm.add_constant(df["lagged"])).fit()
lam = model.params.iloc[1]
if lam >= 0:
return np.inf # not mean-reverting
return -np.log(2) / lamThe half-life is the number to compute first. If your spread has a half-life of 40 days, a strategy holding for 3 days is not trading mean reversion - it's trading noise. If the half-life is 2 hours and your costs are 10 basis points per round trip, the reversion is smaller than your costs.
The critical caveat: cointegration is unstable out of sample. A pair that cointegrated over 2015-2020 frequently does not over 2021-2026. Test cointegration on rolling windows and treat a relationship that only holds in your full sample as an artefact. Relationships break for structural reasons - a merger, a business model shift, a change in index membership - and when they break, the spread does not come back.
Stops, and the reasoning that ends accounts
Mean reversion has a philosophical problem with stop losses that trend following does not.
If your thesis is "this spread has diverged too far and will converge," then a wider divergence is, by your own thesis, a better entry. Stopping out means exiting at the point your model considers most attractive.
This reasoning is coherent, and it is how people blow up. Averaging into a diverging spread works repeatedly and then once does not, and the once is sized like all the previous times combined.
The resolution is not to abandon stops. It is to recognise that a sufficiently large divergence is evidence that your model is wrong, not evidence of a better opportunity. Set a divergence threshold beyond which you conclude the relationship has broken, and exit. Choosing that threshold is the hard part, and it should be derived from the historical distribution of the spread, not from your tolerance for pain.
How it kills you
- Structural breaks in the relationship, which look exactly like an attractive entry.
- Short leg problems - borrow costs, borrow availability, forced recalls at the worst moment. In equity pairs this is a real and frequently underestimated cost.
- Crowding. Popular pairs unwind violently when many participants exit simultaneously. The August 2007 quant quake was precisely this.
- Cost domination. High turnover means costs eat thin edges. Compute cost per round trip against expected reversion size before anything else.
- The high win rate, which makes all of the above feel manageable until it isn't.