Overall Statistics
Total Trades
50
Average Win
3.55%
Average Loss
-3.55%
Compounding Annual Return
29.016%
Drawdown
51.700%
Expectancy
0.642
Net Profit
155.326%
Sharpe Ratio
0.914
Probabilistic Sharpe Ratio
33.881%
Loss Rate
18%
Win Rate
82%
Profit-Loss Ratio
1.00
Alpha
0.302
Beta
-0.09
Annual Standard Deviation
0.335
Annual Variance
0.112
Information Ratio
0.875
Tracking Error
0.403
Treynor Ratio
-3.412
Total Fees
$595.69
class SiegfriedsRework(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetEndDate(2003, 9, 4)
        self.SetCash(100000)
        self.UniverseSettings.Resolution = Resolution.Daily
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(lambda time: None))
        # self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(lambda time: Expiry.EndOfMonth(time)))
        self.Settings.RebalancePortfolioOnInsightChanges = False
        self.Settings.RebalancePortfolioOnSecurityChanges = True
        self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.SelectCoarse, self.SelectFine))
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.SetBenchmark("SPY") # NOT SURE WHAT THIS DOES
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY", 120), self.Rebalance)
        
        self.num_symb_coarse = 8000
        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_peg = 100 # max peg at purcahse only. if stock has peg intially below this threshold, but rises while in holdings, stock won't be liquidated
        
        self.new_symbols = []
        self.invested_stocks = []        
        self.monthly_rebalance = False        
        self.recently_rebalanced = False
    
    ##### Universe Selection, OnSecuritiesChanged, OnData are all called midnight

    
    # coarse/fine universe selection runs everyday at midnight
    def SelectCoarse(self, coarse):
        
        if self.monthly_rebalance == False:
            self.recently_rebalanced = False
            return Universe.Unchanged # if selectcoarse returns universe.unchanged, selectfine is not called
        
        self.recently_rebalanced = True
        
        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 if i.Symbol.Value != 'PDLI']
         
    
    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.NormalizedPEGatio > 0
                            and x.ValuationRatios.NormalizedPEGatio < self.max_peg]
        
        sorted_fine = sorted(filtered_fine, key=lambda x:x.ValuationRatios.NormalizedPEGatio, reverse = False)
        
        quartile = 1 if len(sorted_fine)/4 <= 0 else int(round((len(sorted_fine)/4)))
        bottom_quartile = sorted_fine[:quartile]
        
        # self.Debug(f"filtered_fine: {[str(i.Symbol) for i in filtered_fine]}")
        self.new_symbols = [i.Symbol for i in bottom_quartile]
        # self.Debug(f"chosen_fine: {[str(i) for i in self.new_symbols]}")
        
        for symbol in self.new_symbols:
            if symbol not in self.invested_stocks:
                self.invested_stocks.append(symbol) # add new securities to watchlist

        # self.Debug(f" new symbols: {[str(i) for i in self.new_symbols]}")
        # self.Debug(f"invested stocks: {[str(i) for i in self.invested_stocks]}") 
        
        return self.invested_stocks


    def OnData(self, data):
        if not self.recently_rebalanced:
            return
        insights = []
        for symbol in self.invested_stocks:
            security = self.Securities[symbol]
            if security == self.spy:
                # skip SPY
                continue
            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:
                insights.append(Insight.Price(symbol, timedelta(days = 7560), InsightDirection.Flat))
                self.invested_stocks.remove(security.Symbol) #remove security from watchlist
                self.Debug(f"!!Liquidate {security.Symbol}")
            else:
                insights.append(Insight.Price(symbol, timedelta(days = 7560), InsightDirection.Up))
        self.EmitInsights(insights)

    
    # this is called according to Schedule.On in Initialize    
    def Rebalance(self):
        self.monthly_rebalance = True
        
    # def OnEndOfDay(self):
    #     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}")