| Overall Statistics |
|
Total Orders 16 Average Win 11.88% Average Loss -17.15% Compounding Annual Return 2.740% Drawdown 27.200% Expectancy 0.451 Start Equity 100000 End Equity 160180.14 Net Profit 60.180% Sharpe Ratio 0.059 Sortino Ratio 0.031 Probabilistic Sharpe Ratio 0.006% Loss Rate 14% Win Rate 86% Profit-Loss Ratio 0.69 Alpha -0.008 Beta 0.197 Annual Standard Deviation 0.095 Annual Variance 0.009 Information Ratio -0.388 Tracking Error 0.16 Treynor Ratio 0.029 Total Fees $38.74 Estimated Strategy Capacity $320000.00 Lowest Capacity Asset EPOL UMWMI2IDARTX Portfolio Turnover 0.07% |
# https://quantpedia.com/strategies/value-factor-effect-within-countries/
#
# The investment universe consists of 32 countries with easily accessible equity markets (via ETFs, for example). At the end of every year,
# the investor calculates Shiller’s “CAPE” Cyclically Adjusted PE) ratio, for each country in his investment universe. CAPE is the ratio of
# the real price of the equity market (adjusted for inflation) to the 10-year average of the country’s equity index (again adjusted for inflation).
# The whole methodology is explained well on Shiller’s home page (http://www.econ.yale.edu/~shiller/data.htm) or
# http://turnkeyanalyst.com/2011/10/the-shiller-pe-ratio/). The investor then invests in the cheapest 33% of countries from his sample if those
# countries have a CAPE below 15. The portfolio is equally weighted (the investor holds 0% cash instead of countries with a CAPE higher than 15)
# and rebalanced yearly.
#region imports
from AlgorithmImports import *
#endregion
class ValueFactorCAPEEffectwithinCountries(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2008, 1, 1)
self.SetCash(100000)
self.symbols = {
"Australia" : "EWA", # iShares MSCI Australia Index ETF
"Brazil" : "EWZ", # iShares MSCI Brazil Index ETF
"Canada" : "EWC", # iShares MSCI Canada Index ETF
"Switzerland" : "EWL", # iShares MSCI Switzerland Index ETF
"China" : "FXI", # iShares China Large-Cap ETF
"France" : "EWQ", # iShares MSCI France Index ETF
"Germany" : "EWG", # iShares MSCI Germany ETF
"Hong Kong" : "EWH", # iShares MSCI Hong Kong Index ETF
"Italy" : "EWI", # iShares MSCI Italy Index ETF
"Japan" : "EWJ", # iShares MSCI Japan Index ETF
"Korea" : "EWY", # iShares MSCI South Korea ETF
"Mexico" : "EWW", # iShares MSCI Mexico Inv. Mt. Idx
"Netherlands" : "EWN", # iShares MSCI Netherlands Index ETF
"South Africa" : "EZA", # iShares MSCI South Africe Index ETF
"Singapore" : "EWS", # iShares MSCI Singapore Index ETF
"Spain" : "EWP", # iShares MSCI Spain Index ETF
"Sweden" : "EWD", # iShares MSCI Sweden Index ETF
"Taiwan" : "EWT", # iShares MSCI Taiwan Index ETF
"UK" : "EWU", # iShares MSCI United Kingdom Index ETF
"USA" : "SPY", # SPDR S&P 500 ETF
"Russia" : "ERUS", # iShares MSCI Russia ETF
"Israel" : "EIS", # iShares MSCI Israel ETF
"India" : "INDA", # iShares MSCI India ETF
"Poland" : "EPOL", # iShares MSCI Poland ETF
"Turkey" : "TUR" # iShares MSCI Turkey ETF
}
self.quantile:int = 3
self.max_missing_days:int = 31
self.leverage:int = 2
for country, etf_symbol in self.symbols.items():
data = self.AddEquity(etf_symbol, Resolution.Daily)
data.SetLeverage(self.leverage)
data.SetFeeModel(CustomFeeModel())
# CAPE data import.
self.cape_data = self.AddData(CAPE, 'CAPE', Resolution.Daily).Symbol
self.recent_month:int = -1
def OnData(self, data:Slice) -> None:
if self.Time.month == self.recent_month:
return
self.recent_month = self.Time.month
if self.recent_month != 12:
return
price = {}
for country, etf_symbol in self.symbols.items():
if etf_symbol in data and data[etf_symbol]:
# cape data is still comming in
if self.Securities[self.cape_data].GetLastData() and (self.Time.date() - self.Securities[self.cape_data].GetLastData().Time.date()).days <= self.max_missing_days:
country_cape = self.Securities['CAPE'].GetLastData().GetProperty(country)
if country_cape < 15. and country_cape != 0.:
price[etf_symbol] = data[etf_symbol].Value
long = []
# Cape and price sorting.
if len(price) >= self.quantile:
sorted_by_price = sorted(price.items(), key = lambda x: x[1], reverse = True)
tercile = int(len(sorted_by_price) / self.quantile)
long = [x[0] for x in sorted_by_price[-tercile:]]
# Trade execution.
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long:
self.Liquidate(symbol)
for symbol in long:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, 1 / len(long))
# NOTE: IMPORTANT: Data order must be ascending (datewise)
# Data source: https://indices.barclays/IM/21/en/indices/static/historic-cape.app
class CAPE(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/economic/cape_by_country.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
_header_columns:List[str] = []
def Reader(self, config, line, date, isLiveMode):
data = CAPE()
data.Symbol = config.Symbol
if not line[0].isdigit():
CAPE._header_columns = line.split(',')[1:]
return None
split = line.split(',')
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
for i, col in enumerate(CAPE._header_columns):
if split[i+1] != '':
data[col] = float(split[i+1])
else:
data[col] = 0.
data.Value = float(split[1])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))