專業回測框架:使用 Backtrader

在前一章中,我們手動實作了回測邏輯。但手動回測容易出錯,而且缺乏許多真實交易的重要考量:交易手續費、稅金、滑價 (Slippage)

這就是為什麼我們需要專業的回測框架——Backtrader

Backtrader 的核心概念

Backtrader 是 Python 中最受歡迎的量化回測框架。它的核心概念是:

  • Data Feed:資料來源(股價資料)
  • Strategy:你的交易策略(買賣邏輯)
  • Broker:模擬券商(處理手續費、現金管理)
  • Analyzer:分析器(計算績效指標)
  • Observer:觀察器(畫圖)

安裝 Backtrader

pip install backtrader

用 Backtrader 實作黃金交叉策略

Step 1: 定義策略

import backtrader as bt
import yfinance as yf
import pandas as pd

class GoldenCrossStrategy(bt.Strategy):
    params = (
        ('short_window', 10),   # 短天期 MA
        ('long_window', 30),    # 長天期 MA
        ('print_trades', False), # 是否印出交易明細
    )

    def __init__(self):
        # 計算移動平均線
        self.short_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.short_window
        )
        self.long_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.long_window
        )
        
        # 交叉信號
        self.crossover = bt.indicators.CrossOver(
            self.short_ma, self.long_ma
        )

    def next(self):
        # 沒有持倉,且出現黃金交叉 → 買進
        if not self.position and self.crossover > 0:
            self.buy(size=10)  # 買進 10 股
            if self.params.print_trades:
                self.log(f'買進信號 - 價格: {self.data.close[0]:.2f}')
        
        # 有持倉,且出現死亡交叉 → 賣出
        elif self.position and self.crossover < 0:
            self.close()  # 賣出所有持倉
            if self.params.print_trades:
                self.log(f'賣出信號 - 價格: {self.data.close[0]:.2f}')
    
    def log(self, txt):
        dt = self.datas[0].datetime.date(0)
        print(f'{dt} {txt}')

Step 2: 準備資料

# 下載資料
df = yf.download("2330.TW", start="2020-01-01", end="2024-12-31")

# Backtrader 要求的資料格式
data = bt.feeds.PandasData(
    dataname=df,
    datetime='Date',  # 使用 index 作為日期
    open='Open',
    high='High',
    low='Low',
    close='Close',
    volume='Volume'
)

Step 3: 設定回測引擎

# 建立 Cerebro 引擎
cerebro = bt.Cerebro()

# 加入資料
cerebro.adddata(data)

# 加入策略
cerebro.addstrategy(GoldenCrossStrategy, print_trades=True)

# 設定初始資金
cerebro.broker.setcash(1000000.0)  # 100 萬

# 設定手續費(台灣手續費約 0.1425%)
cerebro.broker.setcommission(commission=0.001425)

# 加入分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')

Step 4: 執行回測

# 執行回測
print(f'初始資金: {cerebro.broker.getvalue():,.0f}')
results = cerebro.run()
print(f'最終資金: {cerebro.broker.getvalue():,.0f}')

# 取得分析結果
strategy = results[0]

# 夏普比率
sharpe = strategy.analyzers.sharpe.get_analysis()
print(f"夏普比率: {sharpe.get('sharperatio', 'N/A')}")

# 最大回撤
drawdown = strategy.analyzers.drawdown.get_analysis()
print(f"最大回撤: {drawdown['max']['drawdown']:.2f}%")

# 報酬率
returns = strategy.analyzers.returns.get_analysis()
print(f"年化報酬率: {returns.get('rnorm100', 0):.2f}%")
print(f"總報酬率: {returns.get('rtot100', 0):.2f}%")

# 交易統計
trades = strategy.analyzers.trades.get_analysis()
if 'total' in trades:
    print(f"\n=== 交易統計 ===")
    print(f"總交易次數: {trades['total']['total']}")
    print(f"獲利交易: {trades['won']['total']}")
    print(f"虧損交易: {trades['lost']['total']}")
    if trades['won']['total'] > 0:
        print(f"勝率: {trades['won']['total'] / trades['total']['total'] * 100:.1f}%")

Step 5: 畫出回測圖表

# 畫圖
cerebro.plot(style='candle', volume=True)

最佳化策略參數

Backtrader 的殺手級功能是參數最佳化——讓電腦自動嘗試不同的參數組合,找出最佳的一組:

# 定義要測試的參數範圍
cerebro = bt.Cerebro()
cerebro.adddata(data)

# 加入策略,指定參數最佳化範圍
cerebro.addstrategy(
    GoldenCrossStrategy,
    short_window=range(5, 30, 5),  # 測試 5, 10, 15, 20, 25
    long_window=range(20, 100, 10), # 測試 20, 30, ..., 90
)

cerebro.broker.setcash(1000000.0)
cerebro.broker.setcommission(commission=0.001425)

# 加入最佳化分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')

# 執行最佳化(會嘗試所有參數組合)
optimized_results = cerebro.run(maxcpus=4)  # 使用 4 核心加速

# 分析最佳化結果
print("\n=== 參數最佳化結果 ===")
print(f"共測試 {len(optimized_results)} 組參數")

# 找出夏普比率最高的策略
best_result = None
best_sharpe = -999

for result in optimized_results:
    sharpe = result.analyzers.sharpe.get_analysis()
    sharpe_ratio = sharpe.get('sharperatio', -999)
    
    params = result.params
    short_w = params.short_window
    long_w = params.long_window
    
    if sharpe_ratio > best_sharpe:
        best_sharpe = sharpe_ratio
        best_result = (short_w, long_w, sharpe_ratio)

print(f"最佳參數:短天期={best_result[0]}, 長天期={best_result[1]}")
print(f"最佳夏普比率:{best_result[2]:.3f}")

使用 Vibe Coding 建立回測

🔥 【Backtrader 回測詠唱範例】 「請幫我使用 Backtrader 回測一個「RSI 超買超賣策略」: 1. 抓取 AAPL 過去 5 年的日資料。 2. 當 RSI < 30 時買進,當 RSI > 70 時賣出。 3. 設定初始資金 100 萬,手續費 0.1%。 4. 計算總報酬、夏普比率、最大回撤。 5. 畫出回測圖表。 6. 對 RSI 的 window 參數做最佳化(測試 7, 14, 21 天)。」

本日總結

在本章中,你學到了:

  1. Backtrader 架構:Data Feed、Strategy、Broker、Analyzer
  2. 自訂策略:在 next() 方法中實現買賣邏輯
  3. 真實成本模擬:手續費設定
  4. 績效分析:夏普比率、最大回撤、交易勝率
  5. 參數最佳化:讓電腦自動找最佳參數組合
  6. 視覺化回測:內建圖表功能

下一章,我們將學習如何用 Prophet 預測股價趨勢!

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!