Pulling financial data via IEX Cloud API I am pulling some financial data from the IEX Cloud API. Since it is a paid API w/ rate limits, I would like to do this as efficiently as possible (i.e., as few calls as possible). Given that the tickers list is likely to have thousands of indexes (oftentimes with duplicates), is there a better/more efficient way than what I'm currently doing? 

(Feel free to use the token in the code, as there is no limit to the number of API calls that you can make to sandboxed data.)

import os
from iexfinance.stocks import Stock
import iexfinance
import pandas as pd

# Set IEX Finance API Token (Test)
os.environ['IEX_API_VERSION'] = 'iexcloud-sandbox'
os.environ['IEX_TOKEN'] = 'Tsk_5798c0ab124d49639bb1575b322841c4'

# List of ticker symbols
tickers = ['MSFT', 'V', 'ERROR', 'INVALID', 'BRK.B', 'MSFT']

# Create output Dataframe
output_df = pd.DataFrame(columns=['Net Income', 'Book Value'])

# Loop through companies in tickers list
for ticker in tickers:

    # Call IEX Cloud API, and output to Dataframe
    try:
        company = Stock(ticker, output_format='pandas')

        try:
            # Get income from last 4 quarters, sum it, and store to temp Dataframe
            df_income = company.get_income_statement(period="quarter", last='4')
            df_income['TTM'] = df_income.sum(axis=1)
            income_ttm = int(df_income.loc['netIncome', 'TTM'])

            # Get book value from most recent quarter, and store to temp Dataframe
            df_book = company.get_balance_sheet(period="quarter")
            book_value = int(df_book.loc['shareholderEquity'])

        # If IEX Cloud errors, make stats = 0
        except (iexfinance.utils.exceptions.IEXQueryError, TypeError, KeyError) as e:
            book_value = 0
            income_ttm = 0

        # Store stats to output Dataframe
        output_df.loc[ticker] = [income_ttm, book_value]

    except ValueError:
        pass

print(output_df)
This seems mostly to be an API question?  Assuming all that data for
each of the companies is required and there's no combined API endpoint
that can be fed with, say, 10s or 100s of symbols, this would seem to
be the best way.  If so, there'd still be possible concurrent execution
options to make multiple API requests at the same time and possible get
more throughput with it, though the number of API calls would still be
the same.

The streaming endpoints looking interesting at first, but they don't support the data here; any other endpoints all look like they only accept single symbols.



What you should definitely do is create a (few) function(s) and ideally
get rid of the try blocks.  From a first look I've zero idea where
stuff gets thrown.  But I'm guessing mostly keys not being present.
Preferably this could be handled directly (with default values or
similar features) that do not require lots of exception handling.  Yes,
Python might encourage this, but simply catching ValueError around a
huge block will eventually catch unintended exceptions too.

So at least something like this would be a good start:

def fetch_company_info(ticker):
    company = Stock(ticker, output_format='pandas')

    book_value = 0
    income_ttm = 0

    try:
        # Get income from last 4 quarters, sum it, and store to temp Dataframe
        df_income = company.get_income_statement(period="quarter", last=4)
        df_income['TTM'] = df_income.sum(axis=1)
        income_ttm = int(df_income.loc['netIncome', 'TTM'])

        # Get book value from most recent quarter, and store to temp Dataframe
        df_book = company.get_balance_sheet(period="quarter")
        book_value = int(df_book.loc['shareholderEquity'])

    # Ignore IEX Cloud errors
    except iexfinance.utils.exceptions.IEXQueryError:
        pass

    return income_ttm, book_value


I also shuffled the variables a bit around - with the right order this
could even be type checked ...

In any case, I can't see anything wrong here and I've removed the other
two exceptions as they look more like hiding problems than actual proper
handling of the exact problem that was encountered.  With the example
names I also couldn't trigger them at all.

The main body of the script is now rather small:

# Loop through companies in tickers list
for ticker in tickers:
    # Call IEX Cloud API, and store stats to Dataframe
    output_df.loc[ticker] = fetch_company_info(ticker)

print(output_df)


If there's no need for reusability then that's it, otherwise I'd suggest
a main function and moving all currently global definitions into it,
then have some argument parser or configuration file handling, etc. to
make it a more fully grown solution.



After some discussion in the comments it turns out that splitting the
return values from the assignment can lead to easy mistakes like the
order of the values being wrong.  With that in mind, either the output_df
global should be kept in the function, or (better) be passed in as
a variable instead:

for ticker in tickers:
    fetch_company_info(ticker, output_df)


and

def fetch_company_info(ticker, output_df):
    ...
    output_df[ticker] = [income_ttm, book_value]