STRASMORE/EXPLORE 2,433 QUERIES

forward_returns

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.

as of table 3×7read in context →
forward_returns — 3 rows by 7 columns, computed from US exchange, SIP and OPRA data.
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
Rows × columns
3 × 7
Computed
Completeness
No missing values
Source
US exchange, SIP and OPRA market data
Licence
Strasmore terms · free, no signup
Formats
JSON · CSV · the SQL below

What each column holds

Column definitions for forward_returns, derived from the stored result.
ColumnTypeRangeNotes
horizon text 3 distinct values (1 year, 3 years, 5 years)
record_closes number 326 to 427
other_closes number 3,201 to 4,108
record_median_pct number 12.6 to 66.3 percent
other_median_pct number 13.7 to 75 percent
record_positive_pct number 78.2 to 99.4 percent
other_positive_pct number 82.6 to 97.6 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,
            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
⌘/Ctrl + Enter
More from this analysisinvesting-at-all-time-highs
ath_by_year table 20×7 drawdowns table 7×5 next_ath ranking 5×4 The 2s10s spread, every print of the half table 124×2 The 2s10s spread, every print of the half table 124×2 Every half-year since 1976: the 2y and 10y change, the twist between them, and the half's lowest 2s10s print table 100×7 See all 2,433 queries →