保姆级教程:用Python和baostock复现Fama-French三因子模型,手把手教你分析A股
用Python实战Fama-French三因子模型从理论到A股分析全流程解析在量化投资领域Fama-French三因子模型犹如一盏明灯为理解股票收益提供了清晰的框架。不同于传统CAPM模型的单一市场视角这一诺奖级理论通过引入市值和账面市值比因子显著提升了解释力。本文将带您用Python和baostock数据完整走通从数据获取到模型应用的闭环特别针对A股市场特性进行适配优化。1. 环境准备与数据源配置工欲善其事必先利其器。复现经典模型首先需要搭建稳定的工具链# 核心工具栈安装 !pip install baostock statsmodels pandas numpy matplotlib seaborn --upgrade关键数据源选择依据baostock提供稳定免费的A股历史行情相比其他接口更适配本土市场央财因子数据国内权威计算的因子数据避免自行构建的计算偏差数据周期建议选择3-5年窗口期既能捕捉市场特征又避免过度拟合注意baostock需注册获取token每日调用限额5000次批量处理时建议添加延时2. 因子数据深度处理原始数据往往需要清洗才能用于建模。央财提供的五因子数据包含我们需要的三大核心因子字段名含义计算方式单位mkt_rf市场风险溢价市场组合收益 - 无风险利率%smb市值因子小市值组合 - 大市值组合收益%hml账面市值比因子高BM组合 - 低BM组合收益%# 数据加载与预处理示例 factors pd.read_csv(fivefactor_daily.csv, index_coltrddy, parse_dates[trddy], usecols[trddy,mkt_rf,smb,hml,rf]) # 处理异常值 factors factors[(factors.abs() 3).all(axis1)] # 剔除3σ以外的极端值 factors factors.asfreq(D).fillna(methodffill) # 交易日对齐3. 个股数据处理技巧以中国平安(601318.SH)为例需特别注意A股特有的数据处理要点复权处理关键参数adjustflag1前复权adjustflag2后复权adjustflag3不复权推荐用于学术研究# baostock数据获取最佳实践 def get_stock_data(code, start, end): bs.login() rs bs.query_history_k_data_plus( code, fieldsdate,close,volume, start_datestart, end_dateend, frequencyd, adjustflag3 # 保持原始价格 ) df rs.get_data().set_index(date) df.index pd.to_datetime(df.index) bs.logout() return df.astype(float) zgpa get_stock_data(sh.601318, 2020-01-01, 2023-06-30)4. 收益率计算与特征工程对数收益率在金融建模中具有数学优势# 收益率计算对比 price zgpa[close] simple_ret price.pct_change() # 简单收益率 log_ret np.log(price/price.shift(1)) # 对数收益率 # 可视化对比 plt.figure(figsize(10,4)) plt.plot(simple_ret.cumsum(), label简单收益率) plt.plot(log_ret.cumsum(), label对数收益率) plt.legend(); plt.title(收益率计算方式对比)关键发现短期来看两种计算方法差异不大长期累计时对数收益率更稳定多因子模型通常采用对数收益率5. 模型构建与结果解读使用statsmodels进行回归分析时需要特别关注金融数据的特性# 合并数据集 data pd.merge(factors, log_ret.to_frame(return), left_indexTrue, right_indexTrue) data[excess_return] data[return] - data[rf] # 计算超额收益 # 三因子模型回归 model sm.OLS( data[excess_return], sm.add_constant(data[[mkt_rf, smb, hml]]) ) results model.fit(cov_typeHAC, cov_kwds{maxlags: 5}) # 考虑自相关回归结果关键指标解读系数经济含义中国平安(2020-2023)统计显著性constα超额收益-0.0002p0.12mkt_rf市场风险暴露0.92***p0.001smb小市值风险暴露-0.31**p0.03hml价值股风险暴露0.45***p0.001*** p0.01, ** p0.05中国平安呈现显著的价值股特征对市值因子呈负暴露6. 多股票对比分析建立自动化分析流程可提升研究效率def analyze_stock(code, start, end): # 获取数据 stock_data get_stock_data(code, start, end) log_ret np.log(stock_data[close]/stock_data[close].shift(1)) # 合并因子 merged pd.merge(factors, log_ret.to_frame(return), left_indexTrue, right_indexTrue) merged merged.dropna() # 模型回归 model sm.OLS( merged[return] - merged[rf], sm.add_constant(merged[[mkt_rf,smb,hml]]) ) results model.fit() return pd.Series({ alpha: results.params[const], beta: results.params[mkt_rf], smb_beta: results.params[smb], hml_beta: results.params[hml], rsquared: results.rsquared }) # 示例对比不同行业股票 stocks { 消费:sh.600519, # 茅台 金融:sh.601318, # 平安 科技:sh.600588, # 用友 医药:sh.600276, # 恒瑞 } results pd.DataFrame({name: analyze_stock(code, 2020-01-01,2023-06-30) for name, code in stocks.items()})行业对比发现消费股呈现低市场风险暴露(β≈0.8)科技股具有显著的小市值特征(SMBβ0)金融股普遍呈现价值股特性(HMLβ0.4)7. 策略回测与绩效评估完整的量化研究需要验证模型的实际效果# 回测框架关键指标计算 def backtest(returns, factors, window252): params: returns: 个股收益率序列 factors: 因子数据 window: 滚动窗口长度 results [] for i in range(window, len(returns)): # 滚动回归 train_data pd.merge( returns.iloc[i-window:i], factors.iloc[i-window:i], left_indexTrue, right_indexTrue ) model sm.OLS( train_data.iloc[:,0] - train_data[rf], sm.add_constant(train_data[[mkt_rf,smb,hml]]) ) res model.fit() # 预测下一期 current_factors factors.iloc[i] pred res.predict([1, current_factors[mkt_rf], current_factors[smb], current_factors[hml]]) results.append(pred[0] current_factors[rf]) # 转回总收益 return pd.Series(results, indexreturns.index[window:]) # 执行回测 pred_ret backtest(log_ret, factors) actual_ret log_ret[pred_ret.index] # 计算IC信息系数 ic pred_ret.corr(actual_ret) print(f信息系数(IC): {ic:.2%})绩效评估矩阵指标计算公式中国平安行业平均年化收益率(1总收益)^(252/天数)-18.2%6.5%最大回撤最大峰值到谷值跌幅-23.4%-28.1%夏普比率年化收益/年化波动0.820.65预测IC预测与实际收益相关系数0.150.088. 常见问题与解决方案Q1因子数据频率如何选择日频数据噪声较大但样本充足月频数据更稳定但可能丢失短期特征推荐做法先用月频验证理论再用日频优化Q2如何处理缺失数据# 缺失数据处理方案 factors factors.fillna(methodffill) # 前向填充 factors factors.dropna() # 或直接删除 # 交易日对齐技巧 factors factors.reindex(pd.date_range(start, end, freqD))Q3回归结果不显著怎么办检查因子共线性factors[[mkt_rf,smb,hml]].corr()尝试扩展时间窗口考虑加入行业虚拟变量在实战中我发现A股市场的市值效应呈现明显的阶段性特征。2015年前小盘股超额收益显著但近年来随着注册制改革这种效应正在减弱。建议每半年重新检验一次因子有效性动态调整模型参数。