Wind取数到Fama-French因子复现:Python与statsmodels实战

📅 发布时间:2026/10/11 18:36:27
Wind取数到Fama-French因子复现:Python与statsmodels实战
简介这份压缩包聚焦法玛-弗伦奇三因子与五因子模型的 Python 实现面向金融量化研究入门者、金融工程学生以及需要实证资产定价的从业者。内容围绕 Wind 金融终端数据接口覆盖因子数据获取、pandas 数据清洗、statsmodels 多元回归建模及结果显著性分析帮助使用者复现经典多因子模型并理解规模、价值、盈利、投资等因子对股票收益的解释能力。压缩包共一百二十一个文件包含一百零一个 Python 脚本、六个 CSV 数据文件以及若干说明文档脚本负责数据下载、预处理与回归分析CSV 用于存放因子表和中间结果整体约十二 MB小巧精干适合直接运行调试。当前已有 1383 人学习浏览说明其在量化学习场景中具备一定参考价值。通过这份资源读者可以快速搭建起从 Wind 取数、因子构造到回归输出的完整流程并借助配套的 README 和示例数据验证模型效果。1. 从Wind取数到Fama-French因子落地这份zip解压之后怎么用把Wind_Python-master.zip解压你看到的是 6 个 CSV、一个 Python 脚本和一个 IDE 工程文件不是成品论文也不是可以直接跑完出图的 Jupyter Notebook。它更像一个「半成品工程」数据管道、因子构建、回归输出的中间产物都在里面需要你自己理清顺序、补上参数、用 Wind 终端把数据重新灌进去才能跑完三因子和五因子模型。换句话说这份资源解决的是「从 Wind 取数到回归检验」这条完整链路上的数据准备工作而不是给你一个黑盒式定价模型。它的使用场景很明确你在做量化研究、毕业论文或者面试笔试题需要自己复现 Fama-French 三因子和五因子模型又不满足于直接调用第三方封装好的因子库。通过这份工程文件你能看到一个人如何用 Python 从 Wind 拉取月度行情和财务数据清洗对齐、构建 SMB、HML、RMW、CMA 因子再交给 statsmodels 做回归。适合具备一定 pandas 基础、正在跟 Wind 数据较劲的从业者和学生。下面我按自己拆这类项目的顺序把每个文件和每一步操作逐条讲清楚。2. 数据集与取数脚本先搞清六个CSV是干什么的拿到这种压缩包我第一步从来不是打开代码而是先把 CSV 列名过一遍搞清楚数据形态。这一步能省掉大量后面摸索的时间。这份资源里的六个 CSV 各自承担了不同环节的数据支撑理解它们之间的生产关系比直接跑脚本更重要。2.1 文件清单和各自定位压缩包里没有单独的因子收益率序列文件所有因子数据都藏在面板数据里。我把每个文件的实际作用整理成一张表你对照自己的复现需求来看文件内容形态在模型链路中的作用final_all_stocks_cleaned_factors_table.csv个股-月份面板包含市值、账面市值比、盈利、投资等因子指标构建 SMB、HML、RMW、CMA 的原始输入output_include_dummy.csv回归输入表包含行业或月份哑变量做带控制变量的多因子回归避免行业/时间效应干扰output_correlation.csv因子两两相关性矩阵检查 SMB、HML、RMW、CMA 之间的多重共线性date_industry_30.csv每只股票每个月的行业分类行业分组、哑变量生成、样本筛选describe_date_industry_30.csv行业分布的描述统计验证取数范围是否覆盖完整检查样本均衡性test.csv验证用样本数据样本外检验防止因子过拟合特定区间这六个文件我建议按「原始数据 → 清洗数据 → 回归输入 → 验证数据」四层归类。final_all_stocks_cleaned_factors_table.csv是核心其它文件都是围绕它生成或衍生的。date_industry_30.csv和describe_date_industry_30.csv如果你只做纯因子回归可能用不上但做哑变量回归时它们是必需品。2.2 begin_Store_Month_data.py从Wind落地月度数据这个脚本名很直白begin 存月度数据。它是整条链路的起点作用是从 Wind 终端把股票行情和财务指标按月度拉下来落成本地 CSV供后续因子构建使用。Wind 的 Python 接口是WindPy使用前提是你电脑上装了 Wind 金融终端并且有数据权限。脚本核心逻辑简化后大致是这样# begin_Store_Month_data.py 的核心取数逻辑简化版 from WindPy import w import pandas as pd w.start() # 连接本地 Wind 终端返回值非 0 即失败 # 股票池你关注的股票代码实际项目里一般用 wset 拉全市场 codes [000001.SZ, 000002.SZ, 600000.SH] # 月度收益率周期为月前复权 ret_df w.wsd( codes, pct_chg, 2018-01-31, 2024-12-31, PeriodM;PriceAdjF ).Data ret_df pd.DataFrame(ret_df).T ret_df.columns codes # 财务指标按报告期取tradeDate 指定用哪一天的财报 fund_df w.wss( codes, eps_ttm,roe_ttm,total_assets, tradeDate20241231;unit1 ).Datawsd接口用于取带时间序列的行情数据参数PeriodM表示把日频数据聚合成月频PriceAdjF是前复权意义是剔除分红送股对价格序列的跳跃影响让收益率计算连续。pct_chg返回的是涨跌幅百分比月度周期下直接就是当月相对上月末的收益率。wss是截面数据接口一次取某一时点所有股票的财务指标tradeDate20241231表示取截至 2024 年 12 月 31 日已披露的最新财报数据。需要特别提醒的是Wind 的wsd返回的是一个.Data列表对象里面对应每个字段每个股票的矩阵。新手最容易在这步翻车直接把.Data当 DataFrame 塞进pd.DataFrame()结果行列全反。我习惯先把.Data转成 numpy 数组再转置最后统一套上股票代码和日期索引。取完数据后脚本会把这些月度数据整合输出成final_all_stocks_cleaned_factors_table.csv这样的文件供后续因子构建直接读取。2.3 数据对齐策略先对齐再建因子Fama-French 因子构建最耗时的不是取数而是对齐。月度数据有一个天然难点不是每只股票在每个月份都有数据。停牌、上市时间晚于起点、退市都会让面板数据出现空洞。常见做法是先把日期索引统一成「Wind 自然月最后一个交易日」再对每只股票的月收益率做重采样最后用merge横向拼接而不是直接pd.concat。# 统一日期索引并 forward-fill 填充停牌缺口 all_data ret_df.merge(fund_df, left_indexTrue, right_indexTrue, howleft) all_data all_data.sort_index().ffill() # 剔除上市不足 6 个月的样本 min_list_days (all_data[trade_date] - all_data[list_date]).dt.days all_data all_data[min_list_days 180]ffill的作用是向前填充把停牌期间缺失的财务指标补成上一个交易日的值。但注意这里有个坑财务指标可以 ffill收益率不能 ffill否则会凭空造出零收益或假收益。停牌期间的收益率应该直接置空或剔除而不是用前值填充。样本内外数据都清理干净后才能进入因子构建环节。3. 构建三因子和五因子从2x3分组到因子序列生成如果你之前只跑过现成的因子库调用这一章是理解整个模型的钥匙。Fama-French 的核心思路不是直接计算某个指标而是把股票按两个维度交叉分组再通过组合收益的差值剥离出「纯因子收益」。这份资源里的final_all_stocks_cleaned_factors_table.csv本质上就是为了支撑这套分组逻辑。3.1 因子方向与分组规则先明确六个因子的定义方向否则后面跑回归时系数符号会看糊涂。三因子里SMB 是「小市值减大市值」HML 是「高账面市值比减低账面市值比」账面市值比高的是价值股低的是成长股。五因子在 SMB 基础上把 HML 拆分重构新增的 RMW 是「高盈利减低盈利」CMA 是「保守投资减激进投资」。因子全称方向分组变量预期含义MKT市场超额收益市场组合减无风险利率无承担系统性风险的补偿SMB小市值减大市值小盘组合减大盘组合流通市值小盘股风险溢价HML高B/M减低B/M价值股减成长股账面市值比价值溢价RMW强盈利减弱盈利高盈利减低盈利ROE或营业利润盈利质量溢价CMA保守减激进低投资减高投资总资产增长率投资行为溢价我在实际复现时习惯用流通市值做规模分组因为 A 股的总市值里包含大量限售股用总市值分组的 SMB 系数在回归中经常不稳定。账面市值比用1/PB近似PB 数据在 Wind 里直接可得。盈利用ROE_TTM投资用total_assets的同比增速。这个选择顺序对应到 FF 原论文里的 2x3 分组法。3.2 2x3分组规模与价值交叉最常见的分组是 2x3市值按中位数分成大小两组账面市值比按 30% 和 70% 分位数分成低中高三组形成 6 个组合。每月按组合内个股等权平均收益计算组合收益率。核心代码可以复用下面的逻辑# 2x3 分组构建 SMB 与 HML月度执行 import pandas as pd import numpy as np def build_ff3(mktcap, bm, ret, group_date): # mktcap: 月度市值面板; bm: 账面市值比; ret: 月度收益率 # 取当月末截面 df pd.DataFrame({ mktcap: mktcap.loc[group_date], bm: bm.loc[group_date], ret: ret.loc[group_date] }).dropna() # 市值按中位数分 S/B df[size] np.where(df[mktcap] df[mktcap].median(), B, S) # BM 按 30%/70% 分位分 L/M/H lower, upper df[bm].quantile([0.3, 0.7]) df[value] pd.cut(df[bm], [-np.inf, lower, upper, np.inf], labels[L, M, H]) groups df.groupby([size, value])[ret].mean() # SMB 三个小盘组合均值 - 三个大盘组合均值 smb (groups[S, H] groups[S, M] groups[S, L]) / 3 - \ (groups[B, H] groups[B, M] groups[B, L]) / 3 # HML 大市值与小市值的价值组合均值差 hml (groups[S, H] groups[B, H]) / 2 - \ (groups[S, L] groups[B, L]) / 2 return smb, hml这段代码按月截面对每只股票打上size和value两个标签然后按六组分别计算等权收益。SMB 的计算方式对小盘三组和大盘三组分别取平均再相减HML 则是高 BM 两组均值减低 BM 两组均值。为什么要取平均而不是只取某一组因为这个过程把规模和价值的交叉影响互相抵消掉了提取出来的才是纯粹的规模溢价和价值溢价。dropna()很关键剔除了当月没有市值、账面市值比或收益率的股票否则分组权重会失真。3.3 五因子的增量盈利因子和投资因子五因子模型的关键变化是摒弃 HML改在 2x2 分组下同时控制规模和盈利/投资。2x2 分组比 2x3 更简洁市值按中位数分两组盈利ROE_TTM和投资总资产增速各自按中位数分两组同样形成 4 个组合然后做均值差。# 在 2x2 分组下构建 RMW 和 CMA沿用上面的 df新增盈利与投资列 def build_ff5_more(mktcap, roe, inv_growth, ret, group_date): df pd.DataFrame({ mktcap: mktcap.loc[group_date], roe: roe.loc[group_date], inv: inv_growth.loc[group_date], ret: ret.loc[group_date] }).dropna() median_size df[mktcap].median() df[size] np.where(df[mktcap] median_size, B, S) df[profit] np.where(df[roe] df[roe].median(), R, W) df[invest] np.where(df[inv] df[inv].median(), A, C) groups df.groupby([size, profit, invest])[ret].mean() # 简化用 2x2: 高盈利组均值 - 低盈利组均值 rmw groups.xs(R, levelprofit).mean() - groups.xs(W, levelprofit).mean() cma groups.xs(C, levelinvest).mean() - groups.xs(A, levelinvest).mean() return rmw, cma真实的 Fama-French 五因子构建比这个模板更细原论文用 2x3 分组分别处理盈利和投资再交叉取平均。但在 A 股做实证时2x2 分组是更现实的选择因为样本量和月度换手率都受限2x3 分组容易出现某组合里只有两三只股票的极端情况均值失真。xs函数在这里用于按层级索引取值如果你不熟悉直接df[df[profit] R][ret].mean()也是一样的效果。3.4 财务数据的时点问题这是整个构建过程中最容易产生“伪因子”的环节如果你直接用当期财报数据做分组等于把未来信息引入了历史回测会产生前视偏差。正确做法是给财务数据设置静默期——财报发布日之后至少 6 个月因子才能使用该期数据。原因在于 A 股年报披露截止日是次年 4 月 30 日一季报、半年报和三季报披露时间各不相同直接用报告期去匹配交易日期会带来严重的数据穿越。# 给财务数据打上可用日期标签强制 6 个月后生效 fund_available_date fund_pub_date pd.DateOffset(months6) all_data all_data[ all_data[trade_date] fund_available_date ]这段代码的原理就是把财务数据的发布日期统一往后推 6 个月。你不用纠结这个值是不是最精确Fama 本人也是这么做的它反映的是“财报数据被市场消化的时间”。跳过这一步你构建的 HML 和 RMW 因子会带上前视偏差回归系数的显著性和投资组合的回测收益都是虚高的实盘一跑就露馅。4. 回归建模从三因子到五因子的差异分析因子序列构建完成后下一步就是把个股超额收益对因子做时间序列回归。这份资源里的output_include_dummy.csv就是为这一步准备的回归输入表。它把个股月度超额收益、因子收益率和哑变量放在同一张表里直接丢进statsmodels就能跑。4.1 回归模型设定Fama-French 时间序列回归的公式是这样的Ri - Rf α β1 × (Rm - Rf) β2 × SMB β3 × HML ε 三因子 Ri - Rf α β1 × (Rm - Rf) β2 × SMB β3 × HML β4 × RMW β5 × CMA ε 五因子其中Ri - Rf是每只股票的月度超额收益因子值用上一节构建的月度序列。回归的核心目的是看两类东西第一α截距项是否显著为 0如果显著不为 0说明该股存在因子无法解释的超额收益第二各因子的系数和 t 值是否稳定决定该股在对应因子上是否有显著暴露。实际操作中我通常先把三因子和五因子都跑一遍再对比调整后的R²看新增的盈利和投资因子有没有提供额外的解释力。如果调整R²提升不大且新因子系数不显著那说明这些样本里五因子相对三因子的增量价值有限。4.2 statsmodels回归与Newey-West校正用 statsmodels 跑回归非常直接但金融时间序列的残差通常存在异方差和自相关直接用默认的 OLS 标准误会让 t 值虚高因子显著性可能被高估。所以回归时我会用 Newey-West 调整后的标准误import statsmodels.api as sm # 读取回归数据包含超额收益和五因子 df pd.read_csv(output_include_dummy.csv, index_col0, parse_datesTrue) # 三因子模型 X3 sm.add_constant(df[[MKT, SMB, HML]]) model3 sm.OLS(df[excess_ret], X3).fit(cov_typeHAC, cov_kwds{maxlags: 4}) # 五因子模型 X5 sm.add_constant(df[[MKT, SMB, HML, RMW, CMA]]) model5 sm.OLS(df[excess_ret], X5).fit(cov_typeHAC, cov_kwds{maxlags: 4}) print(model3.summary()) print(model5.summary())cov_typeHAC是 Newey-West 异方差自相关一致估计量maxlags4是我对月度数据惯用的滞后阶数。经验规则是取1.3 × N^(1/3)对 60 个月的历史数据来说大概是 4 到 5 阶。用不用 HAC 对系数估计值几乎没有影响但标准误会明显变大t 值会变保守这才是更可信的显著性判断。add_constant是为了给模型加上截距项。4.3 共线性诊断output_correlation.csv的用法五因子里 SMB、HML、RMW、CMA 之间理论上应该相关性很低因为它们的设计目的就是捕捉不同维度的溢价。但 A 股市场结构特殊小市值本身就和高盈利、高投资相关所以共线性比美股更常见。这时output_correlation.csv就派上用场了。如果因子相关性矩阵里的绝对值超过 0.7直接放进同一个回归会导致系数估计失真、标准误膨胀。我的处理偏好按严重程度分三档相关性在 0.5 以下直接忽略0.5 到 0.7 之间观察符号是否稳定超过 0.7就考虑把其中一个因子做正交化处理。正交化的常见做法是以某个因子为因变量、其余因子为自变量做回归取残差作为新因子序列相当于剔除掉被其它因子解释的部分。# 检查因子相关性输出上三角矩阵 corr df[[SMB, HML, RMW, CMA]].corr() print(corr[abs(corr) 0.7].stack()) # 如果 RMW 与 HML 相关性过高对 HML 做正交化 from statsmodels.formula.api import ols hml_resid ols(HML ~ RMW CMA, datadf).fit().resid df[HML_orth] hml_resid正交化后的因子序列不再具备原有的经济含义所以如果做论文或报告我建议在正文里明确说明“HML 已经过 RMW 和 CMA 正交化处理”否则读者会对你回归表里的 HML 系数产生误解。另一方面正交化会导致 t 值发生变化这在审稿人眼里是需要交代的模型设定细节不能藏着。5. 避坑篇因子复现中的五个常见翻车点我把拆解过程中遇到的高频问题集中放在这一章按「现象 → 原因 → 解决」的方式记录。你如果正在复现这套流程大概率会撞上其中至少两三个。5.1 财务时点与市值口径现象一HML 因子方向与理论预期相反。你把回归结果打出来HML 系数为负且显著和 Fama-French 论文里的价值溢价方向完全反了。原因十有八九是前视偏差你用了当期财报的账面价值去算账面市值比而这份财务报表在回测时点根本还没有公布。解决方法是强制设置 6 个月静默期或者至少用publish_date来过滤数据而不是用rpt_date。我见过不少初稿论文里的 HML 符号异常最后查下来都是这一处。现象二SMB 因子在不同年份方向反复横跳。今年 SMB 系数显著为正明年变成显著为负。原因通常是市值选用了总市值A 股大量限售股让总市值严重背离实际流通盘。解决方法是统一换用流通市值。Wind 里有现成的mkt_cap_ard指标复权口径下比总市值稳定得多。顺带说一句ST、退市整理期个股在分组时也会污染市值截面构建因子前要把这些样本剔除干净。5.2 日期对齐与回归可靠性现象三月度面板数据 merge 之后全是 NaN。你信心满满地把行情和财务数据合并结果ret列几乎全空或前几个月全空。原因多半是日期索引不对齐行情数据索引是学期末最后交易日财务数据索引是报告期两者根本不在一个时间轴上。解决方法先统一索引到“自然月最后一个交易日”再对财务数据做ffill收益率不填充直接截掉缺失月份。现象四回归 t 值普遍大于 3显著性爆炸。体感上因子解释力超强但换一段样本区间就崩盘。原因是没有处理残差的自相关和异方差普通 OLS 标准误在金融时间序列里通常低估导致 t 值虚高。解决方法是回归加cov_typeHAC并设置合理maxlags。加了 HAC 之后 t 值普遍会回落 0.5 到 1 个点回到合理范围。现象五output_correlation.csv 里因子相关性过高。直接跑五因子回归SMB 和 CMA 的 VIF 都超过 10。原因是 A 股小市值公司普遍资产增速高规模因子与投资因子天然纠缠。解决方法是做正交化或改用三因子模型。不用慌这是 A 股市场结构的真实特征不是你的代码 bug正交化后在论文里说明清楚即可。6. 验证因子有效性分组收益计算与IC检验模型跑完回归不等于因子有效。回归系数显著只能说明样本内拟合好要做样本外验证才算数。我每次完成因子构建后都要过两道检验第一道是分组组合收益的单调性第二道是信息系数IC的稳定性。这套验证流程可以直接套用在这个资源生成的因子表上。6.1 分组组合年化收益检验把股票按因子暴露从低到高分成五组分别计算每组的月度等权收益再年化。如果因子有效收益应该呈现出近似单调的递增或递减且最高组和最低组的差值多空组合显著为正。这段逻辑可以直接跑在final_all_stocks_cleaned_factors_table.csv上# 按市值因子暴露分五组计算年化收益 df[factor_rank] df.groupby(trade_date)[market_cap].rank(pctTrue) df[group] pd.cut(df[factor_rank], 5, labels[Q1, Q2, Q3, Q4, Q5]) group_ret df.groupby([trade_date, group])[monthly_ret].mean().unstack() # 年化月度收益求积后开 12 次方 annual (1 group_ret.mean())**12 - 1 print(annual)代码里的rank(pctTrue)是把因子值按月份转为 0 到 1 的分位数排名pd.cut再切成五等份。月度收益取均值后年化的逻辑是假设每月收益可以复利滚动——严格来说应该按月连乘但作为快速验证用均值近似足够。多空组合收益率就是 Q5 减 Q1如果这个值在多数年份为正且能覆盖交易成本说明因子有落地价值。6.2 信息系数IC与t统计量IC 是另一个更精细的验证指标每月计算因子暴露与次月收益的秩相关系数得到一条 IC 时间序列然后看 IC 的均值、标准差和 t 值。IC 均值大于 0.03 且 t 值大于 2算初步通过IC 的标准差越小代表因子表现越稳定。# 计算月度 IC 序列并检验显著性 from scipy.stats import spearmanr ic_series [] for month, grp in df.groupby(trade_date): # factor_exposure 是因子值future_ret 是下月收益 ic, _ spearmanr(grp[factor_exposure], grp[future_ret]) ic_series.append(ic) ic_series pd.Series(ic_series) t_stat ic_series.mean() / (ic_series.std() / np.sqrt(len(ic_series))) print(fIC均值: {ic_series.mean():.4f}, IC标准差: {ic_series.std():.4f}, t值: {t_stat:.2f})这里用 Spearman 秩相关而不是 Pearson 线性相关是为了避免极端值对相关性判断的干扰。因子值和次月收益之间往往不是线性关系秩相关更稳健。IC 检验和分组收益是互补的分组收益告诉你因子能不能赚钱IC 告诉你因子预测力是不是稳定。我一般先看 IC再分组验证单调性两条都通过才敢把因子放进实盘模型。这套流程从头到尾走一遍真正花时间的其实不是代码而是数据口径和财务时点的反复检查。那些坑我基本都踩过尤其是财务数据 6 个月静默期那一步当年因为这个误用做出了一个回测年化 30% 的“完美策略”后来一查是前视偏差白高兴一场。从那以后我每次拿 Wind 数据复现因子都会强制先跑一遍文件对齐和财务时点检查再谈回归结果。希望这份拆解能帮你少走几步弯路遇到问题也欢迎对照前面几条排查多半能对上号。本文还有配套的精品资源点击获取