一分鐘看懂
用 Python 回測,最常用的工具是 pandas。把歷史資料讀成表格後,均線用 rolling 一行算完,部位、損益、交易成本、回撤都是整欄一起算,這叫向量化回測,十年日線一秒內跑完。最重要的一行是 shift(1):今天收盤才知道的訊號,部位要從明天才開始算,少了這一步就是在偷看未來,回測會好得不真實。本文附的程式碼實際跑過加權指數 2016~2026 年的資料,20/60 日均線策略總損益 31,569 點、最大回撤 −7,808 點。
很多人學 Python 回測,第一步是去找回測框架,結果花了一個禮拜在裝套件、看文件。一個簡單的均線策略,用 pandas 二十幾行就寫得完,而且每一步你都看得懂在算什麼。
這篇的程式碼可以直接複製使用,跑的是加權指數 2016 年 10 月 31 日到 2026 年 10 月 30 日的日線,共 2,430 個交易日。
準備資料
需要一個 CSV 檔,欄位是日期、開盤、最高、最低、收盤:
| date | open | high | low | close |
|---|---|---|---|---|
| 2026-10-29 | 47014.87 | 47015.45 | 46055.07 | 46368.33 |
| 2026-10-30 | 46139.79 | 46139.79 | 44726.26 | 45211.83 |
加權指數的歷史資料可以從證交所網站逐月下載;台指期的歷史資料,期交所也有提供下載。
完整程式碼
import pandas as pd
# 讀取加權指數日線(欄位:date, open, high, low, close)
df = pd.read_csv("twii10.csv", parse_dates=["date"], index_col="date")
# 1. 計算指標:20 日與 60 日均線
df["ma_fast"] = df["close"].rolling(20).mean()
df["ma_slow"] = df["close"].rolling(60).mean()
# 2. 產生部位:快線在慢線上方就做多(1),否則空手(0)
# 今天收盤才知道訊號,所以部位從明天才生效(shift 1 天,避免偷看未來)
df["position"] = (df["ma_fast"] > df["ma_slow"]).astype(int).shift(1).fillna(0)
# 3. 計算每天損益(點數):持有部位 × 當天漲跌
df["change"] = df["close"].diff().fillna(0)
df["pnl"] = df["position"] * df["change"]
# 4. 扣交易成本:部位每變動一次,假設來回各 2 點(稅+滑價)
df["trade"] = df["position"].diff().abs().fillna(0)
df["pnl"] -= df["trade"] * 2
# 5. 累積損益與回撤
df["equity"] = df["pnl"].cumsum()
df["drawdown"] = df["equity"] - df["equity"].cummax()
print("總損益(點):", round(df["equity"].iloc[-1]))
print("最大回撤(點):", round(df["drawdown"].min()))
print("進出次數:", int(df["trade"].sum() / 2))
print("持有天數比例:", round(df["position"].mean() * 100, 1), "%")
執行結果:
總損益(點): 31569
最大回撤(點): -7808
進出次數: 21
持有天數比例: 68.3 %
進出次數顯示 21,是因為最後一筆在 2026 年 10 月 30 日還持有中,還沒出場。
每一步在做什麼
| 步驟 | 程式 | 說明 |
|---|---|---|
| 均線 | rolling(20).mean() | 往前 20 筆收盤價的平均,前 19 天算不出來是空值 |
| 部位 | (快線 > 慢線) | 結果是 True/False,轉成 1/0 |
| 避免偷看未來 | shift(1) | 把部位往後挪一天 |
| 損益 | 部位 × 當天漲跌 | 有部位才算漲跌,空手是 0 |
| 成本 | 部位變動 × 2 點 | 進場一次、出場一次各扣一次 |
| 回撤 | 累積損益 − 歷史最高 | 從最高點掉下來多少 |
這種寫法叫向量化回測:整欄資料一次算完,不用一天一天跑迴圈,所以十年的資料一秒內就跑完。
最容易犯的錯:偷看未來
把 shift(1) 拿掉,等於假設你在今天收盤前就知道今天的收盤價,用它決定今天的部位。回測結果會明顯變好,但實單根本做不到。
| 常見的偷看未來 | 怎麼避免 |
|---|---|
| 用今天收盤算的訊號,賺今天的漲跌 | 部位一律 shift(1) |
| 用全部資料算平均或標準差,再拿來設門檻 | 只用當天以前的資料算 |
| 用今天的最高、最低價決定今天的進場 | 改成用昨天的,或用 stop 單模擬 |
| 用還沒公布的財報或數據 | 用公布日當天之後的資料 |
把 2 點成本換成台指期的金額
這個回測用的是加權指數的點數。換算成台指期,每點價值不同,金額差很多:
| 商品 | 每點價值 | 31,569 點換算 | 最大回撤 −7,808 點換算 |
|---|---|---|---|
| 微台 | 10 元 | 約 31.6 萬元 | 約 −7.8 萬元 |
| 小台 | 50 元 | 約 157.8 萬元 | 約 −39 萬元 |
| 大台 | 200 元 | 約 631 萬元 | 約 −156 萬元 |
實際交易台指期,還要考慮每月換倉的價差、夜盤,以及成交價和收盤價的差距,結果會跟用指數回測不一樣。台指期的稅和成本,可以看台指期交易成本試算。
寫完回測之後先做這三件事
- 檢查 shift:把部位那一欄印出來,確認訊號出現的隔天才有部位。
- 算更多指標:只看總損益不夠,還要看勝率、獲利因子、最大回撤和夏普值,可以看策略回測關鍵指標解析。
- 留一段資料不碰:參數只用前段資料找,後段資料留著最後驗證,做法可以看樣本外測試與滾動前進分析。
平台怎麼選可以看程式交易平台怎麼選,整體流程在期貨程式交易怎麼開始。