worst_windows
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 santa-claus-rally.
| label | measured_from | measured_to | santa_window_pct | following_year_pct |
|---|---|---|---|---|
| Dec 2014 to Jan 2015 | Dec 23 | Jan 5 | -2.9 | -0.81 |
| Dec 2015 to Jan 2016 | Dec 23 | Jan 5 | -2.26 | 9.64 |
| Dec 2007 to Jan 2008 | Dec 21 | Jan 3 | -2.21 | -38.28 |
| Dec 2004 to Jan 2005 | Dec 23 | Jan 4 | -1.61 | 3.01 |
| Dec 2023 to Jan 2024 | Dec 21 | Jan 3 | -0.83 | 23.3 |
- Rows × columns
- 5 × 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 |
|---|---|---|---|
label |
text | 5 distinct values | |
measured_from |
text | 2 distinct values (Dec 21, Dec 23) | |
measured_to |
text | 3 distinct values (Jan 3, Jan 4, Jan 5) | |
santa_window_pct |
number | -2.9 to -0.83 | percent |
following_year_pct |
number | -38.28 to 23.3 | 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
spy_days AS
(
SELECT
date,
toYear(date) AS y,
toMonth(date) AS m,
argMax(toFloat64(close), _ingest_time) AS close
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
GROUP BY date
),
ranked AS
(
SELECT
date,
y,
m,
close,
row_number() OVER (PARTITION BY y, m ORDER BY date DESC) AS sessions_from_end,
row_number() OVER (PARTITION BY y, m ORDER BY date ASC) AS sessions_from_start
FROM spy_days
WHERE m IN (12, 1)
),
windows AS
(
SELECT
toUInt16(if(m = 1, y - 1, y)) AS year,
toUInt16(year + 1) AS next_year,
anyIf(date, m = 12 AND sessions_from_end = 6) AS base_date,
anyIf(date, m = 1 AND sessions_from_start = 2) AS end_date,
anyIf(close, m = 12 AND sessions_from_end = 6) AS base_close,
anyIf(close, m = 12 AND sessions_from_end = 1) AS december_close,
anyIf(close, m = 1 AND sessions_from_start = 2) AS end_close
FROM ranked
GROUP BY year
HAVING countIf(m = 12 AND sessions_from_end = 6) = 1
AND countIf(m = 1 AND sessions_from_start = 2) = 1
),
year_ends AS
(
SELECT
y,
argMax(close, date) AS year_end_close
FROM spy_days
WHERE m = 12
AND date < toStartOfYear(today())
GROUP BY y
)
SELECT
concat('Dec ', toString(w.year), ' to Jan ', toString(w.next_year)) AS label,
concat(formatDateTime(w.base_date, '%b '), toString(toDayOfMonth(w.base_date))) AS measured_from,
concat(formatDateTime(w.end_date, '%b '), toString(toDayOfMonth(w.end_date))) AS measured_to,
round((w.end_close / w.base_close - 1) * 100, 2) AS santa_window_pct,
round((n.year_end_close / w.december_close - 1) * 100, 2) AS following_year_pct
FROM windows AS w
INNER JOIN year_ends AS n ON n.y = w.next_year
ORDER BY santa_window_pct ASC
LIMIT 5
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.