← research2026-10-21 · 7 min read

How to Backtest Value Investing Without Lookahead Bias

A value backtest can be mathematically correct and still use information that investors did not have at the time.

The common failure is simple: join historical prices to today’s cleaned fundamentals by fiscal period. That can place an annual report into the simulation before it was filed, overwrite original figures with later amendments, or include companies selected from today’s surviving universe.

To backtest value investing without lookahead bias, you need three controls:

  1. As-of filing dates: a fundamental becomes eligible only when it was public.
  2. A point-in-time universe: membership must reflect what was investable on each rebalance date.
  3. As-reported values: use the first-reported figure when modeling what an investor originally saw.

These controls apply whether your signal is P/E, P/B, gross profitability, operating margin, or a composite quality score.

For more background, see /blog/lookahead-bias-fundamental-backtests and /blog/point-in-time-fundamentals-data.

Why ordinary fiscal-period joins fail

Suppose a company’s fiscal year ends in December. Its annual revenue, earnings, equity, and cash-flow figures are not necessarily public on December 31. They become usable when the filing reaches SEC EDGAR.

A join like this is therefore unsafe:

fundamentals["year"] = fundamentals["fiscal_period_end"].dt.year
prices["year"] = prices["date"].dt.year
panel = prices.merge(fundamentals, on=["ticker", "year"])

It matches by reporting period, not availability. A January rebalance could receive figures filed weeks later.

The correct question is not:

Which fiscal period belongs to this calendar year?

It is:

What was the latest eligible filing publicly available by this rebalance date?

That requires an availability field such as first_filed and an as-of join where first_filed <= rebalance_date. Same-day inclusion should follow a documented convention. Tradevo Data’s server-side as_of filter is same-day inclusive.

Step 1: define the strategy before touching the data

Write down the rules first. At minimum, specify:

For example, a simple value-and-quality screen might rank eligible companies on:

Using yields rather than raw P/E or P/B often makes ranking rules easier to define around negative denominators. That is a design choice, not a guarantee of better results.

Step 2: build a point-in-time universe

Correct fundamentals do not fix survivorship bias.

If you start with companies that exist today and project them backward, failed, acquired, or delisted firms may disappear from the historical sample. The backtest then answers a different question: how today’s survivors would have performed historically.

A proper universe table should contain effective membership intervals or date-stamped snapshots, including delisted securities where the research question requires them. It should also preserve historical identifiers because tickers can change or be reused.

Tradevo Data covers US companies but does not provide delisted coverage. It should not be treated as a complete point-in-time security master. For research requiring a survivorship-free universe, pair fundamentals with an appropriate historical universe and price source—or choose a provider that integrates those components.

Step 3: select fundamentals by first_filed

For every rebalance date and company:

  1. Exclude rows where first_filed is after the rebalance date.
  2. Choose the latest eligible fiscal observation under your strategy’s period rules.
  3. Use original_value for the information first reported to the market.
  4. Retain the restatement indicator for diagnostics.
  5. Apply a lag only if it represents an explicit operational assumption, not as a substitute for filing dates.

A blanket rule such as “wait three months after fiscal year-end” is only an approximation. Some filings arrive earlier or later. Exact filing dates are cleaner because they model actual public availability.

In Tradevo Data, each row includes first_filed, original_value, latest_value, a restatement flag, and qa_status. Restatements are labeled when a same-tag change, including amendments, exceeds 0.5%. The dataset contains 37,804 labeled restatements.

A small worked example

Assume a strategy rebalances on two dates and the sample contains these conceptual rows:

ticker concept fiscal period first_filed original_value
ABC NetIncome prior annual period before first rebalance available then
ABC NetIncome latest annual period after first rebalance not yet available
ABC StockholdersEquity latest annual period before second rebalance available then

At the first rebalance, the latest-period net income must be excluded because its first_filed date is still in the future. At the second rebalance, it becomes eligible if the filing date is on or before that date.

The same principle applies to every ratio. For a point-in-time P/E calculation, both price and EPS must be known at signal time. If you calculate market capitalization from shares, the share figure also needs a valid availability rule. Never silently combine a historical price with a later-revised denominator.

Pandas example using the free sample

The public proof pack contains 225 rows for five companies, covering their latest three fiscal years, with full methodology. Download it without signup at:

https://github.com/christianpichichero-max/pit-fundamentals

The core pandas operation is a backward as-of join:

import pandas as pd

fund = pd.read_csv("fundamentals.csv", parse_dates=["first_filed"])
rebalances = pd.DataFrame({
    "ticker": ["AAPL", "AAPL"],
    "rebalance_date": pd.to_datetime(["2023-02-01", "2023-11-15"])
})

# Example concept; repeat or pivot for equity, assets, and quality inputs.
eps = fund.loc[fund["concept"].eq("EPSDiluted")].copy()
eps = eps.dropna(subset=["first_filed", "original_value"])

# merge_asof requires sorted keys.
eps = eps.sort_values(["ticker", "first_filed"])
rebalances = rebalances.sort_values(["ticker", "rebalance_date"])

pit = pd.merge_asof(
    rebalances,
    eps,
    left_on="rebalance_date",
    right_on="first_filed",
    by="ticker",
    direction="backward",
    allow_exact_matches=True
)

# Attach a price observable at the chosen signal timestamp.
pit["pe"] = pit["price_at_signal"] / pit["original_value"]

In production, add safeguards:

A useful invariant is:

assert (pit["first_filed"] <= pit["rebalance_date"]).all()

Step 4: choose an explicit restatement policy

There are two legitimate research questions:

For a historical trading simulation, the first question is usually the relevant one. Using revised values can leak later amendments into earlier portfolio decisions.

Still, keep both fields. Comparing original and latest values can reveal whether a signal is sensitive to revisions. A restated flag is also useful for audit trails and robustness checks.

Step 5: test the pipeline for leakage

Add automated tests rather than relying on visual inspection:

Also rerun the backtest with conservative execution delays. A result that disappears under modestly stricter timing assumptions may be driven by timestamp choices rather than the intended factor.

In the current five-company, 225-row public proof pack, the 224 rows with reliable filing dates showed a measured mean lookahead of 35.2 days and a maximum of 48 days. These sample statistics apply only to those 224 reliable-filing-date rows and must not be generalized to the full Tradevo Data dataset.

Data options: a fair comparison

Option Strengths Limitations Better fit when
Build directly from SEC EDGAR Maximum methodological control; public-domain source Taxonomy normalization, amendments, filing timestamps, and QA require substantial work You need custom concepts or can maintain an ingestion pipeline
Tradevo Data Point-in-time US equity fundamentals, original and latest values, filing dates, restatement labels, API and bulk JSON No delisted, non-US, TTM, or Parquet coverage; not a full market-data stack You want a budget-tier PIT fundamentals layer and can supply universe and prices
Sharadar Credible established alternative; available products may support broader research workflows Coverage and licensing details vary by product You want its specific ecosystem; see their pricing page: https://www.nasdaq.com/solutions/data-link
Tiingo Credible API-oriented market-data alternative Check its current products against your exact PIT and restatement requirements You want its API workflow; see their pricing page: https://www.tiingo.com/
QuantConnect Integrated research and execution environment with supported datasets May be less suitable if you want to own every storage and pipeline component You prefer a managed algorithmic-research platform; see their pricing page: https://www.quantconnect.com/

Verify current documentation, coverage, licensing, and pricing directly with each provider.

When they win: another option may be better

Choose another provider or platform if you need delisted-company coverage, a ready-made survivorship-free universe, international equities, TTM fields, Parquet delivery, integrated prices and corporate actions, or an execution environment. Tradevo Data does not provide those capabilities.

Sharadar, Tiingo, or QuantConnect may win when their current coverage, ecosystem, licensing, or integrated workflows better match your requirements. Building directly from SEC EDGAR may win when you need maximum methodological control or custom concepts. Verify each option’s current documentation and pricing before choosing.

An integrated platform may also be the better choice when research speed matters more than controlling each transformation. Paying for a broader stack can be rational if it reduces engineering and audit costs.

When to build it yourself

Build from SEC EDGAR when your research depends on custom taxonomy mappings, filing-document lineage, unusual accounting concepts, or transformation rules that a standardized API cannot expose.

Be realistic about maintenance. You will need to parse filings and amendments, normalize company identifiers and units, resolve duplicate facts, distinguish duration from instant concepts, track revisions, derive only defensible periods, and retain reproducible snapshots. You also need tests for silent upstream changes.

A hybrid approach is often practical: use a normalized source for standard concepts, then independently validate critical rows against EDGAR.

A practical starting point

Tradevo Data provides 632,466 annual point-in-time rows across 5,168 US companies, 16 annual concepts, and up to 12 fiscal years where available. It also provides 1,089,635 quarterly rows across 5,382 companies and seven quarterly concepts. Q4 is reported where tagged or derived and labeled where supported; derived Q4 EPS and shares are not provided.

The JSON fundamentals endpoint, /v1/fundamentals, accepts ticker, as_of, optional concept, and period=annual|quarterly; annual is the default. Bulk JSON is included with Pro through /v1/download and /v1/snapshot?as_of, each supporting period=annual|quarterly. Parquet is not included. Documentation is available at https://tradevodata.com/docs?utm_content=blog-how-to-backtest-value-investing-without-lookahead-bias.

Start by auditing the public proof pack and reproducing the as-of join yourself:

https://github.com/christianpichichero-max/pit-fundamentals

If the schema fits your process, the card-backed seven-day free trial covers 10 companies, a rolling three-year history, and 100 requests per day without bulk. It renews at $29 per month unless canceled. Pro provides complete available history, 5,000 requests per day, and bulk access:

https://tradevodata.com/?utm_content=blog-how-to-backtest-value-investing-without-lookahead-bias

The goal is not to make a backtest look better. It is to ensure every simulated decision uses only the universe, prices, and accounting information that were actually available at that moment.

Not investment advice. Verify competitor coverage, licensing, and pricing directly.

Check your own backtest against the public proof pack.

5 companies, latest 3 fiscal years, 225 point-in-time rows across 16 concepts, full methodology—no signup.

Or download the CSV straight from GitHub — CC0, no signup.

Related comparisons