Introduction

Equity valuation may be a predictive signal for future Equity return. There are various methodologies to evaluate whether the Equity is undervalued or overvalued using metrics like price-to-earnings (P/E), return on equity (ROE), dividend yield, book-to-equity and so on. In this algorithm, we use a ten-year normalized earnings metrics invented by Yale University professor Robert Shiller to find the fair value of the Equity market.

Method

The cyclically adjusted price-to-earnings ratio (CAPE) compares the stock prices with earnings smoothed across multiple years. It is the price divided by the average of ten years of earnings (moving average), adjusted for inflation. The backward-looking earnings smooth out the economic cycle as well as the price fluctuations.

The investment universe consists of 26 countries with easily accessible Equity markets via ETFs. We import the custom CAPE ratio (Shiller PE Ratio) data of those 26 countries and create a dictionary to save the corresponding country ETF. This custom data is from Barclays Indices is in monthly resolution and starts January 1982.

  1. class CAPE(PythonData):
  2. def get_source(self, config, date, isLiveMode):
  3. return SubscriptionDataSource("https://indices.cib.barclays/file.app?action=shared&path=shiller/cape.csv", SubscriptionTransportMedium.REMOTE_FILE)
  4. def reader(self, config, line, date, isLiveMode):
  5. if not (line.strip() and line[1].replace('.', '', 1).isdigit()): return None
  6. try:
  7. index = CAPE()
  8. index.symbol = config.symbol
  9. data = line.split(',')
  10. index.time = datetime.strptime(data[0], "%d/%m/%Y")
  11. symbols = Symbols().tickers
  12. for key, value in symbols.items():
  13. index[key] = float(data[value[0]]) if data[value[0]] else 100000 # very large number to avoid be selected
  14. return index
  15. except:
  16. return None
  17. class Symbols:
  18. def __init__(self):
  19. # the indiex is the country name
  20. # the first element of the value is the column number of CAPE ratio value in custom dataset
  21. # the second element of the value is the corresponding country ETF
  22. self.tickers = {"Australia":[1, "EWA"], # ASX All Ordinaries Index: iShares MSCI Australia ETF
  23. "Brazil":[2, "EWZ"], # Indice Bovespa (Ibovespa): iShares MSCI Brazil ETF
  24. "Canada":[3, "XIC"], # S&P/TSX Composite Index: iShares S&P TSX Capped Cmpst Indx Fnd
  25. "China":[4, "MCHI"], # SSE Composite: iShares MSCI China Index Fund
  26. "Europe":[5, "IEUR"], # STOXX Europe 600 Index: iShares Core MSCI Europe ETF
  27. "France":[6, "EWQ"], # CAC 40 Index: iShares MSCI France ETF
  28. "Germany":[7, "EWG"], # HDAX Index: iShares MSCI Germany ETF
  29. "Hong Kong":[8, "EWH"], # Hang Seng Index: iShares MSCI Hong Kong Index Fund
  30. "Italy":[9, "EWI"], # FTSE MIB Index: iShares MSCI Italy ETF
  31. "India":[10, "INDY"], # NIFTY 50 Index: iShares India 50 ETF
  32. "Israel":[11, "EIS"], # Tel Aviv 125 Index: iShares MSCI Israel ETF
  33. "Japan":[12, "EWJ"], # All Public Companies: iShares MSCI Japan ETF
  34. "Korea":[13,"EWY"], # KOSPI Index: iShares MSCI South Korea ETF
  35. "Mexico":[14, "EWW"], # &P/BMV IPC Index: iShares MSCI Mexico ETF
  36. "Netherlands":[15, "EWN"], # NL 25 Index: iShares MSCI Netherlands ETF
  37. "Poland":[16, "EPOL"], # WIG Index: iShares MSCI Poland ETF
  38. "Russia":[17, "ERUS"], # RTS Index: iShares MSCI Russia ETF
  39. "Singapore":[18, "EWS"], # STI Index: iShares MSCI Singapore ETF
  40. "Southafrica":[19, "EZA"], # FTSE/JSE CAP Top 40 Index: iShares MSCI South Africa ETF
  41. "Spain":[20, "EWP"], # IBEX 35 Index: iShares MSCI Spain ETF
  42. "Sweden":[21, "EWD"], # OMXS 30 index: iShares MSCI Sweden ETF
  43. "Switzerland":[22, "EWL"], # CH 20 index: iShares MSCI Switzerland ETF
  44. "Taiwan":[23, "EWT"], # TWSE: iShares MSCI Taiwan ETF
  45. "Turkey":[24, "TUR"], # BIST 100: iShares MSCI Turkey ETF
  46. "UK":[25, "EWU"], # FTSE 100 Index: iShares MSCI United Kingdom ETF
  47. "USA":[26, "SPY"] # S&P 500 Index: SPDR S&P 500 ETF
  48. }
+ Expand

According to the academic research of Shiller and Campbell, using market data from the S&P index, the lower the CAPE, the higher the investors' likely return from Equities. Therefore, the algorithm then invests in the cheapest 33% of countries from the sample with the lowest CAPE ratio if those countries have a CAPE below 15. If there are no countries with CAPE lower than 15, the algorithm holds cash instead of country ETFs. The portfolio is equally weighted and rebalanced monthly by every update of the CAPE data.

  1. def on_data(self, data):
  2. if data.contains_key(self.symbol):
  3. cape = {}
  4. for key, value in self.symbols.items():
  5. cape[value[1]] = data[self.symbol].get_property(key)
  6. sorted_cape = sorted(cape, key = lambda x: cape[x])
  7. # invests the cheapest 33% of countries if those countries have a CAPE below 15
  8. lowest_cape = sorted_cape[:int(1/3*len(sorted_cape))]
  9. long_list = [i for i in lowest_cape if cape[i] < 15]
  10. invested = [x.key for x in self.portfolio if x.value.INVESTED]
  11. for i in invested:
  12. if i.value not in long_list:
  13. self.liquidate(i)
  14. for i in long_list:
  15. self.set_holdings(i, 1/len(long_list))
+ Expand


Reference

  1. Quantpedia - Value Effect within Countries
  2. Barclays Indices - Historic CAPE Ratio by country

Author

Jing Wu

September 2018