Files
AlphaFlow-MT5-ML-DL-Trading…/notebooks/strategies/ml/backtesting/pairs_trading_cointegration.ipynb
T

975 KiB

Scan All Symbols for Cointegration

In [ ]:
import MetaTrader5 as mt5
import pandas as pd
import itertools
from statsmodels.tsa.stattools import coint

# Initialize MT5 connection
if not mt5.initialize():
    print("MT5 Initialization Failed")
    mt5.shutdown()
    quit()

# Retrieve all available symbols
symbols = [s.name for s in mt5.symbols_get()]
print(f"✅ Found {len(symbols)} symbols in MT5")

# Limit to top N symbols for testing (remove this for full scan)
symbols = symbols[:50]  # Adjust as needed for performance

# Function to fetch price data
def get_data(symbol, n_bars=1000, timeframe=mt5.TIMEFRAME_D1):
    rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, n_bars)
    if rates is None:
        print(f"⚠️ Could not retrieve data for {symbol}")
        return None
    df = pd.DataFrame(rates)
    df['time'] = pd.to_datetime(df['time'], unit='s')
    df.set_index('time', inplace=True)
    return df

# Load data for selected symbols
price_data = {symbol: get_data(symbol) for symbol in symbols if get_data(symbol) is not None}

# Find cointegrated pairs
cointegrated_pairs = []
for sym1, sym2 in itertools.combinations(price_data.keys(), 2):
    try:
        p_value = coint(price_data[sym1]["close"], price_data[sym2]["close"])[1]
        if p_value < 0.05:  # Cointegration threshold
            cointegrated_pairs.append((sym1, sym2, p_value))
            print(f"✅ Cointegrated Pair: {sym1} & {sym2} (p={p_value:.4f})")
    except Exception as e:
        print(f"⚠️ Error processing {sym1} & {sym2}: {e}")

# Sort pairs by strongest cointegration (smallest p-value)
cointegrated_pairs.sort(key=lambda x: x[2])

# Display best pairs
print("\n🔹 Best Cointegrated Pairs:")
for pair in cointegrated_pairs[:10]:  # Show top 10 pairs
    print(f"⚖️ {pair[0]} & {pair[1]} (p={pair[2]:.4f})")

# Shutdown MT5 connection
mt5.shutdown()
✅ Found 2061 symbols in MT5
✅ Cointegrated Pair: EURUSD & AUDNZD (p=0.0059)
✅ Cointegrated Pair: USDCHF & EURAUD (p=0.0083)
✅ Cointegrated Pair: USDCHF & EURCAD (p=0.0279)
✅ Cointegrated Pair: USDCHF & GBPAUD (p=0.0099)
✅ Cointegrated Pair: USDCHF & GBPNOK (p=0.0316)
✅ Cointegrated Pair: USDCHF & NOKSEK (p=0.0437)
✅ Cointegrated Pair: USDJPY & AUDJPY (p=0.0037)
✅ Cointegrated Pair: AUDUSD & AUDJPY (p=0.0134)
✅ Cointegrated Pair: AUDUSD & CHFJPY (p=0.0103)
✅ Cointegrated Pair: AUDUSD & EURJPY (p=0.0171)
✅ Cointegrated Pair: AUDUSD & EURCHF (p=0.0032)
✅ Cointegrated Pair: AUDUSD & GBPCHF (p=0.0097)
✅ Cointegrated Pair: AUDUSD & GBPJPY (p=0.0270)
✅ Cointegrated Pair: AUDUSD & CADJPY (p=0.0240)
✅ Cointegrated Pair: AUDUSD & NZDCHF (p=0.0100)
✅ Cointegrated Pair: AUDUSD & NZDJPY (p=0.0260)
✅ Cointegrated Pair: AUDUSD & NZDUSD (p=0.0004)
✅ Cointegrated Pair: AUDUSD & GBPSEK (p=0.0420)
✅ Cointegrated Pair: AUDUSD & SEKJPY (p=0.0312)
✅ Cointegrated Pair: AUDUSD & SGDJPY (p=0.0049)
✅ Cointegrated Pair: AUDUSD & USDCNH (p=0.0080)
✅ Cointegrated Pair: AUDNZD & NZDCAD (p=0.0092)
✅ Cointegrated Pair: AUDNZD & NZDUSD (p=0.0275)
✅ Cointegrated Pair: AUDNZD & EURHKD (p=0.0037)
✅ Cointegrated Pair: AUDNZD & EURSGD (p=0.0096)
✅ Cointegrated Pair: AUDNZD & USDCZK (p=0.0074)
✅ Cointegrated Pair: AUDCAD & AUDJPY (p=0.0259)
✅ Cointegrated Pair: AUDCAD & CHFJPY (p=0.0413)
✅ Cointegrated Pair: AUDCAD & EURJPY (p=0.0406)
✅ Cointegrated Pair: AUDCAD & EURCHF (p=0.0325)
✅ Cointegrated Pair: AUDCAD & GBPCHF (p=0.0235)
✅ Cointegrated Pair: AUDCAD & GBPJPY (p=0.0477)
✅ Cointegrated Pair: AUDCAD & CADJPY (p=0.0228)
✅ Cointegrated Pair: AUDCAD & NZDCAD (p=0.0103)
✅ Cointegrated Pair: AUDCAD & NZDJPY (p=0.0308)
✅ Cointegrated Pair: AUDCAD & CHFSGD (p=0.0473)
✅ Cointegrated Pair: AUDCAD & EURSEK (p=0.0467)
✅ Cointegrated Pair: AUDCAD & EURZAR (p=0.0404)
✅ Cointegrated Pair: AUDCAD & GBPSGD (p=0.0495)
✅ Cointegrated Pair: AUDCAD & NOKJPY (p=0.0103)
✅ Cointegrated Pair: AUDCAD & SEKJPY (p=0.0440)
✅ Cointegrated Pair: AUDCAD & SGDJPY (p=0.0443)
✅ Cointegrated Pair: AUDCAD & USDCNH (p=0.0238)
✅ Cointegrated Pair: AUDCHF & EURJPY (p=0.0447)
✅ Cointegrated Pair: AUDCHF & EURNZD (p=0.0127)
✅ Cointegrated Pair: AUDCHF & EURDKK (p=0.0204)
✅ Cointegrated Pair: AUDCHF & EURNOK (p=0.0409)
✅ Cointegrated Pair: AUDCHF & EURZAR (p=0.0306)
✅ Cointegrated Pair: AUDCHF & GBPSEK (p=0.0232)
✅ Cointegrated Pair: AUDJPY & EURCHF (p=0.0391)
✅ Cointegrated Pair: AUDJPY & CADJPY (p=0.0318)
✅ Cointegrated Pair: AUDJPY & USDCNH (p=0.0480)
✅ Cointegrated Pair: CHFJPY & EURJPY (p=0.0242)
✅ Cointegrated Pair: CHFJPY & EURDKK (p=0.0232)
✅ Cointegrated Pair: CHFJPY & GBPSEK (p=0.0284)
✅ Cointegrated Pair: EURAUD & CHFSGD (p=0.0284)
✅ Cointegrated Pair: EURJPY & NZDCHF (p=0.0353)
✅ Cointegrated Pair: EURJPY & EURDKK (p=0.0029)
✅ Cointegrated Pair: EURJPY & GBPSEK (p=0.0155)
✅ Cointegrated Pair: EURJPY & USDCNH (p=0.0398)
✅ Cointegrated Pair: EURCHF & CADJPY (p=0.0243)
✅ Cointegrated Pair: EURCHF & NZDJPY (p=0.0450)
✅ Cointegrated Pair: EURCHF & NZDUSD (p=0.0040)
✅ Cointegrated Pair: EURCHF & AUDSGD (p=0.0366)
✅ Cointegrated Pair: EURCHF & SGDJPY (p=0.0137)
✅ Cointegrated Pair: EURCHF & USDCNH (p=0.0272)
✅ Cointegrated Pair: EURNZD & GBPJPY (p=0.0498)
✅ Cointegrated Pair: EURNZD & CADCHF (p=0.0067)
✅ Cointegrated Pair: EURNZD & EURDKK (p=0.0234)
✅ Cointegrated Pair: EURNZD & EURNOK (p=0.0157)
✅ Cointegrated Pair: EURNZD & GBPNOK (p=0.0008)
✅ Cointegrated Pair: EURNZD & GBPSEK (p=0.0343)
✅ Cointegrated Pair: EURCAD & NOKSEK (p=0.0463)
✅ Cointegrated Pair: GBPCHF & CADJPY (p=0.0391)
✅ Cointegrated Pair: GBPCHF & NZDUSD (p=0.0135)
✅ Cointegrated Pair: GBPCHF & USDCNH (p=0.0358)
✅ Cointegrated Pair: GBPJPY & EURDKK (p=0.0006)
✅ Cointegrated Pair: GBPJPY & EURTRY (p=0.0008)
✅ Cointegrated Pair: GBPJPY & GBPSEK (p=0.0307)
✅ Cointegrated Pair: GBPJPY & GBPTRY (p=0.0044)
✅ Cointegrated Pair: GBPJPY & SEKJPY (p=0.0242)
✅ Cointegrated Pair: CADCHF & EURDKK (p=0.0390)
✅ Cointegrated Pair: CADCHF & EURNOK (p=0.0225)
✅ Cointegrated Pair: CADCHF & GBPNOK (p=0.0044)
✅ Cointegrated Pair: CADJPY & NZDJPY (p=0.0275)
✅ Cointegrated Pair: GBPAUD & CHFSGD (p=0.0319)
✅ Cointegrated Pair: GBPAUD & EURPLN (p=0.0082)
✅ Cointegrated Pair: GBPCAD & EURPLN (p=0.0464)
✅ Cointegrated Pair: GBPNZD & EURPLN (p=0.0043)
✅ Cointegrated Pair: NZDCAD & NZDJPY (p=0.0267)
✅ Cointegrated Pair: NZDCAD & EURHKD (p=0.0409)
✅ Cointegrated Pair: NZDCAD & NOKJPY (p=0.0145)
✅ Cointegrated Pair: NZDCHF & AUDSGD (p=0.0109)
✅ Cointegrated Pair: NZDCHF & GBPSEK (p=0.0124)
✅ Cointegrated Pair: NZDCHF & USDCNH (p=0.0489)
✅ Cointegrated Pair: NZDJPY & NZDUSD (p=0.0184)
✅ Cointegrated Pair: NZDJPY & EURDKK (p=0.0263)
✅ Cointegrated Pair: NZDJPY & EURSEK (p=0.0455)
✅ Cointegrated Pair: NZDJPY & GBPSEK (p=0.0484)
✅ Cointegrated Pair: NZDJPY & USDCNH (p=0.0337)
✅ Cointegrated Pair: NZDUSD & SGDJPY (p=0.0207)
✅ Cointegrated Pair: USDSGD & EURNOK (p=0.0378)
✅ Cointegrated Pair: USDSGD & NOKSEK (p=0.0244)
✅ Cointegrated Pair: AUDSGD & GBPSEK (p=0.0438)
✅ Cointegrated Pair: CHFSGD & EURDKK (p=0.0184)
✅ Cointegrated Pair: CHFSGD & EURNOK (p=0.0406)
✅ Cointegrated Pair: CHFSGD & EURPLN (p=0.0145)
✅ Cointegrated Pair: CHFSGD & EURZAR (p=0.0343)
✅ Cointegrated Pair: CHFSGD & GBPNOK (p=0.0194)
✅ Cointegrated Pair: CHFSGD & GBPSEK (p=0.0473)
✅ Cointegrated Pair: EURDKK & EURNOK (p=0.0176)
✅ Cointegrated Pair: EURDKK & EURPLN (p=0.0300)
✅ Cointegrated Pair: EURDKK & EURTRY (p=0.0016)
✅ Cointegrated Pair: EURDKK & GBPNOK (p=0.0010)
✅ Cointegrated Pair: EURDKK & GBPSEK (p=0.0109)
✅ Cointegrated Pair: EURDKK & GBPTRY (p=0.0025)
✅ Cointegrated Pair: EURDKK & SEKJPY (p=0.0025)
✅ Cointegrated Pair: EURPLN & GBPNOK (p=0.0148)
✅ Cointegrated Pair: EURSEK & EURZAR (p=0.0334)
✅ Cointegrated Pair: EURTRY & SEKJPY (p=0.0320)
✅ Cointegrated Pair: GBPSEK & GBPTRY (p=0.0201)
✅ Cointegrated Pair: GBPSEK & SEKJPY (p=0.0292)
✅ Cointegrated Pair: GBPSEK & SGDJPY (p=0.0222)
✅ Cointegrated Pair: GBPTRY & SEKJPY (p=0.0315)
✅ Cointegrated Pair: NOKJPY & SGDJPY (p=0.0357)
✅ Cointegrated Pair: NOKJPY & USDCNH (p=0.0328)
✅ Cointegrated Pair: NOKJPY & USDCZK (p=0.0034)
✅ Cointegrated Pair: SEKJPY & SGDJPY (p=0.0484)

🔹 Best Cointegrated Pairs:
⚖️ AUDUSD & NZDUSD (p=0.0004)
⚖️ GBPJPY & EURDKK (p=0.0006)
⚖️ EURNZD & GBPNOK (p=0.0008)
⚖️ GBPJPY & EURTRY (p=0.0008)
⚖️ EURDKK & GBPNOK (p=0.0010)
⚖️ EURDKK & EURTRY (p=0.0016)
⚖️ EURDKK & SEKJPY (p=0.0025)
⚖️ EURDKK & GBPTRY (p=0.0025)
⚖️ EURJPY & EURDKK (p=0.0029)
⚖️ AUDUSD & EURCHF (p=0.0032)
True

Feature Engineering for Pair Trading
We need to compute additional statistical indicators to enhance our entry and exit signals.

🛠 Key Features:
1️⃣ Price Spread: Difference between two stock prices
2️⃣ Z-score: Standardized spread for signal generation
3️⃣ Rolling Mean & Std: For mean-reversion detection
4️⃣ Correlation & Beta Hedging: Adjust position sizes
5️⃣ ML Features: Apply PCA, Clustering, Deep Learning

In [1]:
import sys
import os
import warnings
from pathlib import Path

# ---------------------------------------------------------------------------
# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY
# ---------------------------------------------------------------------------
project_root = Path.cwd().parent.parent  # Adjust if your notebook is in notebooks/time_series
sys.path.append(str(project_root))
os.chdir(str(project_root))
warnings.filterwarnings("ignore")

import pandas as pd
import numpy as np
import MetaTrader5 as mt5
import vectorbt as vbt

from data.data_loader import get_data_mt5
from features.feature_engineering import add_all_ta_features

import MetaTrader5 as mt5



# Initialize MetaTrader 5 connection
if not mt5.initialize():
    print("MT5 Initialization Failed")
    mt5.shutdown()

def compute_spread_features(df1, df2):
    """
    Compute spread and statistical features for pair trading.
    """
    spread = df1["close"] - df2["close"]
    z_score = (spread - spread.rolling(window=30).mean()) / spread.rolling(window=30).std()

    features = pd.DataFrame({
        "spread": spread,
        "z_score": z_score,
        "rolling_mean": spread.rolling(window=30).mean(),
        "rolling_std": spread.rolling(window=30).std(),
    })
    
    return features.dropna()

# Example usage
df_AUDUSD = get_data_mt5("AUDUSD", n_bars=1000, timeframe=mt5.TIMEFRAME_D1)
df_NZDUSD = get_data_mt5("EURCHF", n_bars=1000, timeframe=mt5.TIMEFRAME_D1)

spread_features = compute_spread_features(df_AUDUSD, df_NZDUSD)
print(spread_features.tail())
             spread   z_score  rolling_mean  rolling_std
time                                                    
2025-02-27 -0.31186  0.274287     -0.313533     0.006098
2025-02-28 -0.31641 -0.503723     -0.313397     0.005982
2025-03-03 -0.31801 -0.762773     -0.313429     0.006005
2025-03-04 -0.31793 -0.739662     -0.313469     0.006032
2025-03-05 -0.31743 -0.648396     -0.313505     0.006053

Machine Learning for Signal Generation

In [2]:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import numpy as np


# Prepare dataset
spread_features["target"] = np.where(spread_features["z_score"].shift(-1) < 0, 1, 0)  # Mean Reversion Signal
X = spread_features.drop("target", axis=1)
y = spread_features["target"]

# Train ML Model
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# Predict signals
spread_features["signal"] = model.predict(X)

print(spread_features.tail())
             spread   z_score  rolling_mean  rolling_std  target  signal
time                                                                    
2025-02-27 -0.31186  0.274287     -0.313533     0.006098       1       0
2025-02-28 -0.31641 -0.503723     -0.313397     0.005982       1       1
2025-03-03 -0.31801 -0.762773     -0.313429     0.006005       1       1
2025-03-04 -0.31793 -0.739662     -0.313469     0.006032       1       1
2025-03-05 -0.31743 -0.648396     -0.313505     0.006053       0       0

Trade Execution Logic

In [3]:
import MetaTrader5 as mt5

def execute_trade(symbol1, symbol2, signal):
    if signal == 1:
        print(f"📈 Enter LONG {symbol1} & SHORT {symbol2}")
        mt5.order_send(symbol1, mt5.ORDER_TYPE_BUY, 0.1)
        mt5.order_send(symbol2, mt5.ORDER_TYPE_SELL, 0.1)
    elif signal == 0:
        print(f"📉 Enter SHORT {symbol1} & LONG {symbol2}")
        mt5.order_send(symbol1, mt5.ORDER_TYPE_SELL, 0.1)
        mt5.order_send(symbol2, mt5.ORDER_TYPE_BUY, 0.1)

# Execute trade for top cointegrated pair
latest_signal = spread_features["signal"].iloc[-1]
execute_trade("AUDUSD", "EURCHF", latest_signal)
📉 Enter SHORT AUDUSD & LONG EURCHF

Risk Management

In [4]:
SL_PIPS = 50
TP_PIPS = 100

def execute_trade(symbol1, symbol2, signal):
    price1 = mt5.symbol_info_tick(symbol1).ask
    price2 = mt5.symbol_info_tick(symbol2).bid

    stop_loss1 = price1 - (SL_PIPS * 0.0001)
    take_profit1 = price1 + (TP_PIPS * 0.0001)

    stop_loss2 = price2 + (SL_PIPS * 0.0001)
    take_profit2 = price2 - (TP_PIPS * 0.0001)

    if signal == 1:
        mt5.order_send(symbol1, mt5.ORDER_TYPE_BUY, 0.1, price1, sl=stop_loss1, tp=take_profit1)
        mt5.order_send(symbol2, mt5.ORDER_TYPE_SELL, 0.1, price2, sl=stop_loss2, tp=take_profit2)
    elif signal == 0:
        mt5.order_send(symbol1, mt5.ORDER_TYPE_SELL, 0.1, price1, sl=stop_loss1, tp=take_profit1)
        mt5.order_send(symbol2, mt5.ORDER_TYPE_BUY, 0.1, price2, sl=stop_loss2, tp=take_profit2)

Run Backtest on Historical Data

In [5]:
for time, row in spread_features.iterrows():
    signal = row["signal"]
    if signal != 0:  # Only execute when signal exists
        print(f"🔄 {time}: Signal {signal}")
        execute_trade("AUDUSD", "NZDUSD", signal)
🔄 2021-06-22 00:00:00: Signal 1.0
🔄 2021-06-23 00:00:00: Signal 1.0
🔄 2021-06-24 00:00:00: Signal 1.0
🔄 2021-06-25 00:00:00: Signal 1.0
🔄 2021-06-28 00:00:00: Signal 1.0
🔄 2021-06-29 00:00:00: Signal 1.0
🔄 2021-06-30 00:00:00: Signal 1.0
🔄 2021-07-01 00:00:00: Signal 1.0
🔄 2021-07-02 00:00:00: Signal 1.0
🔄 2021-07-05 00:00:00: Signal 1.0
🔄 2021-07-06 00:00:00: Signal 1.0
🔄 2021-07-07 00:00:00: Signal 1.0
🔄 2021-07-08 00:00:00: Signal 1.0
🔄 2021-07-09 00:00:00: Signal 1.0
🔄 2021-07-12 00:00:00: Signal 1.0
🔄 2021-07-13 00:00:00: Signal 1.0
🔄 2021-07-14 00:00:00: Signal 1.0
🔄 2021-07-15 00:00:00: Signal 1.0
🔄 2021-07-16 00:00:00: Signal 1.0
🔄 2021-07-19 00:00:00: Signal 1.0
🔄 2021-07-20 00:00:00: Signal 1.0
🔄 2021-07-21 00:00:00: Signal 1.0
🔄 2021-07-22 00:00:00: Signal 1.0
🔄 2021-07-23 00:00:00: Signal 1.0
🔄 2021-07-26 00:00:00: Signal 1.0
🔄 2021-07-27 00:00:00: Signal 1.0
🔄 2021-07-28 00:00:00: Signal 1.0
🔄 2021-07-29 00:00:00: Signal 1.0
🔄 2021-07-30 00:00:00: Signal 1.0
🔄 2021-08-02 00:00:00: Signal 1.0
🔄 2021-08-03 00:00:00: Signal 1.0
🔄 2021-08-04 00:00:00: Signal 1.0
🔄 2021-08-05 00:00:00: Signal 1.0
🔄 2021-08-06 00:00:00: Signal 1.0
🔄 2021-08-09 00:00:00: Signal 1.0
🔄 2021-08-10 00:00:00: Signal 1.0
🔄 2021-08-11 00:00:00: Signal 1.0
🔄 2021-08-12 00:00:00: Signal 1.0
🔄 2021-08-13 00:00:00: Signal 1.0
🔄 2021-08-16 00:00:00: Signal 1.0
🔄 2021-08-17 00:00:00: Signal 1.0
🔄 2021-08-18 00:00:00: Signal 1.0
🔄 2021-08-19 00:00:00: Signal 1.0
🔄 2021-08-20 00:00:00: Signal 1.0
🔄 2021-08-23 00:00:00: Signal 1.0
🔄 2021-08-24 00:00:00: Signal 1.0
🔄 2021-08-25 00:00:00: Signal 1.0
🔄 2021-08-26 00:00:00: Signal 1.0
🔄 2021-08-27 00:00:00: Signal 1.0
🔄 2021-08-30 00:00:00: Signal 1.0
🔄 2021-08-31 00:00:00: Signal 1.0
🔄 2021-09-01 00:00:00: Signal 1.0
🔄 2021-09-02 00:00:00: Signal 1.0
🔄 2021-09-03 00:00:00: Signal 1.0
🔄 2021-09-06 00:00:00: Signal 1.0
🔄 2021-09-07 00:00:00: Signal 1.0
🔄 2021-09-08 00:00:00: Signal 1.0
🔄 2021-09-09 00:00:00: Signal 1.0
🔄 2021-09-10 00:00:00: Signal 1.0
🔄 2021-09-13 00:00:00: Signal 1.0
🔄 2021-09-14 00:00:00: Signal 1.0
🔄 2021-09-15 00:00:00: Signal 1.0
🔄 2021-09-16 00:00:00: Signal 1.0
🔄 2021-09-17 00:00:00: Signal 1.0
🔄 2021-09-20 00:00:00: Signal 1.0
🔄 2021-09-21 00:00:00: Signal 1.0
🔄 2021-09-22 00:00:00: Signal 1.0
🔄 2021-09-23 00:00:00: Signal 1.0
🔄 2021-09-24 00:00:00: Signal 1.0
🔄 2021-09-27 00:00:00: Signal 1.0
🔄 2021-11-01 00:00:00: Signal 1.0
🔄 2021-11-02 00:00:00: Signal 1.0
🔄 2021-11-03 00:00:00: Signal 1.0
🔄 2021-11-04 00:00:00: Signal 1.0
🔄 2021-11-05 00:00:00: Signal 1.0
🔄 2021-11-08 00:00:00: Signal 1.0
🔄 2021-11-09 00:00:00: Signal 1.0
🔄 2021-11-10 00:00:00: Signal 1.0
🔄 2021-11-11 00:00:00: Signal 1.0
🔄 2021-11-12 00:00:00: Signal 1.0
🔄 2021-11-15 00:00:00: Signal 1.0
🔄 2021-11-16 00:00:00: Signal 1.0
🔄 2021-11-17 00:00:00: Signal 1.0
🔄 2021-11-18 00:00:00: Signal 1.0
🔄 2021-11-19 00:00:00: Signal 1.0
🔄 2021-11-22 00:00:00: Signal 1.0
🔄 2021-11-30 00:00:00: Signal 1.0
🔄 2021-12-01 00:00:00: Signal 1.0
🔄 2021-12-02 00:00:00: Signal 1.0
🔄 2022-02-23 00:00:00: Signal 1.0
🔄 2022-03-07 00:00:00: Signal 1.0
🔄 2022-03-08 00:00:00: Signal 1.0
🔄 2022-03-10 00:00:00: Signal 1.0
🔄 2022-03-11 00:00:00: Signal 1.0
🔄 2022-03-14 00:00:00: Signal 1.0
🔄 2022-03-15 00:00:00: Signal 1.0
🔄 2022-04-22 00:00:00: Signal 1.0
🔄 2022-04-25 00:00:00: Signal 1.0
🔄 2022-05-11 00:00:00: Signal 1.0
🔄 2022-05-24 00:00:00: Signal 1.0
🔄 2022-05-25 00:00:00: Signal 1.0
🔄 2022-05-26 00:00:00: Signal 1.0
🔄 2022-06-13 00:00:00: Signal 1.0
🔄 2022-06-16 00:00:00: Signal 1.0
🔄 2022-06-17 00:00:00: Signal 1.0
🔄 2022-06-20 00:00:00: Signal 1.0
🔄 2022-06-21 00:00:00: Signal 1.0
🔄 2022-06-22 00:00:00: Signal 1.0
🔄 2022-06-23 00:00:00: Signal 1.0
🔄 2022-06-24 00:00:00: Signal 1.0
🔄 2022-06-28 00:00:00: Signal 1.0
🔄 2022-06-29 00:00:00: Signal 1.0
🔄 2022-06-30 00:00:00: Signal 1.0
🔄 2022-07-01 00:00:00: Signal 1.0
🔄 2022-07-04 00:00:00: Signal 1.0
🔄 2022-07-05 00:00:00: Signal 1.0
🔄 2022-07-06 00:00:00: Signal 1.0
🔄 2022-07-07 00:00:00: Signal 1.0
🔄 2022-07-08 00:00:00: Signal 1.0
🔄 2022-07-11 00:00:00: Signal 1.0
🔄 2022-07-12 00:00:00: Signal 1.0
🔄 2022-07-13 00:00:00: Signal 1.0
🔄 2022-07-14 00:00:00: Signal 1.0
🔄 2022-08-16 00:00:00: Signal 1.0
🔄 2022-08-17 00:00:00: Signal 1.0
🔄 2022-08-18 00:00:00: Signal 1.0
🔄 2022-09-06 00:00:00: Signal 1.0
🔄 2022-10-05 00:00:00: Signal 1.0
🔄 2022-10-06 00:00:00: Signal 1.0
🔄 2022-10-07 00:00:00: Signal 1.0
🔄 2022-10-10 00:00:00: Signal 1.0
🔄 2022-10-11 00:00:00: Signal 1.0
🔄 2022-10-12 00:00:00: Signal 1.0
🔄 2022-10-13 00:00:00: Signal 1.0
🔄 2022-10-14 00:00:00: Signal 1.0
🔄 2022-10-17 00:00:00: Signal 1.0
🔄 2022-10-18 00:00:00: Signal 1.0
🔄 2022-10-19 00:00:00: Signal 1.0
🔄 2022-10-20 00:00:00: Signal 1.0
🔄 2022-10-21 00:00:00: Signal 1.0
🔄 2022-10-24 00:00:00: Signal 1.0
🔄 2022-10-25 00:00:00: Signal 1.0
🔄 2022-10-26 00:00:00: Signal 1.0
🔄 2022-10-27 00:00:00: Signal 1.0
🔄 2022-10-28 00:00:00: Signal 1.0
🔄 2022-10-31 00:00:00: Signal 1.0
🔄 2022-11-01 00:00:00: Signal 1.0
🔄 2022-11-02 00:00:00: Signal 1.0
🔄 2022-11-03 00:00:00: Signal 1.0
🔄 2022-11-04 00:00:00: Signal 1.0
🔄 2022-11-07 00:00:00: Signal 1.0
🔄 2022-11-08 00:00:00: Signal 1.0
🔄 2022-11-09 00:00:00: Signal 1.0
🔄 2022-11-10 00:00:00: Signal 1.0
🔄 2022-11-11 00:00:00: Signal 1.0
🔄 2022-11-14 00:00:00: Signal 1.0
🔄 2022-11-15 00:00:00: Signal 1.0
🔄 2022-11-16 00:00:00: Signal 1.0
🔄 2022-11-17 00:00:00: Signal 1.0
🔄 2022-11-18 00:00:00: Signal 1.0
🔄 2022-11-21 00:00:00: Signal 1.0
🔄 2022-11-22 00:00:00: Signal 1.0
🔄 2022-11-23 00:00:00: Signal 1.0
🔄 2022-11-24 00:00:00: Signal 1.0
🔄 2022-11-25 00:00:00: Signal 1.0
🔄 2022-11-28 00:00:00: Signal 1.0
🔄 2022-11-29 00:00:00: Signal 1.0
🔄 2022-11-30 00:00:00: Signal 1.0
🔄 2022-12-01 00:00:00: Signal 1.0
🔄 2022-12-02 00:00:00: Signal 1.0
🔄 2022-12-05 00:00:00: Signal 1.0
🔄 2022-12-06 00:00:00: Signal 1.0
🔄 2022-12-07 00:00:00: Signal 1.0
🔄 2022-12-08 00:00:00: Signal 1.0
🔄 2022-12-09 00:00:00: Signal 1.0
🔄 2022-12-12 00:00:00: Signal 1.0
🔄 2022-12-13 00:00:00: Signal 1.0
🔄 2022-12-14 00:00:00: Signal 1.0
🔄 2022-12-15 00:00:00: Signal 1.0
🔄 2022-12-16 00:00:00: Signal 1.0
🔄 2022-12-19 00:00:00: Signal 1.0
🔄 2022-12-20 00:00:00: Signal 1.0
🔄 2022-12-21 00:00:00: Signal 1.0
🔄 2022-12-22 00:00:00: Signal 1.0
🔄 2022-12-27 00:00:00: Signal 1.0
🔄 2022-12-29 00:00:00: Signal 1.0
🔄 2023-02-21 00:00:00: Signal 1.0
🔄 2023-02-22 00:00:00: Signal 1.0
🔄 2023-02-23 00:00:00: Signal 1.0
🔄 2023-02-24 00:00:00: Signal 1.0
🔄 2023-02-27 00:00:00: Signal 1.0
🔄 2023-02-28 00:00:00: Signal 1.0
🔄 2023-03-01 00:00:00: Signal 1.0
🔄 2023-03-02 00:00:00: Signal 1.0
🔄 2023-03-03 00:00:00: Signal 1.0
🔄 2023-03-06 00:00:00: Signal 1.0
🔄 2023-03-07 00:00:00: Signal 1.0
🔄 2023-03-08 00:00:00: Signal 1.0
🔄 2023-03-09 00:00:00: Signal 1.0
🔄 2023-03-10 00:00:00: Signal 1.0
🔄 2023-03-13 00:00:00: Signal 1.0
🔄 2023-03-14 00:00:00: Signal 1.0
🔄 2023-03-15 00:00:00: Signal 1.0
🔄 2023-03-16 00:00:00: Signal 1.0
🔄 2023-03-17 00:00:00: Signal 1.0
🔄 2023-03-20 00:00:00: Signal 1.0
🔄 2023-03-21 00:00:00: Signal 1.0
🔄 2023-03-22 00:00:00: Signal 1.0
🔄 2023-03-23 00:00:00: Signal 1.0
🔄 2023-03-24 00:00:00: Signal 1.0
🔄 2023-03-27 00:00:00: Signal 1.0
🔄 2023-03-28 00:00:00: Signal 1.0
🔄 2023-03-29 00:00:00: Signal 1.0
🔄 2023-03-30 00:00:00: Signal 1.0
🔄 2023-03-31 00:00:00: Signal 1.0
🔄 2023-04-03 00:00:00: Signal 1.0
🔄 2023-04-04 00:00:00: Signal 1.0
🔄 2023-04-05 00:00:00: Signal 1.0
🔄 2023-04-06 00:00:00: Signal 1.0
🔄 2023-04-07 00:00:00: Signal 1.0
🔄 2023-04-28 00:00:00: Signal 1.0
🔄 2023-05-01 00:00:00: Signal 1.0
🔄 2023-05-02 00:00:00: Signal 1.0
🔄 2023-05-03 00:00:00: Signal 1.0
🔄 2023-05-04 00:00:00: Signal 1.0
🔄 2023-05-05 00:00:00: Signal 1.0
🔄 2023-05-08 00:00:00: Signal 1.0
🔄 2023-05-09 00:00:00: Signal 1.0
🔄 2023-05-10 00:00:00: Signal 1.0
🔄 2023-05-11 00:00:00: Signal 1.0
🔄 2023-05-12 00:00:00: Signal 1.0
🔄 2023-05-15 00:00:00: Signal 1.0
🔄 2023-05-16 00:00:00: Signal 1.0
🔄 2023-05-17 00:00:00: Signal 1.0
🔄 2023-05-18 00:00:00: Signal 1.0
🔄 2023-05-19 00:00:00: Signal 1.0
🔄 2023-05-22 00:00:00: Signal 1.0
🔄 2023-05-23 00:00:00: Signal 1.0
🔄 2023-05-24 00:00:00: Signal 1.0
🔄 2023-06-23 00:00:00: Signal 1.0
🔄 2023-06-26 00:00:00: Signal 1.0
🔄 2023-06-27 00:00:00: Signal 1.0
🔄 2023-06-28 00:00:00: Signal 1.0
🔄 2023-06-29 00:00:00: Signal 1.0
🔄 2023-06-30 00:00:00: Signal 1.0
🔄 2023-07-03 00:00:00: Signal 1.0
🔄 2023-07-04 00:00:00: Signal 1.0
🔄 2023-07-05 00:00:00: Signal 1.0
🔄 2023-07-06 00:00:00: Signal 1.0
🔄 2023-07-07 00:00:00: Signal 1.0
🔄 2023-07-10 00:00:00: Signal 1.0
🔄 2023-07-11 00:00:00: Signal 1.0
🔄 2023-07-12 00:00:00: Signal 1.0
🔄 2023-07-13 00:00:00: Signal 1.0
🔄 2023-07-14 00:00:00: Signal 1.0
🔄 2023-07-17 00:00:00: Signal 1.0
🔄 2023-07-18 00:00:00: Signal 1.0
🔄 2023-07-19 00:00:00: Signal 1.0
🔄 2023-07-21 00:00:00: Signal 1.0
🔄 2023-07-24 00:00:00: Signal 1.0
🔄 2023-07-26 00:00:00: Signal 1.0
🔄 2023-07-27 00:00:00: Signal 1.0
🔄 2023-07-28 00:00:00: Signal 1.0
🔄 2023-07-31 00:00:00: Signal 1.0
🔄 2023-08-01 00:00:00: Signal 1.0
🔄 2023-08-02 00:00:00: Signal 1.0
🔄 2023-08-03 00:00:00: Signal 1.0
🔄 2023-08-04 00:00:00: Signal 1.0
🔄 2023-08-07 00:00:00: Signal 1.0
🔄 2023-08-08 00:00:00: Signal 1.0
🔄 2023-08-09 00:00:00: Signal 1.0
🔄 2023-08-16 00:00:00: Signal 1.0
🔄 2023-08-17 00:00:00: Signal 1.0
🔄 2023-08-18 00:00:00: Signal 1.0
🔄 2023-08-21 00:00:00: Signal 1.0
🔄 2023-08-23 00:00:00: Signal 1.0
🔄 2023-08-24 00:00:00: Signal 1.0
🔄 2023-09-07 00:00:00: Signal 1.0
🔄 2023-09-13 00:00:00: Signal 1.0
🔄 2023-09-20 00:00:00: Signal 1.0
🔄 2023-09-21 00:00:00: Signal 1.0
🔄 2023-09-22 00:00:00: Signal 1.0
🔄 2023-09-25 00:00:00: Signal 1.0
🔄 2023-09-26 00:00:00: Signal 1.0
🔄 2023-09-27 00:00:00: Signal 1.0
🔄 2023-09-28 00:00:00: Signal 1.0
🔄 2023-09-29 00:00:00: Signal 1.0
🔄 2023-10-02 00:00:00: Signal 1.0
🔄 2023-10-03 00:00:00: Signal 1.0
🔄 2023-10-04 00:00:00: Signal 1.0
🔄 2023-10-05 00:00:00: Signal 1.0
🔄 2023-10-06 00:00:00: Signal 1.0
🔄 2023-10-09 00:00:00: Signal 1.0
🔄 2023-10-10 00:00:00: Signal 1.0
🔄 2023-10-11 00:00:00: Signal 1.0
🔄 2023-10-12 00:00:00: Signal 1.0
🔄 2023-10-13 00:00:00: Signal 1.0
🔄 2023-11-10 00:00:00: Signal 1.0
🔄 2023-11-23 00:00:00: Signal 1.0
🔄 2023-11-28 00:00:00: Signal 1.0
🔄 2023-11-29 00:00:00: Signal 1.0
🔄 2023-11-30 00:00:00: Signal 1.0
🔄 2023-12-01 00:00:00: Signal 1.0
🔄 2023-12-04 00:00:00: Signal 1.0
🔄 2023-12-05 00:00:00: Signal 1.0
🔄 2023-12-06 00:00:00: Signal 1.0
🔄 2023-12-07 00:00:00: Signal 1.0
🔄 2023-12-08 00:00:00: Signal 1.0
🔄 2023-12-11 00:00:00: Signal 1.0
🔄 2024-01-03 00:00:00: Signal 1.0
🔄 2024-01-04 00:00:00: Signal 1.0
🔄 2024-01-05 00:00:00: Signal 1.0
🔄 2024-01-08 00:00:00: Signal 1.0
🔄 2024-01-09 00:00:00: Signal 1.0
🔄 2024-01-10 00:00:00: Signal 1.0
🔄 2024-01-11 00:00:00: Signal 1.0
🔄 2024-01-12 00:00:00: Signal 1.0
🔄 2024-01-15 00:00:00: Signal 1.0
🔄 2024-01-16 00:00:00: Signal 1.0
🔄 2024-01-17 00:00:00: Signal 1.0
🔄 2024-01-23 00:00:00: Signal 1.0
🔄 2024-01-24 00:00:00: Signal 1.0
🔄 2024-01-26 00:00:00: Signal 1.0
🔄 2024-01-29 00:00:00: Signal 1.0
🔄 2024-01-30 00:00:00: Signal 1.0
🔄 2024-01-31 00:00:00: Signal 1.0
🔄 2024-02-01 00:00:00: Signal 1.0
🔄 2024-02-02 00:00:00: Signal 1.0
🔄 2024-02-05 00:00:00: Signal 1.0
🔄 2024-02-06 00:00:00: Signal 1.0
🔄 2024-02-07 00:00:00: Signal 1.0
🔄 2024-02-08 00:00:00: Signal 1.0
🔄 2024-02-09 00:00:00: Signal 1.0
🔄 2024-02-12 00:00:00: Signal 1.0
🔄 2024-02-13 00:00:00: Signal 1.0
🔄 2024-02-14 00:00:00: Signal 1.0
🔄 2024-02-15 00:00:00: Signal 1.0
🔄 2024-02-16 00:00:00: Signal 1.0
🔄 2024-02-19 00:00:00: Signal 1.0
🔄 2024-02-20 00:00:00: Signal 1.0
🔄 2024-02-21 00:00:00: Signal 1.0
🔄 2024-02-22 00:00:00: Signal 1.0
🔄 2024-02-23 00:00:00: Signal 1.0
🔄 2024-02-26 00:00:00: Signal 1.0
🔄 2024-02-27 00:00:00: Signal 1.0
🔄 2024-02-28 00:00:00: Signal 1.0
🔄 2024-02-29 00:00:00: Signal 1.0
🔄 2024-03-01 00:00:00: Signal 1.0
🔄 2024-04-18 00:00:00: Signal 1.0
🔄 2024-05-15 00:00:00: Signal 1.0
🔄 2024-05-16 00:00:00: Signal 1.0
🔄 2024-05-17 00:00:00: Signal 1.0
🔄 2024-05-21 00:00:00: Signal 1.0
🔄 2024-05-22 00:00:00: Signal 1.0
🔄 2024-05-23 00:00:00: Signal 1.0
🔄 2024-05-24 00:00:00: Signal 1.0
🔄 2024-05-27 00:00:00: Signal 1.0
🔄 2024-05-28 00:00:00: Signal 1.0
🔄 2024-05-29 00:00:00: Signal 1.0
🔄 2024-05-30 00:00:00: Signal 1.0
🔄 2024-05-31 00:00:00: Signal 1.0
🔄 2024-06-03 00:00:00: Signal 1.0
🔄 2024-06-04 00:00:00: Signal 1.0
🔄 2024-06-05 00:00:00: Signal 1.0
🔄 2024-06-06 00:00:00: Signal 1.0
🔄 2024-06-07 00:00:00: Signal 1.0
🔄 2024-06-10 00:00:00: Signal 1.0
🔄 2024-06-11 00:00:00: Signal 1.0
🔄 2024-06-12 00:00:00: Signal 1.0
🔄 2024-06-13 00:00:00: Signal 1.0
🔄 2024-06-14 00:00:00: Signal 1.0
🔄 2024-06-17 00:00:00: Signal 1.0
🔄 2024-06-18 00:00:00: Signal 1.0
🔄 2024-07-30 00:00:00: Signal 1.0
🔄 2024-07-31 00:00:00: Signal 1.0
🔄 2024-08-01 00:00:00: Signal 1.0
🔄 2024-08-02 00:00:00: Signal 1.0
🔄 2024-08-05 00:00:00: Signal 1.0
🔄 2024-08-06 00:00:00: Signal 1.0
🔄 2024-08-07 00:00:00: Signal 1.0
🔄 2024-08-08 00:00:00: Signal 1.0
🔄 2024-08-09 00:00:00: Signal 1.0
🔄 2024-08-12 00:00:00: Signal 1.0
🔄 2024-08-13 00:00:00: Signal 1.0
🔄 2024-08-15 00:00:00: Signal 1.0
🔄 2024-08-16 00:00:00: Signal 1.0
🔄 2024-08-19 00:00:00: Signal 1.0
🔄 2024-08-20 00:00:00: Signal 1.0
🔄 2024-08-21 00:00:00: Signal 1.0
🔄 2024-08-22 00:00:00: Signal 1.0
🔄 2024-08-23 00:00:00: Signal 1.0
🔄 2024-08-26 00:00:00: Signal 1.0
🔄 2024-08-27 00:00:00: Signal 1.0
🔄 2024-08-28 00:00:00: Signal 1.0
🔄 2024-08-29 00:00:00: Signal 1.0
🔄 2024-08-30 00:00:00: Signal 1.0
🔄 2024-09-02 00:00:00: Signal 1.0
🔄 2024-09-03 00:00:00: Signal 1.0
🔄 2024-09-04 00:00:00: Signal 1.0
🔄 2024-09-05 00:00:00: Signal 1.0
🔄 2024-09-06 00:00:00: Signal 1.0
🔄 2024-09-09 00:00:00: Signal 1.0
🔄 2024-09-10 00:00:00: Signal 1.0
🔄 2024-09-11 00:00:00: Signal 1.0
🔄 2024-09-12 00:00:00: Signal 1.0
🔄 2024-09-13 00:00:00: Signal 1.0
🔄 2024-09-16 00:00:00: Signal 1.0
🔄 2024-09-17 00:00:00: Signal 1.0
🔄 2024-09-23 00:00:00: Signal 1.0
🔄 2024-10-28 00:00:00: Signal 1.0
🔄 2024-10-29 00:00:00: Signal 1.0
🔄 2024-10-30 00:00:00: Signal 1.0
🔄 2024-10-31 00:00:00: Signal 1.0
🔄 2024-11-01 00:00:00: Signal 1.0
🔄 2024-11-07 00:00:00: Signal 1.0
🔄 2024-11-08 00:00:00: Signal 1.0
🔄 2024-11-11 00:00:00: Signal 1.0
🔄 2024-11-12 00:00:00: Signal 1.0
🔄 2024-11-13 00:00:00: Signal 1.0
🔄 2024-11-14 00:00:00: Signal 1.0
🔄 2024-11-15 00:00:00: Signal 1.0
🔄 2024-11-18 00:00:00: Signal 1.0
🔄 2024-11-19 00:00:00: Signal 1.0
🔄 2024-11-26 00:00:00: Signal 1.0
🔄 2024-11-27 00:00:00: Signal 1.0
🔄 2024-11-28 00:00:00: Signal 1.0
🔄 2024-11-29 00:00:00: Signal 1.0
🔄 2024-12-02 00:00:00: Signal 1.0
🔄 2024-12-03 00:00:00: Signal 1.0
🔄 2024-12-04 00:00:00: Signal 1.0
🔄 2024-12-05 00:00:00: Signal 1.0
🔄 2024-12-06 00:00:00: Signal 1.0
🔄 2024-12-09 00:00:00: Signal 1.0
🔄 2024-12-10 00:00:00: Signal 1.0
🔄 2024-12-11 00:00:00: Signal 1.0
🔄 2024-12-12 00:00:00: Signal 1.0
🔄 2024-12-13 00:00:00: Signal 1.0
🔄 2024-12-16 00:00:00: Signal 1.0
🔄 2024-12-17 00:00:00: Signal 1.0
🔄 2024-12-19 00:00:00: Signal 1.0
🔄 2024-12-20 00:00:00: Signal 1.0
🔄 2024-12-23 00:00:00: Signal 1.0
🔄 2024-12-24 00:00:00: Signal 1.0
🔄 2024-12-26 00:00:00: Signal 1.0
🔄 2024-12-27 00:00:00: Signal 1.0
🔄 2024-12-30 00:00:00: Signal 1.0
🔄 2025-01-09 00:00:00: Signal 1.0
🔄 2025-01-13 00:00:00: Signal 1.0
🔄 2025-01-20 00:00:00: Signal 1.0
🔄 2025-01-27 00:00:00: Signal 1.0
🔄 2025-01-28 00:00:00: Signal 1.0
🔄 2025-01-29 00:00:00: Signal 1.0
🔄 2025-01-30 00:00:00: Signal 1.0
🔄 2025-02-26 00:00:00: Signal 1.0
🔄 2025-02-27 00:00:00: Signal 1.0
🔄 2025-02-28 00:00:00: Signal 1.0
🔄 2025-03-03 00:00:00: Signal 1.0
🔄 2025-03-04 00:00:00: Signal 1.0

Full Pair Trading Backtest with MT5 + VectorBT

In [1]:
import sys
import os
import warnings
from pathlib import Path
import numpy as np
import pandas as pd
import MetaTrader5 as mt5
import vectorbt as vbt
import statsmodels.api as sm
from statsmodels.tsa.stattools import coint
from sklearn.preprocessing import StandardScaler


import sys
import os
import warnings
from pathlib import Path

# ---------------------------------------------------------------------------
# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY
# ---------------------------------------------------------------------------
project_root = Path.cwd().parent.parent  # Adjust if your notebook is in notebooks/time_series
sys.path.append(str(project_root))
os.chdir(str(project_root))
warnings.filterwarnings("ignore")

# Disable warnings
warnings.filterwarnings("ignore")

# ✅ Load our modules
from data.data_loader import get_data_mt5

# ✅ Initialize MetaTrader 5 connection
if not mt5.initialize():
    print("Failed to initialize MT5")
else:
    print("✅ Connected to MT5")

# ✅ Select Trading Pair
pair1, pair2 = "AUDUSD", "NZDUSD"
n_bars = 2000
timeframe = mt5.TIMEFRAME_D1  # Use 4-hour data

# ✅ Load historical price data
df1 = get_data_mt5(pair1, n_bars, timeframe)
df2 = get_data_mt5(pair2, n_bars, timeframe)

# ✅ Align timestamps
df = pd.DataFrame({
    "time": df1.index,
    f"{pair1}": df1["close"],
    f"{pair2}": df2["close"]
}).dropna().set_index("time")

print(f"\n✅ Data Loaded ({len(df)} bars)")
print(df.head())

# ✅ Check for Cointegration
coint_p_value = coint(df[pair1], df[pair2])[1]
if coint_p_value > 0.05:
    print(f"⚠️ {pair1} & {pair2} are NOT cointegrated (p={coint_p_value:.4f})")
else:
    print(f"{pair1} & {pair2} are cointegrated (p={coint_p_value:.4f})")

✅ Connected to MT5

✅ Data Loaded (2000 bars)
             AUDUSD   NZDUSD
time                        
2017-06-19  0.75973  0.72291
2017-06-20  0.75798  0.72397
2017-06-21  0.75521  0.72160
2017-06-22  0.75405  0.72642
2017-06-23  0.75691  0.72800
✅ AUDUSD & NZDUSD are cointegrated (p=0.0182)
In [2]:
# ✅ Compute Spread (OLS Regression)
X = sm.add_constant(df[pair2])  # Add intercept
model = sm.OLS(df[pair1], X).fit()
hedge_ratio = model.params[pair2]
df["spread"] = df[pair1] - hedge_ratio * df[pair2]

# ✅ Compute Z-Score for Trading
lookback = 100  # Moving window for Z-score
df["spread_mean"] = df["spread"].rolling(lookback).mean()
df["spread_std"] = df["spread"].rolling(lookback).std()
df["z_score"] = (df["spread"] - df["spread_mean"]) / df["spread_std"]

print("\n✅ Spread & Z-score Calculated")
print(df.tail())

✅ Spread & Z-score Calculated
             AUDUSD   NZDUSD    spread  spread_mean  spread_std   z_score
time                                                                     
2025-02-27  0.62356  0.56311  0.076056     0.077058    0.002399 -0.417721
2025-02-28  0.62039  0.55958  0.076318     0.076999    0.002342 -0.290614
2025-03-03  0.62247  0.56164  0.076396     0.076947    0.002296 -0.240165
2025-03-04  0.62707  0.56642  0.076348     0.076899    0.002256 -0.243992
2025-03-05  0.62652  0.56594  0.076265     0.076862    0.002236 -0.267003
In [3]:
# ✅ Define Trading Thresholds
entry_threshold = 1.5  # Enter when Z-score > 1.5 or < -1.5
exit_threshold = 0.5   # Exit when Z-score returns to 0.5

# ✅ Generate Trading Signals
df["long_signal"] = df["z_score"] < -entry_threshold
df["short_signal"] = df["z_score"] > entry_threshold
df["exit_signal"] = df["z_score"].abs() < exit_threshold

# ✅ Convert to VectorBT Signals
entries = df["long_signal"].astype(int) - df["short_signal"].astype(int)  # 1 for long, -1 for short
exits = df["exit_signal"]

print("\n✅ Trading Signals Generated")
print(df[["spread", "z_score", "long_signal", "short_signal", "exit_signal"]].tail())

✅ Trading Signals Generated
              spread   z_score  long_signal  short_signal  exit_signal
time                                                                  
2025-02-27  0.076056 -0.417721        False         False         True
2025-02-28  0.076318 -0.290614        False         False         True
2025-03-03  0.076396 -0.240165        False         False         True
2025-03-04  0.076348 -0.243992        False         False         True
2025-03-05  0.076265 -0.267003        False         False         True
In [4]:
# ✅ Define Initial Cash and Trading Fees
init_cash = 10000
fees = 0.0002  # 0.02% transaction cost

# ✅ Run Portfolio Backtest
portfolio = vbt.Portfolio.from_signals(
    close=df[pair1],  
    entries=entries > 0,
    exits=exits,
    short_entries=entries < 0,
    short_exits=exits,
    size=1,  
    size_type="percent",  
    init_cash=init_cash,
    fees=fees,
    freq="4H"
)

# ✅ Print Final Results
print("\n✅ Backtest Completed")
print(portfolio.stats())

# ✅ Plot Performance
portfolio.plot().show()

✅ Backtest Completed
Start                               2017-06-19 00:00:00
End                                 2025-03-05 00:00:00
Period                                333 days 08:00:00
Start Value                                     10000.0
End Value                                  12095.674086
Total Return [%]                              20.956741
Benchmark Return [%]                         -17.533861
Max Gross Exposure [%]                            100.0
Total Fees Paid                              107.953045
Max Drawdown [%]                              16.732506
Max Drawdown Duration                 118 days 04:00:00
Total Trades                                         27
Total Closed Trades                                  27
Total Open Trades                                     0
Open Trade PnL                                      0.0
Win Rate [%]                                  51.851852
Best Trade [%]                                  9.07005
Worst Trade [%]                               -9.965733
Avg Winning Trade [%]                          3.459788
Avg Losing Trade [%]                          -2.101339
Avg Winning Trade Duration    4 days 00:17:08.571428571
Avg Losing Trade Duration     7 days 13:50:46.153846153
Profit Factor                                  1.775186
Expectancy                                    77.617559
Sharpe Ratio                                   1.092797
Calmar Ratio                                   1.384306
Omega Ratio                                    1.096542
Sortino Ratio                                  1.630103
dtype: object
[Data output - unsupported data type map[string]interface {} for mime type application/vnd.plotly.v1+json]
In [11]:
# ✅ Extract Performance Metrics
total_return = portfolio.total_return()
sharpe_ratio = portfolio.sharpe_ratio()
win_rate = portfolio.win_rate()

print("\n🔹 **Final Performance Metrics:**")
print(f"🔹 Total Return: {total_return:.2%}")
print(f"🔹 Sharpe Ratio: {sharpe_ratio:.2f}")
print(f"🔹 Win Rate: {win_rate:.2%}")

# ✅ Display Trade Log
print("\n🔹 **Trade Log:**")
print(portfolio.orders.records_readable.tail())

# ✅ Plot Equity Curve
portfolio.plot().show()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[11], line 4
      2 total_return = portfolio.total_return()
      3 sharpe_ratio = portfolio.sharpe_ratio()
----> 4 win_rate = portfolio.win_rate()
      6 print("\n🔹 **Final Performance Metrics:**")
      7 print(f"🔹 Total Return: {total_return:.2%}")

AttributeError: 'Portfolio' object has no attribute 'win_rate'

Define Trade Execution Function

In [ ]:
import MetaTrader5 as mt5

def execute_trade(pair1, pair2, signal, lot_size=0.1):
    """
    Execute trades in MT5 for a pair trading strategy.
    
    - pair1: First currency pair (e.g., "AUDUSD")
    - pair2: Second currency pair (e.g., "NZDUSD")
    - signal: 1 for LONG pair1 & SHORT pair2, -1 for SHORT pair1 & LONG pair2, 0 to close
    - lot_size: Position size in lots
    """
    if not mt5.initialize():
        print("MT5 Initialization Failed")
        return
    
    # Define order types
    if signal == 1:
        print(f"📈 Entering LONG {pair1} & SHORT {pair2}")
        order1 = mt5.ORDER_TYPE_BUY
        order2 = mt5.ORDER_TYPE_SELL
    elif signal == -1:
        print(f"📉 Entering SHORT {pair1} & LONG {pair2}")
        order1 = mt5.ORDER_TYPE_SELL
        order2 = mt5.ORDER_TYPE_BUY
    else:
        print(f"🔄 Closing all positions for {pair1} & {pair2}")
        close_all_positions(pair1, pair2)
        return

    # Place trades
    request1 = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": pair1,
        "volume": lot_size,
        "type": order1,
        "price": mt5.symbol_info_tick(pair1).ask if order1 == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(pair1).bid,
        "deviation": 10,
        "magic": 123456,
        "comment": "Pair Trading",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }
    
    request2 = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": pair2,
        "volume": lot_size,
        "type": order2,
        "price": mt5.symbol_info_tick(pair2).ask if order2 == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(pair2).bid,
        "deviation": 10,
        "magic": 123456,
        "comment": "Pair Trading",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }

    # Send orders
    result1 = mt5.order_send(request1)
    result2 = mt5.order_send(request2)

    if result1.retcode == mt5.TRADE_RETCODE_DONE and result2.retcode == mt5.TRADE_RETCODE_DONE:
        print(f"✅ Trade executed: {pair1} {order1}, {pair2} {order2}")
    else:
        print(f"❌ Trade failed: {result1.comment}, {result2.comment}")

    mt5.shutdown()
    
def close_all_positions(pair1, pair2):
    """ Closes all open positions for a given pair. """
    open_positions = mt5.positions_get()
    if open_positions is None:
        print("No open positions found")
        return

    for pos in open_positions:
        if pos.symbol in [pair1, pair2]:
            close_request = {
                "action": mt5.TRADE_ACTION_DEAL,
                "symbol": pos.symbol,
                "volume": pos.volume,
                "type": mt5.ORDER_TYPE_SELL if pos.type == mt5.ORDER_TYPE_BUY else mt5.ORDER_TYPE_BUY,
                "position": pos.ticket,
                "price": mt5.symbol_info_tick(pos.symbol).bid if pos.type == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(pos.symbol).ask,
                "deviation": 10,
                "magic": 123456,
                "comment": "Closing Pair Trading",
                "type_time": mt5.ORDER_TIME_GTC,
                "type_filling": mt5.ORDER_FILLING_IOC,
            }
            result = mt5.order_send(close_request)
            if result.retcode == mt5.TRADE_RETCODE_DONE:
                print(f"✅ Closed {pos.symbol} position")
            else:
                print(f"❌ Failed to close {pos.symbol} position: {result.comment}")

Scan two Symbols for Cointegration

Data Loading

In [1]:
import sys
import os
import warnings
from pathlib import Path

# ---------------------------------------------------------------------------
# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY
# ---------------------------------------------------------------------------
project_root = Path.cwd().parent.parent  # Adjust if your notebook is in notebooks/time_series
sys.path.append(str(project_root))
os.chdir(str(project_root))
warnings.filterwarnings("ignore")

import pandas as pd
import numpy as np
import MetaTrader5 as mt5
import vectorbt as vbt

from data.data_loader import get_data_mt5
from features.feature_engineering import add_all_ta_features

import MetaTrader5 as mt5



# Initialize MetaTrader 5 connection
if not mt5.initialize():
    print("MT5 Initialization Failed")
    mt5.shutdown()

# Define the symbols you want to load
tickers = ["AAPL.NAS", "MSFT.NAS"]

# Define parameters for data retrieval
n_bars = 1000
timeframe = mt5.TIMEFRAME_D1  # Daily timeframe

# Dictionary to store data
symbols_data = {}

# Fetch and store data for each symbol
for ticker in tickers:
    try:
        df = get_data_mt5(ticker, n_bars, timeframe)  # Fetch data
        symbols_data[ticker] = df  # Store in dictionary
        print(f"✅ Successfully loaded data for {ticker}")
    except ValueError as e:
        print(f"⚠️ Error loading {ticker}: {e}")

# Print sample data
for symbol, df in symbols_data.items():
    print(f"\n📊 Data for {symbol}:\n{df.head()}")



✅ Successfully loaded data for AAPL.NAS
✅ Successfully loaded data for MSFT.NAS

📊 Data for AAPL.NAS:
              open    high     low   close  tick_volume  spread  real_volume
time                                                                        
2021-03-11  122.76  123.20  121.25  121.97        62184      10            0
2021-03-12  119.90  121.16  119.15  120.77        61027      10            0
2021-03-15  120.88  123.90  120.74  123.87        52819      10            0
2021-03-16  126.01  127.21  124.71  125.65        68656       0            0
2021-03-17  123.51  125.85  122.33  124.46       102649       0            0

📊 Data for MSFT.NAS:
              open    high     low   close  tick_volume  spread  real_volume
time                                                                        
2021-03-11  236.13  239.17  234.52  237.24       169798       1            0
2021-03-12  234.37  235.72  233.22  235.32       197211       1            0
2021-03-15  233.62  234.47  231.81  234.36       151859       1            0
2021-03-16  236.24  240.05  236.06  238.26       163664       0            0
2021-03-17  235.24  238.54  233.22  236.75       293709       0            0

Feature Engineering

In [2]:
from ta.volatility import BollingerBands
from ta.momentum import RSIIndicator
from ta.trend import MACD

# Function to add features
def add_features(df):
    df["returns"] = df["close"].pct_change()
    
    # Bollinger Bands
    bb = BollingerBands(df["close"])
    df["bb_upper"] = bb.bollinger_hband()
    df["bb_lower"] = bb.bollinger_lband()

    # RSI
    rsi = RSIIndicator(df["close"])
    df["rsi"] = rsi.rsi()

    # MACD
    macd = MACD(df["close"])
    df["macd"] = macd.macd()
    
    return df.dropna()

# Apply to both stocks
symbols_data = {ticker: add_features(df) for ticker, df in symbols_data.items()}

# Print sample after feature addition
for symbol, df in symbols_data.items():
    print(f"\n📊 Features for {symbol}:\n", df.head())
📊 Features for AAPL.NAS:
                open     high      low    close  tick_volume  spread  \
time                                                                  
2021-04-16  133.881  134.565  133.275  134.086        36110      10   
2021-04-19  134.446  135.456  133.701  134.460        37393      10   
2021-04-20  134.755  135.515  131.798  133.118        37088      10   
2021-04-21  132.078  133.722  131.319  133.520        35153      10   
2021-04-22  132.814  134.136  131.390  132.057        37384      10   

            real_volume   returns    bb_upper    bb_lower        rsi      macd  
time                                                                            
2021-04-16            0 -0.002730  136.783601  115.534699  68.157682  3.139965  
2021-04-19            0  0.002789  137.720914  116.012386  68.747924  3.249395  
2021-04-20            0 -0.009981  138.412049  116.286651  64.152705  3.191046  
2021-04-21            0  0.003020  139.043257  116.749343  64.909357  3.141035  
2021-04-22            0 -0.010957  139.207604  117.751596  59.949932  2.949350  

📊 Features for MSFT.NAS:
               open    high     low   close  tick_volume  spread  real_volume  \
time                                                                           
2021-04-16  258.22  260.88  257.90  260.73        21851       1            0   
2021-04-19  260.89  261.44  257.81  258.22        23590       1            0   
2021-04-20  258.59  260.15  256.82  258.07        23187       1            0   
2021-04-21  258.20  260.63  257.22  260.52        21336       1            0   
2021-04-22  260.58  261.69  255.62  256.92        24417       1            0   

             returns    bb_upper    bb_lower        rsi      macd  
time                                                               
2021-04-16  0.003773  265.619490  224.352510  73.256268  6.107722  
2021-04-19 -0.009627  266.696801  226.000199  67.802939  6.104296  
2021-04-20 -0.000581  267.836309  227.056691  67.479650  6.020081  
2021-04-21  0.009494  269.218841  227.959159  69.996059  6.080938  
2021-04-22 -0.013819  269.718922  229.552078  62.360251  5.772139  

Cointegration & Statistical Testing

In [3]:
from statsmodels.tsa.stattools import coint

# Extract closing prices
aapl = symbols_data["AAPL.NAS"]["close"]
msft = symbols_data["MSFT.NAS"]["close"]

# Perform cointegration test
score, p_value, _ = coint(aapl, msft)
print(f"\n⚖️ Cointegration Test p-value: {p_value:.4f}")

# If p-value < 0.05, the pair is cointegrated
if p_value < 0.05:
    print("✅ AAPL and MSFT are cointegrated. Suitable for pair trading.")
else:
    print("⚠️ Not cointegrated. Consider other pairs.")
⚖️ Cointegration Test p-value: 0.7008
⚠️ Not cointegrated. Consider other pairs.

Compute Spread & Z-score

In [9]:
import numpy as np

# Compute spread
spread = aapl - msft

# Compute Z-score
z_score = (spread - spread.mean()) / spread.std()

# Store Z-score in a DataFrame
signals = pd.DataFrame(index=aapl.index)
signals["spread"] = spread
signals["z_score"] = z_score

print("\n📊 Pair Trading Signals:\n", signals.tail())
📊 Pair Trading Signals:
             spread   z_score
time                        
2025-02-26 -159.28 -0.090304
2025-02-27 -155.23  0.004369
2025-02-28 -155.00  0.009745
2025-03-03 -149.33  0.142288
2025-03-04 -153.16  0.052757

Step 5: Define Trading Rules
📌 Trading Strategy Rules:

Go Long (Buy AAPL, Sell MSFT) → If Z-score < -1.5
Go Short (Sell AAPL, Buy MSFT) → If Z-score > 1.5
Exit Position → If |Z-score| < 0.5

In [5]:
# Define trade signals
signals["long"] = signals["z_score"] < -1.5  # Buy AAPL, Sell MSFT
signals["short"] = signals["z_score"] > 1.5  # Sell AAPL, Buy MSFT
signals["exit"] = abs(signals["z_score"]) < 0.5  # Exit trade

print("\n📊 Trade Signals:\n", signals.tail())
📊 Trade Signals:
             spread   z_score   long  short  exit
time                                            
2025-02-26 -159.28 -0.090304  False  False  True
2025-02-27 -155.23  0.004369  False  False  True
2025-02-28 -155.00  0.009745  False  False  True
2025-03-03 -149.33  0.142288  False  False  True
2025-03-04 -153.16  0.052757  False  False  True

Step 6: Backtesting Strategy

In [6]:
# Define returns for AAPL and MSFT
signals["aapl_returns"] = aapl.pct_change()
signals["msft_returns"] = msft.pct_change()

# Compute strategy returns
signals["strategy_returns"] = (
    signals["long"] * signals["aapl_returns"] -
    signals["long"] * signals["msft_returns"] -
    signals["short"] * signals["aapl_returns"] +
    signals["short"] * signals["msft_returns"]
)

# Compute cumulative returns
signals["cumulative_returns"] = (1 + signals["strategy_returns"]).cumprod()

print("\n📊 Strategy Performance:\n", signals[["strategy_returns", "cumulative_returns"]].tail())
📊 Strategy Performance:
             strategy_returns  cumulative_returns
time                                            
2025-02-26               0.0            0.921201
2025-02-27               0.0            0.921201
2025-02-28               0.0            0.921201
2025-03-03               0.0            0.921201
2025-03-04               0.0            0.921201

Visualize Strategy Performance

In [7]:
import matplotlib.pyplot as plt

plt.figure(figsize=(12,6))
plt.plot(signals["cumulative_returns"], label="Pair Trading Strategy", color="blue")
plt.axhline(1, color="red", linestyle="--")
plt.xlabel("Date")
plt.ylabel("Cumulative Returns")
plt.legend()
plt.title("Pair Trading Strategy Performance")
plt.show()

Step 8: Live Trading Execution in MT5

In [ ]:
def execute_trade(signal, symbol1, symbol2):
    """
    Executes trades in MetaTrader 5.
    If 'long', buys symbol1 & sells symbol2.
    If 'short', sells symbol1 & buys symbol2.
    If 'exit', closes all positions.
    """
    if signal == "long":
        mt5.order_send(
            mt5.ORDER_TYPE_BUY, symbol1, volume=1
        )
        mt5.order_send(
            mt5.ORDER_TYPE_SELL, symbol2, volume=1
        )
    elif signal == "short":
        mt5.order_send(
            mt5.ORDER_TYPE_SELL, symbol1, volume=1
        )
        mt5.order_send(
            mt5.ORDER_TYPE_BUY, symbol2, volume=1
        )
    elif signal == "exit":
        mt5.positions_close(symbol1)
        mt5.positions_close(symbol2)

# Execute trade based on last signal
latest_signal = signals.iloc[-1]

if latest_signal["long"]:
    execute_trade("long", "AAPL.NAS", "MSFT.NAS")
elif latest_signal["short"]:
    execute_trade("short", "AAPL.NAS", "MSFT.NAS")
elif latest_signal["exit"]:
    execute_trade("exit", "AAPL.NAS", "MSFT.NAS")