How do you actually use survivorship-bias-free data (i.e., the NaNs after delisting)?

Course Name: [Data

How do you actually use survivorship-bias-free data (i.e., the NaNs after delisting)?

Hi all,

After learning about survivorship bias, I understand why a dataset needs to include companies that were later delisted, acquired, or went bankrupt. What I’m less clear on is how to use that data in practice — specifically, the prices that become NaN after a certain date.

One naive idea is to set the post-delisting price to 0 for companies that went bankrupt or were delisted. But that feels too crude, and I’m not sure how it should be handled for acquisitions or voluntary delistings.

So my questions are:

  1. Is there a systematic way to handle these NaNs when extracting signals or backtesting?
  2. Are there any worked examples or open-source codebases that explicitly incorporate delisted companies rather than quietly dropping them?

So far I haven’t found a codebase that handles this well. Any pointers to papers, blog posts, or repos would be much appreciated. Thanks!

Good question, and you have spotted a real gap. Most tutorials stop at “use survivorship-bias-free data” without showing how to actually consume it. Here is a practical way to think about it, question by question.

Q1. Is there a systematic way to handle these NaNs when extracting signals or backtesting?

Yes. The key idea is that the NaNs are not missing data to fill. They tell you the stock has left the tradable universe. Your job is to respect that, not repair it.

For signals: on any date, rank and select only from stocks that have a price on that date. The classic mistake is forward-filling the whole dataset, which keeps dead stocks in your rankings at their last price forever. Never fill prices past a stock’s last traded date. A simple “is alive” mask (price notna on that date) applied before every ranking step does the job.

For backtests: when a stock you hold gets delisted, decide what you get back. Setting it to 0 every time is too harsh. It depends on why it left:

  • Acquired or voluntarily delisted: assume you sold at the last traded price. Money goes to cash until the next rebalance.
  • Bankruptcy or removed for poor performance: the last price is usually too optimistic. Shumway (1997) and Shumway and Warther (1999) suggest roughly a 30% haircut for NYSE/AMEX stocks and 55% for Nasdaq stocks when the true delisting return is unknown. If your data vendor provides actual delisting returns (CRSP does), use those instead.

Q2. Are there any worked examples or open-source codebases that explicitly incorporate delisted companies?

Yes, two open-source engines do this properly:

  • Zipline (zipline-reloaded): each stock has an auto_close_date. If you still hold it on that date, Zipline automatically sells it at the last known price and cancels open orders. Reading how the ledger handles this is your question answered in code.
  • QuantConnect LEAN: sends a delisting warning event on the stock’s final trading day, then force-liquidates anything you still hold. Their repo has a runnable example, DelistingEventsAlgorithm.py, and their corporate actions docs explain the events.
  • On the data side, Sharadar (Nasdaq Data Link) and Norgate Data include delisted tickers with dates and reasons, which is what makes this logic possible.

And you are right that few public repos do this end to end in plain pandas. The engines above are the best reference implementations. The fastest practical path is usually data with delisting metadata (Sharadar or Norgate) plus Zipline or LEAN, rather than hand-rolling everything yourself.

Quick sanity check after any fix: on any past date, the number of stocks in your universe should match how many actually traded back then, not how many survive today. The max-date technique from the Section 4 notebook is a good way to audit this.

1 Like