advanced
10 minPoint-in-Time Universe Construction
Build a survivorship-bias-free historical S&P 500 universe for any rebalance date.
PITuniversebacktestingDuckDB
## The Survivorship Bias Problem
If you use today's S&P 500 list to backtest 2018 strategies, you're including companies that weren't in the index in 2018 — and excluding companies that were delisted since then. This is survivorship bias and it inflates backtest returns.
python
from valuein_sdk import ValueinClient, ValueinError
try:
with ValueinClient() as client:
def get_pit_universe(as_of_date: str, index: str = "SP500"):
"""Returns the exact index constituents as of as_of_date."""
return client.query(f"""
SELECT r.entity_id, r.symbol, r.name, r.sector
FROM references r
JOIN index_membership im ON r.security_id = im.security_id
WHERE im.index_name = '{index}'
AND im.start_date <= DATE '{as_of_date}'
AND (im.end_date IS NULL OR im.end_date > DATE '{as_of_date}')
""")
# S&P 500 as of Jan 1, 2020 (before COVID additions)
universe_2020 = get_pit_universe("2020-01-01")
print(f"S&P 500 size on 2020-01-01: {len(universe_2020)} companies")
# Compare to current S&P 500
universe_now = get_pit_universe("2026-01-01")
print(f"S&P 500 size on 2026-01-01: {len(universe_now)} companies")
# Companies that are in today's S&P 500 but weren't in 2020
new_additions = set(universe_now.symbol) - set(universe_2020.symbol)
print(f"Added since 2020: {len(new_additions)} companies")
print(sorted(new_additions)[:10])
except ValueinError as e:
print(f"Error: {e}")Try it yourself
Get your API token and run this notebook against 105M+ real SEC EDGAR facts.