The cleanest way to see survivorship bias is to count it. We pulled the breadth options universe on a fixed day — 2020-03-16 — and again on the most recent full session, 2026-10-30:
| Session | Symbols |
|---|---|
| 2020-03-16 | 1,573 |
| 2026-10-30 | 1,521 |
The head counts look stable, which is exactly the trap. A flat total hides that a lot of the names turned over. We took the 2020 list and probed it against two recent sessions. Result: 1,027 (65.3%) of the 2020 names still quote, and 546 (34.7%) no longer do — they were delisted, merged, renamed, or went private in the intervening five years.
Why 35% is a lot
A backtest that builds its universe from today's tickers then runs it backwards is
optimizing on the answer. It only includes the names that made it to 2026. The 546 that did not
— AAWW (Atlas Air, taken private), ABMD, AGN (Allergan,
absorbed by Novartis), ALXN (Alexion, by Eli Lilly), ANTM (Antam, by
UnitedHealth), AKRX — were, in 2020, perfectly normal names you were meant to be
able to buy or avoid.
Drop them and your historical strategy never saw the trades where those names failed. If your edge is relative value across a universe, you have silently removed the worst performers — the exact names that were delisted because they underperformed.
The three ways it sneaks in
- Universe built from today's list. The most common. You iterate over live symbols and assume they were always there.
- Renamed tickers split into two.
ABCbecameAPTV; the free mirror has no permanent security id, so the rename is two unrelated symbols to your loader unless you map them. - Point-in-time IV from a survivor-only feed. If the underlying print is also missing for the gone names, your implied-vol series is thinner exactly where you think the market was stressed.
How to test whether you're affected
You do not need our data to find out — you need the universe you backtested against and a delisting list. For every name in your historical book, ask: was it listed on the day I claimed to trade it? Any name that is listed in the file but absent from the live tape on its own first historical date is a survivorship tell. The count of those is your exposure. A dataset that keeps the pre-delisting rows (ours does, in the breadth tier) lets you run the check; a dataset that only ever contains live names cannot, because the evidence is not in the file.
That is the whole argument: you cannot detect survivorship bias in data that has already removed the dead. Keep the dead and the check is possible.
Want to run the check on real rows?
The breadth tier keeps the gone names and their history. Grab the free SPY + NVDA sample, or
order the breadth tier, and the census script is in the repo: pipeline/universe.py.