Created with Highcharts 12.1.2EquityJan 2023Jan…Feb 2023Mar 2023Apr 2023May 2023Jun 2023Jul 2023Aug 2023Sep 2023Oct 2023Nov 2023Dec 2023Jan 2024100k150k200k250k-50-250012-4-2020250M500M0500k1,000k0100200
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))