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.
| year | closes_counted | record_closes | record_share_pct | series_start_label | counting_from_label | last_session_label |
|---|---|---|---|---|---|---|
| 2007 | 79 | 2 | 2.5 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2007 |
| 2008 | 253 | 0 | 0 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2008 |
| 2009 | 252 | 0 | 0 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2009 |
| 2010 | 252 | 0 | 0 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2010 |
| 2011 | 252 | 0 | 0 | Sep 10, 2003 | Sep 10, 2007 | Dec 30, 2011 |
| 2012 | 250 | 0 | 0 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2012 |
| 2013 | 252 | 48 | 19 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2013 |
| 2014 | 252 | 52 | 20.6 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2014 |
| 2015 | 252 | 11 | 4.4 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2015 |
| 2016 | 252 | 18 | 7.1 | Sep 10, 2003 | Sep 10, 2007 | Dec 30, 2016 |
| 2017 | 251 | 54 | 21.5 | Sep 10, 2003 | Sep 10, 2007 | Dec 29, 2017 |
| 2018 | 251 | 19 | 7.6 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2018 |
| 2019 | 252 | 34 | 13.5 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2019 |
| 2020 | 253 | 33 | 13 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2020 |
| 2021 | 252 | 71 | 28.2 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2021 |
| 2022 | 251 | 1 | 0.4 | Sep 10, 2003 | Sep 10, 2007 | Dec 30, 2022 |
| 2023 | 250 | 0 | 0 | Sep 10, 2003 | Sep 10, 2007 | Dec 29, 2023 |
| 2024 | 252 | 58 | 23 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2024 |
| 2025 | 250 | 40 | 16 | Sep 10, 2003 | Sep 10, 2007 | Dec 31, 2025 |
| 2026 | 179 | 26 | 14.5 | Sep 10, 2003 | Sep 10, 2007 | Sep 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 yearRecord 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.
| horizon | record_closes | other_closes | record_median_pct | other_median_pct | record_positive_pct | other_positive_pct |
|---|---|---|---|---|---|---|
| 1 year | 427 | 4108 | 12.6 | 13.7 | 78.2 | 82.6 |
| 3 years | 343 | 3688 | 26.8 | 38.5 | 99.4 | 93.6 |
| 5 years | 326 | 3201 | 66.3 | 75 | 99.4 | 97.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 horizonRead 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.
| bucket | record_closes | share_pct | cumulative_share_pct |
|---|---|---|---|
| within 1 month | 403 | 94.2 | 94.2 |
| 1 to 3 months | 16 | 3.7 | 97.9 |
| 3 to 6 months | 4 | 0.9 | 98.8 |
| 6 to 12 months | 2 | 0.5 | 99.3 |
| more than 12 months | 3 | 0.7 | 100 |
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_keyThe 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.
| peak_label | trough_label | decline_pct | days_to_new_high | new_high_label |
|---|---|---|---|---|
| Oct 9, 2007 | Mar 9, 2009 | 56.5 | 1983 | Mar 14, 2013 |
| Feb 19, 2020 | Mar 23, 2020 | 34.1 | 181 | Aug 18, 2020 |
| Jan 3, 2022 | Oct 12, 2022 | 25.4 | 746 | Jan 19, 2024 |
| Sep 20, 2018 | Dec 24, 2018 | 20.2 | 221 | Apr 29, 2019 |
| Feb 19, 2025 | Apr 8, 2025 | 19 | 128 | Jun 27, 2025 |
| May 21, 2015 | Feb 11, 2016 | 14.4 | 418 | Jul 12, 2016 |
| Jan 26, 2018 | Apr 2, 2018 | 10.2 | 210 | Aug 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 10The 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.