| Overall Statistics |
|
Total Trades 394 Average Win 2.97% Average Loss -3.04% Compounding Annual Return 2.136% Drawdown 46.600% Expectancy 0.019 Net Profit 37.833% Sharpe Ratio 0.219 Loss Rate 48% Win Rate 52% Profit-Loss Ratio 0.98 Alpha 0.02 Beta 0.514 Annual Standard Deviation 0.138 Annual Variance 0.019 Information Ratio 0.077 Tracking Error 0.138 Treynor Ratio 0.059 Total Fees $7940.38 |
class EarningsQualityFactor(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2003,6,1) #Set Start Date
self.SetEndDate(2018,8,1) #Set End Date
self.SetCash(1000000) #Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
self.previous_fine = None
self.long = None
self.short = None
self.AddUniverse(self.CoarseSelectionFunction,self.FineSelectionFunction)
self.AddEquity("SPY", Resolution.Daily)
# monthly scheduled event but will only rebalance once a year
self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.At(23, 0), self.rebalance)
self.yearly_rebalance = False
def CoarseSelectionFunction(self, coarse):
if self.yearly_rebalance:
# drop stocks which have no fundamental data
filtered_coarse = [x.Symbol for x in coarse if (x.HasFundamentalData) and (x.Market == "usa")]
return filtered_coarse
else:
return []
def FineSelectionFunction(self, fine):
if self.yearly_rebalance:
#filters out the non-financial companies that don't contain the necessary data
fine = [x for x in fine if (x.CompanyReference.IndustryTemplateCode != "B")
and (x.FinancialStatements.BalanceSheet.CurrentAssets.Value != 0)
and (x.FinancialStatements.BalanceSheet.CashAndCashEquivalents.Value != 0)
and (x.FinancialStatements.BalanceSheet.CurrentLiabilities.Value != 0)
and (x.FinancialStatements.BalanceSheet.CurrentDebt.Value != 0)
and (x.FinancialStatements.BalanceSheet.IncomeTaxPayable.Value != 0)
and (x.FinancialStatements.IncomeStatement.DepreciationAndAmortization.Value != 0)]
if not self.previous_fine:
# will wait one year in order to have the historical fundamental data
self.previous_fine = fine
self.yearly_rebalance = False
return []
else:
# calculate the accrual for each stock
fine = self.CalculateAccruals(fine, self.previous_fine)
filtered_fine = [x for x in fine if (x.FinancialStatements.CashFlowStatement.OperatingCashFlow.Value!=0)
and (x.EarningReports.BasicEPS.Value!=0)
and (x.EarningReports.BasicAverageShares.Value!=0)
and (x.OperationRatios.DebttoAssets.Value!=0)
and (x.OperationRatios.ROE.Value!=0)]
for i in filtered_fine:
# cash flow to assets
i.CFA = i.FinancialStatements.CashFlowStatement.OperatingCashFlow.Value/(i.EarningReports.BasicEPS.Value * i.EarningReports.BasicAverageShares.Value)
# debt to assets
i.DA = i.OperationRatios.DebttoAssets.Value
# return on equity
i.ROE = i.OperationRatios.ROE.Value
# sort stocks by four factors respectively
sortedByAccrual = sorted(filtered_fine, key=lambda x: x.Accrual, reverse=True) # high score with low accrual
sortedByCFA = sorted(filtered_fine, key=lambda x: x.CFA) # high score with high CFA
sortedByDA = sorted(filtered_fine, key=lambda x: x.DA, reverse=True) # high score with low leverage
sortedByROE = sorted(filtered_fine, key=lambda x: x.ROE) # high score with high ROE
# create dict to save the score for each stock
score_dict = {}
# assign a score to each stock according to their rank with different factors
for i,obj in enumerate(sortedByAccrual):
scoreAccrual = i
scoreCFA = sortedByCFA.index(obj)
scoreDA = sortedByDA.index(obj)
scoreROE = sortedByROE.index(obj)
score = scoreAccrual + scoreCFA + scoreDA + scoreROE
score_dict[obj.Symbol] = score
sortedByScore = sorted(score_dict, key = lambda x: score_dict[x], reverse = True)
# long stocks with the top score (>30%) and short stocks with the bottom score (<70%)
self.long = sortedByScore[:int(0.3*len(sortedByScore))]
self.short = sortedByScore[-int(0.3*len(sortedByScore)):]
# save the fine data for the next year's analysis
self.previous_fine = fine
return self.long + self.short
else:
return []
def CalculateAccruals(self, current, previous):
accruals = []
for stock_data in current:
#compares this and last year's fine fundamental objects
try:
prev_data = None
for x in previous:
if x.Symbol == stock_data.Symbol:
prev_data = x
break
#calculates the balance sheet accruals and adds the property to the fine fundamental object
delta_assets = float(stock_data.FinancialStatements.BalanceSheet.CurrentAssets.Value)-float(prev_data.FinancialStatements.BalanceSheet.CurrentAssets.Value)
delta_cash = float(stock_data.FinancialStatements.BalanceSheet.CashAndCashEquivalents.Value)-float(prev_data.FinancialStatements.BalanceSheet.CashAndCashEquivalents.Value)
delta_liabilities = float(stock_data.FinancialStatements.BalanceSheet.CurrentLiabilities.Value)-float(prev_data.FinancialStatements.BalanceSheet.CurrentLiabilities.Value)
delta_debt = float(stock_data.FinancialStatements.BalanceSheet.CurrentDebt.Value)-float(prev_data.FinancialStatements.BalanceSheet.CurrentDebt.Value)
delta_tax = float(stock_data.FinancialStatements.BalanceSheet.IncomeTaxPayable.Value)-float(prev_data.FinancialStatements.BalanceSheet.IncomeTaxPayable.Value)
dep = float(stock_data.FinancialStatements.IncomeStatement.DepreciationAndAmortization.Value)
avg_total = (float(stock_data.FinancialStatements.BalanceSheet.TotalAssets.Value)+float(prev_data.FinancialStatements.BalanceSheet.TotalAssets.Value))/2
#accounts for the size difference
stock_data.Accrual = ((delta_assets-delta_cash)-(delta_liabilities-delta_debt-delta_tax)-dep)/avg_total
accruals.append(stock_data)
except:
#value in current universe does not exist in the previous universe
pass
return accruals
def rebalance(self):
#yearly rebalance at the end of June (start of July)
if self.Time.month == 7:
self.yearly_rebalance = True
def OnData(self, data):
if not self.yearly_rebalance: return
if self.long and self.short:
long_stocks = [x.Key for x in self.Portfolio if x.Value.IsLong]
short_stocks = [x.Key for x in self.Portfolio if x.Value.IsShort]
# liquidate the stocks not in the filtered long/short list
for long in long_stocks:
if long not in self.long:
self.Liquidate(long)
for short in short_stocks:
if short not in self.short:
self.Liquidate(short)
long_weight = 0.8/len(self.long)
for i in self.long:
self.SetHoldings(i, long_weight)
short_weight = 0.8/len(self.short)
for i in self.short:
self.SetHoldings(i, -short_weight)
self.yearly_rebalance = False
self.long = False
self.short = False