| Overall Statistics |
|
Total Trades 163 Average Win 1.88% Average Loss -0.55% Compounding Annual Return 16.551% Drawdown 36.200% Expectancy 2.070 Net Profit 75.687% Sharpe Ratio 0.645 Probabilistic Sharpe Ratio 19.409% Loss Rate 31% Win Rate 69% Profit-Loss Ratio 3.45 Alpha 0.186 Beta -0.045 Annual Standard Deviation 0.278 Annual Variance 0.077 Information Ratio 0.101 Tracking Error 0.344 Treynor Ratio -3.984 Total Fees $1161.33 |
class SiegfriedsRework(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2009, 1, 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_peg = 100 # max PB at purcahse only. if stock has PB intially below this threshold, but rises while in holdings, stock won't be liquidated
# security.Fundamentals - only available if fine selection implemented
# Universe Selection, OnSecuritiesChanged, OnData are all called midnight
# All on same time stamp
# Pick Universe -> Handle Changes -> Receive Data
# 1. UniverseSelection(Coarse/Fine) -> 2. OnSecuritiesChanged -> 3. OnData
# 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 and x.Price > 0] # removing ETFs, ETNs
# sorted_coarse = sorted(filtered_coarse, key=lambda k:k.DollarVolume, reverse=True)
sorted_coarse = [x for x in filtered_coarse if x.DollarVolume > self.min_volume]
top_liquid_coarse = sorted_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.NormalizedPEGatio > 0
and x.ValuationRatios.NormalizedPEGatio < self.max_peg]
sorted_fine = sorted(filtered_fine, key=lambda x:x.ValuationRatios.NormalizedPEGatio, 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]}")
# self.new_symbols = [i.Symbol for i in sorted_fine]
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 \
or security.Price <= 0:
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" PEG is {security.Fundamentals.ValuationRatios.NormalizedPEGatio}," \
+ 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
# AAPL gets added universe, we buy AAPL, AAPL removed from universe, AAPL readded*** (duplicate entry in invested_stocks)
if security.Symbol not in self.invested_stocks:
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" PEG is {security.Fundamentals.ValuationRatios.NormalizedPEGatio}," \
# + 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" PEG is {security.Fundamentals.ValuationRatios.NormalizedPEGatio}," \
+ f" MarketCap is {security.Fundamentals.MarketCap/1000000}" \
+ f" and Volume is {security.Fundamentals.DollarVolume/1000000}")
# 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