Documentation

Fundamentals

QuantCraft strategies can use company financial data alongside OHLCV price bars: balance sheets, cash-flow statements, income statements, and earnings history / estimates. The data is loaded once per symbol from the QuantCraft fundamentals service, organized as date-keyed tables, and made available to your strategy as bar-aligned snapshots during a backtest. Callback timing follows Strategy lifecycle; configure symbols and dates in Test (Running code).

This page covers:

  • What financial statement data is available
  • How date-keyed tables are organized
  • How bar-aligned payloads reach your callbacks during a backtest

What you get

Each fundamentals bundle is a single JSON object for one ticker, containing:

  • Balance sheetquarterly and yearly reports.
  • Cash flowquarterly and yearly reports.
  • Income statementquarterly and yearly reports.
  • Earnings:
    • History — reported EPS vs. estimates and surprises by report date.
    • Trend — forward estimates, EPS trend, and analyst revisions.
    • AnnualepsActual per fiscal year.

Each report is a row of camelCase metrics — for example totalAssets, cash, freeCashFlow, totalRevenue, netIncome, epsActual, epsEstimate, surprisePercent. Numeric values may arrive as strings in the underlying JSON, and any missing field comes back as None.

A given ticker may not expose every section. The IDE’s Fundamentals documentation page lists the full set of fields you can expect across each section.


Date-keyed tables

For each section above, fundamentals are organized as a date-keyed table: rows are keyed by report date (YYYY-MM-DD), and metrics are exposed as parallel lists that you can index like price history.

The standard table paths are:

PathMeaning
balanceSheet.quarterly / balanceSheet.yearlyBalance sheet snapshots
cashFlow.quarterly / cashFlow.yearlyCash flow statements
incomeStatement.quarterly / incomeStatement.yearlyIncome statements
earnings.HistoryReported EPS, estimates, surprise per report
earnings.TrendAnalyst estimates, EPS trend, revisions
earnings.AnnualAnnual epsActual

Index convention: newest first

Inside any of these tables, index 0 is the most recent eligible period, index 1 is the previous one, and so on. This matches how price series work in the rest of QuantCraft.

If you want to load a bundle directly (for example outside a backtest), the basic shape is:

from ide.fundamentals_module import QcFundamentals f = QcFundamentals(token, "fundamentals/AAPL.json.gz") latest_total_assets = f.tables["balanceSheet.quarterly"]["totalAssets"][0] prior_total_assets = f.tables["balanceSheet.quarterly"]["totalAssets"][1] same = f.lists["balanceSheet.quarterly.totalAssets"][0] whole = f.raw

What you can read off a loaded bundle:

AttributeWhat it gives you
f.rawThe full decoded JSON tree.
f.symbolResolved ticker (e.g. "AAPL").
f.tablesDict of DateKeyedTable objects keyed by dotted path (e.g. "balanceSheet.quarterly").
f.table(path)Convenience accessor for a single DateKeyedTable.
f.listsFlat map keyed by "<tablePath>.<fieldName>", values aligned newest-first.

A DateKeyedTable exposes periods (the YYYY-MM-DD keys, newest first), columns (field → values), and supports table["fieldName"] indexing.

Discovering fields at runtime is easy: sorted(f.lists.keys()) shows every flat series, and sorted(t.columns.keys()) shows the columns of one table — useful when a server adds or removes fields.


Bar-aligned payloads in backtests

In a backtest, you don’t read fundamentals as raw tables — the engine selects the right report for the current bar and gives it to your callbacks as a snapshot. This is called the bar-aligned payload.

Configuring fundamentals for a backtest

In the Test (backtest) dialog, you provide:

  • A fundamentals token (the QuantCraft authorization token), and
  • One or more fundamentals sources:
    • a single path (one symbol),
    • a list of paths (several symbols), or
    • a symbols map ({"AAPL": "fundamentals/AAPL.json.gz", ...}).

For multi-bundle runs, you can also choose a primary symbol — that bundle’s snapshot is what gets merged into each OHLCV bar’s fundamentals field by default.

If fundamentals are not configured, the engine simply passes fundamentals=None to your callbacks, and you guard for it.

The fundamentals callback parameter

When fundamentals are loaded, the engine passes a BacktestFundamentals handle as the fundamentals argument to on_bar, on_tick, and (when used) on_timer:

def on_bar(bar_index, bar, fundamentals=None, symbol=None): if fundamentals is None: return snap = fundamentals.current(bar["t"]) bs_q = (snap.get("balanceSheet") or {}).get("quarterly") or {} total_assets = bs_q.get("totalAssets")

What the snapshot looks like

A bar-aligned snapshot is a nested dict with only the periods that are valid as of this bar. Each non-null section includes a periodEnd plus the report’s metrics:

{ "barTime": "2026-06-01T00:00:00+00:00", "symbol": "AAPL", "balanceSheet": { "quarterly": { "periodEnd": "2026-03-31", "totalAssets": 352000000.0, "cash": 51000000.0 }, "yearly": { "periodEnd": "2025-09-30", "totalAssets": 344000000.0 } }, "cashFlow": { "quarterly": { "periodEnd": "2026-03-31", "freeCashFlow": 24500000.0 } }, "incomeStatement": { "quarterly": { "periodEnd": "2026-03-31", "totalRevenue": 95000000.0, "netIncome": 23000000.0 } }, "earnings": { "History": { "periodEnd": "2026-03-31", "reportDate": "2026-05-02", "epsActual": 1.53 }, "Trend": null, "Annual": { "periodEnd": "2025-09-30", "epsActual": 6.13 } } }

The same per-bar dict appears under each row’s fundamentals field in the structured backtest result, so what you see in your strategy matches what shows up in the result data.

Looking up older periods (get_data_with_offset)

Sometimes you want not the latest report as of a bar, but a previous one — the prior quarter, prior fiscal year, the previous earnings line:

prior = fundamentals.get_data_with_offset(bar["t"], 1) prev_bs_q = (prior or {}).get("balanceSheet", {}).get("quarterly")

Behavior:

  • Each sub-table is indexed independently. With offset=1, the balanceSheet.quarterly section steps back one quarter, while balanceSheet.yearly steps back one fiscal year, and so on.
  • If a sub-table doesn’t have enough history before the current bar, its section is null in the returned dict (and a one-line note may be printed).
  • The shape is the same as current(...), plus a top-level "offset": <int>.

Multi-symbol fundamentals

When you load multiple bundles (for cross-sectional or comparison strategies), you can pick which one to read from with symbol="TICKER":

def on_bar(bar_index, bar, fundamentals=None, symbol=None): if fundamentals is None: return primary_snap = fundamentals.current(bar["t"]) msft_snap = fundamentals.current(bar["t"], symbol="MSFT") aapl_prior = fundamentals.get_data_with_offset(bar["t"], 1, symbol="AAPL")
  • Omitting symbol uses the primary bundle (the only one loaded, or the one set by fundamentals.primary).
  • fundamentals.symbols lists every uppercase ticker key currently loaded.
  • fundamentals.primary_symbol() returns the primary key.
  • The trading symbol you’re backtesting can be different from the fundamentals symbol(s) you load — for example, trade SPY while reading fundamentals for AAPL and MSFT.

Acceptable bar times

Both current(...) and get_data_with_offset(...) accept several time formats so you can pass bar["t"], bar.t[0], or your own value:

  • datetime (naive treated as UTC),
  • date,
  • ISO string (including a trailing Z),
  • UNIX epoch seconds (int / float).

Practical patterns

A few common shapes:

def on_bar(bar_index, bar, fundamentals=None, symbol=None): if fundamentals is None: return snap = fundamentals.current(bar["t"]) if not snap: return bs_q = (snap.get("balanceSheet") or {}).get("quarterly") or {} is_q = (snap.get("incomeStatement") or {}).get("quarterly") or {} if (bs_q.get("totalAssets") or 0) > 0 and (is_q.get("netIncome") or 0) > 0: ...
def on_bar(bar_index, bar, fundamentals=None, symbol=None): if fundamentals is None: return snap = fundamentals.current(bar["t"]) or {} prior = fundamentals.get_data_with_offset(bar["t"], 1) or {} cur = ((snap.get("incomeStatement") or {}).get("quarterly") or {}).get("totalRevenue") prev = ((prior.get("incomeStatement") or {}).get("quarterly") or {}).get("totalRevenue") if cur and prev: revenue_growth = (cur - prev) / prev

Fundamentals field reference

Field availability depends on the symbol — not every issuer reports every line. Use sorted(f.lists.keys()) or sorted(t.columns.keys()) at runtime to see what a specific bundle actually exposes. Numeric values may arrive as strings; cast with float(...) for math.

Balance sheet

Tables: balanceSheet.quarterly, balanceSheet.yearly

FieldField
accountsPayablelongTermInvestments
accumulatedAmortizationnegativeGoodwill
accumulatedDepreciationnetDebt
accumulatedOtherComprehensiveIncomenetInvestedCapital
additionalPaidInCapitalnetReceivables
capitalLeaseObligationsnetTangibleAssets
capitalStocknetWorkingCapital
capitalSurplusenoncontrollingInterestInConsolidatedEntity
cashnonCurrrentAssetsOther
cashAndEquivalentsnonCurrentAssetsTotal
cashAndShortTermInvestmentsnonCurrentLiabilitiesOther
commonStocknonCurrentLiabilitiesTotal
commonStockSharesOutstandingotherAssets
commonStockTotalEquityotherCurrentAssets
currency_symbolotherCurrentLiab
currentDeferredRevenueotherLiab
dateotherStockholderEquity
deferredLongTermAssetChargespreferredStockRedeemable
deferredLongTermLiabpreferredStockTotalEquity
earningAssetspropertyPlantAndEquipmentGross
filing_datepropertyPlantAndEquipmentNet
goodWillpropertyPlantEquipment
intangibleAssetsretainedEarnings
inventoryretainedEarningsTotalEquity
liabilitiesAndStockholdersEquityshortLongTermDebt
longTermDebtshortLongTermDebtTotal
longTermDebtTotalshortTermDebt
shortTermInvestmentstemporaryEquityRedeemableNoncontrollingInterests
totalAssetstotalCurrentAssets
totalCurrentLiabilitiestotalLiab
totalPermanentEquitytotalStockholderEquity
treasuryStockwarrants

Cash flow

Tables: cashFlow.quarterly, cashFlow.yearly

FieldField
beginPeriodCashFlowendPeriodCashFlow
capitalExpendituresexchangeRateChanges
cashAndCashEquivalentsChangesfiling_date
cashFlowsOtherOperatingfreeCashFlow
changeInCashinvestments
changeInWorkingCapitalissuanceOfCapitalStock
changeReceivablesnetBorrowings
changeToAccountReceivablesnetIncome
changeToInventoryotherCashflowsFromFinancingActivities
changeToLiabilitiesotherCashflowsFromInvestingActivities
changeToNetincomeotherNonCashItems
changeToOperatingActivitiessalePurchaseOfStock
currency_symbolstockBasedCompensation
datetotalCashflowsFromInvestingActivities
depreciationtotalCashFromFinancingActivities
dividendsPaidtotalCashFromOperatingActivities

Income statement

Tables: incomeStatement.quarterly, incomeStatement.yearly

FieldField
costOfRevenuenetIncomeFromContinuingOps
currency_symbolnetInterestIncome
datenonOperatingIncomeNetOther
depreciationAndAmortizationnonRecurring
discontinuedOperationsoperatingIncome
effectOfAccountingChargesotherItems
ebitotherOperatingExpenses
ebitdapreferredStockAndOtherAdjustments
extraordinaryItemsreconciledDepreciation
filing_dateresearchDevelopment
grossProfitsellingAndMarketingExpenses
incomeBeforeTaxsellingGeneralAdministrative
incomeTaxExpensetaxProvision
interestExpensetotalOperatingExpenses
interestIncometotalOtherIncomeExpenseNet
minorityInteresttotalRevenue
netIncome
netIncomeApplicableToCommonShares

Earnings — History

Table: earnings.History (reported EPS vs. estimates per report date)

FieldDescription
beforeAfterMarketWhen the report was released relative to market hours
currencyReporting currency
datePeriod end date
epsActualReported EPS
epsEstimateConsensus EPS estimate
epsDifferenceepsActual − epsEstimate
reportDateDate the figures were reported
surprisePercentEPS surprise as a percentage

Earnings — Trend

Table: earnings.Trend (forward estimates and revisions)

FieldField
dateepsTrendCurrent
periodepsTrend7daysAgo
growthepsTrend30daysAgo
earningsEstimateAvgepsTrend60daysAgo
earningsEstimateLowepsTrend90daysAgo
earningsEstimateHighepsRevisionsUpLast7days
earningsEstimateYearAgoEpsepsRevisionsUpLast30days
earningsEstimateNumberOfAnalystsepsRevisionsDownLast7days
earningsEstimateGrowthepsRevisionsDownLast30days
revenueEstimateAvg
revenueEstimateLow
revenueEstimateHigh
revenueEstimateYearAgoEps
revenueEstimateNumberOfAnalysts
revenueEstimateGrowth

Earnings — Annual

Table: earnings.Annual (annual reported EPS)

FieldDescription
dateFiscal-year period end date
epsActualReported annual EPS

Notes and gotchas

  • Always guard for None. If fundamentals are not configured, fundamentals is None. Inside a snapshot, individual sections (e.g. earnings.Trend) can also be null for some bars.
  • Numbers may be strings. Underlying JSON often ships numeric metrics as strings; coerce with float(...) when doing math.
  • Newest-first indexing applies to date-keyed tables and to offset lookups — index 0 is the most recent eligible period as of the bar, 1 is the one before it, etc.
  • The bar’s timestamp is the source of truth for which report is "current" — pass bar["t"] (or bar.t[0]) directly; you don’t need to parse it yourself.