US examples
Factor crowding analysis
On this page5
Analyze a strategy's factors systematically, so you add, remove and change them based on data rather than intuition. This page runs FinLab's factor analysis workflow on a three-factor US strategy; it suits investors who want to know why a strategy works and when it might stop working.
Raw Data
|
|--- Feature Engineering -----> Factor Features
|
+--- Label Engineering -------> Excess Returns (Labels)
|
|--- calc_factor_return() -----> Factor Return
| |
| +-- calc_centrality() --> Factor Centrality
|
|--- calc_shapley_values() ----> Factor Contribution
|
+--- calc_ic() ----------------> Factor IC- Generate features and labels: features are the factors, labels are excess stock returns.
- Compute factor returns:
calc_factor_return(features, labels) - Compute factor centrality (crowding):
calc_centrality(factor_return, window) - Compute factor contribution:
calc_shapley_values(features, labels) - Compute factor IC:
calc_ic(features, labels) - Compute factor Rank IC:
calc_ic(features, labels, rank=True)
Strategy logic
The example strategy holds stocks that pass three factor conditions at once, rebalanced at each month end:
- Market capitalization (small-cap tilt): in the smallest 30% of stocks by market cap.
- Revenue growth (short-term against long-term revenue trend): 3-period average revenue divided by 12-period average revenue in the top 30%.
- Momentum: 20-day price return in the top 30%.
Code
Start with the strategy's performance:
from finlab import data
from finlab.backtest import sim
from finlab.markets.us import USMarket
data.set_market('us')
close = data.get('price:adj_close')
close = close[close.index.dayofweek < 5] # remove weekends
revenue = data.get('us_income_statement:revenue')
marketcap = data.get('etl:us_market_value')
cond1 = marketcap.rank(pct=True, axis=1) < 0.3
cond2 = (revenue.average(3) / revenue.average(12)).rank(pct=True, axis=1) > 0.7
cond3 = (close / close.shift(20)).rank(pct=True, axis=1) > 0.7
pos = cond1 & cond2 & cond3
report = sim(pos, resample='ME', market=USMarket(), fee_ratio=0.001, tax_ratio=0)
report.creturn.plot()Analyze the factors
Each step continues from the code above.
Generate features and labels
feature.combine() consolidates the factor conditions into a feature matrix, and label.excess_over_mean() computes the forward one-month excess return as the label.
from finlab import data
import finlab.ml.feature as feature
import finlab.ml.label as label
features = feature.combine(
{'marketcap': cond1, 'revenue': cond2, 'momentum': cond3}, resample='ME'
)
# Compute forward 1-month labels
labels = label.excess_over_mean(index=features.index, resample='ME')
features.dropna().head()labels.dropna().head()Plot each factor's return
Factor return is the excess return attributable to a factor over a period, commonly used to judge a factor's effectiveness and investment value. Plotting each factor's cumulative return shows its long-term performance and stability, a key input for choosing factors and building portfolios.
from finlab.tools.factor_analysis import calc_factor_return
from finlab.plot import plot_line
plot_line(
calc_factor_return(features, labels).cumsum(),
unit='.0%',
title='Factor Cumulative Return',
)The chart shows which factors have the stronger cumulative returns and which have been less effective.
Measure crowding with centrality
Factor centrality quantifies how "crowded" a factor has become: it measures the commonality of factor returns.
where is the factor's loading on the first principal component, and $k$ is the total number of factors.
It reflects how factor returns move together:
- Higher centrality: the factor has done well in stock selection recently, but high crowding raises the risk of future drawdowns, so monitor it closely.
- Lower centrality: the factor has underperformed recently, and lower crowding means less risk of a sharp reversal. Watch for its centrality to rise as a signal to re-enter.
from finlab.tools.factor_analysis import calc_centrality
from finlab.plot import plot_line
plot_line(calc_centrality(calc_factor_return(features, labels), 12), title='Centrality')When a factor's centrality is low, consider rotating to other factors for steadier performance, or keep watching it and re-enter when its crowding starts to rise.
See each factor's contribution
calc_shapley_values enumerates every factor combination. At each point in time, it takes the equal-weight mean excess return of the stocks that satisfy every factor in the combination (0 when none do) and splits it evenly among the combination's factors. A factor's contribution is the sum of its shares over all combinations that include it.
Despite the name, this is not the game-theoretic Shapley value: it does not measure the change in return from adding a factor, and the contributions do not sum to the return when all factors hold together. A higher value means the combinations containing the factor performed better.
from finlab.tools.factor_analysis import calc_shapley_values
from finlab.plot import plot_line
plot_line(calc_shapley_values(features, labels))Check predictive power with IC
The Information Coefficient (IC) measures a factor's predictive power as the correlation between factor scores and subsequent returns:
where is the correlation coefficient (typically Pearson or Spearman rank), is the factor value, and is the asset's return over the next period.
IC ranges from -1 to 1:
- Close to 1: strong positive predictive power for future returns.
- Close to -1: strong negative (contrarian) predictive power.
- Close to 0: little or no predictive power.
IC is a primary metric for evaluating factors in quantitative stock selection; factors with a higher IC are generally more valuable to include in a portfolio.
IC needs continuous numerical features rather than boolean conditions, so redefine the features first:
from finlab.tools.factor_analysis import calc_ic
features = feature.combine(
{
'marketcap': -marketcap, # small-cap tilt (negative sign)
'revenue': revenue.average(3) / revenue.average(12),
'momentum': close / close.shift(20),
},
resample='ME',
)
plot_line(calc_ic(features, labels, rank=True))How to adjust it
resample='ME': month-end rebalancing, for both the strategy and the factor analysis.calc_factor_return(features, labels): the return attributable to each factor, from the performance of stocks in the top against the bottom quantile of that factor.calc_centrality(factor_return, 12): a 12-period rolling window for the PCA-based centrality, which reflects the recent crowding level.calc_shapley_values(features, labels): enumerates all factor subsets and splits each subset's return evenly among its factors. Keep $n$ small (under 8–10 factors).calc_ic(features, labels, rank=True): the Spearman rank IC whenrank=True, more robust to outliers than the Pearson IC.
Used together, these tools support data-driven factor rotation: factor returns show long-term efficacy, centrality warns of crowding, contribution scores show which factors appear in the best-performing combinations, and IC measures raw predictive power. Reduce exposure to crowded factors and increase it to factors with an improving IC and low centrality. In the US market, common factors such as size, value and momentum go through regime shifts, which makes this kind of monitoring essential for adaptive strategy management.
Related pages
- Analyzing strategy factors The full guide to factor returns, centrality, Shapley values and IC.
- finlab.tools Reference for the
factor_analysisfunctions. - ML example strategy Use
feature.combine()and labels to train a model.
Was this page helpful?