← research2026-07-08 · 8 min read

How to Detect Lookahead Bias in a Backtest: A Practical Checklist

Lookahead bias in a fundamentals backtest almost never announces itself. It shows up as a backtest that looks unusually good, and then a live strategy that quietly stops working. The mechanism is boring: your join used a date the data wasn't actually public yet. This article is a checklist for finding that, not a pitch for a particular dataset — though we'll use our own free sample to show working code, because it's the one we can show you column-by-column without asking anything of you.

This is not investment advice. Nothing here promises a better-performing backtest — only a more honest one, with fewer joins that leak future information into the past.

Symptom 1: Your signal times earnings suspiciously well

If a fundamentals-driven signal enters positions right before earnings-driven price moves with better-than-random timing, check what date you used to decide the fundamental was "known." A common bug: joining on fiscal period end date (e.g., Q4 2019 = 2019-12-31) instead of the date the 10-K was actually filed and made public. Every company reports its fiscal year end weeks to months before the filing exists. If your backtest thinks Q4 data is available on the period end date, it's trading on the future.

Symptom 2: Live performance decays right after paper trading ends

This is the classic tell. A strategy backtests clean, goes live (or into a genuinely walk-forward paper test), and the edge shrinks or disappears. If the strategy relies on fundamentals and nothing else changed — no regime shift, no capacity issue — lookahead bias in the historical joins is the first thing to rule out, because live trading is the one environment where lookahead is structurally impossible.

Symptom 3: Performance is fragile to a few outlier dates

Run your equity curve breakdown by trade. If a disproportionate share of PnL comes from trades placed in a narrow window around known reporting seasons, and those specific trades have unusually strong hit rates, look at whether the fundamental value used in the join was restated later and your backtest is silently using the restated number instead of what was originally reported.

The Join-Date Audit

The fastest way to check: pull your actual join logic and ask, for every fundamental value, "what date did my code think this became true?" Then compare that to the date it was actually filed. Two dates get confused constantly:

If your pipeline uses the former where it should use the latter, you have a lookahead bug baked into every single row, sized to the actual filing lag for that company and quarter.

A pandas check using first_filed

Our free PIT sample (40 companies, 3,212 rows, no signup) includes a first_filed column specifically so you can run this check yourself. Example:

import pandas as pd

df = pd.read_csv("sample.csv", parse_dates=["first_filed", "fiscal_period_end"])

def check_leakage(df, as_of_date, join_col="fiscal_period_end"):
    as_of = pd.Timestamp(as_of_date)
    naive_join = df[df[join_col] <= as_of]        # what a naive backtest would include
    correct_join = df[df["first_filed"] <= as_of]  # what was actually public by as_of
    leaked = naive_join[~naive_join.index.isin(correct_join.index)]
    return leaked

leaked_rows = check_leakage(df, "2020-03-31")
print(f"{len(leaked_rows)} of {len(df)} rows would be lookahead-leaked "
      f"if you joined on fiscal_period_end instead of first_filed")

If leaked_rows is non-empty for a realistic as_of date, your naive join is including fundamentals before they existed publicly. Swap the join key to first_filed (or whatever your data vendor's equivalent "became public" field is called) and rerun.

The Lag-Shift Test

A second, independent check that doesn't require inspecting your join code line by line: shift your as_of date backward by a fixed number of days across your entire backtest — say 30, 60, 90 days — and rerun. If performance is fairly stable under small shifts, that's a decent sign your logic isn't sitting right on a knife's edge of leaked information. If performance craters the moment you shift even slightly earlier, your original result was likely dependent on data being available earlier than it should have been. This is a blunt instrument, not proof, but it's cheap to run and catches a lot of accidental lookahead.

For deeper background on why this matters mechanically, see lookahead bias in fundamental backtests and our overview of point-in-time fundamentals.

Comparing Your Options for Fixing It

Approach Effort Coverage Cost Best for
Scrape EDGAR yourself, track filing dates High Whatever you build Your time Teams needing full control, custom concepts, or non-US data
Research-grade PIT vendors (e.g. Sharadar, Tiingo, QuantConnect) Low Broad, often quarterly + more concepts See their pricing pages Funds/teams needing quarterly data, longer history, or bulk delivery — this is genuinely where they win over us
Tradevo Data Low 5,212 US companies, 7 concepts, annual only, up to 12 fiscal years $49/mo Individual quants who need PIT-safe annual US fundamentals cheaply, via API
Free PIT sample (GitHub) Low 40 companies, 3,212 rows Free Prototyping the join logic and running the checks in this article before paying for anything

We're not the only affordable option, and we're not claiming to be — the vendors above are credible and some cover more ground (quarterly data, longer history, bulk delivery). See their pricing pages directly since we won't quote numbers we don't control.

When to Build It Yourself (or Use Someone Else)

Being honest about where Tradevo Data isn't the right tool — and where the vendors above genuinely win:

If none of those apply and you just want a point-in-time-safe first_filed/original_value pair for US annual fundamentals without building the EDGAR pipeline yourself, that's the actual use case we built for.

Try It Before You Pay for Anything

Start with the free sample — 40 companies, full methodology, no signup — and run the join-date audit and lag-shift test above on your own logic. If it holds up and you want the full 5,212-company, 313,489-row dataset with first_filed, original_value, latest_value, and restatement flags via API, it's $49/mo at Tradevo Data, instant key after checkout, cancel anytime, docs at /docs.

If you're actively comparing PIT data providers, our alternatives page lays out where we fit and where we don't.


Not investment advice; verify competitor pricing yourself on their own pricing pages.

Check your own backtest against the free sample.

40 large caps, 3,212 point-in-time rows, full methodology — no signup.