Statistical Arbitrage in Crypto — Pairs Trading and Mean Reversion

Statistical arbitrage exploits temporary deviations from equilibrium prices. When two assets that normally move together diverge, a stat arb trader buys the underperformer and shorts the outperformer, betting on convergence. In crypto, this is especially powerful because assets are correlated but not perfectly, creating profitable pockets.

Example: BTC and ETH correlation is 0.85. When ETH underperforms BTC by 3% over a 1-hour period (unusual), a stat arb trader shorts BTC and longs ETH. Historically, they converge within 4-8 hours, locking in ~1-1.5% profit.

Pairs Trading Basics

Selecting Pairs

Not all pairs work. You need:

Python — Pair selection
import pandas as pd
from scipy.stats import pearsonr
def find_cointegrated_pairs(symbols, data):
candidates = []
for i, sym1 in enumerate(symbols):
for sym2 in symbols[i+1:]:
# Calculate correlation
corr, p_value = pearsonr(data[sym1], data[sym2])
if corr > 0.75:
# Test cointegration (Engle-Granger test)
residuals = data[sym1] - (data[sym2] * corr)
adf_stat = adf_test(residuals) # stationarity
if adf_stat < 0.05: # stationary
candidates.append((sym1, sym2, corr))
return candidates

Cointegration Analysis

What is Cointegration?

Two non-stationary series are cointegrated if their linear combination is stationary. In plain English: they move together over time, and deviations from that relationship eventually revert.

For BTC and ETH:

Finding the Hedge Ratio

Use ordinary least squares regression to find the optimal ratio:

Python — Hedge ratio calculation
from sklearn.linear_model import LinearRegression
def get_hedge_ratio(price1, price2):
# Regress price1 on price2 to find beta (hedge ratio)
X = price2.reshape(-1, 1)
y = price1
model = LinearRegression().fit(X, y)
hedge_ratio = model.coef_[0]
intercept = model.intercept_
return hedge_ratio, intercept
# Spread = price1 - (hedge_ratio × price2) + intercept
beta, alpha = get_hedge_ratio(btc_prices, eth_prices)
spread = btc - (beta * eth)

Mean Reversion Strategies

Using Z-Scores

Standardize the spread to detect extremes. When z-score > 2, the spread is unusually wide (trade opportunity):

Python — Mean reversion trading
def mean_reversion_signal(spread, lookback=20):
# Calculate rolling mean and std
mean = spread.rolling(window=lookback).mean()
std = spread.rolling(window=lookback).std()
# Z-score: how many std devs from mean
z_score = (spread - mean) / std
# Trading signals
if z_score[-1] > 2.0:
return "LONG_SPREAD" # spread too wide, buy underperformer
elif z_score[-1] < -2.0:
return "SHORT_SPREAD" # spread too tight, short underperformer
else:
return "NEUTRAL"

Exit Rules

Close when spread returns to mean (z-score → 0) or loss threshold (stop at z-score reversal):

Python — Exit logic
def check_exit(entry_z_score, current_z_score, entry_price):
# Take profit: spread converged halfway back to mean
if abs(current_z_score) < abs(entry_z_score) * 0.5:
return "EXIT", "profit_target"
# Stop loss: z-score moved in wrong direction by >1
if (entry_z_score > 0 and current_z_score < entry_z_score + 1.0):
return "EXIT", "stop_loss"
elif (entry_z_score < 0 and current_z_score > entry_z_score - 1.0):
return "EXIT", "stop_loss"
return "HOLD", None

Backtesting Stat Arb Strategies

Key metrics for stat arb:

Python — Backtest pairs strategy
def backtest_pairs_strategy(price1, price2, hedge_ratio):
spread = price1 - (hedge_ratio * price2)
mean = spread.rolling(20).mean()
std = spread.rolling(20).std()
z_score = (spread - mean) / std
trades = []
position = None
for i in range(len(z_score)):
if position is None:
if z_score[i] > 2.0:
position = {"entry": spread[i], "z_entry": z_score[i], "idx": i}
else:
exit_reason = check_exit(position["z_entry"], z_score[i], spread[i])[1]
if exit_reason:
pnl = position["entry"] - spread[i]
trades.append({"pnl": pnl, "reason": exit_reason})
position = None
return pd.DataFrame(trades)

Adding Smart Money Intelligence

Enhance pairs trading by filtering trades with Smart Money confirmation:

Python — Filtered pairs strategy
def smart_money_pairs_strategy(symbol1, symbol2, z_score):
# Check pair-level Smart Money confirmation
sm1 = get_smart_money_signal(symbol1)
sm2 = get_smart_money_signal(symbol2)
# If one is HIGH bullish and other is HIGH bearish = strong divergence
# Great time to trade the spread
divergence_score = abs(sm1["composite"] - sm2["composite"])
if z_score > 2.0 and divergence_score > 0.3 and sm1["confidence"] != "VETO":
return "TRADE" # High confidence signal
else:
return "SKIP" # Wait for alignment

Supercharge stat arb with Smart Money pairs signals

Filter mean reversion trades with whale consensus and derivatives data. Our API confirms pair divergence validity with 62% accuracy improvement.

Start Trading Pairs →