ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

量化实战:截面因子有效性检验(IC 分析与分层回测)

量化实战:截面因子有效性检验(IC 分析与分层回测) 本篇是「量化工程实战进阶」系列第 47 讲。上一讲#46我们用多版本快照 差异检测把历史被悄悄改写这个最隐蔽的雷变成了可审计、可回滚的记录。本讲我们往研究链条更上游走一步当你有了干净、可追溯的日线底座怎么科学地判断一个因子到底有没有用这一讲给你一套因子体检工具——IC 分析 分层回测全程用魔码行情 API 零 SDK 纯 HTTP 接入代码已本地跑通。一、痛点开场因子不是永动机很多刚做量化的人有一个幻觉写了一个看起来很有道理的因子回测一跑曲线漂亮就以为挖到了 Alpha。但真实世界里因子是会过时的均线偏离因子在震荡市失效。价格偏离 20 日 MA 的均值回归因子趋势市 IC 能到 0.045显著有效震荡市可能掉到 0.012不显著——同一个因子市场环境一变结论全反。动量因子在急跌期反转。20 日动量在牛市 IC 为正但在市场急跌时往往逆转为负前期强势股恰好是机构赎回时先被砸的流动性好的标的。因子之间互相污染。一个因子有效可能只是因为它和行业、市值暴露高度相关不中性化的检验是在自欺欺人。社区里一个被反复印证的结论“因子不是永动机——均线因子在震荡市失效动量因子在熊市反转。如果不做定期体检策略可能在不知不觉中退化成随机游走。”换句话说因子验证不是写完回测就完事而是一套必须反复使用的统计工具。本篇教你用两个最核心、也最被业界公认的工具——IC 分析和分层回测——给因子做体检并给出可落地的工程实现。二、本文你将得到什么一个干净的因子接入层用魔码日线 API零 SDK 纯 HTTP把行情拉成本地可计算的 Python 对象。一个IC 分析工具箱逐期截面 IC、IC 均值 / ICIR / IC0 比例、滚动 IC判断因子的预测力与稳定性。一个分层回测框架按因子值等分 N 组检验高分组收益 低分组的单调性并计算多空组合收益。一份完整可运行代码已验证覆盖接入→因子构造→IC→分层。五个高频坑不中性化、只看 IC 不看单调性、样本太少、把噪声当信号、复权口径不一致导致因子值失真。三、第一步用魔码日线接入构造因子因子检验的第一步永远是把数据弄干净。这里我们用魔码行情 API 的零 SDK 纯 HTTP 接入方式拉取日线——不需要任何客户端 SDK一个requests.get就够了importrequests BASEhttps://api.momaapi.comLICENCETEST-API-TOKEN-MOMA-836089C22111# 演示证书请换成你自己的正式证书deffetch_moma_daily(code,st,et):urlf{BASE}/hsstock/history/{code}/d/n/{LICENCE}?st{st}et{et}rowsrequests.get(url,timeout20).json()ifnotisinstance(rows,list):raiseRuntimeError(f接口返回异常{rows})returnrows魔码日线返回的是结构稳定的字典列表字段含义固定t日期、o开、h高、l低、c收、v成交量、a成交额、pc昨收、sf复权/状态标记。正是这种字段稳定、复权口径一致的特性让你的因子计算可以跨时间复现——不会出现上游改了字段名因子代码就崩的尴尬。注演示证书返回的是固定样本所有标的返回同一段示例数据因此无法在此做真实的全市场截面检验。下方演示用的截面 panel 为固定随机种子生成的演示数据仅用于演示算法逻辑真实证书可直接拉取全市场截面把同样的代码跑在真实数据上即可。四、第二步IC 分析——因子与未来收益的秩相关ICInformation Coefficient信息系数衡量的是当期因子值与下期收益率之间的秩相关系数。业界默认用 Spearman 秩相关因为它对异常值鲁棒、不假设线性关系importnumpyasnpdefrankdata(x):平均秩与 scipy.stats.rankdata 等价避免引入 scipy 依赖。ordernp.argsort(x,kindmergesort)ranksnp.empty(len(x),dtypefloat)sorted_xx[order]i,n0,len(x)whilein:jiwhilej1nandsorted_x[j1]sorted_x[i]:j1avg(ij)/2.01.0forkinrange(i,j1):ranks[order[k]]avg ij1returnranksdefspearman_ic(factor,fwd_ret):mask~np.isnan(factor)~np.isnan(fwd_ret)f,rfactor[mask],fwd_ret[mask]iflen(f)30:returnnp.nan rf,rrrankdata(f),rankdata(r)rf-rf.mean();rr-rr.mean()denomnp.sqrt((rf**2).sum()*(rr**2).sum())returnfloat((rf*rr).sum()/denom)ifdenom0elsenp.nan只算一个 IC 远远不够真正要看的是时间序列上的稳定性。我们通常用三个指标综合判断一个因子指标含义经验阈值IC 均值各期 IC 的平均反映平均预测力 0.02 有一定预测力 0.05 显著ICIRIC 均值 / IC 标准差反映方向稳定性 0.5 为较稳因子IC0 比例IC 为正的期数占比越高越稳定以及一个被低估的工具——滚动 IC把时间轴滑窗看 IC 何时开始衰减、失效是否可逆。一个因子的 IC 在某段时期系统性掉到 0 以下往往对应着市场状态切换如趋势转震荡、牛转熊这时就该考虑降权或关停该因子而不是硬扛。五、第三步分层回测——因子的收益区分度IC 是单变量统计分层回测则模拟真实组合的构建过程把股票按因子值从大到小等分为 N 组通常 5 组分别计算各组未来收益。一个好因子应当表现出单调性——第 1 组因子最小收益显著低于第 5 组因子最大中间各组依次递增如果五条曲线交叉纠缠说明因子没有区分度。defstratified_backtest(panel_factor,panel_ret,n_groups5):all_g,all_ret[],[]fortinrange(len(panel_factor)):f,rpanel_factor[t],panel_ret[t]m~np.isnan(f)~np.isnan(r)all_g.append(f[m]);all_ret.append(r[m])Gnp.concatenate(all_g);Rnp.concatenate(all_ret)edgesnp.quantile(G,np.linspace(0,1,n_groups1))edges[0]-1e-9;edges[-1]1e-9grpnp.clip(np.digitize(G,edges)-1,0,n_groups-1)rows[]forginrange(n_groups):gretR[grpg]mean_retgret.mean()std_retgret.std(ddof1)iflen(gret)1else0.0sharpe(mean_ret/std_ret)*np.sqrt(252)ifstd_ret0else0.0win(gret0).mean()rows.append({group:fG{g1},n:len(gret),mean_ret:mean_ret,sharpe:sharpe,win:win})long_shortrows[-1][mean_ret]-rows[0][mean_ret]returnrows,long_short分层回测的几点工程纪律等权分组避免市值加权掩盖因子效应、只做多检验单边有效性研究阶段不引入空头复杂性、控制行业暴露因子收益可能来自行业偏差应做行业中性化后再检验、计入交易成本通常假设单边 0.05%。六、完整可运行代码已本地跑通下面把上面的组件拼成一份完整脚本Python 3.9 numpy requests 验证通过。为演示算法截面 panel 用固定种子生成的演示数据明确标注非伪造接口数据真实证书可直接替换# -*- coding: utf-8 -*-importrequestsimportnumpyasnpimportdatetime BASEhttps://api.momaapi.comLICENCETEST-API-TOKEN-MOMA-836089C22111# 演示证书请换成你自己的正式证书deffetch_moma_daily(code,st,et):urlf{BASE}/hsstock/history/{code}/d/n/{LICENCE}?st{st}et{et}rowsrequests.get(url,timeout20).json()ifnotisinstance(rows,list):raiseRuntimeError(f接口返回异常{rows})returnrowsdefrankdata(x):ordernp.argsort(x,kindmergesort)ranksnp.empty(len(x),dtypefloat)sxx[order];i,n0,len(x)whilein:jiwhilej1nandsx[j1]sx[i]:j1avg(ij)/2.01.0forkinrange(i,j1):ranks[order[k]]avg ij1returnranksdefspearman_ic(factor,fwd_ret):mask~np.isnan(factor)~np.isnan(fwd_ret)f,rfactor[mask],fwd_ret[mask]iflen(f)30:returnnp.nan rf,rrrankdata(f),rankdata(r)rf-rf.mean();rr-rr.mean()denomnp.sqrt((rf**2).sum()*(rr**2).sum())returnfloat((rf*rr).sum()/denom)ifdenom0elsenp.nandefstratified_backtest(panel_factor,panel_ret,n_groups5):all_g,all_ret[],[]fortinrange(len(panel_factor)):f,rpanel_factor[t],panel_ret[t]m~np.isnan(f)~np.isnan(r)all_g.append(f[m]);all_ret.append(r[m])Gnp.concatenate(all_g);Rnp.concatenate(all_ret)edgesnp.quantile(G,np.linspace(0,1,n_groups1))edges[0]-1e-9;edges[-1]1e-9grpnp.clip(np.digitize(G,edges)-1,0,n_groups-1)rows[]forginrange(n_groups):gretR[grpg]mean_retgret.mean()std_retgret.std(ddof1)iflen(gret)1else0.0sharpe(mean_ret/std_ret)*np.sqrt(252)ifstd_ret0else0.0rows.append({group:fG{g1},n:len(gret),mean_ret:mean_ret,sharpe:sharpe,win:(gret0).mean()})returnrows,rows[-1][mean_ret]-rows[0][mean_ret]defbuild_demo_panel(n_stocks500,n_dates120,seed20260912):rngnp.random.default_rng(seed)factorrng.standard_normal((n_dates,n_stocks))factorfactor*0.7np.roll(factor,1,axis0)*0.3factor[0]rng.standard_normal(n_stocks)fwd_ret0.045*factorrng.standard_normal((n_dates,n_stocks))returnfactor,fwd_retdefmain():realfetch_moma_daily(600519,20250101,20250901)print(真实接口返回 rows,len(real),演示证书固定样本)factor,fwd_retbuild_demo_panel()icsnp.array([spearman_ic(factor[t],fwd_ret[t])fortinrange(len(factor)-1)])icsics[~np.isnan(ics)]print(fIC均值{ics.mean():.4f}ICIR{ics.mean()/ics.std(ddof1):.4f}IC0比例{(ics0).mean():.2%})rows,lsstratified_backtest(factor,fwd_ret,n_groups5)forrinrows:print(r[group],n,r[n],mean_ret%.4f%r[mean_ret],sharpe%.2f%r[sharpe])print(多空(G5-G1)%.4f%ls)if__name____main__:main()真实运行输出节选演示证书接入层已验证、panel 为固定种子演示数据真实接口返回 rows 50 演示证书固定样本 IC均值0.0301 ICIR0.6102 IC0比例68.07% 分组 样本数 平均收益 夏普 胜率 G1 12000 -0.0436 -0.69 48.18% G2 12000 -0.0210 -0.33 49.62% G3 12000 -0.0052 -0.08 49.49% G4 12000 0.0131 0.21 50.66% G5 12000 0.0449 0.71 51.89% 多空组合(G5-G1) 平均收益差0.0885可以看到IC 均值为 0.0301落在有一定预测力区间 0.02~0.05ICIR 0.61大于 0.5方向稳定IC0 比例 68%分层回测呈现清晰单调——从 G1 的 -0.0436 一路递增到 G5 的 0.0449多空组合收益差 0.0885。这组结果正是一个中等有效、稳定、单调因子的标准画像。文中数据为演示用合成截面仅供演示算法逻辑非实时行情生产环境请使用你自己的魔码正式证书并以官方文档与实时返回为准。七、五个高频坑不中性化就下结论。因子收益可能完全来自行业或市值暴露。检验前务必做行业/市值中性化用回归残差或分组去均值否则你以为挖到了因子其实只是买了一篮子大市值股。只看 IC 不看单调性。IC 显著不代表因子可用——分层回测能暴露 IC 是否由少数极端值贡献。五组曲线纠缠的因子再高的 IC 也不可信。样本太少。单期截面少于 30 只股票时 IC 统计不可靠分层回测每组少于 10 个样本也会失真。A 股全市场 5000 标的足够支撑稳健检验。把噪声当信号。IC 在 0.02 附近的因子十有八九是统计噪声。务必看 ICIR 和滚动 IC——一个只在某个特殊窗口显著的因子大概率是过拟合产物下一讲 #48 专讲这个。复权口径不一致导致因子值失真。这是最容易被忽视的数据坑不同数据源前/后复权算法不同算出来的收益率序列能差出 1%长期复利下足以改写策略夏普。魔码行情接口复权口径统一、字段稳定正是为了让因子计算可复现。八、小结与下篇预告因子验证不是回测出一条漂亮曲线而是一套反复使用的统计体检IC 分析回答因子平均有没有预测力、稳不稳定分层回测回答因子能不能真的区分赚钱的股票。两者结合你才能在把真金白银压上去之前先看清这个因子到底是信号还是噪声。下一篇#48我们顺着这个话题往下挖最深的一层回测过拟合检测Walk-Forward 参数稳定性——当你觉得一个策略回测完美时怎么用滚动窗口和样本外检验判断它到底抓住了真边缘还是只是记住了历史噪声。文中接口调用使用演示证书返回数据为样本示例生产环境请使用你自己的魔码正式证书并以官方文档与实时返回为准。魔码官方技术博客https://www.momaapi.com/blog/
返回列表