Fama Decomposition
Calculate the Fama (1972) decomposition of total excess return into Selectivity and Diversification.
Jensen’s Alpha alone conflates two very different sources of excess return: genuine stock/timing selection skill, and simply carrying more total risk than the market by holding an under-diversified portfolio (which, in a CAPM world, should be compensated with extra return even absent any skill). Fama’s decomposition separates the two by comparing the portfolio’s actual return against two different CAPM-implied return benchmarks: one using the portfolio’s actual Beta (systematic risk only), and one using the portfolio’s actual total risk ratio (Sigma_Portfolio / Sigma_Market) in place of Beta.
The formulas are as follows:
\[\text{Selectivity} = (\text{Asset Return} - \text{Risk-Free Rate}) - (\text{Sigma\_Portfolio} / \text{Sigma\_Market}) \cdot (\text{Benchmark Return} - \text{Risk-Free Rate})\] \[\text{Diversification} = \left[\text{Risk-Free Rate} + (\text{Sigma\_Portfolio} / \text{Sigma\_Market}) \cdot (\text{Benchmark Return} - \text{Risk-Free Rate})\right] - \left[\text{Risk-Free Rate} + \text{Beta} \cdot (\text{Benchmark Return} - \text{Risk-Free Rate})\right]\]Selectivity is the return earned above what would be required for a fully diversified portfolio carrying the same total risk, i.e. genuine security selection or timing skill. Diversification is the extra return the manager left on the table (if positive, it is a cost) by taking on unsystematic risk that a fully diversified portfolio of the same total risk would not have. Selectivity plus Diversification equals Jensen’s Alpha (see get_jensens_alpha).
Also known as: Fama’s Net Selectivity, Fama performance decomposition.
Fama Decomposition in Python or via the MCP
Install the Finance Toolkit with:
pip install financetoolkit -U
Then call get_fama_decomposition as shown below.
from financetoolkit import Toolkit
toolkit = Toolkit(
["QQQ", "IWM", "XLE"],
api_key="FINANCIAL_MODELING_PREP_KEY",
start_date="2020-01-01",
end_date="2025-12-31",
)
toolkit.performance.get_fama_decomposition().xs("Selectivity", level=1, axis=1)
Or, after setting up the Finance Toolkit MCP, ask:
How much of the return of QQQ, IWM and XLE came from selectivity in each year from 2021 to 2025, according to Fama's decomposition?
Which returns:
| Date | QQQ | IWM | XLE |
|---|---|---|---|
| 2021 | -0.123 | -0.3304 | -0.1035 |
| 2022 | -0.0751 | 0.0106 | 0.9216 |
| 2023 | 0.2052 | -0.2125 | -0.4352 |
| 2024 | -0.0824 | -0.2697 | -0.28 |
| 2025 | 0.0022 | -0.0738 | -0.1338 |
Parameters
get_fama_decomposition accepts the following parameters.
| Parameter | Type | Description | Example |
|---|---|---|---|
period | str | The period to use for the calculation. Defaults to “quarterly” if the Toolkit is initialised with quarterly=True, otherwise “yearly”. | "quarterly" |
rolling | int | The rolling window size to use for the Beta component of the calculation. If set, Beta is estimated over a rolling window of this many periods across the full return history instead of per period. Defaults to None. | 4 |
rounding | int | None | The number of decimals to round the results to. Defaults to None, which uses the rounding of the Toolkit (4 unless set otherwise). | 2 |
growth | bool | Whether to calculate the growth of the ratios. Defaults to False. | True |
lag | int | list[int] | The lag to use for the growth calculation. Defaults to 1. | 4 |
standardize | bool | Whether to standardize (Z-Score) the result. When combined with growth=True, standardizes the growth values instead of the raw values. Defaults to False. | True |