“Momentum investing” means investing within the shares which have elevated in worth essentially the most.
For this undertaking, we’re going to construct an investing technique that selects the 50 shares with the best worth momentum. From there, we are going to calculate really useful trades for an equal-weight portfolio of those 50 shares.
The very first thing we have to do is import the open-source software program libraries that we’ll be utilizing on this tutorial.
import numpy as np #The Numpy numerical computing library
import pandas as pd #The Pandas knowledge science library
import requests #The requests library for HTTP requests in Python
import xlsxwriter #The XlsxWriter libarary for
import math #The Python math module
from scipy import stats #The SciPy stats module
As earlier than, we’ll must import our checklist of shares and our API token earlier than continuing. Make sure that the .csv
file continues to be in your working listing and import it with the next command:
shares = pd.read_csv('sp_500_stocks.csv')
from secrets and techniques import IEX_CLOUD_API_TOKEN
It’s now time to make the primary model of our momentum screener!
We have to get one-year worth returns for every inventory within the universe. Right here’s how.
image = 'AAPL'
api_url = f'https://sandbox.iexapis.com/secure/inventory/{image}/stats?token={IEX_CLOUD_API_TOKEN}'
knowledge = requests.get(api_url).json()
knowledge
Output:
{'week52change': 1.271858,
'week52high': 462.02,
'week52low': 206.85,
'marketcap': 1937104274094,
'workers': 137265,
'day200MovingAvg': 309.44,
'day50MovingAvg': 390.09,
'float': 4440629054,
'avg10Volume': 54435185.2,
'avg30Volume': 39067154.1,
'ttmEPS': 13.7084,
'ttmDividendRate': 3.22,
'companyName': 'Apple, Inc.',
'sharesOutstanding': 4331609946,
'maxChangePercent': 452.5766,
'year5ChangePercent': 3.0546,
'year2ChangePercent': 1.1867,
'year1ChangePercent': 1.186376,
'ytdChangePercent': 0.512578,
'month6ChangePercent': 0.407457,
'month3ChangePercent': 0.485051,
'month1ChangePercent': 0.19254,
'day30ChangePercent': 0.253108,
'day5ChangePercent': -0.008155,
'nextDividendDate': '2020-08-16',
'dividendYield': 0.007235663525925464,
'nextEarningsDate': '2020-10-17',
'exDividendDate': '2020-08-06',
'peRatio': 34.17,
'beta': 1.15885673879414}
This API name has all the data we want. We will parse it utilizing the identical square-bracket notation as within the first undertaking of this course. Right here is an instance.
knowledge['year1ChangePercent']
Output:
1.186376
Similar to in our first undertaking, it is now time to execute a number of batch API calls and add the data we have to our DataFrame.
We’ll begin by operating the next code cell, which accommodates some code we already constructed final time that we are able to re-use for this undertaking. Extra particularly, it accommodates a operate known as chunks
that we are able to use to divide our checklist of securities into teams of 100.
# Operate sourced from
# https://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in vary(0, len(lst), n):
yield lst[i:i + n] symbol_groups = checklist(chunks(shares['Ticker'], 100))
symbol_strings = []
for i in vary(0, len(symbol_groups)):
symbol_strings.append(','.be part of(symbol_groups[i]))
# print(symbol_strings[i])
my_columns = ['Ticker', 'Price', 'One-Year Price Return', 'Number of Shares to Buy']
Now we have to create a clean DataFrame and add our knowledge to the information body one-by-one.
final_dataframe = pd.DataFrame(columns = my_columns)for symbol_string in symbol_strings:
# print(symbol_strings)
batch_api_call_url = f'https://sandbox.iexapis.com/secure/inventory/market/batch/?varieties=stats,quote&symbols={symbol_string}&token={IEX_CLOUD_API_TOKEN}'
knowledge = requests.get(batch_api_call_url).json()
for image in symbol_string.break up(','):
final_dataframe = final_dataframe.append(
pd.Sequence([symbol,
data[symbol]['quote']['latestPrice'],
knowledge[symbol]['stats']['year1ChangePercent'],
'N/A'
],
index = my_columns),
ignore_index = True)
final_dataframe
The funding technique that we’re constructing seeks to determine the 50 highest-momentum shares within the S&P 500.
Due to this, the following factor we have to do is take away all of the shares in our DataFrame that fall beneath this momentum threshold. We’ll type the DataFrame by the shares’ one-year worth return, and drop all shares outdoors the highest 50.
final_dataframe.sort_values('One-12 months Value Return', ascending = False, inplace = True)
final_dataframe = final_dataframe[:51]
final_dataframe.reset_index(drop = True, inplace = True)
final_dataframe
Similar to within the final undertaking, we now must calculate the variety of shares we have to purchase. The one change we’re going to make is wrapping this performance inside a operate, since we’ll be utilizing it once more later on this Jupyter Pocket book.
Since we’ve already finished many of the work on this, attempt to full the next two code cells with out watching me do it first!
In [8]:
def portfolio_input():
world portfolio_size
portfolio_size = enter("Enter the worth of your portfolio:")
attempt:
val = float(portfolio_size)
besides ValueError:
print("That is not a quantity! n Strive once more:")
portfolio_size = enter("Enter the worth of your portfolio:")
portfolio_input()
print(portfolio_size)
Enter the worth of your portfolio:1000000
1000000
position_size = float(portfolio_size) / len(final_dataframe.index)
for i in vary(0, len(final_dataframe['Ticker'])):
final_dataframe.loc[i, 'Number of Shares to Buy'] = math.ground(position_size / final_dataframe['Price'][i])
final_dataframe
Actual-world quantitative funding corporations differentiate between “prime quality” and “low high quality” momentum shares:
- Excessive-quality momentum shares present “gradual and regular” outperformance over lengthy durations of time
- Low-quality momentum shares may not present any momentum for a very long time, after which surge upwards.
The rationale why high-quality momentum shares are most popular is as a result of low-quality momentum can usually be trigger by short-term information that’s unlikely to be repeated sooner or later (resembling an FDA approval for a biotechnology firm).
To determine high-quality momentum, we’re going to construct a technique that selects shares from the best percentiles of:
- 1-month worth returns
- 3-month worth returns
- 6-month worth returns
- 1-year worth returns
Let’s begin by constructing our DataFrame. You’ll discover that I take advantage of the abbreviation hqm
usually. It stands for high-quality momentum
.
hqm_columns = [
'Ticker',
'Price',
'Number of Shares to Buy',
'One-Year Price Return',
'One-Year Return Percentile',
'Six-Month Price Return',
'Six-Month Return Percentile',
'Three-Month Price Return',
'Three-Month Return Percentile',
'One-Month Price Return',
'One-Month Return Percentile',
'HQM Score'
]hqm_dataframe = pd.DataFrame(columns = hqm_columns)
for symbol_string in symbol_strings:
# print(symbol_strings)
batch_api_call_url = f'https://sandbox.iexapis.com/secure/inventory/market/batch/?varieties=stats,quote&symbols={symbol_string}&token={IEX_CLOUD_API_TOKEN}'
knowledge = requests.get(batch_api_call_url).json()
for image in symbol_string.break up(','):
hqm_dataframe = hqm_dataframe.append(
pd.Sequence([symbol,
data[symbol]['quote']['latestPrice'],
'N/A',
knowledge[symbol]['stats']['year1ChangePercent'],
'N/A',
knowledge[symbol]['stats']['month6ChangePercent'],
'N/A',
knowledge[symbol]['stats']['month3ChangePercent'],
'N/A',
knowledge[symbol]['stats']['month1ChangePercent'],
'N/A',
'N/A'
],
index = hqm_columns),
ignore_index = True)
hqm_dataframe.columns
Output:
Index(['Ticker', 'Price', 'Number of Shares to Buy', 'One-Year Price Return',
'One-Year Return Percentile', 'Six-Month Price Return',
'Six-Month Return Percentile', 'Three-Month Price Return',
'Three-Month Return Percentile', 'One-Month Price Return',
'One-Month Return Percentile', 'HQM Score'],
dtype='object')=
We now must calculate momentum percentile scores for each inventory within the universe. Extra particularly, we have to calculate percentile scores for the next metrics for each inventory:
One-12 months Value Return
Six-Month Value Return
Three-Month Value Return
One-Month Value Return
This is how we’ll do that:
time_periods = [
'One-Year',
'Six-Month',
'Three-Month',
'One-Month'
]for row in hqm_dataframe.index:
for time_period in time_periods:
hqm_dataframe.loc[row, f'{time_period} Return Percentile'] = stats.percentileofscore(hqm_dataframe[f'{time_period} Price Return'], hqm_dataframe.loc[row, f'{time_period} Price Return'])/100
# Print every percentile rating to verify it was calculated correctly
for time_period in time_periods:
print(hqm_dataframe[f'{time_period} Return Percentile'])
#Print your complete DataFrame
hqm_dataframe
0 0.885149
1 0.0237624
2 0.578218
3 0.992079
4 0.89703
...
500 0.211881
501 0.457426
502 0.843564
503 0.255446
504 0.772277
Identify: One-12 months Return Percentile, Size: 505, dtype: object
0 0.837624
1 0.0158416
2 0.839604
3 0.968317
4 0.629703
...
500 0.405941
501 0.39604
502 0.906931
503 0.227723
504 0.776238
Identify: Six-Month Return Percentile, Size: 505, dtype: object
0 0.473267
1 0.908911
2 0.643564
3 0.887129
4 0.19802
...
500 0.374257
501 0.544554
502 0.611881
503 0.70297
504 0.665347
Identify: Three-Month Return Percentile, Size: 505, dtype: object
0 0.530693
1 0.827723
2 0.742574
3 0.879208
4 0.0693069
...
500 0.370297
501 0.762376
502 0.641584
503 0.312871
504 0.792079
Identify: One-Month Return Percentile, Size: 505, dtype: object
We’ll now calculate our HQM Rating
, which is the high-quality momentum rating that we’ll use to filter for shares on this investing technique.
The HQM Rating
would be the arithmetic imply of the 4 momentum percentile scores that we calculated within the final part.
To calculate arithmetic imply, we are going to use the imply
operate from Python’s built-in statistics
module.
In [12]:
from statistics import implyfor row in hqm_dataframe.index:
momentum_percentiles = []
for time_period in time_periods:
momentum_percentiles.append(hqm_dataframe.loc[row, f'{time_period} Return Percentile'])
hqm_dataframe.loc[row, 'HQM Score'] = imply(momentum_percentiles)
As earlier than, we are able to determine the 50 finest momentum shares in our universe by sorting the DataFrame on the HQM Rating
column and dropping all however the high 50 entries.
hqm_dataframe.sort_values(by = 'HQM Rating', ascending = False)
hqm_dataframe = hqm_dataframe[:51]
We’ll use the portfolio_input
operate that we created earlier to just accept our portfolio dimension. Then we are going to use comparable logic in a for
loop to calculate the variety of shares to purchase for every inventory in our funding universe.
portfolio_input()
Enter the worth of your portfolio:1000000
position_size = float(portfolio_size) / len(hqm_dataframe.index)
for i in vary(0, len(hqm_dataframe['Ticker'])-1):
hqm_dataframe.loc[i, 'Number of Shares to Buy'] = math.ground(position_size / hqm_dataframe['Price'][i])
hqm_dataframe
We shall be utilizing the XlsxWriter library for Python to create nicely-formatted Excel recordsdata.
XlsxWriter is a wonderful bundle and gives tons of customization. Nonetheless, the tradeoff for that is that the library can appear very difficult to new customers. Accordingly, this part shall be pretty lengthy as a result of I need to do a great job of explaining how XlsxWriter works.
author = pd.ExcelWriter('momentum_strategy.xlsx', engine='xlsxwriter')
hqm_dataframe.to_excel(author, sheet_name='Momentum Technique', index = False)
You’ll recall from our first undertaking that codecs embody colours, fonts, and in addition symbols like % and $. We’ll want 4 most important codecs for our Excel doc:
- String format for tickers
- $XX.XX format for inventory costs
- $XX,XXX format for market capitalization
- Integer format for the variety of shares to buy
Since we already constructed our codecs within the final part of this course, I’ve included them beneath for you. Run this code cell earlier than continuing.
background_color = '#0a0a23'
font_color = '#ffffff'string_template = author.e book.add_format(
{
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
dollar_template = author.e book.add_format(
{
'num_format':'$0.00',
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
integer_template = author.e book.add_format(
{
'num_format':'0',
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
percent_template = author.e book.add_format(
{
'num_format':'0.0%',
'font_color': font_color,
'bg_color': background_color,
'border': 1
}
)
column_formats = {
'A': ['Ticker', string_template],
'B': ['Price', dollar_template],
'C': ['Number of Shares to Buy', integer_template],
'D': ['One-Year Price Return', percent_template],
'E': ['One-Year Return Percentile', percent_template],
'F': ['Six-Month Price Return', percent_template],
'G': ['Six-Month Return Percentile', percent_template],
'H': ['Three-Month Price Return', percent_template],
'I': ['Three-Month Return Percentile', percent_template],
'J': ['One-Month Price Return', percent_template],
'Ok': ['One-Month Return Percentile', percent_template],
'L': ['HQM Score', integer_template]
}for column in column_formats.keys():
author.sheets['Momentum Strategy'].set_column(f'{column}:{column}', 20, column_formats[column][1])
author.sheets['Momentum Strategy'].write(f'{column}1', column_formats[column][0], string_template)
As earlier than, saving our Excel output may be very straightforward:
author.save()