Survivorship Bias vs Lookahead Bias: The Two Silent Backtest Killers
Most blown-up backtests aren't wrecked by a bad strategy idea. They're wrecked by two data problems that quietly inflate returns: survivorship bias and lookahead bias. They're often mentioned in the same breath, which is a mistake — they come from different places, they fail differently, and one of them is much easier to catch than the other.
This article defines both, explains why lookahead bias is the sneakier of the two, walks through how ordinary fundamentals data causes each one, and lays out what actually fixes them (spoiler: it's not one thing).
Survivorship bias, in one sentence
Your backtest only includes companies that still exist today, so every failure, delisting, and bankruptcy is invisible — and your historical universe silently gets better with hindsight than it actually was at the time.
It's the classic example: you backtest a value strategy on "all S&P 500 companies" using today's constituent list, applied to 2005 data. Every company that got delisted, acquired, or dropped from the index between 2005 and today never shows up in your test. Your strategy looks great because it only ever traded winners-in-hindsight.
Survivorship bias is loud once you know to look for it. If your universe is built from a current list of tickers and applied to past dates, you have it. The fix is mechanical: use a point-in-time universe (constituents as they were, on each date, including the companies that later failed).
Lookahead bias, in one sentence
Your backtest uses a data value on a date before that value was actually knowable, so the strategy is trading on information from the future.
This is the sneaky one. It rarely announces itself. It hides inside something completely mundane: a merge, a join, a groupby().last().
Why lookahead bias is sneakier
Survivorship bias is a universe problem — you either have delisted names in your data or you don't, and that's checkable with a row count. Lookahead bias is a timing problem buried inside data that otherwise looks perfectly normal.
Here's the mechanism most people never notice: financial databases typically store one row per fiscal period, keyed by fiscal period end date (e.g., "FY2022, ended 2022-12-31") — not by the date the number was actually disclosed. A 10-K for fiscal year 2022 isn't filed on December 31, 2022. It's filed weeks or months later, in early-to-mid 2023. If your backtest joins fundamentals to prices using the fiscal period end date as the effective date, you're handing your strategy Q4 2022 revenue on December 31, 2022 — months before any investor could have known it.
The merge runs without errors. The output looks like a normal dataframe. Nothing crashes. Numbers just quietly get used before they were public, because the strategy is trading on data from the future relative to the date you think it's trading on. That's why lookahead bias survives code review, survives peer review, and often survives into live trading before it's caught — because catching it requires knowing to ask "on what date was this number actually public?", not just "is this number correct?"
Restatements make it worse. Companies revise prior-period numbers (that's the restated flag you'll see in decent PIT datasets, and amendments — 10-K/As — are a normal part of that). If your data only stores the latest, current value, you're not just early, you're not even testing the number the market originally saw.
How ordinary fundamentals data causes both, side by side
| Survivorship bias | Lookahead bias | |
|---|---|---|
| Root cause | Universe built from current tickers, not historical ones | Data keyed by fiscal period end, not filing/disclosure date |
| What's missing | Delisted, acquired, bankrupt companies | The gap between period end and public filing date |
| Where it hides | The universe list itself | A join/merge on date, invisible in the code |
| How obvious is it | Fairly obvious once you check for delistings | Not obvious — no error, no crash, silently optimistic |
| Typical inflation | Overstates returns by excluding losers | Overstates returns by using future information |
| Fix | Point-in-time universe / constituent history | Point-in-time fundamentals, filtered by first-known date |
| Also affected by restatements? | Not directly | Yes — original vs. latest values matter |
The actual fix: PIT data + a stable universe
There isn't one fix for both, because they're two different bugs:
- For survivorship bias: build or buy a universe that includes delisted/failed companies as of each historical date, not a snapshot of today's list applied backward.
- For lookahead bias: use fundamentals filtered by the date a value first became public (
first_filed <= as_of), not the fiscal period end date. And decide deliberately whether you're testing against theoriginal_value(what the market actually saw first) or thelatest_value(post-restatement), because those are two different questions.
Do both, and the numbers your backtest trades on are the numbers an investor could actually have had, on the date the strategy would have acted. We go deeper on the mechanics of the merge bug specifically in Lookahead Bias in Fundamental Backtests, and on what point-in-time data actually looks like row-by-row in Point-in-Time Fundamentals, Explained.
On our own 40-company free sample, the measured gap between fiscal period end and first public filing averaged 43.4 days and reached up to 61 days on the reliable-filing rows — scoped to that sample only, not a claim about the broader dataset. That gap is exactly the window lookahead bias exploits.
When the other option is the better choice
Being honest about scope matters more than winning the comparison. A few cases where you should look elsewhere:
- You need quarterly fundamentals. Tradevo Data is annual-only (10-K/10-K/A) right now; quarterly is on the roadmap, not shipped. If your strategy needs 10-Q cadence today, that's a real gap.
- You need international coverage. This is US-only, sourced from SEC EDGAR. Non-US equities need a different provider entirely.
- You need bulk Parquet files or very high request volume. The API is JSON, rate-limited to 5,000 requests/day. If you're doing large-scale offline research across the full universe repeatedly, a bulk-file provider fits better.
- You need decades of history or more concepts. This dataset covers up to 12 fiscal years and 7 core concepts. Established research-grade vendors like Sharadar, Tiingo, or QuantConnect may offer more concepts, longer history, or broader asset-class coverage depending on the plan — see their pricing pages directly, since we won't quote competitor prices here.
- You'd rather build it yourself. SEC EDGAR is public domain. If you have the engineering time, you can pull filings, extract first-filed dates, and track restatements yourself. It's genuinely doable — it's also exactly the plumbing work this dataset already did, which is the tradeoff to weigh against $49/month.
If none of those apply and you mainly need clean, point-in-time US equity fundamentals without the merge-bug risk, the free sample is the lowest-friction way to see the actual row structure: github.com/christianpichichero-max/pit-fundamentals — 40 companies, 3,212 rows, full methodology, no signup. When you're ready for the full 5,214-company dataset, the API is at tradevodata.com.
Not investment advice; verify competitor pricing yourself.