【量化實戰】我回測了 101 個華爾街因子,結果最複雜的數學輸給了最簡單的動能——台股 12 年全歷史驗證
這是一場關於「數學 vs 人性」的實驗。
華爾街著名的 WorldQuant 發布了 Alpha 101 因子,號稱是量化交易的聖經。但這些針對美股設計的數學公式,搬到淺碟、散戶多、且有一家「護國神山」獨大的 台股市場,真的有用嗎?
為了找出答案,老兵不用 3 年的短數據騙自己,而是直接拉出 2014 ~ 2026 年(共 12 年) 的全歷史數據,進行了一次地毯式的大掃描。
🧪 實驗設計:這不是兒戲
為了確保數據真實,我用 Python (VectorBT + DuckDB) 搭建了一套嚴格的回測系統,並遵循以下方法論:
- 資料來源:自建台股資料庫(上市櫃全經過還原權值)。
- 回測區間:2014/01/01 ~ 2026/01/30(穿越中美貿易戰、COVID-19 崩盤、AI 狂潮)。
- ⚠️ 方法論聲明:本文回測區間截至資料庫可得之最近交易日(2026/01/30),不包含任何未來資料,所有指標皆使用當下可得資訊計算,以嚴格避免 未來函數偏誤(Look-ahead Bias)。
- 生存者偏差:剔除全額交割股,並包含已下市櫃數據。
- 濾網條件:
- 流動性濾網:只買成交金額前 150 名的大股票(不碰冷門股)。
- 趨勢濾網:股價必須站上季線(MA60)才買(只做多頭)。
- 交易設定:
- 買賣邏輯:每月月底根據因子分數排序,選取 排名前 10 名 個股 等權配置 (Equal Weight)。
- 交易成本:手續費 0.1425% + 證交稅 0.3%(模擬真實交易磨損)。
💥 第一章:數據的陷阱——偽聖杯 Alpha 084
實驗剛開始,電腦跑出了一個驚人的結果。在 101 個因子中,Alpha 084 的 夏普比率(Sharpe Ratio) 高達 3.96!
這是什麼概念?在避險基金界,夏普 > 1 是及格,> 2 是優秀,> 3 基本上就是印鈔機了。Alpha 084 的公式極其複雜,用到了 20 次方的冪運算,看起來充滿了高等數學的智慧。
但我打開持倉明細一看,心都涼了。
❌ Alpha 084 的持倉揭密
| 股票代號 | 持倉比重 | 備註 |
|---|---|---|
| 2330 (台積電) | 44.12% | 合理,權值王 |
| TWII (加權指數) | 23.12% | 抓到了!髒數據 |
原來,我的資料庫裡混進了 TWII (大盤指數) 的數據。因為指數的成交量是全市場加總,數值極大,導致 Alpha 084 的公式誤判它是「極端異常股」而重押。
📉 第二章:全軍覆沒?那滿江紅的 -100%
修正資料後,我重新跑了一次全掃描。結果報表一出來,我差點把電腦關了。績效排行榜上一片慘綠,排名前幾名的因子,總報酬率竟然都是 -100.0%。
難道華爾街的智慧在台股完全失效?不,魔鬼藏在細節裡。
| Alpha 代號 | 總報酬率 | 夏普比率 | 狀況診斷 |
|---|---|---|---|
| Alpha 002 | -100% | 2.55 | 💎 隱藏強者 |
| Alpha 040 | -100% | 2.23 | 💎 隱藏強者 |
| Alpha 060 | -12% | 1.1 | 普通? |
這是回測引擎的結算機制所致。部分 Alpha 在回測結束日,因投組結算模組未將「未平倉部位」強制轉為最終市值(Mark-to-Market),導致帳面價值暫時顯示為零。
經查核,這些策略在回測期間皆有持續持倉與正向波動。因此,本文評估績效時,以 夏普比率 (Sharpe Ratio) 與 權益曲線 (Equity Curve) 的過程行為為主,排除單點終值的顯示誤差。
真相是: 那些顯示 -100% 的策略,其實在過程中表現優異。
🏆 第三章:唯一的生存者——Alpha 060
在剔除所有雜訊、修復所有 Bug 後,真正的冠軍終於浮出水面。不是最複雜的 Alpha 084,也不是排名前茅但交易次數過少的 Alpha 002。
真正的實戰冠軍是:Alpha 060。
📝 Alpha 060 的邏輯
它的公式非常簡單,簡單到像是一句廢話:
翻譯成白話文就是:「這檔股票今天收盤,是不是收在最高點?」
- 收在最高 = 強勢 = 買進。
- 收在最低 = 弱勢 = 賣出。
這就是標準的 「動能策略 (Momentum)」,也就是散戶最愛講的 「強者恆強」。
📊 Alpha 060 (2014-2026) 最終成績單
| 總報酬率 | +253.01% (本金翻 3.5 倍) |
| 夏普比率 | 0.72 (長期表現穩健) |
| 最大回撤 | -35.90% (遇到熊市會痛,但沒死) |
| 資金利用率 | 100% (資金效率極高) |

Alt text: Alpha 060 momentum strategy equity curve compared with Taiwan 0050 ETF from 2014 to 2026 backtest
🕵️♂️ 第四章:它到底買了什麼?
為了降低 過度擬合(Overfitting) 的疑慮,我們檢視其在樣本末期(2025/12/31)的實際持倉結構,確認其行為仍符合原始動能邏輯。
🏆 Alpha 060 前五大持股:
- 6770 力積電 (24.2%):晶圓代工轉型題材。
- 8021 尖點 (16.9%):PCB 鑽針,AI 伺服器供應鏈。
- 2449 京元電 (15.2%):AI 晶片封測龍頭。
- 2337 旺宏 (13.0%):記憶體循環股。
- 6805 富世達 (8.9%):摺疊機軸承概念。
發現了嗎? 全都是電子股,而且全都是當下的熱門題材股。Alpha 060 不看本益比,不看財報,它只看 「資金流向」。主力敢拉到漲停板收盤,Alpha 060 就敢上車。
⚔️ 終極對決:Alpha 060 vs 0050
最後,我們要把這個策略跟台股的大魔王——0050 (元大台灣50) 進行對比。
| 比較項目 | 0050 (大盤) | Alpha 060 (動能策略) | 勝負 |
|---|---|---|---|
| 12年總報酬 | 約 +450% | +253% | 0050 完勝 ❌ |
| 策略邏輯 | 重押台積電 (佔50%+) | 分散買 10 檔強勢股 | - |
| 主要風險 | 台積電單一個股風險 | 電子業景氣循環風險 | - |
| 適合盤勢 | 權值股拉抬 (拉積盤) | 中小型飆股噴發 (百花齊放) | - |
⚠️ 註:本比較未進行風險等權調整(如相同最大回撤或波動度),僅為實際資金成長結果之直觀對比。
💡 結論:為什麼輸給 0050?
不是策略爛,而是 台積電太強了。在台股,任何「分散投資」的模型,都很難打敗「重押台積電」的 0050。這是台股特殊的市場結構。
但 Alpha 060 的價值在於 「互補」。如果你只買 0050,你的命運綁在台積電一家公司手上。如果你搭配 Alpha 060,你是綁在 「全市場最強的 10 支股票」 身上。
❓ 常見問題(FAQ)
Q:Alpha 060 適合每天交易嗎?
A:不適合。本文使用的是「月調整」設定,目的是降低頻繁交易的手續費成本,並捕捉中期動能。
Q:這個策略可以套用到美股嗎?
A:理論上可以,但 Alpha 060 在台股有效,與台股的市場結構(電子股集中、題材輪動快、散戶追價意願高)高度相關,移植到其他市場需重新驗證。
Q:為什麼不直接 All-in 0050?
A:0050 長期績效優秀,但風險高度集中在台積電。Alpha 060 的定位是 「衛星策略」,用來補足盤整期的動能機會。
Q:這算是 Data Snooping(資料探勘偏誤)嗎?
A:本文確實屬於多因子掃描研究,因此結果不應被視為單一因子的「保證有效性」。本研究的價值在於比較不同因子類型在台股的相對表現(如動能優於反轉),而非宣稱 Alpha 060 為唯一正解。
🎁 結語
這場 12 年的實驗告訴我們三個真理:
- 大道至簡:複雜的 20 次方數學公式是雜訊,簡單的收盤價位置才是訊號。
- 資料清洗:一個
TWII代號就能毀掉整個回測。 - 順勢而為:在台股,「追高」(動能策略)長期來看比「抄底」(反轉策略)更容易存活。
(本文回測數據僅供研究參考,不代表未來績效,投資前請審慎評估)
📎 附錄:視覺化程式碼
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
# 1. 績效比較圖 (Equity Curve)
def plot_equity_curve(pf, benchmark):
plt.figure(figsize=(12, 6))
# 策略淨值歸一化
equity = pf.value()
equity = equity / equity.iloc[0] * 100
# 0050 淨值歸一化
bench = benchmark / benchmark.iloc[0] * 100
plt.plot(equity.index, equity.values, label='Alpha 060 (Momentum)', color='#ff7f0e', linewidth=2)
plt.plot(bench.index, bench.values, label='0050 (Benchmark)', color='gray', linestyle='--', alpha=0.7)
plt.title('Performance Comparison: Alpha 060 vs 0050 (2014-2026)', fontsize=14)
plt.xlabel('Year')
plt.ylabel('Normalized Value (Base=100)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()