Hello. I am completely out of my element when it comes to coding and I am requesting assistance in setting up my strategy for backtesting. I attempted to build on the Piotroski F_Score code that was generously provided here with the idea of subsequently filtering for the top 30 companies with the highest cash flow yield, rebalanced annually. However, when I paste the below codes into the files shown below, I get a couple errors. Is someone able to modify the project and share it with me so that I can backtest it? Thank you for any help you may provide.
main.py
# region imports
from AlgorithmImports import *
from .security_initializer import CustomSecurityInitializer
from .universe import FScoreUniverseSelectionModel
# endregion
class PensiveFluorescentYellowParrot(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 7, 1) # Set Start Date
self.SetEndDate(2023, 7, 1) # Set End Date
self.SetCash(10000000) # Set Strategy Cash
### Parameters ###
fscore_threshold = self.GetParameter("fscore_threshold", 7)
### Reality Modeling ###
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
self.SetSecurityInitializer(CustomSecurityInitializer(self.BrokerageModel, FuncSecuritySeeder(self.GetLastKnownPrices)))
### Universe Settings ###
self.UniverseSettings.Resolution = Resolution.Minute
# Our universe is selected by Piotroski's F-Score
self.AddUniverseSelection(FScoreUniverseSelectionModel(self, fscore_threshold))
self.AddAlpha(ConstantAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(1)))
self.SetPortfolioConstruction(SectorWeightingPortfolioConstructionModel())
self.SetExecution(SpreadExecutionModel(0.01)) # maximum 1% spread allowed
self.AddRiskManagement(NullRiskManagementModel())
# Schedule rebalancing to occur every year on the first trading day of the year
self.Schedule.On(self.DateRules.MonthStart("SPY", 1), self.TimeRules.AfterMarketOpen("SPY"), self.Rebalance)
def OnSecuritiesChanged(self, changes):
self.Log(changes)
def Rebalance(self):
# Get the top 30 companies by Free Cash Flow Yield
top_companies = self.UniverseManager.Keys.OrderByDescending(lambda x: self.UniverseManager[x].GetFreeCashFlowYield(x, self.Securities[x].FundamentalData))[:30]
# Liquidate existing positions
for holding in self.Portfolio.Values:
self.Liquidate(holding.Symbol)
# Allocate equal amounts of portfolio value to the top 30 companies
target_weight = 1.0 / len(top_companies)
for company in top_companies:
self.SetHoldings(company.Symbol, target_weight)f_score.py
#region imports
from AlgorithmImports import *
#endregion
def GetROAScore(fine):
'''Get the Profitability - Return of Asset sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Profitability - Return of Asset sub-score'''
# Nearest ROA as current year data
roa = fine.OperationRatios.ROA.ThreeMonths
# 1 score if ROA datum exists and positive, else 0
score = 1 if roa and roa > 0 else 0
return score
def GetOperatingCashFlowScore(fine):
'''Get the Profitability - Operating Cash Flow sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Profitability - Operating Cash Flow sub-score'''
# Nearest Operating Cash Flow as current year data
operating_cashflow = fine.FinancialStatements.CashFlowStatement.CashFlowFromContinuingOperatingActivities.ThreeMonths
# 1 score if operating cash flow datum exists and positive, else 0
score = 1 if operating_cashflow and operating_cashflow > 0 else 0
return score
def GetROAChangeScore(fine):
'''Get the Profitability - Change in Return of Assets sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Profitability - Change in Return of Assets sub-score'''
# if current or previous year's ROA data does not exist, return 0 score
roa = fine.OperationRatios.ROA
if not roa.ThreeMonths or not roa.OneYear:
return 0
# 1 score if change in ROA positive, else 0 score
score = 1 if roa.ThreeMonths > roa.OneYear else 0
return score
def GetAccrualsScore(fine):
'''Get the Profitability - Accruals sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Profitability - Accruals sub-score'''
# Nearest Operating Cash Flow, Total Assets, ROA as current year data
operating_cashflow = fine.FinancialStatements.CashFlowStatement.CashFlowFromContinuingOperatingActivities.ThreeMonths
total_assets = fine.FinancialStatements.BalanceSheet.TotalAssets.ThreeMonths
roa = fine.OperationRatios.ROA.ThreeMonths
# 1 score if operating cash flow, total assets and ROA exists, and operating cash flow / total assets > ROA, else 0
score = 1 if operating_cashflow and total_assets and roa and operating_cashflow / total_assets > roa else 0
return score
def GetLeverageScore(fine):
'''Get the Leverage, Liquidity and Source of Funds - Change in Leverage sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Leverage, Liquidity and Source of Funds - Change in Leverage sub-score'''
# if current or previous year's long term debt to equity ratio data does not exist, return 0 score
long_term_debt_ratio = fine.OperationRatios.LongTermDebtEquityRatio
if not long_term_debt_ratio.ThreeMonths or not long_term_debt_ratio.OneYear:
return 0
# 1 score if long term debt ratio is lower in the current year, else 0 score
score = 1 if long_term_debt_ratio.ThreeMonths < long_term_debt_ratio.OneYear else 0
return score
def GetLiquidityScore(fine):
'''Get the Leverage, Liquidity and Source of Funds - Change in Liquidity sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Leverage, Liquidity and Source of Funds - Change in Liquidity sub-score'''
# if current or previous year's current ratio data does not exist, return 0 score
current_ratio = fine.OperationRatios.CurrentRatio
if not current_ratio.ThreeMonths or not current_ratio.OneYear:
return 0
# 1 score if current ratio is higher in the current year, else 0 score
score = 1 if current_ratio.ThreeMonths > current_ratio.OneYear else 0
return score
def GetShareIssuedScore(fine):
'''Get the Leverage, Liquidity and Source of Funds - Change in Number of Shares sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Leverage, Liquidity and Source of Funds - Change in Number of Shares sub-score'''
# if current or previous year's issued shares data does not exist, return 0 score
shares_issued = fine.FinancialStatements.BalanceSheet.ShareIssued
if not shares_issued.ThreeMonths or not shares_issued.TwelveMonths:
return 0
# 1 score if shares issued did not increase in the current year, else 0 score
score = 1 if shares_issued.ThreeMonths <= shares_issued.TwelveMonths else 0
return score
def GetGrossMarginScore(fine):
'''Get the Leverage, Liquidity and Source of Funds - Change in Gross Margin sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Leverage, Liquidity and Source of Funds - Change in Gross Margin sub-score'''
# if current or previous year's gross margin data does not exist, return 0 score
gross_margin = fine.OperationRatios.GrossMargin
if not gross_margin.ThreeMonths or not gross_margin.OneYear:
return 0
# 1 score if gross margin is higher in the current year, else 0 score
score = 1 if gross_margin.ThreeMonths > gross_margin.OneYear else 0
return score
def GetAssetTurnoverScore(fine):
'''Get the Leverage, Liquidity and Source of Funds - Change in Asset Turnover Ratio sub-score of Piotroski F-Score
Arg:
fine: Fine fundamental object of a stock
Return:
Leverage, Liquidity and Source of Funds - Change in Asset Turnover Ratio sub-score'''
# if current or previous year's asset turnover data does not exist, return 0 score
asset_turnover = fine.OperationRatios.AssetsTurnover
if not asset_turnover.ThreeMonths or not asset_turnover.OneYear:
return 0
# 1 score if asset turnover is higher in the current year, else 0 score
score = 1 if asset_turnover.ThreeMonths > asset_turnover.OneYear else 0
return scoreuniverse.py
# region imports
from AlgorithmImports import *
from .f_score import *
# endregion
class FScoreUniverseSelectionModel(FineFundamentalUniverseSelectionModel):
def __init__(self, algorithm, fscore_threshold):
super().__init__(self.SelectCoarse, self.SelectFine)
self.algorithm = algorithm
self.fscore_threshold = fscore_threshold
def SelectCoarse(self, coarse):
'''Defines the coarse fundamental selection function.
Args:
algorithm: The algorithm instance
coarse: The coarse fundamental data used to perform filtering
Returns:
An enumerable of symbols passing the filter'''
# We only want stocks with fundamental data and price > $1
filtered = [x.Symbol for x in coarse if x.HasFundamentalData and x.Price > 1]
return filtered
def SelectFine(self, fine):
'''Defines the fine fundamental selection function.
Args:
algorithm: The algorithm instance
fine: The fine fundamental data used to perform filtering
Returns:
An enumerable of symbols passing the filter'''
# We use a dictionary to hold the F-Score of each stock
f_scores = {}
for f in fine:
# Calculate the Piotroski F-Score of the given stock
f_scores[f.Symbol] = self.GetPiotroskiFScore(f)
if f_scores[f.Symbol] >= self.fscore_threshold:
self.algorithm.Log(f"Stock: {f.Symbol.ID} :: F-Score: {f_scores[f.Symbol]}")
# Sort the companies by their F-Scores in descending order
sorted_companies = sorted(f_scores.items(), key=lambda x: x[1], reverse=True)
# Select the top 30% of companies based on their F-Scores
top_30_percent = int(len(sorted_companies) * 0.3)
selected = [symbol for symbol, fscore in sorted_companies[:top_30_percent]]
# Sort the selected companies by Free Cash Flow Yield in descending order
sorted_selected = sorted(selected, key=lambda x: self.GetFreeCashFlowYield(x, self.algorithm.Securities[x].FundamentalData), reverse=True)
return sorted_selected
def GetPiotroskiFScore(self, fine):
'''A helper function to calculate the Piotroski F-Score of a stock
Arg:
fine: MorningStar fine fundamental data of the stock
return:
the Piotroski F-Score of the stock
'''
# initial F-Score as 0
fscore = 0
# Add up the sub-scores in different aspects
fscore += GetROAScore(fine)
fscore += GetOperatingCashFlowScore(fine)
fscore += GetROAChangeScore(fine)
fscore += GetAccrualsScore(fine)
fscore += GetLeverageScore(fine)
fscore += GetLiquidityScore(fine)
fscore += GetShareIssuedScore(finesecurity_initializer.py
# region imports
from AlgorithmImports import *
# endregion
class CustomSecurityInitializer(BrokerageModelSecurityInitializer):
def __init__(self, brokerage_model: IBrokerageModel, security_seeder: ISecuritySeeder) -> None:
super().__init__(brokerage_model, security_seeder)
def Initialize(self, security: Security) -> None:
# First, call the superclass definition
# This method sets the reality models of each security using the default reality models of the brokerage model
super().Initialize(security)
# We want a slippage model with price impact by order size for reality modeling
security.SetSlippageModel(VolumeShareSlippageModel())
Ashutosh
Hi Shane BracewellÂ
There were a couple of mistakes in the code:
1) Schedule.On() event can be called for a symbol only if we have requested the subscription of the data for that symbol.
2) You will have to calculate the Free Cash Flow Yield. The fundamental values and ratios that QC provides can be found in this document. ( I have added a function to calculate this in the backtest attached)
3) Check out the implementation of fetching fundamental data inside fine and course filters.
I've included a backtest that incorporates the revised code and aligns with your suggestions. Please review it and let me know if it's helpful. Additionally, I recommend reviewing the QC documents to gain insight into the implementations.
Best,
Ashutosh
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Shane Bracewell
Hi Ashutosh. Â I appreciate you taking the time to walk me through this, it is very insightful. Thank you.
Shane Bracewell
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!