Analyzing Strategy Factors
Research factor analysis to add, remove, or modify factors based on data rather than intuition. Understand the relationship between factors and strategy returns.
Factor Analysis Workflow in FinLab Package:
Example Strategy
|
+--- Feature Engineering ----> Factor Features (Features)
|
+--- Label Engineering ----> Excess Returns (Labels)
|
+--- calc_factor_return() ----> Factor Return
| |
| +-- calc_centrality() --> Factor Centrality
|
+--- calc_shapley_values() ---> Factor Contribution
|
+--- calc_ic() ----------------> Factor Correlation (IC)
We will demonstrate how to:
- Convert an "Example Strategy" into "Features" and "Labels"
- Use the above data to analyze factor performance
Example Strategy
This strategy trades S&P 500 stocks and is composed of three basic factors: 1. Market capitalization 2. Revenue growth (quarterly, year over year) 3. Momentum
from finlab import data
from finlab.backtest import sim
data.set_market('us')
with data.us_universe(index='SP500'):
marketcap = data.get('etl:us_market_value')
revenue = data.get('us_income_statement:revenue')
price = data.get('price:adj_close')
cond1 = marketcap.rank(pct=True, axis=1) < 0.3
cond2 = (revenue / revenue.shift(4)).rank(pct=True, axis=1) > 0.7
cond3 = (price / price.shift(20)).rank(pct=True, axis=1) > 0.7
pos = cond1 & cond2 & cond3
report = sim(pos, resample='ME', upload=False)
report.creturn.plot()
Creating Features and Labels
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 labels for the next 1 month
labels = label.excess_over_mean(index=features.index, resample='ME')
features.dropna().head()
features is indexed by (datetime, instrument), one row per month-end and ticker (for example AAPL), with one boolean column per factor: marketcap, revenue and momentum.
labels is a Series with the same (datetime, instrument) index: each stock's return over the next month minus the average return of all stocks.
Factor Return
Factor Return measures the excess return brought by a factor over a period of time, commonly used to evaluate a factor's effectiveness and investment value.
In this analysis workflow, features are built based on different factors (such as market cap, revenue growth, momentum), and the factor return for each is calculated. By observing the cumulative factor return trend, you can assess the factor's long-term performance and stability, serving as an important basis for factor selection and portfolio construction.
The chart below shows the cumulative return for each factor.
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',
)
Compare the curves: a factor whose cumulative return keeps rising has been adding excess return, while a flat or falling curve means the factor has not helped over that period.
Factor Centrality
Factor Centrality is a metric used to quantify "commonality" among factors.
Factor Centrality is defined as:
where \(\lambda_j\) is the factor's contribution to the first principal component, and \(k\) is the total number of factors.
This metric reflects the commonality of factor returns:
- Higher values indicate:
- Using this factor for stock selection has performed better recently.
- Higher centrality increases the risk of future mean reversion; close monitoring is needed.
- Lower values indicate:
- Using this factor for stock selection has performed worse recently.
- Lower centrality means lower risk of future mean reversion.
- Consider monitoring the factor when crowding is low, and use it when crowding starts to rise (factor reversion effect).
from finlab.tools.factor_analysis import calc_centrality
plot_line(calc_centrality(calc_factor_return(features, labels), 12), title='Centrality')
If a factor currently has low crowding, you might switch to a different factor to make the strategy more stable, or monitor it closely and use it when crowding starts to rise (factor reversion effect).
Factor Contribution (Shapley Values)
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) 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.
It is worth noting that the time complexity of Shapley Values computation is \(O(2^n)\), where \(n\) is the number of factors. Computation time can be long, so for a large number of factors, consider using alternative methods.
from finlab.tools.factor_analysis import calc_shapley_values
plot_line(calc_shapley_values(features, labels))
Factor Correlation (Information Coefficient)
Factor IC (Information Coefficient) is an important metric for measuring a factor's predictive ability, commonly used to evaluate the correlation between a factor and future returns.
The IC formula is:
where \(\text{corr}\) is the correlation coefficient (usually Pearson), \(\text{Factor Score}\) is the factor score, and \(\text{Future Return}\) is the asset return over a future period.
IC values typically range between -1 and 1:
- IC close to 1 indicates strong positive predictive ability for future returns.
- IC close to -1 indicates strong negative (contrarian) predictive ability for future returns.
- IC close to 0 indicates almost no predictive ability for future returns.
In quantitative stock selection, IC is a key basis for judging factor effectiveness. Factors with higher IC are generally more worth including in the portfolio.
from finlab.tools.factor_analysis import calc_ic
features_raw = feature.combine(
{
'marketcap': -marketcap, # Small cap (negated)
'revenue': revenue / revenue.shift(4),
'momentum': price / price.shift(20),
},
resample='ME',
)
plot_line(calc_ic(features_raw, labels, rank=True))
Factor Trend Analysis
Overview
The "centrality", "contribution", and "correlation" values changing over time can be analyzed for trends using calc_regression_stats:
from finlab.tools.factor_analysis import calc_regression_stats
centrality_df = calc_centrality(calc_factor_return(features, labels), 12)
centrality_trend = calc_regression_stats(centrality_df)
The result is a DataFrame with one row per factor (marketcap, revenue, momentum) and the columns slope, p_value, r_squared, tail_estimate and trend.
Core Fields
| Field | Description | Value Range | Meaning |
|---|---|---|---|
| slope | Linear regression slope | (-inf, +inf) | Trend direction and strength |
| p_value | Statistical significance | [0, 1] | Trend credibility |
| r_squared | Coefficient of determination | [0, 1] | Explanatory power of the linear model |
| tail_estimate | Tail estimate value | (-inf, +inf) | Predicted value at the end of the time series |
| trend | Trend classification | "up"/"down"/"flat" | Simplified trend judgment |
Value Ranges and Interpretations
slope
- Positive: Upward trend, increasing crowding
- Negative: Downward trend, decreasing crowding
- Absolute value: Trend strength (larger = stronger)
p_value (Statistical Significance)
- [0, 0.01]: Highly significant (***)
- [0.01, 0.05]: Significant (**)
- [0.05, 0.1]: Marginally significant (*)
- [0.1, 1]: Not significant
r_squared (Coefficient of Determination)
- [0.8, 1.0]: Very strong explanatory power
- [0.6, 0.8): Strong explanatory power
- [0.4, 0.6): Moderate explanatory power
- [0.2, 0.4): Weak explanatory power
- [0, 0.2): Very weak explanatory power
Trend Combination Analysis
| p_value | r_squared | slope | trend | Meaning |
|---|---|---|---|---|
| small | high | positive | up | Strong and stable upward trend |
| small | high | negative | down | Strong and stable downward trend |
| small | low | any | flat | Trend exists but effect is small / high noise |
| large | high | any | flat | Small sample, high noise, cannot determine |
| large | low | any | flat | Essentially no trend and model has no explanatory power |
Reading the Result
A trend counts as up or down only when p_value < 0.05 and r_squared > 0.1 (the p_value_threshold and r_squared_threshold defaults); otherwise it is flat.
- trend = up (small p_value, high r_squared, positive slope): the factor's centrality is rising steadily. The factor is becoming popular and more capital is chasing it, which also raises the risk of a reversal.
- trend = down (small p_value, high r_squared, negative slope): the factor's centrality is falling steadily; crowding is decreasing.
- trend = flat: either the slope is not significant, or it is significant but explains too little of the variation (low r_squared), so no clear trend can be claimed.