next_ath
Answered against 22 years of US equities and 12 years of US options data and published with the query that produced it. This result is stored as of 2026-09-20, from investing-at-all-time-highs.
| 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 |
- Rows × columns
- 5 × 4
- Computed
- Completeness
- No missing values
- Source
- US exchange, SIP and OPRA market data
- Licence
- Strasmore terms · free, no signup
What each column holds
| Column | Type | Range | Notes |
|---|---|---|---|
bucket |
text | 5 distinct values | |
record_closes |
number | 2 to 403 | |
share_pct |
number | 0.5 to 94.2 | percent |
cumulative_share_pct |
number | 94.2 to 100 | percent |
Computed from Strasmore's warehouse of US exchange, SIP and OPRA market data. Equity prices are delayed; options greeks and implied volatility are end-of-day. This result is stored, not recomputed on load — it is exactly the numbers that were returned on , and the query below is what returned them.
Run it yourself
This is the exact query behind the result above. Change a ticker, a date or a column and run it against the warehouse — no account, no key. The no-signup tier is smaller than the one this page was computed on; a query that reaches past it comes back saying which plan runs it.
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