Fundamental Data

An experimental API for additional financial data.

Quantiacs offers tools for the analysis of fundamental data of companies based on publications from the sec.gov website.

To construct fundamental indicators (equity, EV, EBITDA, etc.) fundamental facts are used (e.g., ‘us-gaap:Revenues’, ‘us-gaap:StockholdersEquity’, etc.).

import qnt.data as qndata
import qnt.data.secgov_fundamental as fundamental

market_data = qndata.stocks.load_ndx_data(min_date="2005-01-01")

indicators_data = fundamental.load_indicators_for(market_data, indicator_names=['roe'])

display(indicators_data.sel(field="roe").to_pandas().tail(2))
display(indicators_data.sel(asset='NAS:AAPL').to_pandas().tail(2))
display(indicators_data.sel(asset=['NAS:AAPL']).sel(field="roe").to_pandas().tail(2))


# indicators_data = fundamental.load_indicators_for(market_data)
# indicators_data = fundamental.load_indicators_for(market_data, fundamental.get_standard_indicator_names())
# indicators_data = fundamental.load_indicators_for(market_data, fundamental.get_complex_indicator_names())
# indicators_data = fundamental.load_indicators_for(market_data, fundamental.get_annual_indicator_names())

Indicator US-GAAP Facts Description
total_revenue ['us-gaap:Revenues'] Total revenue generated from business activities.
liabilities FACT_GROUPS['equity'] + ['us-gaap:Liabilities', 'us-gaap:LiabilitiesAndStockholdersEquity'] Total obligations and debts owed by the business.
assets ['us-gaap:Assets'] Total assets owned by the business.
equity FACT_GROUPS['equity'] Total stockholder's equity, including non-controlling interest.
net_income ['us-gaap:NetIncomeLoss'] Net profit or loss generated by the business.
short_term_investments ['us-gaap:ShortTermInvestments'] Investments that are expected to be converted into cash within a year.
cash_and_cash_equivalents ['us-gaap:CashAndCashEquivalentsAtCarryingValue'] Cash on hand and assets that can be quickly converted to cash.
cash_and_cash_equivalents_full FACT_GROUPS['cash_equivalents'] Extended list of cash and equivalents, including marketable securities.
operating_income ['us-gaap:OperatingIncomeLoss'] Income generated from core business operations.
income_before_taxes FACT_GROUPS['income'] Pre-tax income from all operations and sources.
income_before_income_taxes ['us-gaap:IncomeLossFromContinuingOperationsBeforeIncomeTaxesExtraordinaryItemsNoncontrollingInterest'] Similar to income_before_taxes, but more specific.
depreciation_and_amortization FACT_GROUPS['depreciation_and_amortization'] Total depreciation and amortization expenses.
interest_net FACT_GROUPS['income'] + ['us-gaap:OperatingIncomeLoss'] Net interest, calculated as interest income minus interest expense.
income_interest ['us-gaap:InvestmentIncomeInterest'] Interest income from investments.
interest_expense ['us-gaap:InterestExpense'] Expense incurred from interest payments.
interest_expense_debt ['us-gaap:InterestExpenseDebt'] Interest expense specifically related to debt.
interest_expense_capital_lease ['us-gaap:InterestExpenseLesseeAssetsUnderCapitalLease'] Interest expense specifically related to capital leases.
interest_income_expense_net ['us-gaap:InterestIncomeExpenseNet'] Net amount of interest income and expense.
losses_on_extinguishment_of_debt ['us-gaap:GainsLossesOnExtinguishmentOfDebt'] Losses incurred from the extinguishment of debt.
nonoperating_income_expense ['us-gaap:NonoperatingIncomeExpense'] Income or expenses not related to core business operations.
other_nonoperating_income_expense ['us-gaap:OtherNonoperatingIncomeExpense'] Other income or expenses that are non-operational.
debt FACT_GROUPS['debt'] All forms of long-term and short-term debt.
net_debt FACT_GROUPS['debt'] + FACT_GROUPS['cash_equivalents'] Net debt calculated by subtracting cash equivalents from total debt.
eps ['us-gaap:EarningsPerShareDiluted', 'us-gaap:EarningsPerShare'] Earnings per share, both diluted and basic.
shares FACT_GROUPS['shares'] Total number of outstanding common stock shares.
ebitda_use_income_before_taxes FACT_GROUPS['income'] + FACT_GROUPS['interest'] + FACT_GROUPS['ebitda'] Earnings before interest, taxes, depreciation, and amortization, calculated using income before taxes.
ebitda_use_operating_income FACT_GROUPS['ebitda'] + ['us-gaap:NonoperatingIncomeExpense', 'us-gaap:GainsLossesOnExtinguishmentOfDebt', 'us-gaap:InvestmentIncomeInterest'] + FACT_GROUPS['interest'] Earnings before interest, taxes, depreciation, and amortization, calculated using operating income.
ebitda_simple FACT_GROUPS['depreciation_and_amortization'] + ['us-gaap:OperatingIncomeLoss'] Simplified EBITDA calculation.
roe ['us-gaap:NetIncomeLoss', 'us-gaap:StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest', 'us-gaap:StockholdersEquity'] Return on equity, calculated as net income divided by total equity.
liabilities_divide_by_ebitda FACT_GROUPS['ebitda'] + ['us-gaap:Liabilities', 'us-gaap:StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest', 'us-gaap:StockholdersEquity', 'us-gaap:LiabilitiesAndStockholdersEquity'] + FACT_GROUPS['cash_equivalents'] Total liabilities divided by EBITDA.
net_debt_divide_by_ebitda FACT_GROUPS['ebitda'] + FACT_GROUPS['debt'] + FACT_GROUPS['cash_equivalents'] Net debt divided by EBITDA.

Example use Return on Equity (ROE)

The strategy trades liquid stocks from the Nasdaq 100 index that have a positive return on equity (ROE > 0.15)


import xarray as xr
import qnt.data as qndata
import qnt.output as qnout
import qnt.stats as qnstats
import qnt.data.secgov_fundamental as fundamental


def load_data(min_date="2005-01-01"):
    """Load market and fundamental data."""
    market_data = qndata.stocks.load_ndx_data(min_date=min_date)
    indicators_data = fundamental.load_indicators_for(market_data, indicator_names=['roe'])
    return market_data, indicators_data


def calculate_weights(data, fundamental_data):
    """Calculate weights for the strategy based on fundamental data."""
    roe = fundamental_data.sel(field="roe")
    liquidity = data.sel(field='is_liquid')
    buy = 1
    return xr.where(roe > 0.15, buy, 0) * liquidity


def add_buy_and_hold_enough_bid_for(data, weights_):
    """Add buy and hold condition based on the liquidity of the assets."""
    time_traded = weights_.time[abs(weights_).fillna(0).sum('asset') > 0]
    is_strategy_traded = len(time_traded)
    if is_strategy_traded:
        return xr.where(weights_.time < time_traded.min(), data.sel(field="is_liquid"), weights_)
    return weights_


def plot_performance(stats):
    """Plot the performance of the strategy."""
    performance = stats.to_pandas()["equity"]
    import qnt.graph as qngraph
    qngraph.make_plot_filled(performance.index, performance, name="PnL (Equity)", type="log")


market_data, indicators_data = load_data()

weights = calculate_weights(market_data, indicators_data)
weights = add_buy_and_hold_enough_bid_for(market_data, weights)
weights = qnout.clean(weights, market_data, "stocks_nasdaq100")

stats = qnstats.calc_stat(market_data, weights.sel(time=slice("2006-01-01", None)))
display(stats.to_pandas().tail())
plot_performance(stats)

weights = weights.sel(time=slice("2006-01-01", None))
qnout.check(weights, market_data, "stocks_nasdaq100")
qnout.write(weights)  # to participate in the competition


Potential Issues in Working with Fundamental Data:

  • Inconsistency in fact publication among companies:

    • One company might not publish a specific fact but might provide other data from which this fact can be derived.

    • Another company, on the contrary, might directly provide the fact, omitting intermediary data.

  • Lack of standardized formulas for indicators:

    • Not all indicators have standard calculation formulas.

    • For some of them, each company decides on its own which fundamental facts should be used to form the indicator.

    • This can lead to the same company using different data at different times for one indicator.

    • It’s not accurate to compare companies based on such indicators.

  • Changing the strategy of indicator construction:

    • When updating financial statements, a company may change the methodology or calculation formulas for indicators, introducing an element of uncertainty.

  • Errors and corrections in reports:

    • Reports can contain errors, which are corrected later, but the initial data can distort the analysis.

  • Data omissions:

    • Some facts might be missing in the reports.

    • Companies might release their reports on different dates.

  • Issues with indicators based on stock prices:

    • If a company conducts a stock split before publishing a report, indicators can show unexpected changes, distorting the analysis.

The current implementation of Quantiacs partially resolve these issues:

  • When constructing an indicator, one formula is used for all companies, allowing them to be compared under “similar” conditions.

  • If key data for calculation is missing, the algorithm tries to restore it using other facts or indicators.

  • If data from the SEC gov report is missing, the algorithm tries to restore the missing information based on annual and quarterly reports, or if absent, uses average values.

  • By default, the strategy for constructing indicators is over 12 months (LTM). Users can build indicators for the quarter (QF) or use annual values (AF).

Discover available attributes (us-gaap taxonomy) here. Introduction to Financial Statements here

Example create specific financial indicator

The example demonstrates how to create your own financial indicator. You need to specify:

  • what facts are required for its creation;

  • the algorithm for its construction.


import numpy as np
import qnt.data as qndata
import qnt.data.secgov_fundamental as fundamental



def custom_build_equity(fundamental_facts):
    equity_full = fundamental_facts.sel(
        field='us-gaap:StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest')
    equity_simple = fundamental_facts.sel(field='us-gaap:StockholdersEquity')

    equity = equity_full.where(~np.isnan(equity_full), equity_simple)

    return equity


custom_builder = {
    'equity': {'facts': [
        'us-gaap:StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest',
        'us-gaap:StockholdersEquity'
    ],
        'build': custom_build_equity},
}

market_data = qndata.stocks.load_ndx_data(min_date="2005-01-01")

indicators_data = fundamental.load_indicators_for(market_data,
                                                  indicator_names=['equity'],
                                                  indicators_builders=custom_builder)

display(indicators_data.sel(field="equity").to_pandas().tail(2))
display(indicators_data.sel(asset='NAS:AAPL').to_pandas().tail(2))
display(indicators_data.sel(asset=['NAS:AAPL']).sel(field="equity").to_pandas().tail(2))

Example use specific us-gaap

This example provides a code for creating a stock trading strategy by downloading and utilizing fundamental data from SEC filings. Users can customize the script to fetch any specific financial fact for companies listed in the NASDAQ 100 index.


import time
import xarray as xr
import numpy as np
import pandas as pd

import qnt.data as qndata
import qnt.stats as qnstats
import qnt.output as qnout
import qnt.ta as qnta

GLOBAL_MIN_DATE = "2005-01-01"


def load_stock_data():
    """Load stock data and create a CIK to asset dictionary."""
    stock_list = qndata.stocks.load_ndx_list(min_date=GLOBAL_MIN_DATE)
    cik_asset_dict = {a['cik']: a for a in stock_list if a['cik']}
    data = qndata.stocks.load_ndx_data(min_date=GLOBAL_MIN_DATE, assets=stock_list)
    return stock_list, cik_asset_dict, data


def get_fundamental_indicators():
    """Return a list of fundamental indicators."""
    return [
        'us-gaap:OtherLiabilitiesNoncurrent',
        'us-gaap:ProfitLoss',
        'us-gaap:EarningsPerShareBasic',
        # ...
        'us-gaap:ShareBasedCompensation'
    ]


def extract_last_fact(facts, name):
    """Extract the latest fact with the given name from a list of facts."""
    relevant_facts = [fact for fact in facts if fact['name'] == name]
    return max(relevant_facts, key=lambda f: f['period']['value'])['value'] if relevant_facts else None


def get_fundamental_data(data, min_date_start, cik_asset_dict):
    """Load fundamental data for the given assets."""
    fundamental_indicators = get_fundamental_indicators()
    fundamental_data = xr.concat(
        [data.sel(field='close')] * len(fundamental_indicators),
        pd.Index(fundamental_indicators, name='field')
    )
    fundamental_data[:] = np.nan

    progress = 0
    start_time = time.time()

    for form in qndata.secgov.load_forms(
            ciks=list(cik_asset_dict.keys()),
            types=['10-K'],
            facts=fundamental_indicators,
            skip_segment=True,
            min_date=min_date_start,
    ):
        facts = form['facts']
        progress += 1

        if progress % 500 == 0:
            print("Progress:", progress, form['date'], time.time() - start_time)

        asset_id = cik_asset_dict[form['cik']]['id']

        if asset_id not in data.asset.values:
            continue

        date = form['date']
        nearest_date = fundamental_data.time.loc[date:]
        if len(nearest_date) < 1:
            print("wrong date", form['date'])
            continue

        nearest_date = nearest_date[0].values

        for indicator in fundamental_indicators:
            fundamental_data.loc[{'asset': asset_id, 'time': nearest_date, 'field': indicator}] \
                = extract_last_fact(facts, indicator)

    ratios = fundamental_data.ffill('time')
    return ratios


def calculate_weights(data, fundamental_data):
    """Calculate weights for the strategy based on fundamental data."""
    earnings = fundamental_data.sel(field="us-gaap:EarningsPerShareBasic")
    liquidity = data.sel(field='is_liquid')
    long_lwma = qnta.lwma(earnings, 50)
    short_lwma = qnta.lwma(earnings, 100)
    buy = 1
    return xr.where(short_lwma > long_lwma, buy, 0) * liquidity


def add_buy_and_hold_enough_bid_for(data, weights_):
    """Add buy and hold condition based on the liquidity of the assets."""
    time_traded = weights_.time[abs(weights_).fillna(0).sum('asset') > 0]
    is_strategy_traded = len(time_traded)
    if is_strategy_traded:
        return xr.where(weights_.time < time_traded.min(), data.sel(field="is_liquid"), weights_)
    return weights_


def plot_performance(stats):
    """Plot the performance of the strategy."""
    performance = stats.to_pandas()["equity"]
    import qnt.graph as qngraph
    qngraph.make_plot_filled(performance.index, performance, name="PnL (Equity)", type="log")


stock_list, cik_asset_dict, data = load_stock_data()
fundamental_data = get_fundamental_data(data, GLOBAL_MIN_DATE, cik_asset_dict)
weights = calculate_weights(data, fundamental_data)
weights = add_buy_and_hold_enough_bid_for(data, weights)
weights = qnout.clean(weights, data, "stocks_nasdaq100")

stats = qnstats.calc_stat(data, weights.sel(time=slice("2006-01-01", None)))
display(stats.to_pandas().tail())

plot_performance(stats)

weights = weights.sel(time=slice("2006-01-01", None))

qnout.check(weights, data, "stocks_nasdaq100")
qnout.write(weights)  # to participate in the competition