Fit a Fama-MacBeth (1973) two-pass cross-sectional regression: asset_tickers is treated as the cross-section of test assets, factor_tickers as the risk factor(s) whose risk premia are estimated – the standard procedure for testing whether a proposed risk factor is actually priced.

The method runs in two passes:

  1. Time-series pass (per asset): regress each asset’s returns on the factor(s) to estimate its factor loadings (betas).
  2. Cross-sectional pass (per period): regress that period’s returns of all assets on those betas, which gives one estimate of each factor’s risk premium per period.

The formula is as follows:

\[\text{First pass:}\;\; \text{R\_i},\;\; t = \alpha_{i} + \text{beta\_i'} \cdot F_{t} + e_{i},\;\; \text{t},\;\; \text{for each asset i},\;\; \text{over the full sample}\] \[\text{Second pass:}\;\; \text{R\_i},\;\; t = \gamma_{0},\;\; t + \text{gamma\_t'} \cdot \beta_{i} + u_{i},\;\; \text{t},\;\; \text{for each period t (gamma\_0,t only with add\_constant=True)}\] \[\text{Risk premium:}\;\; \gamma_{hat} = \text{mean}_{t}(\gamma_{t})\] \[\text{Standard error:}\;\; \operatorname{SE}(\gamma_{hat}) = \text{std}_{t}(\gamma_{t}) / \sqrt{T},\;\; \text{with ddof=1}\] \[t = \gamma_{hat} / \operatorname{SE}(\gamma_{hat}),\;\; \text{with p-values from a Student-t distribution with T - 1 degrees of freedom}\]
  • R are the asset tickers’ column (Return by default) and F the factor tickers’ column (the Benchmark by default)

The risk premium is the average of the per-period estimates, and its standard error comes from how much those estimates vary over time. That is the method’s key contribution: it accounts for correlation between the assets’ residuals without having to model it. A significant positive premium means investors are compensated for exposure to the factor. This uses a single full-sample set of betas (the textbook variant), not the original rolling-window betas.

The first-pass betas are estimates, which makes the second-pass standard errors too small; the Shanken (1992) correction, not applied here, widens them. The returns are not in excess of the risk-free rate, so the intercept is the return of a zero-beta asset, which should be close to the risk-free rate if the factors price the assets.

For more information about the method, see the following papers:

  • Fama, E.F. & MacBeth, J.D. (1973). “Risk, Return, and Equilibrium: Empirical Tests.” Journal of Political Economy, 81(3), 607-636.
  • Shanken, J. (1992). “On the Estimation of Beta-Pricing Models.” Review of Financial Studies, 5(1), 1-33.
  • Cochrane, J.H. (2005). “Asset Pricing.” Princeton University Press, Chapter 12.

Also known as: two-pass regression, Fama-MacBeth procedure.

Fama-MacBeth Regression in Python or via the MCP

Install the Finance Toolkit with:

pip install financetoolkit -U

Then call get_fama_macbeth_regression as shown below.

from financetoolkit import Toolkit

toolkit = Toolkit(
    ["AAPL", "MSFT", "JPM", "XOM", "JNJ", "PG", "KO", "WMT", "CAT", "NEE"],
    api_key="FINANCIAL_MODELING_PREP_KEY",
    start_date="2021-01-01",
    end_date="2025-12-31",
)

# The Benchmark (the default factor) is the single risk factor and the ten
# stocks are the test assets.
toolkit.econometrics.get_fama_macbeth_regression(period="monthly")

Or, after setting up the Finance Toolkit MCP, ask:

Was exposure to the S&P 500 rewarded across Apple, Microsoft, JPMorgan Chase, ExxonMobil, Johnson & Johnson, Procter & Gamble, Coca-Cola, Walmart, Caterpillar and NextEra Energy from 2021 to 2025, according to a Fama-MacBeth regression on monthly returns?

Which returns:

  Risk Premium Std. Error t-Statistic P-Value
Intercept 0.0066 0.0058 1.1505 0.2547
Benchmark 0.0101 0.008 1.2668 0.2103

Using Your Own Data

get_fama_macbeth_regression runs on any data, not only prices: ratios, economic indicators, yield curves or your own dataset. Pass a DataFrame, a Series or a dictionary of them to Econometrics as data, with the dates in the rows or the columns, and set changes to how its changes are calculated: "percentage", "log", "difference" for rates and ratios, or None when the data are changes already. For example, for the monthly unemployment rates of the United States and Germany:

from financetoolkit import Economics, Econometrics

economics = Economics(start_date="2000-01-01")

unemployment = economics.get_unemployment_rate(
    countries=["United States", "Germany"], period="monthly"
)

econometrics = Econometrics(data=unemployment, changes="difference")

econometrics.get_fama_macbeth_regression()

Parameters

get_fama_macbeth_regression accepts the following parameters.

ParameterTypeDescriptionExample
factor_tickersstr | list[str] | NoneThe ticker(s) whose returns are used as the risk factor(s) (e.g. “Benchmark” for a single-factor/CAPM-style test). Defaults to None, meaning ["Benchmark"]."Benchmark"
asset_tickersstr | list[str] | NoneThe ticker(s) forming the cross-section of test assets. Defaults to None, meaning every Toolkit ticker (including “Benchmark”) not already used as a factor.["AAPL", "MSFT", "GOOGL"]
periodstrThe data frequency (daily, weekly, monthly, quarterly, or yearly). Defaults to “daily”."weekly"
columnstrThe historical data column to regress on. Defaults to “Return”."Return"
add_constantboolWhether to include an intercept in the second-pass cross-sectional regression. Defaults to True.False
roundingint | NoneThe number of decimals to round the results to. Defaults to None.2
Share