文件
開啟 Studio

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.DataFrame

Run event study and returns the abnormal returns of each stock on each day.

參數

factor_data pd.DataFrame

factor data where index is datetime and columns is asset id

benchmark_adj_close pd.DataFrame

benchmark for CAPM

stock_adj_close pd.DataFrame

stock 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)

plot bool 預設 True

plot 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()
參數型別預設說明
factorDataFrame 或 dict[str, DataFrame]必填因子值,索引是日期、欄位是股票代號;用字典可以一次放多個因子並命名。
adj_closeDataFrame必填還原收盤價,索引是日期、欄位是股票代號。
dayslist[int]None未來報酬的天數(交易日);不指定時是 [5, 10, 20, 60]。
event布林 DataFrameNone事件標記,索引是事件日期、欄位是股票代號。有給時只保留有事件的(日期, 股票)。

回傳以 (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)
參數型別預設說明
returnsDataFrame必填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 scoring
  • finlab.tools.factor_returns -- boolean factor returns & Shapley values
  • finlab.tools.factor_centrality -- PCA-based rolling centrality
  • finlab.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')
參數型別預設說明
dfsdict必填因子名稱對應 DataFrame(或回傳 DataFrame 的函式)。
resamplestr必填取樣頻率,交給 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())  # 每月選出股票的平均超額報酬
參數型別預設說明
featuresDataFrame必填布林特徵,索引是(日期, 股票)。有欄位不是布林值時會丟出 ValueError。
labelsSeries必填索引相同的超額報酬標籤。

回傳每一期、每個因子一欄的報酬表。怎麼解讀:

  • 正值:因子有選股能力(選中的股票表現較好)
  • 負值:因子反向有效(應反向操作)
  • 接近 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
參數型別預設說明
featuresDataFrame必填因子值,索引是(日期, 股票)。
labelsSeries必填索引相同的標籤。
rankboolFalseTrue 時算 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())
參數型別預設說明
featuresDataFrame必填布林特徵,索引是(日期, 股票)。
labelsSeries必填索引相同的超額報酬標籤。

回傳每一期、每個因子一欄的貢獻度。它的算法,以及和賽局論 Shapley 值的差異,見分析策略因子。

fa.calc_centrality()

算出因子報酬的滾動 PCA 集中度,用來監控因子擁擠度。

# 12 個月窗口的集中度(擁擠度)
centrality = fa.calc_centrality(factor_return, 12)
參數型別預設說明
return_dfDataFrame必填以日期為索引的報酬表,例如 calc_factor_return() 的結果。
window_periodsint必填滾動窗口的列數。
n_componentsint1PCA 的成分數。

fa.calc_regression_stats()

對每一欄做隨時間的線性迴歸,判斷趨勢是 up、down 還是 flat,用來偵測集中度、IC 或貢獻度的變化。

print(fa.calc_regression_stats(centrality))
參數型別預設說明
dfDataFrame必填以日期(DatetimeIndex)為索引的時間序列。
p_value_thresholdfloat0.05判斷趨勢的顯著水準。
r_squared_thresholdfloat0.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)

下一步

↑↓ 選擇 Enter 開啟 Esc 關閉