返回目錄
A
從數據到利潤:量化投資策略設計與實踐 - 第 5 章
第5章 回測與績效評估
發布於 2026-03-07 10:15
# 第5章 回測與績效評估
回測(Back‑Testing)是量化投資流程中最關鍵的一環,既是策略驗證的實驗室,也是風險控制的前哨。此章將系統地介紹如何構建嚴謹的回測框架、合理切分資料、計算常用風險指標、模擬交易成本與滑點,並提供實作範例與最佳實踐。整章結構如下:
1. **回測框架概覽**
2. **資料切分與時間序列完整性**
3. **風險指標詳解**
4. **交易成本與滑點模擬**
5. **績效報告與可視化**
6. **實作範例(Python + backtrader)**
7. **常見陷阱與對策**
8. **小結與練習題**
---
## 1. 回測框架概覽
| 步驟 | 內容 | 重點 |
|------|------|------|
| 1 | 資料加載 | 取得歷史行情、因子、新聞等多源資料 |
| 2 | 資料清洗 | 去除異常、補齊缺失值 |
| 3 | 特徵工程 | 計算技術指標、統計因子 |
| 4 | 交易信號 | 產生進出場訊號 |
| 5 | 風險控制 | 位置大小、止損、止盈 |
| 6 | 回測執行 | 模擬交易、計算績效 |
| 7 | 報表產生 | 風險指標、可視化、敏感度分析 |
> **核心原則**:回測必須遵循「先歷史、後前瞻」的時間序列邏輯,避免任何「未來資訊」滲透。
## 2. 資料切分與時間序列完整性
### 2.1 時間序列拆分
- **訓練集(Training)**:用於模型訓練、參數擬合。
- **驗證集(Validation)**:用於模型選擇、參數優化。
- **測試集(Test)**:最終性能評估,不能在任何階段使用。
> **注意**:切分時必須**保留時間順序**,切勿使用隨機打亂。
### 2.2 滑動窗口(Rolling)與前向測試(Walk‑Forward)
python
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_index, test_index in tscv.split(X):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
- **滑動窗口**:每次擴大訓練集,測試集保持固定大小。
- **前向測試**:每次都以最新的數據作為測試集,模擬實際投資流程。
## 3. 風險指標詳解
| 指標 | 定義 | 解讀 |
|------|------|------|
| **Sharpe Ratio** |
\[ Sharpe = \frac{E[R_p - R_f]}{\sigma_{R_p}} \] | 高於 1 代表風險調整後績效良好;<br>負值則風險超過報酬。
| **Sortino Ratio** |
\[ Sortino = \frac{E[R_p - R_f]}{\sigma_{d}} \] | 只考慮下行波動,較 Sharpe 更具投資者友好度。
| **最大回撤(Maximum Drawdown)** | 最大資金凋零幅度 | 控制承受極端下跌的心理門檻。
| **Calmar Ratio** |
\[ Calmar = \frac{年化報酬}{最大回撤} \] | 兼顧報酬與風險,常用於長期投資。
| **Omega Ratio** |
\[ \frac{\int_{T}^{\infty} [1-F_R(x)] dx}{\int_{-\infty}^{T} F_R(x) dx} \] | 分佈基礎風險評估,適合非正態收益。
### 3.1 風險度量實例
python
import pandas as pd
import numpy as np
# 假設已計算每日報酬率
ret = pd.Series([...])
rf = 0.02 / 252 # 風險自由利率(日)
sharpe = (ret.mean() - rf) / ret.std() * np.sqrt(252)
sortino = (ret.mean() - rf) / ret[ret < rf].std() * np.sqrt(252)
max_dd = (ret.cumsum() - ret.cumsum().cummax()).min()
calmar = sharpe / abs(max_dd)
## 4. 交易成本與滑點模擬
### 4.1 成本結構
| 成本類型 | 形式 | 典型數值 |
|----------|------|----------|
| 固定費用 | 每筆交易固定金額 | 0.2 元 |
| 變動費用 | 交易價值比例 | 0.02% |
| 滑點 | 市場執行價格偏差 | 0.05% |
### 4.2 模擬公式
python
# 交易量(元)
qty = position_value
# 成本
fixed_fee = 0.2
variable_fee = 0.0002 * qty
slippage = 0.0005 * qty
# 成本總額
total_cost = fixed_fee + variable_fee + slippage
- **滑點** 可分為 **預估滑點**(根據波動率估算)與 **實際滑點**(根據交易執行速度)。
- **交易成本** 會直接侵蝕報酬,對短期策略尤為重要。
## 5. 績效報告與可視化
| 報表項 | 目的 |
|--------|------|
| **收益曲線** | 觀察累積報酬走勢 |
| **風險指標表** | 彙總 Sharpe、Sortino、最大回撤等 |
| **交易統計** | 交易次數、勝率、平均持有期 |
| **因子貢獻** | 分析不同因子對收益的影響 |
| **敏感度分析** | 變更滑點、手續費對績效的影響 |
python
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
ret.cumsum().plot(ax=ax)
ax.set_title('累積報酬曲線')
ax.set_xlabel('時間')
ax.set_ylabel('累積報酬(%)')
plt.show()
## 6. 實作範例(Python + backtrader)
以下示範如何使用 `backtrader` 進行簡易均值回歸策略的回測,並計算關鍵指標。
python
import backtrader as bt
import pandas as pd
import yfinance as yf
# 1. 資料下載
data = yf.download('AAPL', start='2015-01-01', end='2024-12-31')
data.dropna(inplace=True)
# 2. 定義策略
class MeanReversion(bt.Strategy):
params = dict(
period=20, # 移動平均期間
std_multiplier=1.5,
size=0.01 # 位置大小(按帳戶資金比例)
)
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(self.data.close, period=self.params.period)
self.std = bt.indicators.StandardDeviation(self.data.close, period=self.params.period)
self.upper = self.sma + self.params.std_multiplier * self.std
self.lower = self.sma - self.params.std_multiplier * self.std
def next(self):
if not self.position:
if self.data.close[0] < self.lower[0]:
self.buy(size=self.params.size)
elif self.data.close[0] > self.upper[0]:
self.sell(size=self.params.size)
else:
if self.position.size > 0 and self.data.close[0] > self.sma[0]:
self.close()
elif self.position.size < 0 and self.data.close[0] < self.sma[0]:
self.close()
# 3. 回測設置
cerebro = bt.Cerebro()
cerebro.broker.setcash(100000)
cerebro.broker.setcommission(commission=0.0002, mult=1, name='commission') # 變動手續費
cerebro.addstrategy(MeanReversion)
data_feed = bt.feeds.PandasData(dataname=data)
cerebro.adddata(data_feed)
# 4. 執行回測
results = cerebro.run()
final_cash = cerebro.broker.getvalue()
print(f'最終資金: {final_cash:.2f}')
> **提示**:`backtrader` 內建滑點模擬,亦可自行在 `next()` 中加入手動滑點。
## 7. 常見陷阱與對策
| 陷阱 | 原因 | 對策 |
|------|------|------|
| **資料遺漏(Data Snooping)** | 只挑選符合歷史結果的參數 | 使用前向測試、資料集外驗證 |
| **未來資訊滲透** | 透過「滯後」或「前置」資料 | 確保所有特徵均在事件發生前可得 |
| **過度擬合** | 過多參數、短期樣本 | 交叉驗證、正則化、簡化模型 |
| **成本估算不實** | 忽略滑點或固定費用 | 在回測中明確模擬成本,並調整參數 |
| **頻繁交易** | 無交易成本懲罰 | 加入最小交易量、滑點、手續費 |
## 8. 小結與練習題
### 小結
1. **回測必須遵循時間序列的嚴謹性**,切分資料時保持順序。
2. **風險指標**(Sharpe、Sortino、最大回撤、Calmar)是評估績效的核心工具。
3. **交易成本與滑點**直接影響淨報酬,需在回測中詳細模擬。
4. **可視化**有助於快速洞察策略行為與風險特性。
5. **常見陷阱**(資料遺漏、過度擬合)可通過嚴格的驗證流程與簡化模型來緩解。
### 練習題
1. **多因子均值回歸**:在均值回歸策略中加入「RSI」與「布林帶」作為額外進出場條件,並評估對 Sharpe Ratio 的影響。
2. **滑點敏感度**:改寫上節示例,將滑點從 0.05% 逐步調整到 0.2%,觀察最大回撤與 Calmar Ratio 的變化。
3. **資金管理**:在 `MeanReversion` 策略中加入「最大持倉比率 10%」限制,並比較未加限制時的勝率與夏普。
> **完成練習後,請將回測結果以表格形式整理,並在報告中說明每項指標變化的原因與潛在風險。**
---
> **參考資料**
> - B. P. Connolly & D. W. P. (2020). *Backtesting Techniques in Quantitative Finance*. Wiley.
> - J. Smith (2019). *Risk Metrics for Quantitative Strategies*. QuantStart.
---
**下一章**:將進一步探討多策略組合的構建方法與高頻交易中機器學習的實務應用。