跳轉到

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

sim(position, resample=None, resample_offset=None, trade_at_price='close', position_limit=1, fee_ratio=1.425 / 1000, tax_ratio=3 / 1000, 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=True, fast_mode=False, notification_enable=False, line_access_token='')

Simulate the equity given the stock position history. 回測模擬股票部位所產生的淨值報酬率。

PARAMETER DESCRIPTION
position

買賣訊號紀錄。True 為持有, False 為空手。 若選擇做空position,只要將 sim(position) 改成負的 sim(-position.astype(float))即可做空。

TYPE: DataFrame or Series

resample

交易週期。將 position 的訊號以週期性的方式論動股票,預設為每天換股。其他常用數值為 W、 M 、 Q (每週、每月、每季換股一次),也可以使用 W-Fri 在週五的時候產生新的股票清單,並且於下週交易日下單。

  • D: Daily
  • W: Weekly
  • W-Wed: Every Wednesday
  • M: Monthly
  • MS: Start of every month
  • Q: Quarterly
  • QS: Start of every quarter

Note

'D'與'None'的差別? resample='D' 的意義為每天隨股價變化做再平衡,就算當天股票清單沒變,但股票漲跌後,部位大小會變化,而 resample='D' 會強制再平衡,平均分散風險。

但是當 resample=None 的話,假如清單不變,則不會強制再平衡,只有清單改變時,才做再平衡。適用情境在較常選到大波段標的的趨勢策略,較有機會將強勢股留下,而不會汰強留弱做再平衡。

另外 resample 也接受 pd.DataFrame 以及 pd.Series,並且將其 index 用來當成換股的時間點,例如以下的範例:

from finlab import backtest, data

rev = data.get('monthly_revenue:當月營收')
position = ...

# 月營收發布時才換股
backtest.sim(position, resample=rev)

TYPE: (str, None, DataFrame, Series, FinlabDataFrame) DEFAULT: None

resample_offset

交易週期的時間位移,例如。

  • '1D': 位移一天
  • '1H': 位移一小時

TYPE: str or None DEFAULT: None

trade_at_price

選擇回測之還原股價以收盤價或開盤價計算,預設為'close'。可選'close'、'open'、'open_close_avg'、'high_low_avg'或 'price_avg'。

TYPE: str or DataFrame DEFAULT: 'close'

position_limit

maximum amount of investing a stock. 單檔標的持股比例上限,控制倉位風險。預設為None。範例:0.2,代表單檔標的最多持有 20 % 部位。

TYPE: float DEFAULT: 1

fee_ratio

fee ratio of buying or selling a stock. 交易手續費率,預設為台灣無打折手續費 0.001425。可視個人使用的券商優惠調整費率。

TYPE: float DEFAULT: 1.425 / 1000

tax_ratio

tax ratio of selling a stock. 交易稅率,預設為台灣普通股一般交易交易稅率 0.003。若交易策略的標的皆為ETF,記得設成 0.001。

TYPE: float DEFAULT: 3 / 1000

name

name of the strategy. 策略名稱,預設為 未指名。策略名稱。相同名稱之策略上傳會覆寫。命名規則:全英文或開頭中文,不接受開頭英文接中文。

TYPE: str DEFAULT: '未命名'

stop_loss

停損基準,預設為None,不執行停損。範例:0.1,代表從再平衡開始,虧損 10% 時產生出場訊號。

TYPE: float DEFAULT: None

take_profit

停利基準,預設為None,不執行停利。範例:0.1,代表從再平衡開始, 10% 時產生出場訊號。

TYPE: float DEFAULT: None

trail_stop

移動停損停利基準,預設為None,不執行。範例:0.1,代表從最高點開始下跌,跌至 10% 時產生出場訊號。

TYPE: float DEFAULT: None

touched_exit

是否在回測時,使用觸價停損停利?預設為 False。

TYPE: bool DEFAULT: False

retain_cost_when_rebalance

當持股再平衡時,如有股票繼續持有,是否保留原本進場的成本,當成停損停利的參考?預設為 False。

TYPE: bool DEFAULT: False

stop_trading_next_period

當期已經停損停利,則下一期不買入,預設為 True。

TYPE: bool DEFAULT: True

live_performance_start

策略建構的日期,例如 2022-01-01 此日期之前,策略未撰寫,此日期之後則視為與實單有類似效果,實際不影響回測的結果,單純紀錄而已。

TYPE: str DEFAULT: None

mae_mfe_window

計算mae_mfe於進場後於不同持有天數下的數據變化,主要應用為edge_ratio (優勢比率)計算。預設為0,則Report.display_mae_mfe_analysis(...)中的edge_ratio不會顯現。

TYPE: int DEFAULT: 0

mae_mfe_window_step

與mae_mfe_window參數做搭配,為時間間隔設定,預設為1。若mae_mfe_window設20,mae_mfe_window_step設定為2,相當於python的range(0,20,2),以2日為間距計算mae_mfe。

TYPE: int DEFAULT: 1

market

可選擇'TW_STOCK', 'US_STOCK',分別為台股或加密貨幣, 或繼承 finlab.market.Market 開發回測市場類別。

TYPE: str or Market DEFAULT: None

upload

上傳策略,預設為True,上傳策略。 範例: False,不上傳,可用 finlab.backtest.sim(position, upload=False, ...).display() 快速檢視策略績效。

TYPE: bool DEFAULT: True

fast_mode

預設為False,若設定為True,則會使用快速模式,快速模式會忽略所有的停利停損設定,並且只有換股日進行報酬率模擬,因此會有一些誤差,當持有較多檔股票時,可以大幅加速回測速度。

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Report

回測數據報告

Examples:

Assume the history of portfolio is construct as follows: When market close on 2021-12-31, the portfolio {B: 0.2, C: 0.4} is calculated. When market close on 2022-03-31, the portfolio {A:1} is calculated.

Stock 2330 Stock 1101 Stock 2454
2021-12-31 0% 20% 40%
2022-03-31 100% 0% 0%
2022-06-30 100% 0% 0%

With the portfolio, one could backtest the equity history as follows:

import pandas as pd
from finlab import backtest

position = pd.DataFrame({
    '2330': [0, 1, 1],
    '1101': [0.2, 0, 0],
    '2454': [0.4, 0, 0]
}, index=pd.to_datetime(['2021-12-31', '2022-03-31', '2022-06-30']))

report = backtest.sim(position)

常用參數組合

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

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, stop_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% 停損
    stop_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='')

傳送回測結果之目前部位、近期換股訊息至Line聊天室。

PARAMETER DESCRIPTION
report

回測完的結果報告。

TYPE: Report DEFAULT: None

line_access_token

於Line Notify取得的access_token(權杖)。至Line Notify登入Line帳號後,點選個人頁面,點選「發行權杖」,選擇欲接收訊息的聊天室(可選擇1對1接收Line Notify通知、或是選擇其他群組聊天室),即可取得權杖。

TYPE: str DEFAULT: ''

test

是否進行傳送訊息測試。

TYPE: bool DEFAULT: False

name

策略名稱,預設為空字串。

TYPE: str DEFAULT: ''

Examples:

欲進行測試,則設定test參數為True。

from finlab import backtest

line_access_token = 'xxxxxxxxxxxx'
backtest.line_notify(line_access_token=line_access_token, test=True)

若成功收到通知,則權杖設定已完畢,可直接在sim回測模組中開啟使用,或單獨調用此函式發送回測換股訊息。 於sim中使用:

from finlab import backtest

line_access_token = 'xxxxxxxxxxxx'
position = ...
report = backtest.sim(position, notification_enable =True, line_access_token = line_access_token)

已回測完,單獨傳訊息用:

from finlab import backtest

line_access_token = 'xxxxxxxxxxxx'
report = backtest.sim(position)
backtest.line_notify(report, line_access_token=line_access_token)

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. 未來函數(資料洩露):

    # 錯誤:當日收盤價做判斷,當日收盤價交易
    position = close > close.average(20)  # ❌ 使用當日收盤價
    report = sim(position, trade_at_price='close')  # ❌ 當日收盤價交易
    
    # 正確:隔日開盤價交易
    position = close > close.average(20)
    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 檔

參考資源