| Overall Statistics |
|
Total Orders 12 Average Win 1.68% Average Loss -2.18% Compounding Annual Return -23.767% Drawdown 27.300% Expectancy -0.409 Start Equity 200000 End Equity 155944.35 Net Profit -22.028% Sharpe Ratio -1.51 Sortino Ratio -0.985 Probabilistic Sharpe Ratio 0.456% Loss Rate 67% Win Rate 33% Profit-Loss Ratio 0.77 Alpha -0.184 Beta -0.269 Annual Standard Deviation 0.142 Annual Variance 0.02 Information Ratio -1.655 Tracking Error 0.198 Treynor Ratio 0.796 Total Fees $30.19 Estimated Strategy Capacity $200000000.00 Lowest Capacity Asset MSFT R735QTJ8XC9X Portfolio Turnover 1.85% |
#region imports
from AlgorithmImports import *
#endregion
# Your New Python File
#region imports
from AlgorithmImports import *
#endregion
import numpy as np
import pandas as pd
from datetime import timedelta, datetime
import math
import statsmodels.api as sm
from statsmodels.tsa.stattools import coint, adfuller
'''
This is an example of implementation of the pairs trading strategy discussed in lecture8
'''
class PairsTradingAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2023, 1, 1)
self.SetEndDate(2023,12,1)
self.SetCash(200000)
self.enter = 2 # Set the enter threshold
self.exit = 0 # Set the exit threshold
self.lookback = 90 # Set the loockback period 90 days
self.pairs =['GOOG','MSFT']
self.symbols =[]
for ticker in self.pairs:
self.AddEquity(ticker, Resolution.Daily)
self.symbols.append(self.Symbol(ticker))
self.sym1 = self.symbols[0]
self.sym2 = self.symbols[1]
def stats(self, symbols):
#Use Statsmodels package to compute linear regression and ADF statistics
self.df = self.History(symbols, self.lookback)
self.dg = self.df["open"].unstack(level=0)
#self.Debug(self.dg)
ticker1= str(symbols[0])
ticker2= str(symbols[1])
Y = self.dg[ticker1].apply(lambda x: math.log(x))
X = self.dg[ticker2].apply(lambda x: math.log(x))
X = sm.add_constant(X)
model = sm.OLS(Y,X)
results = model.fit()
sigma = math.sqrt(results.mse_resid) # standard deviation of the residual
slope = results.params[1]
intercept = results.params[0]
res = results.resid #regression residual mean of res =0 by definition
zscore = res/sigma
adf = adfuller (res)
return [adf, zscore, slope]
def OnData(self, data):
self.IsInvested = (self.Portfolio[self.sym1].Invested) or (self.Portfolio[self.sym2].Invested)
self.ShortSpread = self.Portfolio[self.sym1].IsShort
self.LongSpread = self.Portfolio[self.sym1].IsLong
stats = self.stats([self.sym1, self.sym2])
self.beta = stats[2]
zscore= stats[1][-1]
self.wt1 = 1/(1+self.beta)
self.wt2 = self.beta/(1+self.beta)
self.pos1 = self.Portfolio[self.sym1].Quantity
self.px1 = self.Portfolio[self.sym1].Price
self.pos2 = self.Portfolio[self.sym2].Quantity
self.px2 = self.Portfolio[self.sym2].Price
self.equity =self.Portfolio.TotalPortfolioValue
if self.IsInvested:
if self.ShortSpread and zscore <= self.exit or \
self.LongSpread and zscore >= self.exit:
self.Liquidate()
else:
if zscore > self.enter:
#short spread
#rememebr SetHoldings take a Symbol as its first variable.
self.SetHoldings(self.sym1, -self.wt1)
self.SetHoldings(self.sym2, self.wt2)
if zscore < - self.enter:
#long the spread
self.SetHoldings(self.sym1, self.wt1)
self.SetHoldings(self.sym2, -self.wt2)
self.pos1 = self.Portfolio[self.sym1].Quantity
self.pos2 = self.Portfolio[self.sym2].Quantity
self.Debug("sym1 " + str(self.sym1.Value) + " /w "+ str(self.pos1) + " sym2 " +str(self.sym2.Value) + " /w "+str(self.pos2))
self.Debug("Total Account Equity: "+ str( self.equity) + "Total Marginused: "+ str( self.Portfolio.TotalMarginUsed))