{"slug":"look-ahead-bias-in-backtesting","qid":"survivorship_universe","label":"Survivorship in the universe: names trading each year, share still listed in July 2026, and median return","post_title":"Look-Ahead Bias: The Backtest Killer","post_url":"/blog/look-ahead-bias-in-backtesting#q-survivorship_universe","columns":["year","names_trading","still_listed_pct","median_return_all_pct","median_return_survivors_pct","survivor_gap_pp"],"rows":[{"year":2015,"names_trading":7063,"still_listed_pct":52.5,"median_return_all_pct":-5.39,"median_return_survivors_pct":-5.29,"survivor_gap_pp":0.09},{"year":2016,"names_trading":7009,"still_listed_pct":55.4,"median_return_all_pct":7.65,"median_return_survivors_pct":10.85,"survivor_gap_pp":3.2},{"year":2017,"names_trading":7092,"still_listed_pct":57.7,"median_return_all_pct":7.51,"median_return_survivors_pct":11.32,"survivor_gap_pp":3.81},{"year":2018,"names_trading":7245,"still_listed_pct":61.1,"median_return_all_pct":-13.67,"median_return_survivors_pct":-13.03,"survivor_gap_pp":0.64},{"year":2019,"names_trading":7605,"still_listed_pct":63.4,"median_return_all_pct":14.1,"median_return_survivors_pct":17.04,"survivor_gap_pp":2.94},{"year":2020,"names_trading":7745,"still_listed_pct":67.9,"median_return_all_pct":1.06,"median_return_survivors_pct":2.74,"survivor_gap_pp":1.68},{"year":2021,"names_trading":8506,"still_listed_pct":69.1,"median_return_all_pct":6.88,"median_return_survivors_pct":10.47,"survivor_gap_pp":3.59},{"year":2022,"names_trading":9217,"still_listed_pct":73.5,"median_return_all_pct":-20.26,"median_return_survivors_pct":-19.95,"survivor_gap_pp":0.31},{"year":2023,"names_trading":9013,"still_listed_pct":80.8,"median_return_all_pct":4.65,"median_return_survivors_pct":5.78,"survivor_gap_pp":1.13},{"year":2024,"names_trading":9257,"still_listed_pct":87.5,"median_return_all_pct":2.24,"median_return_survivors_pct":2.9,"survivor_gap_pp":0.66}],"shape":"table","sql":"WITH recent AS (\n    SELECT DISTINCT ticker\n    FROM global_markets.stocks_daily_aggs\n    WHERE date >= toDate('2026-06-15')\n      AND date <= toDate('2026-07-28')\n),\nper_name AS (\n    SELECT toYear(date) AS year,\n           ticker,\n           argMin(toFloat64(close), date) AS first_close,\n           argMax(toFloat64(close), date) AS last_close,\n           count() AS sessions\n    FROM global_markets.stocks_daily_aggs\n    WHERE date >= toDate('2015-01-01')\n      AND date <= toDate('2024-12-31')\n      AND close > 1\n    GROUP BY year, ticker\n    HAVING sessions >= 200\n)\nSELECT year,\n       uniqExact(ticker) AS names_trading,\n       round(100 * uniqExactIf(ticker, ticker IN (SELECT ticker FROM recent)) / uniqExact(ticker), 1) AS still_listed_pct,\n       round(100 * quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker)), 2) AS median_return_all_pct,\n       round(100 * quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent)), 2) AS median_return_survivors_pct,\n       round(100 * (quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent))\n                    - quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker))), 2) AS survivor_gap_pp\nFROM per_name\nGROUP BY year\nORDER BY year","computed_at":"2026-07-31T08:39:53.744018+00:00","elapsed":0.004156306}