教學
策略分析模組
本頁內容11
只看累積報酬曲線和夏普率,會漏掉重要的風險面向。回測完成後,用 report.run_analysis() 執行 finlab.analysis 的 6 種內建分析,檢查流動性風險、波動特性與各時期的穩定性。
該用哪一個
| 模組 | 用途 | 適用場景 |
|---|---|---|
| LiquidityAnalysis | 流動性風險檢測(只支援台股) | 大資金策略、低流動性標的 |
| MaeMfeAnalysis | 波動分析(MAE/MFE) | 優化停損停利、了解策略特性 |
| PeriodStatsAnalysis | 時期穩定性分析 | 檢視年度、月度、近期表現 |
| InequalityAnalysis | 不等式因子分析 | 檢查用因子過濾交易能否提升報酬 |
| AlphaBetaAnalysis | Alpha/Beta 分析 | 衡量超額報酬與市場相關性 |
| DrawdownAnalysis | 回撤分析 | 了解最大回撤的時間與幅度 |
執行分析
所有分析模組的呼叫方式都一樣:先完成回測,再把模組名稱(字串)或模組實例交給 report.run_analysis(),模組的參數直接接在後面。
from finlab import data
from finlab.backtest import sim
# 完成回測
close = data.get('price:收盤價')
position = close > close.average(20)
report = sim(position, resample='M')
# 執行分析 - 方法 1: 使用模組名稱(字串)
report.run_analysis('LiquidityAnalysis', required_volume=100000)
# 執行分析 - 方法 2: 使用模組實例
from finlab.analysis.liquidityAnalysis import LiquidityAnalysis
report.run_analysis(LiquidityAnalysis(required_volume=100000))from finlab import data
from finlab.backtest import sim
# 完成回測
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
position = close > close.average(20)
report = sim(position, resample='M')
# 執行分析 - 方法 1: 使用模組名稱(字串)
report.run_analysis('MaeMfeAnalysis', violinmode='group')
# 執行分析 - 方法 2: 使用模組實例
from finlab.analysis.maeMfeAnalysis import MaeMfeAnalysis
report.run_analysis(MaeMfeAnalysis(violinmode='group'))建議用方法 1:直接傳入模組名稱(字串)比較簡潔,FinLab 會自動處理模組載入。
檢查流動性風險
LiquidityAnalysis 檢測策略在進出場時是否會遇到流動性問題:
- 成交量不足:單日成交股數低於要求
- 成交金額不足:單日成交金額低於要求
- 漲跌停板:在漲停買入或跌停賣出(無法成交)
- 警示股/處置股/全額交割股:交易受限的高風險標的
LiquidityAnalysis(
required_volume=200000, # 要求單日成交股數(預設 20 萬股)
required_turnover=1000000, # 要求單日成交金額(預設 100 萬元)
)# 檢測策略流動性風險
report.run_analysis('LiquidityAnalysis', required_volume=100000)結果表有 entry(進場)與 exit(出場)兩列,每一欄是有該項風險的交易比例:
| 風險項目 | 說明 |
|---|---|
| buy_high | 在漲停板(或接近漲停)買入的比例 |
| sell_low | 在跌停板(或接近跌停)賣出的比例 |
| low_volume_stocks | 成交股數不足 required_volume 的比例 |
| low_turnover_stocks | 成交金額不足 required_turnover 的比例 |
| 警示股 | 交易警示股的比例 |
| 處置股 | 交易處置股的比例 |
| 全額交割股 | 交易全額交割股的比例 |
發現問題時:
- 流動性不足:考慮增加選股條件(如「成交量 > 1000 張」)
- 頻繁觸及漲跌停:調整進出場時機(如使用開盤價而非收盤價)
- 過多警示/處置股:加入風控條件排除高風險標的
延伸閱讀:流動性檢測文章
用 MAE/MFE 設定停損停利
MAE/MFE 分析是優化停損停利的核心工具,用 12 個子圖呈現每筆交易的波動輪廓:
- MAE(Maximum Adverse Excursion):持有期間最大不利方向幅度(最大虧損)
- BMFE(Before-MAE MFE):MAE 發生前的最大有利方向幅度
- GMFE(Global MFE):持有期間最大有利方向幅度(最大獲利)
- MDD(Maximum Drawdown):持有期間最大回撤
MaeMfeAnalysis(
violinmode='group', # violin 圖模式: 'group' 或 'overlay'
mfe_scatter_x='mae', # MFE 散點圖的 X 軸: 'mae' 或 'return'
)有兩種執行方式,report.display_mae_mfe_analysis() 是快捷方法:
# 方法 1: 使用 display_mae_mfe_analysis() 快捷方法
report.display_mae_mfe_analysis()
# 方法 2: 使用 run_analysis()
report.run_analysis('MaeMfeAnalysis', violinmode='group')下圖是歷史回測快速上手策略的 MAE/MFE 分析:
from finlab import data
from finlab import backtest
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
# 持有近 6 個月漲幅最強的 20 檔 S&P 500 股票
position = (close / close.shift(126)).is_largest(20)
# 回測,每月底(M)換股
report = backtest.sim(position, resample='M', name='S&P 500 momentum top 20')
# 方法 1: 使用 display_mae_mfe_analysis() 快捷方法
report.display_mae_mfe_analysis()
# 方法 2: 使用 run_analysis()
report.run_analysis('MaeMfeAnalysis', violinmode='group')看懂 12 個子圖
| 位置 | 子圖 | 怎麼看 |
|---|---|---|
| 第一排 | 1. Win Ratio:交易勝率分布(profit vs loss 直方圖) | 藍色是獲利交易、粉色是虧損交易;綠色虛線是平均報酬率 |
| 第一排 | 2. Edge Ratio:不同持有天數的 MFE/MAE 比率 | 數值 > 1 代表獲利幅度大於虧損幅度;觀察策略的「優勢時間區間」 |
| 第一排 | 3. MAE vs Return:MAE 與最終報酬率的關係 | 水平虛線是 MAE 的 75 分位數(Q3);用於設定停損點 |
| 第二排 | 4. GMFE vs MAE:最大獲利 vs 最大虧損 | Q3 虛線是 75% 交易的 GMFE 和 MAE 範圍;用於評估停利空間 |
| 第二排 | 5. BMFE vs MAE:MAE 前的最大獲利 | 數值越高,代表越有機會在停損前先停利;用於優化「移動停利」策略 |
| 第二排 | 6. MDD vs GMFE:最大回撤 vs 最大獲利 | 橘線是 MDD = GMFE 的基準線;點在線下方代表 GMFE > MDD(好訊號,可用移動停利),點在線上方代表 MDD > GMFE(壞訊號,會錯失獲利) |
| 第三排 | 7–9. MAE/BMFE/GMFE Distribution:各指標的分布與密度 | 直方圖 + 密度曲線;藍色是獲利交易、粉色是虧損交易;Q3 虛線是 75 分位數 |
| 第四排 | 10. Indices Stats(Violin Plot):6 個關鍵指標的統計分布 | 指標是 return、mae、bmfe、gmfe、mdd、pdays_ratio;group 模式分群顯示獲利/虧損交易的差異(推薦),overlay 模式疊加顯示所有交易 |
依分析結果設定停損停利
停損點:參考 MAE 的 75 分位數(Q3),設得比 Q3 略寬,避免過度停損。
# 1. 查看 MAE 的 75 分位數(Q3)
trades = report.get_trades()
mae_q3 = trades['mae'].quantile(0.75) # 例如 -8.5%
# 2. 設定停損略寬於 Q3,避免過度停損
stop_loss = abs(mae_q3) * 1.2 # 設為 10%停利點:參考 GMFE 的 75 分位數,設得略低,確保大部分獲利能實現。
# 查看 GMFE 的 75 分位數
gmfe_q3 = trades['gmfe'].quantile(0.75) # 例如 15%
# 設定停利
take_profit = gmfe_q3 * 0.8 # 設為 12%,確保大部分獲利能實現要不要用移動停利:看「MDD vs GMFE」圖的 Missed Win-profits PCT。
- Missed Win-profits PCT < 20%:適合使用移動停利
- Missed Win-profits PCT > 40%:不適合移動停利(會提前出場錯失更大獲利)
# 使用移動停利(透過 sim() 的 trail_stop 參數)
entries = close > close.average(20)
exits = close < close.average(60)
position = entries.hold_until(exit=exits, stop_loss=0.1)
# 最高報酬回落超過 8 個百分點時產生訊號
report = sim(position, resample='M', trail_stop=0.08)延伸閱讀:MAE/MFE 分析文章
檢查各時期的表現是否穩定
PeriodStatsAnalysis 分析策略在不同時期(年度、月度、近期)的表現,並與對照指數(benchmark;美股是 S&P 500)比較,檢驗策略是否穩定。
report.run_analysis('PeriodStatsAnalysis')會產生多個表格:
1. Overall Stats(整體表現):比較策略與 benchmark 在日度、月度、年度的整體指標,每個指標(calmar_ratio、sortino_ratio、sharpe_ratio、profit_factor、tail_ratio、return、volatility)分別用日、月、年報酬計算。節錄如下:
| 指標 | benchmark | strategy |
|---|---|---|
overall_daily / calmar_ratio | 0.149 | 0.066 |
overall_daily / sharpe_ratio | 0.532 | 0.306 |
overall_monthly / return | 0.084 | 0.048 |
2. Yearly Stats(年度表現):熱力圖顯示每年的同樣 7 種指標。
3. Recent Stats(近期表現):顯示最近 M(月)、Q(季)、HY(半年)、Y(年)、3Y(三年)的表現。
怎麼判斷:
- 策略 sharpe_ratio < benchmark:策略風險調整後報酬不如大盤,需改進
- 某些年度表現特別差:檢查是否遇到特定市場環境(如空頭)
- 近期表現大幅衰退:策略可能失效,需重新優化
看因子能不能篩出好交易
InequalityAnalysis 檢查只保留因子值低於或高於門檻 x 的交易時,交易報酬如何變化(逐一試過每個門檻)。交易依進場訊號日的因子值排序。上方兩張圖是每個門檻保留的交易平均報酬減去全部交易的平均報酬(左邊是 <name> < x,右邊是 <name> > x);下方兩張圖是每個切點的因子值與交易筆數。
InequalityAnalysis(
name, # 因子名稱:交易紀錄的欄位名稱與圖表標籤
df=None, # 因子值(日期 × 股票);None 時自動用 data.get(name) 取得
date_type='entry_sig_date', # 'entry_date'、'entry_sig_date'、'exit_date' 或 'exit_sig_date'
target='return', # report.get_trades() 的任一欄位
)執行分析的均線策略,股價淨值比低的股票交易表現比較好嗎?
report.run_analysis('InequalityAnalysis', name='price_earning_ratio:股價淨值比')簡單的趨勢策略裡,市值較大的 S&P 500 公司交易表現比較好嗎?
from finlab import data
from finlab.analysis.inequalityAnalysis import InequalityAnalysis
from finlab.backtest import sim
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
market_value = data.get('etl:us_market_value')
position = close > close.average(20)
report = sim(position, resample='M')
report.run_analysis(InequalityAnalysis(name='market_value', df=market_value))怎麼判斷:
- 曲線隨門檻移動穩定上升:因子能區分好壞交易,可考慮加入當作濾網
- 曲線平坦、接近 0:用這個因子過濾不會改變報酬
- 只保留少數交易的門檻:結果雜訊大,先看下方圖表的交易筆數再決定
衡量超額報酬與市場連動
AlphaBetaAnalysis 衡量策略相對於對照指數的:
- Alpha(α):超額報酬(策略獨特的價值)
- Beta(β):市場敏感度(與大盤的相關性)
report.run_analysis('AlphaBetaAnalysis')結果分成三個區塊:
- 整體摘要:整段回測的 Alpha 與 Beta。Alpha 是年化超額報酬(扣除市場風險後的真實貢獻);Beta 是市場敏感度,例如 Beta 0.8 代表市場上漲 1%,策略預期上漲 0.8%。
- 年度 Alpha/Beta:各年度的 Alpha 與 Beta 拆解,方便觀察策略是否穩定。
- 近期 Alpha/Beta:全期、近 1 月、近 3 月、近 6 月、近 1 年的表現。
要取得原始資料(dict),加上 display=False:
result = report.run_analysis('AlphaBetaAnalysis', display=False)
# result['overall'] → {'alpha': ..., 'beta': ...}
# result['yearly'] → {'alpha': [...], 'beta': [...], 'year': [...]}
# result['recent'] → {'alpha': [...], 'beta': [...], 'ndays': [...]}怎麼判斷:
- Alpha > 0:策略有超額報酬,值得執行
- Alpha < 0:策略不如對照指數,需改進
- Beta > 1.5:策略波動極大,風險高
- Beta < 0:策略與市場負相關(適合避險)
看回撤有多深、多久
DrawdownAnalysis 列出策略與對照指數的回撤:
- 回撤的時間序列
- 持續最久的 5 次回撤
- 跌最深的 5 次回撤
result = report.run_analysis('DrawdownAnalysis')結果是一個 dict,strategy 與 benchmark 各有:
drawdown:回撤序列(index與values)longest_drawdown:持續最久的 5 次回撤,以開始日期為 key,內含End(結束日)、Length(天數)與drawdown(深度)largest_drawdown:跌最深的 5 次回撤,格式相同
怎麼判斷:
- 最大回撤 > -50%:風險過高,需增加風控措施
- 回撤持續時間 > 1 年:策略可能長期失效
- 近期回撤頻繁:市場環境改變,需重新評估策略
自訂分析模組
內建分析不夠用時,繼承 Analysis 類別開發自己的分析。
基礎範例:依市值分組看報酬
依進場訊號日的公司市值分成五組,看各組的平均報酬:
import pandas as pd
from finlab import data
from finlab.analysis import Analysis
class MarketValueAnalysis(Analysis):
def calculate_trade_info(self, report):
"""加入每筆交易在進場訊號日的市值"""
return [['市值', data.get('etl:market_value'), 'entry_sig_date']]
def analyze(self, report):
"""依市值五分位計算平均報酬"""
trades = report.get_trades()
quintile = pd.qcut(trades['市值@entry_sig_date'], 5, labels=False) + 1
self.result = trades.groupby(quintile)['return'].agg(['count', 'mean'])
return self.result.to_dict()
def display(self):
"""顯示分析結果"""
return self.result
# 使用自訂分析
report.run_analysis(MarketValueAnalysis())import pandas as pd
from finlab import data
from finlab.analysis import Analysis
class MarketValueAnalysis(Analysis):
def calculate_trade_info(self, report):
"""加入每筆交易在進場訊號日的市值"""
return [['market_value', data.get('etl:us_market_value'), 'entry_sig_date']]
def analyze(self, report):
"""依市值五分位計算平均報酬"""
trades = report.get_trades()
quintile = pd.qcut(trades['market_value@entry_sig_date'], 5, labels=False) + 1
self.result = trades.groupby(quintile)['return'].agg(['count', 'mean'])
return self.result.to_dict()
def display(self):
"""顯示分析結果"""
return self.result
# 使用自訂分析
report.run_analysis(MarketValueAnalysis())calculate_trade_info() 會在 analyze() 之前執行,自動把額外資訊加入 report.get_trades() 的 DataFrame。例如回傳 [['市值', market_value, 'entry_sig_date']],trades 就會多一個 '市值@entry_sig_date' 欄位。
進階範例:各月份的勝率
分析每個月份的勝率是否有差異:
import pandas as pd
from finlab.analysis import Analysis
class MonthlyWinRateAnalysis(Analysis):
def analyze(self, report):
trades = report.get_trades()
trades['entry_month'] = pd.to_datetime(trades['entry_date']).dt.month
monthly_stats = (
trades.groupby('entry_month')
.agg({'return': ['count', lambda x: (x > 0).mean(), 'mean']})
.round(3)
)
monthly_stats.columns = ['交易次數', '勝率', '平均報酬']
self.result = monthly_stats
return self.result.to_dict()
def display(self):
import plotly.express as px
fig = px.bar(self.result, y='勝率', title='月度勝率分析')
return fig
# 使用
report.run_analysis(MonthlyWinRateAnalysis())Analysis 類別可覆寫的方法
class Analysis:
def is_market_supported(self, market):
"""檢查是否支援該市場
Returns:
bool: True 表示支援,False 表示不支援
"""
return True
def calculate_trade_info(self, report):
"""計算額外的交易資訊
Returns:
list: 格式為 [[欄位名, 資料 DataFrame, 使用的日期欄位], ...]
Examples:
return [
['市值', market_value, 'entry_sig_date'],
['成交量', volume, 'entry_date'],
]
"""
return []
def analyze(self, report):
"""執行分析邏輯
Args:
report: 回測報告物件
Returns:
dict: 分析結果(用於上傳至雲端)
"""
pass
def display(self):
"""顯示分析結果
Returns:
可視化物件(推薦使用 Plotly Figure 或 Pandas Styler)
"""
pass一次跑完常用分析
from finlab import data
from finlab.backtest import sim
# 完成回測
close = data.get('price:收盤價')
position = close > close.average(20)
report = sim(position, resample='M')
# 基本績效
report.display()
# 1. 流動性檢測(大資金必檢)
report.run_analysis('LiquidityAnalysis', required_volume=100000)
# 2. 波動分析
report.display_mae_mfe_analysis()
# 3. 時期穩定性分析
report.run_analysis('PeriodStatsAnalysis')
# 4. Alpha/Beta 分析
report.run_analysis('AlphaBetaAnalysis')
# 5. 自訂市值分析
report.run_analysis(MarketValueAnalysis())from finlab import data
from finlab.backtest import sim
# 完成回測
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
position = close > close.average(20)
report = sim(position, resample='M')
# 基本績效
report.display()
# 1. 波動分析
report.display_mae_mfe_analysis()
# 2. 時期穩定性分析
report.run_analysis('PeriodStatsAnalysis')
# 3. Alpha/Beta 分析
report.run_analysis('AlphaBetaAnalysis')
# 4. 自訂市值分析
report.run_analysis(MarketValueAnalysis())依策略類型挑重點
| 策略類型 | 重點分析模組 |
|---|---|
| 短線交易(日內、週線) | MaeMfeAnalysis(優化停損停利) |
| 長線投資(月線、季線) | PeriodStatsAnalysis(檢視長期穩定性) |
| 大資金策略(> 1000 萬) | LiquidityAnalysis(避免流動性風險) |
| 市場中性策略 | AlphaBetaAnalysis(確認 Beta 接近 0) |
| 高頻交易 | DrawdownAnalysis(確認回撤可控) |
依結果決定下一步
依序檢查,遇到「是」就先處理:
| 檢查 | 是的話 |
|---|---|
| 流動性風險 > 20%? | 增加成交量篩選條件 |
| 夏普率 < 1? | 優化選股條件或停損停利 |
| 最大回撤 > -30%? | 增加風控措施(停損、部位控制) |
| Alpha < 0? | 策略無超額報酬,需重新設計 |
所有檢查都通過後,進行樣本外測試。
下一步
- 歷史回測
sim()的完整參數與回測報告。 - 策略參數優化 一次回測所有條件組合,再用這些分析深入檢查前幾名。
- 從研究到實盤 完整的策略開發流程。
- finlab.analysis 每個分析模組的完整參數。
這一頁有幫到你嗎?