Overall Statistics
class OptimizedVerticalChamber(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2019, 10, 28)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        self.AddUniverse(self.SelectCoarse)
        self.symbols = {}

    def SelectCoarse(self, coarse):
        sor = sorted(coarse, key=lambda c:c.DollarVolume, reverse=True)
        sel = sor[:3]
        for c in sel:
            symbol = c.Symbol
            if symbol not in self.symbols:
                self.symbols[symbol] = SymbolData(symbol)
        
        for c in coarse:
            symbol = c.Symbol
            if symbol in self.symbols:
                self.symbols[symbol].update(c.EndTime, c.AdjustedPrice)
                
        return [c.Symbol for c in sel]
        
    def OnData(self, data):
        
        for symbol, sd in self.symbols.items():
            if sd.IsReady:
                self.Debug(f"{sd.priceWin[0]}, {sd.bbWin[0]}")


class SymbolData():
    def __init__(self, symbol):
        self.symbol = symbol
        self.bb_band = BollingerBands(100, 2, MovingAverageType.Simple)
        self.priceWin = RollingWindow[float](2)
        self.bbWin = RollingWindow[float](2)
        self.crossed = False


    def update(self, time, price):
        self.priceWin.Add(price)
        self.bb_band.Update(time, price)
        if self.bb_band.IsReady:
            upper_band_value = self.bb_band.UpperBand.Current.Value
            self.bbWin.Add(upper_band_value)
        if self.IsReady:
            self.crossed = self.priceWin[1] < self.bbWin[1] and self.priceWin[0] > self.bbWin[1]
    
    @property
    def IsReady(self):
        return self.bb_band.IsReady and self.bbWin.IsReady and self.priceWin.IsReady