本文目錄
免責聲明:本文為程式教學與研究用途,所有程式碼與數字僅為模擬範例,非真實交易結果。回測績效不代表未來表現,任何策略都可能虧損,請勿將本文內容視為個股投資建議。
前言:你的策略是真的賺錢,還是運氣好?
每次看到網路上那種「勝率90%神奇策略」,你有沒有心動想直接下單?先別急。沒有經過回測系統驗證的策略,就像沒試駕就買車,風險完全未知。這篇文章會帶你從零打造一套 Python 台股回測引擎,並整理常見的踩坑陷阱,讓你在投入真金白銀前,先看清楚策略的風險長什麼樣子。
本文所有績效數字都是假設範例,用來示範計算邏輯,不是任何人的實測結果。回測系統能幫你驗證邏輯,但它沒辦法保證未來獲利。
什麼是回測系統?台股投資人都該懂的基本概念
回測系統的核心邏輯很簡單:把你的交易策略套用在過去的歷史資料上,模擬如果當時真的照著規則買賣,結果會賺還是賠。聽起來簡單,但細節魔鬼藏在資料品質、交易成本、滑價假設裡。
假設範例(以下數字為模擬示範,非真實交易結果):一套「20日均線黃金交叉買進、死亡交叉賣出」的策略,套用在某檔個股 2018–2023 年的資料上,可能出現這樣的落差:
| 情境 | 假設年化報酬率 |
|---|---|
| 不計手續費、不計滑價 | 18.3% |
| 計入手續費 0.1425%+證交稅 0.3% | 11.7% |
| 再加入 1 個 tick 滑價模擬 | 9.2% |
同一套策略,光是有沒有把交易成本算進去,結果就可能差將近一倍。這也是為什麼很多新手看網路分享的策略績效總是「看起來很美」,一實戰就翻車——因為對方的回測系統根本沒還原真實交易環境。
一套完整的回測系統至少要包含四個模組:資料抓取、訊號產生、模擬交易(含滑價與手續費)、績效統計(勝率、最大回撤、夏普比率)。缺一不可。
Python 回測系統推薦:三大框架比較
如果你打算自己動手寫程式,市面上開源的 Python 回測框架選項其實不多,以下是常見的三套與它們的特性:
1. Backtrader
老牌框架,社群資源豐富,文件也算完整。缺點是速度偏慢,適合做精細的單一策略回測與資金曲線分析。官方文件可參考 Backtrader Docs。
2. vectorbt
向量化運算,速度快,很適合拿來做大量參數掃描。學習曲線比較陡,語法偏 NumPy 風格,新手容易卡關。官方文件可參考 vectorbt Docs。
3. Zipline-reloaded
Quantopian 停運後由社群接手維護的版本,架構嚴謹,但對台股資料的相容性要自己額外處理,前期設定比較繁瑣。
一個常見的工作流是:用 vectorbt 做初步大量篩選,找出有潛力的參數區間後,再用 Backtrader 做精細的單一策略回測,確認滑價與資金曲線細節。
如果你不想寫程式,或想先用視覺化圖表驗證想法,可以先用 TradingView 的策略測試器(Strategy Tester)練手感,它內建的 Pine Script 語法比純 Python 簡單,而且可以直接在 K 線圖上看到每一筆進出場點位,對新手理解「訊號到底怎麼發生」很有幫助。等你熟悉邏輯後,再轉去寫 Python 做更嚴謹的量化驗證也不遲。
免費回測系統比較:從入門到進階怎麼選
很多人第一個問題就是:「有沒有免費回測系統可以用?」答案是有,但免費工具的限制也要先搞清楚:
| 工具名稱 | 費用 | 適合對象 | 台股資料支援 | 學習難度 |
|---|---|---|---|---|
| TradingView 免費版 | 免費(付費版更完整) | 新手、視覺化驗證 | 支援(延遲報價) | 低 |
| Backtrader (Python) | 完全免費 | 中階,自建策略 | 需自行接資料源 | 中高 |
| vectorbt | 免費(開源版) | 進階,大量參數優化 | 需自行接資料源 | 高 |
| 券商模擬帳戶回測工具 | 免費 | 入門,簡易策略測試 | 支援但功能有限 | 低 |
| Excel + VBA 手刻 | 免費 | 極簡策略、教學用途 | 需手動整理資料 | 中 |
建議是:如果你只是想快速驗證一個想法「這個訊號到底有沒有用」,先上 TradingView 用免費版拉圖測試,幾分鐘就能看到結果;如果你要做嚴謹的多股票、多年度、含交易成本的完整驗證,那還是得回到 Python 生態系,雖然前期投入時間多,但長期來說彈性跟客製化程度是券商工具比不上的。
台股回測系統教學:5 步驟打造你的第一個驗證流程
這裡直接給你一套可執行的實作流程,照著做,大概花一個週末就能跑出第一版結果。
步驟 1:資料下載(用 yfinance 抓台股)
台股在 Yahoo Finance 的代碼格式是「數字代號+.TW」,例如台積電是 2330.TW。以下程式碼可直接執行:
安裝:pip install yfinance pandas numpy
import yfinance as yf
import pandas as pd
抓台積電 2330 的日線資料
df = yf.download("2330.TW", start="2018-01-01″, end="2023-12-31″, auto_adjust=False)
yfinance 新版回傳欄位可能是 MultiIndex,先攤平
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
df = df[["Open", "High", "Low", "Close", "Volume"]].dropna()
print(df.head())
print("資料筆數:", len(df))
注意:
auto_adjust=False會保留未還原的原始價格,另外提供Adj Close欄位。若要做長期策略回測,建議使用還原股價(auto_adjust=True)以避免除權息造成的價格跳空失真。
步驟 2:資料清洗
處理除權息還原、缺值填補、假日對齊。這步驟最容易被忽略,但直接影響回測準確度。常見的錯誤是沒處理除權息,導致「存股策略」的績效被虛增(因為除息當天股價下跌被誤判為虧損,或反過來被平滑掉)。使用 auto_adjust=True 可以讓 yfinance 直接回傳還原股價:
使用還原股價,避免除權息造成的假訊號
df_adj = yf.download("2330.TW", start="2018-01-01″, end="2023-12-31″, auto_adjust=True)
if isinstance(df_adj.columns, pd.MultiIndex):
df_adj.columns = df_adj.columns.get_level_values(0)
df_adj = df_adj[["Open", "High", "Low", "Close", "Volume"]].dropna()
步驟 3:訊號邏輯撰寫(均線交叉範例)
用 Pandas 寫出進出場條件。以下為 20 日均線黃金交叉買進、死亡交叉賣出的範例:
計算均線
df_adj["MA20"] = df_adj["Close"].rolling(window=20).mean()
訊號:收盤價站上 MA20 為進場(1),跌破為出場(0)
shift(1) 是關鍵:用「前一日的訊號」決定「今日持倉」,避免未來函數
df_adj["signal"] = (df_adj["Close"] > df_adj["MA20"]).astype(int)
df_adj["position"] = df_adj["signal"].shift(1).fillna(0)
print(df_adj[["Close", "MA20", "signal", "position"]].tail())
步驟 4:模擬交易引擎(含手續費與滑價)
台股實際交易成本如下(可於台灣證交所與各券商公開資訊查證):
- 買賣手續費:成交金額 × 0.1425%(多數券商有折扣,此處以公定價示範)
- 賣出證交稅:成交金額 × 0.3%
- 滑價:假設每次進出場各吃 0.1% 的價格緩衝
import numpy as np
FEE_RATE = 0.001425 # 手續費 0.1425%
TAX_RATE = 0.003 # 證交稅 0.3%(僅賣出)
SLIPPAGE = 0.001 # 假設滑價 0.1%
單期報酬:持倉 * 當日漲跌幅
df_adj["ret"] = df_adj["Close"].pct_change().fillna(0)
df_adj["strategy_ret"] = df_adj["position"] * df_adj["ret"]
進出場成本:position 由 0 變 1 為買進、由 1 變 0 為賣出
df_adj["trade"] = df_adj["position"].diff().abs().fillna(0)
df_adj["cost"] = df_adj["trade"] * (FEE_RATE + SLIPPAGE) # 買進成本
賣出額外課證交稅
df_adj.loc[df_adj["position"].diff() == -1, "cost"] += TAX_RATE
df_adj["net_ret"] = df_adj["strategy_ret"] – df_adj["cost"]
累積淨值
df_adj["equity"] = (1 + df_adj["net_ret"]).cumprod()
print(df_adj["equity"].iloc[-1])
步驟 5:績效分析
輸出年化報酬率、最大回撤(MDD)、夏普比率、勝率,缺一不可。單看報酬率會讓你誤判風險。
年化報酬率(以 252 個交易日計)
n_days = len(df_adj)
total_return = df_adj["equity"].iloc[-1]
annual_return = total_return ** (252 / n_days) – 1
最大回撤
rolling_max = df_adj["equity"].cummax()
drawdown = df_adj["equity"] / rolling_max – 1
max_drawdown = drawdown.min()
夏普比率(無風險利率假設為 0)
sharpe = (df_adj["net_ret"].mean() / df_adj["net_ret"].std()) * np.sqrt(252)
print(f"年化報酬率:{annual_return:.2%}")
print(f"最大回撤:{max_drawdown:.2%}")
print(f"夏普比率:{sharpe:.2f}")
以上數字僅為程式邏輯示範,不是任何真實策略的績效。實際跑出來的結果會因資料區間、參數、成本假設而完全不同。
這五步做完,你至少能得到一個「不會自欺欺人」的初版策略績效報告。
避雷防坑指南:回測系統如何避免過度擬合
回測系統最常見的三大致命錯誤如下:
雷區 1:過度優化參數(Overfitting)
常見錯誤:為了找「最佳」均線天數,把 5 日到 60 日全部跑過一遍,挑出歷史績效最好的那一組參數。這種做法等於是先知道答案再回頭找規則,樣本內績效再漂亮都沒有意義。
防範建議:一定要切分訓練集與測試集(例如 70% 資料調參、30% 資料驗證),甚至做滾動式的 Walk-Forward 測試,才能知道策略是不是真的穩健,還是純粹背答案。
簡單的訓練/測試切分示範
split_idx = int(len(df_adj) * 0.7)
train = df_adj.iloc[:split_idx]
test = df_adj.iloc[split_idx:]
print(f"訓練集:{train.index[0].date()} ~ {train.index[-1].date()}")
print(f"測試集:{test.index[0].date()} ~ {test.index[-1].date()}")
雷區 2:忽略生存者偏差
常見錯誤:拿「現在還在市場上」的股票清單去做回測,例如只測長期強勢股,卻沒把已經下市或長期弱勢的個股納入樣本,這樣績效會嚴重失真。
防範建議:如果要做全市場策略回測,資料庫一定要包含已下市、已下櫃的個股歷史資料,不然你的策略在紙上完美,實戰卻慘不忍睹。
雷區 3:未來函數(Look-ahead Bias)
常見錯誤:訊號邏輯用到「當日收盤價」去判斷「當日該不該買」,但實際交易根本不可能在收盤前就知道收盤價是多少。這種寫法會讓回測績效無限美化,卻完全無法在真實市場複製。
防範建議:所有訊號判斷都必須基於「已經發生、已經確定」的資料。寫程式時養成習慣,用前一根 K 棒的資料去產生訊號,而不是用當前這根還在跳動的資料。上面的 df["position"] = df["signal"].shift(1) 就是標準做法。
回測系統資料來源整理:台股歷史資料哪裡抓
資料品質決定回測品質,這句話一點都不誇張。整理幾個常用的台股資料來源:
- 台灣證券交易所開放資料平台:官方免費資料,涵蓋個股、大盤、法人籌碼,但格式需要自行整理清洗。官方網址:https://openapi.twse.com.tw/
- FinMind:台灣團隊開發的開源金融資料 API,涵蓋財報、籌碼、技術指標,對新手非常友善,有免費額度。官方文件:https://finmind.github.io/
- yfinance(Python 套件):抓取速度快,適合快速測試,但除權息還原偶有誤差,建議搭配官方資料交叉驗證。官方文件:https://ranaroussi.github.io/yfinance/
- 券商 API(如永豐金 Shioaji、群益):適合已經在做實單交易、需要即時資料串接的進階使用者。
- TradingView 資料庫:內建全球市場資料,雖然免費版有延遲,但視覺化驗證非常方便,適合前期快速篩選想法。
一個常見的做法是「FinMind 抓基本面+籌碼、yfinance 抓價量、證交所官方資料做最終校對」,三方交叉比對,才能確保回測系統吃到的資料是乾淨可信的。
常見問題 FAQ
Q1:回測系統的績效跟實際交易差多少才算合理?
一般來說,如果回測有嚴謹考慮滑價、手續費、且做過樣本外測試,實際交易績效通常會比回測數字略差,差距因策略週轉率與市場流動性而異。如果差距大到不合理,代表你的回測系統可能存在未來函數或過度擬合問題,需要重新檢查邏輯。再次提醒:回測績效不代表未來表現。
Q2:新手該先學 Python 寫回測,還是先用 TradingView?
建議兩者搭配。先用 TradingView 熟悉策略邏輯與視覺化驗證,成本低、上手快;等你確定策略方向後,再用 Python 建立更嚴謹的回測系統,加入完整的交易成本模擬與樣本外測試,這樣學習曲線比較平滑,不會一開始就被程式語法卡住熱情。
Q3:回測系統一定要用到多少年的歷史資料才夠可信?
台股建議至少涵蓋一次完整的多空循環,通常是 8–10 年以上的資料(例如涵蓋 2018 年貿易戰、2020 年疫情崩盤、2022 年升息熊市),這樣才能驗證策略在不同市場環境下的穩健度。只測單一多頭年份的資料,非常容易產生過度樂觀的錯誤結論。
結論:別再憑感覺交易,讓數據替你把關
一套嚴謹的回測系統,不是為了證明你的策略「一定賺錢」,而是幫你在虧錢之前,先看清楚風險在哪裡。從資料來源整理、Python 框架選擇、到避開過度擬合的陷阱,每一步都值得你花時間打磨。
如果你還沒開始,建議先用 TradingView 花一個下午練習拉圖驗證想法,感受一下「回測」到底在做什麼,再逐步進階到 Python 的完整量化流程。投資這條路,工具會一直進步,但紀律跟驗證的習慣,才是真正陪你走到最後的核心能力。
現在就動手,把上面的程式碼貼進你的 Jupyter Notebook,替你的第一個策略建立屬於自己的驗證引擎吧。
最後提醒:本文所有程式碼與數字僅供教學研究,非投資建議。回測績效不代表未來表現,實際交易請自行評估風險。