| Overall Statistics |
|
Total Orders 34 Average Win 2.51% Average Loss -3.39% Compounding Annual Return 2.272% Drawdown 12.900% Expectancy 0.088 Start Equity 1000000 End Equity 1022691.39 Net Profit 2.269% Sharpe Ratio -0.361 Sortino Ratio -0.284 Probabilistic Sharpe Ratio 17.130% Loss Rate 38% Win Rate 62% Profit-Loss Ratio 0.74 Alpha -0.053 Beta 0.106 Annual Standard Deviation 0.096 Annual Variance 0.009 Information Ratio -1.552 Tracking Error 0.132 Treynor Ratio -0.326 Total Fees $319.09 Estimated Strategy Capacity $700000000.00 Lowest Capacity Asset MSFT R735QTJ8XC9X Portfolio Turnover 4.75% |
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 = ("MSFT", "NVDA")
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