Overall Statistics
Total Trades
114
Average Win
2.31%
Average Loss
-1.12%
Compounding Annual Return
-4.016%
Drawdown
36.900%
Expectancy
-0.050
Net Profit
-6.474%
Sharpe Ratio
-0.003
Probabilistic Sharpe Ratio
7.942%
Loss Rate
69%
Win Rate
31%
Profit-Loss Ratio
2.07
Alpha
-0.035
Beta
0.153
Annual Standard Deviation
0.258
Annual Variance
0.066
Information Ratio
-0.67
Tracking Error
0.331
Treynor Ratio
-0.006
Total Fees
$162.22
Estimated Strategy Capacity
$670000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
import datetime

class SuperTrendTester(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)  # Start Date
        #self.SetEndDate(2021, 1, 15)  # End Date
        
        self.SetCash(100000)  # Set Strategy Cash
        
        self.symbol = 'SPY'
        self.res = Resolution.Daily
        self.length = 14
        self.multiplier = 1


        self.SetWarmUp(timedelta(days=30))
        
        self.equity = self.AddEquity(self.symbol, self.res)
        self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.closeWindow = []
        self.highWindow = []
        self.lowWindow = []
        self.atrWindow = []
        self.atrDown = []
        self.atrUp = []
        self.ha = self.HeikinAshi(self.symbol, self.res)
        self.atr = self.ATR(self.symbol, self.length, self.res)

        consolidator = TradeBarConsolidator(timedelta(minutes=1))
        consolidator.DataConsolidated += self.OnDataConsolidated
        self.SubscriptionManager.AddConsolidator(self.symbol, consolidator)
        
        
        self.optres = Resolution.Minute
        self.optmaxday = 30
        self.optminday = 5
        self.optminprice = 1
        self.optmaxprice = 1000

        self.putsheld = False
        self.callsheld = False
        self.noneheld = True

    def SellPuts(self):
        contracts = self.OptionChainProvider.GetOptionContractList(self.symbol, self.Time.date())
        if len(contracts) == 0: return
        filtered_contracts = self.InitialFilter(self.symbol, contracts, self.optminprice, self.optmaxprice, self.optminday, self.optmaxday)
        put = [x for x in filtered_contracts if x.ID.OptionRight == 1] 
        # sorted the contracts according to their expiration dates and choose the ATM options
        contracts = sorted(sorted(put, key = lambda x: abs((self.Securities[self.symbol].Price*0.9) - x.ID.StrikePrice)), 
                                        key = lambda x: x.ID.Date, reverse=True)
        self.contract = contracts[0]
        self.AddOptionContract(self.contract, self.optres)
        self.Debug(str(self.Time) + str(self.contract) + "Put")
        self.Buy(self.contract, 1)
        
    def SellCalls(self):
        contracts = self.OptionChainProvider.GetOptionContractList(self.symbol, self.Time.date())
        if len(contracts) == 0: return
        filtered_contracts = self.InitialFilter(self.symbol, contracts, self.optminprice, self.optmaxprice, self.optminday, self.optmaxday)
        call = [x for x in filtered_contracts if x.ID.OptionRight == 0] 
        # sorted the contracts according to their expiration dates and choose the ATM options
        contracts = sorted(sorted(call, key = lambda x: abs((self.Securities[self.symbol].Price*1.1) - x.ID.StrikePrice)), 
                                        key = lambda x: x.ID.Date, reverse=True)
        self.contract = contracts[1]
        self.AddOptionContract(self.contract, self.optres)
        self.Debug(str(self.Time) + str(self.contract) + "Call")
        self.Buy(self.contract, 1)

    def BuyPuts(self):
        contracts = self.OptionChainProvider.GetOptionContractList(self.symbol, self.Time.date())
        if len(contracts) == 0: return
        filtered_contracts = self.InitialFilter(self.symbol, contracts, self.optminprice, self.optmaxprice, self.optminday, self.optmaxday)
        put = [x for x in filtered_contracts if x.ID.OptionRight == 1] 
        # sorted the contracts according to their expiration dates and choose the ATM options
        contracts = sorted(sorted(put, key = lambda x: abs((self.Securities[self.symbol].Price*0.95) - x.ID.StrikePrice)), 
                                        key = lambda x: x.ID.Date, reverse=True)
        self.contract = contracts[0]
        self.AddOptionContract(self.contract, self.optres)
        self.Debug(str(self.Time) + str(self.contract) + "Put")
        self.Buy(self.contract, 1)
        
    def BuyCalls(self):
        contracts = self.OptionChainProvider.GetOptionContractList(self.symbol, self.Time.date())
        if len(contracts) == 0: return
        filtered_contracts = self.InitialFilter(self.symbol, contracts, self.optminprice, self.optmaxprice, self.optminday, self.optmaxday)
        call = [x for x in filtered_contracts if x.ID.OptionRight == 0] 
        # sorted the contracts according to their expiration dates and choose the ATM options
        contracts = sorted(sorted(call, key = lambda x: abs((self.Securities[self.symbol].Price*1.05) - x.ID.StrikePrice)), 
                                        key = lambda x: x.ID.Date, reverse=True)
        self.contract = contracts[1]
        self.AddOptionContract(self.contract, self.optres)
        self.Debug(str(self.Time) + str(self.contract) + "Call")
        self.Buy(self.contract, 1)
        
    def InitialFilter(self, underlyingsymbol, symbol_list, min_strike_rank, max_strike_rank, min_expiry, max_expiry):         
        if len(symbol_list) == 0 : return
        # fitler the contracts based on the expiry range
        contract_list = [i for i in symbol_list if min_expiry < (i.ID.Date.date() - self.Time.date()).days < max_expiry]
        # find the strike price of ATM option
        atm_strike = sorted(contract_list,
                            key = lambda x: abs(x.ID.StrikePrice - self.Securities[underlyingsymbol].Price))[0].ID.StrikePrice
        strike_list = sorted(set([i.ID.StrikePrice for i in contract_list]))
        # find the index of ATM strike in the sorted strike list
        atm_strike_rank = strike_list.index(atm_strike)
        try: 
            min_strike = strike_list[atm_strike_rank + min_strike_rank]
            max_strike = strike_list[atm_strike_rank + max_strike_rank]
        except:
            min_strike = strike_list[0]
            max_strike = strike_list[-1]
           
        filtered_contracts = [i for i in contract_list if i.ID.StrikePrice >= min_strike and i.ID.StrikePrice <= max_strike]

        return filtered_contracts

    def OnDataConsolidated(self, sender, data):
        if self.IsWarmingUp: return
        if not self.ha.IsReady: return
        if not self.atr.IsReady: return
    
        self.closeWindow.insert(0,self.ha.Close.Current.Value)
        if len(self.closeWindow) > 2:
            self.closeWindow.pop()
        self.highWindow.insert(0,self.ha.High.Current.Value)
        if len(self.highWindow) > 2:
            self.highWindow.pop()
        self.lowWindow.insert(0,self.ha.Low.Current.Value)
        if len(self.lowWindow) > 2:
            self.lowWindow.pop()
        self.atrWindow.insert(0,self.atr.Current.Value)
        if len(self.atrWindow) > 2:
            self.atrWindow.pop()

        self.Value = None
        
        try:
            hl = (self.highWindow[0] + self.lowWindow[0]) / 2
            hltwo = (self.highWindow[0] + self.lowWindow[0]) / 2
            hltwoPrev = (self.highWindow[1] + self.lowWindow[1]) / 2
    
            downNow = hltwo - self.multiplier * self.atrWindow[0]
            downPrev = hltwoPrev - self.multiplier * self.atrWindow[1]
            atrDown = max(downPrev, downNow) if self.closeWindow[1] > downPrev else downNow
            self.atrDown.insert(0,atrDown)
    
            upNow = hltwo + self.multiplier * self.atrWindow[0]
            upPrev = hltwoPrev + self.multiplier * self.atrWindow[1]
            atrUp = min(upNow, upPrev) if self.closeWindow[1] < upPrev else upNow
            self.atrUp.insert(0,atrUp)
        except:
            return
        
        try:
            if self.closeWindow[0] > self.atrUp[1]:
                self.Value = self.atrDown[0]
            elif self.closeWindow[0] < self.atrDown[1]:
                self.Value = self.atrUp[0]
            else:
                pass
            
            if self.Securities[self.symbol].Price < self.Value:
                self.Liquidate
                self.SetHoldings(self.symbol, -1)
                self.SellCalls()
                self.BuyPuts()
                    
            if self.Securities[self.symbol].Price > self.Value:
                self.Liquidate
                self.SetHoldings(self.symbol, 1)
                self.SellPuts()
                self.BuyCalls()

        except:
            return