All Posts
TutorialBacktestingPoint-in-TimeLook-Ahead BiasQuantPIT DataAmendments

The Holy Grail API for Stock Backtesting: Point-in-Time Fundamentals With a Full Amendment Audit Trail

Look-ahead bias is the silent killer of quant backtests. StockFit is the only fundamentals API that ships a per-fact, per-amendment audit trail with `before` values, a true point-in-time ledger you can mathematically rewind. Available on every tier, including free.

Published April 30, 202613 min readStockFit Engineering
The Holy Grail API for Stock Backtesting: Point-in-Time Fundamentals With a Full Amendment Audit Trail

Stock backtesting is a time-travel problem. Every result hinges on one question: did your engine know this fact on this day, or did it cheat? When the answer is “cheat,” the strategy looks brilliant in simulation and bleeds in production. The technical name is look-ahead bias, and it is the silent killer of quant research.

Most fundamentals APIs make look-ahead bias unavoidable. They ship a single normalized snapshot, silently overwrite original numbers when companies amend their filings, and leave you no way to reconstruct what the market actually saw on a given Tuesday three years ago. This post walks through why that happens, and how the StockFit fundamentals endpoints solve it. As of April 2026, StockFit is the only commercial fundamentals API that exposes a per-fact, per-amendment audit trail with before values out of the box, on every tier including free.

What stock backtesting actually is

Backtesting replays a trading strategy against historical market data. You start with a hypothesis (buy stocks where return on invested capital is rising and price-to-book is below the sector median, say), feed it the data the market had at each point in time, and let it issue simulated trades. The output is an equity curve, a Sharpe ratio, a drawdown profile, and a verdict: does this idea actually work?

Done well, backtesting is the closest thing quant research has to a wind tunnel. Done poorly, it is an expensive way to lie to yourself. The single rule that separates the two is this: at simulation time T, your engine may only see information that was actually public on or before T. No previews, no peeks, no “the company restated this number two years later but I'll pretend that's what they reported originally.”

Strategies that depend on this discipline include Graham value screens, Piotroski F-Score quality factors, dividend kings ladders, Magic Formula composites, sector momentum rotations, and any modern pipeline routing news or filings text through transformer models like FinBERT. They all share a layer-1 ingestion problem: where does the historical fundamentals data come from, and is it clean enough to trust?

Look-ahead bias: the silent backtest killer

Look-ahead bias is the bug where your historical simulation uses information that was not yet public. It is rarely a single bad line of code. More often it is data leakage baked into the API you are pulling from. Three failure modes account for almost all real cases.

1. Filing-date leakage

A company's fiscal year ends December 31, 2023. The 10-K is filed on February 15, 2024. A naive ingestion stamps the FY2023 facts with the period end date and joins them to prices starting January 1, 2024. The strategy now trades on annual results six weeks before they were actually disclosed. Every fundamentals factor will quietly outperform during that window, and none of the lift is real.

2. Amendment leakage (the deeper trap)

Suppose the same company files on February 15, 2024, then files a restatement on March 10, 2024 that nudges revenue up by 0.5%. The market reacted to the original numbers on February 15 and reacted again to the corrected numbers on March 10. If your data provider silently overwrites the original facts with the amended ones but leaves the filing date as February 15, your engine now trades on the corrected revenue almost a month before the market saw it.

This is the failure mode almost no one catches, because everything looks fine on the surface. The numbers tie out to the latest 10-K/A. The ratios reconcile. The factor signals are stable. The only symptom is that the backtest beats production by a margin that is just plausible enough to ship. Ten years of dividend kings histories can carry hundreds of silent restatements per company.

3. Survivorship-adjacent leakage

Amendments do not only change values. They sometimes add facts that were missing in the original filing, or split a single line item into segment-level disclosures. A backtest that joins on whatever is available today will see segment revenue retroactively present in periods where the issuer never originally tagged it. Same problem, different shape.

Why a single dateFiled is not enough
Even APIs that surface the original 10-K filing date still time-travel on amendments. Without a per-fact ledger of what changed and when, there is no way to roll a value back to its February 15 state once March 10 arrives. The filing-date stamp solves leakage class 1 only. To eliminate classes 2 and 3, the audit trail has to be in the data itself.

The solution: StockFit's normalized fundamentals endpoints

StockFit ships three normalized fundamentals endpoints, all returning the same shape:

Each period in the response carries seven fields: period (fiscal period end date), fiscalYear, fiscalPeriod (FY, Q1, Q2, Q3, Q4, or TTM), dateFiled (the original disclosure date, never the amendment date), facts (the normalized line items), sources (the per-fact, per-filing audit trail), and derived (the list of facts that were synthesized rather than taken from a filing, like grossProfit when the issuer did not tag it explicitly).

These three endpoints are available on every tier, including free. The audit trail itself is identical at every tier. Paid tiers only scale historical depth: more periods returned per call. A free-tier key is enough to prove the data model end-to-end against the most recent quarters of any US issuer before deciding whether to upgrade for the multi-decade history a serious backtest needs.

For the deeper story on how the underlying XBRL gets normalized in the first place, see Converting XBRL Financials to JSON for Backtesting. This post focuses on what comes after normalization: the audit trail.

What the response actually returns

Two minimal examples make the shape concrete. The first shows a clean filing with no amendments yet. The second shows the same period after a restatement.

Example 1: clean filing, no amendments yet

json
{
  "period": "2024-09-28",
  "fiscalYear": 2024,
  "fiscalPeriod": "FY",
  "dateFiled": "2024-11-01",                // The gate. Only use these facts at backtest dates >= 2024-11-01.
  "facts": {
    "revenue": 391035000000,
    "netIncome": 93736000000,
    "eps": 6.11
  },
  "sources": {
    "0000320193-24-000123": {
      "type": "10-K",
      "dateFiled": "2024-11-01",
      "amendment": false,
      "facts": {
        "revenue": {},                      // Empty {} = original value, never restated.
        "netIncome": {},
        "eps": {}
      }
    }
  }
}

Example 2: same period after a restatement

json
{
  "period": "2024-09-28",
  "fiscalYear": 2024,
  "fiscalPeriod": "FY",
  "dateFiled": "2024-11-01",                // Original disclosure date, unchanged by amendments.
  "facts": {
    "revenue": 391035000000,
    "netIncome": 93700000000,               // Current (post-amendment) value.
    "eps": 6.10
  },
  "sources": {
    "0000320193-24-000123": {
      "type": "10-K",
      "dateFiled": "2024-11-01",
      "amendment": false,
      "facts": {
        "revenue": {},
        "netIncome": {},
        "eps": {}
      }
    },
    "0000320193-25-000015": {
      "type": "10-K",
      "dateFiled": "2025-02-14",            // When the restatement hit the tape.
      "amendment": true,
      "facts": {
        "netIncome": { "before": 93736000000 },   // Use this value for any backtest dated before 2025-02-14.
        "eps": { "before": 6.11 }
        // revenue absent here = unchanged by this amendment.
      }
    }
  }
}

Four interpretation rules cover every case:

  • An empty {} per-fact entry means this filing was the first to supply that fact (or did not change its value from the prior filing).
  • { before: N } means this filing changed the fact, and N is the value the immediately-prior filing carried.
  • A fact name absent from a filing's facts map means that filing did not touch that fact.
  • The keys of sources are sorted ascending by dateFiled. JSON key order is the chronological audit trail.

That is enough to reconstruct any historical state. To know what revenue was on March 1, 2025 versus March 1, 2026, you walk sources chronologically, ignore any filing whose dateFiled is in the future relative to your simulation date, and follow the before chain backward for any restatement that would otherwise leak.

Why this is the only API that does this
Per-fact before deltas. Full chronological accession trail, including amendments whose values were superseded. Original dateFiled never overwritten. Synthesized facts flagged in derived so backtesters know which numbers come straight from the filing and which were computed. Available on the free tier. We have not found another commercial API that exposes any of this, let alone all of it.

Mathematically eliminating look-ahead bias

With the audit trail in hand, point-in-time reconstruction is mechanical. For any backtest date D, two operations are enough:

  • Drop any period whose dateFiled is later than D. Those facts were not yet public.
  • For each surviving period, walk sources in reverse chronological order. Every amendment whose dateFiled is later than D gets undone: for each { before: N } entry it carries, roll the corresponding fact value back to N.

That is the entire algorithm. Here it is end-to-end against the live API:

js
// Reconstruct point-in-time fundamentals for AAPL as of any historical date.
const ASOF = '2025-01-15';

const res = await fetch(
  'https://api.stockfit.io/api/financials/income-statement?symbol=AAPL&period=quarter&limit=20',
  { headers: { 'x-api-key': process.env.STOCKFIT_API_KEY } }
);
const periods = await res.json();

function rewind(period, asOf) {
  // 1. The period must have been public on or before asOf.
  if (period.dateFiled > asOf) return null;

  // 2. Walk amendments newest-first; undo any change filed after asOf.
  const facts = { ...period.facts };
  const futureAmendments = Object.values(period.sources)
    .filter(s => s.amendment && s.dateFiled > asOf)
    .sort((a, b) => b.dateFiled.localeCompare(a.dateFiled));

  for (const amendment of futureAmendments) {
    for (const [label, change] of Object.entries(amendment.facts)) {
      if ('before' in change) facts[label] = change.before;
    }
  }

  return { ...period, facts, asOf };
}

const pointInTime = periods.map(p => rewind(p, ASOF)).filter(Boolean);
console.log(pointInTime);

Because dateFiled values are ISO strings (YYYY-MM-DD), the lexicographic comparisons are also chronological comparisons. No date library needed for the temporal logic. The function returns null for periods that were not yet public on asOf, so a single filter(Boolean) gives you exactly the slice of history your strategy is allowed to see on that day.

Try it yourself with the StockFit MCP server

The same three endpoints are exposed through the StockFit Model Context Protocol server, so you can interrogate the audit trail directly from Claude, Cursor, or any MCP-compatible client. This is the fastest way to convince yourself that the before values are real, before you write a line of integration code.

The MCP tool names are:

  • financials_income_statement
  • financials_balance_sheet
  • financials_cash_flow_statement

Ask your assistant to fetch the income statement, balance sheet, or cash flow statement for any symbol and to list the periods where any sources[*].facts.*.before entry is present. Those are the restatements. Pick any one of them and ask the assistant to compute the prior value, the new value, and the date the change became public. The full ledger is right there in the response. Configuration details are in the API documentation.

Why no other API does this

The honest answer is that it is genuinely hard. A per-fact audit trail is not a feature you bolt onto an existing fundamentals API. It is a property of the underlying data model, and you only get it if, from day one, every raw fact from every filing is preserved with its accession number and filing date intact, in a structure that lets you replay the precedence rules at query time.

Most providers do the opposite. They normalize at ingestion, write a single row per company per period into a relational warehouse, and discard the source XBRL. When an amendment lands, the row is overwritten. The audit signal is gone before the data ever reaches the customer. Reverse-engineering the trail later is impossible because the inputs no longer exist on the provider's side.

StockFit was built the other way. Every fact from every filing, original and every amendment, lives in a wide DuckDB raw-fact table keyed on the SEC accession number. Normalization is a query-time projection, not an ingestion-time decision. That choice is invisible to most callers, who just want a clean income statement. It becomes visible the moment a backtester asks what changed and when, because the answer is already in the data.

Most fundamentals APIs

  • Normalize once at ingestion, discard the source XBRL.
  • Silent overwrites when companies amend their filings.
  • No before values, no per-fact audit trail.
  • Original dateFiled often replaced by the latest amendment's date.
  • Point-in-time data sold separately, usually as an enterprise add-on.

StockFit API

  • Every raw fact from every filing retained, keyed on accession number.
  • Audit trail computed at query time from the original SEC source.
  • Per-fact before deltas on every restated value.
  • Full chronological accession trail, amendments included.
  • Original dateFiled preserved, derived facts flagged separately.
  • Available on the free tier. Paid tiers only extend historical depth.

Stated plainly: as of April 2026, StockFit is the only commercial fundamentals API that exposes a per-fact, per-amendment audit trail with before values out of the box. We can do this because we have every number, from every filing, sourced straight from SEC EDGAR, and the data model was built for it from day one. For a side-by-side with another well-known XBRL-to-JSON provider, see StockFit API vs. sec-api.io.

FAQ

What is point-in-time fundamentals data?

Point-in-time (PIT) fundamentals data is financial data tagged with the date it actually became public, not just the date it describes. A FY2023 income statement is PIT-correct only if the response also tells you the original 10-K was filed on, say, February 15, 2024, and that any later restatements are flagged with their own filing dates. Without that, every backtest that joins fundamentals to prices is a step away from look-ahead bias.

How do I avoid look-ahead bias in a backtest?

Two rules. First, never use a fact in a simulation dated earlier than the fact's original filing date. Second, never use a restated value in a simulation dated earlier than the restatement's filing date; roll the value back to the prior before instead. The StockFit financials endpoints expose both signals natively, so the unwind logic is a few lines of code rather than a research project.

Does StockFit return SEC filing dates with the financial statements?

Yes. Every period carries a top-level dateFiled that is always the original 10-K, 10-Q, 20-F, or 40-F filing date, never an amendment date. Every entry in the sources map carries its own dateFiled too, so amendments have first-class disclosure dates as well.

Can I see when a 10-K was amended and what changed?

Yes. Each amendment shows up as a separate entry in the period's sources map with amendment: true and its own dateFiled. Inside that entry, every fact that the amendment changed carries a { before: N } showing the prior value. Facts the amendment left alone are simply absent. There is no extra endpoint to call and no parsing of amendment narratives required.

Is the audit trail available on the free tier?

Yes. The full per-fact, per-amendment audit trail is on every tier, including free. Paid tiers only scale how far back the historical periods reach, so a free key is enough to verify the data model against recent quarters before committing to a multi-decade backtest.

The Holy Grail, in production

Look-ahead bias is solvable, but only if the data carries the temporal information needed to solve it. StockFit's normalized fundamentals endpoints are the only public API that ship that information by default, with per-fact before values, full amendment timelines, and original filing dates intact. Pair the response with the unwind algorithm above and a backtester gets a mathematically lookahead-free history of US fundamentals out of the box. For the upstream story on how the XBRL gets normalized into clean JSON in the first place, see Converting XBRL Financials to JSON for Backtesting.

Ready to build?

Free API key, no credit card. Every endpoint mentioned in this post is available on the free tier.

Get Your Free API Key