Skip to content
python.financial

Survivorship bias occurs when inclusion in a historical sample depends on remaining observable at a later date. A common example is applying today's stock-index constituents to ten years of historical prices. Securities that were removed, merged, liquidated, or delisted disappear from earlier cross-sections even though they were investable then.

The result is distorted, but its direction and size are not universal. Omitting poor-performance delistings often makes long-only results too optimistic. Omitting an acquired company and its positive terminal payoff can move a result the other way. The effect depends on the universe, strategy, weights, exit reasons, terminal returns, and missing-data policy.

Survivorship bias is one form of point-in-time data failure. It differs from ordinary look-ahead in mechanism, but both let future information determine a historical decision.

Where survivorship bias enters a backtest

A current ticker list is only the most visible route. Bias can enter through:

  • a present-day index, exchange, fund, or vendor universe applied to earlier dates,
  • a database that drops inactive securities or liquidated funds,
  • a query that requires every asset to have data through the sample end,
  • removal of columns containing missing values,
  • omission of a delisting return, merger consideration, liquidation distribution, or fund transfer,
  • ticker joins that lose companies after name, exchange, or symbol changes,
  • present-day sector, country, market-cap, or security-type classifications,
  • a minimum-history filter that excludes short-lived listings from the whole sample, and
  • vendor backfill rules that add a successful fund's earlier record only after it elects to report.

Not every missing observation means the same thing. A NaN can be pre-listing, post-delisting, a market holiday, a trading halt, or a data error. Filling every missing value with zero or dropping the whole column cannot distinguish those states.

Delisting is an economic event, not just the end of a file

A security's last exchange price is not always the investor's final value. A merger can deliver cash or replacement shares. Bankruptcy or an exchange removal can leave an over-the-counter claim, a later distribution, or no recovery. The correct treatment depends on the event and the data available.

Tyler Shumway's 1997 delisting-bias study documented large omitted returns for many performance-related delistings in historical CRSP data. CRSP's current aggregate-return flags separately identify returns compounded through a delisting date and, when appropriate, include the delisting return. This distinction matters. Removing the asset after its last quoted return can erase the final economic loss.

The same issue applies to investment products. Elton, Gruber, and Blake's mutual-fund survivorship study followed all funds existing at a historical start date and incorporated merger terms when funds disappeared. A database of funds that remain marketed today answers a different question.

Do not replace every missing delisting return with a total loss by default. That can be as misleading as assuming zero. Use the documented payoff where available. If it is unavailable, state and stress-test a reason-specific imputation policy.

Python example: the bias can move either way

This six-period example has two securities that survive, one that disappears after an 80% terminal loss, and one that disappears after a 25% acquisition return. Each portfolio equal-weights the names observable at the start of a period and rebalances without costs.

import numpy as np


survivors = np.array([
    [0.02, -0.01],
    [-0.01, 0.01],
    [0.01, 0.00],
    [0.00, 0.01],
    [0.02, -0.01],
    [0.01, 0.02],
])
failed = np.array([0.01, -0.02, -0.80, np.nan, np.nan, np.nan])
acquired = np.array([0.00, 0.03, 0.25, np.nan, np.nan, np.nan])


def equal_weight_total(returns):
    active_portfolio_return = np.nanmean(returns, axis=1)
    return np.prod(1 + active_portfolio_return) - 1


survivor_only = equal_weight_total(survivors)
with_failure = equal_weight_total(
    np.column_stack((survivors, failed))
)
with_acquisition = equal_weight_total(
    np.column_stack((survivors, acquired))
)

print(f"Current survivors only: {survivor_only:.2%}")
print(f"Include failed name and terminal loss: {with_failure:.2%}")
print(f"Include acquired name and terminal gain: {with_acquisition:.2%}")
Current survivors only: 3.55%
Include failed name and terminal loss: -24.48%
Include acquired name and terminal gain: 12.89%

The survivor-only result lies between the two complete three-name scenarios. Excluding the failure overstates the corresponding portfolio, while excluding the acquisition understates it. The numbers are intentionally stark and do not estimate real-market bias. The example also assumes costless equal-weight rebalancing and supplied terminal returns. Real research needs actual membership, event, price, liquidity, and cost records.

Build a point-in-time universe

At each decision time (t), define the eligible set (U_t) from facts available by that time. Portfolio weights should be zero outside (U_t), but an existing holding cannot vanish before the strategy could act or the security's terminal event is booked.

A reproducible equity security master should preserve:

  • a stable security identifier that does not depend on the ticker,
  • ticker, name, exchange, share-class, and identifier validity intervals,
  • listing, last-trade, delisting, merger, and liquidation dates,
  • event reason and the terminal cash, security, or recovery treatment,
  • split, dividend, distribution, and other corporate-action records, and
  • historical universe eligibility and constituent changes.

Keep at least three clocks where relevant: when a change was announced, when the strategy could know it, and when it became effective. A strategy that trades an index change announcement has a different information set from one that rebalances on the index effective date.

For rule-built universes, calculate market capitalization, liquidity, price, fundamentals, classifications, and borrow eligibility from their historical values. Today's large-cap list does not reconstruct yesterday's large-cap opportunity set. Lag data by its actual availability, not merely its reporting period.

Correct return and portfolio handling

Universe history alone is insufficient. The simulator must:

  1. admit a security only after its listing and eligibility conditions are met,
  2. preserve existing holdings through halts and sparse observations according to explicit valuation rules,
  3. book corporate actions and terminal proceeds on the correct dates,
  4. rebalance only when the strategy's schedule and information permit,
  5. apply spread, commissions, market impact, borrow costs, and forced-exit assumptions, and
  6. retain cash when an intended replacement cannot actually be traded.

An equal-weight backtest that recomputes mean(skipna=True) every day silently redistributes weight away from any missing asset. That may be acceptable only when the missing state truly means the position has exited and the proceeds are available for rebalancing. Otherwise it creates an undocumented trade.

Long-short research needs the same care. Failed firms may benefit a short book, but borrow can be recalled, trading can halt, and recovery after delisting may be difficult to realize. Including a terminal return without implementable borrowing and exit assumptions is not enough.

How to audit a dataset

Start with several known events rather than trusting a blanket vendor label.

  • Query historical constituents before and after documented additions and deletions.
  • Trace a bankruptcy, acquisition, ticker change, spin-off, and share-class change by stable identifier.
  • Reconcile quoted returns with terminal proceeds and corporate-action records.
  • Count listings and exits by year and reason. Investigate implausible cliffs or zero exits.
  • Distinguish pre-listing, valid, halted, missing, and post-exit states.
  • Compare an as-of universe with a current-survivor universe as a sensitivity test.
  • Archive raw extracts, query dates, vendor versions, mappings, and imputation rules.

Do not interpret the sensitivity gap as a universal correction factor. A small difference can mean the strategy is insensitive to membership, or it can mean both datasets omit the same events.

What backtesting libraries can and cannot do

Zipline Reloaded has asset lifetimes, stable asset identifiers, symbol mappings, adjustment tables, session calendars, and Pipeline screens. Its bundle interface can ingest point-in-time histories. Those structures can represent a correct universe, but they cannot reconstruct missing constituents or terminal returns from a current-symbol file.

The VectorBT family can simulate supplied price and signal matrices, including dynamic eligibility masks. VectorBT PRO makes broader parameter-grid and data-pipeline sensitivity tests easier, but both editions see only the columns, values, masks, and metadata provided. Fast compiled execution does not make a survivor-only input point in time.

The same boundary applies to every engine. Verify the dataset first, then verify how the engine treats asset birth, missing bars, corporate actions, terminal value, cash, and rebalancing. A backtest is survivorship-aware only when the complete data-to-portfolio path preserves the securities and outcomes that were part of the historical opportunity set.

Choose which optional services may run. You can change these settings at any time.