Overall Statistics
class MyAlgorithm(QCAlgorithm):
    
    def Initialize(self):
        
        self.SetStartDate(2019,1,1)
        self.SetCash(100000)
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
        tickers = ["SPY", "TLT", "BND"]
        # Creates empty dictionary to later hold daily Tradebar price rolling windows
        self.symbolData = {}
        for ticker in tickers:
            self.equity = self.AddEquity(ticker, Resolution.Hour)
            self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw) 
            self.equity.SetLeverage(1.0)
            symbol = self.equity.Symbol
            self.symbolData[symbol] = SymbolData(self, symbol)
        
        self.SetWarmUp(1000)
    
    def OnData(self, data):
        
        for symbol, value in self.symbolData.items():
            if not data.ContainsKey(symbol):
                return
            
            value.window4.Add(data[symbol])
            value.window3.Add(data[symbol])
            value.window2.Add(data[symbol])
            
            if self.IsWarmingUp or not value.IsReady:
                return
        
        for symbol, value in self.symbolData.items():
            
            shares_to_buy = 50
            if value.window3[0].Close > value.window3[1].Close > value.window3[2].Close:
                
                if value.stopbuys == None:
                    value.stopbuys = self.StopMarketOrder(symbol, shares_to_buy, round(value.window3[0].High, 2))
                else:
                    updateSettings = UpdateOrderFields()
                    updateSettings.StopPrice = round(value.window3[0].High, 2)
                    updateSettings.Quantity = round(shares_to_buy)
                    updateSettings.Tag = "Stop Buy Updated for {}".format(symbol)
                    value.stopbuys.Update(updateSettings)
            else:
                if self.Portfolio[symbol].Invested:
                    if value.stopsells == None:
                        value.stopsells = self.StopMarketOrder(symbol, -self.Portfolio[symbol].Quantity, round(value.window3[0].Low, 2))
                    else:
                        updateSettings = UpdateOrderFields()
                        updateSettings.StopPrice = round(value.window3[0].Low, 2)
                        updateSettings.Quantity = -self.Portfolio[symbol].Quantity
                        updateSettings.Tag = "Stop Sell Updated for {}".format(symbol)
                        value.stopsells.Update(updateSettings)
                    
            # Is this the time a stop buy was entered or updated ? Or is it the time it was filled??
            # How do we get the time a stopbuy was filled instead?
            # if (self.Time - self.stopbuys_time[symbol]).days == 0:
            #   do something
    
    def OnOrderEvent(self, orderEvent):
    # Event when the order is filled. Debug log the order fill. :OrderEvent:
        
        #if OrderEvent.FillQuantity == 0:
        if orderEvent.Status != OrderStatus.Filled:
            return

        fetched = self.Transactions.GetOrderById(orderEvent.OrderId)

        self.Debug("{} was filled. Symbol: {}. Quantity: {}. Direction: {}"
                    .format(str(fetched.Type),
                           str(orderEvent.Symbol),
                           str(orderEvent.FillQuantity),
                           str(orderEvent.Direction)))
                           
        if self.symbolData[orderEvent.Symbol].stopbuys is not None and self.symbolData[orderEvent.Symbol].stopbuys.OrderId == orderEvent.OrderId:
            self.symbolData[orderEvent.Symbol].stopbuys = None
            self.symbolData[orderEvent.Symbol].stopbuys_time = self.Time
            self.Debug(str(self.symbolData[orderEvent.Symbol].stopbuys_time))
            
            
        if self.symbolData[orderEvent.Symbol].stopsells is not None and self.symbolData[orderEvent.Symbol].stopsells.OrderId == orderEvent.OrderId:
            self.symbolData[orderEvent.Symbol].stopsells = None
            self.symbolData[orderEvent.Symbol].stopsells_time = self.Time
            self.Debug(str(self.symbolData[orderEvent.Symbol].stopsells_time))
            
class SymbolData:
    
    def __init__(self, algorithm, symbol):
        
        self.algorithm = algorithm
        self.symbol = symbol
        
         # Creates empty dictionary to later hold stop buy values
        self.stopbuys = None
        # Creates empty dictionary to later hold stop sell values
        self.stopsells = None
        # Creates empty dictionary to later hold time of buy breakout occurrence
        self.stopbuys_time = datetime.min
        # Creates empty dictionary to later hold time of sell breakout occurrence
        self.stopsells_time = datetime.min
        
        self.window4 = RollingWindow[TradeBar](4)
        self.window3 = RollingWindow[TradeBar](3)
        self.window2 = RollingWindow[TradeBar](2)
        
        consolidator_daily = TradeBarConsolidator(timedelta(days = 1))
        consolidator_daily.DataConsolidated += self.OnDailyData
        algorithm.SubscriptionManager.AddConsolidator(symbol, consolidator_daily)
        
    # Adding daily bar data to 3,2,1 etc.. rolling windows
    def OnDailyData(self, sender, bar):
        
        self.window4.Add(bar)
        self.window3.Add(bar)
        self.window2.Add(bar)
        
    @property
    def IsReady(self):
        return (self.window4.IsReady and self.window3.IsReady and self.window2.IsReady)