Mean Reversion and Statistical Arbitrage › Statistical Arbitrage: Scaling Beyond Pairs
Quantitative FinanceAll chapters Mean Reversion and Statistical Arbitrage › Statistical Arbitrage: Scaling Beyond PairsOne section · Sections in this chapter6 / 12 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
While pairs trading focuses on individual relationships, statistical arbitrage scales these ideas to portfolios of many small bets. The diversification across many positions is what turns mean reversion trading from speculation into a more systematic, lower-risk strategy. This scaling is more than an incremental improvement because it changes the risk-return characteristics of mean reversion trading.
From Pairs to Portfolios
A stat arb portfolio might hold 100 or more positions simultaneously, each a small mean reversion bet. The key insight is that if each bet has a modest positive expected return but significant idiosyncratic risk, diversification dramatically improves the Sharpe ratio of the portfolio. Basic portfolio theory explains the improvement: independent risks partially cancel when the positions are combined.
Consider independent mean reversion bets, each with expected return and standard deviation . The portfolio expected return scales linearly with the number of positions, as we simply add up the expected returns: total expected return is . However, the portfolio standard deviation grows more slowly. Because the positions are independent, their risks partially cancel, and the portfolio standard deviation is only (assuming independence). The portfolio Sharpe ratio therefore scales as the square root of the number of positions:
where:
- : Sharpe ratio of the portfolio
- : number of independent positions
- : expected return of each position
- : volatility of each position
- : Sharpe ratio of an individual bet
This scaling is the fundamental appeal of statistical arbitrage. Even if individual bets have modest Sharpe ratios, aggregating many uncorrelated bets can produce impressive risk-adjusted returns. A single mean reversion trade with a Sharpe ratio of 0.3 is hardly exciting, but a portfolio of 100 such independent trades would have a Sharpe ratio of 3.0, which is exceptional by any standard. Of course, in practice the positions are never perfectly independent, so the actual diversification benefit is smaller, but the principle remains powerful.


Factor-Based Statistical Arbitrage
Modern stat arb strategies typically use factor models to construct portfolios. Building on the factor model framework from Part IV, we decompose returns as:
where:
- : return of stock
- : stock-specific expected return (the trading signal)
- : returns of common factors (market, sector, style)
- : number of common factors
- : exposure of stock to factor
- : idiosyncratic (residual) return
This decomposition separates stock returns into two components: the part driven by common factors (market movements, sector rotations, style tilts) and the part that is unique to each stock. The stat arb strategy focuses on capturing the component while hedging out factor exposures. A dollar-neutral, beta-neutral portfolio with many long and short positions isolates idiosyncratic returns from factor movements.
This approach makes sense. Factor movements are notoriously difficult to predict, and betting on them exposes the portfolio to large, correlated risks. By constructing a portfolio that has zero exposure to common factors, we eliminate this source of risk entirely. What remains is idiosyncratic risk, which is diversifiable: with enough positions, the idiosyncratic risks cancel out, leaving only the alpha signal.
from sklearn.decomposition import PCA
def create_stat_arb_portfolio(returns, n_factors=3, lookback=60):
"""
Create a statistical arbitrage portfolio using PCA-based factor model.
Returns weights that are dollar-neutral and factor-neutral.
"""
n_assets = returns.shape[1]
# Estimate factor model using PCA (as we covered in Part III)
pca = PCA(n_components=n_factors)
pca.fit(returns[-lookback:])
# Factor returns and loadings
factor_loadings = pca.components_.T # Shape: (n_assets, n_factors)
factor_returns = pca.transform(returns[-lookback:])
# Residual returns (idiosyncratic component)
reconstructed = factor_returns @ factor_loadings.T
residuals = returns[-lookback:] - reconstructed
# Mean residual return as alpha signal
alpha_signal = np.mean(residuals, axis=0)
# Z-score the signal
z_signal = (alpha_signal - np.mean(alpha_signal)) / np.std(alpha_signal)
# Create initial weights based on alpha signal (long high alpha, short low alpha)
raw_weights = z_signal
# Project onto the joint null space of dollar and factor exposures.
constraints = np.column_stack([np.ones(n_assets), factor_loadings])
projection = constraints @ np.linalg.pinv(constraints)
raw_weights = raw_weights - projection @ raw_weights
# Normalize to target gross exposure of 2 (100% long, 100% short)
weights = raw_weights / np.sum(np.abs(raw_weights)) * 2
return {
"weights": weights,
"alpha_signal": alpha_signal,
"factor_loadings": factor_loadings,
"residuals": residuals,
}
# Generate synthetic returns for 20 stocks
np.random.seed(456)
n_days = 252
n_stocks = 20
# Create correlated returns with common factors
true_factors = np.random.randn(n_days, 3)
true_loadings = np.random.randn(n_stocks, 3) * 0.5
idiosyncratic = np.random.randn(n_days, n_stocks) * 0.02
stock_returns = true_factors @ true_loadings.T + idiosyncratic
# Add some alpha to certain stocks
stock_returns[:, :5] += 0.001 # Small positive alpha for first 5 stocks
stock_returns[:, -5:] -= 0.001 # Small negative alpha for last 5 stocks
portfolio_result = create_stat_arb_portfolio(
stock_returns, n_factors=3, lookback=120
)Statistical Arbitrage Portfolio Construction ================================================== Portfolio Statistics: Number of assets: 20 Long positions: 9 Short positions: 10 Sum of weights (dollar neutrality): 0.000000 Gross exposure: 2.00 Factor Exposures (should be near zero): Factor 1: -0.000000 Factor 2: 0.000000 Factor 3: -0.000000

The constructed portfolio achieves the desired properties: the sum of weights is effectively zero (dollar neutrality), and exposure to the three common factors is negligible. This confirms that the portfolio isolates idiosyncratic risk, betting purely on the convergence of residuals.


Mean Reversion in the Residual Space
The factor-neutral construction ensures that portfolio returns come primarily from the convergence of idiosyncratic mispricings. When a stock's residual return is abnormally low (negative alpha signal), the portfolio goes long expecting reversion. When residual return is abnormally high, the portfolio goes short. The underlying assumption is that these idiosyncratic deviations are temporary; they reflect noise, overreaction, or short-term supply-demand imbalances rather than permanent changes in fundamental value.
This approach assumes that idiosyncratic returns mean-revert, meaning that temporary mispricings correct themselves. The economic rationale is similar to pairs trading but operates at the level of individual securities rather than pairs. When a stock underperforms after controlling for all known factors, it suggests the market has temporarily mispriced the stock, and we expect correction. The half-life of this mean reversion is typically on the order of days to weeks for equity stat arb strategies.
def backtest_stat_arb_portfolio(
returns, lookback=60, rebalance_freq=5, n_factors=3
):
"""Backtest a statistical arbitrage strategy with periodic rebalancing."""
n_days, n_assets = returns.shape
portfolio_returns = []
all_weights = []
for t in range(lookback, n_days, rebalance_freq):
# Construct portfolio using data up to t
historical_returns = returns[:t]
if len(historical_returns) < lookback:
continue
result = create_stat_arb_portfolio(
historical_returns, n_factors=n_factors, lookback=lookback
)
weights = result["weights"]
# Calculate returns until next rebalance
end_t = min(t + rebalance_freq, n_days)
period_returns = returns[t:end_t]
for day_ret in period_returns:
portfolio_returns.append(np.dot(weights, day_ret))
all_weights.append(weights)
return np.array(portfolio_returns), all_weights
stat_arb_returns, stat_arb_weights = backtest_stat_arb_portfolio(
stock_returns, lookback=60, rebalance_freq=5
)Statistical Arbitrage Backtest Results ================================================== Stat Arb Strategy: Total Return: 17.28% Annualized Sharpe: 1.26 Maximum Drawdown: -17.50% Daily Volatility: 1.122% Equal-Weight Benchmark: Total Return: -77.10% Annualized Sharpe: -0.59

In this synthetic sample, the statistical arbitrage strategy earns 17.28% with a 1.26 Sharpe ratio, while the equal-weight benchmark loses 77.10% and has a -0.59 Sharpe ratio. The comparison illustrates how factor neutralization can reduce common-market exposure, but the simulated gap should not be read as an expected live-trading advantage.