top of page

Create an Email Alert System with Python to get Earnings Data

Nikhil Adithyan

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.

0 comments

Related Posts

See All

Bring information-rich articles and research works straight to your inbox (it's not that hard). 

Thanks for subscribing!

© 2023 by InsightBig. Powered and secured by Wix

bottom of page