Share Notes

chundev

View the Project on GitHub latteouka/share-notes

Combined Backtest 事件驅動的回撤計算盲點

日期:2026-04-20 情境:多策略共用資金池的聯合回測


TL;DR

把期貨策略和 DCA 策略丟進同一個 backtest engine 時,常見的 equity 曲線實作是事件驅動(trade 結算時才更新資金)而非 mark-to-market(每日重新評價未實現損益)。這個簡化會產生反直覺的結果:加入 DCA 之後「最大回撤」看起來變小,即使 DCA 的進出完全不影響期貨的決策。原因是 DCA 的獲利事件會在 equity 曲線上製造新的高點或填補低谷,使得 peak-to-trough 的 % 計算被改寫。這不是 bug,是設計選擇,但需要知道它低估了 DCA 持倉期間的實際心理壓力。


背景:事件驅動 vs Mark-to-Market

多策略共用資金池的典型 equity 追蹤有兩種實作:

1. 事件驅動(event-driven)

資金 += trade.profit(trade 結算那一刻才更新)

每筆交易結束時一次性把淨損益加到資金上。持倉期間 equity 曲線是「平的」 — 因為沒有事件觸發更新。

2. Mark-to-market(每日評價)

每個交易日:
  未實現損益 = Σ(open positions 當日標的價格 - 成本)
  今日 equity = 現金 + 未實現損益

持倉期間每天跟著標的價格波動,equity 曲線是連續的。

差異點:

項目 事件驅動 Mark-to-Market
計算複雜度 低(只看 trade) 高(每日重算所有 open position)
資料需求 只需 entry/exit 每日收盤價
回撤是否反映持倉浮動 ❌ 否 ✅ 是
期貨的 margin call 是否能模擬 ❌ 否 ✅ 是

大多數量化框架為了效能和簡潔,預設使用事件驅動


觀察:為什麼加入 DCA 回撤變小

實測某個雙策略系統:純期貨(基準)vs 期貨 + DCA(共用資金池):

配置 年化 最大回撤 報酬/回撤比
純期貨(基準) 121.8% 22.1% 5.51
期貨 + DCA(動態 5%) 125.9% 19.4% 6.50

年化變高合理(多一條策略收益),但回撤變小不合理 — DCA 的進出完全不改變期貨的決策,為什麼會讓期貨的回撤減輕?

答案:DCA 的獲利事件穿插進事件序列,改寫了 equity 曲線的形狀


三個機制

機制 1:DCA 獲利墊高「後續回撤的基準點」

DCA 週期結算(+X 萬)發生在期貨某次大虧之前:

時序:
  2023-01-09  DCA #1 關倉 +61 萬       ← 墊高 equity
  ...
  2024-XX     期貨大虧

回撤公式:drawdown = (peak - trough) / peak

情境 peak trough 回撤
純期貨 900 萬 700 萬 22.2%
含 DCA 961 萬(+61 DCA) 761 萬(同樣絕對跌幅) 20.8%

分母變大、分子不變,% 自然變小。

機制 2:DCA 獲利事件直接填補回撤谷底

更極端的情況:DCA 關倉恰好發生在回撤期間或谷底附近

期貨連虧 → capital 從 900 → 750(回撤 16.7%)
DCA 關倉 +61 萬 → capital 跳到 811(回撤只剩 9.9%)
...後續期貨回升...

最大回撤根本沒有機會達到純期貨的深度,因為 DCA 的獲利事件在 equity 曲線上直接拉回一截。

機制 3:事件時序重排改變曲線形狀

純期貨和含 DCA 的事件序列不同:

純期貨:   ↗↘↗↘↘↘↗↗↘↗↗
含 DCA:  ↗↘↗↘⬆↘↘↗↗⬆↗↗   ← 多兩個向上衝擊

最大回撤的日期可能完全改變,落在不同的 peak-trough 對上。純期貨的最大回撤時點可能不是含 DCA 的最大回撤時點 — 兩張圖量到的是不同東西。


盲點:DCA 持倉期間的真實波動被忽略

事件驅動的最大簡化:DCA 持倉期間(可能長達 100+ 天)的標的價格波動完全沒被記入 equity 曲線

例子

DCA 週期 #2:2022-07-01 ~ 2023-01-09(133 天)

事件驅動看到:

實際情況:

事件驅動的 equity 曲線看不到 -73 萬那個谷底。但實盤操作者會看到帳面 -73 萬、承受 -25% 未實現虧損的心理壓力。

實務影響

  1. 最大回撤被低估:事件驅動的 19.4% 不是「真實最壞情況」,真實可能接近 22-25%
  2. Sharpe Ratio 被高估:Sharpe 分母是波動度,事件驅動下持倉期間「沒有波動」,分母小、分子看起來大
  3. 報酬/回撤比被高估:結合前兩點,這個比值對風險的反映過度樂觀
  4. Margin Call 風險無法模擬:期貨持倉期間的盤中大跌無法觸發 margin call check

為什麼大多數 backtest 框架還是用事件驅動

即使有上述盲點,業界仍廣泛使用事件驅動,原因:

1. 資料需求低

Mark-to-market 需要每個持倉標的每個交易日的收盤價(甚至盤中 tick)。多策略 + 多標的組合下,這個資料量指數級增長。

2. 計算成本

每筆交易 O(1) 更新 vs 每天 O(n positions) 重算。長期回測(20 年每日)差異顯著。

3. 對「純粹交易 P&L」的評估足夠

如果只想回答「策略的平均 P&L、勝率、賺賠比」,事件驅動就夠了。mark-to-market 的優勢在「心理壓力、風控觸發」這類需要盤中狀態的問題。

4. 未實現損益的定義模糊

持倉中的「損益」要用收盤價、開盤價、還是盤中 VWAP?不同選擇影響 Sharpe 顯著但都不完美。事件驅動避開這個選擇。


改進方案(按工作量排序)

方案 A:報表中明確標註「事件驅動」

工作量最低。在輸出回撤 / Sharpe 時加註:

最大回撤:19.4%(事件驅動,未計入 DCA 持倉浮動)

至少不誤導讀者。

方案 B:手動補 DCA 持倉期的近似浮動

取 DCA 週期內標的的最大跌幅,在 equity 曲線對應時點手動加一個「浮動谷底」事件。

for cycle in dca_cycles:
    max_drawdown_during_hold = compute_max_intraday_drop(cycle)
    synthetic_equity_dip = cycle.invested_amount * max_drawdown_during_hold
    # 在 equity 曲線插入一個虛擬谷底事件

近似精度中等,實作量小。

方案 C:完整 mark-to-market 重寫

每個交易日重新評價所有 open position。需要:

  1. 每日收盤價(期貨 + DCA 標的)
  2. 持倉狀態追蹤
  3. 重寫 equity 建構邏輯

大工程,但最精確。適合策略要對外公開績效用於機構評估時。


這類 bug 的一般性

事件驅動盲點不只出現在 DCA + 期貨:任何多策略 + 不同持倉時間尺度的組合都會踩到。

常見場景:

共通點:不同策略的「事件頻率」差太多時,事件驅動的偏差會被放大。


學到的事


參考資料