SPY's change from the crossing-day close, 3, 6 and 12 months after each un-inversion
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-19, from What Happens When the Yield Curve Un-Inverts.
| episode | spy_at_cross | spy_3m_pct | spy_6m_pct | spy_12m_pct |
|---|---|---|---|---|
| Mar 2007 | 143.29 | 5.5 | 7 | -7.8 |
| Sep 2024 | 550.95 | 10.3 | 5.8 | 17.8 |
- Rows × columns
- 2 × 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 |
|---|---|---|---|
episode |
text | 2 distinct values (Mar 2007, Sep 2024) | |
spy_at_cross |
number | 143.29 to 550.95 | |
spy_3m_pct |
number | 5.5 to 10.3 | percent |
spy_6m_pct |
number | 5.8 to 7 | percent |
spy_12m_pct |
number | -7.8 to 17.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
daily AS
(
SELECT
date,
dateDiff('day', toDate('1970-01-01'), date) AS dnum,
ifNull(toFloat64(max(yield_2_year)), 0.) AS y2,
ifNull(toFloat64(max(yield_10_year)), 0.) AS y10,
y10 - y2 AS spread
FROM global_markets.treasury_yields
WHERE yield_2_year > 0 AND yield_10_year > 0
GROUP BY date
),
scored AS
(
SELECT
date, dnum, y2, y10, spread,
count() OVER (ORDER BY date ROWS BETWEEN 60 PRECEDING AND 1 PRECEDING) AS rows_prior,
sum(spread < 0) OVER (ORDER BY date ROWS BETWEEN 60 PRECEDING AND 1 PRECEDING) AS neg_prior,
sum(spread > 0) OVER (ORDER BY date ROWS BETWEEN CURRENT ROW AND 20 FOLLOWING) AS pos_next
FROM daily
),
candidates AS
(
SELECT
date, dnum, y2, y10, spread,
lagInFrame(dnum, 1, toInt64(-100000)) OVER (ORDER BY dnum ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS prev_dnum
FROM scored
WHERE spread > 0 AND rows_prior = 60 AND neg_prior >= 35 AND pos_next >= 19
),
episodes AS
(
SELECT date AS cross_date, y2, y10, spread
FROM candidates
WHERE dnum - prev_dnum > 180
AND date <= today() - 380
),
spy AS
(
SELECT date, max(toFloat64(close)) AS px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
GROUP BY date
)
SELECT
formatDateTime(e.cross_date, '%b %Y') AS episode,
round(argMinIf(s.px, s.date, s.date >= e.cross_date), 2) AS spy_at_cross,
round((argMinIf(s.px, s.date, s.date >= addDays(e.cross_date, 91)) / argMinIf(s.px, s.date, s.date >= e.cross_date) - 1) * 100, 1) AS spy_3m_pct,
round((argMinIf(s.px, s.date, s.date >= addDays(e.cross_date, 182)) / argMinIf(s.px, s.date, s.date >= e.cross_date) - 1) * 100, 1) AS spy_6m_pct,
round((argMinIf(s.px, s.date, s.date >= addDays(e.cross_date, 365)) / argMinIf(s.px, s.date, s.date >= e.cross_date) - 1) * 100, 1) AS spy_12m_pct
FROM episodes AS e
CROSS JOIN spy AS s
WHERE s.date BETWEEN e.cross_date AND addDays(e.cross_date, 380)
GROUP BY e.cross_date
HAVING countIf(s.date <= addDays(e.cross_date, 7)) > 0
AND countIf(s.date >= addDays(e.cross_date, 365)) > 0
ORDER BY e.cross_date
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 analysisWhat Happens When the Yield Curve Un-Inverts
Which leg did the work: 2-year and 10-year moves over the 91 days before each crossing
table 7×9
→
Every 2s10s un-inversion: the crossing and the spread 3, 6 and 12 months later
table 7×8
→
The 2s10s spread by month, full history
series 604×5
→
The 2s10s over the trailing six months, with the 2-year and 10-year moves that produced it
series 26×8
→
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,401 queries →