Introduction
The Momentum traders take on a long or short position in the stock in the hopes that the momentum will continue in the same direction. The momentum strategy has a higher degree of volatility than most other strategies. In the tutorial, we'll introduce the trading volume factor to enhance the portfolio return and control the risk of momentum strategy.
Method
The investment universe consists of all stocks on NYSE and NASDAQ. The momentum is usually defined by the stock return in the last \(N\) months. The RateOfChange indicator is constructed to represent the return. The lookback period is one year.
class SymbolData:
def __init__(self, symbol, lookback):
self.symbol = symbol
self.ROC = RateOfChange(lookback)
self.volume = None
The ROC
indicator and volume are updated everyday in the CoarseSelectionFunction. Securities which do not have fundamental data are eliminated.
def coarse_selection_function(self, coarse):
for i in coarse:
if i.symbol not in self.data_dict:
self.data_dict[i.symbol] = SymbolData(i.symbol, self.lookback)
self.data_dict[i.symbol].ROC.update(i.end_time, i.adjusted_price)
self.data_dict[i.symbol].volume = i.volume
if self.monthly_rebalance:
# drop stocks which have no fundamental data
filtered_coarse = [x for x in coarse if (x.has_fundamental_data)]
return [i.symbol for i in filtered_coarse]
else:
return []
Trading volume (turnover) serves as a useful indicator of the level of investor interest in stocks. The number of sellers tends to exceed the number of buyers when the stock falls into disfavor, which will lead to a falling share price. When a stock is popular, the number of buyers exceeds the number of sellers, the price tends to rise. As a result, a firm’s turnover may be a measure of investors' interest in the firm's stock so it could help to identify the future trend of stocks. Here the turnover is calculated as the ratio of the number of shares traded each day to the number of shares outstanding.
In the FineSelectionFunction, after the indicator is ready for all symbols in the self.data_dict
dictionary, turnover is calculated
with the Volume
and EarningReports.basic_average_shares
.
def fine_selection_function(self, fine):
if self.monthly_rebalance:
data_ready = {symbol: symbolData for (symbol, symbolData) in self.data_dict.items() if symbolData.ROC.is_ready}
if len(data_ready) < 100:
self.filtered_fine = []
else:
sorted_fine = [i for i in fine if i.earning_reports.basic_average_shares.three_months != 0 and i.symbol in data_ready]
sorted_fine_symbols = [i.symbol for i in sorted_fine]
filtered_data = {symbol: symbolData for (symbol, symbolData) in data_ready.items() if symbol in sorted_fine_symbols}
for i in sorted_fine:
if i.symbol in filtered_data and filtered_data[i.symbol].volume != 0:
filtered_data[i.symbol].turnover = i.earning_reports.basic_average_shares.three_months / filtered_data[i.symbol].volume
Stocks are sorted into deciles every month based on previous 12-month returns. Each momentum decile is then divided into terciles based on turnover. The long and short portfolios are constructed with the highest turnover from the top momentum decile and the highest volume from the bottom momentum decile, respectively.
for i in sortedFine:
if i.symbol in filteredData and filteredData[i.symbol].volume != 0:
filteredData[i.symbol].turnover = i.earning_reports.basic_average_shares.three_months / filteredData[i.symbol].volume
sortedByROC = sorted(filteredData.values(), key = lambda x: x.ROC.current.value, reverse = True)
topROC = sortedByROC[:int(len(sortedByROC)*0.2)]
bottomROC = sortedByROC[-int(len(sortedByROC)*0.2):]
HighTurnoverTopROC = sorted(topROC, key = lambda x: x.turnover, reverse = True)
HighTurnoverBottomROC = sorted(bottomROC, key = lambda x: x.turnover, reverse = True)
self.long = [i.symbol for i in HighTurnoverTopROC[:int(len(HighTurnoverTopROC)*0.01)]]
self.short = [i.symbol for i in HighTurnoverBottomROC[:int(len(HighTurnoverBottomROC)*0.01)]]
self.filtered_fine = self.long + self.short
self.portfolios.append(self.filtered_fine)
A long-short portfolio is held for three months, and then it is rebalanced.
Therefore, the investor buys 1/3 of its portfolio for three consecutive months and rebalances 1/3 of its portfolio each month.
We save each portfolio in a deque list self.portfolios
and liquidate the portfolio three-months ago when rebalancing.
def on_data(self, data):
if self.monthly_rebalance and self.filtered_fine:
self.filtered_fine = None
self.monthly_rebalance = False
# 1/3 of the portfolio is rebalanced every month
if len(self.portfolios) == self.portfolios.maxlen:
for i in list(self.portfolios)[0]:
self.liquidate(i)
# stocks are equally weighted and held for 3 months
short_weight = 1/len(self.short)
for i in self.short:
self.set_holdings(i, -1/3*short_weight)
long_weight = 1/len(self.long)
for i in self.long:
self.set_holdings(i, 1/3*long_weight)
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!