跳轉到

finlab.backtest

回測引擎模組,用於評估策略在歷史資料上的表現。

使用情境

  • 驗證策略邏輯: 確認策略的進出場邏輯是否符合預期,檢視實際交易記錄
  • 評估風險報酬: 計算年化報酬、夏普率、最大回撤等關鍵績效指標
  • 優化策略參數: 結合 finlab.optimize 模組測試不同參數組合(調倉頻率、停損停利等)
  • 對比多個策略: 比較不同策略的績效表現,找出最適合的交易方法

快速範例

from finlab import data
from finlab.backtest import sim

# 載入資料
close = data.get('price:收盤價')

# 策略邏輯:價格突破 20 日均線
ma20 = close.average(20)
position = close > ma20

# 回測(每月調倉,單股上限 10%)
report = sim(
    position,
    resample='M',           # 每月調倉
    position_limit=0.1      # 單股權重上限 10%
)

# 顯示績效報告
report.display()

詳細教學

參考 歷史回測完整指南,了解:

  • 完整回測參數說明(resample, position_limit, stop_loss 等)
  • 績效指標詳細解讀(年化報酬、夏普率、勝率等)
  • 進階回測技巧(多市場、自訂手續費、滑價設定)
  • 回測報告的深度分析方法

API Reference

sim()

finlab.backtest.sim

Backtest simulation orchestrator.

This is the main entry point for running backtests via sim(). Configuration, validation, report building, and notifications are delegated to focused package modules:

  • finlab.backtest.config -- SimConfig, input validation
  • finlab.backtest.report -- trades, MAE/MFE, report assembly
  • finlab.backtest.notify -- Line notification delivery

SimConfig dataclass

SimConfig(resample=None, resample_offset=None, trade_at_price='close', position_limit=1, fee_ratio=None, tax_ratio=None, name='未命名', stop_loss=None, take_profit=None, trail_stop=None, touched_exit=False, retain_cost_when_rebalance=False, stop_trading_next_period=True, live_performance_start=None, mae_mfe_window=0, mae_mfe_window_step=1, market=None, upload=None, fast_mode=False, notification_enable=False, line_access_token='')

Configuration for backtest simulation parameters.

Groups the many sim() parameters into a single config object for cleaner internal passing between phases.

columns_to_numpy

columns_to_numpy(df)

Convert DataFrame columns to numpy array for pandas 3.0+ compatibility.

sim

sim(position, resample=None, resample_offset=None, trade_at_price='close', position_limit=1, fee_ratio=None, tax_ratio=None, name='未命名', stop_loss=None, take_profit=None, trail_stop=None, touched_exit=False, retain_cost_when_rebalance=False, stop_trading_next_period=True, live_performance_start=None, mae_mfe_window=0, mae_mfe_window_step=1, market=None, upload=None, fast_mode=False, notification_enable=False, line_access_token='')

Simulate the equity given the stock position history.

See the module-level docstring and the parameter docs below for full details.

PARAMETER DESCRIPTION
position

Buy/sell signal history. True = hold, False = flat. Can also be a LazyWide — it will be auto-collected at the rebalance dates determined by resample.

TYPE: DataFrame | Series

resample

Trading period frequency (e.g. 'W', 'M', 'Q').

TYPE: str | None DEFAULT: None

resample_offset

Time offset for the resample period.

TYPE: str | None DEFAULT: None

trade_at_price

Price type for trade execution ('close', 'open', etc.).

TYPE: str | DataFrame DEFAULT: 'close'

position_limit

Maximum single-stock weight (0-1).

TYPE: float DEFAULT: 1

fee_ratio

Trading fee ratio.

TYPE: float | None DEFAULT: None

tax_ratio

Trading tax ratio.

TYPE: float | None DEFAULT: None

name

Strategy name.

TYPE: str DEFAULT: '未命名'

stop_loss

Stop-loss threshold.

TYPE: float | None DEFAULT: None

take_profit

Take-profit threshold.

TYPE: float | None DEFAULT: None

trail_stop

Trailing stop threshold.

TYPE: float | None DEFAULT: None

touched_exit

Use intraday touched stop-loss/take-profit.

TYPE: bool DEFAULT: False

retain_cost_when_rebalance

Keep original cost basis on rebalance.

TYPE: bool DEFAULT: False

stop_trading_next_period

Skip next period after stop-loss/take-profit.

TYPE: bool DEFAULT: True

live_performance_start

ISO date string marking live trading start.

TYPE: str | None DEFAULT: None

mae_mfe_window

Window size for MAE/MFE calculation.

TYPE: int DEFAULT: 0

mae_mfe_window_step

Step size for MAE/MFE window.

TYPE: int DEFAULT: 1

market

Market instance or name string ('TW_STOCK', 'US_STOCK').

TYPE: None | Market DEFAULT: None

upload

Whether to upload the strategy report. When omitted, FINLAB_STRATEGY_NAME/FINLAB_FORCED_STRATEGY_NAME enables upload.

TYPE: bool | None DEFAULT: None

fast_mode

Use fast simulation mode (no stop-loss/take-profit).

TYPE: bool DEFAULT: False

notification_enable

Send Line notifications on completion.

TYPE: bool DEFAULT: False

line_access_token

Line Notify access token.

TYPE: str DEFAULT: ''

RETURNS DESCRIPTION
Report

Report object with backtest results and analytics.

常用參數組合

月線策略(適合上班族):

report = sim(
    position,
    resample='M',           # 每月調倉
    position_limit=0.1,     # 單股上限 10%
    upload=True             # 上傳至雲端
)

週線策略(適合積極型):

report = sim(
    position,
    resample='W',           # 每週調倉
    position_limit=0.05,    # 單股上限 5%(分散風險)
    trade_at_price='open'   # 以開盤價交易(更保守)
)

停損停利策略:

# 方法 1: 在 sim() 中設定
report = sim(
    position.hold_until(stop_loss=0.1, take_profit=0.2),
    resample='M'
)

# 方法 2: 使用 hold_until() 更靈活
exit_condition = close < ma20  # 跌破均線出場
position_with_exit = position.hold_until(
    exit=exit_condition,
    stop_loss=0.1,      # 虧損 10% 停損
    take_profit=0.2     # 獲利 20% 停利
)
report = sim(position_with_exit, resample='M')

常見錯誤與解決方法

1. 策略無任何交易記錄

# 問題:進場條件過嚴,position 全為 False
report = sim(position, resample='M')
trades = report.get_trades()
if len(trades) == 0:
    print("⚠️ 警告:策略無任何交易記錄!")
    # 檢查進場次數
    print(f"平均每日進場股票數:{position.sum(axis=1).mean():.1f}")
    print(f"最大進場股票數:{position.sum(axis=1).max()}")

# 解決:放寬條件或檢查資料範圍

2. KeyError: 日期不存在

# 問題:資料日期範圍不匹配
# 解決:使用 truncate_start 對齊起始日期
import finlab
finlab.truncate_start = '2020-01-01'  # 只回測 2020 年後的資料
report = sim(position, resample='M')

3. 忘記設定 resample 導致每日調倉

# 錯誤:交易成本過高
report = sim(position)  # ❌ 預設每日調倉

# 正確:設定合理的調倉頻率
report = sim(position, resample='M')  # ✅ 每月調倉

line_notify()

finlab.backtest.line_notify

line_notify(report=None, line_access_token='', test=False, name='')

Send backtest position and recent trade signals to a Line chat room.

PARAMETER DESCRIPTION
report

The backtest result report.

TYPE: Report | None DEFAULT: None

line_access_token

Line Notify access token.

TYPE: str DEFAULT: ''

test

If True, send a test message instead of report data.

TYPE: bool DEFAULT: False

name

Strategy name for the notification header.

TYPE: str DEFAULT: ''

LINE 通知設定

  1. 前往 LINE Notify 取得個人 token
  2. 設定環境變數:
    import os
    os.environ['LINE_NOTIFY_TOKEN'] = 'YOUR_TOKEN'
    
  3. 發送通知:
    from finlab.backtest import line_notify
    
    # 策略執行後發送報告
    report = sim(position, resample='M')
    line_notify(f"策略回測完成\n年化報酬:{report.stats['annual_return']:.2%}")
    

常見問題

Q: 回測結果與實盤有落差怎麼辦?

常見原因與解決方法:

  1. 回測未考慮滑價

    # 使用開盤價交易(更保守)
    report = sim(position, trade_at_price='open', resample='M')
    

  2. 成交量不足

    # 使用 LiquidityAnalysis 檢測流動性
    from finlab.analysis import LiquidityAnalysis
    
    liq = LiquidityAnalysis(report)
    liq.display()  # 查看成交量風險
    

  3. 交易價格與實際執行價差sim() 的訊號永遠是「今天產生、隔天執行」,trade_at_price 決定的是隔天用哪個價格成交:

    # trade_at_price='close'(預設)→ 隔日收盤價成交
    # 實務上難以精確在收盤價成交,容易產生滑價
    report = sim(position, trade_at_price='close')  # ⚠️ 隔日收盤價,實務不易精確成交
    
    # trade_at_price='open' → 隔日開盤價成交(推薦)
    # 更貼近實際下單情境:盤後看訊號,隔日開盤掛單
    report = sim(position, trade_at_price='open')  # ✅ 隔日開盤價,較貼近實盤
    

Q: 如何設定自訂手續費?

report = sim(
    position,
    resample='M',
    fee_ratio=0.001425,  # 買賣各 0.1425%(券商手續費)
    tax_ratio=0.003      # 賣出證交稅 0.3%
)

Q: 如何限制最大持股數量?

# 方法 1: 使用 is_largest() 限制進場股票數
position = (close > ma20).is_largest(30)  # 最多持有 30 檔

# 方法 2: 在 sim() 中限制
report = sim(
    position,
    resample='M',
    position_limit=0.1,   # 單股上限 10%
    # 如果 position 中有 50 檔,實際最多持有 10 檔(100% / 10% = 10)
)

Q: 回測速度太慢怎麼辦?

# 1. 使用更長的調倉週期
report = sim(position, resample='Q')  # 季度調倉(比月線快 3 倍)

# 2. 縮短回測期間
import finlab
finlab.truncate_start = '2018-01-01'  # 只測試近 5 年

# 3. 減少股票數量
position = (close > ma20).is_largest(50)  # 只測試 50 檔

參考資源