Fama-French三因子模型在A股市场的Python实战从数据清洗到25组投资组合回归引言在量化投资领域Fama-French三因子模型已成为解释股票收益率的经典框架。本文将带您用Python完整实现该模型在A股市场的实证分析从原始数据获取到因子构建再到25组投资组合的回归检验。不同于理论介绍我们聚焦可复现的代码实现提供可直接应用于A股研究的工具性内容。无论您是金融专业学生还是量化研究员都能通过本文掌握三因子模型的核心实现技术。我们将使用2009-2019年全A股月频数据重点解决以下实际问题如何正确处理A股特有的ST股票和新股问题SMB规模因子和HML价值因子的精确构建方法25组投资组合的市值加权收益率计算回归结果的经济意义解读1. 数据准备与清洗1.1 原始数据获取我们需要以下核心数据表假设已从CSV文件读取import pandas as pd import numpy as np # 基础数据读取 price pd.read_csv(股价数据.csv) # 每月收盘价 ST pd.read_csv(ST标记.csv) # ST状态记录 pb pd.read_csv(市净率.csv) # 每月PB值 mkt pd.read_csv(市值数据.csv) # 每月总市值 ipodate pd.read_csv(上市日期.csv) # 股票IPO日期1.2 数据预处理关键步骤市值与账面市值比合并# 计算账面市值比BM1/PB BM pb.copy() BM[BM] 1/pb.pb BM BM.drop([pb], axis1) # 合并BM与市值数据 f pd.merge(BM, mkt, on[tradedate,stockcode]) f f.loc[f.BM 0].reset_index(dropTrue) # 剔除BM0的股票新股过滤上市不满一年# 计算上市满一年的临界日期 ipodate[entry_date] ipodate.ipodate pd.Timedelta(days365) f pd.merge(f, ipodate, onstockcode) f f.loc[f.tradedate f.entry_date].reset_index(dropTrue)ST股票过滤res [] for date in f.tradedate.unique(): fuse f.loc[f.tradedate date] st_use ST.loc[(ST.entry_dt date) ((ST.remove_dt date) | ST.remove_dt.isna())] valid_codes set(fuse.stockcode) - set(st_use.stockcode) res.append(fuse.set_index(stockcode).loc[valid_codes].reset_index()) f pd.concat(res).reset_index(dropTrue)提示A股数据清洗需特别注意两点(1) 上市首月涨跌幅限制不同 (2) ST股票的特殊交易规则2. 因子构建方法论2.1 规模因子(SMB)与价值因子(HML)Fama-French三因子的核心在于双重排序法构建因子每年5月末进行分组f[ym] f.tradedate.dt.year*100 f.tradedate.dt.month f_5 f.loc[f.ym % 10 5].copy() # 每年5月数据BM分组高、中、低def split_BM(x): x.loc[x.BM x.BM.quantile(0.7), group_BM] H x.loc[x.BM x.BM.quantile(0.3), group_BM] L return x f_5[group_BM] M f_5 f_5.groupby(ym).apply(split_BM)市值分组大、小def split_SIZE(x): x.loc[x.mkt x.mkt.median(), group_SIZE] B return x f_5[group_SIZE] S f_5 f_5.groupby(ym).apply(split_SIZE)形成6个投资组合f_5[portfolio_name] f_5.group_SIZE / f_5.group_BM2.2 因子收益率计算SMB因子公式SMB 1/3(Small/High Small/Medium Small/Low) - 1/3(Big/High Big/Medium Big/Low)HML因子公式HML 1/2(Small/High Big/High) - 1/2(Small/Low Big/Low)Python实现# 计算组合收益率市值加权 port_ret f.groupby([tradedate,portfolio_name]).apply( lambda x: (x.ret*x.mkt).sum()/x.mkt.sum()) # 转换为宽表 port_ret_pivot port_ret.unstack() # 计算因子收益率 SMB (port_ret_pivot[S/H] port_ret_pivot[S/M] port_ret_pivot[S/L])/3 - (port_ret_pivot[B/H] port_ret_pivot[B/M] port_ret_pivot[B/L])/3 HML (port_ret_pivot[S/H] port_ret_pivot[B/H])/2 - (port_ret_pivot[S/L] port_ret_pivot[B/L])/23. 25组投资组合构建3.1 五等分分组方法将市值和BM分别分为5组形成5x5矩阵groups 5 f_5 f.loc[f.ym % 10 5, [stockcode,tradedate,BM,mkt]].copy() # BM五等分 f_5[g_BM] f_5.groupby(tradedate)[BM].rank( pctTrue).apply(lambda x: np.ceil(x*groups)) # 市值五等分 f_5[g_SIZE] f_5.groupby(tradedate)[mkt].rank( pctTrue).apply(lambda x: np.ceil(x*groups))3.2 组合收益率计算# 合并分组信息 f pd.merge(f, f_5[[stockcode,tradedate,g_BM,g_SIZE]], on[stockcode,tradedate]) # 计算25组组合收益率 port_ret_25 f.groupby([tradedate,g_BM,g_SIZE]).apply( lambda x: (x.ret*x.mkt).sum()/x.mkt.sum()) port_ret_25 port_ret_25.unstack().reset_index()4. 回归分析与结果解读4.1 三因子模型回归对每个组合进行时间序列回归R_i - R_f α β1*(Rm-Rf) β2*SMB β3*HML εPython实现import statsmodels.api as sm # 准备解释变量 ff3 pd.concat([SMB, HML, mkt_rf], axis1) ff3[Intercept] 1 x ff3[[SMB,HML,mkt_rf,Intercept]].values # 存储结果 results [] for i in range(25): y port_ret_25.iloc[:,i1].values model sm.OLS(y, x).fit() results.append({ Group: port_ret_25.columns[i1], Alpha: model.params[-1], Beta_MKT: model.params[2], Beta_SMB: model.params[0], Beta_HML: model.params[1], R2: model.rsquared })4.2 结果分析示例典型回归结果呈现组合Alphaβ_MKTβ_SMBβ_HMLR²1/10.003**0.850.620.410.723/30.0011.02-0.080.050.885/5-0.002*1.15-0.35-0.820.81关键发现小市值效应小市值组合(1/x)对SMB因子暴露显著为正价值溢价高BM组合(x/5)对HML因子暴露为负A股特有现象大市值组合表现出更显著的市场风险暴露注意A股市场常出现与美股不同的因子特征需结合本地市场环境解读5. 完整代码框架以下是整合后的Jupyter Notebook代码结构# 1. 数据准备 def load_and_clean(): # 数据读取与清洗代码 ... # 2. 因子构建 def construct_factors(): # SMB/HML构建代码 ... # 3. 组合回归 def portfolio_regression(): # 25组组合回归代码 ... # 主程序 if __name__ __main__: df load_and_clean() factors construct_factors(df) results portfolio_regression(df, factors) # 结果可视化 plot_results(results)实际应用中建议将完整代码封装为类结构class FamaFrench3Factor: def __init__(self, start_date2009-05, end_date2019-12): self.data None self.factors None def load_data(self): # 实现数据加载 ... def build_factors(self): # 实现因子构建 ... def run_regression(self): # 实现回归分析 ...通过这种模块化设计可轻松扩展为Carhart四因子或其他多因子模型。