Arbitrage Pricing Theory and Multi-Factor Models › Building a Multi-Factor Risk Model

Michael BrenndoerferDecember 15, 20258 min read
Quantitative FinanceAll chapters
Part 9 of 15Portfolio Theory and Investment Analysis
Chapter 48Arbitrage Pricing Theory and Multi-Factor Models › Building a Multi-Factor Risk Model
Reading section 9 of 15 · Building a Multi-Factor Risk Model · 8 minSection 9 of 15 · 8 min

Book contents

Section 9 of 15 · 8 min read

Arbitrage Pricing Theory and Multi-Factor Models › Building a Multi-Factor Risk ModelOne section · Sections in this chapter9 / 15 ↓
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

Factor models serve two purposes: explaining expected returns and decomposing risk. Let's build a complete factor risk model for a portfolio.

Portfolio Factor Exposures

For a portfolio with weights wiw_i across nn assets, the portfolio's factor exposure is the weighted average of individual exposures:

βpj=∑i=1nwiβij\beta_p^{j} = \sum_{i=1}^{n} w_i \beta_i^{j}

where:

  • βpj\beta_p^{j}: portfolio sensitivity to factor jj
  • wiw_i: weight of asset ii in the portfolio
  • βij\beta_i^{j}: sensitivity of asset ii to factor jj
  • nn: number of assets

This linearity makes factor models computationally tractable for large portfolios. Rather than tracking the correlations among thousands of individual securities, we need only track each security's exposure to a handful of factors. The portfolio's risk characteristics are then determined by its aggregate factor exposures, a dramatic simplification that enables practical risk management for institutional portfolios.

In[20]:
Code
# Create a portfolio of 5 synthetic stocks with different factor exposures
np.random.seed(123)

stock_betas = pd.DataFrame(
    {
        "MKT": [0.8, 1.0, 1.3, 0.9, 1.5],
        "SMB": [0.4, -0.2, 0.5, -0.3, 0.1],
        "HML": [0.3, -0.5, 0.1, 0.6, -0.8],
    },
    index=["Stock_A", "Stock_B", "Stock_C", "Stock_D", "Stock_E"],
)

# Idiosyncratic volatilities
idio_vol = np.array([0.04, 0.03, 0.05, 0.03, 0.06])

# Portfolio weights (equal-weighted)
weights = np.array([0.2, 0.2, 0.2, 0.2, 0.2])
In[21]:
Code
# Calculate portfolio factor exposures
portfolio_betas = stock_betas.T.dot(weights)
Out[22]:
Console
Individual Stock Factor Exposures
==================================================
         MKT  SMB  HML
Stock_A  0.8  0.4  0.3
Stock_B  1.0 -0.2 -0.5
Stock_C  1.3  0.5  0.1
Stock_D  0.9 -0.3  0.6
Stock_E  1.5  0.1 -0.8

Portfolio Weights:
  Stock_A: 20.0%
  Stock_B: 20.0%
  Stock_C: 20.0%
  Stock_D: 20.0%
  Stock_E: 20.0%

Portfolio Factor Exposures:
  MKT: 1.100
  SMB: 0.100
  HML: -0.060
Out[23]:
Visualization
Grouped bar chart showing MKT, SMB, and HML factor loadings for five stocks (A–E), with dashed horizontal lines indicating the portfolio-level average exposure for each factor.
Factor exposures for the five component stocks. All five have positive market beta, while their size and value tilts differ in sign and magnitude. Dashed lines mark the equal-weight portfolio exposures: MKT 1.10, SMB 0.10, and HML -0.06.

The portfolio maintains a market beta near 1.0 but has specific net exposures to size and value factors based on the underlying holdings.

Factor Risk Decomposition

The total variance of a portfolio in a factor model decomposes into factor risk and idiosyncratic risk. This decomposition is one of the most powerful applications of factor models because it separates diversifiable risk from non-diversifiable risk.

Using the factor covariance matrix ΣF\boldsymbol{\Sigma}_F and idiosyncratic variances σϵ,i2\sigma_{\epsilon,i}^2:

σp2=βp′ΣFβp+∑i=1nwi2σϵ,i2\sigma_p^2 = \boldsymbol{\beta}_p' \boldsymbol{\Sigma}_F \boldsymbol{\beta}_p + \sum_{i=1}^{n} w_i^2 \sigma_{\epsilon,i}^2

where:

  • σp2\sigma_p^2: portfolio variance
  • βp\boldsymbol{\beta}_p: vector of portfolio factor exposures
  • ΣF\boldsymbol{\Sigma}_F: covariance matrix of factor returns
  • wiw_i: weight of asset ii
  • σϵ,i2\sigma_{\epsilon,i}^2: idiosyncratic variance of asset ii

The first term is systematic risk from factor exposures. This term depends on how the factors co-move with each other and how much exposure the portfolio has to each factor. Even with perfect diversification across many securities, this systematic risk cannot be eliminated because it arises from economy-wide forces.

The second term is idiosyncratic risk, which decreases as the portfolio becomes more diversified. Notice that individual idiosyncratic variances are multiplied by squared weights. When weights are small (as in a well-diversified portfolio), squared weights become very small, causing the idiosyncratic component to shrink rapidly.

In[24]:
Code
# Factor covariance matrix (annualized)
factor_cov = ff_factors[["MKT", "SMB", "HML"]].cov() * 12
factor_cov_bps = (
    factor_cov * 10000
)  # Convert to basis points squared for display
Out[25]:
Console
Factor Covariance Matrix (Annualized)
==================================================
       MKT    SMB     HML
MKT  91.54  -2.73   -7.71
SMB  -2.73  97.80    1.90
HML  -7.71   1.90  128.17

The diagonal elements represent the variance of each factor, while off-diagonal elements show co-movements. Low off-diagonal values confirm the factors provide diversification benefits.

In[26]:
Code
# Calculate portfolio risk decomposition
portfolio_beta_vec = portfolio_betas.values

# Systematic risk (factor risk)
systematic_var = portfolio_beta_vec @ factor_cov.values @ portfolio_beta_vec

# Idiosyncratic risk
idio_var_annual = (idio_vol**2) * 12  # Annualize
portfolio_idio_var = np.sum(weights**2 * idio_var_annual)

# Total risk
total_var = systematic_var + portfolio_idio_var
total_vol = np.sqrt(total_var)

# Calculate percentages
sys_pct = (systematic_var / total_var) * 100
idio_pct = (portfolio_idio_var / total_var) * 100
Out[27]:
Console
Portfolio Risk Decomposition (Annualized)
==================================================
Systematic Variance: 0.011260 (71.2%)
Idiosyncratic Variance: 0.004560 (28.8%)
Total Variance: 0.015820
Total Volatility: 12.58%
Out[28]:
Visualization
Horizontal stacked bar showing 0.0113 systematic variance and 0.0046 idiosyncratic variance, labeled 71.2% and 28.8%, with total variance 0.0158 and volatility 12.6%.
Annualized portfolio variance decomposed into systematic factor risk (71.2%) and diversifiable idiosyncratic risk (28.8%). Total variance is 0.0158, corresponding to 12.6% annualized volatility.

The decomposition shows that systematic factor risk dominates, accounting for most of the portfolio variance. The idiosyncratic component is small because diversification across five stocks reduces stock-specific risk. With more holdings, idiosyncratic risk would decrease further.

Marginal Contribution to Risk

Understanding which positions contribute most to portfolio risk helps with risk management. The marginal contribution to risk (MCTR) measures how much total volatility would change with a small increase in each position's weight:

MCTRi=∂σp∂wi=Cov(Ri,Rp)σp\text{MCTR}_i = \frac{\partial \sigma_p}{\partial w_i} = \frac{\text{Cov}(R_i, R_p)}{\sigma_p}

where:

  • MCTRi\text{MCTR}_i: marginal contribution to risk of asset ii
  • σp\sigma_p: portfolio volatility
  • wiw_i: weight of asset ii
  • RiR_i: return on asset ii
  • RpR_p: return on the portfolio

Intuitively, the MCTR shows that an asset's contribution to risk depends not on its standalone volatility, but on its covariance with the portfolio. Assets that move in sync with the portfolio increase risk, while those that move inversely can reduce it.

This insight changes how assets should be evaluated for portfolio construction. A highly volatile stock that is negatively correlated with the rest of the portfolio might reduce total portfolio risk when added. Conversely, a low-volatility stock that is highly correlated with existing holdings might substantially increase risk. The MCTR captures these portfolio-level effects that are invisible when examining securities in isolation.

In[29]:
Code
# Calculate marginal contribution to risk for each stock
# MCTR = (Cov(R_i, R_p)) / sigma_p

# Stock covariance with portfolio factors
stock_factor_cov = stock_betas.values @ factor_cov.values @ portfolio_beta_vec

# Add idiosyncratic component for each stock
stock_portfolio_cov = stock_factor_cov + weights * idio_var_annual

# Marginal contribution to risk
mctr = stock_portfolio_cov / total_vol

# Component contribution to risk (weight * MCTR)
cctr = weights * mctr

# Percent contribution
pct_risk_contribution = (cctr / total_vol) * 100

# Create attribution table
attribution_df = pd.DataFrame(
    {
        "Weight": weights,
        "MCTR": mctr,
        "CCTR": cctr,
        "% of Risk": pct_risk_contribution,
    },
    index=stock_betas.index,
)
Out[30]:
Console
Risk Attribution by Stock
==================================================
Stock            Weight         MCTR         CCTR    % of Risk
--------------------------------------------------
Stock_A           20.0%       0.0930       0.0186        14.8%
Stock_B           20.0%       0.1027       0.0205        16.3%
Stock_C           20.0%       0.1534       0.0307        24.4%
Stock_D           20.0%       0.0801       0.0160        12.7%
Stock_E           20.0%       0.1997       0.0399        31.8%
--------------------------------------------------
Total            100.0%                    0.1258       100.0%

Stock E, with its high market beta and growth tilt, contributes disproportionately to risk despite having equal weight. This analysis helps identify risk concentrations and informs rebalancing decisions.