Skip to content

How to Estimate Fama–French Three- and Five-Factor Regressions

Original question

Can you explain the Fama-French three-factor and five-factor models and show how to estimate their coefficients in Python?
Regress a security or portfolio's excess return on the factor returns. The three-factor model uses the market excess return, size, and value. The five-factor model includes profitability and investment as well. Each slope estimates a partial exposure to a factor, holding the other included factors fixed, and the intercept is the average residual return relative to the chosen model.

A small-cap value fund may outperform the broad market because it has exposures the broad market comparison misses. The regression gives those exposures separate names and estimates how much each contributes to the fitted return. The factors are returns on specified portfolios or portfolio spreads, so a positive slope on the size factor indicates co-movement with periods when small stocks outperform big stocks after accounting for the other factors.

Align the factor and portfolio returns before estimating

Let yt=Ri,t−Rf,ty_t=R_{i,t}-R_{f,t}. The three-factor regression is

yt=α+βM(MKT−RF)t+βSSMBt+βHHMLt+εt.y_t=\alpha+\beta_M(MKT-RF)_t+\beta_S SMB_t+\beta_H HML_t+\varepsilon_t.

The five-factor version includes βRRMWt+βCCMAt\beta_R RMW_t+\beta_C CMA_t. SMB is small minus big, HML is high minus low book-to-market, RMW is robust minus weak operating profitability, and CMA is conservative minus aggressive investment. In the French data library, SMB is constructed differently in the three-factor and five-factor datasets. Use the series from the specification you intend to fit. If you want a nested test of adding two columns to the same regressors, explicitly hold the common columns fixed on an identical date range.

Let XX include an intercept and the selected factors. Minimising the sum of squared residuals yields X⊤Xβ^=X⊤yX^\top X\hat\beta=X^\top y if the design has full column rank. In practice, use a numerical least-squares solver rather than forming an explicit matrix inverse. For inference, consider heteroskedasticity and autocorrelation robust standard errors where the sampling and assumptions warrant them.
The unit convention matters before you fit anything. The French data files typically express returns in percent, while a returns column calculated from prices may be in decimals. Convert both to a common unit and subtract the risk-free series once from the portfolio return. The market factor is already excess market return. Using the same rows for both regressions also matters. If the five-factor model has a shorter sample, fit the three-factor model on those dates too before attributing a change in alpha to the extra regressors.

Explain a month using the fitted exposures

Suppose a monthly three-factor fit has intercept 0.10 percentage points, market beta 0.90, SMB beta 0.60, and HML beta 0.40. In a month when those factor returns are 2%, 1%, and −0.5%-0.5\%, the fitted portfolio excess return is 0.10+0.90(2)+0.60(1)+0.40(−0.5)=2.30%0.10+0.90(2)+0.60(1)+0.40(-0.5)=2.30\%. If the realised excess return is 1.90%, that month's regression residual is −0.40-0.40 percentage points. The intercept describes an average in the sample. Statistical uncertainty, benchmark choice, and trading costs affect how to interpret it.

In the arithmetic above, the value factor subtracts 0.20 percentage points because a positive exposure is multiplied by a negative factor return. If the realised excess return was 2%, the residual is 2.00−2.30=−0.302.00-2.30=-0.30 percentage points. That is one month's unexplained return. Alpha is the intercept estimated across the whole sample and is a different number. Keeping those units straight helps prevent a large residual in one month from being mistaken for persistent abnormal performance.

OLS with dated joins and robust standard errors

Start with a pandas DataFrame named monthly whose columns include the asset's total return asset_return and the chosen factor data, merged by month. Convert every return column to decimal units before fitting. Use a matching market, frequency, and currency, and select the corresponding three- or five-factor dataset before calling the function.
import statsmodels.api as sm def estimate_factors(monthly, factors): data = monthly[["asset_return", "RF", *factors]].dropna().sort_index() y = data["asset_return"] - data["RF"] x = sm.add_constant(data[factors], has_constant="add") return sm.OLS(y, x).fit(cov_type="HAC", cov_kwds={"maxlags": 3}) five = ["Mkt-RF", "SMB", "HML", "RMW", "CMA"] fit = estimate_factors(monthly, five) print(fit.params) print(fit.conf_int())

References and next steps