Python 期貨回測:簡單程式碼出錯的 5 個地方
純 Python 的期貨策略回測,會在想法之外的五個地方失敗:連續月、規則下判斷的那根 K 棒、成交價格、每個來回的成本,以及大型搜尋所需的時間。每一處都有簡單的修正方法;五項修正合在一起,就是腳本結果與你本來可以實際交易的結果之間的差別。
作者 Soren Huang · 發佈 · 更新於 · 編輯原則
目錄
用純 Python 回測期貨策略,會在哪裡出錯?
用 Python 回測期貨策略,會在想法之外的五個地方出錯:連續月(轉倉跳空)、規則下判斷的那根 K 棒(前視偏誤)、成交價格、每個來回的成本,以及搜尋數千種變體所需的時間。這五處修好,想法才算得到公平的檢驗。
回測是把一組固定的交易規則,放在舊價格上重播,並扣除帳戶會付出的成本,讓你讀出規則的表現。回測本身的概念簡單(詳見什麼是回測);短腳本出錯的地方,是它外圍的測試框架。本文的主張是:Python 回測與實際期貨帳戶之間的落差,多半來自這個測試框架,而不是策略想法。五個地方如下:
- 載入 K 棒。期貨合約會到期。腳本若每個合約讀一個檔案,再依日期接起來,建出的序列就含有價格斷層。
- 建立連續月。每次轉倉的斷層必須移除,轉倉日也要依測試前就寫好的規則決定。
- 計算訊號。看一根 K 棒收盤價的規則,不能在同一個收盤價上動作。這就是前視洩漏。
- 模擬成交。零手續費、零滑價、停損單依精確的觸發價成交,是多數腳本的預設值,卻不是任何帳戶的實況。
- 執行搜尋。跑一次沒問題的迴圈,會乘上你嘗試的每一個參數值;時間用完,研究也就停擺。
以下各節依序處理這五處。每一節說出失誤、它對結果造成什麼影響,並以簡單步驟給出修正。
backtrader 與 vectorbt:哪個 Python 回測函式庫適合期貨?
backtrader 適合一個有明確委託邏輯的策略,vectorbt 適合掃描大量參數值,兩者都不會替你建出經回溯調整的連續期貨合約。這是多數 Python 交易人最先接觸的兩個函式庫,運作方式卻相反。
事件驅動回測是一個迴圈:一次走訪一根 K 棒,在該 K 棒上執行策略程式碼,並讓模擬券商在之後的 K 棒成交任何委託。backtrader 就是這樣運作:你寫一個策略類別,引擎每根 K 棒呼叫它一次,預設情況下,在某根 K 棒下的市價單,會在下一根 K 棒的開盤成交。向量化回測則是一次對整欄數字運算:每根 K 棒的訊號在一次運算中算出,每根 K 棒的部位在另一次運算中算出,每根 K 棒的損益再在第三次運算中算出。vectorbt 以 pandas 與 NumPy 為基礎,用這種方式運作,並設計成可以對許多參數值並排執行同一個計算。
| 項目 | backtrader | vectorbt |
|---|---|---|
| 執行模型 | 逐根 K 棒迴圈,搭配模擬券商 | 對整段歷史做陣列運算 |
| 典型用途 | 一個策略,含委託邏輯、部位規則與多個資料源 | 參數掃描,多個變體一起計算 |
| 前視風險 | 設計上較低:在第 N 根 K 棒下的委託,會在第 N+1 根 K 棒成交,除非你關掉這項行為 | 取決於你在套用訊號前,是否把它位移一根 K 棒 |
| 轉倉與連續月 | 由你提供調整後的序列 | 由你提供調整後的序列 |
| 成本 | 手續費與滑價由你設定 | 費用與滑價由你設定 |
最後兩列對期貨最重要。兩個函式庫都接收價格序列作為輸入,不問它從哪裡來。序列有沒有轉倉跳空、涵蓋的是不是正確的交易時段、成本與合約是否相符,都是函式庫看到第一根 K 棒之前就做好的決定。比較回測軟體的六項檢查,不只適用於現成程式,也適用於你圍繞函式庫自行撰寫的程式;其中好幾項談的正是這些輸入。
向量化或事件驅動:哪一種更容易洩漏未來?
向量化程式碼比較常洩漏未來,因為除非你把訊號位移一根 K 棒,否則成交價與決策位在同一列。前視偏誤是指回測中使用了交易人在下委託那一刻不可能知道的資訊。在逐根 K 棒的迴圈裡,事件順序是寫明的:策略讀第 N 根 K 棒,下委託,券商在第 N+1 根 K 棒成交。在向量化腳本裡,這一切只有一行,而這一行容易寫錯。
典型的錯誤是這樣:你用每根 K 棒的收盤價算出訊號,例如「收盤價高於 20 根 K 棒平均」。接著你把訊號乘上同一根 K 棒的報酬。那個報酬是從前一個收盤價到這個收盤價,而訊號要到這個收盤價才知道。腳本等於在價格已經朝有利方向走完之後才買進。修正只要一次位移:第 N+1 根 K 棒的部位,等於第 N 根 K 棒的訊號。用 pandas 的說法,就是在相乘之前,把訊號欄向下位移一列。
向量化程式碼裡還藏著另外兩種洩漏。第一種是使用置中視窗的指標,使第 N 根 K 棒上的值已含有第 N+1 到 N+k 根 K 棒;帶有置中選項的滾動函式就會這樣。第二種是停損或停利,拿來對照開倉那根 K 棒本身的最高價與最低價檢查,於是出場價可能是市場在進場之前就已出現過的價格。前視偏誤與一根 K 棒檢查法介紹的一根 K 棒測試能抓出這三種:把每個輸入往後位移一根 K 棒再重跑;結果崩掉,代表它用了不該有的資訊。
Python 期貨回測要怎麼處理合約轉倉?
自己建立連續月序列:寫明轉倉規則,並在每次轉倉時調整價格。只依日期接合合約,會在轉倉日產生一個斷層,策略會把它當成實際的價格變動來交易。轉倉是從即將到期的期貨合約換到下一個合約;連續月則是用許多合約縫合成的單一價格序列,讓規則能跨越多年運行而沒有跳空。
斷層的存在,是因為同一種期貨的兩個合約,在同一天以不同價格交易。這個價差反映的是直到各自到期為止的資金成本、股利或儲存成本。如果 3 月合約收在某個價格,6 月合約同一天交易價較高,那麼一個在隔夜從 3 月切換到 6 月的序列,就會出現沒有任何交易人經歷過的跳空。突破規則會在這個跳空上觸發。跨越轉倉持有的部位,會把這個跳空記為損益。十年的每季轉倉下,四十個這樣的斷層,可能占去日 K 棒結果的一大部分。
假設性範例
假設 6 月 NQ 合約在轉倉日比 3 月合約高出 40 個指數點。未調整的接合序列會出現 40 點的斷層。以公布的每點價值,每個指數點 $20,跨越那一天持有的多單,每口會多出 $800 的獲利,而任何帳戶都沒有收到;空單則每口多出 $800 的虧損,也沒有任何帳戶付出。一條在十年這類斷層上測試的規則,結果裡有四十筆這樣的幽靈交易。
這個程序有三個部分,三者都在測試前決定:
- 寫下轉倉規則。常見的規則有:到期前固定交易日數,或是下一個合約的成交量或未平倉量首次高於目前合約的那一天。選一種,每次轉倉都沿用。
- 調整價格。回溯調整是量出兩個合約在轉倉日的差額,把更早的每一根 K 棒都平移這個差額,使序列沒有斷層。最近的合約保留真實價格。比例調整是用乘的而不是加的,適合價格變動幅度大的長歷史。
- 把原始序列與調整後序列並排保存。依賴價格水準的規則,例如固定跳動點的停損或保證金檢查,訊號用調整後序列,凡是與真實合約掛鉤的項目則用原始序列。
回溯調整後的價格在遙遠的歷史中可能變成負數,也和報價畫面對不上;這是預期中的。轉倉規則、跳動點價值,以及各種用途該用哪個合約,更完整的說明見期貨回測的合約規格與轉倉。
簡單程式碼會在哪些成本與成交假設上出錯?
加入實際的來回成本與下一根 K 棒成交,是讓純腳本最接近帳戶實況的單一改動。多數腳本一開始是零手續費、零滑價、在訊號那根 K 棒的收盤價成交,停損單則依精確的觸發價成交。每一項預設值對單筆交易影響不大,放到整段歷史上影響就大,合在一起,對交易頻繁的策略有利,因為它們交易筆數最多,每筆賺取的幅度最小。
| 設定 | 純腳本的預設值 | 反映帳戶實況的設定 |
|---|---|---|
| 手續費 | 零 | 券商手續費,加上交易所與結算費用,每口、每個來回 |
| 滑價 | 零 | 流動性高的合約,使用市價單或停損單時,每邊至少一個跳動點;開盤與消息前後更多 |
| 成交的 K 棒 | 與決策同一根 K 棒 | 決策後的下一根 K 棒 |
| 停損單 | 依觸發價成交 | 依觸發價或更差的價格成交 |
| 交易時段 | 檔案裡的每一根 K 棒 | 你實際會交易的交易時段;若計畫如此,收盤時就平倉 |
以 NQ 來說,交易所規格給出的合約規模是指數乘以 $20,最小跳動點為 0.25 個指數點,價值 $5.00,所以每筆交易兩邊各一個跳動點的滑價,每個來回約 $10。交易所可能調整這些數值;請查交易所的合約頁面,或用免費的期貨合約與損益計算機讀取目前的最小跳動點與每點價值。成交是回測只能假設的一項輸入,所以當沖模擬器與回測各教你什麼談的多半是委託實際在哪裡成交。
本文背後的這次回測,顯示每筆交易都已帶著這些設定時,報表長什麼樣子。AlphaPrime(Valynox)測試 NQ 日 K 棒,期間為 2019-01-02 至 2025-06-30,多空皆做,一口,每筆交易收取手續費 $20 與滑價 $10,並採下一根 K 棒成交。規則是 Fair Value Gap 的進場與出場,不加停損、停利或濾網:這是以標準指標組成的替代規則,在此用來說明成本的處理,並非本文的主題。搜尋嘗試了 1,024 種變體,依報酬相對回撤留下這一個;事後挑出被留下的那個,會讓它看起來更好,所以這些數字描述的是過去的一次回測,不代表讀者能從這條規則期待什麼。
來源:AlphaPrime 產品引擎(CPU),2026-10-31 執行。NQ 日線,2019-01-02 至 2025-06-30,多空都做,1 口,每筆手續費 $20、滑價 $10。
| 數字 | 結果 |
|---|---|
| 淨損益(已平倉交易) | $179,675 |
| 最大回撤 | $134,405 |
| 交易筆數 | 88 |
| 勝率 | 62.5% |
| 獲利因子 | 1.647 |
| 平均每筆損益 | $2,041.76 |
| 在市場中的時間 | 91.48% |

照上面成本表的思路來讀這張表。平均每筆損益 $2,041.76,遠高於每筆收取的 $30,所以在日 K 棒上,成本不是決定這個結果的關鍵。最大回撤 $134,405 對淨損益 $179,675,才是決定有沒有人能撐住這個部位的數字;六年半 88 筆交易,對任何每筆數字來說都是小樣本。一個不收任何成本、在訊號 K 棒上成交的腳本,對同一條規則會給出另一張更好看的表。在 AlphaPrime 中,回測裡的手續費、滑價與下一根 K 棒成交,在搜尋的每一次評估內部就已套用,所以候選策略的排名在任何人讀到之前,就已扣除這些設定。
Python 能處理多大的搜尋,速度才變成限制?
參數搜尋是用乘法成長的,所以每次執行要半分鐘的逐根 K 棒迴圈,在數千種變體時就成了限制。完整網格搜尋中的回測次數,是每個參數所試值個數的乘積:
回測次數 = v₁ × v₂ × … × vₖ
這裡 k 是策略的參數個數,v₁、v₂ 以此類推,是每個參數所試值的個數。這個次數中的每一次回測,都是對 K 棒歷史完整走過一遍,包含訊號、成交與成本。多加一個參數,不是在次數上加,而是乘上去。
假設性範例
假設十年小時 K 棒的逐根 K 棒回測,每跑一次要 30 秒。三個參數,各有 10、8 與 4 個值,共 320 次執行,約 2.7 小時。第四個參數有 10 個值,共 3,200 次,約 27 小時。第五個參數有 10 個值,共 32,000 次,約 11 天的無人看管運算。本文背後的這次回測,在日 K 棒上嘗試了一條單一規則策略的 1,024 種變體;同樣的網格換成小時 K 棒,每次執行的 K 棒數約是 24 倍。
K 棒週期也是同樣的乘法。高頻資料回測用一分鐘或逐筆 K 棒,每次執行放進的 K 棒數,約是小時資料的六十倍,所以在小時 K 棒上要跑一天的網格,在一分鐘 K 棒上要跑數週。向量化程式碼解決了一部分:它在一次陣列運算中跑許多變體,這正是 vectorbt 的設計目的。但它也把前視偏誤那一節的位移問題帶了回來,所以一根 K 棒檢查要放進每一次向量化掃描。不論用哪種風格,所需時間取決於做運算的處理器或顯示卡,以及 K 棒資料的長度,因此在寫下迴圈的第一行之前,就該知道變體的數量。
搜尋的規模也改變了讀取勝出策略的方式。Bailey、Borwein、López de Prado 與 Zhu (2014) 顯示,許多變體中的最佳結果,會被挑選這個動作本身虛增,而且虛增的幅度隨嘗試的變體數增加。Harvey 與 Liu (2015) 建議依背後的測試次數對結果打折。因此上例中的五參數網格,變體數是三參數網格的一百倍,需要的是更強的樣本外檢驗,而不是更弱的。如果撰寫與維護這套測試框架不是你想花時間的地方,期貨免費回測軟體中的選項涵蓋同樣的五個步驟,只是取捨不同。
下一次搜尋之前,先做一件事:寫下每個參數與你打算嘗試的值的個數,把它們相乘,開著成本與下一根 K 棒成交計時跑一次,再把兩者相乘。如果答案要用天來計,在開始之前就先縮小網格,或改變測試框架的風格。
常見問題
期貨交易人該從哪個 Python 回測函式庫開始?
依工作而不是依名氣來選。backtrader 這類逐根 K 棒的函式庫,適合一個有明確委託規則、停損與多個資料源的策略,它預設在下一根 K 棒成交委託,去掉了一種常見的洩漏。vectorbt 這類以陣列為基礎的函式庫,適合掃描許多參數值。兩種情況下,連續月、交易時段與成本設定,都是函式庫看到資料之前你要做的工作。
backtrader 與 vectorbt 會替你處理期貨轉倉嗎?
兩者都不會從原始合約檔建出經回溯調整的連續序列。你要自己決定轉倉規則,量出每個轉倉日兩個合約的價差,把較早的合約平移這個差額,再把完成的序列交給函式庫。與真實價格水準掛鉤的項目,例如固定跳動點的停損,則另外保留原始合約序列。
為什麼我的 Python 回測和實單差這麼多?
通常的原因出在測試框架,而不是想法:訊號在產生它的那根 K 棒成交、零手續費與零滑價、停損單依精確觸發價成交、把轉倉跳空當成實際的價格變動來交易,以及來自你從不交易的交易時段的 K 棒。把每個輸入往後位移一根 K 棒,並用實際成本重跑回測;結果下降的幅度,就是其中有多少來自測試框架。
純 Python 迴圈能處理高頻資料回測嗎?
跑一次的話可以;數年的一分鐘 K 棒,在逐根 K 棒迴圈裡可在數分鐘到數小時內完成。若是參數網格,執行次數會乘上每個參數,而從小時資料換成一分鐘資料,K 棒數約乘上六十,所以數千種變體的網格就要花上數天。向量化程式碼能縮短這段時間,前提是訊號在套用前已位移一根 K 棒。
References
- Bailey, D. H., Borwein, J., López de Prado, M., & Zhu, Q. J. (2014). Pseudo-Mathematics and Financial Charlatanism: The Effects of Backtest Overfitting on Out-of-Sample Performance. Notices of the American Mathematical Society, 61(5), 458–471.
- Harvey, C. R., & Liu, Y. (2015). Backtesting. The Journal of Portfolio Management, 42(1), 13–28.
- CME Group. E-mini Nasdaq-100 Futures contract specifications (contract size $20 × index, minimum tick 0.25 index points = $5.00). cmegroup.com/markets/equities/nasdaq/e-mini-nasdaq-100.contractSpecs.html

