Strasmore Research
Deep Dives Matt ConnorBy Matt Connor · data as of September 20, 2026 · refreshed weekly

Investing at All-Time Highs: What the Data Says

Should you invest at an all-time high? Every SPY record close on file, with the forward returns that followed and the deepest drawdown that began from one.

Investing at all-time highs feels like buying at the top, and the stored SPY history says the feeling and the record disagree. An all-time high is a close at or above every earlier close in a series, and in a rising market it is an ordinary event. The panels below count the record closes in each year, compare the one-, three- and five-year changes that followed record closes with the changes that followed every other session, measure how long the next record took to arrive, and list the deepest falls that began from one. Every figure is computed from stored SPY closes with the query beneath it, and the sample period is stated rather than borrowed from a 1926 average.

How often does the S&P 500 hit an all-time high?

The series is SPY, the S&P 500 ETF, one close per session, price only. Dividends are excluded, which understates every forward change below by roughly the fund's yield per year, for record days and non-record days alike, so the comparison between the two groups holds even though the levels run low. The file starts on Sep 10, 2003. Earlier cycles are not in it, and no figure from a longer series is imported to fill the gap. Most explainers, including brokerage help pages, answer this question with an average of large-cap stock returns from 1926 onward and a suggestion to invest gradually rather than wait. Those averages are unconditional: they say nothing about the specific sessions that were record closes, which is the question being asked.

One definitional point matters. In the first years of any series, a close that beats every earlier close is a record of the file rather than of the market. The first four years of the file are used only to seed the running high, and every count on this page begins on Sep 10, 2007.

QueryRecord closes per year, SPY (price basis, first four years of the file excluded)
yearcloses_countedrecord_closesrecord_share_pctseries_start_labelcounting_from_labellast_session_label
20077922.5Sep 10, 2003Sep 10, 2007Dec 31, 2007
200825300Sep 10, 2003Sep 10, 2007Dec 31, 2008
200925200Sep 10, 2003Sep 10, 2007Dec 31, 2009
201025200Sep 10, 2003Sep 10, 2007Dec 31, 2010
201125200Sep 10, 2003Sep 10, 2007Dec 30, 2011
201225000Sep 10, 2003Sep 10, 2007Dec 31, 2012
20132524819Sep 10, 2003Sep 10, 2007Dec 31, 2013
20142525220.6Sep 10, 2003Sep 10, 2007Dec 31, 2014
2015252114.4Sep 10, 2003Sep 10, 2007Dec 31, 2015
2016252187.1Sep 10, 2003Sep 10, 2007Dec 30, 2016
20172515421.5Sep 10, 2003Sep 10, 2007Dec 29, 2017
2018251197.6Sep 10, 2003Sep 10, 2007Dec 31, 2018
20192523413.5Sep 10, 2003Sep 10, 2007Dec 31, 2019
20202533313Sep 10, 2003Sep 10, 2007Dec 31, 2020
20212527128.2Sep 10, 2003Sep 10, 2007Dec 31, 2021
202225110.4Sep 10, 2003Sep 10, 2007Dec 30, 2022
202325000Sep 10, 2003Sep 10, 2007Dec 29, 2023
20242525823Sep 10, 2003Sep 10, 2007Dec 31, 2024
20252504016Sep 10, 2003Sep 10, 2007Dec 31, 2025
20261792614.5Sep 10, 2003Sep 10, 2007Sep 18, 2026
The exact SQL behind every number
WITH
    daily AS
    (
        SELECT
            date,
            toFloat64(argMax(close, _ingest_time)) AS close
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'SPY'
        GROUP BY date
    ),
    flagged AS
    (
        SELECT
            date,
            close,
            close >= max(close) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS is_record,
            min(date) OVER ()                                                                          AS series_start
        FROM daily
    )
SELECT
    toYear(date)                                                 AS year,
    count()                                                      AS closes_counted,
    countIf(is_record = 1)                                       AS record_closes,
    round(100 * countIf(is_record = 1) / count(), 1)             AS record_share_pct,
    any(formatDateTime(series_start, '%b %e, %Y'))               AS series_start_label,
    any(formatDateTime(addYears(series_start, 4), '%b %e, %Y'))  AS counting_from_label,
    formatDateTime(max(date), '%b %e, %Y')                       AS last_session_label
FROM flagged
WHERE date >= addYears(series_start, 4)
GROUP BY year
ORDER BY year
Run this yourself

Record closes arrive in clusters. Years with none are bear markets and the long climbs back to the prior peak, while the busiest years print a record every few sessions. The counted window opens in 2007. The current year, 2026, has recorded 26 record closes across 179 sessions through Sep 18, 2026, 14.5% of them.

What happens after the market hits an all-time high?

The instinct says a record close is the top. The check is to take every session in the counted window, split the set into record closes and all other closes, and look forward the same distance from each. The panel does that at 252, 756 and 1,260 sessions ahead (one, three and five trading years), and a session counts only when its full horizon has already played out, which shrinks the five-year rows to the earlier part of the window.

QueryForward price change after record closes vs all other sessions, SPY
horizonrecord_closesother_closesrecord_median_pctother_median_pctrecord_positive_pctother_positive_pct
1 year427410812.613.778.282.6
3 years343368826.838.599.493.6
5 years326320166.37599.497.6
The exact SQL behind every number
WITH
    daily AS
    (
        SELECT
            date,
            toFloat64(argMax(close, _ingest_time)) AS close
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'SPY'
        GROUP BY date
    ),
    flagged AS
    (
        SELECT
            date,
            close,
            close >= max(close) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)     AS is_record,
            min(date) OVER ()                                                                              AS series_start,
            leadInFrame(close, 252)  OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS close_1y,
            leadInFrame(close, 756)  OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS close_3y,
            leadInFrame(close, 1260) OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS close_5y
        FROM daily
    ),
    unpivoted AS
    (
        SELECT
            date,
            close,
            is_record,
            arrayJoin([('1 year', close_1y), ('3 years', close_3y), ('5 years', close_5y)]) AS pair,
            tupleElement(pair, 1) AS horizon,
            tupleElement(pair, 2) AS close_fwd
        FROM flagged
        WHERE date >= addYears(series_start, 4)
    )
SELECT
    horizon,
    countIf(is_record = 1)                                                                                    AS record_closes,
    countIf(is_record = 0)                                                                                    AS other_closes,
    round(quantileDeterministicIf(0.5)(100 * (close_fwd / close - 1), toYYYYMMDD(date), is_record = 1), 1)    AS record_median_pct,
    round(quantileDeterministicIf(0.5)(100 * (close_fwd / close - 1), toYYYYMMDD(date), is_record = 0), 1)    AS other_median_pct,
    round(100 * countIf(is_record = 1 AND close_fwd > close) / countIf(is_record = 1), 1)                      AS record_positive_pct,
    round(100 * countIf(is_record = 0 AND close_fwd > close) / countIf(is_record = 0), 1)                      AS other_positive_pct
FROM unpivoted
WHERE close_fwd > 0
GROUP BY horizon
HAVING countIf(is_record = 1) > 0 AND countIf(is_record = 0) > 0
ORDER BY horizon
Run this yourself

Read the median columns first. At one year the median price change after a record close was 12.6%, against 13.7% after every other session; at five years, 66.3% against 75%. The positive-share columns answer the question most people mean, "was it higher later": 78.2% of record closes were followed by a higher close one trading year on, and 99.4% five trading years on, from samples of 427 and 326 record sessions. The comparison group, 4108 non-record sessions at the one-year horizon, includes every bear-market low in the window, sessions nobody could identify as lows at the time. Keep that in mind whichever column reads higher. "All other days" is a category no investor can buy on purpose, while "today is a record close" is known by 4 p.m.

How long until the next all-time high?

A record close followed by another within weeks is a market grinding higher; one that stands for years marks the top of a cycle. For every counted record close set at least a year before the end of the file, the panel measures the calendar days until the next record close and sorts the results into buckets. The one-year cutoff means each row has had a full twelve months to resolve.

QueryCalendar days from a record close to the next one, SPY
bucketrecord_closesshare_pctcumulative_share_pct
within 1 month40394.294.2
1 to 3 months163.797.9
3 to 6 months40.998.8
6 to 12 months20.599.3
more than 12 months30.7100
The exact SQL behind every number
WITH
    daily AS
    (
        SELECT
            date,
            toFloat64(argMax(close, _ingest_time)) AS close
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'SPY'
        GROUP BY date
    ),
    flagged AS
    (
        SELECT
            date,
            close >= max(close) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS is_record,
            min(date) OVER ()                                                                          AS series_start,
            max(date) OVER ()                                                                          AS series_end
        FROM daily
    ),
    highs AS
    (
        SELECT
            date,
            series_end,
            leadInFrame(date, 1) OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS next_high
        FROM flagged
        WHERE is_record = 1
          AND date >= addYears(series_start, 4)
    ),
    gaps AS
    (
        SELECT
            if(next_high > date, dateDiff('day', date, next_high), 99999) AS gap_days
        FROM highs
        WHERE date <= subtractDays(series_end, 365)
    ),
    bucketed AS
    (
        SELECT
            multiIf(gap_days <= 31,  'within 1 month',
                    gap_days <= 92,  '1 to 3 months',
                    gap_days <= 183, '3 to 6 months',
                    gap_days <= 365, '6 to 12 months',
                                     'more than 12 months') AS bucket,
            count()                                          AS record_closes,
            min(gap_days)                                    AS sort_key
        FROM gaps
        GROUP BY bucket
    )
SELECT
    bucket,
    record_closes,
    round(100 * record_closes / sum(record_closes) OVER (), 1)                                        AS share_pct,
    round(100 * sum(record_closes) OVER (ORDER BY sort_key ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
              / sum(record_closes) OVER (), 1)                                                        AS cumulative_share_pct
FROM bucketed
ORDER BY sort_key
Run this yourself

The first bucket dominates: 94.2% of record closes were followed by another within a month, which is what a cluster looks like from the inside. Adding the buckets through 6 to 12 months, 99.3% of record closes saw a new record inside twelve months, leaving 0.7% that waited longer. Those few are the final record of each run, the close set just before a cycle turned; by construction every other record in a cluster is followed quickly by the next one. A 52-week high is the weaker cousin of this measure: the best close of the trailing year, which an index or a stock can set while still well below its all-time peak.

What is the worst drawdown that started from an all-time high?

Every drawdown begins from a record close by definition: the running high is the reference, and the drawdown is how far below it the index closed before the next record. The panel groups the counted history into runs, one per record close, and ranks the runs by their deepest closing decline.

QueryDeepest closing declines from a record close, SPY
peak_labeltrough_labeldecline_pctdays_to_new_highnew_high_label
Oct 9, 2007Mar 9, 200956.51983Mar 14, 2013
Feb 19, 2020Mar 23, 202034.1181Aug 18, 2020
Jan 3, 2022Oct 12, 202225.4746Jan 19, 2024
Sep 20, 2018Dec 24, 201820.2221Apr 29, 2019
Feb 19, 2025Apr 8, 202519128Jun 27, 2025
May 21, 2015Feb 11, 201614.4418Jul 12, 2016
Jan 26, 2018Apr 2, 201810.2210Aug 24, 2018
The exact SQL behind every number
WITH
    daily AS
    (
        SELECT
            date,
            toFloat64(argMax(close, _ingest_time)) AS close
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'SPY'
        GROUP BY date
    ),
    flagged AS
    (
        SELECT
            date,
            close,
            close >= max(close) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS is_record,
            min(date) OVER ()                                                                          AS series_start
        FROM daily
    ),
    runs AS
    (
        SELECT
            date,
            close,
            series_start,
            max(if(is_record = 1, date, toDate('1970-01-01'))) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS peak_date
        FROM flagged
    ),
    by_peak AS
    (
        SELECT
            peak_date,
            argMin(close, date) AS peak_close,
            min(close)          AS trough_close,
            argMin(date, close) AS trough_date
        FROM runs
        WHERE peak_date >= addYears(series_start, 4)
        GROUP BY peak_date
    ),
    with_next AS
    (
        SELECT
            peak_date,
            peak_close,
            trough_close,
            trough_date,
            leadInFrame(peak_date, 1) OVER (ORDER BY peak_date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS next_peak
        FROM by_peak
    )
SELECT
    formatDateTime(peak_date, '%b %e, %Y')                                        AS peak_label,
    formatDateTime(trough_date, '%b %e, %Y')                                      AS trough_label,
    round(100 * (1 - trough_close / peak_close), 1)                               AS decline_pct,
    if(next_peak > peak_date, dateDiff('day', peak_date, next_peak), 0)           AS days_to_new_high,
    if(next_peak > peak_date, formatDateTime(next_peak, '%b %e, %Y'), 'not yet')  AS new_high_label
FROM with_next
WHERE decline_pct >= 10
ORDER BY decline_pct DESC
LIMIT 10
Run this yourself

The deepest fall in the file began from the Oct 9, 2007 record and bottomed on Mar 9, 2009, 56.5% below the high on a closing basis. The next record close arrived on Mar 14, 2013, 1983 days after the peak. The second-deepest, from Feb 19, 2020, fell 34.1% and took 181 days to reach a new record. Those rows are the honest answer to "what is the worst case", and they are already inside the forward-return medians above: a record close is a day with a distribution of outcomes, and this panel shows the left tail with dates attached. The path back from each of these lows is measured in how markets recover from crashes.

Investing at all-time highs with an index fund: does timing matter?

For an index mutual fund, the time of day does not. The fund prices once a day at its 4 p.m. ET net asset value (NAV), and an order placed at 9:31 a.m. and one placed at 3:59 p.m. fill at the same closing NAV; the mechanics are in when mutual funds trade. An index ETF such as SPY trades all session at a live price, so an ETF order does carry a time, though nothing above turns on it. Every figure on this page is a close-to-close comparison, and the day-level question, record close or not, is the one the data can answer. What it says is that record closes were common and usually followed by more of them, and that on a handful of dated occasions one marked a top that took years to reclaim. The approach most explainers recommend, a fixed purchase schedule that ignores the level, is a way of not making the call at all; dollar-cost averaging works through what that does to an average purchase price, and missing the best days measures what sitting out the biggest sessions did to the same series.

FAQ

Is it a bad time to invest when the stock market is at an all-time high?

The stored SPY history does not treat a record close as a bad day in itself: 78.2% of counted record closes were followed by a higher close one trading year later, with a median price change of 12.6%. The exceptions cluster at the end of each cycle, and the deepest, from the Oct 9, 2007 high, took 1983 days to reach a new record. Whether that range of outcomes fits a particular plan is a personal question the data cannot settle.

How often does the S&P 500 hit a new all-time high?

It ranges from none in a bear-market year to dozens in a strong one, since records arrive in clusters. In 2026, SPY has closed at a record 26 times through Sep 18, 2026, 14.5% of sessions so far.

What is the difference between an all-time high and a 52-week high?

An all-time high is a close above every prior close in a security's history. A 52-week high is the highest close of the trailing year only, so a stock can set one while sitting far below its record. Every all-time high is also a 52-week high; the reverse is not true.

Does the time of day I buy an index fund matter?

Not for a mutual fund, which prices once a day at its 4 p.m. ET closing NAV and fills every order received during the session at that price. An ETF trades all day, so its fill depends on the time; the record-close question on this page is a daily one either way.


Every panel carries its SQL; expand one to see exactly how the record closes were counted and the runs measured. To run the same split on another ticker or over a different horizon, ask the question on the Strasmore terminal.