Overall Statistics
class OptimizedResistanceCircuit(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2019, 1, 29)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        
        # choose the equities with top 100 DollarVolumn as universe
        self.AddUniverse(self.Universe.DollarVolume.Top(100))
        
        # self-designed alpha model
        self.SetAlpha(WeeklyHighsLowsAlphaModel())
        
        # use existing models for portfolio construction, execution, and risk management
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
        self.SetExecution(ImmediateExecutionModel())
        self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(0.01))


class WeeklyHighsLowsAlphaModel(AlphaModel):
    
    def __init__(self, lookback = 360, insightPeriod = 10, resolution = Resolution.Daily):
        self.resolution = resolution
        self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), insightPeriod)
        self.lookback = lookback
        self.symbolData = {}
        
        resolutionString = Extensions.GetEnumString(resolution, Resolution)
        self.Name = "{}({}, {})".format(self.__class__.__name__, resolution, insightPeriod)
    
    def Update(self, algorithm, data):
        '''Updates this alpha model with the latest data from the algorithm.
        This is called each time the algorithm receives data for subscribed securities
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        insights = []
        for symbol, symbolData in self.symbolData.items():
            if symbolData.Max.IsReady and symbolData.Min.IsReady:
                
                if algorithm.Securities[symbol].Price > symbolData.Max.Current.Value:
                    insights.append(Insight.Price(symbol, self.insightPeriod, InsightDirection.Up))
                    
                if algorithm.Securities[symbol].Price < symbolData.Min.Current.Value:
                    insights.append(Insight.Price(symbol, self.insightPeriod, InsightDirection.Down))
        
        return insights
    
    def OnSecuritiesChanged(self, algorithm, changes):
        '''Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''
        for added in changes.AddedSecurities:
            symbolData = self.symbolData.get(added.Symbol)
            if symbolData is None:
                # create MIN/MAX indicators
                symbolData = SymbolData(algorithm, added.Symbol, self.lookback)
                self.symbolData[added.Symbol] = symbolData
            else:
                # a security that was already initialized was re-added, reset the indicators
                symbolData.Max.Reset()
                symbolData.Min.Reset()
    

class SymbolData:
    def __init__(self, algorithm, symbol, lookback):
        self.symbol = symbol
        self.Max = Maximum(lookback)
        self.Min = Minimum(lookback)
        ### or 
        # self.Max = algorithm.MAX(symbol, lookback, Resolution.Daily)
        # self.Min = algorithm.MIN(symbol, lookback, Resolution.Daily)
        self.window = RollingWindow[TradeBar](lookback)
        algorithm.Consolidate(symbol, CalendarType.Weekly, self.CalendarTradeBarHandle)
    
    def CalendarTradeBarHandle(self, bar):
        self.Max.Update(bar.EndTime, bar.Close)
        self.Min.Update(bar.EndTime, bar.Close)
        self.window.Add(bar)