# https://quantpedia.com/strategies/riding-industry-bubbles/
#
# The investment universe consists of equity industry funds (or ETFs) which are proxy for equity industry indexes. Investor uses 10 years of
# past data to calculate industry’s alpha based on CAPM model (from the regression model industry_return = alpha + beta*market return, it is
# possible to use alternative models like the Fama/French 3 factor model). A bubble in an industry is detected if the industry’s alpha is
# statistically significant (source academic paper uses 97,5% significance threshold, but it is possible to use other values). Investor is
# long in each industry experiencing a bubble by applying 1/N rule (investment is divided equally between industries in bubble). If no bubble
# is detected then he/she makes no investment. Data examination, alpha calculation and portfolio rebalancing is done on monthly basis.
from collections import deque
import numpy as np
class Riding_Industry_Bubbles(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2008, 1, 1)
self.SetCash(100000)
self.spy = 'SPY'
self.symbols = ['XLF', 'XLV', 'XLP', 'XLY', 'XLI', 'XLE', 'XLB', 'XLK', 'XLU']
self.period = 10 * 12 * 21
self.SetWarmUp(self.period)
# Daily price data.
self.data = {}
for symbol in self.symbols + [self.spy]:
data = self.AddEquity(symbol, Resolution.Daily)
self.data[symbol] = deque(maxlen=self.period)
self.Schedule.On(self.DateRules.MonthStart(self.symbols[0]), self.TimeRules.AfterMarketOpen(self.symbols[0]), self.Rebalance)
def OnData(self, data):
for symbol in self.symbols + [self.spy]:
if self.Securities.ContainsKey(symbol):
price = self.Securities[symbol].Price
if price != 0:
self.data[symbol].append(price)
else:
# Append latest price as a next one in case there's 0 as price.
if len(self.data[symbol]) > 0:
last_price = self.data[symbol][-1]
self.data[symbol].append(last_price)
def Rebalance(self):
if len(self.data[self.spy]) != self.data[self.spy].maxlen: return
market_closes = [x for x in self.data[self.spy]]
separete_months = [market_closes[x:x+21] for x in range(0, len(market_closes),21)]
market_monthly_returns = []
for month in separete_months:
market_monthly_returns.append(self.Return([x for x in month]))
market_returns_mean = np.mean(market_monthly_returns)
t_stat = {}
for symbol in self.symbols:
if len(self.data[symbol]) == self.data[symbol].maxlen:
closes = [x for x in self.data[symbol]]
separete_months = [closes[x:x+21] for x in range(0, len(closes),21)]
etf_monthly_returns = []
for month in separete_months:
etf_monthly_returns.append(self.Return([x for x in month]))
etf_returns_mean = np.mean(etf_monthly_returns)
# t-stat calc.
diffs = []
for a,b in zip(etf_monthly_returns, market_monthly_returns):
diffs.append(a-b)
diff_std = np.std(diffs)
t_stat[symbol] = (etf_returns_mean - market_returns_mean) / (diff_std / np.sqrt(len(etf_monthly_returns)))
long = []
if len(t_stat) != 0:
long = [x[0] for x in t_stat.items() if x[1] >= 2]
# Trade execution.
invested = [x.Key.Value 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:
self.SetHoldings(symbol, 1 / len(long))
def Return(self, history):
return (history[-1] - history[0]) / history[0]