← All posts

S&P 500 Members: How Many Survive 20 Years in the Index?

Only 238 of the 481 S&P 500 companies tracked from 2005 are still in the index in 2025. Point-in-time index turnover and survivorship analysis in Python.

What’s the question?

Any backtest that draws its universe from a current index membership list is testing a portfolio nobody could have held. The companies in the S&P 500 today are there partly because they performed well enough to stay. Survivorship bias is the name for that error: measuring a strategy on a sample already filtered by the outcome the strategy is meant to predict.

The practical question is how large the distortion is. If index membership were close to permanent, using a current constituent list as a historical universe would be a minor approximation. If membership churns heavily, that list describes a different population from the one that existed twenty years ago, and every result computed on it inherits the difference.

A second question is worth settling at the same time. Index exit is often treated as a constant annual hazard, so that a 5% yearly turnover rate is assumed to leave about 60% of a cohort standing after ten years. Whether that convenient assumption holds is a matter of measurement. A cohort study of S&P 500 entrants measures ten-year survival from the year each company joined; the test below follows a whole roster from one date instead.

The approach

Two datasets answer this. The first is the index event log: every addition and removal with its effective date, from 1990 through the end of 2025. The second is the point-in-time constituent list, the roster as it actually stood on a chosen date rather than one reconstructed backwards from today.

  1. Pull every membership change between 1990 and 2025 and count additions by calendar year.
  2. Express additions as a share of the 500 available slots to get an annual turnover rate, then invert that rate for the average tenure it implies.
  3. Take the roster as it stood on 31 December 2005 and follow that fixed cohort forward, checking membership again at the end of 2010, 2015, 2020 and 2025.
  4. Compare the observed survival at each horizon against what a constant annual exit rate would predict.

Each company is tracked by its persistent entity identifier rather than by ticker symbol. Symbols get reassigned and companies rename themselves, so a symbol-based join records identity changes as exits and misses real ones. The identifier stays attached to the company, which turns “is this still the same member” into a question the data answers rather than one the analyst judges.

Code

import pandas as pd
import xfinlink as xfl

xfl.set_api_key("YOUR_API_KEY")  # free at https://xfinlink.com/signup

parts, offset = [], 0
while True:
    page = xfl.index_events("sp500", start="1990-01-01", end="2025-12-31",
                            limit=1000, offset=offset)
    if page.empty:
        break
    parts.append(page)
    offset += len(page)
    if len(page) < 1000:
        break

events = pd.concat(parts, ignore_index=True)
events["year"] = pd.to_datetime(events["effective_date"]).dt.year
by_year = (events.groupby(["year", "event_type"]).size()
           .unstack(fill_value=0).reindex(range(1990, 2026), fill_value=0))
by_year["turnover_pct"] = by_year["added"] / 500 * 100

# Follow one fixed cohort forward, tracked by entity identifier
base = xfl.index("sp500", as_of="2005-12-31", limit=1000)
cohort_ids = set(base["entity_id"].dropna())
mean_turnover = by_year.loc[2006:2025, "turnover_pct"].mean() / 100

for as_of, years in [("2010-12-31", 5), ("2015-12-31", 10),
                     ("2020-12-31", 15), ("2025-12-31", 20)]:
    later = xfl.index("sp500", as_of=as_of, limit=1000)
    left = len(cohort_ids & set(later["entity_id"].dropna()))
    print(f"{as_of}  {years:2d}y  still in {left:3d}  "
          f"{left / len(cohort_ids) * 100:5.1f}%  "
          f"flat-rate {(1 - mean_turnover) ** years * 100:5.1f}%")

Full script with formatting and visualisation: sp500-index-turnover-survivorship-python.py

Output

Membership events 1990-2025: 1,693  (added 847, removed 846)

Additions per year, five-year blocks
  1991-1995    14.8 adds/yr    2.96% turnover    33.8 yr implied tenure
  1996-2000    37.2 adds/yr    7.44% turnover    13.4 yr implied tenure
  2001-2005    19.6 adds/yr    3.92% turnover    25.5 yr implied tenure
  2006-2010    30.0 adds/yr    6.00% turnover    16.7 yr implied tenure
  2011-2015    20.4 adds/yr    4.08% turnover    24.5 yr implied tenure
  2016-2020    27.4 adds/yr    5.48% turnover    18.2 yr implied tenure
  2021-2025    17.4 adds/yr    3.48% turnover    28.7 yr implied tenure

Survival of the 481 companies tracked from the 2005-12-31 roster
  average turnover 2006-2025: 4.76% a year
  date         years  still in    share  flat-rate
  2010-12-31       5       356    74.0%      78.4%
  2015-12-31      10       305    63.4%      61.4%
  2020-12-31      15       262    54.5%      48.1%
  2025-12-31      20       238    49.5%      37.7%

What this tells us

Turnover is cyclical rather than trending. The five-year blocks range from 2.96% a year in 1991-1995 up to 7.44% in 1996-2000, and the most recent block sits near the bottom of that range at 3.48%. Implied tenure makes each rate easier to hold in mind: at the pace of 1996-2000 the index replaces itself in 13.4 years, while the pace of 2021-2025 stretches that to 28.7 years.

Half the cohort is gone within twenty years. Of the 481 companies tracked from the end of 2005, 238 were still index members at the end of 2025. A study that starts in 2005 with today’s constituents therefore draws from a population omitting slightly more than half of what was available at the time, and the omitted half is not a random sample of it. How the members of a starting roster fared against the index itself, departed companies included, is measured in a count of S&P 500 stocks that beat the index.

The constant-hazard assumption fails in both directions, which is the more useful result. Over the first five years the cohort lost members faster than a flat 4.76% rate predicts, 74.0% remaining against 78.4%, because the 2006-2010 window carried elevated turnover of 6.00% a year as the credit crisis pushed companies out. Past ten years the relationship inverts and keeps widening. At twenty years the flat rate predicts 37.7% and the observed figure is 49.5%, a gap of almost twelve percentage points.

Two forces produce that shape. The exit rate varies by period, so any single average misprices calm and turbulent stretches alike. The cohort also hardens: companies still in the index after fifteen years are disproportionately large, long-tenured members a committee has no reason to remove, while the names most at risk of deletion left early. One exponential curve cannot represent both effects.

So what?

Build the historical universe from the roster that applied on the rebalance date, which the as_of parameter of the index membership endpoint returns directly. A strategy tested that way holds companies that later failed or were acquired, and the weaker result it produces is the honest one. Over a twenty-year window the correction touches about half the sample, which is enough to change conclusions rather than merely tighten them. The same correction, applied to portfolio size, is the subject of a test of how many stocks diversify a portfolio on dated and current rosters.

Do not model index exit as a constant annual hazard. The numbers above show that approximation over-predicting survival at five years and under-predicting it by twelve points at twenty, so a turnover assumption calibrated on a recent decade will misstate a longer study in a direction that depends on which decade was used. Where the horizon matters, measure the cohort directly at each date rather than compounding one rate.

Track membership by persistent entity identifier as well. Over a span this long companies rename and change symbols, and a symbol-keyed join quietly records those events as exits, inflating apparent turnover. Both habits, rosters by date and keys by entity, carry over to a year-by-year ranking of the largest S&P 500 companies.

Built with xfinlink — free financial data API for Python.