ARTICLE DETAIL

资讯详情

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

Wind Python接口实现Fama-French三因子与五因子模型实战

Wind Python接口实现Fama-French三因子与五因子模型实战 简介一份聚焦Fama-French三因子与五因子模型的Python实现资源面向金融量化学习者、资产定价研究者和策略开发人员解决从Wind金融终端获取因子数据到模型回归分析的全流程问题。压缩包共121个文件、约11.98MB主体为101个Python脚本覆盖数据抓取、清洗、因子构建、回归建模与结果输出等环节同时包含CSV因子数据表、Markdown说明文档、配置文件等便于对照学习和二次开发。已有1382人学习下载。整体来看这套资料既演示了市场、规模、价值、盈利、投资五类因子的计算与合并也提供了面板回归、描述性统计的完整代码结合输出数据可直观理解各因子对股票收益的解释力。适合需要快速上手用Python开展多因子实证分析或希望复现经典资产定价模型的研究者。1. 标题里的 FamaFench 就是 Fama-FrenchWind 数据到因子模型的最小闭环标题里的 FamaFench 是 Fama-French 常见的拼写误写对应的是金融学里那套三因子、五因子定价模型。Wind_Python-master.zip 则是量化团队从 Wind 内部或网盘渠道拿到的 Python 接口包解压后目录名带 -master 后缀真正要做的不是解压后 import 一下就完事。想用 python 算出三因子、五因子得同时打通行情数据、财务数据、月度分组再平衡三个环节任何一个环节的口径出错因子净值曲线都会在回测阶段给你脸色看。这篇文章按本地因子研究的常规路径来讲先让 WindPy 在 python 环境里被 import 进来并稳定取数再把三因子和五因子的分组逻辑写成 pandas 函数最后落库、回归、自检。适合手里有 Wind 终端账号、想用 python 做量化策略复现的投研和开发同学没有终端账号也能看懂计算口径把取数层换掉即可复用。2. Wind 金融数据接口 python 环境从 master.zip 到 w.wsd 最小取数闭环2.1 把 WindPy 装进 pythonzip 包、PYTHONPATH 与 w.start() 错误码Wind 终端自带 Python 接口目录常见位置是C:\Wind\Wind.Python\WindPy从 zip 包解压的话WindPy 目录就在解压后的Wind_Python-master下面。这里不需要重新走一遍 python 安装教程只需要保证 python 是 64 位、版本在终端配套范围内然后把 WindPy 所在目录暴露给解释器。常见做法是写一个sitecustomize.py或者直接在当前脚本里临时把路径塞进 sys.path。import sys sys.path.append(rC:\Wind\Wind.Python) from WindPy import w err w.start() # w.start() 返回 WindErrorCode0 表示连接成功 print(error code:, err) print(connected:, w.isconnected())首次调用w.start()时Wind 终端必须已经在运行并且完成登录。如果返回-40520002常见情况是终端没开、登录态过期或者 python 位数和终端不匹配-40520016一类错误码则多半是接口被杀毒软件拦了。注意w.start()只需要调用一次之后w.isconnected()可以用来做心跳检查断线时返回 False。2.2 wsd 与 wss行情和财务数据的字段、参数与返回结构WindPy 里最常用的两个接口是w.wsd和w.wss。wsd拿时间序列wss拿截面数据前者用于收盘价、收益率后者用于市值、财务指标。两个接口返回的都是WindData对象核心属性是.Codes、.Fields、.Times和.Data其中.Data是嵌套列表外层对应字段内层对应每只股票或每个日期。# wsd拉取单只股票的历史收盘价与涨跌幅 data w.wsd(600519.SH, close,pct_chg, 2024-01-01, 2024-12-31, FillDataPrevious) print(data.ErrorCode) print(data.Fields) print(data.Times[0], data.Data[0][0], data.Data[1][0])上面的代码里FillDataPrevious表示停牌日沿用上一交易日数据避免留下 NaN 空档。PriceAdj参数控制复权方式F是前复权B是后复权计算收益类因子时建议统一用后复权价自行算收益率而不是直接拿pct_chg去拼组合原因后面第 5 章会说。常用 wsd 字段含义常用 wss 字段含义close收盘价mkt_cap总市值元pct_chg涨跌幅%mkt_cap_float流通市值元volume成交量股total_equity所有者权益合计amt成交额元or_ttm营业总收入 TTMtradestatus交易状态tot_assets总资产截面数据和财务数据用 wss 拉取codes [600519.SH, 000001.SZ] data w.wss(codes, mkt_cap_float,total_equity,or_ttm, tradeDate20240628;unit1) import pandas as pd df pd.DataFrame(data.Data, indexdata.Fields, columnsdata.Codes).T df.columns [mkt_cap_float, total_equity, or_ttm] print(df)这里tradeDate是截面取值日期unit1表示市值的单位为元而不是千元或万元。wss返回的.Data外层是字段、内层是股票所以转 DataFrame 时要T转置让行变成股票、列变成字段这个方向反了是新手最容易犯的错。2.3 把数据落进 sqlite先解决日期和主键因子计算不是一次性跑完就结束后面要反复用同一份历史数据先落库比每次现拉靠谱。常见做法是存 SQLite 或本地 MySQL下面给一个最小化的落库脚本import pandas as pd import sqlite3 # 假设 df 已通过 wsd 生成包含 code, trade_date, close, pct_chg conn sqlite3.connect(factor.db) df.to_sql(stock_daily, conn, if_existsappend, indexFalse) conn.close()落库前先建好主键避免重复拉数把表撑出脏数据CREATE TABLE IF NOT EXISTS stock_daily ( code VARCHAR(10), trade_date DATE, close DOUBLE, pct_chg DOUBLE, PRIMARY KEY (code, trade_date) );to_sql不会主动去重直接 append 会导致同一天的数据出现两行回归时因子收益对不上日期。更稳的写法是先把 DataFrame 按主键去重再逐批写入或者用INSERT OR REPLACE的语义把 pandas 的if_exists换成replace只在重建全表时用增量写入时一定要走主键约束。3. 用 Wind 数据复现三因子SMB、HML 的月度分组构建3.1 再平衡日与 look-ahead 限制为什么固定在每年 6 月末Fama-French 三因子的标准做法是每年 6 月末做一次分组持有到下一年 6 月末。这个日历不是随便选的账面市值比用的是上一年年报的账面价值而 A 股年报最晚 4 月 30 日披露完毕6 月末取数时年报已经全部公开不会用到未来信息。如果用当期最新财报去算 BM等于把还没公布的数据提前写进因子回测收益虚高实盘直接失效。分组维度是市值和账面市值比。市值取 6 月末最后一个交易日的流通市值BM 用「所有者权益 / 总市值」。这里要注意单位换算wss 返回的所有者权益单位是元市值也换成元两者相除得到无单位比率。拿到截面数据后按市值中位数分成 S小和 B大两组再按 BM 的 30% 和 70% 分位分成 L低、M中、H高三组两两交叉得到 6 个组合。3.2 python 实现 2x3 分组市值中位数与 BM 的 30/70 分位下面是 2x3 分组的核心代码输入是某一年 6 月末的截面数据输出是 6 个组合的标志列import pandas as pd import numpy as np def assign_ff3_groups(cross_section): cross_section 必须包含列 code, mkt_cap_float, bm, ret_1m ret_1m 是下一个月7 月的月度收益率 df cross_section.copy() # 市值按中位数分大小 mid df[mkt_cap_float].median() df[size_group] np.where(df[mkt_cap_float] mid, B, S) # BM 按 30% / 70% 分位分三组 lo, hi df[bm].quantile([0.3, 0.7]) df[bm_group] pd.cut( df[bm], bins[-np.inf, lo, hi, np.inf], labels[L, M, H] ) # 组合名S/L 表示小市值、低 BM 组 df[portfolio] df[size_group] / df[bm_group] return df # 每个组合按市值加权计算月度收益 group_ret df.groupby(portfolio).apply( lambda x: np.average(x[ret_1m], weightsx[mkt_cap_float]) )分组后三因子的定义是组合收益的差值SMB (S/L S/M S/H)/3 - (B/L B/M B/H)/3 HML (S/H B/H)/2 - (S/L B/L)/2SMB的构造把小市值三个组合的等权平均收益减去大市值三个组合的等权平均收益捕捉的是纯粹的规模溢价HML则在高 BM 两个组合和低 BM 两个组合之间做差捕捉价值溢价。这里用的是等权平均而不是市值加权是 Fama-French 原文的口径和组合构建时的市值加权不是一回事别混。每个月重复一次上面的截面分组把得到的三组数据按月拼起来就得到三个因子收益率序列。注意每组持有期是 12 个月所以同一只股票会在相邻两次分组中重复出现这是再平衡设计的正常现象。3.3 时间序列回归因子载荷、t 值与风险溢价因子序列构建好之后对单只股票或一个组合做时间序列回归检验它在三个因子上的暴露import statsmodels.api as sm # factors 是包含 mkt, smb, hml 三列的 DataFrame # stock_ret 是同一时间段的个股月度收益率 # rf 是无风险利率序列用一年期定存利率或 SHIBOR 1M 折算月利率 excess_ret stock_ret - rf X sm.add_constant(factors[[mkt, smb, hml]]) model sm.OLS(excess_ret, X).fit() print(model.params) print(model.tvalues)回归前用pd.merge(..., ontrade_date, howinner)把因子序列和个股收益对齐howinner保证两边都没有 NaN。mkt因子是市场组合收益率减无风险利率smb和hml直接用上一节构建的因子收益率序列。回归系数smb的 t 值如果显著大于 2说明该股票在小市值风格上有稳定的暴露截距项alpha接近 0 且不显著说明三因子模型对该组合的解释力足够。4. 五因子扩展RMW、CMA 的构建与共线性处理4.1 盈利和投资的口径营业利润/权益与总资产增速五因子模型在三因子基础上加上了盈利因子 RMW 和投资因子 CMA。RMW 的排序变量是营业利润率用营业利润除以所有者权益英文叫 operating profitabilityCMA 的排序变量是总资产增长率用当年总资产相对于上一年总资产的增速取值越小代表投资越保守。两个变量都用年报数据和 BM 一样在每年 6 月末取最新一期已披露年报避免未来函数。A 股计算这两个变量时有个细节营业利润要用合并报表的营业利润不要用净利润因为净利润里包含了非经常性损益噪音更大。总资产增速的分母是上一年年报总资产分子用当年年报总资产增长率算出来是同比增速不是环比。Wind 的 wss 直接拉or_ttm只能拿到营业总收入营业利润对应字段是oper_profit总资产对应tot_assets这些字段在数据接口的财务栏目里都能查到。4.2 2x2x2 分组代码与 SMB 的三种取法五因子的标准分组有两种Fama-French 原文用 2x2x2 分组把市值按中位数分成 S/B把 BM、OP、INV 分别按中位数分成高低两组然后交叉。SMB 不再是一套组合差而是三套 2x2 分组下小市值组合与大市值组合收益差的平均值def assign_ff5_groups(cross_section): df cross_section.copy() mid_size df[mkt_cap_float].median() df[size_group] np.where(df[mkt_cap_float] mid_size, B, S) for col, prefix in [(bm, BM), (op, OP), (inv, INV)]: med df[col].median() df[prefix] np.where(df[col] med, H, L) # 单变量分组下的 SMB smb_bm (df.loc[df[size_group] S, ret_1m].mean() - df.loc[df[size_group] B, ret_1m].mean()) smb_op (df.loc[df[size_group] S, ret_1m].mean() - df.loc[df[size_group] B, ret_1m].mean()) smb_inv (df.loc[df[size_group] S, ret_1m].mean() - df.loc[df[size_group] B, ret_1m].mean()) smb (smb_bm smb_op smb_inv) / 3 return smb上面为了直观简化了组合收益计算实际落地时要用市值加权而不是均值并且要把 BM、OP、INV 三个变量的分组结果保存下来同一只股票在三个维度上的分组成员不同后面算 RMW 和 CMA 还要用到。RMW 的定义是 OP 高组收益减 OP 低组收益CMA 是 INV 低组保守减 INV 高组激进两个因子都要在 S/B 两个市值组内先求差再平均这样才剥离掉规模效应。4.3 回归前先查 VIFHML 在五因子里的位置加进 RMW 和 CMA 之后一个常见问题是四个因子之间的相关性变高。特别是 HML 与 RMW、CMA 在 A 股历史上经常出现明显相关高 BM 的公司往往同时是低投资、稳定盈利的公司。直接做 OLS 回归没问题但因子载荷的 t 值会失真。from statsmodels.stats.outliers_influence import variance_inflation_factor X factors[[smb, hml, rmw, cma]].values vif [variance_inflation_factor(X, i) for i in range(X.shape[1])] print(dict(zip([smb, hml, rmw, cma], vif)))VIF 超过 5 就需要警惕超过 10 说明该因子基本被其他因子线性表示。遇到 HML 的 VIF 偏高常见处理不是删掉 HML而是在报告回归结果时同时给出三因子和五因子的回归让读者自己看增量解释力。alpha从三因子到五因子是否显著变小才是评判五因子是否优于三因子的依据。5. 因子序列自检的三个技巧对齐、复权与断点检查因子算完不等于能用回测前先跑三个自检能省掉后面调 bug 的大量时间。第一个技巧是日期对齐用 inner join别用 outer。因子收益率和个股收益率的频率都是月频但 Wind 返回的日期格式可能一个是月末最后一个交易日、一个是月初第一个交易日直接 concat 会在中间插入 NaN。统一做法是先把两边日期都转成period再mergefactors[month] factors[trade_date].dt.to_period(M) ret[month] ret[ret_date].dt.to_period(M) merged pd.merge(factors, ret, onmonth, howinner)第二个技巧是组合收益用复权价格自己算不要直接引用pct_chg。wsd 的pct_chg是相对前收盘的涨跌幅遇到除权除息日会跳变而因子组合持有一整年中间会有分红送转直接用pct_chg拼出的组合净值会被除权缺口带偏。用 wsd 拉后复权收盘价然后pct_change()算月收益和因子序列在同一口径下对齐。第三个技巧是把数据断点检查写进调度脚本。w.wsd 拉长历史时偶尔会遇到部分交易日数据缺失ErrorCode返回 0 但.Data里存在nan。检查函数不需要复杂def check_factor_series(series): assert series.isna().sum() 0, f存在 NaN: {series.index[series.isna()]} assert len(series) expected_months, 因子月份数和回测区间不一致 # 三因子 A 股历史 SMB 长期均值应该为正 print(SMB 年化均值:, series.mean() * 12)这个检查跑在每个月的数据更新任务后面因子断更、Wind 返回空数据、复权口径被改都能在第一时间炸出来。把这套自检挂进调度脚本每个交易日开盘前重算一次因子序列比模型失效后再排查收益归因要省事得多。本文还有配套的精品资源点击获取
返回列表