84,120 做多,停損設在 84,036,目標價設在 84,271。下一分鐘的 K 棒出現了:開盤 84,118,最高 84,290,最低 84,010,收盤 84,240。
兩個價位都落在這根 K 棒的範圍內。停損被觸及,目標價也被觸及,而 OHLC 這 4 個數字,對於哪個先發生完全沒有提供任何資訊。你的回測卻還是算出了結果。程式的某個迴圈裡,有一行程式碼替你做了決定——你寫它的時候,大概沒把它當成一項建模假設。
在我看來,這是短週期策略中最主要的虛假績效來源,影響甚至超過手續費和滑價,因為它看起來不像一項假設,而像是程式裡理所當然的流程。
錯誤做法 1:讓 if-else 判斷順序決定
常見寫法大概是這樣:
if bar.high >= target:
exit(target, "tp")
elif bar.low <= stop:
exit(stop, "sl")
沒有人是刻意選擇「目標價優先於停損」。只是先打了目標價的檢查,因為那是理想情況,也是你當時最先想到的。把兩個分支對調,權益曲線就變了;光是這點,就該讓你看出策略的損益有一部分取決於你在編輯器裡怎麼排列程式碼。
我用 BTCUSDT 永續合約跑了一個刻意選得很普通的均值回歸短線策略:3 個月的 1 分鐘 K 棒、4,812 筆交易,停損設在進場價的 0.10%,目標價設在 0.18%。訊號相同、手續費相同,只改變平手時的判定方式。
八分之一的交易左右了整體結果。這就是停損和目標價間距很小時的算術結果:模糊不清的交易,正是那些價格雙向移動的交易;它們往往也是最值得關注的交易,而每筆交易因為這次擲硬幣般的判定,損益都可能差上整段停損到目標價的距離。12.6% 的交易 × 0.28% 的價差,相當於每單位樣本總名目成交額的 3.5%,遠大於策略真正的優勢。
這個比例取決於 K 棒週期和停損、目標價間距的相對大小;K 棒週期越長,情況就惡化得越快。同樣的停損、目標價和訊號,改用不同週期重取樣後如下:
| K 棒週期 | 同一根 K 棒內觸及兩個價位的交易比例 | Sharpe(目標價優先) |
|---|---|---|
| 1s | 0.3% | 0.91 |
| 1m | 12.6% | 2.31 |
| 5m | 34% | 3.60 |
| 15m | 49% | 4.42 |
| 1h | 71% | 5.88 |
看看這張表實際說明了什麼。週期越長,回測結果越好。每位研究者直覺上都會認為小時 K 棒比較保守,雜訊較少,也比較不會對市場微結構過度擬合。但如果棒內判定方式總是對你有利,週期越長,就只是給你更大的空間,讓你假設自己運氣很好。用 1h K 棒時,10 筆交易就有 7 筆完全由判定慣例決定。這種回測測的不是策略,而是重複 3,400 次的兩個 `if` 陳述式誰排在前面。
錯誤做法 2:假設停損以停損價成交
假設你修正了判定順序,讓停損優先,帳上記錄剛好 0.10% 的損失,再加上 taker 手續費,你覺得這樣很嚴謹。但還有兩個地方不對。
第一,停損是觸發條件,不是成交保證。在 Binance USDⓈ-M 上,STOP_MARKET 訂單一達到觸發條件就會轉成市價單,接著吃掉訂單簿上的流動性。市場平靜時,滑價可能只有 1、2 個 tick。但真正觸發停損的那 1 分鐘——出現 40 點 K 棒實體、下方還有連環強制平倉的那一分鐘——你要跨過的那一側訂單簿,偏偏流動性最薄。我在樣本中把停損觸發和逐筆成交資料比對後,成交價中位數比觸發價差 1.4 bps,第 95 百分位則差 11 bps。若停損距離是 10 bps,尾端情況會讓你多承受原先設定風險的十分之一。
第二個問題更不明顯,而且是永續合約特有的:究竟是哪種價格觸發停損。Binance 的停損單預設以標記價格觸發;標記價格由指數價格加上平滑後的基差計算而來,不是該交易所的最新成交價。你的 OHLC 序列記錄的是最新成交價。兩者是不同的序列,而在真正觸發停損的事件發生時,兩者差距往往最大。
| 最新成交價(你的 K 線資料) | 標記價格(預設觸發依據) | |
|---|---|---|
| 來源 | 此交易所的成交紀錄 | 多個交易所的指數 + 基差 |
| 影線走勢 | 完整波動 | 大幅平滑 |
| 常見價差 | 平靜時 1–3 bps,連環強制平倉時的那 1 分鐘為 20–35 bps | |
| 對回測的影響 | 停損觸發了卻不該觸發,反之亦然 | |
因此,最新成交價資料裡出現 25 bps 的影線,就可能在回測中觸發停損,但即時標記價格其實從未接近你的觸發價 10 bps 以內。或者情況相反:指數價格變動的那天,你所在的交易所價格反應較慢。如果把 workingType 設為 CONTRACT_PRICE,至少能讓即時交易行為和你的資料一致;對研究人員來說,通常這才是合理做法,因為要誠實模擬以標記價格觸發的停損,就得在整個成交引擎中另外維護一組價格序列。
我印象最深的一次是,我們團隊有人把停利價從 0.18% 調到 0.21%,「改善」了一個策略。Sharpe 從 2.3 升到 3.1。策略沒有找到新的優勢。只是目標價移出了 1 分鐘影線分布的密集區,於是較少交易落入程式悄悄判為獲利的模糊區間。他們優化的其實是平手判定方式。
錯誤做法 3:一律假設最糟情況,並稱之為保守
直覺上的修正方式就是悲觀一點。如果兩個價位都被觸及,就判停損。好了,不再樂觀,可以上線了。
我以前也這麼做。它比另一種做法好,但仍然不對,原因有兩個。
它會淘汰其實沒問題的策略。對 12.6% 的交易採用悲觀判定,這個策略的 Sharpe 比逐筆成交資料判定後的 0.94 低了 2.1 點。如果真實數字是 0.94,而你的判定方式只報出 0.18,你就會丟掉這個想法,轉而研究更差的東西。差了 2 點 Sharpe 的保守判定稱不上保守,只是披著道德姿態的雜訊。
更糟的是,它會扭曲最佳化結果。把採用悲觀平手判定的參數掃描交給最佳化器,它就會學著避開模糊情況,因為模糊情況現在都會造成純粹的懲罰。它會往更寬的停損、較近的目標價,或是兩個價位很少同時落入同一根 K 棒的較慢週期移動,最後提供一組根據你的成交判定方式挑選、而非根據市場挑選的參數。這和偏袒獲利的做法造成同一種錯誤,只是方向相反,而且在績效報告上同樣難以察覺。
我們在進行任何分析前都會先用這條經驗法則:如果 stop_distance + target_distance 小於該 K 棒週期的區間第 75 百分位數,那麼棒內假設對損益的影響就大於訊號本身。把這兩個數字都算出來。只要 4 行程式碼,卻比任何其他單一檢查更常讓策略審查就此打住。
真正有效的做法
K 棒內的價格路徑就是資料。去取得它,或是量化你無法取得的部分。
- 用你能取得的最精細序列判定。需要分析的那些分鐘,從 Binance 下載 aggTrades 大約只有幾百筆資料,就能直接判定哪個價位先被觸及,以及掃單時的成交價格。你不需要整段回測都用 tick 資料,只需處理那些有歧義的 K 棒。我在樣本中只有 129,600 分鐘裡的 606 分鐘符合條件。下載量很小,不必建置整套基礎架構。
- 如果沒有 tick 資料,只要降低 1、2 個時間週期來判定即可。訊號採用 15m K 棒,出場則用 1s 或 1m K 棒判定。模糊比例會從 49% 降至不到 1%,剩下的誤差小到可以坦然忽略。
- 務必報告範圍。每次都用樂觀和悲觀判定各跑 1 次回測,並把 2 個 Sharpe 和逐筆資料判定的結果並列。這個差距就是棒內的不確定性,應該和 Sharpe 信賴區間一樣,列在績效報告上。當範圍是 0.2–2.3 時,範圍內沒有任何結論算得上可靠。
- 把模糊比例列為核心指標。我們會把它放在每張策略卡的最上方,和交易筆數、成交額並列。比例高於約 5%,代表受測的是出場邏輯,而非進場邏輯。
- 將觸發和成交分開建模。根據交易所實際使用的價格序列判定觸發;成交價則使用根據逐筆資料校準的滑價隨機值,而不是直接當作觸發價。
股票市場也有同樣的問題,只是形式不同。某檔股票的停損設在 62.00,隔夜跳空跌到 58.40,不可能以 62.00 成交,而會在開盤成交價以下的某個價位成交。若日 K 回測把跳空穿越停損的滑價記為 −$0.00,就會輕易告訴你停損保護改善了最大回撤。事實並非如此,只是它從未在關鍵的那些日子接受考驗。暫停交易也一樣:你的停損實際成交的地方是恢復交易時的集合競價,成交價可能連 K 棒低點都看不出來。
這些情況都不罕見。只要理解 K 棒是摘要,而停損加目標價策略,賭的就是摘要捨棄掉的事件順序。等紙上交易引擎終於拿即時成交資料跑起策略時,市場資料會清楚表明事件順序;它從來不在乎你先打了哪個 if 分支。
← 所有文章


