Python实战Fama-French多因子模型:从数据获取到回归分析与策略应用

Python实战Fama-French多因子模型:从数据获取到回归分析与策略应用
1. 从CAPM到Fama-French为什么我们需要多因子模型如果你在金融、量化投资或者数据分析领域摸爬滚打过一阵子肯定对“资产定价”这个词不陌生。简单说就是给一个资产比如股票定个“合理”的价格或者更准确地说是估算它的预期收益率。几十年来资本资产定价模型CAPM一直是教科书里的经典它告诉我们一只股票的预期超额收益率只和它相对于整个市场的风险Beta有关。市场涨它涨得更多市场跌它跌得更狠那你就该多赚点反之亦然。听起来很完美逻辑也自洽。但现实总是比理论骨感。早在CAPM诞生后不久实证研究就发现了大量它无法解释的“异象”。比如市值小的公司股票长期来看收益率似乎比大公司更高规模效应账面市值比高的公司通常被认为是“价值股”表现也往往好于低账面市值比的公司“成长股”这就是价值效应。CAPM的单一市场因子在这些“异象”面前显得力不从心。这就引出了我们今天要深入实操的Fama-French模型。它的核心思想非常直接既然一个因子市场不够那就多加几个Fama和French两位教授通过严谨的实证分析先后提出了三因子模型市场、规模、价值和五因子模型在上述基础上增加了盈利能力和投资风格因子。这些因子不再是凭空想象而是基于长期市场数据归纳出的、能显著解释股票收益截面差异的风险来源。对于数据分析师和量化研究者来说Fama-French模型不仅仅是一个理论更是一个强大的实证工具包。我们可以用它来评估基金或投资策略的真实表现是基金经理能力强还是仅仅暴露在了某个因子的风险上可以构建因子投资组合也可以作为更复杂模型的基础。接下来我将手把手带你用Python完整走一遍Fama-French三因子和五因子模型的实证分析流程从数据获取到回归检验再到结果解读过程中我会穿插大量实际编码时才会遇到的坑和技巧。2. 环境搭建与数据源选择避开第一个坑工欲善其事必先利其器。做金融数据分析环境配置和数据获取是第一步也是最容易踩坑的地方。2.1 Python环境与核心库我强烈建议使用Anaconda来管理你的Python环境它能很好地解决包依赖冲突的问题。创建一个专门用于金融分析的环境是不错的选择conda create -n ff_analysis python3.9 conda activate ff_analysis接下来安装核心库。除了经典的pandas、numpy、matplotlib我们还需要以下关键库pandas-datareader: 从网络获取金融数据的神器。注意它的一些数据源如雅虎财经可能不太稳定。statsmodels: 进行回归分析的核心它的OLS普通最小二回归和WLS加权最小二回归接口非常清晰。scipy: 用于一些统计检验。seaborn: 让图表更美观。安装命令很简单pip install pandas-datareader statsmodels seaborn。注意pandas-datareader的版本和可用数据源经常变化。如果遇到问题一个更稳定、更专业的替代方案是使用yfinance库专门针对雅虎财经或akshare一个强大的中文财经数据接口库。本文为保持通用性仍以datareader为例但会指出备选方案。2.2 关键数据获取因子数据与个股数据Fama-French模型分析需要两类数据因子数据和个股或投资组合收益率数据。1. 因子数据这是模型的“输入”。最权威的来源是肯尼斯·弗伦奇教授的个人数据图书馆。我们可以直接用pandas-datareader从网站抓取。import pandas_datareader.data as web import pandas as pd # 定义开始和结束日期 start_date ‘2010-01-01’ end_date ‘2023-12-31’ # 获取Fama-French三因子数据 (月度) # ‘F-F_Research_Data_Factors’ 是经典三因子 ff3_monthly web.DataReader(‘F-F_Research_Data_Factors’, ‘famafrench’, start_date, end_date)[0] # 通常返回一个列表第0个元素是月度数据 print(ff3_monthly.head())运行上述代码你会得到一个DataFrame列通常包括Mkt-RF(市场超额收益)SMB(规模因子)HML(价值因子) 以及RF(无风险利率)。所有数据都是小数形式如0.01表示1%。实操心得web.DataReader返回的数据结构有时让人困惑。它经常返回一个字典或列表。使用[0]获取月度数据是常见做法但最好先打印一下整个返回对象或者查看ff3_monthly的类型和形状确保你取到了正确的数据表。对于五因子数据代码名为F-F_Research_Data_5_Factors_2x3。2. 个股/投资组合数据这是模型的“被解释变量”。你需要计算目标资产比如某只股票、一个自定义的投资组合的超额收益率。假设我们分析苹果公司AAPLimport yfinance as yf # 使用yfinance作为更稳定的数据源示例 # 下载苹果公司股价数据 aapl yf.download(‘AAPL’, startstart_date, endend_date, progressFalse) # 计算月度收益率使用月末收盘价并重采样 aapl_monthly aapl[‘Adj Close’].resample(‘M’).last() # 取每月最后一天的收盘价 aapl_returns aapl_monthly.pct_change().dropna() # 计算月度收益率 # 将收益率数据索引转换为月末日期便于与因子数据对齐 aapl_returns.index aapl_returns.index.to_period(‘M’).to_timestamp(‘M’)现在我们需要计算苹果股票的超额收益率AAPL_Excess aapl_returns - ff3_monthly[‘RF’]。这里有一个关键对齐操作必须确保因子数据和收益率数据在时间索引上完全匹配同一个月度频率同一时间点。通常需要将两者的索引转换为一致的格式如PeriodIndex或DateTimeIndex后进行合并。# 将因子数据索引也转换为DateTimeIndex便于合并 ff3_monthly.index pd.to_datetime(ff3_monthly.index, format‘%Y%m’) ff3_monthly.index ff3_monthly.index pd.offsets.MonthEnd(0) # 对齐到月末 # 合并数据 data pd.concat([aapl_returns.rename(‘AAPL_Return’), ff3_monthly], axis1, join‘inner’) # 计算超额收益 data[‘AAPL_Excess’] data[‘AAPL_Return’] - data[‘RF’] data data.dropna() print(data[[‘AAPL_Excess’, ‘Mkt-RF’, ‘SMB’, ‘HML’]].head())join‘inner’确保了只保留两者共有的时间点这是避免后续回归出现维度错误的关键一步。3. 三因子模型回归实战代码与解读数据准备妥当后我们就可以进行回归分析了。我们将以苹果公司AAPL2010-2023年的月度数据为例运行Fama-French三因子模型。3.1 执行OLS回归使用statsmodels的公式API或数组接口进行回归非常方便。这里使用公式API更直观。import statsmodels.formula.api as smf # 定义回归公式被解释变量 ~ 解释变量1 解释变量2 ... # 注意这里使用的是超额收益对超额因子回归因此无需常数项错 ff3_model smf.ols(formula‘Q(“AAPL_Excess”) ~ Mkt_RF SMB HML’, datadata) # 注意列名中的减号‘-’在公式中可能引发歧义最好提前重命名列 data data.rename(columns{‘Mkt-RF’: ‘Mkt_RF’}) # 重命名列 ff3_model smf.ols(formula‘AAPL_Excess ~ Mkt_RF SMB HML’, datadata) ff3_results ff3_model.fit() print(ff3_results.summary())运行后你会得到一份详细的回归结果摘要。我们最需要关注的是以下几部分系数coef及其t统计量t和P值P|t|这对应着Alpha截距项和三个因子的载荷Beta。const(Alpha): 代表在控制了市场、规模、价值风险后该资产获得的超额收益。理论上如果市场有效Alpha应为0且不显著。一个显著为正的Alpha可能表示该资产有超额收益或模型遗漏了重要因子。Mkt_RF(Beta_Mkt): 资产对市场风险的暴露程度。类似于CAPM的Beta但更纯净因为控制了SMB和HML。SMB(Beta_SMB): 资产对规模因子的暴露。正值表示该资产表现更像小盘股。HML(Beta_HML): 资产对价值因子的暴露。正值表示该资产表现更像价值股。R-squaredR方模型解释了资产收益率波动的百分比。对于个股R方通常在20%-40%之间因为个股受自身特质风险影响很大。对于分散化的投资组合R方可能高达80%-90%。F-statisticF统计量检验整个模型是否显著的指标。3.2 结果解读与可视化假设我们得到的部分结果如下数值为示例OLS Regression Results Dep. Variable: AAPL_Excess R-squared: 0.345 Model: OLS Adj. R-squared: 0.338 Method: Least Squares F-statistic: 48.76 Date: ... Prob (F-statistic): 2.36e-25 Time: ... Log-Likelihood: 435.12 No. Observations: 168 AIC: -862.2 Df Residuals: 164 BIC: -849.6 Df Model: 3 Covariance Type: nonrobust coef std err t P|t| [0.025 0.975] ------------------------------------------------------------------------------ const 0.0082 0.002 4.100 0.000 0.004 0.012 Mkt_RF 1.1056 0.078 14.174 0.000 0.952 1.259 SMB -0.3124 0.112 -2.789 0.006 -0.533 -0.091 HML -0.1897 0.099 -1.916 0.057 -0.385 0.006 解读Alpha (const): 0.0082月均0.82%t值为4.1P值远小于0.05统计显著。这意味着在2010-2023年间即使控制了市场、规模、价值风险苹果公司股票每月仍能产生约0.82%的超额收益。这听起来很诱人但要注意这可能是因为苹果公司独特的商业模式、品牌效应等特质风险未被因子捕捉也可能只是这段样本期内的运气。市场因子载荷 (Mkt_RF): 1.1056高度显著。说明苹果股价波动比市场整体波动略大是一只贝塔略高于1的股票。规模因子载荷 (SMB): -0.3124显著为负。这符合直觉苹果是全球市值最大的公司之一其收益率变动模式与“小盘股”因子相反。当小盘股表现好时SMB为正苹果往往表现相对较差。价值因子载荷 (HML): -0.1897在10%的显著性水平上边缘显著P0.057。负值表明苹果更偏向“成长股”属性低账面市值比当价值股受追捧时HML为正苹果表现可能相对较弱。可视化能帮助我们更直观地理解。我们可以绘制残差图、拟合值与实际值对比图等。import matplotlib.pyplot as plt import seaborn as sns # 1. 残差的时间序列图检验自相关 residuals ff3_results.resid fig, axes plt.subplots(2, 2, figsize(12, 8)) axes[0, 0].plot(residuals.index, residuals) axes[0, 0].axhline(y0, color‘r’, linestyle‘--’) axes[0, 0].set_title(‘Residuals over Time’) axes[0, 0].set_xlabel(‘Date’) axes[0, 0].set_ylabel(‘Residual’) # 2. 残差的正态性检验Q-Q图 import scipy.stats as stats stats.probplot(residuals, dist“norm”, plotaxes[0, 1]) axes[0, 1].set_title(‘Q-Q Plot’) # 3. 拟合值 vs 实际值 fitted_values ff3_results.fittedvalues axes[1, 0].scatter(fitted_values, data[‘AAPL_Excess’], alpha0.6) axes[1, 0].plot([fitted_values.min(), fitted_values.max()], [fitted_values.min(), fitted_values.max()], ‘r--’, lw2) # 45度线 axes[1, 0].set_xlabel(‘Fitted Values’) axes[1, 0].set_ylabel(‘Actual Excess Returns’) axes[1, 0].set_title(‘Fitted vs Actual’) # 4. 残差 vs 拟合值检验异方差 axes[1, 1].scatter(fitted_values, residuals, alpha0.6) axes[1, 1].axhline(y0, color‘r’, linestyle‘--’) axes[1, 1].set_xlabel(‘Fitted Values’) axes[1, 1].set_ylabel(‘Residuals’) axes[1, 1].set_title(‘Residuals vs Fitted’) plt.tight_layout() plt.show()这些诊断图可以帮助我们判断OLS回归的基本假设如残差独立同分布、同方差等是否被严重违反。如果残差表现出明显的自相关或异方差可能需要考虑使用更稳健的标准误如Newey-West调整或改用GLS等模型。4. 扩展到五因子模型数据获取与模型比较Fama-French五因子模型在三因子的基础上增加了盈利因子RMW Robust Minus Weak和投资因子CMA Conservative Minus Aggressive。其思想是盈利能力更强、投资更保守的公司长期收益也可能更高。4.1 获取并处理五因子数据数据获取方式与三因子类似# 获取Fama-French五因子数据 (月度) ff5_monthly web.DataReader(‘F-F_Research_Data_5_Factors_2x3’, ‘famafrench’, start_date, end_date)[0] # 重命名列去除特殊字符 ff5_monthly ff5_monthly.rename(columns{‘Mkt-RF’: ‘Mkt_RF’, ‘RMW’: ‘RMW’, ‘CMA’: ‘CMA’}) ff5_monthly.index pd.to_datetime(ff5_monthly.index, format‘%Y%m’) ff5_monthly.index ff5_monthly.index pd.offsets.MonthEnd(0) # 合并苹果收益率与五因子数据 data_ff5 pd.concat([aapl_returns.rename(‘AAPL_Return’), ff5_monthly], axis1, join‘inner’) data_ff5[‘AAPL_Excess’] data_ff5[‘AAPL_Return’] - data_ff5[‘RF’] data_ff5 data_ff5.dropna()4.2 运行五因子回归并对比三因子# 运行五因子模型回归 ff5_model smf.ols(formula‘AAPL_Excess ~ Mkt_RF SMB HML RMW CMA’, datadata_ff5) ff5_results ff5_model.fit() print(ff5_results.summary())对比两个模型的摘要我们关注以下几点Alpha的变化加入RMW和CMA后Alpha是否依然显著数值是变大还是变小如果Alpha变得不显著或大幅减小说明新增的因子捕捉了一部分原来被归为“超额收益”的风险。因子载荷的显著性苹果对RMW和CMA的暴露是否显著这反映了苹果在盈利能力和投资风格上的特征。R方的提升五因子模型的R方是否比三因子模型有显著提高提高的幅度说明了新增因子的解释力。假设五因子回归结果中RMW的系数为0.15显著CMA的系数为-0.10不显著。这表明苹果公司的收益率变动与高盈利公司有正相关但对投资风格因子不敏感。同时Alpha可能从三因子时的0.82%降至0.65%且显著性减弱。这说明盈利因子解释了一部分苹果的优异表现。4.3 模型比较统计学视角我们可以使用统计检验来比较嵌套模型三因子是五因子的子集。statsmodels提供了似然比检验Likelihood Ratio Test。# 似然比检验 lr_test_statistic -2 * (ff3_results.llf - ff5_results.llf) # 两倍对数似然值之差 from scipy.stats import chi2 p_value chi2.sf(lr_test_statistic, df2) # 自由度是新增变量数即2 print(f‘Likelihood Ratio Test Statistic: {lr_test_statistic:.4f}’) print(f‘P-value: {p_value:.4f}’)如果P值小于0.05我们可以在5%的显著性水平上拒绝“三因子模型足够”的原假设认为五因子模型确实提供了显著更好的拟合。此外还可以比较调整后R方Adj. R-squared和AIC/BIC信息准则这些准则在模型复杂度变量数和拟合优度之间做了权衡值越小通常表示模型越好。5. 深入探讨实务中的注意事项与进阶思考把模型跑出来只是第一步如何解释结果、意识到模型的局限性才是从“会跑代码”到“会做分析”的关键跨越。5.1 模型假设与常见问题经典的Fama-French回归基于一系列OLS假设在实际金融时间序列数据中这些假设常常被违背异方差性残差的方差可能随时间变化。这不会影响系数估计的无偏性但会使标准误估计有偏从而影响t检验和F检验的可靠性。解决方案在回归时使用异方差稳健的标准误statsmodels中可以通过在.fit()方法中添加参数cov_type‘HC0’或更复杂的cov_type‘HAC’Newey-West同时处理异方差和自相关来实现。ff3_results_robust ff3_model.fit(cov_type‘HAC’, cov_kwds{‘maxlags’: 6}) # 使用Newey-West标准误 print(ff3_results_robust.summary())自相关性残差在时间上可能相关。这在日度或周度数据中更常见月度数据也可能存在。自相关性同样会导致标准误低估。Newey-West标准误是常用的修正方法。因子相关性SMB、HML等因子之间并非完全正交存在一定程度的相关性。这会导致多重共线性使得个别因子系数的估计不准确方差变大但整个模型的预测能力可能不受影响。查看方差膨胀因子VIF可以诊断共线性严重程度。from statsmodels.stats.outliers_influence import variance_inflation_factor from statsmodels.tools.tools import add_constant # 计算VIF需要常数项先准备包含常数的解释变量矩阵 X add_constant(data[[‘Mkt_RF’, ‘SMB’, ‘HML’]]) vif_data pd.DataFrame() vif_data[“feature”] X.columns vif_data[“VIF”] [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] print(vif_data)通常VIF大于10被认为存在严重共线性。对于Fama-French因子VIF通常在可接受范围内。5.2 从个股到投资组合分散化与模型解释力我们对苹果单只股票的分析R方只有0.345。这是因为个股收益中包含了大量公司特有的“特质风险”这些风险无法被共同的因子解释。如果我们分析一个充分分散化的投资组合例如按市值加权的标普500指数基金或一个按某个特征构建的多空组合模型的解释力R方会高得多因为特质风险被分散掉了。在实践中Fama-French模型更常用于评估基金或投资策略的整体表现而不是单只股票。5.3 Alpha的再审视是能力还是风险一个显著为正的Alpha是所有投资者梦寐以求的。但在下结论之前必须谨慎数据窥探偏差你可能只是偶然选择了一段该股票表现特别好的时期。解决方法是进行样本外检验或更长时间的样本分析。模型设定误差也许不是基金经理能力强而是你的模型遗漏了重要的风险因子。例如对于科技股是否应该加入一个“科技行业因子”对于全球投资的基金是否应该加入汇率因子这就是为什么后续研究发展出了更多因子如动量因子、流动性因子、行业因子等。交易成本理论上的Alpha没有考虑买卖股票的佣金、冲击成本等。实际的交易成本可能会吞噬掉一部分甚至全部的理论Alpha。因此在报告中呈现Alpha时更稳妥的说法是“在Fama-French三因子或五因子模型框架下该资产/策略在样本期内获得了统计上显著的正Alpha。” 而不是直接宣称发现了“超额收益”或“定价错误”。5.4 批量处理与自动化分析在实际研究中我们往往需要分析成百上千只股票或几十个投资组合。这时就需要自动化。核心是写一个回归函数然后循环应用。def run_ff_regression(returns_series, factor_df, model_type‘3factor’): “”” 运行Fama-French回归的通用函数。 参数: returns_series: 个股或组合的收益率序列 (pd.Series) factor_df: 包含因子数据的DataFrame model_type: ‘3factor’ 或 ‘5factor’ 返回: results_dict: 包含回归结果关键指标的字典 “”” # 数据对齐与合并 (参考前面章节) # ... 此处省略具体合并代码 ... merged_data ... # 根据模型类型选择公式 if model_type ‘3factor’: formula ‘excess_return ~ Mkt_RF SMB HML’ elif model_type ‘5factor’: formula ‘excess_return ~ Mkt_RF SMB HML RMW CMA’ else: raise ValueError(“model_type must be ‘3factor’ or ‘5factor’”) # 执行回归 model smf.ols(formulaformula, datamerged_data) results model.fit(cov_type‘HAC’, cov_kwds{‘maxlags’: 6}) # 使用稳健标准误 # 提取关键结果 results_dict { ‘Alpha’: results.params.get(‘const’, 0), ‘Alpha_t’: results.tvalues.get(‘const’, 0), ‘Alpha_p’: results.pvalues.get(‘const’, 1), ‘Beta_Mkt’: results.params.get(‘Mkt_RF’, 0), ‘R2’: results.rsquared, ‘R2_adj’: results.rsquared_adj, ‘No_obs’: results.nobs } # 根据模型类型添加其他因子载荷 factor_list [‘SMB’, ‘HML’] if model_type‘3factor’ else [‘SMB’, ‘HML’, ‘RMW’, ‘CMA’] for f in factor_list: results_dict[f‘Beta_{f}’] results.params.get(f, 0) results_dict[f‘{f}_t’] results.tvalues.get(f, 0) return results_dict # 示例批量分析一个股票列表 stock_list [‘AAPL’, ‘MSFT’, ‘GOOGL’, ‘AMZN’, ‘TSLA’] all_results {} for ticker in stock_list: # 假设已经有一个函数 get_stock_returns(ticker) 来获取收益率序列 # ret_series get_stock_returns(ticker) # results run_ff_regression(ret_series, ff3_monthly, ‘3factor’) # all_results[ticker] results pass # 将结果转换为DataFrame便于查看 results_df pd.DataFrame(all_results).T print(results_df[[‘Alpha’, ‘Alpha_p’, ‘Beta_Mkt’, ‘R2’]].head())通过这样的批量分析你可以快速筛选出哪些股票在历史上有显著的正Alpha需谨慎解读或者比较不同股票对各类因子的暴露差异。6. 超越基础从实证检验到策略构建掌握了基础的回归分析后Fama-French模型可以成为更深入量化研究的起点。6.1 构建自己的因子投资组合Fama-French因子本身是通过构建多空投资组合计算出来的。例如SMB因子是做多一篮子小市值股票同时做空一篮子大市值股票。你可以模仿这个思路在A股或其他市场根据市值、估值、盈利等指标构建本土化的因子并检验其有效性。步骤通常包括选股确定股票池如全A股剔除ST、上市不足N月等。排序在每个调仓期如每月底根据选定的指标如总市值、BP账面市值比对股票进行排序。分组将股票按排序分为若干组如10组或2组。构建组合计算每组股票下一期的等权或市值加权平均收益率。计算因子收益做多排名最高的一组做空排名最低的一组其收益率差即为该因子收益。用Python实现上述流程你就能复现出属于自己的“SMB”或“HML”因子序列并与学术界的标准因子进行对比。6.2 时序检验与截面检验我们上面做的是时序回归即用同一资产不同时期的收益率对同期的因子收益率回归。这主要用于检验一个特定资产是否在样本期内有Alpha。 另一种重要的检验是截面回归它在每个时间截面上用许多资产的收益率对它们上一期的特征如市值、BP值进行回归来检验这些特征是否能预测未来的收益率。Fama-MacBeth回归是截面检验的经典方法statsmodels没有直接的内置函数但用pandas的groupby和循环也能实现。这能帮助你发现新的、有效的选股因子。6.3 将模型整合进更大的分析框架在实际的量化投研系统中Fama-French回归可能只是一个模块。它的输出Alpha、因子暴露可以作为基金业绩归因报告的核心部分。风险管理系统的输入用于监控投资组合对各类风格因子的暴露是否偏离了基准。因子择时策略的基础通过预测因子的未来收益来调整组合暴露。整个流程走下来从数据获取、清洗、对齐到模型回归、结果诊断、批量处理每一个环节都有需要注意的细节。最大的体会是金融数据分析远不止把数据塞进模型然后看输出那么简单。对数据来源的理解、对日期对齐的谨慎、对模型假设的检验、对统计结果的经济学解读这些“软技能”往往比写代码本身更重要。比如用yfinance下载的数据是本地时间而Fama-French因子数据是月度数据如何精确地将月末的股价与当月的因子匹配就需要仔细思考。又比如看到显著的Alpha第一反应不应该是兴奋而应该是怀疑是不是我的数据周期选在了牛市是不是这个行业有特殊的驱动因素没被模型包含多问几个为什么你的分析才会更有深度也更能经得起推敲。