跳轉到

策略參數優化

當你開發了一個交易策略後,如何找出最佳的條件組合? finlab.optimize 模組提供了 sim_conditions() 函數,可以自動測試所有條件組合,並透過視覺化工具快速比較績效,幫助你找出最強的策略。

為什麼需要策略優化?

在開發交易策略時,我們常常會有多個選股條件:

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

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

快速上手

基礎範例:3 個條件的所有組合

假設我們有 3 個選股條件:

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            # 不上傳至雲端
)

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

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

視覺化比較策略績效

1. 累積報酬率折線圖

比較各組合的淨值曲線:

report_collection.plot_creturns().show()

累積報酬率

從圖中可以看出哪個組合的長期績效最好,以及各組合的穩定性。

2. 指標分群棒狀圖

report_collection.plot_stats('bar').show()

棒狀圖

並排顯示所有組合的 12 種關鍵指標,方便快速比較。

3. 指標分級熱力圖

report_collection.plot_stats('heatmap')

熱力圖

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

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

如何解讀熱力圖

  • 尋找 avg_score 最高的組合(通常在最上方)
  • 檢查該組合是否在關鍵指標(夏普率、勝率、最大回撤)都有不錯的表現
  • 避免選擇某些指標特別差(深紫色)的組合

取得詳細績效指標

stats = report_collection.get_stats()
print(stats)

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

策略層級指標

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

交易層級指標

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

avg_bmfe 的意義

avg_bmfe 代表在停損觸發前,股價曾經漲到多高。若此數值越高,代表越有機會在停損前先執行停利,是優化停利點的重要參考。

進階範例:5 個條件的優化

當條件增加到 5 個時,組合數會達到 31 種 (2^5 - 1)。手動測試非常困難,但 sim_conditions() 可以輕鬆處理:

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

自訂要顯示的指標

如果只關心特定指標,可以使用 indicators 參數:

# 只顯示報酬率、夏普率、最大回撤
report_collection.plot_stats('bar', indicators=['daily_mean', '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'])

結合停損停利優化

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

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

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

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

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

# 5. 出場訊號 + 停損 + 移動停利
hold_until = {'exit': exits, 'stop_loss': 0.1, 'trailing_stop_profit': 0.15}

停損停利設定建議

  1. 先執行 report.display_mae_mfe_analysis() 分析波動特性
  2. 根據 MAE 分布設定停損點(避免過度停損)
  3. 根據 MFE 分布設定停利點(確保獲利能實現)
  4. 使用 sim_conditions() 測試不同停損停利組合的效果

常見問題與最佳實踐

Q1: 組合太多導致運算時間過長?

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

  1. 先篩選重要條件: 使用單一條件回測,移除績效過差的條件
  2. 分批測試: 將條件分成技術面、基本面、籌碼面分別優化
  3. 使用更長的 resample: 改用 resample='Q'(季度)減少計算量

Q2: 如何避免過度配適(overfitting)?

  1. 樣本外測試: 使用歷史資料優化,用最近資料驗證
  2. 避免條件過多: 條件數 > 5 時要特別小心
  3. 檢查組合邏輯: 最佳組合是否有商業邏輯支撐?
  4. 多個指標綜合評估: 不要只看報酬率,也要看夏普率、回撤、勝率

Q3: 條件的資料頻率不一致怎麼辦?

FinLab 會自動對齊資料頻率:

# 日資料
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
}

Q4: 為什麼某些組合回測失敗?

可能原因:

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

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

Q5: 如何存取特定組合的回測報告?

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

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

實戰工作流程建議

  1. 定義候選條件 (5-8 個)
  2. 單一條件回測: 確認每個條件都有基本績效
  3. 使用 sim_conditions(): 測試所有組合
  4. 視覺化分析:
  5. 用熱力圖找出 top 3 組合
  6. 用折線圖確認淨值穩定性
  7. 用棒狀圖比較關鍵指標
  8. 深入分析 top 3:
  9. 執行 MAE/MFE 分析
  10. 檢查流動性風險
  11. 執行樣本外測試
  12. 選擇最終策略: 綜合考量績效、風險、邏輯性

參考資源