文件
開啟 Studio

教學

策略參數優化

本頁內容9

策略開發後,如何找出最好的條件組合?finlab.optimize 的 sim_conditions() 會自動回測所有組合,搭配視覺化工具比較績效。

開發交易策略時,常常會有好幾個選股條件:

  • 技術面:均線交叉、創新高、RSI 指標
  • 基本面:本益比、營收成長、獲利能力
  • 籌碼面:投信買超、融資變化

每個條件單獨使用可能績效普通,組合起來卻可能產生意想不到的效果。手動測試所有組合非常耗時,sim_conditions() 可以自動化這個過程。

一次回測所有條件組合

把條件放進字典,key 是條件名稱,交給 sim_conditions():

from finlab import data
from finlab.backtest import sim
from finlab.optimize.combinations import sim_conditions

close = data.get("price:收盤價")
rev = data.get('monthly_revenue:當月營收')
營業利益成長率 = data.get('fundamental_features:營業利益成長率')

# 定義 3 個條件
c1 = (close > close.average(20)) & (close > close.average(60))  # 技術面:突破均線
c2 = 營業利益成長率 > 0  # 基本面:獲利成長
c3 = rev.average(3) / rev.average(12) > 1.1  # 基本面:營收加速

# 設定出場條件
exits = close < close.average(20)

# 組合字典,key 為條件名稱
conditions = {'c1': c1, 'c2': c2, 'c3': c3}

# 測試所有組合
report_collection = sim_conditions(
    conditions=conditions,
    hold_until={'exit': exits, 'stop_loss': 0.1},  # 跌破均線出場,停損 10%
    resample='M',  # 每月調整
    position_limit=0.1,  # 單一持股上限 10%
    upload=False,  # 不上傳至雲端
)

hold_until 是訊號進出場語法糖的參數;其他參數(resample、position_limit、upload 等)會原樣交給 sim()。

這段程式會自動測試以下 7 種組合:

  1. c1(僅技術面)
  2. c2(僅獲利成長)
  3. c3(僅營收加速)
  4. c1 & c2(技術面 + 獲利成長)
  5. c1 & c3(技術面 + 營收加速)
  6. c2 & c3(獲利成長 + 營收加速)
  7. c1 & c2 & c3(三個條件全部)

用圖表比較各組合

下面的圖是五個條件(c1 到 c5)的範例輸出,實際結果依你的條件與資料而定。

累積報酬率折線圖

比較各組合的淨值曲線:

report_collection.plot_creturns().show()
累積報酬率
累積報酬率

指標分群棒狀圖

report_collection.plot_stats('bar').show()
棒狀圖
棒狀圖

指標分級熱力圖

熱力圖回傳的是上色的表格,在 notebook 裡直接顯示,不用加 .show():

report_collection.plot_stats('heatmap')
熱力圖
熱力圖

熱力圖用顏色深淺表示各指標的排名百分位(0–100%),數值越大代表排名越前:

  • avg_score:各指標平均分數,分數越高代表整體評價越好
  • 預設以 avg_score 降冪排序,最好的組合在最上方
  • 顏色越亮(黃色)代表該指標在所有組合中排名越前

只看部分指標、換排序方式

indicators 指定要顯示的指標,heatmap_sort_by 指定熱力圖的排序欄位:

# 只顯示報酬率、夏普率、最大回撤
report_collection.plot_stats(
    'bar', indicators=['cagr', 'daily_sharpe', 'max_drawdown']
).show()

# 熱力圖以夏普率排序
report_collection.plot_stats('heatmap', heatmap_sort_by='daily_sharpe')

# 熱力圖以多個指標排序
report_collection.plot_stats('heatmap', heatmap_sort_by=['daily_sharpe', 'win_ratio'])

取得詳細績效指標

stats = report_collection.get_stats()
print(stats)

會回傳一個 DataFrame,包含 12 種指標:

策略層級指標

指標說明評估方向
cagr年化報酬率(複利,CAGR)越高越好
daily_sharpe年化夏普率(風險調整後報酬)越高越好
daily_sortino年化索提諾比率(下檔風險調整後報酬)越高越好
max_drawdown最大回撤率(負值)絕對值越小越好
avg_drawdown平均回撤率(負值)絕對值越小越好

交易層級指標

指標說明評估方向
win_ratio每筆交易勝率越高越好
avg_return每筆交易平均獲利率越高越好
avg_mae每筆交易平均最大不利方向幅度(負值)絕對值越小越好
avg_bmfeMAE 發生前的平均最大有利方向幅度越高越好
avg_gmfe每筆交易平均最大有利方向幅度越高越好
avg_mdd每筆交易平均最大回撤率(負值)絕對值越小越好

取出單一組合的回測報告

report_collection.reports 是一個 dict,key 是組合名稱:

print(report_collection.reports.keys())
# 輸出: dict_keys(['c1', 'c2', 'c3', 'c1 & c2', 'c1 & c3', ...])

# 取得特定組合的報告
report = report_collection.reports['c1 & c3']
report.display()

進階範例:5 個條件

5 個條件會產生 31 種組合(2^5 - 1):

from finlab import data
from finlab.optimize.combinations import sim_conditions

close = data.get("price:收盤價")
pe = data.get('price_earning_ratio:本益比')
rev = data.get('monthly_revenue:當月營收').index_str_to_date()
rev_ma3 = rev.average(3)
rev_ma12 = rev.average(12)

# 5 個條件
c1 = (close > close.average(20)) & (close > close.average(60))  # 均線多頭排列
c2 = close == close.rolling(20).max()  # 創 20 日新高
c3 = pe < 15  # 低本益比
c4 = rev_ma3 / rev_ma12 > 1.1  # 營收加速成長
c5 = rev / rev.shift(1) > 0.9  # 營收月增率 > -10%

exits = close < close.average(20)

conditions = {'c1': c1, 'c2': c2, 'c3': c3, 'c4': c4, 'c5': c5}

report_collection = sim_conditions(
    conditions=conditions,
    hold_until={'exit': exits, 'stop_loss': 0.1},
    resample='M',
    position_limit=0.1,
    upload=False,
)

# 查看熱力圖,快速找出最佳組合
report_collection.plot_stats('heatmap')

假設熱力圖顯示 c1 & c3 & c4 組合的 avg_score 最高,接著:

  1. 檢視累積報酬曲線:確認淨值成長是否穩定
  2. 檢查關鍵指標:
    • 夏普率 > 1.5?(風險調整後報酬是否足夠)
    • 最大回撤 < -30%?(最差情況能否承受)
    • 勝率 > 50%?(交易勝率是否合理)
  3. 分析組合意義:c1 & c3 & c4 代表「技術面突破 + 估值便宜 + 營收成長」,這個組合有其商業邏輯

同時測試停損停利

sim_conditions() 的 hold_until 參數支援多種出場邏輯:

# 1. 僅設定出場訊號
hold_until = {'exit': exits}

# 2. 出場訊號 + 停損
hold_until = {'exit': exits, 'stop_loss': 0.1}

# 3. 出場訊號 + 停利
hold_until = {'exit': exits, 'take_profit': 0.2}

# 4. 出場訊號 + 停損 + 停利
hold_until = {'exit': exits, 'stop_loss': 0.1, 'take_profit': 0.2}

# 5. 出場訊號 + 停損(移動停利需在 sim() 中設定 trail_stop)
hold_until = {'exit': exits, 'stop_loss': 0.1}
# 搭配: sim_conditions(..., trail_stop=0.15)

實戰工作流程

定義候選條件

準備 5–8 個條件。

單一條件回測

確認每個條件都有基本績效。

用 sim_conditions() 測試所有組合

一次回測所有組合。

用圖表分析

  • 用熱力圖找出 top 3 組合
  • 用折線圖確認淨值穩定性
  • 用棒狀圖比較關鍵指標

深入分析 top 3

  • 執行 MAE/MFE 分析
  • 檢查流動性風險
  • 執行樣本外測試

選擇最終策略

綜合考量績效、風險、邏輯性。

常見問題

組合太多,運算時間過長?

條件數量 > 6 時,組合數會超過 63 種。建議:

  1. 先篩選重要條件:用單一條件回測,移除績效過差的條件
  2. 分批測試:將條件分成技術面、基本面、籌碼面分別優化
  3. 使用更長的 resample:改用 resample='Q'(季度)減少計算量
如何避免過度配適(overfitting)?
  1. 樣本外測試:用歷史資料優化,用最近的資料驗證
  2. 避免條件過多:條件數 > 5 時要特別小心
  3. 檢查組合邏輯:最佳組合是否有商業邏輯支撐?
  4. 多個指標綜合評估:不要只看報酬率,也要看夏普率、回撤、勝率
條件的資料頻率不一致怎麼辦?

混合使用沒問題,FinLab 會自動對齊(forward-fill):

# 日資料
close = data.get("price:收盤價")  # 每日更新

# 月資料
rev = data.get('monthly_revenue:當月營收')  # 每月更新

# 季資料
eps = data.get('financial_statement:每股盈餘')  # 每季更新

# 混合使用沒問題,FinLab 會自動 forward fill
conditions = {
    'c1': close > close.average(20),
    'c2': rev.average(3) > rev.average(12),
    'c3': eps > 0,
}
為什麼某些組合回測失敗?

可能原因:

  1. 條件過嚴格:交集後沒有任何股票符合
  2. 資料缺失:某些條件的資料範圍不足
  3. 記憶體不足:條件組合過多導致 OOM

可從 log 中查看具體錯誤訊息。

下一步

↑↓ 選擇 Enter 開啟 Esc 關閉