Overall Statistics |
Total Orders 5115 Average Win 0.12% Average Loss -0.08% Compounding Annual Return 46.558% Drawdown 29.600% Expectancy 0.573 Start Equity 10000000 End Equity 31479797.98 Net Profit 214.798% Sharpe Ratio 0.898 Sortino Ratio 1.365 Probabilistic Sharpe Ratio 26.366% Loss Rate 35% Win Rate 65% Profit-Loss Ratio 1.41 Alpha 0 Beta 0 Annual Standard Deviation 0.433 Annual Variance 0.187 Information Ratio 0.934 Tracking Error 0.433 Treynor Ratio 0 Total Fees $512234.96 Estimated Strategy Capacity $1000.00 Lowest Capacity Asset EY R735QTJ8XC9X Portfolio Turnover 4.26% |
# region imports from AlgorithmImports import * # endregion def get_r_o_a_score(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.operation_ratios.ROA.three_months # 1 score if ROA datum exists and positive, else 0 score = 1 if roa and roa > 0 else 0 return score def get_operating_cash_flow_score(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.financial_statements.cash_flow_statement.cash_flow_from_continuing_operating_activities.three_months # 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 get_r_o_a_change_score(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.operation_ratios.ROA if not roa.three_months or not roa.one_year: return 0 # 1 score if change in ROA positive, else 0 score score = 1 if roa.three_months > roa.one_year else 0 return score def get_accruals_score(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.financial_statements.cash_flow_statement.cash_flow_from_continuing_operating_activities.three_months total_assets = fine.financial_statements.balance_sheet.total_assets.three_months roa = fine.operation_ratios.ROA.three_months # 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 get_leverage_score(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.operation_ratios.long_term_debt_equity_ratio if not long_term_debt_ratio.three_months or not long_term_debt_ratio.one_year: 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.three_months < long_term_debt_ratio.one_year else 0 return score def get_liquidity_score(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.operation_ratios.current_ratio if not current_ratio.three_months or not current_ratio.one_year: return 0 # 1 score if current ratio is higher in the current year, else 0 score score = 1 if current_ratio.three_months > current_ratio.one_year else 0 return score def get_share_issued_score(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.financial_statements.balance_sheet.share_issued if not shares_issued.three_months or not shares_issued.twelve_months: return 0 # 1 score if shares issued did not increase in the current year, else 0 score score = 1 if shares_issued.three_months <= shares_issued.twelve_months else 0 return score def get_gross_margin_score(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.operation_ratios.gross_margin if not gross_margin.three_months or not gross_margin.one_year: return 0 # 1 score if gross margin is higher in the current year, else 0 score score = 1 if gross_margin.three_months > gross_margin.one_year else 0 return score def get_asset_turnover_score(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.operation_ratios.assets_turnover if not asset_turnover.three_months or not asset_turnover.one_year: return 0 # 1 score if asset turnover is higher in the current year, else 0 score score = 1 if asset_turnover.three_months > asset_turnover.one_year else 0 return score
# region imports from AlgorithmImports import * from security_initializer import CustomSecurityInitializer from universe import FScoreUniverseSelectionModel # endregion class PensiveFluorescentYellowParrot(QCAlgorithm): def initialize(self): self.set_start_date(2020, 7, 1) # Set Start Date self.set_end_date(2023, 7, 1) # Set Start Date self.set_cash(10000000) # Set Strategy Cash ### Parameters ### # The Piotroski F-Score threshold we would like to invest into stocks with F-Score >= of that fscore_threshold = self.get_parameter("fscore_threshold", 7) ### Reality Modeling ### # Interactive Broker Brokerage fees and margin self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) # Custom security initializer self.set_security_initializer(CustomSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))) ### Universe Settings ### self.universe_settings.resolution = Resolution.MINUTE # Our universe is selected by Piotroski's F-Score self.add_universe_selection(FScoreUniverseSelectionModel(self, fscore_threshold)) # Assume we want to just buy and hold the selected stocks, rebalance daily self.add_alpha(ConstantAlphaModel(InsightType.PRICE, InsightDirection.UP, timedelta(1))) # Avoid overconcentration of risk in related stocks in the same sector, we invest the same size in every sector self.set_portfolio_construction(SectorWeightingPortfolioConstructionModel()) # Avoid placing orders with big bid-ask spread to reduce friction cost self.set_execution(SpreadExecutionModel(0.01)) # maximum 1% spread allowed # Assume we do not have any risk management measures self.add_risk_management(NullRiskManagementModel()) def on_securities_changed(self, changes): # Log the universe changes to test the universe selection model # In this case, the added security should be the same as the logged stocks with F-score >= 7 self.log(changes)
# 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.set_slippage_model(VolumeShareSlippageModel())
# region imports from AlgorithmImports import * from f_score import * # endregion class FScoreUniverseSelectionModel(FineFundamentalUniverseSelectionModel): def __init__(self, algorithm, fscore_threshold): super().__init__(self.select_coarse, self.select_fine) self.algorithm = algorithm self.fscore_threshold = fscore_threshold def select_coarse(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.has_fundamental_data and x.price > 1] return filtered def select_fine(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.get_piotroski_f_score(f) if f_scores[f.symbol] >= self.fscore_threshold: self.algorithm.log(f"Stock: {f.symbol.id} :: F-Score: {f_scores[f.symbol]}") # Select the stocks with F-Score higher than the threshold selected = [symbol for symbol, fscore in f_scores.items() if fscore >= self.fscore_threshold] return selected def get_piotroski_f_score(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 += get_r_o_a_score(fine) fscore += get_operating_cash_flow_score(fine) fscore += get_r_o_a_change_score(fine) fscore += get_accruals_score(fine) fscore += get_leverage_score(fine) fscore += get_liquidity_score(fine) fscore += get_share_issued_score(fine) fscore += get_gross_margin_score(fine) fscore += get_asset_turnover_score(fine) return fscore