第 7 章 · 02 Fama-MacBeth 本节摘要:本节讲金融面板数据专用的回归方法——Fama-MacBeth 两阶段横截面回归。普通 OLS 假设误差独立,但金融面板(很多资产 × 很多期)的残差在同一期跨资产高度相关,直接 OLS 标准误偏小、t 值虚高。Fama 和 MacBeth 在 1973 年提出两阶段法:第一阶段对每个资产做时间序列回归估「因子暴露 β」;第二阶段对每个时间点做横截面回归,把收益对 β 回归估「风险溢价 λ」。最后把所有期的 λ 取平均、用各期 λ 的标准差算 t 值,自然处理了横截面相关。本节用 Fama-French 五因子 + 17 行业组合演示完整流程,并对照 linearmodels 库的一行实现。 内容来源:原项目 ,汉化并套用体系化模板。
本节摘要:本节讲金融面板数据专用的回归方法——Fama-MacBeth 两阶段横截面回归。普通 OLS 假设误差独立,但金融面板(很多资产 × 很多期)的残差在同一期跨资产高度相关,直接 OLS 标准误偏小、t 值虚高。Fama 和 MacBeth 在 1973 年提出两阶段法:第一阶段对每个资产做时间序列回归估「因子暴露 β」;第二阶段对每个时间点做横截面回归,把收益对 β 回归估「风险溢价 λ」。最后把所有期的 λ 取平均、用各期 λ 的标准差算 t 值,自然处理了横截面相关。本节用 Fama-French 五因子 + 17 行业组合演示完整流程,并对照 linearmodels 库的一行实现。
内容来源:原项目
ch07/02_fama_macbeth.ipynb,汉化并套用体系化模板。
⚠️ 风险提示:风险溢价估计对样本期高度敏感,某段历史的正溢价可能在下一段消失。t 值显著不代表稳健,要做子样本稳定性分析。
阅读完本节,你应当能够:
线性因子模型假设资产超额收益是因子暴露 × 因子溢价:
要估的是 β(暴露)和 λ(溢价)。最朴素的做法是把这个面板数据(资产 × 时间)直接丢进 OLS。问题是:
同一时期所有股票受共同的宏观冲击(如加息、衰退),残差跨资产相关。OLS 假设残差独立,会严重低估标准误,把不显著的因子误判为显著。Fama-MacBeth 通过「先时间序列后横截面」的两阶段设计,自然处理这种横截面相关。
💡 核心心法:Fama-MacBeth 的精髓是用横截面回归系数的时间序列标准差来估标准误——这天然吸收了横截面相关,不需要显式建模协方差。
notebook 用 Fama-French 五因子做演示,这是学术界最常用的多因子模型:
| 因子 | 全称 | 含义 |
|---|---|---|
| SMB | Small Minus Big | 小盘股减大盘股(规模因子) |
| HML | High Minus Low | 高账面市值比减低(价值因子) |
| RMW | Robust Minus Weak | 强盈利减弱盈利(盈利因子) |
| CMA | Conservative Minus Aggressive | 保守投资减激进投资(投资因子) |
| Rm-Rf | 市场超额 | 全市场收益减无风险利率(市场因子) |
通过 pandas_datareader 直接从 Ken French 数据库取:
import pandas_datareader.data as web ff_factor = 'F-F_Research_Data_5_Factors_2x3' ff_factor_data = web.DataReader(ff_factor, 'famafrench', start='2010', end='2017-12')[0]
测试资产用 17 行业组合,减去无风险利率得到超额收益:
ff_portfolio_data = web.DataReader('17_Industry_Portfolios', 'famafrench', start='2010', end='2017-12')[0] ff_portfolio_data = ff_portfolio_data.sub(ff_factor_data.RF, axis=0)
对每个资产(行业组合),把它的时间序列收益对因子时间序列回归,得到该资产对各因子的暴露 β:
from statsmodels.api import OLS, add_constant betas = [] for industry in ff_portfolio_data: step1 = OLS(endog=ff_portfolio_data.loc[ff_factor_data.index, industry], exog=add_constant(ff_factor_data)).fit() betas.append(step1.params.drop('const')) betas = pd.DataFrame(betas, columns=ff_factor_data.columns, index=ff_portfolio_data.columns)
得到一个 17(资产)× 5(因子)的 β 矩阵——每个行业对各因子的暴露。
对每个时间点,把当期 17 个资产的收益对它们的 β 做横截面回归,得到该期的因子溢价 λ:
lambdas = [] for period in ff_portfolio_data.index: step2 = OLS(endog=ff_portfolio_data.loc[period, betas.index], exog=betas).fit() lambdas.append(step2.params) lambdas = pd.DataFrame(lambdas, index=ff_portfolio_data.index, columns=betas.columns.tolist())
得到一个 96(月份数)× 5(因子)的 λ 矩阵——每个时期各因子的瞬时溢价。
lambdas.mean().sort_values().plot.barh(figsize=(12, 4)) # 平均风险溢价 t = lambdas.mean().div(lambdas.std()) # t 统计量
💡 t 值的计算:Fama-MacBeth 的标准误 = 各期 λ 的时间序列标准差 / √T。这等价于
mean / std * sqrt(T),但 notebook 用简化形式mean / std做相对比较。t > 2(粗略)视为显著。
手动循环有助于理解,但实务用 linearmodels.LinearFactorModel,一行就跑完两阶段并给出完整推断:
from linearmodels.asset_pricing import LinearFactorModel mod = LinearFactorModel(portfolios=ff_portfolio_data, factors=ff_factor_data) res = mod.fit() print(res) print(res.full_summary)
输出含每个因子的风险溢价估计、t 值、各组合的 α(无法被因子解释的超额),以及 J 检验(模型整体显著性)。结果与手动实现一致,但统计推断更严谨(用了 Newey-West 等稳健标准误)。
| 问题 | 应对 |
|---|---|
| β 时变性 | β 不是常数,可用滚动窗口估,或用 conditionally linear model |
| 样本期敏感 | 拆子样本分别跑,画 lambdas.rolling(24).mean() 看稳定性 |
| 测试资产选择 | 行业/规模/价值组合是经典选择,自建因子要确保足够分散 |
| 多重共线 | 因子间相关性高时,β 估计不稳,可先做因子正交化 |
notebook 用 24 个月滚动平均画 λ 的时变曲线,常发现某个溢价在某段显著正、另一段反转——这正是金融「显著性」的脆弱性。
下一节,我们实战用线性模型(Ridge/Lasso)预测股票横截面收益,并用 MultipleTimeSeriesCV 做严格评估。