Overall Statistics
Total Trades
199
Average Win
0.59%
Average Loss
-0.40%
Compounding Annual Return
297.761%
Drawdown
5.100%
Expectancy
0.350
Net Profit
14.606%
Sharpe Ratio
7.407
Probabilistic Sharpe Ratio
92.713%
Loss Rate
45%
Win Rate
55%
Profit-Loss Ratio
1.46
Alpha
0.935
Beta
1.833
Annual Standard Deviation
0.235
Annual Variance
0.055
Information Ratio
6.015
Tracking Error
0.216
Treynor Ratio
0.949
Total Fees
$199.00
Estimated Strategy Capacity
$57000000.00
Lowest Capacity Asset
FB V6OIPNZEM8V9
# 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

        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.Minute).Symbol
        
        self.Schedule.On(self.DateRules.EveryDay("SPY"),
                 self.TimeRules.AfterMarketOpen(self.SPY, 0),       
                 self.StartTrading)
                 
        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]
        
        
        # 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.Minute
        self.lastMonth = -1
        self.lastHour = -1
        self.allowTrades = True
        self.switchSafety = False
        
        self.Log("Initialized")
        
        self.isUptrend = []
        self.upTrendCount = 0
        
    def EndOfDay(self):
        if self.IsWarmingUp: return
        self.allowTrades = False
        self.Log('End Of Day Stop Trade')
        
    def StartTrading(self):
        self.switchSafety = False
        self.allowTrades = True

    def CoarseFilter(self, coarse):
        self.Log(f'CoarseFilter called on {self.Time}')
        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]
        
        self.Log("Picked number of symbols in universe: " + str(len(finalSymbols)))
        
        return [x.Symbol for x in finalSymbols]
        
    def OnData(self, data):
        if self.upTrendCount > 0:
            self.Debug(f"Placing trades at {self.Time}")
            self.size = len(self.isUptrend)
            for symbol in self.isUptrend:
                self.Debug("Buying: " + str(symbol))
                self.SetHoldings(symbol, 1/len(self.isUptrend), False)#, "SMA: " + str(self.data[symbol].Sma.Current.Value) + ". RSI: " + str(self.data[symbol].Rsi.Current.Value))
            self.upTrendCount = 0
            self.isUptrend = []
            
    def OnSecuritiesChanged(self, changes):
        for added in changes.AddedSecurities:
            symbol = added.Symbol
            if symbol in self.staticAssets: 
                symbolData = SymbolData(symbol, self)
                self.data[symbol] = symbolData
                continue
            added.MarginModel = PatternDayTradingMarginModel()
            
            self.Log(f'New Securities Added: {[security.Symbol.Value for security in changes.AddedSecurities]}')
            
            symbolData = SymbolData(symbol, self)
            self.data[symbol] = symbolData
            
        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, symbol, algorithm):
        self.Symbol = symbol
        self.algorithm = algorithm
        
        hourlyConsolidator = TradeBarConsolidator(self.Custom)
        hourlyConsolidator.DataConsolidated += self.OnDataConsolidated
        algorithm.SubscriptionManager.AddConsolidator(symbol, hourlyConsolidator)
        
        # Define our indicator
        self.Sma = SimpleMovingAverage(int(self.algorithm.moving_average))
        self.Rsi = RelativeStrengthIndex(14, MovingAverageType.Simple)
        # Register indicator to our consolidator
        algorithm.RegisterIndicator(symbol, self.Sma, hourlyConsolidator)
        algorithm.RegisterIndicator(symbol, self.Rsi, hourlyConsolidator)
        
        # Rolling window to hold 30 min bars
        self.barWindow = RollingWindow[TradeBar](2)
        
        
    # Store every thirty minute bar in rolling window
    def OnDataConsolidated(self, sender, bar):
        self.barWindow.Add(bar)

        if bar.Symbol in self.algorithm.staticAssets or not self.IsReady: return

        self.algorithm.Log(". VIX: " + str(self.algorithm.Securities[self.algorithm.vix].Price) + ". VIX SMA: " + str(self.algorithm.data[self.algorithm.vix].Sma.Current.Value) + ". VIX RSI: " + str(self.algorithm.data[self.algorithm.vix].Rsi.Current.Value))
        self.algorithm.Log(". Symbols: " + str(self.algorithm.data.keys))
        if self.algorithm.allowTrades == False: return
        #i moved place trades liquidate logic back to ondata
        if self.algorithm.Securities[self.algorithm.vix].Price > self.algorithm.data[self.algorithm.vix].Sma.Current.Value and self.algorithm.data[self.algorithm.vix].Rsi.Current.Value > 50:
            self.algorithm.Liquidate()
            self.algorithm.Log("ONDATA VIX OR QQQ CHECK FAILED LIQUIDATING")
        # if self.algorithm.lastHour == self.algorithm.Time.hour or self.algorithm.Time.hour < 10:
        #     return   
        self.algorithm.lastHour = self.algorithm.Time.hour
        self.PlaceTrades(bar)
                
    @property
    def IsReady(self):
        return self.Sma.IsReady and self.Rsi.IsReady and self.barWindow.IsReady
        
    def Custom(self, dt):
        period = timedelta(hours=1)
        start = dt.replace(minute=30)
        if start > dt:
            start -= period
        return CalendarInfo(start, period)
        
    def PlaceTrades(self, data):
        self.algorithm.Debug(str(self.Symbol.Value) + " at " + str(self.algorithm.Time) + ". RSI: " + str(self.Rsi.Current.Value) + ". SMA*T: " + str(self.Sma.Current.Value * (1 + self.algorithm.sma_tolerance)) + ". PRICE: " + str(self.algorithm.Securities[self.Symbol].Price))
        if self.algorithm.Securities[self.Symbol].Price > (self.Sma.Current.Value * (1 + self.algorithm.sma_tolerance)) and not self.Rsi.Current.Value < self.algorithm.rsi_value and not self.Rsi.Current.Value > self.algorithm.rsi_upper and not self.algorithm.Securities[self.algorithm.qqq].Price < self.algorithm.data[self.algorithm.qqq].Sma.Current.Value:
            self.algorithm.upTrendCount += 1
            self.algorithm.isUptrend.append(self.Symbol)