API 參考
finlab.tools
本頁內容6
finlab.tools 有兩組分析工具。event_study 看某類事件(例如除權、財報公布)前後,股價有沒有超出大盤的異常報酬;factor_analysis 評估選股因子的報酬、和未來報酬的相關性、在組合裡的貢獻與擁擠度,幫你理解策略的績效從哪裡來、該留哪些因子。
選哪個函式
| 你想做的事 | 用這個 |
|---|---|
| 整理事件與事件後的報酬資料 | create_factor_data() |
| 算每次事件前後每一天的異常報酬 | event_study() |
| 畫出事件前後的平均異常報酬 | plot_event_study() |
| 把布林因子轉成特徵與下一期超額報酬標籤 | fa.generate_features_and_labels() |
| 評估因子的整體表現 | fa.calc_factor_return() |
| 評估因子與未來報酬的相關性 | fa.calc_ic() |
| 比較含有各因子的組合表現 | fa.calc_shapley_values() |
| 監控因子擁擠度 | fa.calc_centrality() |
| 偵測集中度、IC 或貢獻度的趨勢 | fa.calc_regression_stats() |
研究事件前後的股價反應
事件研究用 CAPM 模型:每個事件先用事件之前的一段日報酬,對大盤擬合出 beta,再算事件前後每一天的異常報酬(實際報酬減掉 beta 乘大盤報酬)。適合分析財報公告、法說會、股利發放這類事件對股價的短期與長期影響。
下面分析除權交易日前後的股價表現:
from finlab import data
from finlab.tools.event_study import create_factor_data, event_study
factor = data.get('price_earning_ratio:股價淨值比')
adj_close = data.get('etl:adj_close')
benchmark = data.get('benchmark_return:發行量加權股價報酬指數')
# 事件:每檔股票的除權交易日
dividend_info = data.get('dividend_announcement')
v = dividend_info[['stock_id', '除權交易日']].set_index(['stock_id', '除權交易日'])
v['value'] = 1
event = v[~v.index.duplicated()].reset_index().drop_duplicates(
subset=['stock_id', '除權交易日']
).pivot(index='除權交易日', columns='stock_id', values='value').notna()
factor_data = create_factor_data({'pb': factor}, adj_close, event=event)
abnormal_returns = event_study(factor_data, benchmark, adj_close)event_study() 回傳的表每一列是一次事件、每一欄是相對事件日的一天,內容是異常報酬;預設 estimation_period=(-5, 20),欄位就是 -5 到 19。sample_period=(-45, -20) 是擬合 beta 用的區間,所以事件日之前要留至少 45 個交易日的價格。plot=True(預設)會順便用 plot_event_study() 畫圖。
finlab.tools.event_study
event_study(factor_data, benchmark_adj_close, stock_adj_close, sample_period, estimation_period, plot) -> pd.DataFrameRun event study and returns the abnormal returns of each stock on each day.
參數
factor_datapd.DataFramefactor data where index is datetime and columns is asset id
benchmark_adj_closepd.DataFramebenchmark for CAPM
stock_adj_closepd.DataFramestock price for CAPM
sample_period(int, int)預設(-45, -20)period for fitting CAPM
estimation_period(int, int)預設(-5, 20)period for calculating alpha (abnormal return)
plotbool預設Trueplot the result
範例
from finlab.tools.event_study import create_factor_data
from finlab.tools.event_study import event_study
factor = data.get('price_earning_ratio:股價淨值比')
adj_close = data.get('etl:adj_close')
benchmark = data.get('benchmark_return:發行量加權股價報酬指數')
# create event dataframe
dividend_info = data.get('dividend_announcement')
v = dividend_info[['stock_id', '除權交易日']].set_index(['stock_id', '除權交易日'])
v['value'] = 1
event = v[~v.index.duplicated()].reset_index().drop_duplicates(
subset=['stock_id', '除權交易日']
).pivot(index='除權交易日', columns='stock_id', values='value').notna()
# calculate factor_data
factor_data = create_factor_data({'pb':factor}, adj_close, event=event)
r = event_study(factor_data, benchmark, adj_close)
plt.bar(r.columns, r.mean().values)
plt.plot(r.columns, r.mean().cumsum().values)create_factor_data()
整理 event_study() 要用的資料:每個(日期, 股票)的因子值、因子五分位與未來報酬。
from finlab.tools.event_study import create_factor_data
factor_data = create_factor_data({'pb': factor}, adj_close, event=event)
factor_data.head()| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
factor | DataFrame 或 dict[str, DataFrame] | 必填 | 因子值,索引是日期、欄位是股票代號;用字典可以一次放多個因子並命名。 |
adj_close | DataFrame | 必填 | 還原收盤價,索引是日期、欄位是股票代號。 |
days | list[int] | None | 未來報酬的天數(交易日);不指定時是 [5, 10, 20, 60]。 |
event | 布林 DataFrame | None | 事件標記,索引是事件日期、欄位是股票代號。有給時只保留有事件的(日期, 股票)。 |
回傳以 (date, asset) 為索引的 DataFrame,欄位有 <名稱>_factor、<名稱>_factor_quantile(每天橫向切成五分位,1 最低到 5 最高,和 df.cs.bucket(5) 相同),以及每個天數一欄的 <n>D 未來報酬。落在非交易日的事件日期,會移到 adj_close 的下一個交易日。
這個函式和 finlab.ml.alphalens.create_factor_data 不一樣。
plot_event_study()
把事件研究的結果畫成圖:每天的平均異常報酬畫成長條,平均累積異常報酬畫成折線,加上 0.1 個標準差的範圍帶。
from finlab.tools.event_study import plot_event_study
plot_event_study(abnormal_returns)| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
returns | DataFrame | 必填 | event_study() 的結果。 |
回傳 matplotlib.axes.Axes。
分析因子的選股能力
factor_analysis 的主要函式都接收兩個參數:features 是以 (日期, 股票) 為索引、每個因子一欄的 DataFrame;labels 是索引相同的 Series,通常是下一期的超額報酬。可以用 generate_features_and_labels() 一次產生,或用 finlab.ml.feature.combine() 與 finlab.ml.label.excess_over_mean() 自行組合。
finlab.tools.factor_analysis
Factor analysis toolkit -- public facade.
All public symbols are re-exported from focused submodules so that
existing from finlab.tools.factor_analysis import ... imports keep
working unchanged.
Submodules
finlab.tools.factor_metrics-- IC, correlation, NDCG scoringfinlab.tools.factor_returns-- boolean factor returns & Shapley valuesfinlab.tools.factor_centrality-- PCA-based rolling centralityfinlab.tools.factor_regression-- OLS trend analysis
fa.generate_features_and_labels()
把因子字典轉成特徵表和超額報酬標籤。
from finlab import data
from finlab.tools import factor_analysis as fa
marketcap = data.get('etl:market_value')
revenue = data.get('monthly_revenue:當月營收')
close = data.get('price:收盤價')
revenue_growth = revenue.average(3) / revenue.average(12)
momentum = close / close.shift(20)
# 布林因子 → 特徵(日期 × 股票)與下個月的超額報酬標籤
factors = {
'small_cap': marketcap.rank(pct=True, axis=1) < 0.3, # 小市值
'revenue_growth': revenue_growth.rank(pct=True, axis=1) > 0.7, # 營收成長
'momentum': momentum.rank(pct=True, axis=1) > 0.7, # 動能
}
features, labels = fa.generate_features_and_labels(factors, resample='ME')| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
dfs | dict | 必填 | 因子名稱對應 DataFrame(或回傳 DataFrame 的函式)。 |
resample | str | 必填 | 取樣頻率,交給 feature.combine() 與 label.excess_over_mean(),例如 'ME' 是每月底。 |
回傳 (features, labels)。下面的範例都延續這裡的 features、labels 和因子。
fa.calc_factor_return()
算出每一期各布林因子選出股票的等權平均報酬,用來評估因子的整體表現。
factor_return = fa.calc_factor_return(features, labels)
factor_return.cumsum().plot(title='因子累積報酬')
print(factor_return.mean()) # 每月選出股票的平均超額報酬| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
features | DataFrame | 必填 | 布林特徵,索引是(日期, 股票)。有欄位不是布林值時會丟出 ValueError。 |
labels | Series | 必填 | 索引相同的超額報酬標籤。 |
回傳每一期、每個因子一欄的報酬表。怎麼解讀:
- 正值:因子有選股能力(選中的股票表現較好)
- 負值:因子反向有效(應反向操作)
- 接近 0:因子無選股能力
fa.calc_ic()
算出每一期因子值與標籤的相關係數(IC),評估因子與未來報酬的相關性。IC 需要連續的因子值,不是布林值:
from finlab.ml import feature
features_raw = feature.combine(
{'small_cap': -marketcap, 'revenue_growth': revenue_growth, 'momentum': momentum},
resample='ME',
)
ic = fa.calc_ic(features_raw, labels, rank=True)
print(ic.mean() / ic.std()) # IC_IR| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
features | DataFrame | 必填 | 因子值,索引是(日期, 股票)。 |
labels | Series | 必填 | 索引相同的標籤。 |
rank | bool | False | True 時算 Spearman 等級相關,否則是 Pearson。 |
回傳每一期、每個因子一欄的 IC。IC 的評估標準:
| IC 值 | 評價 | 說明 |
|---|---|---|
| > 0.05 | 優秀 | 因子有強選股能力 |
| 0.03 ~ 0.05 | 良好 | 因子有選股能力 |
| 0.01 ~ 0.03 | 一般 | 因子弱選股能力 |
| < 0.01 | 無效 | 因子無選股能力 |
fa.calc_shapley_values()
算出每一期各布林因子的貢獻度:每個因子組合的報酬,平分給組合內的因子。
shapley = fa.calc_shapley_values(features, labels)
print(shapley.mean())| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
features | DataFrame | 必填 | 布林特徵,索引是(日期, 股票)。 |
labels | Series | 必填 | 索引相同的超額報酬標籤。 |
回傳每一期、每個因子一欄的貢獻度。它的算法,以及和賽局論 Shapley 值的差異,見分析策略因子。
fa.calc_centrality()
算出因子報酬的滾動 PCA 集中度,用來監控因子擁擠度。
# 12 個月窗口的集中度(擁擠度)
centrality = fa.calc_centrality(factor_return, 12)| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
return_df | DataFrame | 必填 | 以日期為索引的報酬表,例如 calc_factor_return() 的結果。 |
window_periods | int | 必填 | 滾動窗口的列數。 |
n_components | int | 1 | PCA 的成分數。 |
fa.calc_regression_stats()
對每一欄做隨時間的線性迴歸,判斷趨勢是 up、down 還是 flat,用來偵測集中度、IC 或貢獻度的變化。
print(fa.calc_regression_stats(centrality))| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
df | DataFrame | 必填 | 以日期(DatetimeIndex)為索引的時間序列。 |
p_value_threshold | float | 0.05 | 判斷趨勢的顯著水準。 |
r_squared_threshold | float | 0.1 | 判定不是 flat 的最低 R 平方。 |
回傳每一欄一列的表,欄位有 slope、intercept、r_squared、p_value、tail_estimate 和 trend。
從因子分析到回測的完整範例
from finlab import data
from finlab.backtest import sim
from finlab.tools import factor_analysis as fa
# 步驟 1:建立因子
marketcap = data.get('etl:market_value')
revenue = data.get('monthly_revenue:當月營收')
close = data.get('price:收盤價')
revenue_growth = revenue.average(3) / revenue.average(12)
momentum = close / close.shift(20)
factors = {
'small_cap': marketcap.rank(pct=True, axis=1) < 0.3, # 小市值
'revenue_growth': revenue_growth.rank(pct=True, axis=1) > 0.7, # 營收成長
'momentum': momentum.rank(pct=True, axis=1) > 0.7, # 動能
}
# 步驟 2:特徵與標籤(下個月的超額報酬)
features, labels = fa.generate_features_and_labels(factors, resample='ME')
# 步驟 3:分析各因子表現
print("=== 平均每月因子報酬 ===")
print(fa.calc_factor_return(features, labels).mean())
# 步驟 4:計算 Shapley values(貢獻度)
print("=== 平均 Shapley 值 ===")
print(fa.calc_shapley_values(features, labels).mean())
# 步驟 5:回測策略
position = factors['small_cap'] & factors['revenue_growth'] & factors['momentum']
report = sim(position, resample='M')
report.display()
stats = report.get_stats()
print(f"年化報酬: {stats['cagr']:.2%}")
print(f"夏普率: {stats['daily_sharpe']:.2f}")
print(f"最大回撤: {stats['max_drawdown']:.2%}")常見問題
如何判斷一個因子是否有效?
使用三個指標綜合判斷:
# 1. 因子報酬(應顯著 > 0)
print(factor_return.mean())
# 2. IC(應 > 0.02)
print(ic.mean())
# 3. IC_IR(應 > 0.5)
print(ic.mean() / ic.std())
# 結論:三個指標都合格才認為因子有效Shapley values 如何應用於策略優化?
# 各因子的平均貢獻度
contribution = shapley.mean()
# 保留貢獻度為正的因子;都不為正時保留最好的一個
kept = contribution[contribution > 0].index
if kept.empty:
kept = contribution.nlargest(1).index
print(f"原始因子數: {len(contribution)}")
print(f"優化後因子數: {len(kept)}")
# 使用優化後的因子組合
optimized_position = factors[kept[0]]
for name in kept[1:]:
optimized_position = optimized_position & factors[name]IC 和因子報酬有什麼不同?
IC(Information Coefficient):
- 衡量因子值與未來報酬的相關性
- 範圍 [-1, 1],越接近 1 越好
- 適合評估連續型因子(如市值、ROE)
因子報酬:
- 衡量因子選出的股票的平均報酬
- 適合評估二元因子(True/False)
- 直接反映選股效果
from finlab.ml import feature
pe = data.get('price_earning_ratio:本益比')
# 連續型因子用 IC
continuous = feature.combine({'pe': pe}, resample='ME')
ic = fa.calc_ic(continuous, labels, rank=True)
# 二元因子用因子報酬
binary = feature.combine({'low_pe': pe.rank(pct=True, axis=1) < 0.3}, resample='ME')
factor_return = fa.calc_factor_return(binary, labels)下一步
這一頁有幫到你嗎?