MOMENTUM Investing Strategy on NASDAQ Stocks with Python [MUST READ]

Lachezar Haralampiev
Quant Factory
Published in
7 min readDec 25, 2022

Recreating a Strategy on Nasdaq 100 Index with Yearly Re-Construction of Equally Weighted Portfolio with Python.

Thank you for being part of my community, I had a lot of requests about a backtest of a Nasdaq-related strategy. Thus, in this article, I am exploring a strategy that identifies the top 5 momentum stocks each year and invests in them.

Momentum investing is a strategy that involves buying securities that have recently performed well, on the assumption that they will continue to do so in the future. This is based on the idea that stocks that are trending upward tend to continue doing so, while those that are trending downwards tend to continue going down.

There are several ways to approach momentum investing, but a common way would be to use technical indicators which estimate momentum and then form a strategy based on those estimates. Some common momentum indicators are the MACD, RSI, and Stochastic Oscillator. However, in this article, we are going to use another one, namely the one-year return at the end of each year(1st of January to 31st of December).

Project Key Features

  • Scrape and download all Nasdaq 100 index constituents into a list.
  • Download daily prices for the last 10 years for all the stocks in the constituents list.
  • At the end of each year, we will determine the total return for each stock in the Nasdaq 100 index, ranking the stocks and selecting the top 5 to form an equally-weighted portfolio.
  • We will hold this portfolio for the entire next year, repeating this process for each subsequent year.
  • Then we will calculate the return for the entire research period.
  • Finally, we will compare the performance of this portfolio to the return of the NASDAQ 100 index itself.

Let’s Get Started

Let’s load the packages we would need for the whole project.

import requests
import pandas as pd
import yfinance as yf
import numpy as np
from datetime import *
import matplotlib.pyplot as plt
plt.style.use('ggplot')

1. Scrape And Download Nasdaq 100 Constituents Symbols.

nasdaq_request = pd.read_html('https://en.wikipedia.org/wiki/Nasdaq-100')

nasdaq_stocks = nasdaq_request[4]['Ticker'].unique().tolist()

print(nasdaq_stocks)

⚠️Using this list may introduce a survivorship bias because it includes only the stocks which are the most recent constituents and not the ones which were historically (over the years, some stocks are taken out of the index and new ones included). However, overcoming the survivorship bias is out of the scope of this article. ⚠️

In this step, we are reading directly the wikipedia html page and getting all tickers. After we receive the result of the request, we convert it into a list of unique symbols.

2. Download Daily Prices For The Last 10 Years For All The Stocks In The Constituents List And Calculate Yearly Return.

end_date = date.today()

start_date = end_date-pd.DateOffset(365*10)

prices_df = yf.download(tickers=nasdaq_stocks, start=start_date, end=end_date)

prices_df = prices_df.stack()

prices_df.index.names = ['Date', 'symbol']

prices_df

In the second step, we are first defining the end_date to be the date today, then we calculate the start_date to be the date exactly 10 years ago (365 days times 10 years). Then we download all the symbols prices and do .stack() to stack the symbols into a Multi-Index, then we calculate the 1-year rolling return (.pct_change(252))for each company into the.groupby().transform() by first grouping on the company level and then supplying the lambda function into the transform method. Finally, we are setting the index names.

3. Aggregate On The Yearly And Symbol Level And Get The Last Return For The Given Year For Each Company, Then Rank The Companies By Their Yearly Return.

4. Filter Only Top 5 Companies Per Momentum Rank At The End Of Each Year.

filtered_df = yearly_data[yearly_data['rank']<6][:'2021']

filtered_df[['yearly_return', 'rank']].head(20)

Filter out only the Top 5 companies at the end of each year, then select all companies up to the end of 2021. Now we can use those 5 companies, at the beginning of the next year and form portfolios with equal weights.

5. Move The Index With 1 Day In The Future To At The Begining Of Next Year For Convenience.

filtered_df = filtered_df.reset_index(level=1)

filtered_df.index = filtered_df.index + pd.DateOffset(1)

filtered_df = filtered_df.reset_index().set_index(['Date', 'symbol']).unstack().stack()

filtered_df[['yearly_return', 'rank']].head(20)

Next, we move the index to 1 day in the future for convenience so we can use those companies for calculating the portfolio return for the corresponding year. Again they were estimated exactly on the last day of each year, and then on the first day of the new year, we are forming the portfolios with those companies and holding them until the next year.

6. Get A Dictionary With Each Year As A Key And A Corresponding List Of Stocks.

dates = filtered_df.index.get_level_values('Date').unique().tolist()

fixed_dates ={}

for d in dates:

fixed_dates[d.strftime('%Y-%m-%d')] = filtered_df.xs(d, level=0).index.tolist()

fixed_dates

In this step, we are getting the unique dates from the datetime index into a list of dates. Next, we are creating an empty dictionary which we are going to fill with the list of the stocks in which we would invest for the corresponding year. After, that we are just looping over the dates and then adding the list of stocks for the given key (date).

7. Calculate Returns For All Stocks Using Already Downloaded Prices In The Begining. Then For Each Year Filter Out Only The Returns Of The Stocks We Are Investing In And Then Calculate The Mean Return As We Have An Equally Weighted Portfolio.

⚠️If you want to learn more about quant trading and how to break into the algorithmic trading industry.⚠️

8. Download Data For QQQ ETF As A Proxy For Nasdaq And Calculate Daily Returns, So We Can Compare Our Portfolio And Nasdaq Index.

qqq_df = yf.download(tickers='QQQ',
start=portfolio_df.index.min(),
end=portfolio_df.index.max())['Adj Close'].pct_change().dropna().to_frame('nasdaq_returns')

final_df = portfolio_df.merge(qqq_df, right_index=True, left_index=True)

final_df

9. Visualize The Results And Compare Both Portfolios.

Setting the Out-Of-Sample start date to be the 1st of January 2020:

Strategy Most Important Metrics:

Conclusion

In this article, we explored how to re-create and backtest a momentum investment strategy based on the Nasdaq 100 index’s top-performing stocks with a rebalancing period of 1-year.

Our analysis showed that the portfolio of the top 5 performing stocks during last year is massively over-performing the Nasdaq 100 index itself. This suggests that it may be worth considering further research on the potential investment strategy based on this analysis. However, an apparent issue is the Survivorship Bias which we did not handle here and it is out of the scope of this article. Furthermore, the portfolio formation is based on equal weights, in reality, you may want to use a more sophisticated method of portfolio formation, namely: risk-parity, efficient frontier, hierarchical risk-parity or market cap weighted.

I hope this article is helpful! Let me know if you have any questions or if you would like further information on any of the topics covered.

A Message from QuantFactory:

Thank you for being part of our community! Before you go:

  • Master Quantitative Research with Python👉 here.
  • Join our Premium Discord Server👉 here.
  • Subscribe to our Exclusive Stocks Newsletter 👉 here.
  • If you liked the story feel free to clap 👏 and follow the author
  • For more stories from the Algorithmic Trading and Quantitative Analysis world you should follow the Quant Factory Publication

*Note that this article does not provide personal investment advice and I am not a qualified licensed investment advisor. All information found here is for entertainment or educational purposes only and should not be construed as personal investment advice.

--

--