| Overall Statistics |
|
Total Trades 5343 Average Win 0.11% Average Loss -0.08% Compounding Annual Return 43.097% Drawdown 29.900% Expectancy 0.534 Net Profit 207.457% Sharpe Ratio 1.151 Probabilistic Sharpe Ratio 49.459% Loss Rate 34% Win Rate 66% Profit-Loss Ratio 1.33 Alpha 0 Beta 0 Annual Standard Deviation 0.288 Annual Variance 0.083 Information Ratio 1.151 Tracking Error 0.288 Treynor Ratio 0 Total Fees $566224.62 Estimated Strategy Capacity $120000.00 Lowest Capacity Asset DLNG VLK5WIZ88O6D Portfolio Turnover 4.22% |
#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 score# 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, 8, 19) # Set End Date
self.SetCash(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.GetParameter("fscore_threshold", 7)
### Reality Modeling ###
# Interactive Broker Brokerage fees and margin
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
# Custom security initializer
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))
# Assume we want to just buy and hold the selected stocks, rebalance daily
self.AddAlpha(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.SetPortfolioConstruction(SectorWeightingPortfolioConstructionModel())
# Avoid placing orders with big bid-ask spread to reduce friction cost
self.SetExecution(SpreadExecutionModel(0.01)) # maximum 1% spread allowed
# Assume we do not have any risk management measures
self.AddRiskManagement(NullRiskManagementModel())
def OnSecuritiesChanged(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.SetSlippageModel(VolumeShareSlippageModel())# 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]}")
# 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 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(fine)
fscore += GetGrossMarginScore(fine)
fscore += GetAssetTurnoverScore(fine)
return fscore