Back

October 2026

Get Notified when a Public Company cuts their Dividend using Python + Polygon.io

We are going to write a basic script that detects when a company cuts a dividend. We will be using Python and Polygon.io for financial data. You will need an account (Free to sign up).

Polygon.io offers a Python client, we won’t be using it. We will just call REST API. For our script, we will take two arguments:

  • Ticker (required)
  • After Date (optional)

The Python call will look like:

POLYGON_API_KEY="YOUR_SECRET_KEY" python3 main.py AAPL 2020-01-01

or,

POLYGON_API_KEY="YOUR_SECRET_KEY" python3 main.py AAPL

Now, the script (below, split into parts and explained):

import os
import sys
import time
from datetime import datetime
import requests

DATE_FORMAT = '%Y-%m-%d'
DEFAULT_INIT_DATE = '1980-01-01'

# get_all_dividends :: This will fetch a list of past dividends from the Polygon API.
# It will return a list with the following fields:
#  - cash_amount
#  - ex_dividend_date
# 
def get_all_dividends(ticker, initial_date):
    previous_minute = -1
    current_count = 0

    results = []
    url = build_url(ticker, initial_date)
    while True:
        current_minute = datetime.now().minute
        if current_minute != previous_minute:
            # Reset the rate limit counter
            previous_minute = current_minute
            current_count = 0
        
        if current_count >= 5:
            # Wait a minute
            print ('Waiting 10 seconds, then seeing if we can continue...')
            time.sleep(10)
            continue
        
        current_count += 1 # Increase the rate limit counter
        page_of_results, next_url, rate_limited = get_single_page_of_results(url)
        if rate_limited:
            current_count = 5 # Force a wait
            time.sleep(1)
            continue
        
        url = next_url
        results += page_of_results
        if not next_url:
            break
    return results

# Build the initial url
def build_url(ticker, initial_date):
    url = 'https://api.polygon.io/v3/reference/dividends?ticker=' + ticker
    url += '&apiKey=' + get_polygon_api_key()
    url += '&limit=1000' # Using 1000 because its the max
    url += '&ex_dividend_date.gte=' + initial_date.strftime(DATE_FORMAT)
    url += '&sort=ex_dividend_date'
    url += '&order=asc'
    return url

def get_single_page_of_results(url):
    if url == '':
        # The next_url came back empty, so we are done
        return [], '', False
    
    # Fetch the data
    r = requests.get(url, headers={'Authorization': 'Bearer ' + get_polygon_api_key()}, timeout=30)
    if r.status_code == 429:
        # We were rate limited
        return [], '', True
    if r.status_code != 200:
        # The request has failed, and we should stop the script
        print ('Error: ' + str(r.status_code) + ' - ' + r.text)
        sys.exit(1)
    
    # Extract the response
    data = r.json()
    next_url = ''
    if 'next_url' in data:
        next_url = data['next_url']

    return data.get('results', []), next_url, False

def get_polygon_api_key():
    if not os.environ.get('POLYGON_API_KEY'):
        print ('Please set the POLYGON_API_KEY environment variable')
        sys.exit(1)
    return os.environ['POLYGON_API_KEY']

def main():
    # Get the arguments
    if len(sys.argv) < 2:
        print ('Please provide a ticker symbol')
        return
    ticker = sys.argv[1]
    ticker = ticker.upper()
    print ('Ticker: ' + ticker)

    # Get the second argument if it exists
    initial_date = datetime.strptime(DEFAULT_INIT_DATE, DATE_FORMAT).date()
    if len(sys.argv) > 2:
        initial_date = datetime.strptime(sys.argv[2], DATE_FORMAT).date()
    print ('Initial Date: ' + str(initial_date))

    all_dividends = get_all_dividends(ticker, initial_date)
    # All Dividends is a list of objects. An object contains the following
    # fields:
    # - cash_amount
    # - declaration_date
    # - dividend_type
    # - ex_dividend_date
    # - frequency
    # - pay_date
    # - record_date
    previous_cash_amount = 0
    cuts = 0
    for dividend in all_dividends:
        if dividend['dividend_type'] != 'CD':
            # Not a consistent dividend
            continue
        
        if dividend['cash_amount'] < previous_cash_amount:
            # This dividend is less than the previous one
            print ('Dividend cut detected: ' + str(dividend['ex_dividend_date']))
            cuts += 1
        previous_cash_amount = dividend['cash_amount']
    
    print ('Done, total cuts: ' + str(cuts))

main()

Above is the entire script, and below I’ve split it into parts. Let’s start with the imports:

import os
import sys
import time
from datetime import datetime
import requests

We use the datetime library to parse the dates to strings and back. We use the sys for arguments, and exiting with error status codes. We use requests to make the HTTP requests. datetime, os, sys and time are all standard libraries. You may need to run the following command for requests.

python3 -m pip install requests

Next, we have two constants:

DATE_FORMAT = '%Y-%m-%d'
DEFAULT_INIT_DATE = '1980-01-01'

As mentioned, the second optional argument is the initial date. If not provided, we go as far as January 1, 1980. I did this because it decreases the amount of data being pulled. I think 40 years is enough. The DATE_FORMAT is the expected format for the date.

Next, let’s look at the code to make the request against Polygon. We are calling the dividends endpoint.

# get_all_dividends :: This will fetch a list of past dividends from the Polygon API.
# It will return a list with the following fields:
#  - cash_amount
#  - ex_dividend_date
# 
def get_all_dividends(ticker, initial_date):
    previous_minute = -1
    current_count = 0

    results = []
    url = build_url(ticker, initial_date)
    while True:
        current_minute = datetime.now().minute
        if current_minute != previous_minute:
            # Reset the rate limit counter
            previous_minute = current_minute
            current_count = 0
        
        if current_count >= 5:
            # Wait a minute
            print ('Waiting 10 seconds, then seeing if we can continue...')
            time.sleep(10)
            continue
        
        current_count += 1 # Increase the rate limit counter
        page_of_results, next_url, rate_limited = get_single_page_of_results(url)
        if rate_limited:
            current_count = 5 # Force a wait
            time.sleep(1)
            continue
        
        url = next_url
        results += page_of_results
        if not next_url:
            break
    return results

# Build the initial url
def build_url(ticker, initial_date):
    url = 'https://api.polygon.io/v3/reference/dividends?ticker=' + ticker
    url += '&apiKey=' + get_polygon_api_key()
    url += '&limit=1000' # Using 1000 because its the max
    url += '&ex_dividend_date.gte=' + initial_date.strftime(DATE_FORMAT)
    url += '&sort=ex_dividend_date'
    url += '&order=asc'
    return url

def get_single_page_of_results(url):
    if url == '':
        # The next_url came back empty, so we are done
        return [], '', False
    
    # Fetch the data
    r = requests.get(url, headers={'Authorization': 'Bearer ' + get_polygon_api_key()}, timeout=30)
    if r.status_code == 429:
        # We were rate limited
        return [], '', True
    if r.status_code != 200:
        # The request has failed, and we should stop the script
        print ('Error: ' + str(r.status_code) + ' - ' + r.text)
        sys.exit(1)
    
    # Extract the response
    data = r.json()
    next_url = ''
    if 'next_url' in data:
        next_url = data['next_url']

    return data.get('results', []), next_url, False

def get_polygon_api_key():
    if not os.environ.get('POLYGON_API_KEY'):
        print ('Please set the POLYGON_API_KEY environment variable')
        sys.exit(1)
    return os.environ['POLYGON_API_KEY']

The first aspect of this is the rate limiting. I have a free account and I’m limited to 5 requests per minute. The code starts by checking if the current minute is different than the one in previous_minute. If so, it resets the count. Otherwise, we check the count. If count is greater than or equal to 5, wait 10 seconds. This could be less, but most companies will require one call.

We then make the request. We get three arguments back, the results (list of dividend data), the next url if there’s more pages, or we were rate limited (and need to wait).

Why the rate limit check again? Technically, we could use our rate limit in multiple spots and be throttled. As of right now, this code could scale horizontally across multiple computers, but each process tracks its own request count. The API can still throttle requests made with the same key.

We move into the next function: build_url . This is only called for the initial url. Polygon returns a full URL in the response to use in the next API call. I determined the majority of these values by looking at the API docs. The sort matters because we are looking if dividends have been cut (reversing would mess up the logic).

Finally, get_single_page_of_results function. This is making an API call, extracting some response values or checking if the HTTP status code returned a non-200.

This would set us up with all dividend information. Now, let’s look at the main function.

def main():
    # Get the arguments
    if len(sys.argv) < 2:
        print ('Please provide a ticker symbol')
        return
    ticker = sys.argv[1]
    ticker = ticker.upper()
    print ('Ticker: ' + ticker)

    # Get the second argument if it exists
    initial_date = datetime.strptime(DEFAULT_INIT_DATE, DATE_FORMAT).date()
    if len(sys.argv) > 2:
        initial_date = datetime.strptime(sys.argv[2], DATE_FORMAT).date()
    print ('Initial Date: ' + str(initial_date))

    all_dividends = get_all_dividends(ticker, initial_date)
    
    # All Dividends is a list of objects. An object contains the following
    # fields:
    # - cash_amount
    # - declaration_date
    # - dividend_type
    # - ex_dividend_date
    # - frequency
    # - pay_date
    # - record_date
    previous_cash_amount = 0
    cuts = 0
    for dividend in all_dividends:
        if dividend['dividend_type'] != 'CD':
            # Not a consistent dividend
            continue
        
        if dividend['cash_amount'] < previous_cash_amount:
            # This dividend is less than the previous one
            print ('Dividend cut detected: ' + str(dividend['ex_dividend_date']))
            cuts += 1
        previous_cash_amount = dividend['cash_amount']
    
    print ('Done, total cuts: ' + str(cuts))

As you can see, we just iterate through the dividends checking for a cut on the cash amount.

Below is a screenshot of me running the script:

Screenshot 2023-09-05 at 2.19.20 PM.png

Screenshot 2023-09-05 at 2.33.02 PM.png

In terms of notifying you, I won’t demonstrate that part. You can setup a simple Slackbot via webhook, or email with Sendgrid.

Thanks for reading! You can make this code much more efficient, but I chose to keep it simple.