
財務分析基礎:手把手回測——一個擇時策略的完整實作與拆解,以 SPY 黃金交叉為例
上一篇講了回測會怎麼騙你,這一篇實際跑一次給你看。用 SPY 二十年真實資料,回測最有名的擇時策略「黃金交叉」(50 日均線上穿 200 日),對決買進持有,走完回測七步。結果很誠實:擇時的總報酬(9.0%)其實輸給買進持有(10.3%),但最大回撤從 -55% 砍到 -34%。更關鍵的是——它在含 2008 的樣本內贏、在 2015 到 2024 的樣本外輸,親手示範為什麼樣本外驗證能拆穿漂亮的回測。
WRITTEN BY

- Name
- Harry Chang
上一篇(036)講了回測會用哪些方式騙你——生存者偏誤、前視偏誤、過度擬合、忽略成本。光講陷阱不夠,這一篇實際跑一次回測給你看:用真實資料、真實策略,走完完整流程,並在每一步套用上一篇的警告。
我們要測的是全世界最有名、最簡單的擇時策略——黃金交叉 (Golden Cross):當 50 日均線(短期)向上穿越 200 日均線(長期),代表趨勢轉多,買進並持有;當 50 日跌破 200 日(死亡交叉),出場抱現金。對手是最樸實的買進持有 (Buy & Hold)。
先破梗,這篇的結論很誠實:擇時策略的總報酬其實輸給買進持有——但它把最大回撤砍了一半。 一個「輸了報酬、贏了安穩」的結果,正好逼我們回答「回測到底在測什麼」。
NOTE
回測七步:① 提出可證偽的假設 → ② 取得乾淨資料 → ③ 精確定義規則(避免前視) → ④ 執行 → ⑤ 用對的指標衡量 → ⑥ 用 036 的清單壓力測試 → ⑦ 誠實下結論。本文就照這七步走。
- 步驟 1-3:假設、資料、規則
- 步驟 4:執行(核心程式碼)
- 步驟 5:用對的指標衡量
- 步驟 4 的收穫:回測在測什麼
- 步驟 6:用 036 的清單壓力測試
- 步驟 7:誠實下結論
- 誠實的三個提醒
- 總結
- 參考資料 (References)
步驟 1-3:假設、資料、規則
假設(要可證偽):「當 50 日均線在 200 日均線之上時持有 SPY、否則抱現金,長期報酬與風險會優於單純買進持有。」這句話夠精確,能被數據明確否證——這是好假設的條件。
資料:SPY(追蹤 S&P 500 的 ETF)2004–2024 的還原調整收盤價(adjusted close,已還原股利與分割,否則報酬會少算)。用單一 ETF 有個好處——天生沒有生存者偏誤(SPY 沒下市);若改成回測一籃子個股,就必須用 point-in-time 名單納入下市股(036 的教訓)。
規則(魔鬼在細節):最容易犯的錯就在這一步——前視偏誤。看到今天的均線交叉,最快要明天才能成交,所以訊號必須延遲一天再進場:

上圖把規則畫出來:綠色陰影是「在場內」的時段。可以看到策略在 2008 年底、2022 年果斷離場,避開了大跌段。
步驟 4:執行(核心程式碼)
回測的核心邏輯不到十行,關鍵全在防弊的細節:
sma_s, sma_l = px.rolling(50).mean(), px.rolling(200).mean()
signal = (sma_s > sma_l).astype(float) # 1 = 在場,0 = 抱現金
pos = signal.shift(1) # ★ 延遲一天成交 → 杜絕前視偏誤
trades = pos.diff().abs() # 每次進出場 = 1 次交易
strat_ret = pos * daily_ret - trades * 0.001 # 抱現金報酬 0%,每次換倉扣 0.1% 成本
equity = (1 + strat_ret).cumprod() # 資金曲線
shift(1)是整段程式最重要的一行——少了它,你就是用「今天才知道的訊號」在「今天」交易,回測績效會虛高、上線後現形。- 成本
0.001(每次換倉 0.1%)把 036 的「忽略成本」補上。全期只交易 19 次,成本影響不大,但不放進去就是不誠實。
步驟 5:用對的指標衡量
只看「賺多少」會誤導,一份合格的回測至少要四個維度(全期 2005–2024):
| 指標 | 買進持有 | 黃金交叉(扣成本) | 誰贏 |
|---|---|---|---|
| 年化報酬 (CAGR) | 10.3% | 9.0% | 買進持有 |
| 年化波動 | 18.7% | 13.2% | 擇時 |
| 夏普值 (Sharpe) | 0.62 | 0.72 | 擇時 |
| 最大回撤 (MaxDD) | -55.2% | -33.7% | 擇時 |
註:夏普值以無風險利率 0% 計算(抱現金那段實際上還能賺國庫券利息,此處保守略去,對策略略有低估)。

資金曲線(對數尺度)講了一個故事:綠線(擇時)在 2008 年金融海嘯靠著提早離場一度大幅領先;但接下來 2015–2024 的長多頭裡,藍線(買進持有)一路追上、反超,最後收在更高的位置。擇時的超額報酬,幾乎全來自躲過 2008 那一次。
步驟 4 的收穫:回測在測什麼

這張回撤圖(水面下深度)才是擇時策略真正的賣點:買進持有在 2008 年一度腰斬 -55%,擇時只跌 -34%。所以「誰贏」取決於你的目標函數——要最高的長期報酬,買進持有勝;要能睡得著、少一半的回撤,擇時勝。 回測不會告訴你「該選哪個」,它告訴你「每個選擇的代價是什麼」。
步驟 6:用 036 的清單壓力測試
漂亮的全期數字先別高興,套上上一篇的檢查表:
① 樣本外驗證(最致命的一關):把資料切兩半,一半設計、一半驗證:
| 期間 | 買進持有 CAGR / 回撤 | 黃金交叉 CAGR / 回撤 |
|---|---|---|
| 樣本內 2005–2014(含 2008) | 7.6% / -55% | 9.6% / -17% |
| 樣本外 2015–2024 | 13.0% / -34% | 8.5% / -34% |
真相大白:策略在含 2008 的樣本內大勝(躲過海嘯),到了 2015–2024 的樣本外報酬大輸(13.0% vs 8.5%),而且 2020 年 COVID 那次閃崩太快,200 日均線根本來不及反應,連回撤都沒守住(一樣 -34%)。一個只在特定歷史(2008)有效的策略,就是這樣被樣本外測試拆穿的。
② 參數敏感度(過度擬合檢查):換幾組均線天數看看,全期扣成本後的年化報酬:
| 均線組合 | 20/100 | 50/150 | 50/200 | 100/200 | 100/250 |
|---|---|---|---|---|---|
| 年化報酬 | 6.6% | 9.4% | 9.0% | 8.2% | 8.1% |
五組結果散布在 6.6%~9.4%,而且沒有任何一組贏過買進持有的 10.3%。如果我只挑出 50/150 那組來報告、假裝其他不存在,那就是在過度擬合、在騙你也騙自己。誠實的回測要秀出整片參數的樣子,不是只秀最甜的那一格。
步驟 7:誠實下結論
把七步走完,這個回測真正能說的話是:
- 黃金交叉不是印鈔機:全期報酬輸買進持有(9.0% vs 10.3%),五組參數無一勝出。
- 但它是個減震器:回撤從 -55% 降到 -34%、夏普值更高——它用一點報酬換來大幅的安穩。
- 它的「edge」是歷史特定的:樣本外(2015–2024)報酬大輸,證明全期的好看主要來自躲過 2008。
- 它適合誰:承受不了腰斬、寧可少賺也要少震盪的人;不適合追求最大化長期報酬的人。
這才是一份回測該有的結論——重點不是這招能不能賺,而是它用什麼換什麼、在什麼條件下才成立。
誠實的三個提醒
- 這仍然只是一次回測。單一資產(SPY)、單一市場(美股)、單一段歷史。換成別的市場、別的期間,結論可能翻盤——一次回測是一個資料點,不是定律。
- 我沒有把水擠乾:真實世界還有稅、股利在抱現金期間的機會成本、大額下單的滑價、以及心理面(連續兩年跑輸大盤時,你抱得住這套策略嗎?)。
- 回測的價值不在找到聖杯,在戒掉幻想。它最大的功用,是幫你在下真金之前,誠實看見一套策略的極限與代價——這正好呼應這個系列的初衷:別被漂亮數字騙,尤其是自己跑出來的那個。
總結
- 回測七步:假設 → 資料 → 規則(
shift(1)杜絕前視) → 執行(含成本) → 對的指標(不只看報酬) → 036 清單壓測 → 誠實結論。 - 實測結果:黃金交叉全期 CAGR 9.0% 輸買進持有 10.3%,但最大回撤 -34% 遠優於 -55%,夏普更高——輸報酬、贏安穩。
- 樣本外拆穿一切:樣本內(含 2008)大勝、樣本外(2015–2024)大輸——策略的 edge 是躲過一次海嘯的歷史特例。
- 參數要全秀:5 組均線 6.6%~9.4%,無一勝過買進持有;只報最甜那組就是過度擬合。
- 回測的真義:不是找聖杯,是在下真金前誠實看見代價。
參考資料 (References)
- 資料來源:SPY 還原調整收盤價 2004–2024(Yahoo Finance chart API)。完整回測程式碼:本站
findata/backtest_037.py。 - Meb Faber (2007), "A Quantitative Approach to Tactical Asset Allocation"(移動平均擇時的經典研究)
- 搭配閱讀:財務 010 回測與偏誤——生存者偏誤