How Long Does It Take the Stock Market to Recover From a Crash? Median 2.1 Years (Python)

How long does it take the stock market to recover from a crash? For the S&P 500, the typical answer is about two years: across all 12 declines of 20% or more since 1928, the median time from the old record high to a new record high was 2.1 years. The spread around that median is huge, though. The fastest recovery (2020) took 0.5 years, and the slowest (1929) took 25.0 years on the price index alone.

I measured every crash from daily closing prices with a short pandas script, which is included below so you can rerun it on any index or fund. The main pattern: how deep a crash goes tells you more about the wait than anything else. The four crashes that took five or more years to recover all fell 48% or more. Every crash shallower than 40% was back at a record within 3.3 years.

(If you end up with a sharp investing framework of your own, an AI book-writing tool like ImagineYourBook can turn your notes into a draft finance ebook or lead magnet with Word and EPUB export. More on that at the end.)

TL;DR

  • Across all 12 S&P 500 crashes of 20%+ since 1928, the median time from record high to a new record was 2.1 years (2.0 years since 1946).
  • From the bottom, the median climb back to the old high took 1.5 years.
  • Depth decides the wait: every crash shallower than 40% recovered within 3.3 years; the four that fell 48%+ took 5.5 to 25 years.
  • Corrections of 10–20% (14 of them) took a median 7 months to reach a new high.
  • Dividends cut the infamous 1929 wait from 25.0 to 15.3 years (7.2 after inflation). Pandas script included.

How I measured “recovery”

“Recovered” can mean three different things, and articles on this question often mix them up. Here is the definition I used, kept strict:

  • Crash: the S&P 500 closes at least 20% below its all-time closing high.
  • Peak: the last record close before the drop.
  • Trough: the lowest close before the index makes a new record.
  • Recovered: the first close at or above the old record.

The data is the S&P 500 price index (^GSPC) from Yahoo Finance, daily closes from December 30, 1927 to October 29, 2026, loaded with yfinance. The finding itself is a cummax() one-liner: divide the price by its running maximum, and every value below 1.0 is time spent “under water.”

One consequence of measuring from all-time highs: a slump that happens while the market is still below an older record counts as part of the bigger episode, not as a separate crash. The 1937–38 slide (βˆ’54.5% from its March 1937 high) sits inside the 1929 recovery, and 2011’s βˆ’19.4% dip came while the index was still below its 2007 record. If you count every bear market from its own local peak, you get more episodes and shorter typical waits.

All 12 S&P 500 crashes since 1928

Record highTroughNew recordDepthPeak β†’ trough (yrs)Trough β†’ old high (yrs)Peak β†’ new high (yrs)
Sep 16, 1929Jun 01, 1932Sep 22, 1954βˆ’86.2%2.722.325.0
Aug 03, 1956Oct 22, 1957Sep 24, 1958βˆ’21.5%1.20.92.1
Dec 12, 1961Jun 26, 1962Sep 03, 1963βˆ’28.0%0.51.21.7
Feb 09, 1966Oct 07, 1966May 04, 1967βˆ’22.2%0.70.61.2
Nov 29, 1968May 26, 1970Mar 06, 1972βˆ’36.1%1.51.83.3
Jan 11, 1973Oct 03, 1974Jul 17, 1980βˆ’48.2%1.75.87.5
Nov 28, 1980Aug 12, 1982Nov 03, 1982βˆ’27.1%1.70.21.9
Aug 25, 1987Dec 04, 1987Jul 26, 1989βˆ’33.5%0.31.61.9
Mar 24, 2000Oct 09, 2002May 30, 2007βˆ’49.1%2.54.67.2
Oct 09, 2007Mar 09, 2009Mar 28, 2013βˆ’56.8%1.44.15.5
Feb 19, 2020Mar 23, 2020Aug 18, 2020βˆ’33.9%0.10.40.5
Jan 03, 2022Oct 12, 2022Jan 19, 2024βˆ’25.4%0.81.32.0
S&P 500 price index (^GSPC), daily closes Dec 1927 to Oct 29, 2026, Yahoo Finance. Declines of 20%+ from an all-time closing high. recovery.py

The median crash fell 33.7%. Measured from the old peak, the median wait for a new record was 2.1 years. Measured from the bottom, the median climb back took just 1.5 years. Both views matter. The first is what someone who bought at the top went through. The second is what a buyer at the bottom saw, or anyone who kept investing on the way down.

Excluding the Great Depression barely moves the number: for the 11 crashes that started in 1946 or later, the median peak-to-new-high time was 2.0 years, and the median trough-to-old-high time was 1.3 years.

Scatter chart of S&P 500 crash depth vs years to a new record high on a log scale: crashes of 21% to 36% recovered in 0.5 to 3.3 years, while 1973 (-48%), 2000 (-49%), 2007 (-57%) and 1929 (-86%) took 7.5, 7.2, 5.5 and 25.0 years
Every crash right of the 45% line took 5.5+ years; every one shallower than 40% was back within 3.3 years (recovery.py).

Why depth matters more than anything else

A loss and the gain needed to undo it are not symmetric. That is plain arithmetic, not a market forecast:

Drop from the highGain needed to get backExample
βˆ’10%+11%a typical correction
βˆ’20%+25%bear-market threshold
βˆ’34%+52%2020 (βˆ’33.9%)
βˆ’50%+100%2000 (βˆ’49.1%)
βˆ’57%+133%2007–09 (βˆ’56.8%)
βˆ’86%+614%1929–32 (βˆ’86.2%)
Required gain = 1 / (1 βˆ’ loss) βˆ’ 1. Printed by recovery.py.

A 20% drop needs a 25% gain to get back to even. A 57% drop, like 2007–09, needs +133%. The 1929 crash, at βˆ’86.2% on daily closes, needed +614%. Because the required gain grows faster than the loss, recovery time grows faster than depth, which is why the scatter chart above uses a log scale. Below about βˆ’40%, the S&P 500’s history has been a matter of months to a few years. Past roughly βˆ’45%, every recovery took at least 5.5 years.

The speed of the fall matters too. In 2020 the index took 33 calendar days to fall 33.9% (from 3,386.15 on February 19 to 2,237.40 on March 23) and had a new record close of 3,389.78 by August 18. The 2000 crash was shallower than 2007’s at the bottom (βˆ’49.1% vs βˆ’56.8%) yet took longer overall (7.2 vs 5.5 years), because the decline itself dragged on for 2.5 years.

The animation lines up six post-war crashes on the same clock, with each record high set to 100:

Animated line chart of six S&P 500 crashes rebased to their record high = 100, years after the peak on the x-axis: 2020 recovers in 0.5 years, 1987 in 1.9, 2022 in 2.0, 2007 in 5.5, 2000 in 7.2 and 1973 in 7.5 years
Six post-war crashes on one clock: three were back within 2 years, three needed 5.5 to 7.5 years (weekly closes, recovery.py).

Three of the six had recovered before the three-year mark. The other three needed 5.5 to 7.5 years.

Smaller drops: corrections of 10% to 20%

Most of the scary headlines are about corrections, not crashes. Using the same rules, the S&P 500 had 14 declines of 10% to just under 20% from a record close that never turned into a full crash. The median time from peak to new high was 7 months, and the longest took 1.5 years. That list includes 2025: the index fell 18.9% from its February 19 record to its April 8 low and set a new record on June 27, 2025.

The famous “25 years” is a price-only number

The price index ignores dividends, and dividends do a lot of the work in a recovery. For crashes since 1988 I reran the same test on the S&P 500 Total Return index (^SP500TR, Yahoo Finance). For 1929 I used Robert Shiller’s monthly S&P data, which includes dividends and the CPI (monthly averages, so its dates are a little coarser):

Horizontal bar chart of years from peak to break-even, price index vs dividends reinvested: 2000 crash 7.2 vs 6.1 years, 2007 crash 5.5 vs 4.5 years, 1929 crash 25.0 years on price, 15.3 years with dividends, 7.2 years with dividends after inflation
With dividends reinvested, the 1929 wait drops from 25.0 to 15.3 years, and to 7.2 years after inflation (Shiller monthly data).
  • 2000 crash: 7.2 years on price, 6.1 years with dividends reinvested.
  • 2007 crash: 5.5 years on price, 4.5 years with dividends.
  • 2020 and 2022: dividends hardly matter over 0.5 to 2 years (0.5 and 1.9 years with dividends).
  • 1929 crash: 25.0 years on price (September 1929 to September 1954), 15.3 years with dividends reinvested (January 1945). Adjusted for the 1930s deflation as well, a dividend-reinvesting investor was whole in real terms by November 1936, 7.2 years after the peak.

That doesn’t make 1929 painless. Even with dividends, the real loss at the bottom was about 77%. But “it took 25 years to recover” describes a price chart, not the experience of someone who held the index and reinvested its income.

Rule: when someone quotes a recovery time, ask two questions: measured from the peak or from the bottom, and price or total return? The same 1929 crash is a 25.0-year, 15.3-year or 7.2-year recovery depending on the answer.

The Python script

This script finds every crash, prints the table above, and compares price-only recovery with total-return recovery since 1988. Change the ticker argument to "^IXIC", "QQQ", or any fund you own to run the same test on it. The 1929 dividend check lives in a separate helper (shiller_1929.py) because it reads Shiller’s spreadsheet instead of Yahoo data.

"""How long does it take the stock market to recover from a crash?
Every S&P 500 decline of 20%+ since 1928, measured peak -> trough -> back to the old peak.
Data: Yahoo Finance daily closes (^GSPC price index, ^SP500TR total return) via yfinance.
pip install yfinance pandas"""
import pandas as pd

END = "2026-10-29"   # last full trading day when this was written

def load(ticker="^GSPC"):
    try:
        s = pd.read_pickle(f"src/{ticker.strip('^').lower()}.pkl")["Close"][ticker]
    except FileNotFoundError:
        import yfinance as yf
        s = yf.download(ticker, start="1927-12-01", auto_adjust=False, progress=False)["Close"][ticker]
    return s.loc[:END].dropna()

def crashes(s, threshold=0.20):
    """One row per decline of at least `threshold` from a record close, until a new record."""
    peak = s.cummax(); dd = s / peak - 1
    rows, start = [], None
    for date, d in dd.items():
        if d < 0 and start is None:
            start = date                                  # first close below the old record
        elif d == 0 and start is not None:
            seg = dd.loc[start:date]
            if seg.min() <= -threshold:
                p = s.loc[:start].index[-2]               # the record close itself
                t = seg.idxmin()
                rows.append(dict(peak=p, trough=t, recovered=date, depth=seg.min()))
            start = None
    if start is not None and dd.loc[start:].min() <= -threshold:   # still under water
        seg = dd.loc[start:]
        rows.append(dict(peak=s.loc[:start].index[-2], trough=seg.idxmin(), recovered=pd.NaT, depth=seg.min()))
    df = pd.DataFrame(rows)
    yrs = lambda a, b: (b - a).dt.days / 365.25
    df["down_yrs"] = yrs(df.peak, df.trough)
    df["up_yrs"] = yrs(df.trough, df.recovered)            # trough back to the old peak
    df["total_yrs"] = yrs(df.peak, df.recovered)           # peak to new record
    return df

def recover_date(s, peak, trough):
    """First close at or above the peak level after the trough (None if not yet)."""
    after = s.loc[trough:]
    hit = after[after >= s.loc[peak]]
    return hit.index[0] if len(hit) else pd.NaT

if __name__ == "__main__":
    s = load()
    C = crashes(s)
    print(f"S&P 500 price index, {s.index[0]:%b %d %Y} to {s.index[-1]:%b %d %Y}: {len(C)} declines of 20%+")
    print(f"{'peak':>11} {'trough':>11} {'recovered':>11} {'depth':>7} {'down':>6} {'up':>6} {'total':>6}  (years)")
    for _, r in C.iterrows():
        rec = f"{r.recovered:%Y-%m-%d}" if pd.notna(r.recovered) else "not yet"
        print(f"{r.peak:%Y-%m-%d} {r.trough:%Y-%m-%d} {rec:>11} {r.depth:>7.1%} {r.down_yrs:>6.1f} {r.up_yrs:>6.1f} {r.total_yrs:>6.1f}")
    done = C.dropna(subset=["recovered"])
    post = done[done.peak >= "1946-01-01"]
    print(f"\nMedian peak-to-new-high: all {done.total_yrs.median():.1f} yrs, since 1946 {post.total_yrs.median():.1f} yrs")
    print(f"Median trough-to-old-high: all {done.up_yrs.median():.1f} yrs, since 1946 {post.up_yrs.median():.1f} yrs")
    print(f"Median depth: {done.depth.median():.1%};  current close {s.iloc[-1]:,.2f} ({s.iloc[-1]/s.max()-1:.1%} from record)")
    c = crashes(s, 0.10); c = c[(c.depth > -0.20)].dropna(subset=["recovered"])
    print(f"Corrections of 10-20% from a record: {len(c)}, median {c.total_yrs.median()*12:.0f} months peak to new high, "
          f"longest {c.total_yrs.max():.1f} yrs")
    for d in (0.10, 0.20, 0.34, 0.50, 0.57, 0.86):
        print(f"  a -{d:.0%} fall needs +{1/(1-d)-1:.0%} to get back")
    tr = load("^SP500TR")                                  # dividends reinvested, starts 1988
    print("\nSince 1988, price-only vs dividends reinvested (years from peak to a new high):")
    for _, r in done[done.peak >= "1988-01-04"].iterrows():
        p2 = tr.loc[r.peak - pd.Timedelta(days=10):r.trough].idxmax()
        t2 = tr.loc[p2:r.recovered].idxmin()
        rec2 = recover_date(tr, p2, t2)
        print(f"  {r.peak:%Y} crash: price {r.total_yrs:.1f} yrs, total return {(rec2 - p2).days/365.25:.1f} yrs "
              f"(depth {tr.loc[t2]/tr.loc[p2]-1:.1%})")

Output (data through October 29, 2026):

S&P 500 price index, Dec 30 1927 to Oct 29 2026: 12 declines of 20%+
       peak      trough   recovered   depth   down     up  total  (years)
1929-09-16 1932-06-01  1954-09-22  -86.2%    2.7   22.3   25.0
1956-08-03 1957-10-22  1958-09-24  -21.5%    1.2    0.9    2.1
1961-12-12 1962-06-26  1963-09-03  -28.0%    0.5    1.2    1.7
1966-02-09 1966-10-07  1967-05-04  -22.2%    0.7    0.6    1.2
1968-11-29 1970-05-26  1972-03-06  -36.1%    1.5    1.8    3.3
1973-01-11 1974-10-03  1980-07-17  -48.2%    1.7    5.8    7.5
1980-11-28 1982-08-12  1982-11-03  -27.1%    1.7    0.2    1.9
1987-08-25 1987-12-04  1989-07-26  -33.5%    0.3    1.6    1.9
2000-03-24 2002-10-09  2007-05-30  -49.1%    2.5    4.6    7.2
2007-10-09 2009-03-09  2013-03-28  -56.8%    1.4    4.1    5.5
2020-02-19 2020-03-23  2020-08-18  -33.9%    0.1    0.4    0.5
2022-01-03 2022-10-12  2024-01-19  -25.4%    0.8    1.3    2.0

Median peak-to-new-high: all 2.1 yrs, since 1946 2.0 yrs
Median trough-to-old-high: all 1.5 yrs, since 1946 1.3 yrs
Median depth: -33.7%;  current close 7,720.39 (-1.3% from record)
Corrections of 10-20% from a record: 14, median 7 months peak to new high, longest 1.5 yrs
  a -10% fall needs +11% to get back
  a -20% fall needs +25% to get back
  a -34% fall needs +52% to get back
  a -50% fall needs +100% to get back
  a -57% fall needs +133% to get back
  a -86% fall needs +614% to get back

Since 1988, price-only vs dividends reinvested (years from peak to a new high):
  2000 crash: price 7.2 yrs, total return 6.1 yrs (depth -47.4%)
  2007 crash: price 5.5 yrs, total return 4.5 yrs (depth -55.3%)
  2020 crash: price 0.5 yrs, total return 0.5 yrs (depth -33.8%)
  2022 crash: price 2.0 yrs, total return 1.9 yrs (depth -24.5%)

What this means for your own portfolio

SituationWhat 1928–2026 history saysWhat to do
A 10–20% correction14 cases, median 7 months to a new high, longest 1.5 yearsUsually nothing; rebalance if your bands are hit
A 20–40% bear market8 cases, 0.5 to 3.3 years to a new highKeep contributing; don’t sell to cash
A 45%+ collapse4 cases, 5.5 to 25 years on priceHold money needed within 5 years outside stocks
You reinvest dividends2000: 7.2 β†’ 6.1 yrs; 1929: 25.0 β†’ 15.3 yrsMeasure your recovery on total return, not price
You hold one stock or sectorNasdaq Composite: about 15 years after 2000Index history doesn’t transfer to concentrated bets
Sources: recovery.py, shiller_1929.py; Yahoo Finance; Robert Shiller data. History, not a forecast.

Three practical takeaways from the data:

  1. Plan for the median, but survive the tail. Two years is the typical wait. Waits of 5.5 to 7.5 years have happened three times since 1973. Money you need within five years arguably shouldn’t depend on a quick rebound.
  2. Keep contributing. The trough-to-old-high leg (median 1.5 years) is shorter than the full episode because shares bought near the bottom recover first. Our lump sum vs DCA test looks at what happens to a cash pile that waits on the sidelines instead.
  3. Rebalancing helps in practice. Selling bonds to buy stocks during a crash shortens your personal recovery. The how often to rebalance backtest shows annual rebalancing was enough. For judging how bumpy a fund’s ride is beyond its drawdown, see Sharpe ratio vs Sortino ratio.

For context on today: the S&P 500 closed at 7,720.39 on October 29, 2026, 1.3% below its record close of 7,818.93 on October 6, 2026. That’s nowhere near crash territory. This is history, not a forecast, and not investment advice.

Limitations

  • Only 12 crashes. The median moves if you add or drop one episode, and four of the 12 come from very different eras (pre-war, 1970s inflation, dot-com, 2008).
  • Index, not your portfolio. A single stock, a sector fund, or a leveraged ETF can take far longer. The Nasdaq Composite took about 15 years to regain its March 2000 closing peak (April 2015, per Yahoo Finance ^IXIC), and plenty of individual stocks never did.
  • Nominal for most rows. Apart from the 1929 check, the numbers ignore inflation, and inflation can flip the story. In Shiller’s monthly data the 1973 crash took 7.5 years on price, only 3.5 years with dividends reinvested (July 1976), but 12.0 years with dividends after inflation (January 1985).
  • Closing prices. Intraday lows were slightly deeper, and the 20% line is a convention, not a law (1990 and 2018 both stopped just short at βˆ’19.9% and βˆ’19.8%).

Turn your crash notes into something people read

If you’ve been building this kind of analysis for yourself (drawdown rules, a rebalancing checklist, a crash playbook for a nervous spouse), it is close to being a short finance guide or a lead magnet for a newsletter. ImagineYourBook turns a prompt or your notes into a complete text draft with a cover, exported as Word and EPUB. If you already have a long write-up, its Manuscript Rewrite can reshape it into a shorter edition. The trial is $0 today but requires a card. It includes one 10,000-word Small Book, and the Author plan ($29.99/month) starts after 7 days unless you cancel before day 8. Check every number in the draft against your own script before you publish it.

Sources (checked October 30, 2026): Yahoo Finance: S&P 500 (^GSPC) history Β· Yahoo Finance: S&P 500 Total Return (^SP500TR) Β· Yahoo Finance: Nasdaq Composite (^IXIC) Β· Robert Shiller, U.S. stock market data (ie_data.xls) Β· yfinance on GitHub Β· ImagineYourBook pricing and trial terms. Scripts: recovery.py (shown above), shiller_1929.py. Not investment advice.

Leave a Comment