XanderRobbins
Universal-Pairs-Trading-System
Python

A professional-grade quantitative trading system that implements statistical arbitrage through mean-reversion strategies on cointegrated asset pairs.

Last updated Jul 10, 2026
10
Stars
2
Forks
0
Issues
0
Stars/day
Attention Score
39
Language breakdown
Python 98.1%
Jupyter Notebook 1.9%
โ–ธ Files click to expand
README

Universal Pairs Trading System

A Python backtesting framework for statistical arbitrage on cointegrated asset pairs. Implements mean-reversion signal generation, ATR-based risk management, and comprehensive performance analysis.

Works with any two assets available through yfinance: stocks, ETFs, futures (e.g., CL=F), and forex pairs.


What it does

Given two historically cointegrated assets, the system:

  • Validates the pair statistically (Engle-Granger, Johansen, ADF, half-life)
  • Computes a hedge-ratio-adjusted log spread
  • Generates long/short signals when the z-score deviates beyond a threshold
  • Applies regime detection and volatility-adjusted entry thresholds
  • Sizes positions using ATR-based risk budgeting
  • Runs a backtest with transaction costs and slippage
  • Outputs performance metrics, charts, and a trade journal CSV

Files

| File | Purpose | |------|---------| | config.py | All configuration parameters (pair, strategy, risk, backtest, output) | | data_handler.py | Data download, cleaning, cointegration tests, hedge ratio, spread computation | | strategy.py | Signal generation: z-score, regime detection, momentum filter, position management | | backtester.py | Return calculation, transaction costs, equity curve, drawdown, performance metrics | | risk_manager.py | ATR-based position sizing, stop-loss/take-profit, portfolio heat limits | | visualization.py | Price charts, spread analysis, equity curve, trade distribution, monthly heatmap | | main.py | Orchestration: runs the full pipeline end-to-end, interactive or programmatic | | tests/test_strategy.py | Unit tests for spread math, z-score, cointegration detection, signal validity |


Setup

pip install -r requirements.txt

Requirements: pandas, numpy, matplotlib, seaborn, yfinance, statsmodels, scipy, pytest

Python 3.8+.


How to run

Interactive

python main.py

Prompts you to pick from several pre-configured examples (SPY-QQQ, GLD-SLV, CL-HO, AAPL-MSFT) or enter a custom pair.

Programmatic

from main import runpairsstrategy

result = runpairsstrategy( asset1='GLD', asset2='GDX', pair_name='Gold-Miners', start_date='2015-01-01', initialcapital=500000, asset_type='etf' )

metrics = result['metrics'] print(metrics['SharpeRatio'], metrics['TotalReturn_Pct'])

Multi-pair comparison

from main import runmultiplepairs

pairs = [ ('SPY', 'QQQ', 'SPY-QQQ'), ('GLD', 'SLV', 'Gold-Silver'), ('GLD', 'GDX', 'Gold-Miners'), ]

df = runmultiplepairs(pairs, startdate='2015-01-01', initialcapital=500_000)

Tests

pytest tests/ -v

All tests use synthetic data, no network access required.


Configuration

Strategy and risk parameters live in config.py and can be overridden after construction:

from config import Config

config = Config(asset1='SPY', asset2='QQQ', start_date='2015-01-01')

Strategy

config.strategy.window = 30 # z-score rolling window config.strategy.zentrylong = -2.0 # long entry threshold config.strategy.zentryshort = 2.0 # short entry threshold config.strategy.z_exit = 0.5 # exit threshold

Risk

config.risk.riskpertrade = 0.02 # 2% capital at risk per trade config.risk.maxpositionsize = 0.30 # max 30% capital per position config.risk.atrstopmultiple = 2.5 # stop at 2.5x ATR from entry

Costs

config.backtest.transactioncostpct = 0.0005 # 5 bps commission config.backtest.slippage_pct = 0.0002 # 2 bps slippage config.backtest.commissionpercontract = 2.50 # futures only

Presets are available for common pairs:

config = Config.from_preset('oil-crack')  # CL=F / HO=F
config = Config.from_preset('gold-silver')
config = Config.from_preset('spy-qqq')

Output

Results are saved to results/<pair_name>/:

  • 01_prices.png โ€” Asset1 and Asset2 price series
  • 02_spread.png โ€” Spread with Bollinger bands and z-score with signals
  • 03_equity.png โ€” Equity curve with drawdown
  • 04_trades.png โ€” Trade P&L distribution, MAE/MFE scatter
  • 05_monthly.png โ€” Monthly returns heatmap
  • 06_cointegration.png โ€” Rolling cointegration p-value over time
  • trade_journal.csv โ€” One row per trade with full metadata

Signal logic summary

Entry: z-score crosses entry threshold AND regime is mean-reverting AND momentum confirms direction. Optionally restricted to periods where the rolling cointegration test is significant.

Exit: z-score crosses back through the exit threshold, or regime shifts to Volatile_Trending.

Position sizing: risk amount / (ATR * stop_multiple), capped by capital percentage and portfolio heat limits. Position is halved during extreme volatility (ATR > 90th percentile).


Author

Alexander Robbins University of Florida โ€” Math, CS, Economics robbins.a@ufl.edu https://github.com/XanderRobbins


License

MIT

๐Ÿ”— More in this category

ยฉ 2026 GitRepoTrend ยท XanderRobbins/Universal-Pairs-Trading-System ยท Updated daily from GitHub