Track earnings dates with Yahoo Finance and Google Calendar API

Jagshemash

An earnings date is the day when a company releases their periodic financial report. I like seeing them posted by my chat bot but thought it would be nice to have them on my personal calendar too. I wanted to automate the process of finding the date and adding it to Google Calendar(s). If you are fed up with the lack of earnings notifications from your broker then this should help you get started on making a script for yourself.

Earnings dates are known for their accompanying volatility. People might trade over shorter time horizons so it can be interesting to see the price action leading up to and right after the event. Even if you aren’t a TrAdER ? perhaps you want to track competitor information as soon as it is available.

Financial reports have interesting information for product managers, strategists and marketers such as:

  • Statements from the CEO and other C-suite members
  • Numbers for growth, usually product and segment specific
  • How much money they make
  • How they spend their money

You can learn the basics of how to read financial statements here: https://freshprinceofstandarderror.com/finance/accounting-ratios-for-apes/

Where to find information about reporting date?

Yahoo Finance for free, find some other provider for more reliable and likely paid data. The most popular package to get that data is yfinance API. If you don’t have a Python environment then set one up from here: https://freshprinceofstandarderror.com/ai/setting-up-your-data-science-environment/

pip install yfinance

Since we’ll be adding events to Google Calendar having these official packages from Google will help. You don’t have to install it if all you need are the dates for earnings events.

You can also find them in the official documentation if the packages change in the future.
https://developers.google.com/gmail/api/quickstart/python

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

I’ll only be using Pandas, I believe it is a dependency for yfinance anyway. Open your favourite Python editor and import these packages.

import yfinance as yf
import pandas as pd 

Yfinance allows creating a ticker object and using the calendar method to get the next reporting date. Data isn’t always clean when the date has not been announced, but such is the life of a data scientist.

companies = ['COIN', 'BB','CRSR']
y_data = yf.Tickers(companies)

sym = list(y_data.tickers.values())
earnings = []
for symbol in sym:
    earnings.append(symbol.calendar)
    print(symbol.calendar)

We picked some symbols and put them in a list called companies. Feel free to add other tickers in the companies list. It is handy to convert the dictionary within the y_data object to a list and iterate through each symbol. The loop is storing all that info in an empty list called earnings.

You can concatenate the list[] we created in that loop into a single DataFrame to stare at it properly.

earning_df = pd.concat(earnings)[pd.concat(earnings).index == 'Earnings Date']

The data is messy for the second symbol (BB is BlackBerry). There was also a lot of unnecessary information for all the symbols so it was sliced by ‘Earnings Date’ only. BB has two dates for earnings because it is going to be sometime between 22 September and 27 September.

It makes sense to use the earliest date to not miss the actual event. Usually the data will correct itself closer to the real date. We can pick the earliest date by imputing NaN values in the last column with the values in the first column.

The index is kind of useless too and doesn’t give the user information about the company symbol. A better user experience would be to see the symbol next to the date.

earning_df['Value'] = earning_df['Value'].fillna(value=earning_df.iloc[:,0])
earning_df.index = companies

Awesome! ?

You can look at the last column by slicing the DataFrame. I also have those dates pushing to my Slack channel. I wrote about making Slack bots, it is really easy. It’s actually my preferred way of getting the notifications since I have realised I’ll eventually have to write code to remove duplicated calendar events because of changes to earnings dates.

You can find that article on the following link: https://freshprinceofstandarderror.com/product/slack-bots-for-product-managers/

Cleaning (barely) all that into a simple function will make the process easier to maintain. Since we’ll be passing it to the calendar API later and not using this data for time-series analysis it can be returned as a string instead of a datetime.

companies = ['COIN', 'BB','CRSR']

import yfinance as yf
import pandas as pd

def get_earnings_date(company_list):
    """
    Returns a dataframe of expected earnings dates and annonuced earning date for each company.
    """

    y_data = yf.Tickers(company_list)
    sym = list(y_data.tickers.values())
    earnings = []
    for symbol in sym:
        earnings.append(symbol.calendar)
        
    earning_df = pd.concat(earnings)[pd.concat(earnings).index == 'Earnings Date']
    earning_df['Value'] = earning_df['Value'].fillna(value=earning_df.iloc[:,0])
    earning_df['Value']
    earning_df.index = company_list #replace the index with symbols

    return earning_df.astype(str)

How to put it into my calendar though?

Google conveniently provides APIs for a lot of their services. It requires understanding some authentication and a little familiarity with Google Cloud Platform. This should be easy to follow for beginners though.

Sign in to https://console.cloud.google.com/ and make a new project.

Then go to the API and Services dashboard.

Go to the OAuth consent screen. We will make the page which asks a user for permissions when they sign-up using a Google account.

Create one, invite your own Gmail account as a tester. I didn’t fill out anything that wasn’t mandatory. They mostly want some emails.

After that go to credentials and create an OAuth client ID. The description under it is self-explanatory. I picked Desktop App for application type.

You should be able to see and download the credentials. Call it credentials.json, save it to the same folder as your Python file for this project. Eventually we create a token so that a user won’t have to keep going through the credential authorisation journey.

Final step is to go into dashboard and enable the Google Calendar API.

Testing if everything is working

Google has convenient documentation available for you to ignore here: https://developers.google.com/calendar/api/quickstart/python

They have a code snippet for testing, this will require having that credentials.json file in the folder. You should copy the code from that link since it is official and unlikely to be malicious. While all my stuff is safe to use I prefer people think about security and stay cynical.

Get rid of the bottom section from if __name__ = ‘__main__’ if you are using a Jupyter notebook. I’ll add my code that is current as of July 2021.

from __future__ import print_function
import datetime
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']


def main():
    """Shows basic usage of the Google Calendar API.
    Prints the start and name of the next 10 events on the user's calendar.
    """
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    service = build('calendar', 'v3', credentials=creds)

    # Call the Calendar API
    now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
    print('Getting the upcoming 10 events')
    events_result = service.events().list(calendarId='primary', timeMin=now,
                                        maxResults=10, singleEvents=True,
                                        orderBy='startTime').execute()
    events = events_result.get('items', [])

    if not events:
        print('No upcoming events found.')
    for event in events:
        start = event['start'].get('dateTime', event['start'].get('date'))
        print(start, event['summary'])

main()

If everything worked correctly it should ask you to authorise access to Google Calendar in your browser. If it had an error it would be because

  1. You probably are using a different account than the one you invited during the test
  2. Don’t have the credentials.json saved in the same folder
  3. Forgot to enable the Google Calendar API.

You will also see the last 10 calendar events from your calendar.

Creating compatible earnings events

All the hard work is complete. But how do we add an event to the calendar? Google has the documentation here: https://developers.google.com/calendar/api/guides/create-events

Remember the consent screen we had created in Google Cloud? It requires more aggressive permissions now that we want to write a new event into calendars. This will require adjusting the scope of the consent screen, a user must now provide permissions to add events.

It was possible to do it in the first place but this will reinforce the idea of consent screens and scope for the permissions being requested.

Go to the OAuth consent screen and edit the app. I clicked next until I got to Scope then added Events so people can provide permissions for editing events.

Don’t forget to click save and continue. Cool!

Google calendar events have to be a specific format. It is a simple Python dictionary, the values have to be strings for summary and dateTime. A long list of possible parameters are here: https://developers.google.com/calendar/api/v3/reference/events

We will keep ours simple.

event = {
   'summary': 'Google I/O 2015',

  'start': {
    'dateTime': '2021-07-24T09:00:00-07:00',
    'timeZone': 'Australia/Melbourne',
  },
  'end': {
    'dateTime': '2021-07-24T09:00:00-07:00',
    'timeZone': 'Australia/Melbourne',
  }
}

Delete the token.json file in the same folder since we won’t use the old token anymore.

SCOPES = ['https://www.googleapis.com/auth/calendar.events']

flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
service = build('calendar', 'v3', credentials=creds)
event = service.events().insert(calendarId='primary', body=event).execute()
print('Event created: %s' % (event.get('htmlLink')))

Now you must have a pretty good understanding of how the consent screen works! We have requested permissions for viewing and editing events. Being able to scope the correct right permissions from users is an important skill to have.

It correctly added that date to my calendar for 25th of July. Notice that the event was for the 24th but since we had picked the time zone as Melbourne it automatically adjusted the UTC datetime.

The test worked, everything is now in order. Need 3 functions to finish this project.

  1. A function that pushes an event to your calendar
  2. A function that converts the earnings date DataFrame into a compatible Google Calendar event format.
  3. Either a function or a loop to use the the previous functions in the right order.

Function for adding an event to Google Calendar

Google had provided a nice function, the one we used earlier. It asks for credentials and saves the token so people aren’t harassed for permissions each time. Using that flow makes sense and we will be editing the original Google function.

I’ll add all the packages needed too so you can save it to a .PY file and start cleaning up your notebook.

from __future__ import print_function
import datetime
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/calendar.events']

def event_adder(event_dict):
    """
    Adds event to a calendar using the Google Calendar API.
    """
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    service = build('calendar', 'v3', credentials=creds)

    # Call the Calendar API
    event = service.events().insert(calendarId='primary', body=event_dict).execute()

Function for formatting the earnings dates properly

It’ll take in a series of the earnings dates. We’ll return all the earnings events in proper format in one convenient list.

def earndate_to_gevent(er_series):
    """
    Takes a series of earnings dates and returns a list of Google Calender API events
    """
    earning_dict = er_series.to_dict()
    symbols = list(earning_dict.keys())
    er_dates = list(earning_dict.values())
    g_events = []

    for e_desc , e_date in zip(symbols,er_dates):
        event = {
                'summary': e_desc + " Earnings Day",

                'start': {
                        'date': e_date,
                        'timeZone': 'Australia/Melbourne',
                    },
                    'end': {
                        'date': e_date,
                        'timeZone': 'Australia/Melbourne',
                    }
                }
        g_events.append(event)
    return g_events

Putting it all together in a loop

for earnings_event in earndate_to_gevent(df['Value']):
    print(earnings_event)
    event_adder(earnings_event)

Such good work! So many new concepts! Very nice! ?

What you learned – so much!

  • How to get earnings data from Yahoo finance
  • Using Google Cloud Platform
  • Consent pages and authorisations
  • Saving a token
  • Adding events to Google Calendar

What you can learn next

  • Cleaning up all that code.

Well this should be a solid foundation for getting started, go forth and experiment.