從 Sharpe 2.16 到輸基準:一場 lookahead 的攔截實錄
讀者互動
已追蹤瀏覽 0 次,登入會員可按讚與收藏。
從 Sharpe 2.16 到輸基準:一場 lookahead 的攔截實錄
一句話結論
一個原本看起來「碾壓基準」的類股輪動策略,在程式內找到一個非常細微的時間錯位後,重新跑出來的真實風險調整後報酬只有 0.7247 ,反而 輸給單純的 50/50 基準(0.9359) 。原本看似驚人的成績,是 100% 來自一個未來資訊外洩的 bug。本篇文章把整個從「看似超強」→「audit 攔住」→「修正後確認無 alpha」的過程完整公開,作為研究誠實系統運作的一個具體案例。
為什麼要寫這篇
我們的研究系統每天都在跑很多策略候選,能夠通過層層檢驗、最後上架到網站策略頁面的,其實只是極少數。多數實驗的結局,是某個原本看起來很漂亮的數字,在嚴謹檢驗的某個環節被擋下來;這些「沒上架的故事」,比上架的故事更能說明研究的真實樣貌。
K562(Sector Momentum VT)就是其中一個典型案例。它一度是策略池裡風險調整後報酬最高的候選之一,差一步就要進入下一階段的上架審查。但在程式碼 audit 環節,我們找到了一行看似無害、實際上卻把未來資訊偷渡進今日訊號的程式錯誤。修正之後,整套策略的所有亮點全部消失。
這就是研究誠實原則最核心的一句話: 寧可結論變成 null(無發現),也不能讓誤上架的策略傷害用戶資金與平台信任。
資料來源
- 實驗編號 :K562("Sector Momentum VT — Deep Validation for Listing")
- 資料源 :
yfinance - 樣本期 :2005-01-03 到 2026-05-05,共 5,368 個交易日
- 資產組合 :
- SPY(基準大盤)
- 9 個 SPDR sector ETFs:XLK、XLF、XLV、XLE、XLI、XLY、XLP、XLU(其中 8 個進入 momentum 排序)
- GLD(黃金,作為 50% 配置的避險腳)
- ^VIX(用於 volatility targeting 權重計算)
- 策略結構 :50% 「動能最強的單一類股 ETF × VT 倉位」 + 50% GLD
- VT 公式 :
12 / VIX,限制在 [0, 1] 之間 - 動能訊號 :類股 60 個交易日累積報酬率,每天選累積最高的那一個
- 基準 :50% SPY + 50% GLD(同樣 daily rebalance)
故事的起點:一個看起來太漂亮的 Sharpe
K562 的前身是 K560 — 一個 2026 年 4 月初做的「類股動能 + VT」實驗。當時我們先用月度再平衡跑出風險調整後報酬約 1.57,已經比基準(≈0.94)顯著好;改成日度再平衡並挑「最強動能 1 檔」之後,數字甚至跳到 2.16 ,並通過了多個 OOS 子期間的測試。
這個成績如果是真的,意義非常重大:
- 它挑戰我們先前的研究結論 K301 Claim 1(在 SPY/QQQ 上,VIX 已經足以作為波動代理;額外加類股動能應該無法在已 VT 過的組合上再產生顯著 alpha)
- 它的訊號邏輯非常直觀:哪個類股最近 60 天最強,下一天就配它,再用 VIX 做槓桿調節
- 它的 turnover 只有 14.4%,意味著實務上交易成本可控
正因為「太漂亮」,我們特別警覺。在我們的研究筆記本裡, 一個風險調整後報酬遠高於基準的策略,應該先懷疑是不是 bug,而不是先慶祝 。這條經驗法則在 K547(同個 family 的早期變體)就已經寫進規則庫,這次再次救了我們。
隱藏的時間錯位:lookahead 是什麼
要理解這個 bug,先理解什麼叫 lookahead bias(前視偏差)。
回測的基本紀律是:「 用今天結束之前能取得的資訊,去決定明天的部位;明天的報酬才是這個決定的後果 」。任何把「明天的資訊」用進「今天的決定」的程式邏輯,都會讓回測結果嚴重失真。
最典型的形式是「同日同步」:
- 用
t日收盤後才知道的 60 天動能 → 決定t日(同一天)的權重 → 套用t日的當日報酬
這在程式碼裡看起來會像這樣:
# 錯誤寫法(lookahead)
vt_w = vt_weights[i] # i 日的 VIX 計算出來的權重
mom = sec_mom_arr[t][i] # i 日的 60 天動能(含當天收盤)
ret = vt_w * mom_winner_ret[i] # 套用 i 日當天報酬
問題在於,動能是「 到 i 日收盤為止 」算出來的,VIX 訊號也是 i 日收盤時才確定,但策略卻立刻在 i 日當天就賺到那檔類股的當日漲跌。實務上不可能 — 你必須在 i 日收盤之後才知道應該買誰,最早只能在 i+1 日進場。
K562 在 4/19 第一次跑出來的版本,就犯了這個錯。
Audit 機制:BLOCKED 4 天才換來重做
我們的研究系統有一個強制機制: 任何風險調整後報酬遠高於基準的策略,在進入下一階段(上架前的深度驗證)之前,必須先通過 lookahead audit。 4 月底,這個 audit 抓出了 K562 的時間錯位,並把這個 K 直接標成 BLOCKED 狀態,4 天內不允許任何下游 agent 引用它的數字寫文章、寫論文段落、或排上策略表。
這 4 天聽起來像是「卡關」,但其實是研究流程最有價值的部分。如果沒有這個強制 BLOCK,誤導性的 2.16 很可能已經被某篇文章引用、被某段論文 narrative 採用、甚至被排上策略候選。修正起來的成本會遠遠高於 BLOCK 期間的延遲。
修正:把 i 換成 i-1,基準與策略同步處理
修正本身非常簡短:
# 修正後(lag-fixed)
prev = i - 1
vt_w = vt_weights[prev] # 用前一日收盤後算的權重
mom = sec_mom_arr[t][prev] # 用前一日收盤的 60 天動能
ret = vt_w * mom_winner_ret[i] # 套用今日報酬
關鍵不只是「策略邏輯改 lag」,還有一個常被忽略的細節: 基準也必須用同樣的 lag 慣例重新跑 。如果只把策略的訊號往前挪一天、卻讓基準維持「同日進場」,比較就會變成不對等的對照組,反而會讓策略的劣勢被誇大。我們的修正是:
- 策略:訊號用
i-1、報酬用i - 基準(50/50 SPY/GLD):權重一樣用
i-1(雖然固定 50/50,但 rebalance 動作對齊到收盤後決定) - 同樣的 random seed、同樣的樣本期、同樣的 transaction cost 處理
只有當兩邊用同一套時序慣例,得到的差異才能被解讀為「策略 alpha」。
Lag-fix 之後:所有亮點消失
修正版的結果如下:
| 指標 | 修正前(4/19) | 修正後(5/6) | 基準 50/50 |
|---|---|---|---|
| 風險調整後報酬(日度) | 2.16 | 0.7247 | 0.9359 |
| 年化報酬 | — | 8.50% | — |
| 年化波動 | — | 11.73% | — |
| 最大回撤 | — | -21.89% | — |
| 與基準差異統計強度 | 顯著正向 | 達顯著水準(顯著性 0.236,未達) | — |
修正後的策略, 輸基準約 0.21 個風險調整單位 。我們再進一步把 K562 完整推進原本要做的 8 點上架檢核,結果同樣很清楚:
| 檢核項目 | 結果 |
|---|---|
| V1 嚴格統計檢定(Newey-West) | FAIL(顯著性 0.236,未達) |
| V2A 5-period OOS(窗 A) | FAIL(5 個子期間只贏 1 個) |
| V2B 5-period OOS(窗 B) | FAIL(同樣 1/5) |
| V3 月度再平衡是否仍贏基準 | FAIL |
| V4 動能視窗(20–252 日)穩健性 | FAIL(沒有任何視窗顯著贏) |
| V5 20bp 交易成本仍正報酬 | PASS(但變成接近 0) |
| V6 Top-1 是否優於 Top-2/3 | FAIL(Top-2/3 反而較好但仍非顯著) |
| V7 Bootstrap 5,000 次「贏基準」機率 | FAIL(贏基準機率只有 1.2%) |
| 通過 / 總數 | 1 / 8 |
V7 的數字最直觀:把整段樣本切成 5,000 次 bootstrap 重抽, 只有 1.2% 的重抽結果讓策略贏基準 。換句話說,即使你重複多年實驗,這套策略「真實上贏 50/50 基準」的機率非常低。
從 K560 到 K301:這次發現對我們研究地圖的影響
K562 修正後落地的結論,跟我們先前在 K301 Claim 1 的觀察是一致的: 在已經做了 VT(用 VIX 控制總曝險)的美股組合上,再用類股動能做選股,並沒有提供穩健的額外 alpha 。
這不是說 momentum 完全沒用 — 文獻上 Moskowitz, Ooi & Pedersen (2012) 與 Asness, Moskowitz & Pedersen (2013) 都顯示時序動能有實證基礎。但 動能與 VT 組合在一起、並且只挑單一類股集中持有 這個特定設定,在嚴格的 lag 慣例下並不會穩定打敗一個簡單的 50/50。
這也是為什麼我們的策略上架門檻不只看單一指標 — 還包含 OOS 跨期間穩健性、bootstrap 信心區間、交易成本敏感度、以及方法論審查。 單一窗口的高風險調整後報酬永遠可疑。
系統性教訓
這次事件給研究系統留下三條規則:
- 任何風險調整後報酬大幅超越基準的策略,先進 lookahead audit 才能進下游流程。 不是「順便」,是強制 gate。
- 修 lag 必須對稱。 策略改
i-1之後,基準與所有比較對象都要用同一套時序慣例。否則差異是 artifact。 - BLOCK 不是失敗,是研究流程的價值之一。 願意把一個漂亮的數字標成不可用、付出 4 天延遲,是把研究誠實這件事認真對待的表現。
每個 K 編號背後都有這樣的決策過程。系統裡已經內建層層檢核(HLZ (2016) 嚴格統計檢定、bootstrap、cross-OOS、Codex code review),就是因為任何一層放鬆都可能讓誤導性的數字流到讀者面前。
結論
K562 沒有上架。原本的 2.16 是 100% 的 lookahead 假象,修正後的真實表現是 0.7247,輸給 50/50 基準。8 個上架檢核只通過 1 個,bootstrap 贏基準機率 1.2%。
這就是 null result,也是 K562 這個實驗的最終結論。我們不會把它寫進策略表、不會用它的數字當論文 narrative,也不會在文章裡暗示「其實調一下還是有用」。 研究失敗也是研究結果 ;這個流程的價值在於:當數字真的好的時候,讀者可以更有信心相信它經得起同樣強度的檢驗。
下一步,我們會把這個案例的方法論(lookahead audit 觸發條件、對稱 lag fix、bootstrap 贏基準機率作為硬指標)寫進策略上架 SOP,讓未來的策略候選都走過同一條路。
圖表
後記(2026-05-07)
本文寫成時,K562 驗證腳本(experiments/k562/k562_k560_sector_validation.py)已完成 lookahead patch,核心數字 Sharpe 2.16→0.7247 與基準 0.9359 由此驗證 rerun 取得。
2026-05-06 lookahead audit 擴展(scripts/lookahead_audit.py 加 singular *_weight + signal-at-t shape 偵測)後發現,K560 主腳本(experiments/k560/k560_sector_rotation_vt.py)線上仍是 pre-patch 版本,與 K562 同一 lookahead 模式。同日已補上同類 lag patch(line 249-262 加 sig_idx = i-1 lag)並 rerun 驗證——9 條策略全部 Sharpe 下跌 1.4-2.9 倍,Harvey 全部 FAIL,與 K562 模式一致。
本文「100% 來自 bug」的觀察結論專指 K562/K560 momentum 旋轉這條策略;不延伸到所有 sector-momentum 類別,其他 sector momentum 策略仍需各自走 lookahead audit + 重做。防呆規則庫亦明確指向 scripts/lookahead_audit.py 加 K547/K556/K583/K570/K222/K645/K646/K560 patch family。
詳情
- audience
- research
- experiment_refs
- K562
- audience_backfill
- {"reason":"validator_371_historical_backfill","script":"scripts/backfill_audience.py","applied_at":"2026-05-26T16:21:47+00:00","article_id":"mile_91af7c48","previous_audience":"general"}
相關文章
先讀正式關聯,若無則使用標籤與主題相似度補齊