Overall Statistics |
Total Trades 607 Average Win 0.70% Average Loss -0.96% Compounding Annual Return 9.393% Drawdown 26.700% Expectancy 0.423 Net Profit 255.214% Sharpe Ratio 0.65 Probabilistic Sharpe Ratio 5.074% Loss Rate 18% Win Rate 82% Profit-Loss Ratio 0.74 Alpha 0.022 Beta 0.463 Annual Standard Deviation 0.108 Annual Variance 0.012 Information Ratio -0.29 Tracking Error 0.116 Treynor Ratio 0.152 Total Fees $1602.61 Estimated Strategy Capacity $1700000.00 Lowest Capacity Asset BIL TT1EBZ21QWKL |
''' Hybrid Asset Allocation by Wouter Keller See: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4346906 This is the "balanced" version from the paper ''' # region imports from AlgorithmImports import * import numpy as np # endregion class HAA(QCAlgorithm): def Initialize(self): self.SetStartDate(2009, 1, 1) # Set Start Date self.SetCash(100000) # Set Strategy Cash # Asset lists self.selO = ['SPY','IWM','VWO','VEA','VNQ','DBC','IEF','TLT'] self.selD = ['BIL', 'IEF'] self.selP = ['TIP'] # Add assets self.eqs = list(set(self.selO+self.selD+self.selP)) for eq in self.eqs: self.AddEquity(eq,Resolution.Minute) # Algo Parameters self.TO, self.TD = [4,1] self.mom_prds = [1,3,6,12] #1, 3, 6, and 12 months. self.hprd = np.max(self.mom_prds)*35 # Scheduler self.Schedule.On(self.DateRules.MonthStart(self.selO[0]), self.TimeRules.AfterMarketOpen(self.selO[0],30), self.rebal) self.Trade = True def rebal(self): self.Trade = True def OnData(self, data): if self.Trade: # Get price data and trading weights h = self.History(self.eqs,self.hprd,Resolution.Daily)['close'].unstack(level=0) wts = self.trade_wts(h) # trade port_tgt = [PortfolioTarget(x,y) for x,y in zip(wts.index,wts.values)] self.SetHoldings(port_tgt) self.Trade = False def trade_wts(self,hist): # initialize wts Series wts = pd.Series(0,index=hist.columns) # end of month values h_eom = (hist.loc[hist.groupby(hist.index.to_period('M')).apply(lambda x: x.index.max())] .iloc[:-1,:]) # ===================================== # check if canary universe is triggered # ===================================== # build dataframe of momentum values mom = h_eom.iloc[-1,:].div(h_eom.iloc[[-p-1 for p in self.mom_prds],:],axis=0)-1 mom_avg = mom.mean(axis=0) # Determine number of canary securities with negative momentum n_canary = np.sum(mom_avg[self.selP]<0) # % equity offensive pct_in = 1-n_canary # ===================================== # get weights for offensive and defensive universes # ===================================== # determine weights of offensive universe if pct_in > 0: # price / SMA mom_in = mom_avg.loc[self.selO].sort_values(ascending=False) # equal weightings to top relative momentum securities in_wts = pd.Series(pct_in/self.TO,index=mom_in.index[:self.TO]) wts = pd.concat([wts,in_wts]) # determine weights of defensive universe if pct_in < 1: # price / SMA mom_out = mom_avg.loc[self.selD].sort_values(ascending=False) # equal weightings to top relative momentum securities out_wts = pd.Series((1-pct_in)/self.TD,index=mom_out.index[:self.TD]) wts = pd.concat([wts,out_wts]) wts = wts.groupby(wts.index).sum() return wts