This project implements a statistical arbitrage pairs trading strategy for forex pairs using cointegration, hedge ratio estimation, spread normalization, signal generation, backtesting, walk-forward validation, and pair scanning.
The main objective is to test whether two non-stationary price series have a stable long-term relationship and whether deviations from that relationship can be traded using a mean-reversion strategy.
-
Download daily forex price data with
yfinance -
Validate raw price series using the Augmented Dickey-Fuller test
-
Test pair cointegration using the Engle-Granger method
-
Estimate hedge ratio and intercept with OLS regression
-
Compute residual spread
-
Calculate rolling z-score without look-ahead bias
-
Generate long/short/flat trading signals
-
Backtest the spread strategy with transaction costs
-
Compute performance metrics:
- Total return
- Sharpe ratio
- Maximum drawdown
- Number of trades
-
Generate a four-panel strategy report
-
Run walk-forward validation
-
Scan a forex universe for statistically valid pairs
-
Filter scanner results by AR(1) half-life
The strategy is based on the following statistical workflow:
- Load two forex price series as
YandX. - Check that both raw price series are non-stationary using the ADF test.
- Test whether the two series are cointegrated using the Engle-Granger test.
- Estimate the hedge ratio with OLS:
Y = alpha + beta * X + residual
- Compute the residual spread:
spread = Y - beta * X - alpha
-
Confirm that the spread is stationary.
-
Convert the spread into a rolling z-score.
-
Generate trading signals:
z < -2: long spreadz > 2: short spread-0.5 < z < 0.5: close position
-
Backtest the strategy using shifted signals to avoid look-ahead bias.
Signals are shifted by one period before being applied to returns. This means a signal generated at time t is only traded from time t+1.
The walk-forward validation splits the dataset into rolling train/test windows.
For each fold:
- The hedge ratio is estimated only on the training window.
- Spread mean and standard deviation are learned only from the training spread.
- The test window is traded using frozen training parameters.
- Folds are skipped if the training data does not satisfy the statistical requirements.
This provides a more realistic out-of-sample validation compared to a full-sample backtest.
The pair scanner tests all pair combinations inside a given forex universe.
For each pair, it applies:
- ADF tests on raw price series
- Engle-Granger cointegration test
- OLS hedge ratio estimation
- Spread stationarity check
- AR(1) half-life calculation
- Half-life filter:
5 < half_life < 60
Valid pairs are sorted by cointegration p-value.
Clone the repository and install the required Python packages:
pip install -r requirements.txtRun the main script:
python pairs.pyThe script will:
- Download forex price data
- Run stationarity and cointegration tests
- Build the pairs trading strategy
- Run the backtest
- Run walk-forward validation
- Run the pairs scanner
- Save the strategy plot
-----Backtest Results-----
Total return: 1.46%
Max drawdown: -3.99%
N trades: 30
Sharpe ratio: 0.1577
-----Walk Forward Results-----
WF Combined Return: 3.55%
WF Mean Fold Return: 1.17%
WF Mean Sharpe: 1.3610
WF Folds: 3
WF Skipped: 3
The strategy creates a four-panel visual report:
- Normalized prices of both assets
- Residual spread
- Z-score with entry and exit zones
- Cumulative strategy returns
The plot is saved as:
pairs_result.png
This project is a simplified research backtest. It does not model all real-world trading conditions.
Current limitations include:
- No bid/ask spread modeling
- No slippage modeling
- No leverage or margin modeling
- No realistic FX order execution
- No live trading integration
- Simplified spread-level return calculation
- Historical statistical relationships may break in future market regimes
This project is for educational and research purposes only. It is not financial advice and should not be used as a live trading system without further validation, risk management, and execution modeling.