教學
策略參數優化
本頁內容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, # 不上傳至雲端
)from finlab import data
from finlab.backtest import sim
from finlab.optimize.combinations import sim_conditions
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
revenue = data.get('us_income_statement:revenue') # 季資料
operating_income = data.get('us_income_statement:operating_income') # 季資料
# 定義 3 個條件
c1 = (close > close.average(20)) & (close > close.average(60)) # 技術面:突破均線
c2 = operating_income.pct_change(4) > 0 # 基本面:獲利年增
c3 = revenue.pct_change(4) > 0.1 # 基本面:營收年增超過 10%
# 設定出場條件
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 種組合:
c1(僅技術面)c2(僅獲利成長)c3(僅營收加速)c1 & c2(技術面 + 獲利成長)c1 & c3(技術面 + 營收加速)c2 & c3(獲利成長 + 營收加速)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_bmfe | MAE 發生前的平均最大有利方向幅度 | 越高越好 |
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')from finlab import data
from finlab.optimize.combinations import sim_conditions
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
raw_close = data.get('price:close') # 估值用未還原股價
eps = data.get('us_income_statement:eps_diluted')
revenue = data.get('us_income_statement:revenue')
pe = raw_close / eps.rolling(4).sum() # 近四季本益比
revenue_ma2 = revenue.average(2)
revenue_ma4 = revenue.average(4)
# 5 個條件
c1 = (close > close.average(20)) & (close > close.average(60)) # 均線多頭排列
c2 = close == close.rolling(20).max() # 創 20 日新高
c3 = (pe > 0) & (pe < 15) # 低本益比
c4 = revenue_ma2 / revenue_ma4 > 1.05 # 營收加速(近 2 季對近 4 季)
c5 = revenue / revenue.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.5?(風險調整後報酬是否足夠)
- 最大回撤 < -30%?(最差情況能否承受)
- 勝率 > 50%?(交易勝率是否合理)
- 分析組合意義:
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 種。建議:
- 先篩選重要條件:用單一條件回測,移除績效過差的條件
- 分批測試:將條件分成技術面、基本面、籌碼面分別優化
- 使用更長的 resample:改用
resample='Q'(季度)減少計算量
如何避免過度配適(overfitting)?
- 樣本外測試:用歷史資料優化,用最近的資料驗證
- 避免條件過多:條件數 > 5 時要特別小心
- 檢查組合邏輯:最佳組合是否有商業邏輯支撐?
- 多個指標綜合評估:不要只看報酬率,也要看夏普率、回撤、勝率
條件的資料頻率不一致怎麼辦?
混合使用沒問題,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,
}季資料會對齊到每次 SEC 申報日,再自動 forward fill:
data.set_market('us')
# 日資料
close = data.get('price:adj_close') # 每日更新
# 季資料,對齊到每次 SEC 申報日
revenue = data.get('us_income_statement:revenue')
eps = data.get('us_income_statement:eps_diluted')
# 混合使用沒問題,FinLab 會自動 forward fill
conditions = {
'c1': close > close.average(20),
'c2': revenue.average(2) > revenue.average(4),
'c3': eps > 0,
}為什麼某些組合回測失敗?
可能原因:
- 條件過嚴格:交集後沒有任何股票符合
- 資料缺失:某些條件的資料範圍不足
- 記憶體不足:條件組合過多導致 OOM
可從 log 中查看具體錯誤訊息。
下一步
- 策略分析模組 對選出的組合做 MAE/MFE、流動性與穩定性分析。
- 歷史回測
sim()的完整參數與報告。 - 從研究到實盤 完整的策略開發流程。
- finlab.optimize
sim_conditions()與ReportCollection的完整參數。
這一頁有幫到你嗎?