Overall Statistics
Total Trades
552
Average Win
0.66%
Average Loss
-0.48%
Compounding Annual Return
65.947%
Drawdown
33.700%
Expectancy
0.386
Net Profit
66.177%
Sharpe Ratio
1.885
Probabilistic Sharpe Ratio
69.412%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
1.39
Alpha
0.214
Beta
0.837
Annual Standard Deviation
0.316
Annual Variance
0.1
Information Ratio
0.819
Tracking Error
0.171
Treynor Ratio
0.711
Total Fees
$561.23
# (c) Blash18

from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel   # Help us Create.Symbols for manual universes
from config import parameters                                                               # List of parameters defining the algorithm
import universes                                                                            # Universe Selection classes
from universes import Uparam, ManualSymbolsLists, SymbolData
from operator import itemgetter

class BlashAlgorithm(QCAlgorithm):

    def Initialize(self):

        
        # Optimization Parameters
        parameters["GainSellTrigger"] = float(self.GetParameter('GainSell'))
        parameters["LossSellTrigger"] = float(self.GetParameter('LossSell'))
        parameters["SellCooldown"] = int(self.GetParameter('SellCooldown'))
        parameters["NumberLaggrardToSell"] = int(self.GetParameter('SellLaggards'))
        parameters["SMABuyGapTrigger"] = float(self.GetParameter('SMAGap'))
        parameters["PPOBuyGapTrigger"] =  float(self.GetParameter('PPOGap'))

        
        # ------------------------------------ Initialization & setup ----------------------------------------
        
        self.SetTimeZone("America/New_York")
    
        # Back testing only
        self.SetStartDate(parameters["start_date_year"], parameters["start_date_month"], parameters["start_date_day"])  
        self.SetEndDate(parameters["end_date_year"], parameters["end_date_month"], parameters["end_date_day"])  
        
        self.AddEquity(parameters["Benchmark"],Resolution.Daily)
        self.SetBenchmark(parameters["Benchmark"]) 
        
        # In live trading actual cash will be set at the Brokerage level 
        self.SetCash(parameters["StartCash"])
        
        
        # ------------------------------------- Universe creation --------------------------------------------
    
        # self.UniverseSettings.DataNormalizationMode=DataNormalizationMode.Raw   ??? 
        self.UniverseSettings.Resolution = Resolution.Hour  # Universe list of symbols is refreshed Monthly but symbol data is updated Hourly 
        self.UniverseSettings.Leverage = 1
       
        if Uparam["SelectedUniverse"] == "":
            self.AddUniverseSelection(universes.QC500UniverseSelectionModel()) # Auto seleced Universe
        else: # Manual selected Universes
            SymbolsList = ManualSymbolsLists[Uparam["SelectedUniverse"]]
            finalSymbols = []
            for ticker in SymbolsList:
                symbol = Symbol.Create(ticker, SecurityType.Equity, Market.USA)    # ??? What if Market is not USA?
                finalSymbols.append(symbol)
            self.AddUniverseSelection(ManualUniverseSelectionModel(finalSymbols))
        
        # My "Internal" Universe.
        # Contains a list of Symbols and Indicators. ??? History? Fundamentals?
        self.TickerTable = {}
        # Actual max numbers of tickers to invest in
        self.MaxTickers = max(parameters["MaxNumberOfTickersToInvest"],1) # ???? 1 is placeholder should be len()
        # Free slots to invest in       
        self.ToInvest = self.MaxTickers
        # Track lost buys because not enough Cash in Portfolio to buy
        self.LostBuys=0
        # Track our buying ticketsize
        self.TicketSizeValue = parameters["StartCash"] / (self.MaxTickers+parameters["CashBufferNumber"])  #??? need to redibe the cash
        # track dates of last time a symbol was sold. Enables us to implement SellCooldown
        self.SoldSymbolDates = {}
    
    # ---------------------------------------- New Benchmark plot --------------------------------------------
        # Variable to hold the last calculated benchmark value
        self.lastBenchmarkValue = None
        # Our inital benchmark value scaled to match our portfolio
        self.BenchmarkPerformance = self.Portfolio.TotalPortfolioValue

        # a try to create a QQQ sentiment indicator
        self.MyQQQIndicator = 1
        self.QQQrsi = self.RSI("QQQ", 7)
    
        self.SetWarmUp(timedelta(days=30))
       
       # ----------------------------------------- Schedules ---------------------------------------------
       
        if parameters["NumberLaggrardToSell"] > 0:
            self.Schedule.On(self.DateRules.MonthStart(), self.TimeRules.At(9, 0), self.SellLaggardsFunction) # parameters["SellLaggardsFrequency"]
        self.Schedule.On(self.DateRules.EveryDay("QQQ"), self.TimeRules.At(8, 0), self.CalculateMyQQQIndicator)
        self.Schedule.On(self.DateRules.Every(parameters["SellFrequency"]), self.TimeRules.At(10, 0), self.SellFunction) # ??? Market might be closed
        self.Schedule.On(self.DateRules.Every(parameters["BuyFrequency"]), self.TimeRules.At(11, 0), self.BuyFunction)   # ??? Market might be closed
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(12,0), self.PlotStuff)
        
       # ----------------------------------------- Opening Log--------------------------------------------
        
        self.Debug(f"Blash18: {self.Time}")
        
        if self.LiveMode:
            self.Debug("Trading Live!")
        
        self.Log(f"Init:{self.Time} Cash:{self.Portfolio.Cash}")
        self.Log(f'Universe:{Uparam["SelectedUniverse"]} MaxInvested:{self.MaxTickers}')
        self.Log(f'SMAgap:{parameters["SMABuyGapTrigger"]} SellGain:{parameters["GainSellTrigger"]} SellLoss:{parameters["LossSellTrigger"]}') # ??? Add all
        self.Log("---")
    
    def CalculateMyQQQIndicator(self):
        
       
        if self.QQQrsi.Current.Value > 30:
            self.MyQQQIndicator = 1
        elif self.QQQrsi.Current.Value <= 30:
            self.MyQQQIndicator = 0
        
    # ------------------------------------------------------------------------------------------------------   
    # ---------------------------------------------- Buy loop ----------------------------------------------
    
    def BuyFunction(self):
        
        # if self.MyQQQIndicator == 1:
            
        # Calculate how many investments (Equities) we currently have 
        investedPortfolio = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        numberOfSymbolsInvested = len(investedPortfolio)
        
        # Find how many open positions to be invested before we reach our Max limit
        self.ToInvest = self.MaxTickers - numberOfSymbolsInvested 
        
        if self.ToInvest == 0:
            return
        
        # For Graph display
        self.TicketSizeValue = self.Portfolio.TotalPortfolioValue / (self.MaxTickers+parameters["CashBufferNumber"])
        
        # ---- check for Candidates
        self.SymbolCandidateList = self.CheckCandidates()
        # ---- sort candidates
        self.RankedSymbolCandidateList = self.RankCandidates(self.SymbolCandidateList)
        # ---- buy best candidates
        self.BuyCandidates(self.RankedSymbolCandidateList)
        
    # ------------------------------------------- Check Candidates ------------------------------------------
    
    def CheckCandidates(self):
        temp_candidate_list = []
        
        for security, symbolData in self.TickerTable.items():
            symbol = security.Symbol
            
            if not self.Portfolio[symbol].Invested or parameters["AllowMultipleBuysBeforeSell"]:
                
                # Check SellCooldown
                if symbol in self.SoldSymbolDates:
                    last_sale_time = self.SoldSymbolDates[symbol]
                    current_time = self.Time
                    if (current_time - last_sale_time).days < parameters["SellCooldown"]:
                        continue
                    
                # Indicators
                RSI = symbolData.rsi.Current.Value
                SMA = symbolData.sma.Current.Value
                PPO = symbolData.ppo.Current.Value
                BIG_Window = symbolData.smaLong.Current.Value
                SMALL_Window = symbolData.smaShort.Current.Value
                Slope = symbolData.rcShort.Slope.Current.Value
                # self.Debug(symbolData.smaShort.Current.Value)
                
                # Fundamentals
                # Bring Fundanetal data here
                
                # Strategy point: The algo for Candidate selection (based on indicators and fundamentals)
                if ( RSI <= parameters["RSIBuyGapTrigger"]) and (PPO <= parameters["PPOBuyGapTrigger"]) and (SMA > self.Securities[symbol].Price * parameters["SMABuyGapTrigger"]):
                ###if  BIG_Window > SMALL_Window and Slope > 0:
                    temp_candidate_list.append(security)
                
        return temp_candidate_list

    # ---------------------------------------- Rank (sort) Candidates ---------------------------------------

    def RankCandidates(self, SymbolCandidateList):
        temp_list = []
        
        for security in SymbolCandidateList:
            temp_list.append([security,
            self.TickerTable[security].rsi.Current.Value,
            self.TickerTable[security].sma.Current.Value,
            self.TickerTable[security].ppo.Current.Value])
        
        # Strategy point: The algo for Candidate sorting (based on indicators and fundamentals we collected)
        if parameters["SortCandidatesby"] == "RSI":
            RankedSymbolCandidateList = sorted(temp_list, key=itemgetter(1))[:self.ToInvest] # sort by RSI and return :ToInvest best results
        elif parameters["SortCandidatesby"] == "PPO":
            RankedSymbolCandidateList = sorted(temp_list, key=itemgetter(3))[:self.ToInvest] # sort by RSI and return :ToInvest best results
        elif parameters["SortCandidatesby"] == "PPO&RSI":
            RankedSymbolCandidateList = sorted(temp_list, key=itemgetter(3,1))[:self.ToInvest] # sort by RSI and return :ToInvest best results
        elif parameters["SortCandidatesby"] == "RSI&PPO":
            RankedSymbolCandidateList = sorted(temp_list, key=itemgetter(1,3))[:self.ToInvest] # sort by RSI and return :ToInvest best results                
        else:
            RankedSymbolCandidateList = temp_list[:self.ToInvest]
        
        return RankedSymbolCandidateList

    # ---------------------------------------- Buy winning Candidates ---------------------------------------

    def BuyCandidates(self, RankedSymbolCandidateList):        
        for record in self.RankedSymbolCandidateList:
            security = record[0]
            symbol = security.Symbol
    
            # collect for taagging Order only
            RSI = record[1]
            SMA = record[2]
            PPO = record[3]
            
            cash = self.Portfolio.MarginRemaining                                   # How much cash we have free
            portfolio_percentage = 1 / self.MaxTickers                              # What percent of the Portfolio we want to allocate
            quantity = self.CalculateOrderQuantity(symbol, portfolio_percentage)    # What is the quantity we need to buy
            close_price = self.Securities[symbol].Price                             # What is the current equity price
            
            cost_of_trade = close_price * quantity                                  # What is the cost of the expected order
            adj_cost_of_trade = cost_of_trade * (1 + parameters["CashTradeBuffer"]) # Cost or order adjusted to include a buffer
            
            if cash > adj_cost_of_trade:
                
                #self.Debug(f"margin: {cash} - cost: {adj_cost_of_trade}")
                #self.Debug(len([symbol for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]))
                #self.Debug([(symbol.Value, self.Portfolio[symbol].HoldingsValue) for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested])
                
                self.SetHoldings(symbol, 1/ (self.MaxTickers+parameters["CashBufferNumber"]), False, "Buy: "+
                str(symbol)+ " SMA:"+str(round(SMA,2))+" RSI:"+str(round(RSI,2))+" PPO:"+str(round(PPO,2))+" Cash B4:"+str(round(self.Portfolio.Cash,2))+" ToInvest:"+str(self.ToInvest))
                
            else:
                self.LostBuys+=1    
        
    # ----------------------------------------- Sell loop -----------------------------------------
    def SellFunction(self):
        
        #if self.MyQQQIndicator == 1:
            
        investedPortfolio = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        numberOfSymbolsInvested = len(investedPortfolio)
        
        for security, symbolData in self.TickerTable.items():
            symbol = security.Symbol
            if self.Portfolio[symbol].Invested:
                if self.Portfolio[symbol].UnrealizedProfitPercent >= parameters["GainSellTrigger"]:
                    self.Liquidate(symbol, "Profit: "+str(symbol)+":"+str(round(self.Portfolio[symbol].UnrealizedProfitPercent*100,2))+"%")
                    self.SoldSymbolDates[symbol]=self.Time
                elif self.Portfolio[symbol].UnrealizedProfitPercent <= -1 * parameters["LossSellTrigger"]:
                    self.Liquidate(symbol, "Loss: "+str(symbol)+":"+str(round(self.Portfolio[symbol].UnrealizedProfitPercent*100,2))+"%")
                    self.SoldSymbolDates[symbol]=self.Time
                
        
    # ------------------------------------- Sell Laggards loop -------------------------------------
    def SellLaggardsFunction(self):
        
        #self.Debug(len([symbol for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]))
        #self.Debug([(symbol.Value, self.Portfolio[symbol].HoldingsValue) for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested])
        
       
        #investedPortfolio = [[x.Key.Value,x.Value.UnrealizedProfitPercent] for x in self.Portfolio if x.Value.Invested]
        investedPortfolio = [[symbol,self.Portfolio[symbol].UnrealizedProfitPercent] for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]
        numberOfSymbolsInvested = len(investedPortfolio)
        
        if numberOfSymbolsInvested > 0:
            sortedInvestedPortfolio = sorted(investedPortfolio, key=itemgetter(1), reverse = False)
            
            # How many we need to clear?
            toLiquidate = min(self.MaxTickers-self.ToInvest-1, parameters["NumberLaggrardToSell"])

            if toLiquidate > 0:
                for i in range(0,toLiquidate):
                    if i<numberOfSymbolsInvested:
                        symbol= sortedInvestedPortfolio[i][0]
                        self.Liquidate(symbol, "Laggard: "+str(symbol)+" Unrealized:"+str(round(self.Portfolio[symbol].UnrealizedProfitPercent*100,2))+"%")
                        self.SoldSymbolDates[symbol]=self.Time
    
    
    # -------------------------------------- Plots and Graphs --------------------------------------
    def PlotStuff(self):
        self.Plot('Portfolio Cash', 'Cash', self.Portfolio.Cash)
        self.Plot('Lost buys', 'Lost', self.LostBuys)
        self.Plot('Margin Remaining', 'MarginRemaining', self.Portfolio.MarginRemaining)
        self.Plot('Ticket Size', 'Size', self.TicketSizeValue)
        self.Plot('To Invest','ToInvest', self.ToInvest)
        self.Plot('My QQQ Indicator','MyQQQIndicator', self.QQQrsi.Current.Value)
        bench = self.Securities[parameters["Benchmark"]].Close
        if self.lastBenchmarkValue is not  None:
           self.BenchmarkPerformance = self.BenchmarkPerformance * (bench/self.lastBenchmarkValue)
        # store today's benchmark close price for use tomorrow
        self.lastBenchmarkValue = bench
        # make our plots
        self.Plot(parameters["AlgoName"] + " vs Benchmark", "Portfolio", self.Portfolio.TotalPortfolioValue)
        self.Plot(parameters["AlgoName"] + " vs Benchmark", parameters["Benchmark"], self.BenchmarkPerformance)
        
    # ------------------------------------------  On Data  ------------------------------------------
    def OnData(self, data):
        for security, symbolData in self.TickerTable.items():
            if not data.ContainsKey(security.Symbol):
                return

        
    # -------------------------------------- On Securities Change ---------------------------------------
    def OnSecuritiesChanged(self, changes):
        self.changes = changes
        self.Log(f"OnSecuritiesChanged({self.Time}):: {changes}")
        
        for security in changes.RemovedSecurities:
            #self.Liquidate(security)
            self.TickerTable.pop(security, None)
        
        for security in changes.AddedSecurities:
            if security not in self.TickerTable and security.Symbol.Value != parameters["Benchmark"]:
                symbol = security.Symbol
                rsi = self.RSI(symbol, parameters["RSIdays"], Resolution.Daily)
                sma = self.SMA(symbol, parameters["SMAdays"], Resolution.Daily)
                smaLong = self.SMA(symbol, parameters["SmaLong"], Resolution.Daily)
                smaShort = self.SMA(symbol, parameters["SmaShort"], Resolution.Daily)
                rcShort = self.RC(symbol, parameters["SmaShort"], Resolution.Daily)
                ppo = self.PPO(symbol, 4, 14, MovingAverageType.Simple, Resolution.Daily)
                
                symbolData = SymbolData(symbol, rsi, sma, smaLong, smaShort, rcShort, ppo)
                
                self.TickerTable[security] = symbolData
                
                
    # ------------------------------------------- On End --------------------------------------------
    def OnEndOfAlgorithm(self):
        self.Liquidate()
        #for security in self.Portfolio:
        #    if self.Portfolio[security.Symbol].Invested: # ??? 
        #        self.Debug(str(ticker)+" "+str(self.Portfolio[ticker].Quantity))
        
        self.Debug(len([symbol for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]))
        self.Debug([(symbol.Value, self.Portfolio[symbol].HoldingsValue) for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested])
        
        self.Debug("Cash: "+str(self.Portfolio.Cash))
        self.Debug("Total Holdings Value: "+str(self.Portfolio.TotalHoldingsValue))
        self.Debug("Total Portfolio Value: "+str(self.Portfolio.TotalPortfolioValue))
        self.Debug("Lost buys: "+str(self.LostBuys))
# (c) Blash18
# Confidential

parameters = {}


parameters["AlgoName"] = "BLASH18"                  
parameters["StartCash"] = 100000                    # First Cash deposit for the Algorithm
parameters["MaxNumberOfTickersToInvest"] = 20       # Max Number of Equities we will invest in
parameters["CashBufferNumber"] = 1                  # We we allocate investment (% of Holdings) we will allocate to ["MaxNumberOfTickersToInvest"] + ["CashBufferNumber"] so we don't risk too low cash
parameters["CashTradeBuffer"] = 0.01                # Each trade(order) cash buffer

parameters["AllowDebt"] = False                     # ------ To Be implemented. True is we allow the algorithm to buy equity even without sufficient free cash. False is in no condition we allow to be cash negative.
parameters["AllowPortfolioRedestribution"] = False  # ??? if not enough free cash to buy a new stock. Do we sell the relevant part of the portfolio to generate Cash for the buy.
                                                    # if we don't AllowDebt or AllowPortfolioRedistribuion - no Buy will be made and we get a warning message.
parameters["AllowMultipleBuysBeforeSell"] = False   # Can you buy a ticker tou own before you sell it


#parameters["SMABuyGapTrigger"] = 1.00  --> GetParameter     # For Buy current Price should be lower (reverse momentum) than Historic price times BuyPercentTrigger. The higher this number is we'll make more Buys
#parameters["PPOBuyGapTrigger"] = 3  --> GetParameter        # <=
parameters["RSIBuyGapTrigger"] = 100        # <= For Buy 
parameters["RSITriggerDirection"] = "down"
parameters["PPOTriggerDirection"] = "down"

#parameters["GainSellTrigger"] = 0.14    --> GetParameter    # Increase needed to Sell decision --- Tagged "Profit"
#parameters["LossSellTrigger"] = 0.08   --> GetParameter     # Loss needed for Sell decision  --- Tagged "Loss"
#parameters["SellCooldown"] = 0    --> GetParameter          #  How many trading days the Algoritm must wait before it is allowed to consider buying the Stock again


parameters["SortCandidatesby"] = "PPO&RSI"  ##  this is a whitelist. only 'RSI', 'PPO', 'SMA', 'RSI&PPO', 'PPO&RSI' allowed. add here if new options


parameters["BuyFrequency"] = [DayOfWeek.Monday,DayOfWeek.Tuesday,DayOfWeek.Wednesday,DayOfWeek.Thursday,DayOfWeek.Friday]
parameters["SellFrequency"] = [DayOfWeek.Monday,DayOfWeek.Tuesday,DayOfWeek.Wednesday,DayOfWeek.Thursday,DayOfWeek.Friday]
#parameters["SellLaggardsFrequency"] = [DayOfWeek.Monday] # Not implemented
#parameters["NumberLaggrardToSell"] = 3 --> GetParameter

# Back testing dates
parameters["start_date_year"] = 2020
parameters["start_date_month"] = 1
parameters["start_date_day"] = 1
parameters["end_date_year"] = 2020
parameters["end_date_month"] = 12
parameters["end_date_day"] = 31

parameters["Benchmark"] = "QQQ"


parameters["AllowMultipleBuysBeforeSell"] = False


parameters["MonkeyBuyer"] = False
parameters["MonkeySeller"] = False

parameters["RSIdays"] = 14
parameters["SMAdays"] = 30
parameters["SmaLong"] = 30
parameters["SmaShort"] = 3
#
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel

Uparam = {}

Uparam["SelectedUniverse"]= "nasdaq100_A2Z" # if blank go for Automatic Universe selection
Uparam["CoarseUniverseSize"] = 3000
Uparam["FineUniverseSize"] =102
Uparam["UniverseMinMarketCap"] = 2e9
Uparam["PrimaryExchangeID"] = ["NAS"]
Uparam["UniverseSectors"] = [MorningstarSectorCode.BasicMaterials,
                            MorningstarSectorCode.ConsumerCyclical,
                            MorningstarSectorCode.FinancialServices,
                            MorningstarSectorCode.RealEstate,
                            MorningstarSectorCode.ConsumerDefensive,
                            MorningstarSectorCode.Healthcare,
                            MorningstarSectorCode.Utilities,
                            MorningstarSectorCode.CommunicationServices,
                            MorningstarSectorCode.Energy,
                            MorningstarSectorCode.Industrials,
                            MorningstarSectorCode.Technology]

ManualSymbolsLists = {"nasdaq100_A2Z":["AAPL","ADBE","ADI","ADP","ADSK","AEP","ALGN","ALXN","AMAT","AMD","AMGN","AMZN","ANSS","ASML","ATVI","AVGO","BIDU","BIIB","BKNG","CDNS","CDW","CERN","CHKP","CHTR","CMCSA","COST","CPRT","CSCO","CSX","CTAS","CTSH","DLTR","DOCU","DXCM","EA","EBAY","EXC","FAST","FB","FISV","FOX","FOXA","GILD","GOOG","GOOGL","IDXX","ILMN","INCY","INTC","INTU","ISRG","JD","KDP","KHC","KLAC","LRCX","LULU","MAR","MCHP","MDLZ","MELI","MNST","MRNA","MRVL","MSFT","MTCH","MU","MXIM","NFLX","NTES","NVDA","NXPI","OKTA","ORLY","PAYX","PCAR","PDD","PEP","PTON","PYPL","QCOM","REGN","ROST","SBUX","SGEN","SIRI","SNPS","SPLK","SWKS","TCOM","TEAM","TMUS","TSLA","TXN","VRSK","VRSN","VRTX","WBA","WDAY","XEL","XLNX","ZM"],
                        
                        "nasdaq100_S2L":["FOX","FOXA","CHKP","CDW","TCOM","INCY","VRSN","CERN","MXIM","SIRI","DLTR","SWKS","CPRT","FAST","SPLK","PAYX","VRSK","ANSS","SGEN","ORLY","CTAS","OKTA",
                        "PCAR","XEL","ALXN","XLNX","MRVL","DXCM","MTCH","CDNS","EBAY","MAR","KHC","MCHP","ROST","AEP","WBA","SNPS","BIIB","EXC","IDXX","ALGN","EA","CTSH","KDP","LULU","MNST",
                        "PTON","KLAC","NXPI","DOCU","MRNA","WDAY","REGN","ADI","TEAM","ILMN","VRTX","ADSK","CSX","ADP","FISV","ATVI","NTES","MDLZ","LRCX","GILD","BKNG","BIDU","ISRG","MU","AMAT",
                        "MELI","INTU","AMD","ZM","SBUX","CHTR","JD","AMGN","TXN","COST","TMUS","QCOM","AVGO","CSCO","PEP","PDD","CMCSA","ADBE","INTC","ASML","NFLX","PYPL","NVDA","FB","TSLA",
                        "GOOGL","GOOG","AMZN","MSFT","AAPL"],

                        "nasdaq100_L2S":["AAPL","MSFT","AMZN","GOOG","GOOGL","TSLA","FB","NVDA","PYPL","NFLX","ASML","INTC","ADBE","CMCSA","PDD","PEP","CSCO","AVGO","QCOM","TMUS","COST","TXN","AMGN","JD","CHTR","SBUX","ZM","AMD","INTU","MELI","AMAT","MU","ISRG","BIDU","BKNG","GILD","LRCX","MDLZ","NTES","ATVI","FISV","ADP","CSX","ADSK","VRTX","ILMN","TEAM","ADI","REGN","WDAY","MRNA","DOCU","NXPI","KLAC","PTON","MNST","LULU","KDP","CTSH","EA","ALGN","IDXX","EXC","BIIB","SNPS","WBA","AEP","ROST","MCHP","KHC","MAR","EBAY","CDNS","MTCH","DXCM","MRVL","XLNX","ALXN","XEL","PCAR","OKTA","CTAS","ORLY","SGEN","ANSS","VRSK","PAYX","SPLK","FAST","CPRT","SWKS","DLTR","SIRI","MXIM","CERN","VRSN","INCY","TCOM","CDW","CHKP","FOXA","FOX"],

                        "debug_list":["MSFT", "AAPL"]
                        }

 # ------------------------------------- Symbol Data -----------------------------------------

class SymbolData:
    def __init__(self, symbol, rsi, sma, smaLong, smaShort, rcShort,  ppo):
        self.Symbol = symbol
        self.rsi = rsi
        self.sma = sma
        self.smaLong = smaLong
        self.smaShort = smaShort
        self.rcShort = rcShort
        self.ppo = ppo

 
 # ------------------------------ QC500UniverseSelectionModel ---------------------------------

class QC500UniverseSelectionModel(FundamentalUniverseSelectionModel):
    '''Defines the QC500 universe as a universe selection model for framework algorithm
    For details: https://github.com/QuantConnect/Lean/pull/1663'''

    def __init__(self, filterFineData = True, universeSettings = None, securityInitializer = None):
        '''Initializes a new default instance of the QC500UniverseSelectionModel'''
        super().__init__(filterFineData, universeSettings, securityInitializer)
        self.numberOfSymbolsCoarse = Uparam["CoarseUniverseSize"]
        self.numberOfSymbolsFine = Uparam["FineUniverseSize"]
        self.sorted1BySymbol = {} 
        self.lastMonth = -1

    def SelectCoarse(self, algorithm, coarse):
        '''coarse selection
        Must have fundamental data
        Must have positive previous-day close price
        Must have positive volume on the previous trading day
        Sort by tarde Dollar Volume (previous day)
        '''
        
        if algorithm.Time.month == self.lastMonth:
            return Universe.Unchanged
        self.lastMonth = algorithm.Time.month
            
        sorted1 = sorted([x for x in coarse if x.HasFundamentalData and x.Volume > 0 and x.Price >0],
                        key = lambda x: x.DollarVolume, reverse=True)[:self.numberOfSymbolsCoarse]
        self.sorted1BySymbol = {x.Symbol:x.DollarVolume for x in sorted1}
        
        # If no security has met the QC500 criteria, the universe is unchanged.
        # A new selection will be attempted on the next trading day as self.lastMonth is not updated
        count = len(self.sorted1BySymbol)
        #algorithm.Debug("U coarse:"+str(count))
        if count == 0:
            return Universe.Unchanged

        # return the symbol objects our sorted collection
        return list(self.sorted1BySymbol.keys())

    def SelectFine(self, algorithm, fine):
        
        # fine is a list of FineFundamental objects
        # expects is to return a list of Symbol objects
        
        # Strategy point: Sorts our list
        sortedBySector = sorted([x for x in fine if x.CompanyReference.PrimaryExchangeID in Uparam["PrimaryExchangeID"]
                                        #and x.CompanyReference.CountryId == "USA"
                                        #and (algorithm.Time - x.SecurityReference.IPODate).days > 30
                                        #and x.ValuationRatios.ForwardPERatio > 0
                                        and x.AssetClassification.MorningstarSectorCode in Uparam["UniverseSectors"]
                                        and x.MarketCap >= Uparam["UniverseMinMarketCap"]],
                               key = lambda x: x.MarketCap, reverse = True)
                               

        count = len(sortedBySector)
        algorithm.Debug("U fine:"+str(count))
        if count == 0:
            return Universe.Unchanged
        
        # converts our list of fine to a list of Symbols 
        sortedBySectorSymbols = [f.Symbol for f in sortedBySector]
        
        # logging fine Universe ???
        #algorithm.Debug(str(algorithm.Time)+" " +str(len(sortedBySectorSymbols)))
        #counter = self.numberOfSymbolsFine
        #for security in sortedBySectorSymbols:
        #    algorithm.Debug(security)
        #    counter +=1
        #    if counter == self.numberOfSymbolsFine:
        #        break
       
        return sortedBySectorSymbols[:self.numberOfSymbolsFine]