Arbitrage Pricing Theory and Multi-Factor Models › Building a Multi-Factor Risk Model
Quantitative FinanceAll chapters 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 across assets, the portfolio's factor exposure is the weighted average of individual exposures:
where:
- : portfolio sensitivity to factor
- : weight of asset in the portfolio
- : sensitivity of asset to factor
- : 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.
# 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])# Calculate portfolio factor exposures
portfolio_betas = stock_betas.T.dot(weights)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
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 and idiosyncratic variances :
where:
- : portfolio variance
- : vector of portfolio factor exposures
- : covariance matrix of factor returns
- : weight of asset
- : idiosyncratic variance of asset
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.
# 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 displayFactor 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.17The 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.
# 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) * 100Portfolio Risk Decomposition (Annualized) ================================================== Systematic Variance: 0.011260 (71.2%) Idiosyncratic Variance: 0.004560 (28.8%) Total Variance: 0.015820 Total Volatility: 12.58%

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:
where:
- : marginal contribution to risk of asset
- : portfolio volatility
- : weight of asset
- : return on asset
- : 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.
# 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,
)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.