Quantitative Momentum Strategy
Quantitative Momentum is an investment strategy which selects for investment the stocks whose price appreciated the most during a period (usually the recent year, ignoring the most recent month).
The goal of this section is trying to create an investing strategy following the instruction on https://github.com/nickmccullum/algorithmic-trading-python/tree/master, with this strategy it selects the 50 stocks in S&P 600 list with the highest price momentum. From there, the trades with an equal-weight portfolio of these 50 stocks will be calculated.
Load the list of S&P 600 companies from Wikepedia
The S&P 600 is an index of small-cap company stocks created by Standard & Poor’s, selected by a committee based on recent profitability and other factors.
Currently there are 3 S&P Small Cap 600 ETFs traded on the U.S. markets, the largest one is iShares S&P Small-Cap 600 Value ETF IJS with around 7.09 Billon USD in assets.
import pandas as pd
def load_data(url):
html = pd.read_html(url, header=0)
return html
url = 'https://en.wikipedia.org/wiki/List_of_S%26P_600_companies'
df = load_data(url)[0]
df.head()
| Symbol | Company | GICS Sector | GICS Sub-Industry | Headquarters Location | SEC filings | CIK |
---|
0 | AAON | AAON, Inc. | Industrials | Building Products | Tulsa, Oklahoma | view | 824142 |
---|
1 | AAP | Advance Auto Parts, Inc. | Consumer Discretionary | Automotive Retail | Raleigh, North Carolina | view | 1158449 |
---|
2 | AAT | American Assets Trust | Real Estate | Diversified REITs | San Diego, California | view | 1500217 |
---|
3 | ABCB | Ameris Bancorp | Financials | Regional Banks | Atlanta, Georgia | view | 351569 |
---|
4 | ABG | Asbury Automotive Group | Consumer Discretionary | Automotive Retail | Duluth, Georgia | view | 1144980 |
---|
Retrieve the latest stock price using yfinance
We retrieve the latest year’s stock price using yfinance.
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')
data = yf.download(
tickers = list(df['Symbol']),
period = '1y',
interval = '1d',
group_by = 'ticker',
auto_adjust = True,
prepost = True,
threads = True,
proxy = None
)
[*********************100%%**********************] 602 of 602 completed
2 Failed downloads:
['MOG.A', 'CWEN.A']: Exception('%ticker%: No data found, symbol may be delisted')
data['AAON'].head()
Price | Open | High | Low | Close | Volume |
---|
Date | | | | | |
---|
2023-02-24 | 52.804963 | 54.090144 | 52.440610 | 53.970898 | 367500 |
---|
2023-02-27 | 53.970902 | 55.037468 | 53.281940 | 53.984154 | 653700 |
---|
2023-02-28 | 57.634324 | 62.536555 | 56.309398 | 60.257679 | 1987800 |
---|
2023-03-01 | 60.085437 | 60.681652 | 59.515718 | 59.999317 | 566400 |
---|
2023-03-02 | 59.793949 | 60.708151 | 59.436218 | 60.522659 | 792450 |
---|
Calculate 1-year return and remove the lowest momentum stocks
tickers_unavailable = ['MOG.A', 'CWEN.A']
tickers = [ticker for ticker in list(df['Symbol']) if ticker not in tickers_unavailable]
print('Number of available tickers: ', len(tickers))
Number of available tickers: 600
my_columns = ['Ticker', 'Price', 'One-Year Price Return', 'Number of Shares to Buy']
final_dataframe = pd.DataFrame(columns = my_columns)
final_dataframe
| Ticker | Price | One-Year Price Return | Number of Shares to Buy |
---|
portfolio_size = input("Enter the value of your portfolio:")
try:
val = float(portfolio_size)
except ValueError:
print("That's not a number! \n Try again:")
portfolio_size = input("Enter the value of your portfolio:")
Enter the value of your portfolio:1000000
for i in range(0,len(tickers)):
ticker = tickers[i]
final_dataframe = final_dataframe.append(
pd.Series(
[
ticker,
data[ticker]['Close'].to_list()[-1],
data[ticker]['Close'].to_list()[-1] - data[ticker]['Close'].to_list()[0],
'NA'
],
index = my_columns),
ignore_index = True)
final_dataframe.head()
| Ticker | Price | One-Year Price Return | Number of Shares to Buy |
---|
0 | AAON | 84.050003 | 30.079105 | NA |
---|
1 | AAP | 61.099998 | -74.325050 | NA |
---|
2 | AAT | 21.530001 | -2.397324 | NA |
---|
3 | ABCB | 45.880001 | -1.409645 | NA |
---|
4 | ABG | 214.619995 | -6.620010 | NA |
---|
final_dataframe.sort_values('One-Year Price Return', ascending=False, inplace=True)
final_dataframe = final_dataframe[:50]
final_dataframe.reset_index(drop=True, inplace=True)
final_dataframe.head()
| Ticker | Price | One-Year Price Return | Number of Shares to Buy |
---|
0 | AMR | 389.880005 | 234.812805 | NA |
---|
1 | IBP | 234.089996 | 122.137169 | NA |
---|
2 | POWL | 161.710007 | 118.683826 | NA |
---|
3 | WDFC | 267.000000 | 97.170700 | NA |
---|
4 | ANF | 122.820000 | 93.830000 | NA |
---|
import math
position_size = float(portfolio_size) / len(tickers)
for i in range(0,len(final_dataframe['Ticker'])):
final_dataframe.loc[i, 'Number of Shares to Buy'] = math.floor(position_size / final_dataframe['Price'][i])
final_dataframe.head()
| Ticker | Price | One-Year Price Return | Number of Shares to Buy |
---|
0 | AMR | 389.880005 | 234.812805 | 4 |
---|
1 | IBP | 234.089996 | 122.137169 | 7 |
---|
2 | POWL | 161.710007 | 118.683826 | 10 |
---|
3 | WDFC | 267.000000 | 97.170700 | 6 |
---|
4 | ANF | 122.820000 | 93.830000 | 13 |
---|
Save the output in Excel using XlsxWriter
import xlsxwriter
writer = pd.ExcelWriter('quantitative momentum.xlsx', engine = 'xlsxwriter')
final_dataframe.to_excel(writer, 'Recommended Trades', index = False)
- String format for tickers
- $xx.xx format for stock prices
- $xx,xxx for market capitalization
- Integer format for the number of shares to buy
background_color = '#0a0a23'
font_color = '#ffffff'
string_format = writer.book.add_format(
{
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
dollar_format = writer.book.add_format(
{
'num_format':'$0.00',
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
capital_format = writer.book.add_format(
{
'num_format':'$#,##0',
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
integer_format = writer.book.add_format(
{
'num_format':'0',
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
percent_format = writer.book.add_format(
{
'num_format':'0.0%',
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
column_formats = {
'A': ['Ticker', string_format],
'B': ['Stock Price', dollar_format],
'C': ['One-Year Price Return', dollar_format],
'D': ['Number of Shares to Buy', integer_format],
}
for column in column_formats.keys():
writer.sheets['Recommended Trades'].set_column(f'{column}:{column}', 18, column_formats[column][1])
writer.sheets['Recommended Trades'].write(f'{column}1', column_formats[column][0], string_format)
writer.save()