五月卖出、离场观望有效吗?标普500实证
回测2005年至2026年的标普500夏季与冬季表现,比较五月卖出、十月底买回的收益差距及实际成本,看看这条策略为何难以执行。
“五月卖出、离场观望”是股市中最广为人知的日历效应规则:在四月底卖出,整个夏季持有现金,十月底再买回。以标普500指数跟踪产品的价格计算,从2005年4月最后一个交易日收盘至2026年4月最后一个交易日收盘,11月至次年4月这一半年的平均回报确实高于5月至10月这一半年。两者之间的差距确实存在,但小于这句俗语所暗示的程度。5月至10月这一半年平均也能取得收益,这正是该规则作为投资计划失效的地方。
规则内容及检验方法
这一说法源自伦敦的一个旧习惯:春季赛马活动结束后离开城市,秋季再返回。以现代且可检验的形式来看,它是一条有两个日期的切换规则。投资者从十月最后一个交易日持有广泛股票指数,直到次年四月最后一个交易日。其余六个月不持有任何资产。
检验这条规则需要两项格言本身没有提供的信息:价格序列和明确的时间窗口。下文所有数据均使用 SPY,即历史最悠久的标普500 ETF,价格取自 global_markets.delayed_stocks_minute_aggs 表中的常规交易时段分钟线,区间从2005年4月最后一个交易日收盘至2026年4月最后一个交易日收盘。本文所称月度回报,是当月最后一个常规交易时段价格相对于上月最后一个常规交易时段价格的变化,仅计价格回报。股息将在下文单独讨论;月度回报的计算方式将说明这一差异为何会改变结果。
“五月卖出并离场”策略是否有效?2005年至2026年的表现
每个数字背后的完整 SQL
WITH monthly AS (
SELECT toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(close, window_start) AS month_close
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2005-04-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-04-30')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY month_start
)
SELECT toYear(apr_m.month_start) AS year,
round(100 * (toFloat64(oct_m.month_close) / toFloat64(apr_m.month_close) - 1), 2) AS may_oct_pct,
round(100 * (toFloat64(apr_next.month_close) / toFloat64(oct_m.month_close) - 1), 2) AS nov_apr_pct
FROM monthly AS apr_m
INNER JOIN monthly AS oct_m ON oct_m.month_start = addMonths(apr_m.month_start, 6)
INNER JOIN monthly AS apr_next ON apr_next.month_start = addMonths(apr_m.month_start, 12)
WHERE toMonth(apr_m.month_start) = 4
ORDER BY year每个点代表一个夏季及其后的冬季。图表包含21个完整组合,起始于2005年夏季,终止于截至2026年4月的冬季。
在大多数单独年份中,两条线的表现并不一致;在表现最差的年份,两者却一致。从4月底到10月底,2008的夏季阶段录得-29.81%,为整个期间的最大跌幅。随后冬季录得-10.03%。在4月卖出的持有人避开了大幅下跌,随后又在最后一段下跌期间重新入场。整个周期中,季节规律与市场损失仅有一半时间相互吻合。
2015年以来,夏季缺口是否已经减弱?
这句格言真正声称的是平均表现,而这些平均值取决于统计区间。以2015年为界,将整个样本分成长度相近的两部分,并采用相同方法计算。
每个数字背后的完整 SQL
WITH monthly AS (
SELECT toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(close, window_start) AS month_close
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2005-04-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-04-30')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY month_start
),
pairs AS (
SELECT toYear(apr_m.month_start) AS season_year,
toFloat64(oct_m.month_close) / toFloat64(apr_m.month_close) - 1 AS may_oct,
toFloat64(apr_next.month_close) / toFloat64(oct_m.month_close) - 1 AS nov_apr
FROM monthly AS apr_m
INNER JOIN monthly AS oct_m ON oct_m.month_start = addMonths(apr_m.month_start, 6)
INNER JOIN monthly AS apr_next ON apr_next.month_start = addMonths(apr_m.month_start, 12)
WHERE toMonth(apr_m.month_start) = 4
),
tagged AS (
SELECT may_oct,
nov_apr,
arrayJoin(['All seasons 2005-2025',
if(season_year <= 2014, 'First half 2005-2014', 'Second half 2015-2025')]) AS era
FROM pairs
)
SELECT era,
count() AS season_count,
round(100 * avg(may_oct), 2) AS avg_may_oct_pct,
round(100 * avg(nov_apr), 2) AS avg_nov_apr_pct,
round(100 * (avg(nov_apr) - avg(may_oct)), 2) AS winter_minus_summer_pct,
countIf(may_oct > 0) AS summers_positive
FROM tagged
GROUP BY era
ORDER BY era在全部 21 个季节中,5月至10月的平均回报为 3.88%,11月至次年4月为 6.06%,两者相差 2.18 个百分点。这一差距构成该规则的全部依据。再看夏季一栏:其中 16 个夏季最终高于起点,因此,平均而言,离场反而错过了上涨,而不是避开下跌。
两部分的结果并不相同。较早阶段的差距为 4.84 个百分点,较晚阶段为 -0.24 个百分点。常见的说法只引用差距,却不说明背后的年份,而年份本身就是论据。任何季节性统计,都是对某一段历史的判断;这段历史区间由传播这一说法的人自行选择。
这真的是八月和九月效应吗?
六个月作为分析区间过于粗略。按月拆分日历,可以检验夏季窗口是否整体偏弱,还是其中某个月份贡献了全部结果。
每个数字背后的完整 SQL
WITH monthly AS (
SELECT toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(close, window_start) AS month_close
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2005-04-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-04-30')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY month_start
),
month_returns AS (
SELECT m1.month_start AS month_start,
100 * (toFloat64(m1.month_close) / toFloat64(m0.month_close) - 1) AS ret_pct
FROM monthly AS m1
INNER JOIN monthly AS m0 ON m0.month_start = addMonths(m1.month_start, -1)
)
SELECT formatDateTime(month_start, '%b') AS label,
count() AS readings,
round(avg(ret_pct), 2) AS avg_return_pct,
round(quantileDeterministic(0.5)(ret_pct, cityHash64(month_start)), 2) AS median_return_pct,
countIf(ret_pct > 0) AS positive_readings
FROM month_returns
GROUP BY label
ORDER BY min(toMonth(month_start))弱势集中在九月。九月平均为 -0.75%,在 12 个年份中的 21 个年份收高,中位数为 0.38%。八月平均为 0.28%。七月位于卖方跳过的这段区间中部,平均为 2.63%;十月是该规则重新买入前的最后一个月,平均为 0.97%。
这一窗口中的效应范围很窄:夏末出现一段疲弱期,前后月份的表现则与全年其他月份大致相似。若在四月底退出以避开九月,也会错过五月、六月、七月和八月。
What the switch actually costs
The seasonal gap is a gross number. A rule has to survive its own frictions.
每个数字背后的完整 SQL
WITH monthly AS (
SELECT toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(close, window_start) AS month_close
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2005-04-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-04-30')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY month_start
),
pairs AS (
SELECT toYear(apr_m.month_start) + 1 AS year,
toFloat64(oct_m.month_close) / toFloat64(apr_m.month_close) AS summer_ratio,
toFloat64(apr_next.month_close) / toFloat64(oct_m.month_close) AS winter_ratio
FROM monthly AS apr_m
INNER JOIN monthly AS oct_m ON oct_m.month_start = addMonths(apr_m.month_start, 6)
INNER JOIN monthly AS apr_next ON apr_next.month_start = addMonths(apr_m.month_start, 12)
WHERE toMonth(apr_m.month_start) = 4
)
SELECT year,
round(exp(sum(log(summer_ratio * winter_ratio)) OVER (ORDER BY year)), 2) AS buy_and_hold_growth,
round(exp(sum(log(winter_ratio)) OVER (ORDER BY year)), 2) AS winter_only_growth
FROM pairs
ORDER BY yearA dollar held through every season from the end of April 2005 grew to $6.21 by the end of April 2026. The same dollar in the market only from November through April, idle the rest of the time, grew to $3.14. Both lines are price only with nothing deducted, which flatters the switching rule. Here is what the chart leaves out.
- Two trades every year, in every one of the 21 seasons. The ticket is free at most brokers now. The bid ask spread paid on each round trip is not.
- Tax. In a taxable account the April sale realizes whatever gain built up since November. A November to April holding period runs under a year by construction, which places the gain at short-term rates for a US taxpayer.
- Idle cash. The winter-only line credits the sidelined money with zero. A real holder would park it, and where to park idle cash covers the instruments people use for that.
- Dividends, which the next panel prices out.
每个数字背后的完整 SQL
SELECT toYear(ex_dividend_date) AS year,
round(sumIf(cash_amount, toMonth(ex_dividend_date) >= 5 AND toMonth(ex_dividend_date) <= 10), 3) AS may_oct_dividends_usd,
round(sumIf(cash_amount, toMonth(ex_dividend_date) < 5 OR toMonth(ex_dividend_date) > 10), 3) AS nov_apr_dividends_usd
FROM global_markets.stocks_dividends
WHERE ticker = 'SPY'
AND cash_amount > 0
AND ex_dividend_date >= toDate('2005-01-01')
AND ex_dividend_date <= toDate('2025-12-31')
GROUP BY year
ORDER BY yearSPY's ex-dividend dates land in March, June, September and December, so two of the four fall inside the six months a seller sits out. In 2025, $3.592 per share of distributions carried a May to October ex-date, against $3.689 in the other six months. The two columns track each other across the whole chart. Sitting out half the calendar forgoes roughly half the fund's income every year, on top of whatever the price does in those months. The ex-dividend date is the cutoff that decides who gets paid.
One more cost carries no line item. Index returns arrive in clusters, and missing the best days puts numbers on what a handful of absent sessions does to a long-run balance. Six months out of every year is a lot of absent sessions. That is the same arithmetic behind dollar cost averaging as a discipline about time in the market rather than timing.
为什么一条著名的日历规则值得怀疑
十二个月可以组成4,095种非空投资子集。用一段价格历史遍历这些组合,总会有某种组合显得格外突出。这是搜索本身带来的结果。
“五月卖出”有一个真实优势:它早在本次样本开始前就已广为人知。因此,本页所列年份构成样本外检验,而不是拟合结果。检验显示,两者差距有限,且主要集中在某一个月;在同一期间,这条切换规则的表现落后于买入并持有策略。前视偏差介绍了回测如何在不知不觉中提前“得出”答案,而日历规则正是典型案例:先知道回报,再挑选月份。
常见问题
“五月卖出,远离市场”策略如今仍然有效吗?
在2005年4月至2026年4月的SPY价格数据中,11月至次年4月的平均回报为6.06%,而5月至10月为3.88%,因此在完整样本中,季节性差异依然存在。夏季阶段的平均回报仍为正,21个夏季中有16个最终收高。
哪些月份实际上表现较弱?
在这一时间窗口内,疲弱表现主要集中在9月。该月平均回报为-0.75%,21年中有12年收高。7月是这一策略回避的另一个月份,同期平均回报为2.63%。
5月卖出是否优于买入并持有?
在这一样本中并非如此。从4月底2005起持续持有的一美元,到2026年4月增长至$6.21;相比之下,仅持有冬季阶段的策略为$3.14。上述比较未计入交易成本、税费和股息。
如果5月卖出,股息会怎样?
基金的除息日不会因夏季到来而暂停。SPY每年四个季度除息日中,有两个落在5月至10月期间;如果投资者在除息日当天未持有该基金,则无法获得这笔分配。
季节性模式是否等同于预测?
不是。基于21个季节计算的历史平均值,只能描述特定时间窗口内发生过的情况。正如第一张图所示,单个年份相对该平均值的表现差异很大,而且时间窗口本身也是一种选择。
这里的每个图表面板都对应一条已存储查询,并附有其SQL。您可以在Strasmore终端中更换ticker、调整时间窗口,并检验这种季节性差异是否仍然成立。