Statistical / ML for QuantsFactor ModelsCAPMFama-FrenchRisk Premia

Factor Models: CAPM, APT, and Fama-French

Module 1 of 422 min readLevel: Medium

Setup

Context and Assumptions

Factor models decompose asset returns into systematic exposures to common risk factors and an idiosyncratic residual. They are used on every quant equity desk — for risk attribution, portfolio construction, and alpha signal design.

The central question factor models answer is: why do different assets earn different expected returns? The answer, in all factor frameworks, is compensation for bearing systematic risk that cannot be diversified away.

Notation throughout. Let:

  • Ri,tR_{i,t} = excess return of asset ii at time tt (return minus risk-free rate rfr_f)
  • E[Ri]\mathbb{E}[R_i] = unconditional expected excess return of asset ii
  • βi,k\beta_{i,k} = factor loading (sensitivity) of asset ii to factor kk
  • λk\lambda_k = risk premium for factor kk (expected excess return per unit of factor exposure)
  • εi,t\varepsilon_{i,t} = idiosyncratic return; E[εi,t]=0\mathbb{E}[\varepsilon_{i,t}] = 0, uncorrelated with factors

Key assumptions that vary by model are stated in each section.


Theory

1. CAPM: Capital Asset Pricing Model

Assumptions.

  1. Investors are mean-variance optimisers (Markowitz 1952) with identical beliefs.
  2. All assets are tradeable; no transaction costs, taxes, or short-selling constraints.
  3. Returns are jointly normally distributed (or investors have quadratic utility).
  4. A risk-free asset exists, lendable and borrowable at rate rfr_f.
  5. All investors have the same investment horizon.

Under these assumptions, every investor holds the same risky portfolio — the market portfolio MM, which in equilibrium is the value-weighted portfolio of all risky assets.

Derivation of the SML. Consider any asset ii. Form a portfolio with weight α\alpha in asset ii and (1α)(1-\alpha) in the market portfolio. Expected excess return and variance:

E[Rp]=αE[Ri]+(1α)E[RM]\mathbb{E}[R_p] = \alpha \mathbb{E}[R_i] + (1-\alpha)\mathbb{E}[R_M] σp2=α2σi2+2α(1α)σiM+(1α)2σM2\sigma_p^2 = \alpha^2 \sigma_i^2 + 2\alpha(1-\alpha)\sigma_{iM} + (1-\alpha)^2 \sigma_M^2

In equilibrium, asset ii is already in the market portfolio, so the efficient portfolio locus through (i,M)(i, M) must be tangent to the Capital Market Line at α=0\alpha = 0. Computing dE[Rp]/dσpd\mathbb{E}[R_p]/d\sigma_p at α=0\alpha = 0 and equating to the Sharpe ratio of the market gives:

E[Ri]=βiE[RM],βi=Cov(Ri,RM)Var(RM)=σiMσM2.\mathbb{E}[R_i] = \beta_i \, \mathbb{E}[R_M], \qquad \beta_i = \frac{\text{Cov}(R_i, R_M)}{\text{Var}(R_M)} = \frac{\sigma_{iM}}{\sigma_M^2}.

This is the Security Market Line (SML). Assets plot on the SML in equilibrium; deviations from it are alphas — excess returns not explained by market beta.

Time-series regression form (Jensen 1968):

Ri,t=αi+βiRM,t+εi,t.R_{i,t} = \alpha_i + \beta_i R_{M,t} + \varepsilon_{i,t}.

Under CAPM, αi=0\alpha_i = 0 for all assets in equilibrium.

2. APT: Arbitrage Pricing Theory

Assumptions (Ross 1976).

  1. Returns are generated by a K-factor linear model: Ri,t=E[Ri]+k=1Kβi,kFk,t+εi,t,R_{i,t} = \mathbb{E}[R_i] + \sum_{k=1}^K \beta_{i,k} F_{k,t} + \varepsilon_{i,t}, where Fk,tF_{k,t} are zero-mean factor realisations and εi,t\varepsilon_{i,t} are idiosyncratic, mutually uncorrelated, with bounded variance.
  2. There are sufficiently many assets to form well-diversified portfolios.
  3. No-arbitrage: no portfolio with zero cost, zero systematic risk, and positive expected return.

Result. In a no-arbitrage economy, expected returns satisfy (approximately):

E[Ri]=λ0+k=1Kβi,kλk,\mathbb{E}[R_i] = \lambda_0 + \sum_{k=1}^K \beta_{i,k} \lambda_k,

where λ0=rf\lambda_0 = r_f (zero-beta return) and λk\lambda_k is the risk premium for factor kk. The APT does not specify which factors matter — it only says that if factors explain covariance structure, their premia must exist to preclude arbitrage.

CAPM is a special case of APT with K=1K=1 and F1=RMF_1 = R_M, plus the equilibrium assumptions that fix λ1=E[RM]\lambda_1 = \mathbb{E}[R_M].

3. Fama-French Three-Factor Model

Motivation. Fama and French (1992, 1993) documented that CAPM beta does not fully explain the cross-section of expected returns. Two anomalies survive controlling for market beta:

  • Size effect: small-cap stocks earn higher average returns than large-cap.
  • Value effect: stocks with high book-to-market (B/M) ratio earn higher average returns than growth stocks.

Factor construction (Fama-French 1993).

Let SMBtSMB_t (Small Minus Big) and HMLtHML_t (High Minus Low) be zero-cost long-short factor portfolios constructed monthly:

  • SMB: long bottom 50% of stocks by market cap, short top 50%.
  • HML: long top 30% of stocks by B/M, short bottom 30%; sort within size buckets to control for size.

The three-factor model:

Ri,t=αi+βiMRM,t+βiSMBSMBt+βiHMLHMLt+εi,t.R_{i,t} = \alpha_i + \beta_i^M R_{M,t} + \beta_i^{SMB} \, SMB_t + \beta_i^{HML} \, HML_t + \varepsilon_{i,t}.

Under the model, αi=0\alpha_i = 0 in equilibrium. Empirically, αi\alpha_i is close to zero for most equity portfolios but non-zero for momentum strategies — motivating Carhart (1997) to add a momentum factor MOMMOM.

Fama-MacBeth two-pass regression. The canonical approach to estimating factor premia in the cross-section:

  1. First pass (time-series): For each asset ii, regress Ri,tR_{i,t} on factors to estimate β^i,k\hat{\beta}_{i,k}.
  2. Second pass (cross-section): At each time tt, regress Ri,tR_{i,t} on estimated betas: Ri,t=γ0,t+kγk,tβ^i,k+ui,t.R_{i,t} = \gamma_{0,t} + \sum_k \gamma_{k,t} \hat{\beta}_{i,k} + u_{i,t}. Average the cross-sectional estimates: λ^k=T1tγ^k,t\hat{\lambda}_k = T^{-1} \sum_t \hat{\gamma}_{k,t}. Standard errors use the time-series standard deviation of γ^k,t\hat{\gamma}_{k,t}, which is robust to cross-sectional heteroskedasticity (but not to estimation error in betas — the Shanken correction applies).

Implementation

"""
Factor model estimation: CAPM and Fama-French three-factor.

Assumptions:
- Returns are in excess of the risk-free rate (monthly frequency)
- Factor data follows Ken French's construction conventions
- All returns and factors are in decimal form (not percent)
"""

from __future__ import annotations

import numpy as np
import pandas as pd
from scipy import stats
import statsmodels.api as sm
from typing import NamedTuple


class FactorRegressionResult(NamedTuple):
    alpha: float           # annualised intercept
    alpha_tstat: float     # t-statistic on alpha
    betas: dict[str, float]
    r_squared: float
    residual_std: float    # annualised idiosyncratic vol


def capm_regression(
    excess_returns: pd.Series,
    market_excess_return: pd.Series,
) -> FactorRegressionResult:
    """
    Estimate CAPM beta via OLS. Both series must be monthly excess returns.
    Alpha is annualised (multiplied by 12).
    """
    X = sm.add_constant(market_excess_return.rename("MKT"))
    model = sm.OLS(excess_returns, X).fit()

    alpha_monthly = model.params["const"]
    alpha_tstat = model.tvalues["const"]

    return FactorRegressionResult(
        alpha=alpha_monthly * 12,
        alpha_tstat=alpha_tstat,
        betas={"MKT": model.params["MKT"]},
        r_squared=model.rsquared,
        residual_std=model.resid.std() * np.sqrt(12),
    )


def fama_french_regression(
    excess_returns: pd.Series,
    factors: pd.DataFrame,   # columns: MKT, SMB, HML (monthly excess returns)
) -> FactorRegressionResult:
    """
    Estimate Fama-French three-factor model via OLS.
    factors must contain columns ['MKT', 'SMB', 'HML'].
    """
    required = {"MKT", "SMB", "HML"}
    if not required.issubset(factors.columns):
        raise ValueError(f"factors must contain columns {required}")

    X = sm.add_constant(factors[["MKT", "SMB", "HML"]])
    model = sm.OLS(excess_returns, X).fit()

    alpha_monthly = model.params["const"]
    alpha_tstat = model.tvalues["const"]

    return FactorRegressionResult(
        alpha=alpha_monthly * 12,
        alpha_tstat=alpha_tstat,
        betas={k: model.params[k] for k in ["MKT", "SMB", "HML"]},
        r_squared=model.rsquared,
        residual_std=model.resid.std() * np.sqrt(12),
    )


def fama_macbeth(
    returns: pd.DataFrame,    # T x N matrix of monthly excess returns
    factors: pd.DataFrame,    # T x K matrix of factor returns
    shanken_correction: bool = True,
) -> pd.DataFrame:
    """
    Fama-MacBeth two-pass cross-sectional regression.

    Pass 1: time-series OLS per asset → beta estimates.
    Pass 2: cross-sectional OLS at each t → gamma_t estimates.

    Returns DataFrame with columns: lambda (mean gamma), t_stat, std_error.
    Shanken (1992) correction adjusts SE for errors-in-variables from beta estimation.
    """
    T, N = returns.shape
    K = factors.shape[1]
    factor_names = list(factors.columns)

    # --- Pass 1: estimate betas ---
    X_ts = sm.add_constant(factors)
    betas = np.zeros((N, K))     # N assets x K factors
    for i, asset in enumerate(returns.columns):
        res = sm.OLS(returns[asset], X_ts).fit()
        betas[i] = [res.params[k] for k in factor_names]

    # --- Pass 2: cross-sectional regression at each t ---
    gammas = np.zeros((T, K + 1))   # intercept + K factor premia
    for t in range(T):
        y_t = returns.iloc[t].values
        X_cs = sm.add_constant(betas)
        res_t = sm.OLS(y_t, X_cs).fit()
        gammas[t] = res_t.params

    lambda_hat = gammas.mean(axis=0)
    se_raw = gammas.std(axis=0, ddof=1) / np.sqrt(T)

    if shanken_correction:
        # Shanken (1992): inflate SE by (1 + lambda' Sigma_F^{-1} lambda)
        sigma_f = np.cov(factors.values.T, ddof=1)
        lam_k = lambda_hat[1:]   # factor premia only
        c = 1.0 + lam_k @ np.linalg.inv(sigma_f) @ lam_k
        se_raw[1:] *= np.sqrt(c)

    t_stats = lambda_hat / se_raw

    index = ["intercept"] + factor_names
    return pd.DataFrame({
        "lambda": lambda_hat,
        "t_stat": t_stats,
        "std_error": se_raw,
    }, index=index)

Validation

CAPM. A correct implementation satisfies:

  • For the market portfolio itself: β=1\beta = 1, α=0\alpha = 0, R2=1R^2 = 1 (by construction).
  • For the risk-free asset: β=0\beta = 0, α=0\alpha = 0, R2=0R^2 = 0.

Fama-French. Using monthly data from Ken French's data library (publicly available at mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html):

  • SMB average monthly return ≈ +0.20% (1963–2022); HML ≈ +0.37%.
  • Small-cap value portfolios load positively on both SMB and HML; large-cap growth loads negatively.
  • The three-factor model explains R2>0.95R^2 > 0.95 for most size/B-M sorted decile portfolios, versus R20.75R^2 \approx 0.75 for CAPM alone.

Fama-MacBeth. Applying to 25 Fama-French size/B-M portfolios over 1963–2022 should yield:

  • Market premium λ^M0.5%\hat{\lambda}_M \approx 0.5\% per month (t-stat ≈ 2.0).
  • SMB premium λ^SMB0.2%\hat{\lambda}_{SMB} \approx 0.2\% (t-stat ≈ 1.8).
  • HML premium λ^HML0.3%\hat{\lambda}_{HML} \approx 0.3\% (t-stat ≈ 2.3).

Limitations

CAPM: Empirically Rejected

CAPM assumes homogeneous beliefs, no frictions, and a mean-variance world. In practice:

  • The SML is too flat: low-beta stocks earn higher risk-adjusted returns than CAPM predicts (Black, Jensen, Scholes 1972); high-beta stocks underperform. This underlies the low-volatility anomaly.
  • The true market portfolio is unobservable (Roll's critique, 1977): the value-weighted equity index is not the market portfolio (it excludes human capital, real estate, private equity, foreign assets). CAPM is therefore not testable in principle.

Fama-French: The Factor Zoo

Harvey, Liu, and Zhu (2016) documented over 300 claimed cross-sectional return predictors. Most are likely false discoveries:

  • Data snooping: factors found in-sample lose significance out-of-sample.
  • Non-stationarity: the HML value premium has been weak since 2007; the size premium has been disputed.
  • Structural explanation: Fama and French view the factors as risk premia; others (behavioural finance) attribute them to mispricing. The distinction matters for whether premia persist.

Estimation Errors in Betas

OLS beta estimates are noisy, especially from short time-series. The Shanken (1992) errors-in-variables correction partially addresses this, but shrinkage estimators (James-Stein, Ledoit-Wolf) or Bayesian shrinkage (Vasicek 1973) often outperform in practice for portfolio construction.

Changing Factor Premia

Factor premia are not stationary. Post-publication decay (McLean and Pontiff 2016) is well-documented: anomalies shrink by roughly 25% after academic publication as capital arbitrages them away. Factor models trained on historical data should be validated on out-of-sample periods; the period should not overlap the discovery sample.


The Interview Angle requires Premium

How this topic appears in real quant interviews — the questions you'll be asked at L1/L2/L3 and the model answers — is part of Premium.

Verify your understanding before moving on.