SEC EDGAR/XBRL Fundamentals

This notebook demonstrates AbaQuant’s SEC EDGAR/XBRL fundamentals provider using a fixture shaped exactly like the official SEC Company Facts JSON — so it runs fully offline while still exercising the real pipeline:

SEC Company Facts -> canonical statement tables -> credit inputs -> proxy metrics

It also demonstrates disk-cache reuse for both raw SEC JSON and normalized statement snapshots. For live SEC access, construct a ticker with fundamentals_provider="sec", set a real contact sec_user_agent (or the ABAQUANT_SEC_USER_AGENT environment variable), and use financial_cache="disk".

Sections:

  1. Build an offline SEC-style fixture and provider

  2. Inspect raw SEC Company Facts metadata

  3. Inspect the canonical statement tables

  4. Build credit inputs and assess proxy metrics

  5. Demonstrate disk-cache reuse across provider instances

Setup

import abaquant
print(f"AbaQuant version: {abaquant.__version__}")
AbaQuant version: 1.0.0rc1
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any

from abaquant.marketdata import get_ticker
from abaquant.marketdata.providers import SecCompanyFacts, SecXbrlProvider

1. Build an offline SEC-style fixture and provider

_sec_company_facts_fixture() mimics the shape of SEC’s real Company Facts JSON (us-gaap and dei taxonomies, one fact per reporting period). OfflineSecProvider subclasses AbaQuant’s real SecXbrlProvider and simply substitutes the fixture for an HTTP call.

def _fact(value: float, end: str, *, fp: str = "FY", form: str = "10-K") -> dict[str, Any]:
    return {"val": value, "end": end, "fp": fp, "form": form, "filed": "2026-03-01"}


def _sec_company_facts_fixture() -> dict[str, Any]:
    current = "2026-01-31"
    prior = "2025-01-31"

    def usd(*records):
        return {"units": {"USD": list(records)}}

    def shares(*records):
        return {"units": {"shares": list(records)}}

    return {
        "cik": 1045810,
        "entityName": "NVIDIA CORP",
        "facts": {
            "us-gaap": {
                "RevenueFromContractWithCustomerExcludingAssessedTax": usd(
                    _fact(400.0, current), _fact(360.0, prior)
                ),
                "GrossProfit": usd(_fact(200.0, current), _fact(180.0, prior)),
                "OperatingIncomeLoss": usd(_fact(80.0, current), _fact(70.0, prior)),
                "InterestExpenseNonOperating": usd(_fact(5.0, current), _fact(6.0, prior)),
                "NetIncomeLoss": usd(_fact(50.0, current), _fact(40.0, prior)),
                "EarningsBeforeInterestTaxesDepreciationAmortization": usd(
                    _fact(95.0, current), _fact(80.0, prior)
                ),
                "Assets": usd(_fact(440.0, current), _fact(400.0, prior)),
                "AssetsCurrent": usd(_fact(180.0, current), _fact(160.0, prior)),
                "InventoryNet": usd(_fact(30.0, current), _fact(40.0, prior)),
                "LiabilitiesCurrent": usd(_fact(90.0, current), _fact(90.0, prior)),
                "CashAndCashEquivalentsAtCarryingValue": usd(
                    _fact(25.0, current), _fact(20.0, prior)
                ),
                "Liabilities": usd(_fact(190.0, current), _fact(200.0, prior)),
                "StockholdersEquity": usd(_fact(300.0, current), _fact(270.0, prior)),
                "RetainedEarningsAccumulatedDeficit": usd(
                    _fact(150.0, current), _fact(130.0, prior)
                ),
                "DebtAndFinanceLeaseObligations": usd(_fact(120.0, current), _fact(130.0, prior)),
                "LongTermDebtAndFinanceLeaseObligationsNoncurrent": usd(
                    _fact(100.0, current), _fact(110.0, prior)
                ),
                "NetCashProvidedByUsedInOperatingActivities": usd(
                    _fact(64.0, current), _fact(55.0, prior)
                ),
            },
            "dei": {
                "EntityCommonStockSharesOutstanding": shares(
                    _fact(1000.0, current), _fact(1000.0, prior)
                )
            },
        },
    }


class OfflineSecProvider(SecXbrlProvider):
    """SEC provider variant that serves fixture Company Facts instead of HTTP."""

    def __init__(self):
        super().__init__(cik_by_symbol={"NVDA": "1045810"})

    def company_facts(self, symbol, **kwargs):
        clean_symbol = symbol.upper()
        cached = self._company_facts_cache.get(clean_symbol)
        if cached is not None:
            return cached
        facts = SecCompanyFacts(clean_symbol, "0001045810", _sec_company_facts_fixture())
        self._company_facts_cache[clean_symbol] = facts
        return facts


class OfflineQuoteProvider:
    """Minimal quote provider used alongside SEC fundamentals."""

    name = "offline-quotes"

    def fast_info(self, symbol):
        return {"last_price": 100.0}

    def info(self, symbol):
        return {"marketCap": 1000.0}


ticker = get_ticker(
    "NVDA",
    provider=OfflineQuoteProvider(),
    fundamentals_provider=OfflineSecProvider(),
)

2. Inspect raw SEC Company Facts metadata

facts = ticker.financials.sec_facts()
print(f"Entity name: {facts.get('entityName')}")
print(f"CIK:         {facts.get('cik')}")
print(f"Taxonomies:  {sorted(facts.get('facts', {}))}")
Entity name: NVIDIA CORP
CIK:         1045810
Taxonomies:  ['dei', 'us-gaap']

3. Inspect the canonical statement tables

SEC-derived facts are normalized into the same canonical tables used by every other provider in AbaQuant.

ticker.financials.balance_sheet(source="sec").head()
2026-01-31 2025-01-31
line_item
Stockholders Equity 300.0 270.0
Current Assets 180.0 160.0
Inventory 30.0 40.0
Current Liabilities 90.0 90.0
Cash And Cash Equivalents 25.0 20.0
ticker.financials.income_statement(source="sec").head()
2026-01-31 2025-01-31
line_item
Total Revenue 400.0 360.0
Gross Profit 200.0 180.0
Operating Income 80.0 70.0
Interest Expense 5.0 6.0
Net Income 50.0 40.0
ticker.financials.cash_flow_statement(source="sec").head()
2026-01-31 2025-01-31
line_item
Operating Cash Flow 64.0 55.0

4. Build credit inputs and assess proxy metrics

The same credit_inputs() / assess_from_financials() pipeline used with any other provider, now fed from SEC facts.

inputs = ticker.financials.credit_inputs(source="sec")
assessment = ticker.credit.assess_from_financials(source="sec")

print("Credit inputs resolved from SEC-style facts:")
for key in ("total_debt", "total_equity", "revenue", "operating_cash_flow", "previous_total_assets"):
    print(f"  {key:24s}: {getattr(inputs, key)}")

print("\nCredit proxy metrics from SEC-style facts:")
for key in ("debt_to_equity", "net_debt_to_ebitda", "altman_z_score", "piotroski_f_score"):
    print(f"  {key:28s}: {assessment.metrics[key]}")
print(f"  {'synthetic_credit_proxy_score':28s}: {assessment.synthetic_credit_proxy_score}")
Credit inputs resolved from SEC-style facts:
  total_debt              : 120.0
  total_equity            : 300.0
  revenue                 : 400.0
  operating_cash_flow     : 64.0
  previous_total_assets   : 400.0

Credit proxy metrics from SEC-style facts:
  debt_to_equity              : 0.4
  net_debt_to_ebitda          : 1.0
  altman_z_score              : 5.389712918660288
  piotroski_f_score           : 8
  synthetic_credit_proxy_score: 100.0

5. Demonstrate disk-cache reuse across provider instances

financial_cache="disk" persists both the raw SEC JSON and the normalized statement snapshot. A second, independent provider instance can then read from cache with refresh_policy="cache_only" and make zero JSON requests.

class CachedOfflineSecProvider(SecXbrlProvider):
    """SEC provider variant that demonstrates persistent raw JSON caching."""

    def __init__(self, cache_directory):
        super().__init__(cache_mode="disk", cache_directory=cache_directory)
        self.request_count = 0

    def _request_json(self, url):
        self.request_count += 1
        if url.endswith("company_tickers.json"):
            return {"0": {"ticker": "NVDA", "cik_str": 1045810, "title": "NVIDIA CORP"}}
        return _sec_company_facts_fixture()


with TemporaryDirectory() as directory:
    first_provider = CachedOfflineSecProvider(directory)
    first_ticker = get_ticker(
        "NVDA",
        provider=OfflineQuoteProvider(),
        fundamentals_provider=first_provider,
        financial_cache="disk",
        cache_directory=directory,
    )
    first_total_debt = first_ticker.financials.total_debt(source="sec")

    second_provider = CachedOfflineSecProvider(directory)
    second_ticker = get_ticker(
        "NVDA",
        provider=OfflineQuoteProvider(),
        fundamentals_provider=second_provider,
        financial_cache="disk",
        cache_directory=directory,
    )
    second_total_debt = second_ticker.financials.total_debt(
        source="sec", refresh_policy="cache_only"
    )
    raw_facts = second_ticker.financials.sec_facts(refresh_policy="cache_only")

    print(f"First total debt:              {first_total_debt}")
    print(f"Second total debt (cache-only): {second_total_debt}")
    print(f"First provider JSON requests:   {first_provider.request_count}")
    print(f"Second provider JSON requests:  {second_provider.request_count} (should be 0)")
First total debt:              120.0
Second total debt (cache-only): 120.0
First provider JSON requests:   2
Second provider JSON requests:  0 (should be 0)

Takeaway

The SEC provider slots into the exact same MarketTicker facade as any other provider. For live requests, remember to set a real, project-specific sec_user_agent — SEC EDGAR requires it and will reject anonymous or generic user agents.