Introduction
The momentum effect states that what was strongly going up in the near past will probably continue to go up shortly. It is one of the most used trading anomalies, but the strategy using only the momentum can suffer significant drawdowns sometimes.
Some research papers show that the return of momentum strategies depend on the overall market conditions. This state of the market can be defined in various ways like the investors' sentiment, prior market returns and so on. Therefore, this algorithm will combine the momentum effect with the market state filter to turn off the momentum trading in down market state times.
Method
As we know, a stock market index tracks the price changes of a select group of stocks and compiles those stock price changes into a single value. For example, S&P500 is composed of only 500 large-cap stocks. A broad market index is characterized by including stocks from companies of all sizes(large, mid and small-cap based on their values). The most popular U.S. broad market indexes include the Russell 3000, the Wilshire 5000 Total Market Index and the MSCI U.S. Broad Market Index. Those broad-based market indexes attempt to cover the entire market and their return can be a good benchmark of the current market state.
class MomentumandStateofMarkeFiltersAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2011, 1, 1)
self.set_end_date(2018, 8, 1)
self.set_cash(100000)
# add Wilshire 5000 Total Market Index data
self.wilshire_symbol = self.add_data(Fred, Fred.wilshire.price5000, Resolution.DAILY).symbol
self.w5000_return = self.ROC(self.wilshire_symbol, 252)
# initialize the RateOfChange indicator of Wilshire 5000 total market index
history = self.history(self.wilshire_symbol, 500, Resolution.DAILY)
for tuple in history.loc[self.wilshire_symbol].itertuples():
self.w5000_return.update(tuple.index, tuple.value)
In this algorithm, we choose the Wilshire 5000 Total Market Index to be the market state measure. For the period of index return, longer horizons should capture more dramatic changes in the state of the market, but longer horizons also reduce the number of observations of changes in the market's state. Here we choose 12 months return according to the paper Market States and Momentum from Guttierez, Cooper and Hameed. The daily index price comes from the US Federal Reserve (FRED) dataset.
The investment universe contains all stocks on NYSE and NASDAQ with a price higher than $1. We use the momentum percent indicator to gauge the momentum effect.
In the CoarseSelectionFunction, the MOMP
indicator value for each Symbol in coarse
is updated with the adjusted price and saved in the dictionary self.momp
.
def coarse_selection_function(self, coarse):
coarse = [x for x in coarse if (x.has_fundamental_data and x.adjusted_price > 1)]
for i in coarse:
if i.symbol not in self.momp:
self.momp[i.symbol] = SymbolData(i.symbol, self.lookback, self)
else:
self.momp[i.symbol].MOMP.update(self.time, i.adjusted_price)
When the indicator is ready, stocks are then sorted based on the previous six months momentum percent value.
Top 20 Stocks with the highest MOMP
are in the long stock list, 20 Stocks with the lowest MOMP
are in the short stock list.
self.m_o_m_p_ready = {symbol: SymbolData for symbol, SymbolData in self.momp.items() if SymbolData.MOMP.is_ready}
if self.m_o_m_p_ready:
# sort stocks by 6 months' momentum
sort_by_m_o_m_p = sorted(self.m_o_m_p_ready, key = lambda x: self.m_o_m_p_ready[x].MOMP.current.value, reverse = True)
self.long = sort_by_m_o_m_p[:20]
self.short = sort_by_m_o_m_p[-20:]
return self.long+self.short
The strategy is rebalanced monthly. At the beginning of each month, we identify the state of the market. If the market’s one-year return is positive, we define the state of the market as "UP" otherwise the state is "DOWN". When the market is in "UP" state, we go long on the previous six-month winners (highest Momentum) and goes short on the last six-month losers (lowest Momentum). Stocks are equally weighted. If the market is in "DOWN" state, we liquidate all asset holdings and invest in the long-term Treasury bond ETF to control the downside risk.
def on_data(self, data):
if self.month_start and self.selection:
self.month_start = False
self.selection = False
if self.long is None or self.short is None: return
# if the previous 12 months return on the broad equity market was positive
if self.w5000_return.current.value > 0:
stocks_invested = [x.key for x in self.portfolio if x.value.INVESTED]
for i in stocks_invested:
if i not in self.long+self.short:
self.liquidate(i)
short = [symbol for symbol in self.short if symbol in data.bars]
short_weight = 0.5/len(short)
# goes short on the prior six-month losers (lowest decile)
for short_symbol in short:
self.set_holdings(short_symbol, -short_weight)
# goes long on the prior six-month winners (highest decile)
long = [symbol for symbol in self.long if symbol in data.bars]
long_weight = 0.5/len(long)
for long_symbol in long:
self.set_holdings(long_symbol, long_weight)
else:
self.liquidate()
self.set_holdings(self.tlt, 1)
Derek Melchin
See the attached backtest for an updated version of the algorithm with the following changes:
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Jing Wu
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!