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:
Build an offline SEC-style fixture and provider
Inspect raw SEC Company Facts metadata
Inspect the canonical statement tables
Build credit inputs and assess proxy metrics
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.