| Overall Statistics |
|
Total Orders 32 Average Win 1.44% Average Loss -2.54% Compounding Annual Return -12.620% Drawdown 18.100% Expectancy -0.314 Start Equity 1000000 End Equity 873943.39 Net Profit -12.606% Sharpe Ratio -1.492 Sortino Ratio -1.467 Probabilistic Sharpe Ratio 0.803% Loss Rate 56% Win Rate 44% Profit-Loss Ratio 0.57 Alpha -0.12 Beta -0.119 Annual Standard Deviation 0.094 Annual Variance 0.009 Information Ratio -2.111 Tracking Error 0.147 Treynor Ratio 1.176 Total Fees $291.86 Estimated Strategy Capacity $910000000.00 Lowest Capacity Asset AAPL R735QTJ8XC9X Portfolio Turnover 4.42% |
from AlgorithmImports import *
from statsmodels.tsa.stattools import adfuller
import numpy as np
class PairsTradingStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2023, 12, 1)
self.SetEndDate(2024, 12, 1)
self.SetCash(1000000) # $1M starting capital
self.pair = ("AAPL", "MSFT")
self.symbol1 = self.AddEquity(self.pair[0], Resolution.Daily).Symbol
self.symbol2 = self.AddEquity(self.pair[1], Resolution.Daily).Symbol
self.lookback = 20
self.entry_threshold = 2
self.exit_threshold = 0
self.position = 0
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(15, 45), self.TradeLogic)
def TradeLogic(self):
history = self.History([self.symbol1, self.symbol2], self.lookback, Resolution.Daily)
if history.empty:
return
closes = history['close'].unstack(level=0)
spread = np.log(closes[self.symbol1]) - np.log(closes[self.symbol2])
mean = spread.mean()
std = spread.std()
zscore = (spread.iloc[-1] - mean) / std
weight1 = 1 / (1 + self.GetBeta())
weight2 = self.GetBeta() / (1 + self.GetBeta())
if self.position == 0:
if zscore > self.entry_threshold:
self.SetHoldings(self.symbol1, -weight1)
self.SetHoldings(self.symbol2, weight2)
self.position = -1
elif zscore < -self.entry_threshold:
self.SetHoldings(self.symbol1, weight1)
self.SetHoldings(self.symbol2, -weight2)
self.position = 1
elif self.position == 1 and zscore >= self.exit_threshold:
self.Liquidate()
self.position = 0
elif self.position == -1 and zscore <= -self.exit_threshold:
self.Liquidate()
self.position = 0
def GetBeta(self):
history = self.History([self.symbol1, self.symbol2], self.lookback, Resolution.Daily)
returns = history['close'].unstack(level=0).pct_change().dropna()
if returns.empty:
return 1
beta = np.cov(returns[self.symbol1], returns[self.symbol2])[0, 1] / np.var(returns[self.symbol2])
return beta