Overall Statistics
Total Orders
92
Average Win
4.63%
Average Loss
-3.26%
Compounding Annual Return
4.029%
Drawdown
26.600%
Expectancy
0.493
Start Equity
100000
End Equity
194559.53
Net Profit
94.560%
Sharpe Ratio
0.174
Sortino Ratio
0.099
Probabilistic Sharpe Ratio
0.078%
Loss Rate
38%
Win Rate
62%
Profit-Loss Ratio
1.42
Alpha
0.002
Beta
0.19
Annual Standard Deviation
0.087
Annual Variance
0.008
Information Ratio
-0.349
Tracking Error
0.156
Treynor Ratio
0.079
Total Fees
$813.55
Estimated Strategy Capacity
$57000000.00
Lowest Capacity Asset
XLK RGRPZX100F39
Portfolio Turnover
1.20%
# 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.
#
# QC Implementation:

import numpy as np
from AlgorithmImports import *
import statsmodels.api as sm

class RidingIndustryBubbles(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2008, 1, 1)
        self.SetCash(100000)

        self.market = '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.market]:
            data = self.AddEquity(symbol, Resolution.Daily)
            self.data[symbol] = RollingWindow[float](self.period)
        
        # self.settings.daily_precise_end_time = False
        # self.settings.minimum_order_margin_portfolio_percentage = 0.

        self.selection_flag = False
        self.schedule.on(self.date_rules.month_start(self.market),
                        self.time_rules.after_market_open(self.market),
                        self.selection)

    def OnData(self, data):
        # Store daily price data.
        for symbol in self.symbols + [self.market]:
            symbol_obj = self.Symbol(symbol)
            if symbol_obj in data and data[symbol_obj]:
                self.data[symbol].Add(data[symbol_obj].Value)
        
        if not self.selection_flag:
            return
        self.selection_flag = False

        if not self.data[self.market].IsReady and self.market in data: return
    
        market_closes = [x for x in self.data[self.market]]
        separete_months = [market_closes[x:x+21] for x in range(0, len(market_closes),21)]
        market_monthly_returns = []
        for month in separete_months:
            month_of_prices = [x for x in month]
            market_monthly_returns.append(month_of_prices[0] / month_of_prices[-1] - 1)
        
        # Prepared for regression.    
        market_monthly_returns = np.array(market_monthly_returns).T
        market_monthly_returns = sm.add_constant(market_monthly_returns)
        
        t_stat = {}
        for symbol in self.symbols:
            if self.data[symbol].IsReady and symbol in data:
                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:
                    month_of_prices = [x for x in month]
                    etf_monthly_returns.append(month_of_prices[0] / month_of_prices[-1] - 1)
                
                # alpha t-stat calc.
                model = sm.OLS(etf_monthly_returns, market_monthly_returns)
                
                results = model.fit()
                alpha_tstat = results.tvalues[0]
                alpha_pvalue = results.pvalues[0]
                t_stat[symbol] = (alpha_tstat, alpha_pvalue)
        
        long = []
        if len(t_stat) != 0:
            long = [x[0] for x in t_stat.items() if x[1][0] >= 2 and x[1][1] >= 0.025] # The result is statistically significant, by the standards of the study, when p ≤ α
        
        # 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 selection(self) -> None:
        self.selection_flag = True