536 KiB
536 KiB
In [1]:
import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import ta
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from scipy.spatial.distance import euclidean
from statsmodels.tsa.stattools import coint
# ✅ Initialize MT5 Connection
if not mt5.initialize():
print("MT5 Initialization Failed")
mt5.shutdown()
quit()
# ✅ Retrieve All Symbols in MT5
symbols = [s.name for s in mt5.symbols_get()]
print(f"✅ Found {len(symbols)} symbols in MT5")
# ✅ Limit to Top 50 Symbols for Performance (adjust as needed)
symbols = symbols[:50]
# ✅ Function to Fetch Price Data from MT5
def get_mt5_data(symbol, n_bars=1000, timeframe=mt5.TIMEFRAME_D1):
"""Fetches historical data from MT5"""
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[["close"]]
# ✅ Load Data for All Symbols
data = {symbol: get_mt5_data(symbol) for symbol in symbols if get_mt5_data(symbol) is not None}
# ✅ Merge Data into One DataFrame
df = pd.concat(data.values(), axis=1, keys=data.keys()).dropna()
print(f"📊 Data Loaded for {len(df.columns)//2} Symbols")
✅ Found 2061 symbols in MT5 📊 Data Loaded for 25 Symbols
In [2]:
# ✅ Add Features
def add_features(df):
for sym in symbols:
df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \
ta.volatility.bollinger_lband(df[(sym, "close")], window=20)
df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc()
df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")]
return df.dropna()
df = add_features(df)
print(f"✅ Feature Engineering Complete. Shape: {df.shape}")
✅ Feature Engineering Complete. Shape: (947, 200)
C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")] C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:4: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \ C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:6: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc() C:\Users\moham\AppData\Local\Temp\ipykernel_11396\3836770481.py:7: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()` df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")]
In [3]:
from sklearn.preprocessing import StandardScaler
# ✅ Standardize the Data
scaler = StandardScaler()
scaled_data = scaler.fit_transform(df.xs("close", axis=1, level=1))
# ✅ PCA: Find Principal Components
pca = PCA(n_components=3) # Keep 3 Principal Components
pca_components = pca.fit_transform(scaled_data)
# ✅ Assign PCA Factor Weights to Each Symbol
factor_df = pd.DataFrame(pca.components_.T, index=symbols, columns=[f"PC{i+1}" for i in range(pca.n_components_)])
print(factor_df)
# ✅ Find Closest Pairs Based on PCA Factor Similarity
def find_pca_pairs(factor_df):
pairs = []
for i, sym1 in enumerate(factor_df.index):
for j, sym2 in enumerate(factor_df.index):
if i < j:
distance = euclidean(factor_df.loc[sym1], factor_df.loc[sym2])
pairs.append((sym1, sym2, distance))
# ✅ Select Best Pairs with Smallest Distance
sorted_pairs = sorted(pairs, key=lambda x: x[2])
return sorted_pairs[:10] # Return Top 10 Closest Pairs
selected_pairs = find_pca_pairs(factor_df)
print("🎯 Best PCA-Based Pairs:", selected_pairs)
PC1 PC2 PC3
EURUSD -0.074120 0.270696 0.039489
GBPUSD -0.061762 0.274868 -0.131038
USDCHF -0.108607 -0.215683 -0.051013
USDJPY 0.173301 -0.089506 -0.055852
USDCAD 0.164355 -0.045407 0.066992
AUDUSD -0.162769 0.089750 -0.103257
AUDNZD 0.085779 -0.196785 -0.144085
AUDCAD -0.086638 0.126954 -0.108870
AUDCHF -0.174758 -0.053159 -0.103954
AUDJPY 0.155454 -0.083599 -0.149799
CHFJPY 0.182018 -0.008838 -0.035827
EURGBP -0.009282 -0.105880 0.444556
EURAUD 0.128375 0.172335 0.163352
EURJPY 0.178240 0.011701 -0.047627
EURCHF -0.166736 0.074977 -0.007875
EURNZD 0.165860 0.075182 0.087998
EURCAD 0.087572 0.246827 0.111828
GBPCHF -0.151606 0.108690 -0.173693
GBPJPY 0.175006 0.031103 -0.130606
CADCHF -0.166122 -0.101996 -0.081290
CADJPY 0.159921 -0.106765 -0.103625
GBPAUD 0.122411 0.200551 -0.034171
GBPCAD 0.081074 0.256699 -0.086531
GBPNZD 0.154990 0.109517 -0.096284
NZDCAD -0.109840 0.200551 0.019302
NZDCHF -0.180453 0.004945 -0.058713
NZDJPY 0.155090 -0.037133 -0.126900
NZDUSD -0.158342 0.134926 -0.038115
USDSGD -0.057412 -0.223057 -0.079595
AUDSGD -0.174453 0.007578 -0.127653
CHFSGD 0.125553 0.162212 0.016395
EURDKK 0.168947 0.074703 -0.044841
EURHKD -0.077918 0.264445 0.052825
EURNOK 0.168174 0.075413 0.125895
EURPLN -0.138813 -0.154186 0.145267
EURSEK 0.166724 -0.012255 0.137284
EURSGD -0.116597 0.205586 0.004615
EURTRY 0.177953 0.036838 -0.081277
EURZAR 0.151932 0.071716 0.207157
GBPDKK 0.022237 0.108872 -0.439560
GBPNOK 0.168420 0.100331 0.009281
GBPSEK 0.170438 0.026468 -0.026887
GBPSGD -0.095840 0.220007 -0.184375
GBPTRY 0.176567 0.040673 -0.100254
NOKJPY 0.100376 -0.101244 -0.281504
NOKSEK -0.102030 -0.175865 -0.071520
SEKJPY 0.149099 0.026119 -0.195165
SGDJPY 0.179101 -0.048630 -0.042813
USDCNH 0.172838 -0.067409 0.055439
USDCZK 0.066981 -0.211104 -0.209816
🎯 Best PCA-Based Pairs: [('EURUSD', 'EURHKD', 0.015209774849310981), ('NZDCAD', 'EURSGD', 0.016933687040313963), ('EURTRY', 'GBPTRY', 0.01940957888058514), ('CHFJPY', 'EURJPY', 0.02398707933964542), ('USDCAD', 'USDCNH', 0.026259049063238678), ('EURJPY', 'GBPSEK', 0.026627907561686315), ('GBPJPY', 'GBPTRY', 0.03186350419122791), ('EURNZD', 'EURNOK', 0.037968480416527), ('CHFJPY', 'GBPSEK', 0.03821655894445786), ('CHFJPY', 'SGDJPY', 0.04050607133426434)]
In [6]:
from sklearn.cluster import KMeans
# Use factor_df from PCA as input for clustering
kmeans = KMeans(n_clusters=3, random_state=42)
factor_df['cluster'] = kmeans.fit_predict(factor_df)
print("📌 Symbol Clusters:")
print(factor_df[['cluster']])
# Group symbols by cluster and form pairs within each cluster
def find_cluster_pairs_from_factor_df(factor_df):
pairs = []
for cluster in factor_df['cluster'].unique():
symbols_in_cluster = factor_df[factor_df['cluster'] == cluster].index.tolist()
for i, sym1 in enumerate(symbols_in_cluster):
for j, sym2 in enumerate(symbols_in_cluster):
if i < j:
pairs.append((sym1, sym2))
return pairs
cluster_pairs = find_cluster_pairs_from_factor_df(factor_df)
print("🎯 Cluster-Based Pairs:", cluster_pairs)
📌 Symbol Clusters:
cluster
EURUSD 1
GBPUSD 1
USDCHF 2
USDJPY 0
USDCAD 0
AUDUSD 1
AUDNZD 2
AUDCAD 1
AUDCHF 2
AUDJPY 0
CHFJPY 0
EURGBP 2
EURAUD 0
EURJPY 0
EURCHF 1
EURNZD 0
EURCAD 0
GBPCHF 1
GBPJPY 0
CADCHF 2
CADJPY 0
GBPAUD 0
GBPCAD 1
GBPNZD 0
NZDCAD 1
NZDCHF 1
NZDJPY 0
NZDUSD 1
USDSGD 2
AUDSGD 1
CHFSGD 0
EURDKK 0
EURHKD 1
EURNOK 0
EURPLN 2
EURSEK 0
EURSGD 1
EURTRY 0
EURZAR 0
GBPDKK 1
GBPNOK 0
GBPSEK 0
GBPSGD 1
GBPTRY 0
NOKJPY 0
NOKSEK 2
SEKJPY 0
SGDJPY 0
USDCNH 0
USDCZK 2
🎯 Cluster-Based Pairs: [('EURUSD', 'GBPUSD'), ('EURUSD', 'AUDUSD'), ('EURUSD', 'AUDCAD'), ('EURUSD', 'EURCHF'), ('EURUSD', 'GBPCHF'), ('EURUSD', 'GBPCAD'), ('EURUSD', 'NZDCAD'), ('EURUSD', 'NZDCHF'), ('EURUSD', 'NZDUSD'), ('EURUSD', 'AUDSGD'), ('EURUSD', 'EURHKD'), ('EURUSD', 'EURSGD'), ('EURUSD', 'GBPDKK'), ('EURUSD', 'GBPSGD'), ('GBPUSD', 'AUDUSD'), ('GBPUSD', 'AUDCAD'), ('GBPUSD', 'EURCHF'), ('GBPUSD', 'GBPCHF'), ('GBPUSD', 'GBPCAD'), ('GBPUSD', 'NZDCAD'), ('GBPUSD', 'NZDCHF'), ('GBPUSD', 'NZDUSD'), ('GBPUSD', 'AUDSGD'), ('GBPUSD', 'EURHKD'), ('GBPUSD', 'EURSGD'), ('GBPUSD', 'GBPDKK'), ('GBPUSD', 'GBPSGD'), ('AUDUSD', 'AUDCAD'), ('AUDUSD', 'EURCHF'), ('AUDUSD', 'GBPCHF'), ('AUDUSD', 'GBPCAD'), ('AUDUSD', 'NZDCAD'), ('AUDUSD', 'NZDCHF'), ('AUDUSD', 'NZDUSD'), ('AUDUSD', 'AUDSGD'), ('AUDUSD', 'EURHKD'), ('AUDUSD', 'EURSGD'), ('AUDUSD', 'GBPDKK'), ('AUDUSD', 'GBPSGD'), ('AUDCAD', 'EURCHF'), ('AUDCAD', 'GBPCHF'), ('AUDCAD', 'GBPCAD'), ('AUDCAD', 'NZDCAD'), ('AUDCAD', 'NZDCHF'), ('AUDCAD', 'NZDUSD'), ('AUDCAD', 'AUDSGD'), ('AUDCAD', 'EURHKD'), ('AUDCAD', 'EURSGD'), ('AUDCAD', 'GBPDKK'), ('AUDCAD', 'GBPSGD'), ('EURCHF', 'GBPCHF'), ('EURCHF', 'GBPCAD'), ('EURCHF', 'NZDCAD'), ('EURCHF', 'NZDCHF'), ('EURCHF', 'NZDUSD'), ('EURCHF', 'AUDSGD'), ('EURCHF', 'EURHKD'), ('EURCHF', 'EURSGD'), ('EURCHF', 'GBPDKK'), ('EURCHF', 'GBPSGD'), ('GBPCHF', 'GBPCAD'), ('GBPCHF', 'NZDCAD'), ('GBPCHF', 'NZDCHF'), ('GBPCHF', 'NZDUSD'), ('GBPCHF', 'AUDSGD'), ('GBPCHF', 'EURHKD'), ('GBPCHF', 'EURSGD'), ('GBPCHF', 'GBPDKK'), ('GBPCHF', 'GBPSGD'), ('GBPCAD', 'NZDCAD'), ('GBPCAD', 'NZDCHF'), ('GBPCAD', 'NZDUSD'), ('GBPCAD', 'AUDSGD'), ('GBPCAD', 'EURHKD'), ('GBPCAD', 'EURSGD'), ('GBPCAD', 'GBPDKK'), ('GBPCAD', 'GBPSGD'), ('NZDCAD', 'NZDCHF'), ('NZDCAD', 'NZDUSD'), ('NZDCAD', 'AUDSGD'), ('NZDCAD', 'EURHKD'), ('NZDCAD', 'EURSGD'), ('NZDCAD', 'GBPDKK'), ('NZDCAD', 'GBPSGD'), ('NZDCHF', 'NZDUSD'), ('NZDCHF', 'AUDSGD'), ('NZDCHF', 'EURHKD'), ('NZDCHF', 'EURSGD'), ('NZDCHF', 'GBPDKK'), ('NZDCHF', 'GBPSGD'), ('NZDUSD', 'AUDSGD'), ('NZDUSD', 'EURHKD'), ('NZDUSD', 'EURSGD'), ('NZDUSD', 'GBPDKK'), ('NZDUSD', 'GBPSGD'), ('AUDSGD', 'EURHKD'), ('AUDSGD', 'EURSGD'), ('AUDSGD', 'GBPDKK'), ('AUDSGD', 'GBPSGD'), ('EURHKD', 'EURSGD'), ('EURHKD', 'GBPDKK'), ('EURHKD', 'GBPSGD'), ('EURSGD', 'GBPDKK'), ('EURSGD', 'GBPSGD'), ('GBPDKK', 'GBPSGD'), ('USDCHF', 'AUDNZD'), ('USDCHF', 'AUDCHF'), ('USDCHF', 'EURGBP'), ('USDCHF', 'CADCHF'), ('USDCHF', 'USDSGD'), ('USDCHF', 'EURPLN'), ('USDCHF', 'NOKSEK'), ('USDCHF', 'USDCZK'), ('AUDNZD', 'AUDCHF'), ('AUDNZD', 'EURGBP'), ('AUDNZD', 'CADCHF'), ('AUDNZD', 'USDSGD'), ('AUDNZD', 'EURPLN'), ('AUDNZD', 'NOKSEK'), ('AUDNZD', 'USDCZK'), ('AUDCHF', 'EURGBP'), ('AUDCHF', 'CADCHF'), ('AUDCHF', 'USDSGD'), ('AUDCHF', 'EURPLN'), ('AUDCHF', 'NOKSEK'), ('AUDCHF', 'USDCZK'), ('EURGBP', 'CADCHF'), ('EURGBP', 'USDSGD'), ('EURGBP', 'EURPLN'), ('EURGBP', 'NOKSEK'), ('EURGBP', 'USDCZK'), ('CADCHF', 'USDSGD'), ('CADCHF', 'EURPLN'), ('CADCHF', 'NOKSEK'), ('CADCHF', 'USDCZK'), ('USDSGD', 'EURPLN'), ('USDSGD', 'NOKSEK'), ('USDSGD', 'USDCZK'), ('EURPLN', 'NOKSEK'), ('EURPLN', 'USDCZK'), ('NOKSEK', 'USDCZK'), ('USDJPY', 'USDCAD'), ('USDJPY', 'AUDJPY'), ('USDJPY', 'CHFJPY'), ('USDJPY', 'EURAUD'), ('USDJPY', 'EURJPY'), ('USDJPY', 'EURNZD'), ('USDJPY', 'EURCAD'), ('USDJPY', 'GBPJPY'), ('USDJPY', 'CADJPY'), ('USDJPY', 'GBPAUD'), ('USDJPY', 'GBPNZD'), ('USDJPY', 'NZDJPY'), ('USDJPY', 'CHFSGD'), ('USDJPY', 'EURDKK'), ('USDJPY', 'EURNOK'), ('USDJPY', 'EURSEK'), ('USDJPY', 'EURTRY'), ('USDJPY', 'EURZAR'), ('USDJPY', 'GBPNOK'), ('USDJPY', 'GBPSEK'), ('USDJPY', 'GBPTRY'), ('USDJPY', 'NOKJPY'), ('USDJPY', 'SEKJPY'), ('USDJPY', 'SGDJPY'), ('USDJPY', 'USDCNH'), ('USDCAD', 'AUDJPY'), ('USDCAD', 'CHFJPY'), ('USDCAD', 'EURAUD'), ('USDCAD', 'EURJPY'), ('USDCAD', 'EURNZD'), ('USDCAD', 'EURCAD'), ('USDCAD', 'GBPJPY'), ('USDCAD', 'CADJPY'), ('USDCAD', 'GBPAUD'), ('USDCAD', 'GBPNZD'), ('USDCAD', 'NZDJPY'), ('USDCAD', 'CHFSGD'), ('USDCAD', 'EURDKK'), ('USDCAD', 'EURNOK'), ('USDCAD', 'EURSEK'), ('USDCAD', 'EURTRY'), ('USDCAD', 'EURZAR'), ('USDCAD', 'GBPNOK'), ('USDCAD', 'GBPSEK'), ('USDCAD', 'GBPTRY'), ('USDCAD', 'NOKJPY'), ('USDCAD', 'SEKJPY'), ('USDCAD', 'SGDJPY'), ('USDCAD', 'USDCNH'), ('AUDJPY', 'CHFJPY'), ('AUDJPY', 'EURAUD'), ('AUDJPY', 'EURJPY'), ('AUDJPY', 'EURNZD'), ('AUDJPY', 'EURCAD'), ('AUDJPY', 'GBPJPY'), ('AUDJPY', 'CADJPY'), ('AUDJPY', 'GBPAUD'), ('AUDJPY', 'GBPNZD'), ('AUDJPY', 'NZDJPY'), ('AUDJPY', 'CHFSGD'), ('AUDJPY', 'EURDKK'), ('AUDJPY', 'EURNOK'), ('AUDJPY', 'EURSEK'), ('AUDJPY', 'EURTRY'), ('AUDJPY', 'EURZAR'), ('AUDJPY', 'GBPNOK'), ('AUDJPY', 'GBPSEK'), ('AUDJPY', 'GBPTRY'), ('AUDJPY', 'NOKJPY'), ('AUDJPY', 'SEKJPY'), ('AUDJPY', 'SGDJPY'), ('AUDJPY', 'USDCNH'), ('CHFJPY', 'EURAUD'), ('CHFJPY', 'EURJPY'), ('CHFJPY', 'EURNZD'), ('CHFJPY', 'EURCAD'), ('CHFJPY', 'GBPJPY'), ('CHFJPY', 'CADJPY'), ('CHFJPY', 'GBPAUD'), ('CHFJPY', 'GBPNZD'), ('CHFJPY', 'NZDJPY'), ('CHFJPY', 'CHFSGD'), ('CHFJPY', 'EURDKK'), ('CHFJPY', 'EURNOK'), ('CHFJPY', 'EURSEK'), ('CHFJPY', 'EURTRY'), ('CHFJPY', 'EURZAR'), ('CHFJPY', 'GBPNOK'), ('CHFJPY', 'GBPSEK'), ('CHFJPY', 'GBPTRY'), ('CHFJPY', 'NOKJPY'), ('CHFJPY', 'SEKJPY'), ('CHFJPY', 'SGDJPY'), ('CHFJPY', 'USDCNH'), ('EURAUD', 'EURJPY'), ('EURAUD', 'EURNZD'), ('EURAUD', 'EURCAD'), ('EURAUD', 'GBPJPY'), ('EURAUD', 'CADJPY'), ('EURAUD', 'GBPAUD'), ('EURAUD', 'GBPNZD'), ('EURAUD', 'NZDJPY'), ('EURAUD', 'CHFSGD'), ('EURAUD', 'EURDKK'), ('EURAUD', 'EURNOK'), ('EURAUD', 'EURSEK'), ('EURAUD', 'EURTRY'), ('EURAUD', 'EURZAR'), ('EURAUD', 'GBPNOK'), ('EURAUD', 'GBPSEK'), ('EURAUD', 'GBPTRY'), ('EURAUD', 'NOKJPY'), ('EURAUD', 'SEKJPY'), ('EURAUD', 'SGDJPY'), ('EURAUD', 'USDCNH'), ('EURJPY', 'EURNZD'), ('EURJPY', 'EURCAD'), ('EURJPY', 'GBPJPY'), ('EURJPY', 'CADJPY'), ('EURJPY', 'GBPAUD'), ('EURJPY', 'GBPNZD'), ('EURJPY', 'NZDJPY'), ('EURJPY', 'CHFSGD'), ('EURJPY', 'EURDKK'), ('EURJPY', 'EURNOK'), ('EURJPY', 'EURSEK'), ('EURJPY', 'EURTRY'), ('EURJPY', 'EURZAR'), ('EURJPY', 'GBPNOK'), ('EURJPY', 'GBPSEK'), ('EURJPY', 'GBPTRY'), ('EURJPY', 'NOKJPY'), ('EURJPY', 'SEKJPY'), ('EURJPY', 'SGDJPY'), ('EURJPY', 'USDCNH'), ('EURNZD', 'EURCAD'), ('EURNZD', 'GBPJPY'), ('EURNZD', 'CADJPY'), ('EURNZD', 'GBPAUD'), ('EURNZD', 'GBPNZD'), ('EURNZD', 'NZDJPY'), ('EURNZD', 'CHFSGD'), ('EURNZD', 'EURDKK'), ('EURNZD', 'EURNOK'), ('EURNZD', 'EURSEK'), ('EURNZD', 'EURTRY'), ('EURNZD', 'EURZAR'), ('EURNZD', 'GBPNOK'), ('EURNZD', 'GBPSEK'), ('EURNZD', 'GBPTRY'), ('EURNZD', 'NOKJPY'), ('EURNZD', 'SEKJPY'), ('EURNZD', 'SGDJPY'), ('EURNZD', 'USDCNH'), ('EURCAD', 'GBPJPY'), ('EURCAD', 'CADJPY'), ('EURCAD', 'GBPAUD'), ('EURCAD', 'GBPNZD'), ('EURCAD', 'NZDJPY'), ('EURCAD', 'CHFSGD'), ('EURCAD', 'EURDKK'), ('EURCAD', 'EURNOK'), ('EURCAD', 'EURSEK'), ('EURCAD', 'EURTRY'), ('EURCAD', 'EURZAR'), ('EURCAD', 'GBPNOK'), ('EURCAD', 'GBPSEK'), ('EURCAD', 'GBPTRY'), ('EURCAD', 'NOKJPY'), ('EURCAD', 'SEKJPY'), ('EURCAD', 'SGDJPY'), ('EURCAD', 'USDCNH'), ('GBPJPY', 'CADJPY'), ('GBPJPY', 'GBPAUD'), ('GBPJPY', 'GBPNZD'), ('GBPJPY', 'NZDJPY'), ('GBPJPY', 'CHFSGD'), ('GBPJPY', 'EURDKK'), ('GBPJPY', 'EURNOK'), ('GBPJPY', 'EURSEK'), ('GBPJPY', 'EURTRY'), ('GBPJPY', 'EURZAR'), ('GBPJPY', 'GBPNOK'), ('GBPJPY', 'GBPSEK'), ('GBPJPY', 'GBPTRY'), ('GBPJPY', 'NOKJPY'), ('GBPJPY', 'SEKJPY'), ('GBPJPY', 'SGDJPY'), ('GBPJPY', 'USDCNH'), ('CADJPY', 'GBPAUD'), ('CADJPY', 'GBPNZD'), ('CADJPY', 'NZDJPY'), ('CADJPY', 'CHFSGD'), ('CADJPY', 'EURDKK'), ('CADJPY', 'EURNOK'), ('CADJPY', 'EURSEK'), ('CADJPY', 'EURTRY'), ('CADJPY', 'EURZAR'), ('CADJPY', 'GBPNOK'), ('CADJPY', 'GBPSEK'), ('CADJPY', 'GBPTRY'), ('CADJPY', 'NOKJPY'), ('CADJPY', 'SEKJPY'), ('CADJPY', 'SGDJPY'), ('CADJPY', 'USDCNH'), ('GBPAUD', 'GBPNZD'), ('GBPAUD', 'NZDJPY'), ('GBPAUD', 'CHFSGD'), ('GBPAUD', 'EURDKK'), ('GBPAUD', 'EURNOK'), ('GBPAUD', 'EURSEK'), ('GBPAUD', 'EURTRY'), ('GBPAUD', 'EURZAR'), ('GBPAUD', 'GBPNOK'), ('GBPAUD', 'GBPSEK'), ('GBPAUD', 'GBPTRY'), ('GBPAUD', 'NOKJPY'), ('GBPAUD', 'SEKJPY'), ('GBPAUD', 'SGDJPY'), ('GBPAUD', 'USDCNH'), ('GBPNZD', 'NZDJPY'), ('GBPNZD', 'CHFSGD'), ('GBPNZD', 'EURDKK'), ('GBPNZD', 'EURNOK'), ('GBPNZD', 'EURSEK'), ('GBPNZD', 'EURTRY'), ('GBPNZD', 'EURZAR'), ('GBPNZD', 'GBPNOK'), ('GBPNZD', 'GBPSEK'), ('GBPNZD', 'GBPTRY'), ('GBPNZD', 'NOKJPY'), ('GBPNZD', 'SEKJPY'), ('GBPNZD', 'SGDJPY'), ('GBPNZD', 'USDCNH'), ('NZDJPY', 'CHFSGD'), ('NZDJPY', 'EURDKK'), ('NZDJPY', 'EURNOK'), ('NZDJPY', 'EURSEK'), ('NZDJPY', 'EURTRY'), ('NZDJPY', 'EURZAR'), ('NZDJPY', 'GBPNOK'), ('NZDJPY', 'GBPSEK'), ('NZDJPY', 'GBPTRY'), ('NZDJPY', 'NOKJPY'), ('NZDJPY', 'SEKJPY'), ('NZDJPY', 'SGDJPY'), ('NZDJPY', 'USDCNH'), ('CHFSGD', 'EURDKK'), ('CHFSGD', 'EURNOK'), ('CHFSGD', 'EURSEK'), ('CHFSGD', 'EURTRY'), ('CHFSGD', 'EURZAR'), ('CHFSGD', 'GBPNOK'), ('CHFSGD', 'GBPSEK'), ('CHFSGD', 'GBPTRY'), ('CHFSGD', 'NOKJPY'), ('CHFSGD', 'SEKJPY'), ('CHFSGD', 'SGDJPY'), ('CHFSGD', 'USDCNH'), ('EURDKK', 'EURNOK'), ('EURDKK', 'EURSEK'), ('EURDKK', 'EURTRY'), ('EURDKK', 'EURZAR'), ('EURDKK', 'GBPNOK'), ('EURDKK', 'GBPSEK'), ('EURDKK', 'GBPTRY'), ('EURDKK', 'NOKJPY'), ('EURDKK', 'SEKJPY'), ('EURDKK', 'SGDJPY'), ('EURDKK', 'USDCNH'), ('EURNOK', 'EURSEK'), ('EURNOK', 'EURTRY'), ('EURNOK', 'EURZAR'), ('EURNOK', 'GBPNOK'), ('EURNOK', 'GBPSEK'), ('EURNOK', 'GBPTRY'), ('EURNOK', 'NOKJPY'), ('EURNOK', 'SEKJPY'), ('EURNOK', 'SGDJPY'), ('EURNOK', 'USDCNH'), ('EURSEK', 'EURTRY'), ('EURSEK', 'EURZAR'), ('EURSEK', 'GBPNOK'), ('EURSEK', 'GBPSEK'), ('EURSEK', 'GBPTRY'), ('EURSEK', 'NOKJPY'), ('EURSEK', 'SEKJPY'), ('EURSEK', 'SGDJPY'), ('EURSEK', 'USDCNH'), ('EURTRY', 'EURZAR'), ('EURTRY', 'GBPNOK'), ('EURTRY', 'GBPSEK'), ('EURTRY', 'GBPTRY'), ('EURTRY', 'NOKJPY'), ('EURTRY', 'SEKJPY'), ('EURTRY', 'SGDJPY'), ('EURTRY', 'USDCNH'), ('EURZAR', 'GBPNOK'), ('EURZAR', 'GBPSEK'), ('EURZAR', 'GBPTRY'), ('EURZAR', 'NOKJPY'), ('EURZAR', 'SEKJPY'), ('EURZAR', 'SGDJPY'), ('EURZAR', 'USDCNH'), ('GBPNOK', 'GBPSEK'), ('GBPNOK', 'GBPTRY'), ('GBPNOK', 'NOKJPY'), ('GBPNOK', 'SEKJPY'), ('GBPNOK', 'SGDJPY'), ('GBPNOK', 'USDCNH'), ('GBPSEK', 'GBPTRY'), ('GBPSEK', 'NOKJPY'), ('GBPSEK', 'SEKJPY'), ('GBPSEK', 'SGDJPY'), ('GBPSEK', 'USDCNH'), ('GBPTRY', 'NOKJPY'), ('GBPTRY', 'SEKJPY'), ('GBPTRY', 'SGDJPY'), ('GBPTRY', 'USDCNH'), ('NOKJPY', 'SEKJPY'), ('NOKJPY', 'SGDJPY'), ('NOKJPY', 'USDCNH'), ('SEKJPY', 'SGDJPY'), ('SEKJPY', 'USDCNH'), ('SGDJPY', 'USDCNH')]
In [7]:
import vectorbt as vbt
# Option 1: Use the PCA-selected pair
pair1, pair2 = selected_pairs[0][:2]
# Option 2: Alternatively, use a cluster-based pair
# pair1, pair2 = cluster_pairs[0]
print(f"Testing pair: {pair1} vs {pair2}")
df1, df2 = data[pair1], data[pair2]
# Compute Spread & Z-Score
spread = df1["close"] - df2["close"]
z_score = (spread - spread.rolling(60).mean()) / spread.rolling(60).std()
# Define Entry & Exit Signals
entries = z_score < -1.5 # Buy pair1, Sell pair2
short_entries = z_score > 1.5 # Sell pair1, Buy pair2
exits = abs(z_score) < 0.5 # Exit when mean reversion occurs
# Backtest using vectorbt
portfolio = vbt.Portfolio.from_signals(
close=df1["close"],
entries=entries,
exits=exits,
short_entries=short_entries,
short_exits=exits,
size=1,
size_type="percent",
init_cash=10000,
fees=0.0002,
freq="1H"
)
print(portfolio.stats())
portfolio.plot().show()
Testing pair: EURUSD vs EURHKD Start 2021-04-29 00:00:00 End 2025-03-05 00:00:00 Period 41 days 16:00:00 Start Value 10000.0 End Value 9726.830626 Total Return [%] -2.731694 Benchmark Return [%] -12.028977 Max Gross Exposure [%] 100.0 Total Fees Paid 95.663717 Max Drawdown [%] 11.550894 Max Drawdown Duration 30 days 09:00:00 Total Trades 25 Total Closed Trades 24 Total Open Trades 1 Open Trade PnL 31.724849 Win Rate [%] 37.5 Best Trade [%] 4.555476 Worst Trade [%] -1.846803 Avg Winning Trade [%] 1.590328 Avg Losing Trade [%] -1.137923 Avg Winning Trade Duration 1 days 16:13:20 Avg Losing Trade Duration 0 days 13:08:00 Profit Factor 0.817878 Expectancy -12.703926 Sharpe Ratio -0.579445 Calmar Ratio -1.865092 Omega Ratio 0.978605 Sortino Ratio -0.829624 Name: close, dtype: object
c:\Users\moham\miniconda3\envs\ml\Lib\site-packages\vectorbt\utils\datetime_.py:24: FutureWarning: 'H' is deprecated and will be removed in a future version. Please use 'h' instead of 'H'.
[Data output - unsupported data type map[string]interface {} for mime type application/vnd.plotly.v1+json]
In [ ]:
def live_pair_trading(pair1, pair2):
"""Executes pair trading strategy on MT5."""
df_signals = generate_pair_signals(pair1, pair2)
latest = df_signals.iloc[-1]
if latest["long_signal"]:
place_order(pair1, LOT_SIZE, is_buy=True, magic=MAGIC_NUMBER)
place_order(pair2, LOT_SIZE, is_buy=False, magic=MAGIC_NUMBER)
elif latest["short_signal"]:
place_order(pair1, LOT_SIZE, is_buy=False, magic=MAGIC_NUMBER)
place_order(pair2, LOT_SIZE, is_buy=True, magic=MAGIC_NUMBER)
elif latest["exit_signal"]:
close_positions(pair1, MAGIC_NUMBER)
close_positions(pair2, MAGIC_NUMBER)
# ✅ Run Live Trading
while True:
live_pair_trading(pair1, pair2)
time.sleep(3600) # Run every hour
In [1]:
import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import ta
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from scipy.spatial.distance import euclidean
from statsmodels.tsa.stattools import coint
# ✅ Initialize MT5 Connection
if not mt5.initialize():
print("MT5 Initialization Failed")
mt5.shutdown()
# ✅ Fetch Data for Multiple Symbols
symbols = ["AUDUSD", "NZDUSD", "EURUSD", "GBPUSD", "USDJPY", "USDCAD"] # Can be expanded
n_bars = 2000 # Historical lookback
def get_mt5_data(symbol, n_bars, timeframe=mt5.TIMEFRAME_H1):
"""Fetches historical data from MT5"""
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, n_bars)
if rates is None:
raise ValueError(f"Could not retrieve data for {symbol}")
df = pd.DataFrame(rates)
df["time"] = pd.to_datetime(df["time"], unit="s")
df.set_index("time", inplace=True)
return df[["close"]]
# ✅ Load Data for All Symbols
data = {symbol: get_mt5_data(symbol, n_bars) for symbol in symbols}
# ✅ Merge Data into One DataFrame
df = pd.concat(data.values(), axis=1, keys=data.keys())
# ✅ Add Technical Features (Volatility, Momentum, etc.)
def add_features(df):
for sym in symbols:
df[(sym, "volatility")] = ta.volatility.bollinger_hband(df[(sym, "close")], window=20) - \
ta.volatility.bollinger_lband(df[(sym, "close")], window=20)
df[(sym, "momentum")] = ta.momentum.ROCIndicator(df[(sym, "close")], window=10).roc()
df[(sym, "mean_reversion")] = df[(sym, "close")].rolling(50).mean() - df[(sym, "close")]
return df.dropna()
df = add_features(df)
print(df.head()) # Verify Features
AUDUSD NZDUSD EURUSD GBPUSD USDJPY USDCAD \
close close close close close close
time
2024-11-07 19:00:00 0.66772 0.60247 1.08048 1.29899 153.016 1.38504
2024-11-07 20:00:00 0.66700 0.60199 1.07981 1.29726 152.963 1.38617
2024-11-07 21:00:00 0.66675 0.60170 1.07806 1.29645 153.085 1.38616
2024-11-07 22:00:00 0.66773 0.60238 1.08025 1.29827 152.853 1.38613
2024-11-07 23:00:00 0.66802 0.60252 1.08031 1.29871 152.899 1.38594
AUDUSD NZDUSD ... \
volatility momentum mean_reversion volatility ...
time ...
2024-11-07 19:00:00 0.014067 0.819883 -0.007538 0.011677 ...
2024-11-07 20:00:00 0.013384 0.521446 -0.006740 0.011059 ...
2024-11-07 21:00:00 0.012231 0.571679 -0.006423 0.010070 ...
2024-11-07 22:00:00 0.010929 0.689125 -0.007317 0.008573 ...
2024-11-07 23:00:00 0.009795 0.741970 -0.007517 0.007439 ...
EURUSD GBPUSD \
mean_reversion volatility momentum mean_reversion
time
2024-11-07 19:00:00 -0.002424 0.015236 0.462487 -0.006039
2024-11-07 20:00:00 -0.001998 0.014693 0.354303 -0.004392
2024-11-07 21:00:00 -0.000547 0.014055 0.441604 -0.003720
2024-11-07 22:00:00 -0.002993 0.013454 0.600533 -0.005625
2024-11-07 23:00:00 -0.003309 0.012935 0.633848 -0.006133
USDJPY USDCAD \
volatility momentum mean_reversion volatility momentum
time
2024-11-07 19:00:00 1.974957 -0.640251 0.60886 0.010612 -0.267865
2024-11-07 20:00:00 2.063996 -0.698524 0.68188 0.009988 -0.118891
2024-11-07 21:00:00 2.118684 -0.561225 0.58790 0.008682 -0.215240
2024-11-07 22:00:00 2.138153 -0.756405 0.84584 0.007324 -0.169250
2024-11-07 23:00:00 2.109487 -0.759400 0.82878 0.005920 -0.241130
mean_reversion
time
2024-11-07 19:00:00 0.004597
2024-11-07 20:00:00 0.003484
2024-11-07 21:00:00 0.003525
2024-11-07 22:00:00 0.003583
2024-11-07 23:00:00 0.003809
[5 rows x 24 columns]
In [ ]:
from sklearn.preprocessing import StandardScaler
# ✅ Standardize the Data
scaler = StandardScaler()
scaled_data = scaler.fit_transform(df.xs("close", axis=1, level=1))
# ✅ PCA: Find Principal Components
pca = PCA(n_components=3) # Keep 3 Principal Components
pca_components = pca.fit_transform(scaled_data)
# ✅ Assign PCA Factor Weights to Each Symbol
factor_df = pd.DataFrame(pca.components_.T, index=symbols, columns=[f"PC{i+1}" for i in range(pca.n_components_)])
print(factor_df)
# ✅ Find Closest Pairs Based on PCA Factor Similarity
def find_pca_pairs(factor_df):
pairs = []
for i, sym1 in enumerate(factor_df.index):
for j, sym2 in enumerate(factor_df.index):
if i < j:
distance = euclidean(factor_df.loc[sym1], factor_df.loc[sym2])
pairs.append((sym1, sym2, distance))
# ✅ Select Best Pairs with Smallest Distance
sorted_pairs = sorted(pairs, key=lambda x: x[2])
return sorted_pairs[:10] # Return Top 10 Closest Pairs
selected_pairs = find_pca_pairs(factor_df)
print("🎯 Best PCA-Based Pairs:", selected_pairs)
In [ ]:
from sklearn.cluster import KMeans
# ✅ K-Means Clustering
kmeans = KMeans(n_clusters=3, random_state=42)
df["cluster"] = kmeans.fit_predict(scaled_data)
# ✅ Group Stocks by Clusters
clusters = df.groupby("cluster").apply(lambda x: x.index.tolist())
print("📌 Identified Clusters:", clusters)
# ✅ Select Best Pairs within Clusters
def find_cluster_pairs(clusters):
best_pairs = []
for cluster in clusters:
for i, sym1 in enumerate(cluster):
for j, sym2 in enumerate(cluster):
if i < j:
best_pairs.append((sym1, sym2))
return best_pairs
cluster_pairs = find_cluster_pairs(clusters)
print("🎯 Cluster-Based Pairs:", cluster_pairs)
In [ ]:
import vectorbt as vbt
# ✅ Select Pair for Backtest
pair1, pair2 = selected_pairs[0][:2]
df1, df2 = data[pair1], data[pair2]
# ✅ Compute Spread & Z-Score
spread = df1["close"] - df2["close"]
z_score = (spread - spread.rolling(60).mean()) / spread.rolling(60).std()
# ✅ Define Entry & Exit Signals
entries = z_score < -1.5 # Buy Pair1, Sell Pair2
exits = abs(z_score) < 0.5 # Exit when mean reversion happens
short_entries = z_score > 1.5 # Sell Pair1, Buy Pair2
# ✅ Backtest with vectorbt
portfolio = vbt.Portfolio.from_signals(
close=df1["close"],
entries=entries,
exits=exits,
short_entries=short_entries,
short_exits=exits,
size=1,
size_type="percent",
init_cash=10000,
fees=0.0002,
freq="1H"
)
# ✅ Results
print(portfolio.stats())
portfolio.plot().show()
In [ ]:
def live_pair_trading(pair1, pair2):
"""Executes pair trading strategy on MT5."""
df_signals = generate_pair_signals(pair1, pair2)
latest = df_signals.iloc[-1]
if latest["long_signal"]:
place_order(pair1, LOT_SIZE, is_buy=True, magic=MAGIC_NUMBER)
place_order(pair2, LOT_SIZE, is_buy=False, magic=MAGIC_NUMBER)
elif latest["short_signal"]:
place_order(pair1, LOT_SIZE, is_buy=False, magic=MAGIC_NUMBER)
place_order(pair2, LOT_SIZE, is_buy=True, magic=MAGIC_NUMBER)
elif latest["exit_signal"]:
close_positions(pair1, MAGIC_NUMBER)
close_positions(pair2, MAGIC_NUMBER)
# ✅ Run Live Trading
while True:
live_pair_trading(pair1, pair2)
time.sleep(3600) # Run every hour