return_buckets
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-17, from the-7-5-3-1-rule-mutual-funds.
| bucket | window_count | share_pct |
|---|---|---|
| Below 0% | 0 | 0 |
| 0% to 25% | 17 | 8.8 |
| 25% to 50% | 34 | 17.6 |
| 50% to 100% | 13 | 6.7 |
| Above 100% | 129 | 66.8 |
- Rows × columns
- 5 × 3
- 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 (0% to 25%, 25% to 50%, 50% to 100%…) | |
window_count |
number | 0 to 129 | count |
share_pct |
number | 0 to 66.8 | 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.
the exact SQL behind every number
WITH
monthly AS
(
SELECT
toStartOfMonth(date) AS month_start,
toDate(min(date)) AS first_session,
argMin(toFloat64(close), date) AS first_close
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
GROUP BY month_start
),
divs AS
(
SELECT
toStartOfMonth(ex_dividend_date) AS month_start,
sum(cash) AS month_cash
FROM
(
SELECT
ex_dividend_date,
max(toFloat64(cash_amount)) AS cash
FROM global_markets.stocks_dividends
WHERE ticker = 'SPY'
GROUP BY ex_dividend_date
)
GROUP BY month_start
),
grid AS
(
SELECT
m.month_start AS month_start,
m.first_session AS first_session,
m.first_close AS first_close,
addMonths(m.month_start, 84) AS end_month,
sum(ifNull(d.month_cash, 0)) OVER (ORDER BY m.month_start ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS cash_before
FROM monthly AS m
LEFT JOIN divs AS d ON d.month_start = m.month_start
),
windows AS
(
SELECT
((e.first_close + e.cash_before - s.cash_before) / s.first_close - 1) * 100 AS ret
FROM grid AS s
INNER JOIN grid AS e ON e.month_start = s.end_month
),
stats AS
(
SELECT
count() AS total,
countIf(ret < 0) AS below_zero,
countIf(ret >= 0 AND ret < 25) AS to_25,
countIf(ret >= 25 AND ret < 50) AS to_50,
countIf(ret >= 50 AND ret < 100) AS to_100,
countIf(ret >= 100) AS above_100
FROM windows
)
SELECT
tupleElement(band, 1) AS bucket,
tupleElement(band, 2) AS window_count,
tupleElement(band, 3) AS share_pct
FROM stats
ARRAY JOIN
[
('Below 0%', below_zero, round(below_zero / total * 100, 1)),
('0% to 25%', to_25, round(to_25 / total * 100, 1)),
('25% to 50%', to_50, round(to_50 / total * 100, 1)),
('50% to 100%', to_100, round(to_100 / total * 100, 1)),
('Above 100%', above_100, round(above_100 / total * 100, 1))
] AS band
Run your own version of this
The same 22 years of US equities and 12 years of options data are queryable in SQL or plain English. A free account runs 100 queries a day and takes no card.
More from this analysisthe-7-5-3-1-rule-mutual-funds
rolling_7y
series 193×5
→
step_up
table 10×5
→
worst_windows
table 8×6
→
Top 25 weekly-options underlyings by distinct contracts traded, with expiration weekdays
ranking 25×4
→
Annualized volatility vs total return, 25 large caps, calmest to wildest (~2 years)
ranking 25×3
→
SPY options median spread by expiration date, near-the-money strikes only
ranking 25×4
→
See all 2,309 queries →