Overall Statistics
Total Trades
396
Average Win
0.17%
Average Loss
-0.06%
Compounding Annual Return
-2.515%
Drawdown
3.600%
Expectancy
0.003
Net Profit
-1.878%
Sharpe Ratio
-0.916
Sortino Ratio
-1.013
Probabilistic Sharpe Ratio
8.147%
Loss Rate
74%
Win Rate
26%
Profit-Loss Ratio
2.89
Alpha
-0.043
Beta
-0.035
Annual Standard Deviation
0.048
Annual Variance
0.002
Information Ratio
-0.529
Tracking Error
0.149
Treynor Ratio
1.261
Total Fees
$456.50
Estimated Strategy Capacity
$3900000.00
Lowest Capacity Asset
PEP R735QTJ8XC9X
Portfolio Turnover
4.55%
from AlgorithmImports import *
from datetime import timedelta, datetime

class SMAPairsTrading(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018, 7, 1)   
        self.SetEndDate(2019, 3, 31)
        self.SetCash(100000)

        self.ko = self.AddEquity("KO", Resolution.Hour, dataNormalizationMode=DataNormalizationMode.Raw).Symbol
        self.pep = self.AddEquity("PEP", Resolution.Hour, dataNormalizationMode=DataNormalizationMode.Raw).Symbol

        self.spreadMean = SimpleMovingAverage(500)
        self.spreadStd = StandardDeviation(500)
        self.period = timedelta(hours=2)

        #1. Call for 500 bars of history data for each symbol in the pair and save to the variable history
        history = self.History[TradeBar]([self.ko, self.pep], 500)
        #2. Iterate through the history and update the mean and standard deviation with historical data 
        for bars in history:
            time, spread = self.GetSpread(bars)
            if spread:
                self.UpdateIndicators(time, spread)

    def GetSpread(self, bars: TradeBars):
        pep_bar = bars.get(self.pep)
        ko_bar = bars.get(self.ko)
        if not (pep_bar and ko_bar):
            return None, None
        return pep_bar.EndTime, abs(pep_bar.Close - ko_bar.Close)

    def UpdateIndicators(self, time, spread):
        self.spreadMean.Update(time, spread)
        self.spreadStd.Update(time, spread)

    def OnData(self, data):
        _, spread = self.GetSpread(data.Bars)
        if not spread:
            return
        self.UpdateIndicators(self.Time, spread)

        weight = .5
        spreadMean = self.spreadMean.Current.Value
        upperthreshold = spreadMean + self.spreadStd.Current.Value
        lowerthreshold = spreadMean - self.spreadStd.Current.Value

        # Close positions if reverses to the mean
        ko_holdings = self.Portfolio[self.ko]
        if ko_holdings.IsLong and spread < spreadMean:
            self.PlotSpread(spread, lowerthreshold, spreadMean, upperthreshold)
            self.Liquidate()
            return

        if ko_holdings.IsShort and spread > spreadMean:
            self.Liquidate()
            self.PlotSpread(spread, lowerthreshold, spreadMean, upperthreshold)
            return

        if spread > upperthreshold:
            self.PlotSpread(spread, lowerthreshold, spreadMean, upperthreshold)
            self.SetHoldings([
                PortfolioTarget(self.ko, weight),
                PortfolioTarget(self.pep, -weight)
            ])

        if spread < lowerthreshold:
            self.PlotSpread(spread, lowerthreshold, spreadMean, upperthreshold)
            self.SetHoldings([
                PortfolioTarget(self.ko, -weight),
                PortfolioTarget(self.pep, weight)
            ])

    def PlotSpread(self, spread, lowerthreshold, mean, upperthreshold):
        self.Plot('Spread', 'Current', spread)
        self.Plot('Spread', 'Mean', mean)
        self.Plot('Spread', 'Lower', lowerthreshold)
        self.Plot('Spread', 'Upper', upperthreshold)