Strasmore Research

Bán tháng Năm rồi đứng ngoài có hiệu quả?

Kiểm định quy tắc Bán tháng Năm rồi đứng ngoài trên hai thập kỷ của S&P 500: chênh lệch mùa hè và chi phí thực tế khi chuyển đổi danh mục.

Bán vào tháng Năm rồi đứng ngoài thị trường là quy tắc theo lịch được biết đến nhiều nhất trên thị trường cổ phiếu: bán vào cuối tháng Tư, giữ tiền mặt trong suốt mùa hè và mua lại vào cuối tháng Mười. Tính trên giá các quỹ bám sát S&P 500 từ mức đóng cửa cuối tháng Tư năm 2005 đến mức đóng cửa cuối tháng Tư năm 2026, giai đoạn từ tháng Mười Một đến tháng Tư quả thực có mức sinh lời trung bình cao hơn giai đoạn từ tháng Năm đến tháng Mười. Chênh lệch này là có thật, nhưng nhỏ hơn hàm ý của câu nói trên. Giai đoạn mùa hè cũng có mức tăng trung bình, và đây là điểm khiến quy tắc này không còn hiệu quả khi được áp dụng như một kế hoạch đầu tư.

Quy tắc nói gì và cách kiểm định

Cụm từ này bắt nguồn từ một thói quen cũ ở London: rời thành phố sau các cuộc đua ngựa mùa xuân và trở lại vào mùa thu. Ở dạng hiện đại có thể kiểm định, đây là một quy tắc chuyển đổi với hai mốc thời gian. Bạn nắm giữ một chỉ số cổ phiếu diện rộng từ ngày giao dịch cuối cùng của tháng 10 đến ngày giao dịch cuối cùng của tháng 4. Trong sáu tháng còn lại, bạn không nắm giữ tài sản nào.

Để kiểm định, cần hai yếu tố mà câu ngạn ngữ này không cung cấp: một chuỗi giá và một khoảng thời gian được xác định rõ. Mọi con số dưới đây đều sử dụng SPY, ETF mô phỏng S&P 500 lâu đời nhất, với dữ liệu giá từ các thanh một phút trong giờ giao dịch thông thường tại bảng global_markets.delayed_stocks_minute_aggs, tính từ giá đóng cửa cuối cùng của tháng 4 năm 2005 đến giá đóng cửa cuối cùng của tháng 4 năm 2026. Lợi suất tháng ở đây được tính bằng giá trong giờ giao dịch thông thường cuối tháng so với giá của tháng trước, chỉ xét giá. Cổ tức được trình bày trong một phần riêng ở bên dưới, còn cách đo lường lợi suất tháng giải thích vì sao sự khác biệt này làm thay đổi kết quả.

Chiến lược “bán vào tháng Năm rồi đứng ngoài thị trường” có hiệu quả không? Thành tích từ năm 2005 đến năm 2026

Truy vấnETF theo dõi S&P 500: tháng 5 đến tháng 10 so với tháng 11 đến tháng 4, theo từng mùa
Câu lệnh SQL chính xác đằng sau mỗi con số
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
Run this yourself

Mỗi điểm ghép một mùa hè với mùa đông tiếp theo. Biểu đồ có 21 cặp hoàn chỉnh, bắt đầu từ mùa hè năm 2005 và kết thúc với mùa đông kết thúc vào tháng Tư 2026.

Hai đường biểu diễn không đồng thuận trong phần lớn các năm riêng lẻ, nhưng lại đồng thuận trong năm tồi tệ nhất. Từ cuối tháng Tư đến cuối tháng Mười, 2008 giai đoạn mùa hè ghi nhận -29.81%, mức giảm sâu nhất trong chuỗi. Mùa đông tiếp theo ghi nhận -10.03%. Nhà đầu tư bán vào tháng Tư đã tránh được một đợt giảm mạnh vào mùa thu, rồi quay lại thị trường đúng vào chặng giảm cuối cùng. Lịch theo mùa và mức thiệt hại chỉ trùng khớp trong một nửa quãng đường.

Khoảng chênh lệch mùa hè có thu hẹp kể từ năm 2015?

Trung bình mới là điều câu ngạn ngữ thực sự muốn nói, còn khung thời gian là nền tảng của các mức trung bình đó. Chia chuỗi dữ liệu tại năm 2015 sẽ tạo ra hai giai đoạn có độ dài tương đương và được tính theo cùng một phương pháp.

Truy vấnChênh lệch theo từng giai đoạn: lợi nhuận trung bình nửa năm và spread giữa hai giai đoạn
Câu lệnh SQL chính xác đằng sau mỗi con số
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
Run this yourself

Trong toàn bộ 21 mùa, giai đoạn từ tháng 5 đến tháng 10 đạt mức trung bình 3.88%, còn giai đoạn từ tháng 11 đến tháng 4 đạt 6.06%, tạo ra chênh lệch 2.18 điểm phần trăm. Chênh lệch đó là toàn bộ cơ sở của quy tắc này. Hãy xem cột mùa hè bên cạnh: 16 số mùa hè kết thúc ở mức cao hơn thời điểm bắt đầu, cho thấy việc đứng ngoài thị trường trong giai đoạn trung bình đã bỏ lỡ một khoản tăng giá thay vì tránh được một khoản lỗ.

Hai giai đoạn cho kết quả khác nhau. Giai đoạn đầu ghi nhận chênh lệch 4.84 điểm, còn giai đoạn sau là -0.24 điểm. Những cách diễn giải phổ biến thường nêu một khoảng chênh lệch nhưng không nói rõ các năm đứng sau con số đó, trong khi chính các năm mới là cơ sở của lập luận. Mọi thống kê theo mùa đều là một nhận định về một khoảng lịch sử nhất định, và việc lựa chọn khoảng đó phụ thuộc vào người đưa ra nhận định.

Có thực sự tồn tại hiệu ứng tháng 8 và tháng 9?

Sáu tháng là một khoảng thời gian khá thô. Chia lịch theo từng tháng giúp xác định liệu giai đoạn mùa hè có suy yếu đồng đều hay chỉ một phần trong đó tạo ra toàn bộ kết quả.

Truy vấnLợi nhuận trung bình của ETF theo dõi S&P 500 theo từng tháng dương lịch, tháng 5/2005 đến tháng 4/2026
Câu lệnh SQL chính xác đằng sau mỗi con số
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))
Run this yourself

Mức suy yếu tập trung ở tháng 9. Tháng này có mức bình quân là -0.75% và kết thúc cao hơn trong 12 trên tổng số 21 năm, với trung vị là 0.38%. Tháng 8 có mức bình quân 0.28%. Tháng 7, nằm giữa giai đoạn mà người bán bỏ qua, có mức bình quân 2.63%; còn tháng 10, tháng cuối cùng trước khi quy tắc mua trở lại, có mức bình quân 0.97%.

Hiệu ứng trong giai đoạn này khá hẹp: chỉ có một khoảng suy yếu vào cuối mùa hè, trong khi các tháng liền kề không khác nhiều so với phần còn lại của năm. Thoát vị thế vào cuối tháng 4 để tránh tháng 9 cũng đồng nghĩa với việc bỏ qua tháng 5, tháng 6, tháng 7 và tháng 8.

What the switch actually costs

The seasonal gap is a gross number. A rule has to survive its own frictions.

Truy vấnTăng trưởng của $1: nắm giữ cả năm so với chỉ nắm giữ từ tháng 11 đến tháng 4
Câu lệnh SQL chính xác đằng sau mỗi con số
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 year
Run this yourself

A 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.
Truy vấnCác khoản phân phối của SPY theo tháng ex-dividend: trong giai đoạn tháng 5 đến tháng 10 so với ngoài giai đoạn này
Câu lệnh SQL chính xác đằng sau mỗi con số
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 year
Run this yourself

SPY'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.

Vì sao một quy tắc lịch nổi tiếng vẫn đáng hoài nghi

Mười hai tháng cho phép chọn một tập hợp để đầu tư theo 4.095 cách khác nhau, không kể tập rỗng. Khi tìm kiếm trong không gian đó dựa trên một chuỗi giá duy nhất, sẽ có một kết quả trông rất nổi bật. Đó là hệ quả của quá trình tìm kiếm.

Sell in May có một lợi thế đáng ghi nhận so với một quy tắc mới được khai thác từ dữ liệu tuần trước: quy tắc này đã nổi tiếng từ lâu trước khi giai đoạn dữ liệu hiện tại bắt đầu. Vì vậy, các năm trong bài đóng vai trò là phép kiểm định ngoài mẫu, thay vì kết quả được khớp theo dữ liệu. Phép kiểm định cho thấy chênh lệch khiêm tốn, tập trung vào một tháng, và gắn với một quy tắc chuyển đổi nhưng vẫn kém hơn chiến lược mua và nắm giữ trong cùng giai đoạn. Thiên lệch nhìn trước trình bày cách một backtest âm thầm biết trước đáp án, và các quy tắc theo lịch là ví dụ điển hình: các tháng được lựa chọn sau khi đã biết lợi suất.

Câu hỏi thường gặp

Chiến lược bán vào tháng 5 rồi đứng ngoài thị trường còn hiệu quả không?

Xét giá SPY từ tháng 4/2005 đến tháng 4/2026, giai đoạn từ tháng 11 đến tháng 4 đạt trung bình 6.06%, so với 3.88% trong giai đoạn từ tháng 5 đến tháng 10. Vì vậy, chênh lệch theo mùa vẫn tồn tại trong toàn bộ mẫu dữ liệu. Nửa mùa hè vẫn ghi nhận mức tăng trung bình, với 16 trong số 21 mùa hè kết thúc ở mức cao hơn thời điểm bắt đầu.

Những tháng nào thực sự yếu?

Trong giai đoạn này, mức yếu tập trung vào tháng 9. Tháng này đạt trung bình -0.75% và kết thúc cao hơn trong 12 trên 21 năm. Tháng 7, một tháng khác mà chiến lược này đứng ngoài thị trường, đạt trung bình 2.63% trong cùng giai đoạn.

Bán vào tháng 5 có hiệu quả hơn mua và nắm giữ không?

Không trong mẫu dữ liệu này. Một dollar được nắm giữ liên tục từ cuối tháng 4 2005 tăng lên $6.21 vào tháng 4 2026. Trong khi đó, chiến lược chỉ chuyển sang nắm giữ vào mùa đông đạt $3.14. So sánh này chưa tính chi phí giao dịch, thuế và cổ tức.

Nếu bán vào tháng 5, cổ tức sẽ được xử lý thế nào?

Ngày giao dịch không hưởng quyền (ex-dividend date) của quỹ vẫn diễn ra trong mùa hè. Hai trong bốn ngày ex-dividend theo quý của SPY rơi vào giai đoạn từ tháng 5 đến tháng 10. Nhà đầu tư không nắm giữ quỹ vào ngày ex-dividend sẽ không nhận được khoản phân phối đó.

Mô hình theo mùa có giống một dự báo không?

Không. Mức trung bình lịch sử của 21 mùa chỉ mô tả những gì đã xảy ra trong một khoảng thời gian xác định. Kết quả từng năm dao động đáng kể quanh mức trung bình đó, như biểu đồ đầu tiên cho thấy. Bản thân việc lựa chọn khoảng thời gian cũng là một giả định.


Mỗi bảng trong bài này là một stored query kèm SQL tương ứng. Hãy thay đổi ticker, điều chỉnh khoảng thời gian và kiểm tra liệu chênh lệch theo mùa có còn tồn tại trên terminal Strasmore hay không.

#seasonality#backtesting#sp-500#market-history#quant