Performance Attribution and Investment Alpha › Factor-Based Attribution

Michael BrenndoerferDecember 17, 202513 min read
Quantitative FinanceAll chapters
Part 9 of 15Portfolio Theory and Investment Analysis
Chapter 50Performance Attribution and Investment Alpha › Factor-Based Attribution
Reading section 3 of 10 · Factor-Based Attribution · 13 minSection 3 of 10 · 13 min

Book contents

Section 3 of 10 · 13 min read

Performance Attribution and Investment Alpha › Factor-Based AttributionOne section · Sections in this chapter3 / 10 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

While Brinson attribution uses holdings data to decompose returns by sector, factor-based attribution uses returns data to decompose performance by exposure to systematic risk factors. This approach, rooted in the factor models we explored in Part IV Chapter 3, separates returns into two components: the portion explained by factor exposures (beta) and the residual unexplained by factors (alpha).

From CAPM to Multi-Factor Attribution

Building on our understanding of the CAPM from Part IV Chapter 2, recall that expected returns are determined by exposure to market risk. The CAPM posits a linear relationship between expected excess returns and market beta, establishing the foundational insight that investors are compensated for bearing systematic risk:

E[Ri]−Rf=βi(E[Rm]−Rf)E[R_i] - R_f = \beta_i (E[R_m] - R_f)

where:

  • E[Ri]E[R_i]: expected return of asset ii
  • RfR_f: risk-free rate
  • βi\beta_i: sensitivity of asset ii to market movements
  • E[Rm]E[R_m]: expected market return

This equation tells us that in equilibrium, the only source of expected excess returns should be exposure to market risk. Any return above or below this expectation represents either luck or skill. In an attribution context, we transition from this expectation to analyzing realized returns, which allows us to decompose actual performance into its constituent sources.

The realized return version of the CAPM turns the equilibrium relationship into a regression framework:

Ri−Rf=αi+βi(Rm−Rf)+ϵiR_i - R_f = \alpha_i + \beta_i (R_m - R_f) + \epsilon_i

where:

  • RiR_i: realized return of asset ii
  • RfR_f: risk-free rate
  • αi\alpha_i: alpha (excess return unexplained by beta)
  • βi\beta_i: realized market beta
  • RmR_m: realized market return
  • ϵi\epsilon_i: idiosyncratic error term

This regression equation sets up factor-based attribution. The term βi(Rm−Rf)\beta_i (R_m - R_f) represents the return attributable to market exposure, capturing the portion of returns that any investor could have earned simply by taking on market risk. The intercept αi\alpha_i represents the average return unexplained by market exposure, the "alpha" that active managers seek to generate. If αi\alpha_i is statistically large and positive, it suggests the manager added value beyond simply taking market risk.

The residual term ϵi\epsilon_i captures the period-by-period deviation from the linear relationship, representing idiosyncratic returns that are neither systematic nor persistent. This component averages to zero over time (by construction of OLS) and represents noise rather than skill.

Multi-factor models extend this decomposition to multiple sources of systematic risk, recognizing that the market factor alone does not capture all priced risks. Using the Fama-French three-factor model as an example, we add size and value factors to provide a richer characterization of systematic return drivers:

Ri−Rf=αi+βiMKT(Rm−Rf)+βiSMB⋅SMB+βiHML⋅HML+ϵiR_i - R_f = \alpha_i + \beta_i^{\text{MKT}}(R_m - R_f) + \beta_i^{\text{SMB}} \cdot \text{SMB} + \beta_i^{\text{HML}} \cdot \text{HML} + \epsilon_i

where:

  • Ri,RfR_i, R_f: realized asset return and risk-free rate
  • αi\alpha_i: alpha (excess return unexplained by factor exposures)
  • βiMKT\beta_i^{\text{MKT}}: exposure to market risk
  • RmR_m: realized market return
  • βiSMB\beta_i^{\text{SMB}}: exposure to size premium (Small Minus Big)
  • βiHML\beta_i^{\text{HML}}: exposure to value premium (High Minus Low)
  • SMB,HML\text{SMB}, \text{HML}: returns of the size and value factors
  • ϵi\epsilon_i: idiosyncratic error term

Each beta coefficient measures the portfolio's sensitivity to a particular factor. A positive SMB beta indicates a tilt toward smaller companies, while a negative HML beta suggests a growth orientation. These exposures determine how much of the portfolio's return can be attributed to each systematic factor.

Factor-based attribution then decomposes total return into:

  • Market contribution: βMKT(Rm−Rf)\beta^{\text{MKT}}(R_m - R_f)
  • Size contribution: βSMB⋅SMB\beta^{\text{SMB}} \cdot \text{SMB}
  • Value contribution: βHML⋅HML\beta^{\text{HML}} \cdot \text{HML}
  • Alpha: α\alpha (residual skill)

This decomposition answers a critical question: how much of the portfolio's return came from passive exposure to known risk factors versus security selection or market timing skill? A portfolio with high factor contributions and low alpha is essentially providing factor exposure, which can be replicated cheaply through index funds or smart beta products. A portfolio with significant alpha after controlling for factors demonstrates skill that may justify higher fees.

Estimating Factor Exposures

Factor exposures are estimated using time-series regression, as covered in Part III Chapter 19. The regression framework treats factor returns as explanatory variables and portfolio excess returns as the dependent variable, allowing us to estimate the sensitivity of the portfolio to each factor.

Given TT periods of return data, we regress portfolio excess returns on factor returns:

Rt−Rf,t=α+β1F1,t+β2F2,t+⋯+βkFk,t+ϵtR_t - R_{f,t} = \alpha + \beta_1 F_{1,t} + \beta_2 F_{2,t} + \cdots + \beta_k F_{k,t} + \epsilon_t

where:

  • Rt−Rf,tR_t - R_{f,t}: portfolio excess return at time tt
  • α\alpha: intercept (estimated alpha)
  • β1,…,βk\beta_1, \dots, \beta_k: factor loadings
  • F1,t,…,Fk,tF_{1,t}, \dots, F_{k,t}: factor returns at time tt
  • ϵt\epsilon_t: residual return at time tt

The OLS estimates provide both the factor loadings and an estimate of alpha with associated standard errors for statistical inference. The standard errors enable hypothesis testing, allowing us to assess whether each coefficient, including alpha, is statistically distinguishable from zero.

The regression framework offers several advantages for attribution. First, it requires only return data rather than detailed holdings information, making it applicable to hedge funds and other vehicles that do not disclose positions. Second, it automatically accounts for correlations between factors through the multivariate regression framework. Third, it provides statistical inference capabilities that allow us to quantify uncertainty around our estimates.

Implementation with Simulated Data

Let's implement factor-based attribution using a realistic simulation of portfolio returns.

In[11]:
Code
import numpy as np

np.random.seed(42)

# Simulate 60 months of factor returns
n_months = 60

# Factor returns (monthly)
# Market excess return: ~0.5% monthly mean, 4.5% monthly vol
mkt_rf = np.random.normal(0.005, 0.045, n_months)

# SMB (Small Minus Big): ~0.2% monthly mean, 3% monthly vol
smb = np.random.normal(0.002, 0.030, n_months)

# HML (High Minus Low): ~0.3% monthly mean, 3% monthly vol
hml = np.random.normal(0.003, 0.030, n_months)

# Risk-free rate: ~0.2% monthly
rf = np.ones(n_months) * 0.002
In[12]:
Code
# Generate portfolio returns with known factor exposures
# True parameters
true_alpha = 0.002  # 20 bps monthly alpha (about 2.4% annually)
true_beta_mkt = 1.10  # Slightly higher market exposure
true_beta_smb = 0.25  # Mild small-cap tilt
true_beta_hml = -0.15  # Slight growth tilt (negative value exposure)
idio_vol = 0.015  # Idiosyncratic volatility

# Generate portfolio excess returns
idio_returns = np.random.normal(0, idio_vol, n_months)
portfolio_excess = (
    true_alpha
    + true_beta_mkt * mkt_rf
    + true_beta_smb * smb
    + true_beta_hml * hml
    + idio_returns
)

portfolio_returns = portfolio_excess + rf
In[13]:
Code
# Run factor regression
!uv pip install statsmodels
import statsmodels.api as sm

# Prepare factor matrix with constant for alpha
factors = pd.DataFrame({
    'MKT_RF': mkt_rf,
    'SMB': smb,
    'HML': hml
})
factors_with_const = sm.add_constant(factors)

# OLS regression
model = sm.OLS(portfolio_excess, factors_with_const)
results = model.fit()
alpha_annualized = results.params['const'] * 12
Out[14]:
Console
Factor Model Regression Results
==================================================

Alpha (annualized): 3.57%
Alpha t-statistic: 1.43
Alpha p-value: 0.1578

Factor Loadings:
  Market Beta: 1.150
  SMB Beta:    0.300
  HML Beta:    -0.170

R-squared: 0.907

The regression recovers factor exposures close to the true values (given estimation noise). The R-squared of around 70-80% indicates that the three factors explain most of the portfolio's return variation, with the remainder being idiosyncratic. The alpha estimate and its t-statistic allow us to assess whether the manager added value beyond factor exposures.

Out[15]:
Visualization
Two-bar chart comparing true monthly alpha of 0.20 percent with an estimated monthly alpha of approximately 0.30 percent.
True monthly alpha of 0.20% compared with the regression estimate of approximately 0.30%. Alpha is shown separately because it is a return, not a dimensionless factor loading.
Grouped bar chart comparing true and estimated MKT, SMB, and HML factor betas, including negative HML loadings.
True and regression-estimated factor betas. The estimated MKT, SMB, and HML loadings remain close to the seeded values, with MKT the dominant exposure.

Return Attribution by Factor

With the factor loadings estimated, we can decompose realized returns into contributions from each factor.

In[16]:
Code
# Calculate return contributions
alpha_est = results.params["const"]
beta_mkt = results.params["MKT_RF"]
beta_smb = results.params["SMB"]
beta_hml = results.params["HML"]

# Average monthly factor returns
avg_mkt = np.mean(mkt_rf)
avg_smb = np.mean(smb)
avg_hml = np.mean(hml)
avg_port_excess = np.mean(portfolio_excess)

# Return contributions (monthly)
mkt_contribution = beta_mkt * avg_mkt
smb_contribution = beta_smb * avg_smb
hml_contribution = beta_hml * avg_hml
total_monthly_contribution = (
    alpha_est + mkt_contribution + smb_contribution + hml_contribution
)
Out[17]:
Console

Return Attribution (Monthly)
----------------------------------------
Average Portfolio Excess Return: 0.031%

Decomposition:
  Alpha:              0.297%
  Market Factor:      -0.225%
  Size Factor (SMB):  0.057%
  Value Factor (HML): -0.098%
  Total:              0.031%
In[18]:
Code
# Annualized attribution parameter
ann_factor = 12

# Calculate annualized metrics
avg_port_excess_ann = avg_port_excess * ann_factor
alpha_ann = alpha_est * ann_factor
mkt_contrib_ann = mkt_contribution * ann_factor
smb_contrib_ann = smb_contribution * ann_factor
hml_contrib_ann = hml_contribution * ann_factor
Out[19]:
Console

Return Attribution (Annualized)
----------------------------------------
Average Portfolio Excess Return: 0.37%

Decomposition:
  Alpha:              3.57%
  Market Factor:      -2.70%
  Size Factor (SMB):  0.68%
  Value Factor (HML): -1.18%

In this seeded 60-month sample, alpha is the largest positive component at 3.57% annualized. The realized Market and Value factor means are negative, so the portfolio's MKT and HML exposures detract 2.70% and 1.18%, respectively, while SMB adds 0.68%. Together, the four components reconcile to the sample's 0.37% annualized average excess return. This result is specific to the simulated sample rather than a claim about long-run factor premia.

Visualization of Factor Contributions

Out[20]:
Visualization
Bar chart showing annualized contributions of positive 3.57 percent alpha, negative 2.70 percent MKT, positive 0.68 percent SMB, and negative 1.18 percent HML.
Annualized attribution in the seeded 60-month sample. Alpha contributes 3.57% and SMB adds 0.68%, while MKT and HML detract 2.70% and 1.18%; the components sum to the 0.37% annualized average excess return.

Risk Attribution

Beyond return attribution, factor models also allow us to attribute portfolio risk (variance) to factor sources. This analysis answers a complementary question: where does portfolio volatility come from?

The mathematical foundation for risk attribution relies on a key property of OLS regression. Because the regression residuals ϵ\epsilon are uncorrelated with the factors by construction of ordinary least squares, the variance of the sum equals the sum of the variances. This orthogonality allows us to cleanly separate systematic from idiosyncratic risk.

Thus, the total variance decomposes into systematic and idiosyncratic components:

Var(Rp−Rf)=β′ΣFβ+σϵ2\text{Var}(R_p - R_f) = \boldsymbol{\beta}' \boldsymbol{\Sigma}_F \boldsymbol{\beta} + \sigma_\epsilon^2

where:

  • Var(Rp−Rf)\text{Var}(R_p - R_f): total variance of portfolio excess returns
  • β\boldsymbol{\beta}: vector of factor loadings
  • ΣF\boldsymbol{\Sigma}_F: factor covariance matrix
  • σϵ2\sigma_\epsilon^2: idiosyncratic variance

The first term, β′ΣFβ\boldsymbol{\beta}' \boldsymbol{\Sigma}_F \boldsymbol{\beta}, captures systematic variance arising from factor exposures. This quadratic form accounts for each factor's individual variance and for the correlations between factors. When factors are positively correlated, systematic risk may be amplified; when negatively correlated, they may partially offset. The second term, σϵ2\sigma_\epsilon^2, represents idiosyncratic variance that cannot be attributed to any factor, which reflects security-specific risks that diversify away in large portfolios.

In[21]:
Code
# Factor covariance matrix
factor_cov = factors.cov()

# Factor betas as vector
betas = np.array([beta_mkt, beta_smb, beta_hml])

# Systematic variance
systematic_var = betas @ factor_cov.values @ betas

# Idiosyncratic variance (from regression residuals)
idio_var = np.var(results.resid, ddof=4)  # 4 parameters estimated

# Reconcile the two modeled components to a common attributed total.
# The separately estimated sample variance differs slightly because it uses a
# different degrees-of-freedom convention.
observed_var = np.var(portfolio_excess, ddof=1)
total_var = systematic_var + idio_var

# Risk ratios
syst_ratio = systematic_var / total_var
idio_ratio = idio_var / total_var

# Annualized volatilities
total_vol_ann = np.sqrt(total_var * 12)
syst_vol_ann = np.sqrt(systematic_var * 12)
idio_vol_ann = np.sqrt(idio_var * 12)
Out[22]:
Console
Risk Attribution (Monthly Variance)
----------------------------------------
Observed Variance:      0.002528
Attributed Variance:    0.002540
Systematic Variance:   0.002292 (90.2%)
Idiosyncratic Variance:0.000248 (9.8%)

Annualized Volatility:
Total:       17.46%
Systematic:  16.59%
Idiosyncratic: 5.46%
Out[23]:
Visualization
Horizontal stacked bar showing approximately 90 percent systematic and 10 percent idiosyncratic contributions to attributed portfolio variance, with annualized volatility annotations.
Variance decomposition normalized to the sum of the systematic and idiosyncratic model components. Systematic factor exposure accounts for about 90% of attributed variance, with idiosyncratic risk contributing about 10%.