Picking Stocks with a Quantitative Momentum Strategy in Python

A simple yet useful method to optimize the process of choosing stocks




Disclaimer: This article is strictly for educational purposes and should not be taken as an investment tip.


Introduction

While I was an amateur trader, the process of choosing the right stocks to trade was a nightmare. News on stocks, uncertainty, and emotions adds to the bitterness of this process. A long way ahead, today, I found my own solution using my best companion Python. In this article, we are going to build a simple quantitative momentum strategy in python that filters and picks out the best intraday stocks. But wait, what is a quantitative momentum strategy?


A Quantitative Momentum strategy is a strategy implemented to choose stocks that have increased in price the most. Simply speaking, it is the process of identifying stocks with a great uptrend. Now that we have built some understanding of what quantitative momentum strategy is and how it can be used to pick stocks. Let’s implement the strategy in python!


Python Implementation


The steps involved in this article are:


- Importing the required packages
- Extracting the list of all S&P 500 stock's symbols
- Pulling Intraday data of all the stocks in the S&P 500
- Calculating percentage change and momentum of all stocks
- Finding stocks with greater momentum
- Backtesting with a equal-weight portfolio

Step-1: Importing Packages


The primary packages of this article are the Pandas package to deal with data, the NumPy package to work with arrays, and the Requests package to pull data using an API. The secondary packages are going to be the Math package for mathematical functions, the SciPy package for complex functions, and the Statistics package for statistical functions.


Python Implementation:



import pandas as pd
import requests
import numpy as np
from scipy.stats import percentileofscore as score
from statistics import mean
from math import floor


Now that we have imported all the required packages into our python environment. Let’s proceed in extracting the list of all S&P 500 stock’s symbols.


Step-2: Extracting the List of all S&P 500 Stock’s Symbols


I’ve seen tutorials building complex web scraping functions to make this particular task get done. But, that’s not the way we are going to follow and our code is going to be much simpler and efficient.


Python Implementation:



sp500 = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')
sp500_list = np.array(sp500[0]['Symbol'])
print(sp500_list[:20])


Output:



Code Explanation: As I said before, we are not going to complicate our code or use any web scraping techniques to extract the symbols of the stocks in the S&P 500 but, we used a highly efficient function named ‘read_html’ provided by the Pandas package. This function will search for tables in the given URL and will return the data in a list format. Coming back to our code, we first extracted the symbols of the stocks in the S&P 500 using the ‘read_html’ function and stored it into the ‘sp500’ variable. Then we defined a variable ‘sp500_list’ to store the extracted data into a NumPy array. Now that we have collected all the S&P 500 stock’s symbols. Let’s pull some intraday data!


Step-3: Pulling Intraday Data using IEX Cloud API


In this step, we are going to pull the intraday data of all the S&P 500 stocks using IEX Cloud API. If you don’t know how to pull stock data using IEX Cloud API, I suggest you read my article on it here.


Python Implementation:



def get_intraday_prices(symbol):
    ticker = symbol
    iex_api_key = 'pk_32ef64b2003542b6a829b8f94831c789'
    url = f'https://cloud.iexapis.com/stable/stock/{ticker}/intraday-prices?token={iex_api_key}'
    df = requests.get(url).json()
    date = df[1]['date']
        
    time = []
    open = []
    high = []
    low = []
    close = []
    volume = []
    number_of_trades = []
    
    for i in range(len(df)):
        time.append(df[i]['label'])
        open.append(df[i]['open'])
        high.append(df[i]['high'])
        low.append(df[i]['low'])
        close.append(df[i]['close'])
        volume.append(df[i]['volume'])
        number_of_trades.append(df[i]['numberOfTrades'])
        
    time_df = pd.DataFrame(time).rename(columns = {0:'Time'})
    open_df = pd.DataFrame(open).rename(columns = {0:'Open'})
    high_df = pd.DataFrame(high).rename(columns = {0:'High'})
    low_df = pd.DataFrame(low).rename(columns = {0:'Low'})
    close_df = pd.DataFrame(close).rename(columns = {0:'Close'})
    volume_df = pd.DataFrame(volume).rename(columns = {0:'Volume'})
    number_of_trades_df = pd.DataFrame(number_of_trades).rename(columns = {0:'Number of Trades'})
     
    frames = [time_df, open_df, high_df, low_df, close_df, volume_df, number_of_trades_df]
    df = pd.concat(frames, axis = 1, join = 'inner')
    df = df.set_index('Time')
    return df

df = pd.DataFrame(columns = sp500_list)
for i in df.columns:
    try:
        df[i] = get_intraday_prices(i)['Close']
        print(f'{i} is successfully extracted')
    except:
        pass
    
df.to_csv('sp500.csv')
sp500 = pd.read_csv('sp500.csv').set_index('Time')
sp500.head()


Output:



Code Explanation: This piece of code can be classified into three parts: Defining a function to pull intraday data, pulling intraday data for all the stocks in the S&P 500 using the defined function, saving and importing the extracted data.


The first part comprises the code from lines 1 to 36. In the first part, we are first defining a function named ‘get_intraday_prices’ which takes a stock’s symbol as the parameter. Inside the function, we are first defining two variables to store the API key and the URL. Using the ‘get’ function provided by the Requests package, we are extracting the intraday data in a JSON format. After doing some data processing and manipulations, we are returning the data in the form of a Pandas dataframe.


The second part comprises the code from lines 38 to 44. In this part, we are iterating over the symbols of the stocks to pull intraday data of each stock. We are storing the intraday data of each stock into the ‘df’ variable.


The third part comprises the code from lines 46 to 48. The main goal of this part is to save and import the extracted intraday data of all stocks. This step is optional but highly recommended as you can save time from running the iteration process when you're reopening the script. We are first saving the extracted intraday data in the name of ‘sp500’ using the ‘to_csv’ function provided by the Pandas package. Then, with the help of the ‘read_csv’ function, we are importing the saved dataframe.


Step-4: Calculating Percentage Change and Momentum


In this step, we are going to calculate the day change and the momentum of each stock.


Python Implementation:



# CALCULATING DAY CHANGE OF STOCKS

dc = []
for i in sp500.columns:
    dc.append(sp500[i].pct_change().sum())
    
sp500_momentum = pd.DataFrame(columns = ['symbol', 'day_change'])
sp500_momentum['symbol'] = sp500.columns
sp500_momentum['day_change'] = dc

# CALCULATING MOMENTUM

sp500_momentum['momentum'] = 'N/A'
for i in range(len(sp500_momentum)):
    sp500_momentum.loc[i, 'momentum'] = score(sp500_momentum.day_change, sp500_momentum.loc[i, 'day_change'])/100
    
sp500_momentum['momentum'] = sp500_momentum['momentum'].astype(float)    
sp500_momentum.head()


Output:



Code Explanation: First, we are creating an empty list named ‘pc’ to store the percentage change of each stock. Utilizing a for-loop, we are iterating over the symbols of all the stocks in the S&P 500 and appended the calculated percentage change using the ‘pct_change’ function provided by the Pandas package into the ‘dc’ list. We are then creating a dataframe named ‘sp500_momentum’ to store the symbol of the stock, percentage change, and the momentum which we are going to calculate.


Coming to the code to calculate the momentum, we are first creating a column named ‘momentum’ in the ‘sp500_momentum’ dataframe and filled it with null values. Then we are passing on a for-loop to fill the null values with the actual momentum values.


Step-5: Finding Stocks having Greater Momentum


In this step, we are going to sort the stocks based on the momentum value we calculated and find the top 10 stocks having the highest momentum.


Python Implementation:



top_picks = sp500_momentum.nlargest(10, 'momentum')['symbol'].reset_index().drop('index', axis = 1)

np.array(top_picks)


Output:



Code Explanation: I always follow the principle of keeping the code simple and efficient. I did it here too. We are first creating a dataframe named ‘top_picks’ which stores the top 10 stocks having greater momentum than the rest. In order to sort and find the top 10 stocks, we used the ‘nlargest’ function provided by the Pandas package.


Step-6: Backtesting


In this step, we are going to backtest by investing an equal amount of money in the top 10 stocks with the highest momentum, and let’s see the results.


Python Implementation:



portfolio_val = 1000000
per_stock_val = portfolio_val/len(top_picks)

day_close = []
for i in top_picks['symbol']:
    data = sp500[i]
    day_close.append(data[-1])
    
backtest_df = pd.DataFrame(columns = ['selected_symbols', 'day_close', 'number_of_stocks', 'return', 'return_percentage'])
backtest_df['selected_symbols'] = top_picks['symbol']
backtest_df['day_close'] = day_close
for i in range(len(backtest_df)):
    backtest_df.loc[i, 'number_of_stocks'] = floor(per_stock_val/day_close[i])
    
returns = []
for i in top_picks['symbol']:
    ret = np.diff(sp500[i])
    ret = ret[~np.isnan(ret)]
    returns.append(round(sum(ret), 2))
    
backtest_returns = []
return_percentage = []
for i in range(len(backtest_df)):
    br = returns[i]*backtest_df.loc[i, 'number_of_stocks']
    rp = br/per_stock_val*100
    backtest_returns.append(round(br, 2))
    return_percentage.append(round(rp, 2))
backtest_df['return'] = backtest_returns
backtest_df['return_percentage'] = return_percentage

backtest_df


Output:



Code Explanation: Firstly, we are defining two variables to store the total investment value, and investment value per stock respectively. Next, we are passing a for-loop to find the day’s close price of all the top 10 stocks and appended those values into the ‘day_close’ variable. In further lines of code, we created a dataframe named ‘backtest_df’ which comprises the calculated number of stocks to buy in each of them, the returns we got by investing in those top 10 stocks, and finally the return percentage of our investment. As you can see, we got some nice returns. That’s it!


Final Thoughts!


In this article, we learned how to implement a simple quantitative momentum strategy to pick tradable stocks. You can improve this article in two ways:

  • Picking stocks for long-term investment: In this article, we picked stocks for intraday trading but, you can also implement the strategy to pick long-term investable stocks. By doing this, you will learn how to tune a quantitative momentum strategy accordingly and to use several metrics to build a strategy.

  • More stocks: This whole article is based only on the stocks listed in the S&P 500 market index but, you can consider stocks listed on many more exchanges. The advantage of performing this task is you will be able to perform research on a large scale given the amount of data being pulled and you can master the art of extracting data using stocks APIs along with data processing as this task has an extensive amount of it.

That’s it! You have reached the end of this article. Hope you found something useful in this article.