Hi Team,

I´m new to QC & trying out some basic stuff.
Goal here is to track the breakpoints of a classic "value" portfolio (as outlined in https://www.quantconnect.com/tutorials/strategy-library/book-to-market-value-anomaly#Book-to-Market-Value-Anomaly-Source), then pick out one ticker (the one with largest market cap on the first rebalancing period) and track its inclusion  / exclusion in the value portfolio.
More precisely I want to track its P/B at each rebalancing date and see whether it´s inside the value portfolio or not. You can see this from the custom charts I added.

Besides massive speed issues (not sure how to improve on them) I seem to misunderstand some basic concepts on the flow of the algorithm. For example, why is the P/B Ratio of the asset I´m tracking always constant? Is this the way to fetch the current P/B ratio (line 102). Equally unsure about fetching last Price on rebalancing date (line 125)..
Then for to me inexplicable reasons the backtest broke down midway (!) in my last attempt due to line 125.

Also wondering if this type of analysis should better be done in a research notebook? But unclear how to import universes, etc.

Always knew I was dumb & slow, but hardly been as frustrated as this. Thank you so much for taking the time to look into it

Below is the original backtest, while here is the code with some changes:

from QuantConnect.Data.UniverseSelection import *
from System.Drawing import Color
import operator
import math
import numpy as np
import pandas as pd
import scipy as sp

class BooktoMarketAnomaly(QCAlgorithm):

def Initialize(self):

self.SetStartDate(2016,1,2) # Set Start Date
self.SetEndDate(2019,3,15) # Set End Date
self.SetCash(50000) # Set Strategy Cash

self.flag0 = 1 # variable to select test-asset in first rebalancing month
self.flag1 = 1 # variable to control the monthly rebalance of coarse and fine selection function
self.flag2 = 0 # variable to control the monthly rebalance of OnData function
self.flag3 = 0 # variable to record the number of rebalancing times

self.test_asset_object = None
self.test_asset_ticker = None
self.long_list = None
self._changes = None

# Initialize Universe + Settings
self.UniverseSettings.Resolution = Resolution.Daily

# Initialize Benchmark
self.SetBenchmark("SPY")

# Schedule functions
# Trigger an event every day a specific symbol is trading --> here monthly
self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY"), Action(self.Rebalancing))

# Initialize Charts
breakpPlot = Chart('Fundamentals')

def CoarseSelectionFunction(self, coarse):
if self.flag1:
self.filtered_coarse = [x.Symbol for x in coarse if (x.HasFundamentalData and x.AdjustedPrice > 5)]
return self.filtered_coarse
else:
return []

def FineSelectionFunction(self, fine):
if self.flag1:
self.flag1 = 0
self.flag2 = 1

fine = [x for x in fine if (x.ValuationRatios.PBRatio > 0)]
# Calculate the market cap and add the "MarketCap" property to fine universe object
for i in fine:
i.MarketCap = float(i.EarningReports.BasicAverageShares.ThreeMonths * (i.EarningReports.BasicEPS.TwelveMonths*i.ValuationRatios.PERatio))
# Syntax : sorted(iterable, key, reverse) --> reverse means from highest (expensive) to lowest (cheap)
top_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse=True)[:int(len(fine)*0.2)] # Pick out top 20% mcap of all stock

top_bm = sorted(top_market_cap, key = lambda x: 1 / x.ValuationRatios.PBRatio, reverse=True)[:int(len(top_market_cap)*0.2)] # From top 20% mcap pick out top 20% value stocks
self.sorted_by_bm = [i.Symbol for i in top_bm]

top_bm_ratios = [i.ValuationRatios.PBRatio for i in top_bm]
current_universe_ratios = dict(zip(top_bm, top_bm_ratios))

current_min = min(current_universe_ratios.items(), key=lambda x: x[1])
current_max = max(current_universe_ratios.items(), key=lambda x: x[1])

# Save P/B Ratio cut-off breakpoint for plot
self.breakpoint_min = current_min[1]
self.breakpoint_max = current_max[1]

# FOR TESTING
if self.flag0:
top_bm_mcaps = [i.MarketCap for i in top_bm]
first_universe_mcaps = dict(zip(top_bm, top_bm_mcaps))
max_mcap = max(first_universe_mcaps.items(), key=lambda x: x[1])

self.test_asset_object = max_mcap[0] # this is a security object just like in top_bm
self.test_asset_ticker = self.test_asset_object.SecurityReference.SecuritySymbol

self.long_list = [self.test_asset_object.Symbol]
self.test_asset_pb = self.test_asset_object.ValuationRatios.PBRatio
# Add test-asset to our securities
# Close this if-statement; never return again
self.flag0 = 0
else:
self.test_asset_pb = self.test_asset_object.ValuationRatios.PBRatio
self.Debug('Current P/B Ratio: ' +str(self.test_asset_pb))

if self.test_asset_pb > self.breakpoint_max:
self.long_list = list()
else:
long_set = set(self.long_list)
self.long_list = list(long_set)

self.flag3 += 1

return self.long_list

else:
return []

def OnData(self, data):
if self.flag3 > 0:
if self.flag2 == 1:
self.flag2 = 0

if self.test_asset_ticker:
self.test_lastPrice = self.ActiveSecurities[str(self.test_asset_ticker)].Price

# if we have no changes, do nothing
if self._changes == None: return
# liquidate removed securities
for security in self._changes.RemovedSecurities:
if security.Invested:
self.Liquidate(security.Symbol)

self.SetHoldings(security.Symbol, 1)

self._changes = None
self.Plot('Fundamentals', 'Test-Asset P/B Ratio', self.test_asset_pb)
self.Plot('Fundamentals', 'Breakpoint Min', self.breakpoint_min)
self.Plot('Fundamentals', 'Breakpoint Max', self.breakpoint_max)

# this event fires whenever we have changes to our universe
def OnSecuritiesChanged(self, changes):
self._changes = changes

def Rebalancing(self):
self.flag1 = 1