Introduction

Multiple factors can explain abnormal Equity returns which could be used to build profitable Equity long-short strategy. The most common factors are momentum, short-term reversal, market value, size factors and so on. In this tutorial, we will choose a few factors associated with earnings quality to investigate the return premium on stocks.

Method

Earnings Quality Metrics

Earnings quality refers to the ability of reported earnings to predict a company's future earnings. The earnings should be attributable to higher sales or lower costs, rather than artificial profits created by accounting anomalies or tricks such as inflation of inventories or changing depreciation. It is one of the most critical measures in financial reporting systems. High earnings quality indicates a healthy development in the firm's business and can improve the market efficiency.

The first metric of earnings quality is accruals. It is defined by cash flow relative to reported earnings. The high-quality earnings firms are characterized by low accruals while the low-quality firms are characterized by high accruals. The formula is

A= (ΔCAΔCash)(ΔCLΔSTDΔITP)Dep

where

  • ΔCA = annual change in current assets
  • ΔCash = change in cash and cash equivalents
  • ΔCL = change in current liabilities
  • ΔSTD = change in debt included in current liabilities
  • ΔITP = change in income taxes payable
  • ΔDep = annual depreciation and amortization expense

We use an annual change reported for two consecutive fiscal years. To calculate the accruals, we save the fine fundamental object in the last year and calculate the difference in the next year. The algorithm will start to trade after one-year initialization.

  1. def calculate_accruals(self, current, previous):
  2. accruals = []
  3. for stock_data in current:
  4. #compares this and last year's fine fundamental objects
  5. try:
  6. prev_data = None
  7. for x in previous:
  8. if x.symbol == stock_data.symbol:
  9. prev_data = x
  10. break
  11. #calculates the balance sheet accruals and adds the property to the fine fundamental object
  12. delta_assets = float(stock_data.financial_statements.balance_sheet.current_assets.value)-float(prev_data.financial_statements.balance_sheet.current_assets.value)
  13. delta_cash = float(stock_data.financial_statements.balance_sheet.cash_and_cash_equivalents.value)-float(prev_data.financial_statements.balance_sheet.cash_and_cash_equivalents.value)
  14. delta_liabilities = float(stock_data.financial_statements.balance_sheet.current_liabilities.value)-float(prev_data.financial_statements.balance_sheet.current_liabilities.value)
  15. delta_debt = float(stock_data.financial_statements.balance_sheet.current_debt.value)-float(prev_data.financial_statements.balance_sheet.current_debt.value)
  16. delta_tax = float(stock_data.financial_statements.balance_sheet.income_tax_payable.value)-float(prev_data.financial_statements.balance_sheet.income_tax_payable.value)
  17. dep = float(stock_data.financial_statements.income_statement.depreciation_and_amortization.value)
  18. avg_total = (float(stock_data.financial_statements.balance_sheet.total_assets.value)+float(prev_data.financial_statements.balance_sheet.total_assets.value))/2
  19. #accounts for the size difference
  20. stock_data.accrual = ((delta_assets-delta_cash)-(delta_liabilities-delta_debt-delta_tax)-dep)/avg_total
  21. accruals.append(stock_data)
  22. except:
  23. #value in current universe does not exist in the previous universe
  24. pass
  25. return accruals
+ Expand

The second metric is cash flow to assets ratio. It is calculated by dividing cash flows from operations by the average total assets. Firms with high cash flow to total assets are of high earnings quality. The other two metrics are Return on Equity (ROE) and Debt to Assets (DA) which are available properties of fine fundamental objects. The Higher the ROE ratio, the better the earnings quality. Low debt to assets means low leverage. It leads to more stable earnings and less dependence on the current financing conditions in the economy. Therefore, low debt to assets ratios is a signal of high earnings quality. Before calculating those variables, we should make sure their values are all positive.

  1. def fine_selection_function(self, fine):
  2. if self.yearly_rebalance:
  3. #filters out the non-financial companies that don't contain the necessary data
  4. fine = [x for x in fine if (x.company_reference.industry_template_code != "B")
  5. and (x.financial_statements.balance_sheet.current_assets.value != 0)
  6. and (x.financial_statements.balance_sheet.cash_and_cash_equivalents.value != 0)
  7. and (x.financial_statements.balance_sheet.current_liabilities.value != 0)
  8. and (x.financial_statements.balance_sheet.current_debt.value != 0)
  9. and (x.financial_statements.balance_sheet.income_tax_payable.value != 0)
  10. and (x.financial_statements.income_statement.depreciation_and_amortization.value != 0)]
  11. if not self.previous_fine:
  12. # will wait one year in order to have the historical fundamental data
  13. self.previous_fine = fine
  14. self.yearly_rebalance = False
  15. return []
  16. else:
  17. # calculate the accrual for each stock
  18. fine = self.calculate_accruals(fine, self.previous_fine)
  19. filtered_fine = [x for x in fine if (x.financial_statements.cash_flow_statement.operating_cash_flow.value != 0)
  20. and (x.earning_reports.basic_e_p_s.value != 0)
  21. and (x.earning_reports.basic_average_shares.value != 0)
  22. and (x.operation_ratios.debtto_assets.value != 0)
  23. and (x.operation_ratios.ROE.value != 0)]
  24. for i in filtered_fine:
  25. # cash flow to assets
  26. i.CFA = i.financial_statements.cash_flow_statement.operating_cash_flow.value/(i.earning_reports.basic_e_p_s.value * i.earning_reports.basic_average_shares.value)
  27. # debt to assets
  28. i.DA = i.operation_ratios.debtto_assets.value
  29. # return on equity
  30. i.ROE = i.operation_ratios.ROE.value
+ Expand

The Scoring System

The investment universe consists of all non-financial stocks from NYSE, Amex and Nasdaq. Next, we will build a composite scoring system to rank the stocks in the universe. The first step is sorting stocks by four factors respectively. “good” quality has a high score, so ideally a stock has low accruals, low debt to assets, high ROE, and high cash flow to assets will be allocated high score. Therefore, we sort the accruals and debt to assets ratio in descending orders and sort the ROE and cash flow to assets ratios in ascending order. Then the score of each stock is the sum of rank in four factors.

  1. # sort stocks by four factors respectively
  2. sortedByAccrual = sorted(filtered_fine, key=lambda x: x.accrual, reverse=True) # high score with low accrual
  3. sortedByCFA = sorted(filtered_fine, key=lambda x: x.CFA) # high score with high CFA
  4. sortedByDA = sorted(filtered_fine, key=lambda x: x.DA, reverse=True) # high score with low leverage
  5. sortedByROE = sorted(filtered_fine, key=lambda x: x.ROE) # high score with high ROE
  6. # create dict to save the score for each stock
  7. score_dict = {}
  8. # assign a score to each stock according to their rank with different factors
  9. for i,obj in enumerate(sortedByAccrual):
  10. score_accrual = i
  11. score_c_f_a = sortedByCFA.index(obj)
  12. score_d_a = sortedByDA.index(obj)
  13. score_r_o_e = sortedByROE.index(obj)
  14. score = score_accrual + score_c_f_a + score_d_a + score_r_o_e
  15. score_dict[obj.symbol] = score
  16. sortedByScore = sorted(score_dict, key = lambda x: score_dict[x], reverse = True)
  17. # long stocks with the top score (> 30%) and short stocks with the bottom score (< 70%)
  18. self.long = sortedByScore[:int(0.3*len(sortedByScore))]
  19. self.short = sortedByScore[-int(0.3*len(sortedByScore)):]
+ Expand

Trade and Rebalance

Based on the composite factor score, the algorithm goes long the top 30% of high score stocks and short the bottom 30% of low score stocks. Final factor portfolio is formed at the end of each June and is rebalanced yearly.

  1. def on_data(self, data):
  2. if not self.yearly_rebalance: return
  3. if self.long and self.short:
  4. long_stocks = [x.key for x in self.portfolio if x.value.IS_LONG]
  5. short_stocks = [x.key for x in self.portfolio if x.value.IS_SHORT]
  6. # liquidate the stocks not in the filtered long/short list
  7. for long in long_stocks:
  8. if long not in self.long:
  9. self.liquidate(long)
  10. for short in short_stocks:
  11. if short not in self.short:
  12. self.liquidate(short)
  13. long_weight = 0.8/len(self.long)
  14. for i in self.long:
  15. self.set_holdings(i, long_weight)
  16. short_weight = 0.8/len(self.short)
  17. for i in self.short:
  18. self.set_holdings(i, -short_weight)
+ Expand


Reference

  1. Quantpedia - Earnings Quality Factor

Author

Jing Wu

August 2018