Overall Statistics
Total Trades
93
Average Win
0.65%
Average Loss
-0.19%
Compounding Annual Return
11.547%
Drawdown
2.700%
Expectancy
0.066
Net Profit
0.450%
Sharpe Ratio
0.528
Probabilistic Sharpe Ratio
45.448%
Loss Rate
76%
Win Rate
24%
Profit-Loss Ratio
3.46
Alpha
-0.496
Beta
0.849
Annual Standard Deviation
0.196
Annual Variance
0.038
Information Ratio
-3.169
Tracking Error
0.19
Treynor Ratio
0.122
Total Fees
$93.03
Estimated Strategy Capacity
$490000000.00
Lowest Capacity Asset
NFLX SEWJWLJNHZDX
# VXX version - best length 22 
# VIX hour version - best length 11
# VIX daily version - best length 22

import numpy as np
from datetime import datetime


class BasicTemplateAlgorithm(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2021, 10, 15)   
        #self.SetEndDate(2021, 10, 5) 
        self.SetEndDate(datetime.now())    
        self.SetCash(23400)           
        self.Settings.FreePortfolioValuePercentage = 0.00
        self.data = {}
        self.SetBenchmark("SPY")
        
        self.CoarseSize = 8
        #period = 10*21
        
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        self.trailing_stop = self.GetParameter("trailing-stop")
        self.trailing_stop = float(self.trailing_stop) if self.trailing_stop else 0.04
        
        self.moving_average = self.GetParameter("moving-average")
        self.moving_average = float(self.moving_average) if self.moving_average else 13
        
        self.rsi_value = self.GetParameter("rsi-value")
        self.rsi_value = float(self.rsi_value) if self.rsi_value else 55
        
        self.sma_tolerance = self.GetParameter("sma-tolerance")
        self.sma_tolerance = float(self.sma_tolerance) if self.sma_tolerance else 0.0063
        
        self.vix_length = self.GetParameter("vix-length")
        self.vix_length = float(self.vix_length) if self.vix_length else 8
        
        self.rsi_upper = self.GetParameter("rsi-upper")
        self.rsi_upper = float(self.rsi_upper) if self.rsi_upper else 65
        
        self.qqq_length = self.GetParameter("qqq-length")
        self.qqq_length = float(self.qqq_length) if self.qqq_length else 20
        
        
        self.screener_price = self.GetParameter("screener-price")
        self.screener_price = float(self.screener_price) if self.screener_price else 60
        
        self.AddRiskManagement(TrailingStopRiskManagementModel(self.trailing_stop))

        self.SPY = self.AddEquity("SPY", Resolution.Hour).Symbol
        
        # self.Schedule.On(self.DateRules.EveryDay("SPY"),
        #          self.TimeRules.BeforeMarketClose(self.SPY,2),       
        #          self.ForceTrade)
                 
        self.Schedule.On(self.DateRules.EveryDay("SPY"),
                 self.TimeRules.AfterMarketOpen(self.SPY, 0),       
                 self.StartTrading)
                 
        # self.Schedule.On(self.DateRules.EveryDay("SPY"),
        #          self.TimeRules.AfterMarketOpen(self.SPY, 2),       
        #          self.ForceTrade)
                 
                 
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose(self.SPY, 1), self.EndOfDay)
        

        self.vix = self.AddEquity("VXX", Resolution.Minute).Symbol
        self.qqq = self.AddEquity("QQQ", Resolution.Minute).Symbol
        self.staticAssets = [self.SPY, self.vix, self.qqq]
        self.vixSma = self.SMA(self.vix, self.vix_length, Resolution.Hour)
        self.vixRsi = self.RSI(self.vix, 14, Resolution.Hour)
        self.qqqSma = self.SMA(self.qqq, self.qqq_length, Resolution.Hour)
        
        vixHistory = self.History(self.vix, 14, Resolution.Hour)
        for tuple in vixHistory.loc[self.vix].itertuples():
            self.vixSma.Update(tuple.Index, tuple.close)
            self.vixRsi.Update(tuple.Index, tuple.close)
            

        qqqHistory = self.History(self.qqq, 20, Resolution.Hour)
        for tuple in qqqHistory.loc[self.qqq].itertuples():
            self.qqqSma.Update(tuple.Index, tuple.close)
            

            
        self.Log(". VIX SMA INITIALIZED: " + str(self.vixSma.Current.Value)) 
        
        self.AddUniverse(self.CoarseFilter)
        '''
        If VVIX > 50 day SMA, 
            Sell all current assets
            Switch to vxx and IEF (50/50)
        '''
        
        self.UniverseSettings.Resolution = Resolution.Hour
        self.lastMonth = -1
        self.lastHour = -1
        self.allowTrades = True
        self.switchSafety = False
        
        self.Log("Initialized")
        
    def EndOfDay(self):
        if self.IsWarmingUp: return
        self.Log('Checking End Of Day VIX & QQQ')
        if self.Securities[self.qqq].Price < self.qqqSma.Current.Value:
            #self.Liquidate()
            self.Log('Daily VIX & QQQ Check Failed')
            self.Log("Stopping Trading")
            self.allowTrades = False
        else:
            self.Log('Daily VIX & QQQ Check Passed')
            self.Log("Stopping Trading")
            self.allowTrades = False
        
    def StartTrading(self):
        self.switchSafety = False
        self.allowTrades = True
    
    # def ForceTrade(self):
    #     if self.IsWarmingUp: return
    #     self.Log("FINAL TRADE")
    #     # self.Log(". Symbols: " + str(self.data.keys))
    #     if self.allowTrades == False: return
    #     # i moved place trades liquidate logic back to ondata
    #     if self.Securities[self.vix].Price > self.vixSma.Current.Value and self.vixRsi.Current.Value > 50 or self.Securities[self.qqq].Price < self.qqqSma.Current.Value:
    #         self.Liquidate()
    #         self.Log("Force Trade VIX or QQQ Check Failed, Liquidating")
    #     if self.lastHour == self.Time.hour or self.Time.hour < 10:
    #         return   
    #     self.lastHour = self.Time.hour
    #     self.PlaceTrades(data)
    
    def CoarseFilter(self, coarse):
        #if self.lastMonth == self.Time.month:
        self.Log(f'CoarseFilter called on {self.Time}')
            #return Universe.Unchanged
        self.lastMonth = self.Time.month
        
        topStocksByVolume = sorted([x for x in coarse
            if x.Price > self.screener_price and x.Volume > 0 and x.HasFundamentalData and
                x.Symbol.Value not in ["GME", "AMC", "GOOG", "SPY"]], # Manually exclude companies
            key = lambda x: x.DollarVolume, reverse=True)[:self.CoarseSize]
            
        return [x.Symbol for x in topStocksByVolume]
    
    # def FineFilter(self, fine):
        
    #     finalSymbols = sorted([x for x in fine], key = lambda x: x.MarketCap, reverse = True)[:8]
        
    #     ''' Filter out high volatile stocks '''
    #     # preSdSymbols = [x for x in coarse]
    #     # history = self.History(preSdSymbols, 50, Resolution.Daily)
    #     # standardDeviations = {}
    #     # for symbol in preSdSymbols:
    #     #     sd = StandardDeviation(50)
    #     #     for tuple in history.loc[symbol].itertuples():
    #     #         sd.Update(tuple.Index, tuple.close)
    #     #     standardDeviations[symbol] = sd.Current.Value
    #     # finalSymbols = [x for x in preSdSymbols if standardDeviations[x] < 1000]#5]
        
        
        self.Log("Picked number of symbols in universe: " + str(len(finalSymbols)))
        
        return [x.Symbol for x in finalSymbols]
        
    def PlaceTrades(self, data):
        isUptrend = []
        for symbol, symbolData in self.data.items():
            self.Debug(str(symbol.Value) + " at " + str(self.Time) + ". RSI: " + str(symbolData.Rsi.Current.Value) + ". SMA*T: " + str(symbolData.Sma.Current.Value * (1 + self.sma_tolerance)) + ". PRICE: " + str(self.Securities[symbol].Price))
            if not data.ContainsKey(symbol):
                self.Log("Does not contain data for " + str(symbol))
                continue
            if self.Securities[symbol].Price > (symbolData.Sma.Current.Value * (1 + self.sma_tolerance)) and not symbolData.Rsi.Current.Value < self.rsi_value and not symbolData.Rsi.Current.Value > self.rsi_upper and not self.Securities[self.qqq].Price < self.qqqSma.Current.Value:
                isUptrend.append(symbol)
            # elif self.Portfolio[symbol].Invested:
            #     self.Log("Liquidating: " + str(symbol))
            #     self.Liquidate(symbol, "SMA: " + str(self.data[symbol].Sma.Current.Value) + ". RSI: " + str(self.data[symbol].Rsi.Current.Value))
        
        for symbol in isUptrend:
            self.Debug("Buying: " + str(symbol))
            self.SetHoldings(symbol, 1/len(isUptrend), False, "SMA: " + str(self.data[symbol].Sma.Current.Value) + ". RSI: " + str(self.data[symbol].Rsi.Current.Value))
        
    def OnData(self, data):
        if self.IsWarmingUp: return
        self.Log(". VIX: " + str(self.Securities[self.vix].Price) + ". VIX SMA: " + str(self.vixSma.Current.Value))
        self.Log(". Symbols: " + str(self.data.keys))
        if self.allowTrades == False: return
        #i moved place trades liquidate logic back to ondata
        if self.Securities[self.vix].Price > self.vixSma.Current.Value and self.vixRsi.Current.Value > 50:
            self.Liquidate()
            self.Log("ONDATA VIX OR QQQ CHECK FAILED LIQUIDATING")
        if self.lastHour == self.Time.hour or self.Time.hour < 10:
            return   
        self.lastHour = self.Time.hour
        self.PlaceTrades(data)
            
    def OnSecuritiesChanged(self, changes):
        for added in changes.AddedSecurities:
            symbol = added.Symbol
            if symbol in self.staticAssets: continue
            added.MarginModel = PatternDayTradingMarginModel()
            sma = self.SMA(symbol, self.moving_average, Resolution.Hour)
            rsi = self.RSI(symbol, 14, MovingAverageType.Simple, Resolution.Hour)
            # sto = self.STO(symbol)
            history = self.History(symbol, 15, Resolution.Hour)
            for tuple in history.loc[symbol].itertuples():
                sma.Update(tuple.Index, tuple.close)
                rsi.Update(tuple.Index, tuple.close)
                # sto.Update(TradeBar)
            
            self.Log(f'New Securities Added: {[security.Symbol.Value for security in changes.AddedSecurities]}')
            
            
            symbolData = SymbolData(sma, rsi)
            self.data[symbol] = symbolData
            
            #self.data[symbol].Sma.Current.Value
            
        for removed in changes.RemovedSecurities:
            symbol = removed.Symbol
            self.Liquidate(symbol, tag="Symbol Removed From Universe")
            self.data.pop(symbol, None)
            self.Log(f'Securities Removed{[security.Symbol.Value for security in changes.RemovedSecurities]}')
            
class SymbolData:
    def __init__(self, sma, rsi):
        self.Sma = sma
        self.Rsi = rsi
        # self.Sto = sto
            
            
# FOR JOVAD: Need 2 versions 1 with a universe that only trades top 5 market cap stocks and 1 with Dropbox spreadsheet
# Need to add STO, RSI, AND HAVE 3 SMA HERE (fast, medium, slow)
# Need minute support with bars, 60 minutes consolidated for now
# Need to understand how I can add more indicators if I want - struggled with warming up onData. Tried adding more indicators using for symbol, sma in self.data.items(): but it didn't work

# FOR JOVAD NEW SESSION: HOW TO PICK STOCKS WITH HIGHEST 


# FOR JOVAD 6/29- NEW STRATEGY TO TEST: IF RSI PREVIOUS CLOSE < RSI CURRENT CLOSE ENTER TRADE ELSE EXIT TRADE, TRAILING STOP 5% HOURLY CHART
# STOCK PICKING HOUR SMA NEEDS TO BE ADJUSTED TO CRYPTO AS WELL WITHOUT UNIVERSE SELECTION + FIX EXCHANGE IS CLOSED ERROR
# ALSO FOR STOCK PICKING - WE NEED TO ADD FUNCTIONALITY TO REMOVE MANUALLY SELECTED TICKERS LIKE GOOG AND GME
# ASK FOR ADVISE IF WE CAN FIND A LOGIC TO EXCLUDE DANGEROUS TICKERS LIKE AMC AND GME