Skip to main content

Introduction to Financial Statements

OpenBB Platform data extensions provide access to financial statements as quarterly or annual.

There are also endpoints for ratios and other common non-GAAP metrics.

Most data providers require a subscription to access all data, refer to the website of a specific provider for details on entitlements and coverage.

Financial statement functions are grouped under the obb.equity.fundamental module.

info

To begin, import the OpenBB Platform into a Python session:

from openbb import obb

Financial Statements

The typical financial statements consist of three endpoints:

  • Balance Sheet: obb.equity.fundamental.balance()
  • Income Statement: obb.equity.fundamental.income()
  • Cash Flow Statement: obb.equity.fundamental.cash()

The main parameters are:

  • symbol: The company's ticker symbol.
  • period: 'annual' or 'quarter'. Default is 'annual'.
  • limit: Limit the number of results returned, from the latest. Default is 5. For perspective, 150 will go back to 1985. The amount of historical records varies by provider.
Field Names
info
  • Every data provider has their own way of parsing and organizing the three financial statements.
  • Items within each statement will vary by source and by the type of company reporting.
  • Names of line items will vary by source.
  • "Date" values may differ because they are from the period starting/ending or date of reporting.

This example highlights how different providers will have different labels for company facts.

import pandas as pd

df = pd.DataFrame()

df["yfinance"] = (
obb.equity.fundamental.balance("TGT", provider="yfinance", limit=3)
.to_df().get("total_assets")
)

df["fmp"] = (
obb.equity.fundamental.balance("TGT", provider="fmp", limit=3)
.to_df().get("total_assets")
)

df["intrinio"] = (
obb.equity.fundamental.balance("TGT", provider="intrinio", limit=3)
.to_df().get("total_assets")
)

df["polygon"] = (
obb.equity.fundamental.balance("TGT", provider="polygon", limit=3)
.to_df().get("total_assets")
)

df
yfinancefmpintriniopolygon
042779000000427790000004277900000042779000000
151248000000512480000005124800000051248000000
253811000000538110000005381100000053811000000
353335000000533350000005333500000053335000000
Weighted Average Shares Outstanding

This key metric will be found under the income statement. It might also be called, 'basic', and the numbers do not include authorized but unissued shares.

A declining count over time is a sign that the company is returning capital to shareholders in the form of buy backs. Under ideal circumstances, it is more capital-efficient, for both company and shareholders, because distributions are double-taxed.

The company pays income tax on dividends paid, and the beneficiary pays income tax again on receipt.

A company will disclose how many shares are outstanding at the end of the period as a weighted average over the reporting period - three months.

Let's take a look at Target. To make the numbers easier to read, we'll divide the entire column by one million.

data = (
obb.equity.fundamental.income("TGT", provider='fmp', limit=150, period="quarter")
.to_df()
)

shares = data["weighted_average_basic_shares_outstanding"]/1000000

Where this data starts,

shares.head(1)
dateweighted_average_basic_shares_outstanding
1986-07-311168.82

versus currently,

shares.tail(1)
dateweighted_average_basic_shares_outstanding
2023-10-31461.6

Thirty-seven years later, the share count is approaching a two-thirds reduction. That is 12.2% over the past five years.

shares.pct_change(20).iloc[-1]
-0.12

In four reporting periods, 1.3 million shares have been taken out of the float.

shares.iloc[-4] - shares.iloc[-1]
-1.3

With an average closing price of 144.27,thatrepresentsapproximately144.27, that represents approximately 190M in buy backs.

price = (
obb.equity.price.historical("TGT", start_date="2022-10-01", provider="fmp")
.to_df()
)

round((price["close"].mean()*1300000)/1000000, 2)
187.55
Dividends Paid

Dividends paid is in the cash flow statement. We can calculate the amount-per-share with the reported data.

dividends = (
obb.equity.fundamental.cash("TGT", provider='fmp', limit=150, period="quarter")
.to_df()[["payment_of_dividends"]]
)

dividends["shares"] = data[["weighted_average_basic_shares_outstanding"]]
dividends["div_per_share"] = abs(dividends["payment_of_dividends"]/dividends["shares"])

dividends["div_per_share"].tail(4)
datediv_per_share
2023-01-28-1.07973
2023-04-29-1.07833
2023-07-29-1.08102
2023-10-31-1.09835

This can be compared against the real amounts paid to common share holders, as announced.

note

The dates immediately above represent the report date, dividends paid are attributed to the quarter they were paid in. The value from "2023-01-28" equates to the fourth quarter of 2022.

data = (
obb.equity.fundamental.dividends("TGT", provider="fmp")
.to_df()
[["ex_dividend_date", "amount"]]
)
data.ex_dividend_date = data.ex_dividend_date.astype(str)
data.set_index("ex_dividend_date").loc["2023-08-15": "2022-11-15"]
ex_dividend_datedividend
2022-11-151.08
2023-02-141.08
2023-05-161.08
2023-08-151.1

The numbers check out, and the 2Bpaidtoinvestorsoverfourquartersismorethantentimesthe2B paid to investors over four quarters is more than ten times the 190M returned through share buy backs.

Financial Attributes

The openbb-intrinio data extension has an endpoint for extracting a single fact from financial statements.

There is a helper function for looking up the correct tag.

Search Financial Attributes

Search attributes by keyword.

obb.equity.fundamental.search_attributes("marketcap").to_df().head(1)
idnametagstatement_codestatement_typeparent_namesequencefactortransactiontypeunit
0tag_BgkbWyMarket Capitalizationmarketcapcalculationsindustrialnanvaluationusd

The tag is what we need, in this case it is what we searched for.

marketcap = (
obb.equity.fundamental.historical_attributes(symbol="TGT", tag = "marketcap", frequency="quarterly")
.to_df()
)

marketcap.tail(5)
datevalue
2022-12-3166929627287
2023-03-3175023699391
2023-06-3059916953938
2023-09-3050614370690
2023-11-2260495000000

Doing some quick math, and ignoring the most recent value, we can see that the market cap of Target was down nearly a quarter over the last four reporting periods.

marketcap.index = marketcap.index.astype(str)
(
(marketcap.loc["2023-09-30"].value - marketcap.loc["2022-12-31"].value)/marketcap.loc["2022-12-31"].value
)
-0.24

Ratios and Other Metrics

Details

Other valuation functions are derivatives of the financial statements, but the data provider does the math.

Values are typically ratios between line items, on a per-share basis, or as a percent growth.

This data set is where you can find EPS, FCF, P/B, EBIT, quick ratio, etc.

Quick Ratio

Target's quick ratio could be one reason why its share price is losing traction against the market. Its ability to pay current obligations is not optimistically reflected in a 0.27 score, approximately 50% below the historical median.

ratios = (
obb.equity.fundamental.ratios("TGT", limit=50, provider="fmp")
.to_df()
)

display(f"Current Quick Ratio: {ratios['quick_ratio'].iloc[-1]}")
display(f"Median Quick Ratio: {ratios['quick_ratio'].median()}")
Current Quick Ratio: 0.27
Median Quick Ratio: 0.58
Free Cash Flow Yield

The metrics endpoint, with the openbb-fmp data extension, has a field for free cash flow yield. It is calculated by taking the free cash flow per share divided by the current share price. We could arrive at this answer by writing some code, but these types of endpoints do the work so we don't have to. This is part of the value-add that API data distributors provide, they allow you to get straight to work with data.

We'll use this endpoint to extract the data, and compare with some of Target's competition over the last ten years.

# List of other retail chains
tickers = ["COST", "BJ", "DLTR", "DG", "WMT", "BIG", "M", "KSS", "TJX"]
# Create a dictionary of tickers and company names.
names = {
ticker: obb.equity.profile(ticker, provider="fmp").results[0].name
for ticker in tickers
}
# Create a column for each.
fcf_yield = pd.DataFrame()
for ticker in tickers:
fcf_yield[names[ticker]] = (
obb.equity.fundamental.metrics(ticker, provider="fmp", period="annual", limit=10)
.to_df()
.reset_index()
.set_index("calendar_year")
.sort_index(ascending=False)
["free_cash_flow_yield"]
)
fcf_yield.transpose()
2023202220212020201920182017201620152014
Costco Wholesale Corporation0.02792180.01485960.02658180.03935120.02590610.02743790.06088360.008940590.03074140.0374833
BJ's Wholesale Club Holdings, Inc.nan0.04470920.06721280.1135510.05663050.09110690.02618630.06587130.0169474nan
Dollar Tree, Inc.nan0.0107560.0139570.0756270.0403380.04125190.03406940.06346550.01660250.0410471
Dollar General Corporationnan0.008255890.03750740.05897310.03692170.04619710.04260880.05077610.03952410.0460518
Walmart Inc.0.03124250.0283720.06546220.04459130.0620230.05727490.1010380.07350590.05971170.0415436
Big Lots, Inc.nan-0.5504690.02526160.1157570.0694642-0.1118530.0372190.1007210.1104430.089253
Macy's, Inc.nan0.05047260.270980.03911140.09130080.1014260.1557610.0989930.06563360.072322
Kohl's Corporationnan-0.1439610.1896770.1479680.1194920.1397990.09613670.198790.08165180.110697
The TJX Companies, Inc.0.02715880.02349750.05176870.04016680.04882660.03993520.05369650.04332790.04644160.0406432

Explore the rest of the fundamental module under the Reference section.