drawdowns
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.
| 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 |
- Rows × columns
- 7 × 5
- 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 |
|---|---|---|---|
peak_label |
text | 7 distinct values (Feb 19, 2020, Feb 19, 2025, Jan 26, 2018…) | |
trough_label |
text | 7 distinct values (Apr 2, 2018, Apr 8, 2025, Dec 24, 2018…) | |
decline_pct |
number | 10.2 to 56.5 | percent |
days_to_new_high |
number | 128 to 1,983 | US dollars |
new_high_label |
text | 7 distinct values (Apr 29, 2019, Aug 18, 2020, Aug 24, 2018…) |
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,
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