Overall Statistics
Total Trades
111
Average Win
0.70%
Average Loss
-0.38%
Compounding Annual Return
5.184%
Drawdown
29.900%
Expectancy
1.237
Net Profit
11.669%
Sharpe Ratio
0.318
Probabilistic Sharpe Ratio
13.045%
Loss Rate
21%
Win Rate
79%
Profit-Loss Ratio
1.83
Alpha
0.075
Beta
-0.072
Annual Standard Deviation
0.201
Annual Variance
0.04
Information Ratio
-0.359
Tracking Error
0.27
Treynor Ratio
-0.89
Total Fees
$438.34
class SiegfriedsRework(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2010, 7, 1)
        self.SetEndDate(2012, 9, 4)
        self.SetCash(100000)
        self.UniverseSettings.Resolution = Resolution.Daily
        self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.SelectCoarse, self.SelectFine))
        self.AddEquity("SPY")
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY", 120), self.Rebalance)
        self.num_symb_coarse = 8000
        self.monthly_rebalance = False 
        self.new_symbols = []
        self.invested_stocks = []
        self.min_market_cap = 5
        self.min_roic = 0.7
        self.max_roic = 20 # max ROIC at purcahse only. if stock has roic intially below this threshold, but rises while in holdings, stock won't be liquidated
        self.min_volume = 200000 # minimum trading volume
        self.max_eveb = 100 
        
    # coarse/fine universe selection runs everyday at midnight
    def SelectCoarse(self, coarse):
        
        if self.monthly_rebalance == False:
            return Universe.Unchanged # if selectcoarse returns universe.unchanged, selectfine is not called
            
        self.monthly_rebalance = False
        
        filtered_coarse = [x for x in coarse if x.HasFundamentalData] # removing ETFs, ETNs
        sorted_coarse = sorted(filtered_coarse, key=lambda k:k.DollarVolume, reverse=True)
        min_vol_coarse = [x for x in sorted_coarse if x.DollarVolume > self.min_volume]
        top_liquid_coarse = min_vol_coarse[:self.num_symb_coarse]
        
        return [i.Symbol for i in top_liquid_coarse]
         
    
    def SelectFine(self, fine):
        
        filtered_fine = [x for x in fine if x.CompanyReference.CountryId == "USA"
                            and x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.Energy
                            and x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
                            and x.MarketCap/1000000 > self.min_market_cap
                            and x.FinancialStatements.BalanceSheet.InvestedCapital.TwelveMonths > 0
                            and x.FinancialStatements.IncomeStatement.EBIT.TwelveMonths/x.FinancialStatements.BalanceSheet.InvestedCapital.TwelveMonths > self.min_roic
                            and x.FinancialStatements.IncomeStatement.EBIT.TwelveMonths/x.FinancialStatements.BalanceSheet.InvestedCapital.TwelveMonths < self.max_roic
                            and x.ValuationRatios.EVToEBIT3YrAvg > 0
                            and x.ValuationRatios.EVToEBIT3YrAvg < self.max_eveb] 
        
        sorted_fine = sorted(filtered_fine, key=lambda x:x.ValuationRatios.EVToEBIT3YrAvg, reverse = False)
        
        triptile = 1 if len(sorted_fine)/4 <= 0 else int(round((len(sorted_fine)/4)))
        bottom_triptile = sorted_fine[:triptile]
        
        # self.Debug(f"filtered_fine: {[str(i.Symbol) for i in filtered_fine]}")
        self.new_symbols = [i.Symbol for i in bottom_triptile]
        # self.Debug(f"chosen_fine: {[str(i) for i in self.new_symbols]}")
        
        return self.new_symbols # new symbols to add to our watchlist
    
    
    # this is called each time a security is added or removed from our universe
    def OnSecuritiesChanged(self, changes):

        # liquidate securities based on their fundamentals, remove from watchlist
        for symbol in self.Portfolio.Keys:
            
            # we only care about securities we are invested in
            if not self.Portfolio[symbol].Invested:
                continue
            
            security = self.Securities[symbol]
                                 
            # liquidate stocks that go below min ROIC
            if security.Fundamentals.FinancialStatements.IncomeStatement.EBIT.TwelveMonths/security.Fundamentals.FinancialStatements.BalanceSheet.InvestedCapital.TwelveMonths < self.min_roic \
                    or security.Fundamentals.MarketCap/1000000 < self.min_market_cap:
                self.invested_stocks.remove(security.Symbol)
                self.Liquidate(security.Symbol)
                # self.Debug(f"!!!Selling {security} @ {security.Price}," \
                #                  + f" ROIC is {security.Fundamentals.FinancialStatements.IncomeStatement.EBIT.TwelveMonths/security.Fundamentals.FinancialStatements.BalanceSheet.InvestedCapital.TwelveMonths}," \
                #                  + f" EBITDAEV is {security.Fundamentals.ValuationRatios.EVToEBIT3YrAvg}," \
                #                  + f" MarketCap is {security.Fundamentals.MarketCap/1000000}" \
                #                  + f" and Volume is {security.Fundamentals.DollarVolume/1000000}")
            
        if len(self.new_symbols) > 0: 
            
            for security in changes.AddedSecurities:
                if security.Fundamentals == None: # i.e. is an ETF/is SPY
                    continue
                
                # prevent duplicate entry in invested_stocks
                if security.Symbol not in self.invested_stocks and security.Symbol.Value is not str("PDLI R735QTJ8XC9X"):
                    self.invested_stocks.append(security.Symbol)  # add new securities to watchlist
                
        if len(self.invested_stocks) > 0:
            
            # for symbol in self.invested_stocks:
            #     security = self.Securities[symbol]
            #     self.SetHoldings(security.Symbol, 0.9/len(self.invested_stocks)) #0.9 to keep cash buffer
            #     self.Debug(f"Buying {security.Symbol} @ {security.Price}," \
            #                     + f" ROIC is {security.Fundamentals.FinancialStatements.IncomeStatement.EBIT.TwelveMonths/security.Fundamentals.FinancialStatements.BalanceSheet.InvestedCapital.TwelveMonths}," \
            #                     + f" EBITEV is {security.Fundamentals.ValuationRatios.EVToEBIT3YrAvg}," \
            #                     + f" MarketCap is {security.Fundamentals.MarketCap/1000000}" \
            #                     + f" and Volume is {security.Fundamentals.DollarVolume/1000000}")
            
            for symbol in self.invested_stocks:
                security = self.Securities[symbol]
                self.SetHoldings([PortfolioTarget(security.Symbol, (0.9/len(self.invested_stocks)))]) #0.9 to keep cash buffer
                # self.Debug(f"Buying {security.Symbol} @ {security.Price}," \
                #                 + f" ROIC is {security.Fundamentals.FinancialStatements.IncomeStatement.EBIT.TwelveMonths/security.Fundamentals.FinancialStatements.BalanceSheet.InvestedCapital.TwelveMonths}," \
                #                 + f" EBITEV is {security.Fundamentals.ValuationRatios.EVToEBIT3YrAvg}," \
                #                 + f" MarketCap is {security.Fundamentals.MarketCap/1000000}" \
                #                 + f" and Volume is {security.Fundamentals.DollarVolume/1000000}")
                                
                                
                                
        if len(self.new_symbols) > 0:
            self.Debug(f"Invested Stocks List: {[str(symbol) for symbol in self.invested_stocks]}")
            self.Debug(f"Portfolio Invested Stocks: {[str(symbol) for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]}")
            self.Debug(f"Value of Stocks: {[float(self.Portfolio[symbol].Quantity * self.Portfolio[symbol].Price) for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]}")
            self.Debug(f"Portfolio % Invested: {(self.Portfolio.TotalPortfolioValue - self.Portfolio.Cash)/self.Portfolio.TotalPortfolioValue}")
    
    # daily data is received at midnight
    def OnData(self, data):
        pass
        
    # this is called according to Schedule.On in Initialize
    def Rebalance(self):
        self.monthly_rebalance = True
        
    # def OnEndOfDay(self):
        
    #     # If there's a bug in QC data (e.g., PDLI currently showing incorrect stock price of $0),
    #     # symbols could end up in self.invested_stocks but not actually invested in portfolio
    #     self.invested_stocks = [symbol for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]
        
    #     self.Debug(f"Invested Stocks List: {[str(symbol) for symbol in self.invested_stocks]}")
    #     self.Debug(f"Portfolio Invested Stocks: {[str(symbol) for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]}")
    #     self.Debug(f"Value of Stocks: {[float(self.Portfolio[symbol].Quantity * self.Portfolio[symbol].Price) for symbol in self.Portfolio.Keys if self.Portfolio[symbol].Invested]}")
    #     self.Debug(f"Portfolio % Invested: {(self.Portfolio.TotalPortfolioValue - self.Portfolio.Cash)/self.Portfolio.TotalPortfolioValue}")