Pairs Trading Algorithm Development
Pairs trading is a special case of statistical arbitrage: we trade one pair of assets, opening simultaneously a long position in one and short in the other. The strategy is market-neutral: profit doesn't depend on overall market direction, only on spread narrowing/widening between two assets.
Pair Selection
Not all pairs are suitable for trading. Requirements:
Cointegration — statistically significant (p-value < 0.05 by Engle-Granger or Johansen test). Rebalance and check every 2–4 weeks: cointegration may disappear.
Economic sense — pair must have fundamental connection:
- BTC spot vs BTC perpetual futures
- ETH vs stETH (Lido staked ETH)
- Binance Coin (BNB) vs similar exchange tokens
- Competing Layer-1: SOL vs AVAX, DOT vs ATOM
- DeFi token basket (UNI, AAVE, CRV) against DeFi index
Liquidity: both assets must have sufficient volume for execution without significant slippage.
Half-life: period of mean reversion. 3–30 days — acceptable range. Too short — won't have time to enter, too long — capital frozen too long.
Trade Mechanics
Example: BTC/USDT spot and BTCUSDT perpetual futures
Spread = BTC_PERP - BTC_SPOT
Basis = PERP_price - SPOT_price (usually positive due to funding)
If basis abnormally high (Z-score > 2): sell PERP, buy SPOT. Wait for narrowing. Profit = basis change.
Position sizes: for market neutrality, value of both positions should be equal (or weighted by hedge ratio β).
def calculate_position_sizes(capital, hedge_ratio, price_x, price_y):
# Dollar-neutral
position_value = capital / 2
qty_y = position_value / price_y # sell Y
qty_x = qty_y * hedge_ratio # buy X
return qty_x, qty_y
Dynamic Hedge Ratio
Fixed hedge ratio becomes outdated. Use rolling window OLS or Kalman Filter for continuous updating.
Important: sharp change in hedge ratio may signal structural shift in relationships between assets. Monitor β stability.
Pairs Trading Risks
Divergence risk: spread continues widening instead of narrowing. Fundamental reason: one asset changes structurally (delisting, hack, regulatory action). Stop-loss at Z-score = 3 or 4.
Funding risk: for perpetual positions, funding rate may eat profits. Especially when high positive funding on PERP (you pay as PERP short holder).
Liquidity risk: unexpected market move may make simultaneous closing of both legs difficult.
Correlation breakdown: especially relevant in crypto — during market-wide moves (BTC dump/pump) correlations break.
P&L Calculation and Backtesting
def backtest_pairs(spread, z_scores, entry_z=2.0, exit_z=0.5, stop_z=3.5):
position = 0 # 1 = long spread, -1 = short spread
pnl = []
for i, (spread_val, z) in enumerate(zip(spread, z_scores)):
if position == 0:
if z > entry_z:
position = -1 # short spread
entry_spread = spread_val
elif z < -entry_z:
position = 1 # long spread
entry_spread = spread_val
elif position == 1:
current_pnl = spread_val - entry_spread
if z > -exit_z or z < -stop_z:
pnl.append(current_pnl)
position = 0
elif position == -1:
current_pnl = entry_spread - spread_val
if z < exit_z or z > stop_z:
pnl.append(current_pnl)
position = 0
return pnl
Monitoring Active Positions
Every N minutes recalculate Z-score and hedge ratio. Dashboard shows:
- Current Z-score and its history
- P&L of current position
- Unrealized PnL per leg
- Funding payments (for perpetual)
- Time in position vs expected half-life
Alert when approaching stop-loss level or on sharp hedge ratio change (>20% from previous value).
Stack: Python (statsmodels, numpy, pykalman), CCXT for order execution, PostgreSQL for trade history, Grafana for monitoring. Algorithm runs as daemon process with configurable parameters via config file or UI.







