API reference
finlab.tools
On this page6
finlab.tools holds two sets of analysis tools. event_study shows whether stocks earn abnormal returns, beyond the benchmark, around a type of event such as earnings announcements. factor_analysis evaluates stock-selection factors: their returns, their correlation with future returns, their contribution inside combinations and their crowding, so you can see where a strategy's performance comes from and which factors to keep.
Which function to use
| You want to | Use |
|---|---|
| Build event and future-return data | create_factor_data() |
| Compute each event's abnormal return on each day around it | event_study() |
| Plot the average abnormal return around events | plot_event_study() |
| Turn boolean factors into features and next-period excess-return labels | fa.generate_features_and_labels() |
| Evaluate overall factor performance | fa.calc_factor_return() |
| Evaluate factor correlation with future returns | fa.calc_ic() |
| Compare how the combinations containing each factor perform | fa.calc_shapley_values() |
| Monitor factor crowding | fa.calc_centrality() |
| Detect trends in centrality, IC or contributions | fa.calc_regression_stats() |
Study price reactions around events
An event study here uses a CAPM model: for each event, beta is fitted on the stock's daily returns against a benchmark over a window before the event, and the abnormal return (actual return minus beta times the benchmark return) is reported for each day around the event. Use it to analyze the short-term and long-term impact of events such as earnings announcements, investor conferences and dividend distributions.
How do S&P 500 stocks move around their earnings announcements?
from finlab import data
from finlab.tools.event_study import create_factor_data, event_study
data.set_market('us')
with data.us_universe(index='SP500'):
adj_close = data.get('price:adj_close')
eps_actual = data.get('us_earnings_surprises:eps_actual')
eps_estimated = data.get('us_earnings_surprises:eps_estimated')
benchmark = data.get('world_index:adj_close')[
['^GSPC']
] # S&P 500, one-column DataFrame
# Events: earnings announcements; leave room for the 45-day CAPM estimation window
event = eps_actual.notna().loc['2016-06':]
surprise = (eps_actual - eps_estimated) / eps_estimated.abs()
factor_data = create_factor_data({'surprise': surprise}, adj_close, event=event)
abnormal_returns = event_study(factor_data, benchmark, adj_close)create_factor_data()
Builds the factor data used by event_study(): factor values, factor quintiles and future returns for each (date, asset).
from finlab.tools.event_study import create_factor_data
factor_data = create_factor_data({'surprise': surprise}, adj_close, event=event)
factor_data.head()| Parameter | Type | Default | Description |
|---|---|---|---|
factor | DataFrame or dict[str, DataFrame] | required | Factor values, index = dates, columns = asset ids. A dict names several factors. |
adj_close | DataFrame | required | Adjusted close prices, index = dates, columns = asset ids. |
days | list[int] | None | Future-return horizons in trading days; None uses [5, 10, 20, 60]. |
event | boolean DataFrame | None | Event flags (index = event dates, columns = asset ids). When given, only (date, asset) pairs with an event are kept. |
Returns a DataFrame indexed by (date, asset) with <name>_factor, <name>_factor_quantile (the cross-sectional quintile on each date, 1 = lowest to 5 = highest, like df.cs.bucket(5)) and one <n>D future-return column per horizon. Event dates that fall on non-trading days move to the next trading day in adj_close.
This function is not identical to finlab.ml.alphalens.create_factor_data.
event_study()
Fits beta before each event and returns the abnormal return of each event on each day around it.
from finlab.tools.event_study import event_study
abnormal_returns = event_study(factor_data, benchmark, adj_close, plot=False)
abnormal_returns.mean() # Average abnormal return per day| Parameter | Type | Default | Description |
|---|---|---|---|
factor_data | DataFrame | required | Output of create_factor_data(); its (date, asset) index lists the events. |
benchmark_adj_close | DataFrame | required | Benchmark prices for the CAPM fit; the first column is used. |
stock_adj_close | DataFrame | required | Stock prices, index = dates, columns = asset ids. |
sample_period | (int, int) | (-45, -20) | Days relative to the event used to fit beta. |
estimation_period | (int, int) | (-5, 20) | Days relative to the event for which abnormal returns are computed. |
plot | bool | True | Plot the mean daily and cumulative abnormal return with plot_event_study(). |
Returns a DataFrame with one row per event and one column per day of estimation_period (for the defaults, -5 to 19), holding the abnormal returns.
plot_event_study()
Plots the mean abnormal return per day as bars and the mean cumulative abnormal return as a line with a 0.1 standard deviation band.
from finlab.tools.event_study import plot_event_study
plot_event_study(abnormal_returns)| Parameter | Type | Default | Description |
|---|---|---|---|
returns | DataFrame | required | The output of event_study(). |
Returns a matplotlib.axes.Axes.
Analyze factors
The main factor_analysis functions take features (a DataFrame indexed by (date, stock) with one column per factor) and labels (a Series with the same index, typically next-period excess returns), as built by generate_features_and_labels() or by finlab.ml.feature.combine() and 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()
Turns a dict of factors into a feature table and excess-return labels.
from finlab import data
from finlab.tools import factor_analysis as fa
data.set_market('us')
with data.us_universe(index='SP500'):
marketcap = data.get('etl:us_market_value')
revenue = data.get('us_income_statement:revenue')
close = data.get('price:adj_close')
# Boolean factors -> features (date x stock) and next-month excess-return labels
factors = {
'small_cap': marketcap.rank(pct=True, axis=1) < 0.3,
'revenue_growth': (revenue / revenue.shift(4)).rank(pct=True, axis=1) > 0.7,
'momentum': (close / close.shift(20)).rank(pct=True, axis=1) > 0.7,
}
features, labels = fa.generate_features_and_labels(factors, resample='ME')| Parameter | Type | Default | Description |
|---|---|---|---|
dfs | dict | required | Factor name to DataFrame (or a callable that returns one). |
resample | str | required | Sampling frequency passed to feature.combine() and label.excess_over_mean(), e.g. 'ME' for month end. |
Returns (features, labels). The examples below continue from these features, labels and factors.
fa.calc_factor_return()
Computes the equal-weight return of the stocks each boolean factor selects, per date, to evaluate overall factor performance.
factor_return = fa.calc_factor_return(features, labels)
factor_return.cumsum().plot(title='Factor cumulative return')
print(factor_return.mean()) # Average excess return of the selected stocks, per month| Parameter | Type | Default | Description |
|---|---|---|---|
features | DataFrame | required | Boolean features indexed by (date, stock). A column that is not boolean raises ValueError. |
labels | Series | required | Excess-return labels with the same index. |
Returns a table of per-date returns, one column per factor. How to read it:
- Positive: factor has stock selection ability (selected stocks outperform)
- Negative: factor works in reverse (should inverse the signal)
- Near 0: factor has no stock selection ability
fa.calc_ic()
Computes the correlation (IC) between factor values and labels per date, to evaluate factor correlation with future returns. IC needs continuous factor values, not booleans:
from finlab.ml import feature
features_raw = feature.combine(
{
'small_cap': -marketcap,
'revenue_growth': revenue / revenue.shift(4),
'momentum': close / close.shift(20),
},
resample='ME',
)
ic = fa.calc_ic(features_raw, labels, rank=True)
print(ic.mean() / ic.std()) # IC_IR| Parameter | Type | Default | Description |
|---|---|---|---|
features | DataFrame | required | Factor values indexed by (date, stock). |
labels | Series | required | Labels with the same index. |
rank | bool | False | True computes Spearman rank correlation; otherwise Pearson. |
Returns a table of IC values per date, one column per factor. IC evaluation standards:
| IC Value | Rating | Description |
|---|---|---|
| > 0.05 | Excellent | Factor has strong stock selection ability |
| 0.03 ~ 0.05 | Good | Factor has stock selection ability |
| 0.01 ~ 0.03 | Average | Factor has weak stock selection ability |
| < 0.01 | Ineffective | Factor has no stock selection ability |
fa.calc_shapley_values()
Computes each boolean factor's contribution per date: each factor combination's return is split evenly among its factors.
shapley = fa.calc_shapley_values(features, labels)
print(shapley.mean())| Parameter | Type | Default | Description |
|---|---|---|---|
features | DataFrame | required | Boolean features indexed by (date, stock). |
labels | Series | required | Excess-return labels with the same index. |
Returns a table of per-date contributions, one column per factor. For how it is computed and how it differs from the game-theoretic Shapley value, see Analyzing strategy factors.
fa.calc_centrality()
Computes the rolling PCA centrality of factor returns, to monitor factor crowding.
# Centrality (crowding) over a 12-month window
centrality = fa.calc_centrality(factor_return, 12)| Parameter | Type | Default | Description |
|---|---|---|---|
return_df | DataFrame | required | Date-indexed returns, such as the output of calc_factor_return(). |
window_periods | int | required | Rolling window length in rows. |
n_components | int | 1 | Number of PCA components. |
fa.calc_regression_stats()
Fits a linear trend to each column over time and classifies it as up, down or flat, to detect trends in centrality, IC or contributions.
print(fa.calc_regression_stats(centrality))| Parameter | Type | Default | Description |
|---|---|---|---|
df | DataFrame | required | Time series with a DatetimeIndex. |
p_value_threshold | float | 0.05 | Significance level for the trend. |
r_squared_threshold | float | 0.1 | Minimum R-squared for a trend that is not flat. |
Returns one row per column with slope, intercept, r_squared, p_value, tail_estimate and trend.
Complete example: from factor analysis to a backtest
from finlab import data
from finlab.backtest import sim
from finlab.tools import factor_analysis as fa
# Step 1: Build factors
data.set_market('us')
with data.us_universe(index='SP500'):
marketcap = data.get('etl:us_market_value')
revenue = data.get('us_income_statement:revenue')
close = data.get('price:adj_close')
factors = {
'small_cap': marketcap.rank(pct=True, axis=1) < 0.3,
'revenue_growth': (revenue / revenue.shift(4)).rank(pct=True, axis=1) > 0.7,
'momentum': (close / close.shift(20)).rank(pct=True, axis=1) > 0.7,
}
# Step 2: Features and labels (next-month excess return)
features, labels = fa.generate_features_and_labels(factors, resample='ME')
# Step 3: Analyze each factor
print("=== Average monthly factor return ===")
print(fa.calc_factor_return(features, labels).mean())
# Step 4: Shapley values (contribution)
print("=== Average Shapley value ===")
print(fa.calc_shapley_values(features, labels).mean())
# Step 5: Backtest the combined strategy
position = factors['small_cap'] & factors['revenue_growth'] & factors['momentum']
report = sim(position, resample='M')
report.display()
stats = report.get_stats()
print(f"Annual return: {stats['cagr']:.2%}")
print(f"Sharpe ratio: {stats['daily_sharpe']:.2f}")
print(f"Max drawdown: {stats['max_drawdown']:.2%}")Common questions
How do I tell whether a factor is effective?
Use three metrics together:
# 1. Factor return (should be clearly > 0)
print(fa.calc_factor_return(features, labels).mean())
# 2. IC (should be > 0.02)
ic = fa.calc_ic(features_raw, labels, rank=True)
print(ic.mean())
# 3. IC_IR (should be > 0.5)
print(ic.mean() / ic.std())
# Conclusion: Factor is effective only when all three metrics passHow do I apply Shapley values to strategy optimization?
# Average contribution of each factor
contribution = fa.calc_shapley_values(features, labels).mean()
# Keep factors with a positive contribution; keep the best one if none is positive
kept = contribution[contribution > 0].index
if kept.empty:
kept = contribution.nlargest(1).index
print(f"Original factor count: {len(contribution)}")
print(f"Optimized factor count: {len(kept)}")
# Combine the kept factors
optimized_position = factors[kept[0]]
for name in kept[1:]:
optimized_position = optimized_position & factors[name]What is the difference between IC and factor return?
IC (Information Coefficient):
- Measures the correlation between factor values and future returns
- Range [-1, 1], closer to 1 is better
- Suitable for evaluating continuous factors (e.g., market cap, ROE)
Factor return:
- Measures the average return of stocks selected by the factor
- Suitable for evaluating binary factors (True/False)
- Directly reflects stock selection effectiveness
from finlab.ml import feature
eps = data.get('us_income_statement:eps_diluted')
raw_close = data.get('price:close') # unadjusted price for valuation
earnings_yield = eps.rolling(4).sum() / raw_close # trailing-12-month E/P
# Use IC for continuous factors
continuous = feature.combine({'earnings_yield': earnings_yield}, resample='ME')
ic = fa.calc_ic(continuous, labels, rank=True)
# Use factor return for binary factors
binary = feature.combine(
{'earnings_yield': earnings_yield.rank(pct=True, axis=1) > 0.7}, resample='ME'
)
factor_return = fa.calc_factor_return(binary, labels)Next steps
- Analyzing strategy factors End to end: factor returns, crowding, contributions and IC.
- Complete strategy workflow From research to production, including the factor analysis step.
- finlab.ml Use machine learning to discover factors.
Was this page helpful?