Never miss an earnings report again
Staying informed about upcoming earnings announcements is essential for making timely investment decisions. However, keeping track of multiple companies can be time-consuming and effortful. In this article, we’ll explore how you can automate the process by setting up a system to email you a list of earnings announcements for the next 7 days. With this approach, you’ll receive relevant updates directly in your inbox, helping you stay ahead of the market without the hassle. Whether a trader or a long-term investor, this automated solution will save you time and ensure you never miss important financial events.
To do that, we will need a set of APIs to provide the necessary information, and in this case, we have selected the Financial Modelling Prep APIs.
But first, let’s do our imports and set our API token:
import requests
from datetime import datetime, timedelta
import pandas as pd
from collections import defaultdict
import json
from jinja2 import Template
token = '<YOUR API TOKEN HERE>'
Select our universe
First, we need to select the companies we are interested in. In our case, we will select the companies of the S&P 500, but you may want to limit this list to fewer companies. We will be using the FMP API for that as follows:
url = 'https://financialmodelingprep.com/api/v3/sp500_constituent'
querystring = {"apikey":token}
resp = requests.get(url, querystring).json()
df_sp_500 = pd.DataFrame(resp)
df_sp_500.set_index('symbol', inplace=True)
Set our earnings calendar
Now that we have the stocks we are interested, we will use the FMP API for the earning calendar for the next 7 days from the day that the script is executed, and then keep only the stocks that we are interested in.
# get today and one week after
today = datetime.today()
next_week = today + timedelta(days=7)
# Format both dates as strings in the format YYYY-MM-DD
today_str = today.strftime('%Y-%m-%d')
next_week_str = next_week.strftime('%Y-%m-%d')
url = 'https://financialmodelingprep.com/api/v3/earning_calendar'
querystring = {"apikey":token, "from":today_str, "to":next_week_str}
response = requests.get(url, params=querystring)
df_earnings = pd.DataFrame(response.json())
df_earnings.set_index('symbol', inplace=True)
df_earnings = df_earnings.merge(df_sp_500, left_index=True, right_index=True)
Get our key ratios and indicators
Wouldn’t it be better to have the report with some already calculated information?
First, with the use of FMP API for key metrics we will get the current PE ratio, as well as the calculation of the fair value, and add them to our data with the earnings
def get_key_ratios(row):
ticker = row.name
url = f'https://financialmodelingprep.com/api/v3/key-metrics-ttm/{ticker}'
querystring = {"apikey":token}
response = requests.get(url, params=querystring)
if response.status_code != 200:
return pd.Series([None, None])
data = response.json()[0]
peRatioTTM = data.get('peRatioTTM', None)
earningsYieldTTM = data.get('earningsYieldTTM',None)
return pd.Series([peRatioTTM, earningsYieldTTM])
df_earnings[['priceEarningsRatio', 'priceFairValue']] = df_earnings.apply(get_key_ratios, axis=1)
Secondly, we will add some more technical information so you will be able to have more holistic information in the same report. We will be getting with FMPs technical API the:
RSI (Relative Strength Index), so we will be able to show if currently the stock can be considered overbought, or oversold
Also, we will get a fast and a slow Simple Moving Average so we can identify the trend (if the fast SMA is above the slow SMA, it indicates an uptrend; otherwise, it’s a downtrend)
def get_indicators(row):
ticker = row.name
# get rsi
url = f'https://financialmodelingprep.com/api/v3/technical_indicator/1day/{ticker}'
querystring = {"apikey":token, "type":"rsi", "period":10}
response = requests.get(url, params=querystring)
if response.status_code != 200:
rsi = None
else:
data = response.json()[0]
rsi = data.get('rsi', None)
# earningsYieldTTM = data.get('earningsYieldTTM',None)
# get fast SMA
url = f'https://financialmodelingprep.com/api/v3/technical_indicator/1day/{ticker}'
querystring = {"apikey":token, "type":"sma", "period":10}
response = requests.get(url, params=querystring)
if response.status_code != 200:
sma_fast = None
else:
data = response.json()[0]
sma_fast = data.get('sma', None)
# get slow SMA
url = f'https://financialmodelingprep.com/api/v3/technical_indicator/1day/{ticker}'
querystring = {"apikey":token, "type":"sma", "period":50}
response = requests.get(url, params=querystring)
if response.status_code != 200:
return pd.Series([None, None])
else:
data = response.json()[0]
sma_slow = data.get('sma', None)
# calculate trend
if sma_slow is None or sma_fast is None:
SMATrendUp = None
else:
SMATrendUp = sma_fast > sma_slow
return pd.Series([rsi, SMATrendUp])
# get_indicators('AAPL')
df_earnings[['rsi', 'SMATrendUp']] = df_earnings.apply(get_indicators, axis=1)
df_earnings = df_earnings[['date','epsEstimated','time','name', 'priceEarningsRatio', 'priceFairValue' ,'rsi', 'SMATrendUp']]
Now we have our basic information for the upcoming EPS announcements in the form of a pandas dataframe:
Historic EPS
Our report would not be of any use, if we did not include the past EPS announcements to have a comprehensive view. To gather this information, we will use again FMP API again for the historical earnings calendar as below:
hist_eps_list = []
for i, row in df_earnings.iterrows():
ticker = row.name
url = f'https://financialmodelingprep.com/api/v3/historical/earning_calendar/{ticker}'
querystring = {"apikey":token}
response = requests.get(url, params=querystring)
if response.status_code != 200:
continue
else:
data = response.json()
hist_eps_list.extend(data)
df_historical_eps = pd.DataFrame(hist_eps_list)
df_historical_eps['date'] = pd.to_datetime(df_historical_eps['date'])
df_historical_eps = df_historical_eps.dropna(subset=['eps', 'epsEstimated'])
min_date = pd.Timestamp.now() - pd.DateOffset(years=1)
df_historical_eps = df_historical_eps[df_historical_eps['date'] >= min_date]
df_historical_eps = df_historical_eps.sort_values(by=['symbol', 'date'], ascending=[True, False])
You should note that we are keeping the historical announcements of 1 year back. Of course, you can alter this part to suit your needs better. The API returns decades of data for you to “play” with
Let’s get some price action!
At this point, besides showing just the historical EPS, wouldn’t be great to see how the price reacted to those announcements and present it to our report?
Initially, we will get all the close prices for all the stocks in our list, instead of iterating through the whole dataframe and calling the same FMP API for the same stock, again and again…
list_of_tickers = df_historical_eps['symbol'].unique()
# get the minimum date of the df
min_date = df_historical_eps['date'].min().strftime('%Y-%m-%d')
data_prices = []
for ticker in list_of_tickers:
url = f'https://financialmodelingprep.com/api/v3/historical-price-full/{ticker}'
querystring = {"apikey":token, "from": min_date}
response = requests.get(url, params=querystring)
if response.status_code != 200:
continue
else:
data = response.json()
symbol = data["symbol"]
historical_data = data["historical"]
data_list = [{'symbol': symbol, 'date': entry['date'], 'close': entry['close']} for entry in historical_data]
data_prices.extend(data_list)
df_prices = pd.DataFrame(data_prices)
Now we are ready to update our dataframe with the price change for each past earning announcement
def get_monthly_change(df, symbol, date):
df['date'] = pd.to_datetime(df['date'])
symbol_data = df[df['symbol'] == symbol].sort_values(by='date')
start_row = symbol_data[symbol_data['date'] == pd.to_datetime(date)]
if start_row.empty:
return f"No data found for {symbol} on {date}"
start_price = start_row['close'].values[0]
target_date = pd.to_datetime(date) + pd.DateOffset(months=1)
end_row = symbol_data[symbol_data['date'] >= target_date].iloc[0]
end_price = end_row['close']
change = (end_price - start_price) / start_price * 100
return change
def apply_monthly_change(row, df):
return get_monthly_change(df, row['symbol'], row['date'])
df_historical_eps['monthly_change'] = df_historical_eps.apply(lambda row: apply_monthly_change(row, df_prices), axis=1)
df_historical_eps = df_historical_eps[['date', 'symbol', 'eps', 'epsEstimated', 'monthly_change']]
This way we finalised our data gathering! Let’s move to presenting them.
Prepare the report
Now that we have all the data, let’s prepare our report. To follow this article better, check our end game below! The report will be grouped by day, and have all the information that we gathered before:
First, we need to create our HTML template, which with the help of jinja2, will render our final HTML report! In order to make something professional, we will use some CSS and bootstrap. Nobody says no to a nicely formatted email.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weekly Earnings Report</title>
<!-- Bootstrap CSS -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<style>
.header {
background-color: #343a40;
color: white;
padding: 15px;
}
.earnings-day {
margin-top: 30px;
}
.stock-line {
font-weight: bold;
}
.indicator {
font-size: 0.9em;
color: gray;
}
.up-arrow {
color: green;
}
.down-arrow {
color: red;
}
.table {
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<!-- Header Section -->
<div class="header text-center">
<h1>SP500 Earnings Announcements - Following 7 Days</h1>
</div>
<!-- Earnings Report Section -->
{% for day, stocks in earnings_data.items() %}
<div class="earnings-day">
<h2>{{ day }}</h2>
<!-- Stocks Section -->
{% for stock in stocks %}
<div class="stock-line">
{{ stock.company_name }} ({{ stock.ticker }}) - Estimated EPS: {{ stock.epsEstimated }} | P/E Ratio: {{ stock.pe_ratio | round(2) }} | {% if stock.time == "bmo" %} Before Market Open {% elif stock.time == "amc" %} After Market Close {% endif %}
<br>
<span class="indicator">
SMA Slow/Fast Trend:
{% if stock.slow_ma_above_fast_ma %}
<span class="up-arrow">▲</span>
{% else %}
<span class="down-arrow">▼</span>
{% endif %} |
RSI: {{ stock.rsi | round(2) }}
</span>
</div>
<!-- Past Earnings Table -->
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Date</th>
<th>Estimated EPS</th>
<th>Actual EPS</th>
<th>Price Action Next Month</th>
</tr>
</thead>
<tbody>
{% for past in stock.past_earnings %}
<tr>
<td>{{ past.date }}</td>
<td>{{ past.estimated_eps }}</td>
<td>{{ past.actual_eps }}</td>
<td>{{ past.price_action | round(2) }} %</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</div>
{% endfor %}
</div>
<!-- Bootstrap JS (Optional) -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
Now that we have the HTML template, we need to format our data into a dictionary that the template will understand and render our report. We can do this with the following code:
# Helper function to get past earnings data
def get_past_earnings(symbol, df_historical_eps):
past_earnings = df_historical_eps[df_historical_eps['symbol'] == symbol].copy() # Match the symbol
past_earnings_list = []
for _, row in past_earnings.iterrows():
past_earnings_list.append({
"date": row['date'].strftime('%Y-%m-%d'), # Convert Timestamp to string
"estimated_eps": row['epsEstimated'],
"actual_eps": row['eps'],
"price_action": row['monthly_change']
})
return past_earnings_list
# Create the earnings_data dictionary
earnings_data = defaultdict(list)
# Iterate over df_earnings rows
for _, row in df_earnings.iterrows():
date = pd.to_datetime(row['date']).strftime('%A, %d %b %Y') # Convert date to string
symbol = row.name # Assuming this is the company ticker (check if correct)
# Get past earnings for the symbol (ensure symbol matches ticker in df_historical_eps)
past_earnings = get_past_earnings(symbol, df_historical_eps)
# Append the company's earnings data
earnings_data[date].append({
"company_name": row['name'], # Company name
"ticker": symbol, # Use ticker/symbol for the company
"time": row['time'],
"epsEstimated": row['epsEstimated'],
"pe_ratio": row['priceEarningsRatio'],
"rsi": row['rsi'],
"slow_ma_above_fast_ma": row['SMATrendUp'],
"past_earnings": past_earnings # Historical earnings data
})
# Convert defaultdict to a regular dict
earnings_data = dict(earnings_data)
This will create a dictionary that will look like the below screenshot
Now let’s run the code and save the output to an HTML file so that we will be able to see it in our browser:
with open('template.html', 'r') as file:
html_template = file.read()
template = Template(html_template)
html_output = template.render(earnings_data=earnings_data)
with open('output.html', 'w') as file:
file.write(html_output)
If you open the output.html with your browser, you will see your final report as we promised in the beginning.
Send the email!
Now let’s see how we will send the email! We will do it through an SMPT server that we have access to.
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# Email settings
smtp_server = 'smtp.example.com' # Replace with your SMTP server
smtp_port = 587 # or use 465 for SSL
sender_email = 'your_email@example.com'
receiver_email = 'receiver_email@example.com'
password = 'your_email_password'
message = MIMEMultipart()
message['From'] = sender_email
message['To'] = receiver_email
message['Subject'] = 'Upcoming earning announcements'
message.attach(MIMEText(html_output, 'html'))
# Send the email
try:
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls() # Secure the connection
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message.as_string())
server.quit()
print("Email sent successfully!")
except Exception as e:
print(f"Error: {e}")
Thats it! The email should be already in your inbox!
Get it better!
Every trader has their own way of thinking, so what works for someone, does not necessarily mean that will work for you. So we will put below a list of ideas so you can prepare this email as you feel it is better for you:
The list of stocks returned here is extensive and can be overwhelming. Limit the list to fewer stocks, like filtering to a sector, or creating your own list of stocks to monitor.
Maybe you want to get the email daily, so you limit the stocks to the earnings reports of tomorrow.
Put the indicators that you are used to. This article uses the RSI and some simple trend identification. Just change them to what is your personal preference to see in this report.
Put some more visual statistics on the historical EPS announcements. Like how many constant growth EPS were announced in a row.
I hope you enjoyed the article! If you found this useful, please clap and share your thoughts below!
Disclaimer: While we explore the exciting world of investing in this article, it’s crucial to note that the information provided is for educational purposes only. I’m not a financial advisor, and the content here doesn’t constitute financial advice. Always do your research and consider consulting with a professional before making any investment decisions.