ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

WindPython实战:Fama-French三因子五因子模型构建与回归

WindPython实战:Fama-French三因子五因子模型构建与回归 简介这份资源是基于Wind金融终端数据接口的Python实现面向金融量化研究者与学生提供Fama-French三因子、五因子模型从数据获取、清洗到回归分析的完整工程代码。压缩包共121个文件以101个Python脚本为主配合CSV因子数据表、TXT说明文档、XML工程配置及Markdown笔记约11.98MB目录包含日期行业数据、因子表构建、回归输出等模块。已有1380人学习下载。资源包含面板回归、因子暴露度计算、描述性统计与相关性分析等实际可运行代码并附带清洗后的因子数据CSV和带有虚拟变量的输出文件便于直接复现三因子与五因子回归结果同时提供README说明与中间过程数据帮助理解从Wind接口调用到结果分析的完整链路适合作为金融计量入门及实证研究的参考工具箱。1. Fama-French 三因子、五因子模型在 WindPython 下先把数据闭环做扎实手算 Fama-French 三因子、五因子模型最花时间的从来不是跑回归而是把每只股票的市值、账面市值比、盈利、投资指标在月末对齐成一张干净的面板。拿到Wind_Python-master.zip这类源码包以后你会发现里面封装好的 WindPy 工具函数往往是通用的真正要自己动手的是把 SMB、HML、RMW、CMA 四个因子的取数、分组、换手逻辑写成可复现的脚本。同一时点、同一套剔除规则谁跑都能得到同样的因子净值这是做资产定价和基金归因的基本功。本文按“环境与字段 → 三因子构造与回归 → 五因子扩展 → 滚动回归与排错”四条线推进给的是能在 Wind 终端上直接落地的做法。适用对象是 A 股量化研究、基金分析和资产定价方向的工程师与分析师。建议从两到三年的短样本起步先验证口径再铺全样本。2. WindPy 环境准备与 Fama-French 模型的四个关键字段2.1 连接 WindPy 并确认终端状态WindPython 不是独立的云 API它的进程直接复用 Wind 金融终端的本地通信协议。启动连接前必须先打开 Wind 终端并完成登录否则后面所有请求都会返回连接错误。最小连接代码只有两行from WindPy import w # 启动连接重复调用不影响现有会话 w.start() # 1 表示已连接0 表示未连上 print(w.isconnected()) # 返回终端版本和用户信息方便确认账号权限 print(w.wiqs())逻辑说明w.start()只需要在脚本开头执行一次w.isconnected()用来判断终端是否在线返回 1 才能继续。w.wiqs()返回的是一个列表包含终端版本、用户名等基本信息主要作用是确认当前登录账号是否具备股票行情和财务数据权限。如果是在服务器上做定时任务必须保证服务器终端窗口长期存活并勾选自动登录否则凌晨跑批的时候会因为会话过期静默失败。提示WindPy 连接失败时经常报错误码 -40520015含义是“终端未连接”。先看终端窗口别急着改代码。2.2 四维指标市值、BM、ROE、总资产增速的 Wind 字段映射Fama-French 模型需要的数据维度并不多但第一个坑就在字段名。“规模”要用总市值不用流通市值“账面市值比”用市净率取倒数“盈利”维度用 ROETTM“投资”维度用总资产同比增速。对应到 Wind 接口字段分别是mkt_cap_ard、pb_lf、roe_ttm、yoy_asset。因子维度分析指标Wind 字段说明规模总市值元mkt_cap_ard用总市值不用流通市值市值加权必须用它账面市值比市净率pb_lfBM 1 / pb_lf负值直接剔除盈利 OP净资产收益率roe_ttmTTM 口径季度更新投资 INV总资产同比增速yoy_asset低增速对应保守型组合字段取数代码如下res w.wss(stocks, mkt_cap_ard,pb_lf,roe_ttm,yoy_asset, tradeDate20240628;unit1) cap res.Data[0] # 总市值 pb res.Data[1] # 市净率 roe res.Data[2] # 盈利能力 asset_yoy res.Data[3] # 投资强度逻辑说明wss是 Wind 的截面快照接口参数串里的字段顺序决定返回Data列表的下标顺序。tradeDate20240628指定取数基准日unit1表示金额单位取元。拿到res后第一件事是检查res.ErrorCode非 0 直接跳过这批结果否则后面做分组时会把坏数据带进去。2.3 股票池剔除与月度收益对齐规则三因子和五因子模型的组合收益是按月度计算的月度收益必须用前复权口径否则分红除权会直接污染收益率序列。Wind 里pct_chg_per配合cycleM可以取月度涨跌幅但长期样本中除权除息误差会累积更稳妥的是直接用前复权收盘价自行计算close w.wsd(600519.SH, close, 2023-06-30, 2024-06-28, returnType1;freqM) # returnType1 表示前复权freqM 表示月末频率股票池剔除规则建议固定为三条剔除 ST 和 *ST剔除上市不足 6 个月的次新股剔除月度收益缺失的停牌股。金融股是否剔除取决于研究目标做全市场资产定价通常不剔做行业中性组合再单独分层。把剔除逻辑写成一个独立函数保证每期调用的是同一套口径这样因子序列的前后可比性才成立。3. 三因子模型实现在 WindPython 中构造 SMB、HML 与时间序列回归3.1 2×3 分组法与六组合收益的计算逻辑Fama-French 三因子的组合划分是固定的按市值中位数分成小盘 S 与大盘 B 两组再按账面市值比的 30% 和 70% 分位数分成低 L、中 N、高 H 三组交叉得到六个组合SL、SN、SH、BL、BN、BH。组合收益按内部股票的市值加权计算这是学术定义的标准做法等权只能作为备选方案。两个因子的定义式如下SMB (SL SN SH) / 3 − (BL BN BH) / 3HML (SH BH) / 2 − (SL BL) / 2SMB 的直观含义是小市值组合跑赢大市值组合的超额部分HML 是高账面市值比组合跑赢低账面市值比组合的超额部分。定义式不复杂复杂在每个月末都要重新切分组合再按换仓后的权重计算下个月收益循环累计成因子时间序列。3.2 Python 代码月度因子值与回归一步到位下面的函数完成单个月末时点的三因子构造输入日期字符串输出该月的市场收益、SMB、HML五个步骤注释在代码里。循环调用后拼起来就是因子序列。import pandas as pd import numpy as np from WindPy import w def build_three_factors_snapshot(trade_date): 输入月末日期返回该月的市值加权市场收益、SMB、HML # 1. 取沪深 A 股股票池 const w.wset(sectorconstituent, fdate{trade_date};windcodea00101010000) stocks const.Data[1] if const.ErrorCode 0 else [] # 2. 取月度收益、总市值、市净率、ST 状态 res w.wss(stocks, pct_chg_per,mkt_cap_ard,pb_lf,st_status, ftradeDate{trade_date};cycleM;returnType1) df pd.DataFrame({ code: stocks, ret: res.Data[0], cap: res.Data[1], pb: res.Data[2], st: res.Data[3], }) # 3. 折算 BM 并过滤异常样本 df[bm] 1.0 / df[pb] df df[(df[st] 0) (df[bm] 0) df[ret].notna() df[cap].notna() (df[cap] 0)] # 4. 规模按中位数分两组BM 按 30%/70% 分位分三组 df[size_grp] np.where(df[cap] df[cap].median(), B, S) q30, q70 df[bm].quantile(0.3), df[bm].quantile(0.7) df[bm_grp] pd.cut(df[bm], [-np.inf, q30, q70, np.inf], labels[L, N, H]) # 5. 市值加权组合收益 def cap_wavg(g): return (g[ret] * g[cap]).sum() / g[cap].sum() pm df.groupby([size_grp, bm_grp]).apply(cap_wavg) # 6. 因子公式 smb ((pm[(S, L)] pm[(S, N)] pm[(S, H)]) / 3 - (pm[(B, L)] pm[(B, N)] pm[(B, H)]) / 3) hml ((pm[(S, H)] pm[(B, H)]) / 2 - (pm[(S, L)] pm[(B, L)]) / 2) mkt_ret (df[ret] * df[cap]).sum() / df[cap].sum() return mkt_ret, smb, hml逻辑说明wset(sectorconstituent)是 Wind 的板块成分接口windcodea00101010000对应沪深 A 股板块也可以换成中证 800 或申万一级行业。returnType1配合pct_chg_per返回前复权收益cycleM表示按月频率取值。cap_wavg函数实现组合内部的总市值加权和学术定义保持一致。groupby返回的pm是以组合名称元组为索引的 Seriespm[(S, L)]这种定位方式直观且不容易把组名写错。月度循环时把返回值存入列表再拼接就得到完整因子序列。回填市场收益时注意如果目标是全 A 市场因子市场收益应该用全部样本股的市值加权收益而不是上证指数或沪深 300 的收益。因子序列中的 MKT 和回归用的市场指数要保持一致否则 alpha 的计算基准会失真。3.3 回归结果怎么看α、t 值与模型设定因子序列构建完成后检验目标组合或个股是否被三因子定价跑一个时间序列 OLSimport statsmodels.api as sm # factors: 行为月份列为 MKT/SMB/HML # port_ret: 目标组合月度收益序列 # rf: 同期无风险利率序列 X sm.add_constant(factors) # 加截距项 y port_ret - rf # 组合超额收益 model sm.OLS(y, X).fit(cov_typeHAC, cov_kwds{maxlags: 6}) print(model.summary())参数说明cov_typeHAC表示使用 Newey-West 标准误用来修正残差的异方差和自相关月度收益序列里自相关虽然不强但异常波动月份会导致异方差HAC 是稳妥选择。maxlags6是 Newey-West 的滞后阶数月度数据经验值取 6 到 12样本不足 36 个月时适当下调。回归结果核心看两点第一截距项 α 的 t 值绝对值小于 2 说明模型能解释该组合的收益大于 2 说明存在显著定价误差第二因子系数的显著性某个因子系数长期不显著说明该因子对这个组合没有解释力。三因子模型在 A 股有个已知现象HML 因子经常不显著高账面市值比组合并不稳定跑赢低账面市值比组合。这不是回归方法的问题而是模型结构本身需要扩展这正是五因子模型的切入点。4. 五因子模型扩展RMW、CMA 的构造与三因子增量对照4.1 为什么五因子比三因子多出盈利与投资维度Fama 和 French 在 2015 年增加盈利因子 RMW 和投资因子 CMA核心理由是三因子回归的残差里仍然存在系统性定价模式高盈利公司相对低盈利公司有正溢价低投资公司相对高投资公司有正溢价。RMW 因子做多高盈利组合、做空低盈利组合CMA 因子做多低投资组合、做空高投资组合目标是吃掉三因子残差里的剩余信息。在 A 股实现时盈利维度用 ROETTM近似投资维度用总资产同比增速近似这是国内实证研究里用得比较普遍的口径。严格复刻论文原始定义需要自己从利润表和资产负债表算营运利润率和总资产增速那会引入报表对齐、合并范围等额外复杂度。先用roe_ttm和yoy_asset跑通全流程是性价比更高的起步方式。4.2 在 WindPython 中加入 ROE 与总资产增速分组五因子的 2×3 分组与三因子同构规模按中位数分两组其余每个维度按 30% 和 70% 分位数分三组。三个维度依次与规模交叉形成 18 个基础组合。SMB 由三个维度分别算出的 SMB 之均值得到这是五因子模型与三因子模型在构造上的关键差异。def build_five_factors_snapshot(trade_date): # 取数时多带 roe_ttm 和 yoy_asset 两个字段 res w.wss(stocks, pct_chg_per,mkt_cap_ard,pb_lf,st_status,roe_ttm,yoy_asset, ftradeDate{trade_date};cycleM;returnType1) df pd.DataFrame({ code: stocks, ret: res.Data[0], cap: res.Data[1], pb: res.Data[2], st: res.Data[3], roe: res.Data[4], yoy: res.Data[5], }) df[bm] 1.0 / df[pb] df df[(df[st] 0) (df[bm] 0)] df df.dropna(subset[ret, cap, bm, roe, yoy]) # 分组建列 df[size_grp] np.where(df[cap] df[cap].median(), B, S) def add_group(value_col, label): q30 df[value_col].quantile(0.3) q70 df[value_col].quantile(0.7) df[label] pd.cut(df[value_col], [-np.inf, q30, q70, np.inf], labels[0, 1, 2]) add_group(bm, BMG) add_group(roe, OPG) add_group(yoy, INVG) def cap_wavg(g): return (g[ret] * g[cap]).sum() / g[cap].sum() P_BM df.groupby([size_grp, BMG]).apply(cap_wavg) P_OP df.groupby([size_grp, OPG]).apply(cap_wavg) P_INV df.groupby([size_grp, INVG]).apply(cap_wavg) # 三个维度的规模因子分别计算后取平均 SMB_BM ((P_BM[(S, 0)] P_BM[(S, 1)] P_BM[(S, 2)]) / 3 - (P_BM[(B, 0)] P_BM[(B, 1)] P_BM[(B, 2)]) / 3) SMB_OP ((P_OP[(S, 0)] P_OP[(S, 1)] P_OP[(S, 2)]) / 3 - (P_OP[(B, 0)] P_OP[(B, 1)] P_OP[(B, 2)]) / 3) SMB_INV ((P_INV[(S, 0)] P_INV[(S, 1)] P_INV[(S, 2)]) / 3 - (P_INV[(B, 0)] P_INV[(B, 1)] P_INV[(B, 2)]) / 3) SMB (SMB_BM SMB_OP SMB_INV) / 3 HML ((P_BM[(S, 2)] P_BM[(B, 2)]) / 2 - (P_BM[(S, 0)] P_BM[(B, 0)]) / 2) RMW ((P_OP[(S, 2)] P_OP[(B, 2)]) / 2 - (P_OP[(S, 0)] P_OP[(B, 0)]) / 2) CMA ((P_INV[(S, 0)] P_INV[(B, 0)]) / 2 - (P_INV[(S, 2)] P_INV[(B, 2)]) / 2) mkt_ret (df[ret] * df[cap]).sum() / df[cap].sum() return {MKT: mkt_ret, SMB: SMB, HML: HML, RMW: RMW, CMA: CMA}逻辑说明add_group函数把pd.cut的分位逻辑包装成可复用模块分别对 BM、ROE、总资产增速做三分组。每个维度的小组序号 0/1/2 从低到高排列所以 HML 和 RMW 是“2 减 0”CMA 因为是低投资对应高收益方向反着是“0 减 2”。SMB取三个维度 SMB 的均值这一点和三因子模型里只用 BM 一个维度计算 SMB 不同是五因子模型的标准做法。4.3 三因子与五因子的回归对比与检验指标拿到五因子序列后同一个组合分别跑三因子和五因子回归对比结果能直接看出扩展因子是否带来增量信息factors3 ff_data[[MKT, SMB, HML]] factors5 ff_data[[MKT, SMB, HML, RMW, CMA]] r3 sm.OLS(port_ret - rf, sm.add_constant(factors3)).fit() r5 sm.OLS(port_ret - rf, sm.add_constant(factors5)).fit() print(三因子 R2:, round(r3.rsquared, 4), alpha t:, round(r3.tvalues[const], 3)) print(五因子 R2:, round(r5.rsquared, 4), alpha t:, round(r5.tvalues[const], 3))R² 提升说明五因子吸收了更多收益波动但更关键的指标是 α 的 t 值变化。如果三因子回归里某组合 α 显著为正加入 RMW 和 CMA 后 α 变得不显著说明新增因子解释了这部分超额收益的来源。和 3.3 节一样这里仍然用 HAC 标准误样本量小于 36 个月时滞后阶数降到 3 或 4。多组合情形下可以做一个简单联合检验取 N 个测试组合分别跑五因子回归统计 α t 值绝对值大于 2 的比例。比例明显低于三因子模型说明扩展有效。更严格的 GRS 检验需要构造 N 个组合的残差协方差矩阵在样本数不大时方差波动剧烈作为内部参考可以发布结论要谨慎。5. 滚动回归与 WindPython 数据排错的三个实战注意点5.1 wss 批量上限与分段获取写法Wind 终端对单次wss请求的股票数量有限制全 A 股 5000 多只股票一次请求很容易触发错误码。常见做法是按 800 只一段分批拉取再把结果拼成完整列表def get_by_batch(codes, fields, params, batch800): out [[] for _ in fields.split(,)] for i in range(0, len(codes), batch): part codes[i:ibatch] res w.wss(part, fields, params) if res.ErrorCode ! 0: print(请求失败错误码:, res.ErrorCode) continue for k, arr in enumerate(res.Data): out[k].extend(arr) return outbatch参数根据终端版本和网络环境调整800 是保守值。分段请求失败时该批数据直接丢弃能保证整体任务不中断。5.2 缺失值与异常值的定位方法因子构造结果不对时先用最简单的方式定位把每个月分组前后的股票数量打印出来数量骤减的那一期大概率是数据源问题。常见情况是停牌股导致pct_chg_per返回 None直接dropna会静默删掉大量样本。更稳妥的做法是先做缺失统计确认删除比例不超过 5% 再执行过滤miss df[[ret, cap, bm, roe, yoy]].isna().mean() print(缺失比例:\n, miss)5.3 36 个月滚动 alpha 的完整代码单只股票或组合的动态 alpha 追踪用滚动 36 个月窗口回归实现。窗口滑过每一期保留当期回归的 α 和 t 值拼接成时间序列def rolling_five_factor_alpha(stock_ret, rf, factors, window36): stock_ret: 个股月度收益 Series索引为日期 factors: DataFrame五因子收益索引与 stock_ret 对齐 data pd.concat([stock_ret - rf, factors], axis1).dropna() data.columns [ex_ret, MKT, SMB, HML, RMW, CMA] result [] for i in range(window, len(data) 1): win data.iloc[i - window:i] X sm.add_constant(win[[MKT, SMB, HML, RMW, CMA]]) model sm.OLS(win[ex_ret], X).fit() result.append({ date: data.index[i - 1], alpha: model.params[const], t_alpha: model.tvalues[const], rsquared: model.rsquared, }) return pd.DataFrame(result).set_index(date)滚动窗口的边界条件容易写错注意循环从window开始data.iloc[i-window:i]取的是含当前期在内的最近 36 个月。因子数据如果和个股收益的月份索引没有完全对齐dropna会删掉两端后续索引错位时可以先用reindex统一频率。月度频率下无风险利率序列的变化幅度很小对 α 的数值影响有限但不代表可以省略。Wind 的数据浏览器里可以导出定期存款利率或国债收益率序列统一用月末值保证所有组合和个股用的是同一个 rf 口径即可。本文还有配套的精品资源点击获取
返回列表