Average S&P 500 forward return after fearful, greedy, and ordinary days (2024-2026)
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-07-16, from Buy When Others Are Fearful: The Data.
| market_mood | days | fwd_5d_pct | fwd_20d_pct | fwd_60d_pct |
|---|---|---|---|---|
| Extreme fear (least upbeat) | 45 | 0.08 | 1.47 | 6.35 |
| In between | 359 | 0.38 | 1.24 | 4.03 |
| Extreme greed (most upbeat) | 45 | 0.13 | 1.53 | 2.85 |
- Rows × columns
- 3 × 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 |
|---|---|---|---|
market_mood |
text | 3 distinct values | |
days |
number | 45 to 359 | |
fwd_5d_pct |
number | 0.08 to 0.38 | percent |
fwd_20d_pct |
number | 1.24 to 1.53 | percent |
fwd_60d_pct |
number | 2.85 to 6.35 | 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 sent AS (
SELECT toDate(published_utc) AS d,
(countIf(s='positive') - countIf(s='negative')) / (countIf(s='positive') + countIf(s='negative')) AS net
FROM (SELECT published_utc, JSONExtractString(ins,'sentiment') AS s
FROM global_markets.stocks_news ARRAY JOIN insights AS ins
WHERE JSONExtractString(ins,'sentiment') != '')
GROUP BY d HAVING countIf(s='positive') + countIf(s='negative') >= 20
),
spyd AS (
SELECT toDate(toTimeZone(window_start,'America/New_York')) AS d,
argMax(toFloat64(close), toTimeZone(window_start,'America/New_York')) AS c
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker='SPY' AND window_start >= '2024-01-01'
AND (toHour(toTimeZone(window_start,'America/New_York'))*60 + toMinute(toTimeZone(window_start,'America/New_York'))) BETWEEN 570 AND 959
GROUP BY d
),
spy AS (
SELECT d, c, leadInFrame(c,5) OVER w AS c5, leadInFrame(c,20) OVER w AS c20, leadInFrame(c,60) OVER w AS c60
FROM spyd WINDOW w AS (ORDER BY d ASC ROWS BETWEEN CURRENT ROW AND 60 FOLLOWING)
),
j AS (
SELECT s.net AS net, (spy.c5/spy.c-1)*100 AS f5, (spy.c20/spy.c-1)*100 AS f20, (spy.c60/spy.c-1)*100 AS f60
FROM sent s INNER JOIN spy ON s.d = spy.d WHERE spy.c60 > 0
),
qt AS (SELECT quantile(0.1)(net) AS p10, quantile(0.9)(net) AS p90 FROM j)
SELECT multiIf(net <= (SELECT p10 FROM qt), 'Extreme fear (least upbeat)',
net >= (SELECT p90 FROM qt), 'Extreme greed (most upbeat)',
'In between') AS market_mood,
count() AS days,
round(avg(f5),2) AS fwd_5d_pct,
round(avg(f20),2) AS fwd_20d_pct,
round(avg(f60),2) AS fwd_60d_pct
FROM j GROUP BY market_mood
ORDER BY multiIf(market_mood='Extreme fear (least upbeat)',0, market_mood='In between',1, 2)
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 analysisBuy When Others Are Fearful: The Data
News sentiment coverage by year: how many tagged insights, and the share downbeat
ranking 3×3
→
The S&P 500's underwater curve: worst drawdown from a prior high, by month
series 127×2
→
SPY's 20 best and 20 worst days since 2016, counted by year
ranking 11×3
→
S&P 500 worst intra-year drawdown vs the year's price return, since 2016
ranking 11×3
→
The ten biggest single-day gains for SPY since 2016
ranking 10×2
→
SPY total return since 2016, after removing the best single days
ranking 5×2
→
See all 2,170 queries →