从零到一跑通多因子选股:Alpha101与Alpha191量化因子库实战教程(附IC检验与分层回测代码)
从零到一跑通多因子选股:Alpha101与Alpha191量化因子库实战教程(附IC检验与分层回测代码)
【免费下载链接】jqdatasdk简单易用的量化金融数据包(easy utility for getting financial market data of China)项目地址: https://gitcode.com/gh_mirrors/jq/jqdatasdk
很多入门量化的朋友第一次接触 Alpha因子,都是先从网上抄公式开始的:把 WorldQuant 的 101 个表达式搬进本地脚本,再用 pandas 一行行实现rank、correlation、ts_rank。我也经历过这个阶段,直到某天发现自己的 alpha_004 计算结果和行情软件的指标对不上——原因很琐碎:除零没处理、复权口径不一致、停牌日没剔除。就是这些不起眼的细节,让"照着公式实现"变成了一场漫长的排错。后来我切换到 jqdatasdk 这套量化金融数据包,直接调用它内置的 Alpha101 与 Alpha191 量化因子库,才发现把公式交给经过验证的服务端去算,省下的不只是时间,还有一整套容易踩坑的数据处理环节。本文就记录我从零搭建因子研究流程的完整路径,包含可直接运行的代码。
一次因子失效的复盘:为什么要用现成的量化因子库
先讲一个真实教训。2022 年三季度,我手工实现的一个反转因子突然失效,IC 从 -0.05 掉到接近 0。排查了整整两天,最后定位到三个问题:
- 某只股票某天成交量是 0,
delta(volume, 1)里出现了 NaN,而我用的fillna(0)把本该是缺失的样本硬生生变成了"涨跌幅为 0"; - 送转除权后,不复权价格出现跳空,反转因子被"假信号"污染;
- 次新股上市前 20 天没有历史数据,窗口函数算出来的排名严重失真。
这三类问题,本质是"因子计算"和"数据清洗"被混在了一起。而 jqdatasdk 的 alpha101、alpha191 两个模块,把公式封装成一行函数调用,数据口径、缺失值、复权逻辑都在服务端统一处理,本地拿到的就是干净的因子值序列。这对把精力放在"策略本身"而非"公式翻译"的研究者来说,是实打实的效率提升。
开工前准备:安装、认证与两张"因子地图"
环境准备三步走
先装包、认证、验证连通性。pip install jqdatasdk之后,用官方申请的账号登录即可:
# 这段代码解决"如何初始化 jqdatasdk 环境"的问题 import jqdatasdk # 用账号密码或 token 完成认证 jqdatasdk.auth('你的用户名', '你的密码') # 或者:jqdatasdk.auth_by_token('你的token') # 确认认证是否成功 print(jqdatasdk.is_auth()) # 输出 True 说明已就绪 # 可选:通过 git 方式获取源码(源码仓库地址见项目主页) # git clone https://gitcode.com/gh_mirrors/jq/jqdatasdk认证失败最常见的报错是Please run jqdatasdk.auth first,这类错误都源自utils.py里的assert_auth装饰器,它会拦截一切未认证的函数调用。
两个因子库的分工差异
动手前先搞清楚 alpha101 和 alpha191 的定位区别,否则很容易传错参数:
| 维度 | alpha101 | alpha191 |
|---|---|---|
| 计算维度 | 全市场截面,一次算一个交易日 | 自选股票池,按code传入 |
| 函数签名 | alpha_xxx(enddate, index='all') | alpha_xxx(code, end_date=None, fq='pre') |
| 返回类型 | Series,index 为股票代码 | 按传入股票池返回因子值 |
| 适用场景 | 全市场打分、截面排序 | 盯住自选池做深度研究 |
一个典型的组合用法是:用 alpha101 在全市场做初筛,再用 alpha191 对进入视野的几十只标的做精细对比。
第一步:把候选因子做成一张"截面快照"
因子研究的第一步,是把"因子表达式"变成"每个交易日、每只股票都有一个数值"的表格。这段代码展示如何用 alpha101 批量取全市场因子值:
# 这段代码解决"如何一次性拿到全市场某天的多个Alpha因子值"的问题 import pandas as pd from jqdatasdk import alpha101 # 取 2023-06-30 收盘后的三个因子截面 # alpha_101: (close - open) / (high - low + 0.001),捕捉日内价格位置 # alpha_012: sign(delta(volume,1)) * (-1 * delta(close,1)),量价背离信号 # alpha_004: -1 * Ts_Rank(rank(low), 9),短期低点反转信号 factor_date = '2023-06-30' snapshot = pd.DataFrame({ 'alpha_101': alpha101.alpha_101(factor_date), 'alpha_012': alpha101.alpha_012(factor_date), 'alpha_004': alpha101.alpha_004(factor_date), }) # 剔除缺失值,避免脏数据污染后续分析 snapshot = snapshot.dropna() print(snapshot.head()) print('有效样本数:', len(snapshot))如果你关心的是自己跟踪的 30 只股票,而不是全市场,就换 alpha191:
# 这段代码解决"如何对自选股票池计算Alpha191因子"的问题 from jqdatasdk import alpha191 watch_list = ['000001.XSHE', '600519.XSHG', '000858.XSHE', '601318.XSHG', '600036.XSHG'] # fq='pre' 表示前复权,避免除权跳空干扰因子值 factor_191 = alpha191.alpha_001(code=watch_list, end_date='2023-06-30', fq='pre') print(factor_191)值得注意的一点:alpha191 的因子表达式面向"单只股票的时间序列",例如alpha_001是成交量变化与开盘涨幅的 6 日相关性;而 alpha101 里同名的alpha_001是截面排名类公式。同名不同义,引用前务必看清模块前缀。
第二步:用 IC 检验给因子做"体检"
拿到因子值只是开始,接下来要回答"这个因子到底有没有预测力"。IC(信息系数)就是因子值与未来收益的秩相关系数,可以把它理解成体检报告上的一个核心指标:绝对值接近 1 说明因子和未来收益高度相关,接近 0 则说明因子基本是噪声。
# 这段代码解决"如何量化评估因子预测能力(IC检验)"的问题 import numpy as np import pandas as pd from scipy.stats import spearmanr from jqdatasdk import alpha101, get_price def calc_ic(factor_series, enddate, horizon=20): """计算某一天因子值与未来horizon日收益的秩相关(IC)""" codes = list(factor_series.index) # 取未来收益:用 horizon 个交易日后价格计算涨幅 fwd = get_price(codes, start_date=enddate, end_date=None, count=horizon + 1, fields=['close'], frequency='daily') close = fwd['close'].unstack() # 未来收益 = 最后一日收盘 / 因子日收盘 - 1 ret = close.iloc[-1] / close.iloc[0] - 1 ret = ret.reindex(factor_series.index) valid = factor_series.notna() & ret.notna() if valid.sum() < 30: return np.nan ic, _ = spearmanr(factor_series[valid], ret[valid]) return ic # 连续滚动 20 个交易日,看因子IC的稳定性 dates = pd.date_range('2023-06-01', periods=20, freq='B').strftime('%Y-%m-%d') ic_series = pd.Series({d: calc_ic(alpha101.alpha_101(d), d) for d in dates}) print('滚动IC序列:') print(ic_series.round(3)) print('IC均值: %.3f, IC标准差: %.3f' % (ic_series.mean(), ic_series.std())) print('IC>0占比: %.0f%%' % ((ic_series > 0).mean() * 100))经验判断标准(仅供参考,不构成投资建议):
- IC 绝对值均值在 0.03 以上,且正负方向稳定,才值得继续往下做;
- IC 的标准差过大,说明因子在不同市场环境里忽好忽坏;
- 建议至少用 20 个交易日以上的 IC 均值做判断,单日 IC 噪声太大。
第三步:分层回测验证因子的"区分度"
IC 高不等于能赚钱,还需要看因子的单调性。分层回测的做法很直观:按因子值把股票分成 5 组,算每组未来一段时间的平均收益。如果第 1 组(因子值最小)到第 5 组(因子值最大)的收益单调递增或递减,说明因子确实能把股票区分开。
# 这段代码解决"如何用分层回测验证因子区分度"的问题 def layered_backtest(factor_series, enddate, groups=5, horizon=20): """按因子值分5组,比较各组未来20日平均收益""" codes = list(factor_series.index) fwd = get_price(codes, start_date=enddate, end_date=None, count=horizon + 1, fields=['close'], frequency='daily') close = fwd['close'].unstack() ret = (close.iloc[-1] / close.iloc[0] - 1).reindex(factor_series.index) df = pd.DataFrame({'factor': factor_series, 'fwd_ret': ret}).dropna() # 按因子值分位数打组标签,1=最小,5=最大 df['group'] = pd.qcut(df['factor'], groups, labels=False) + 1 return df.groupby('group')['fwd_ret'].mean() # 用第一步的截面快照做演示 from jqdatasdk import alpha101 snap = alpha101.alpha_004('2023-06-30') # 反转因子 result = layered_backtest(snap, '2023-06-30') print(result.round(4))分层结果解读:
- 若各组收益呈单调阶梯状(如 -1.2%、-0.5%、0.1%、0.8%、1.5%),因子区分度良好;
- 若中间组收益高于两端(U 形或倒 U 形),说明因子只有排序能力而没有线性区分能力,需要警惕;
- 分层回测样本太少时结论不可靠,最好跨多个时间段重复验证,避免过拟合。
组合与风控:别把仓位押在单一因子上
单一因子再优秀,也有阶段性失效的风险。更稳的做法是把多个低相关的因子加权合成。关键在于相关性监控——两个因子如果相关系数常年高于 0.7,它们本质上是同一个信号的两种写法,组合起来并不能分散风险。
# 这段代码解决"如何构建多因子组合并监控因子间相关性"的问题 from jqdatasdk import alpha101 date = '2023-06-30' # 挑选逻辑上互补的三类因子:动量、反转、量价 factor_dict = { 'momentum': alpha101.alpha_001(date), # 趋势强度 'reversal': alpha101.alpha_004(date), # 短期反转 'volume_price': alpha101.alpha_012(date) # 量价背离 } mat = pd.DataFrame(factor_dict) # 因子之间相关性过高时,考虑剔除冗余因子 print('因子相关系数矩阵:') print(mat.corr().round(3)) # 用 IC 均值作为权重的简易合成(实际可按滚动IC动态调整) weights = pd.Series({'momentum': 0.4, 'reversal': 0.35, 'volume_price': 0.25}) composite = sum(mat[c] * w for c, w in weights.items()) print('合成因子前10名:') print(composite.sort_values(ascending=False).head(10))风控层面的三个习惯:
- 暴露度控制:合成因子里单一风格(如纯小市值)的权重设上限;
- 相关性监控:每月重算一次因子间相关矩阵,发现相关性陡增就复查数据;
- 动态调权:用滚动 IC 序列给因子赋权,让表现好的因子阶段性获得更高权重,但要控制调仓频率,避免过度交易。
性能与避坑:缓存机制和四个常见错误
理解源码里的 LRU 缓存
翻看jqdatasdk/alpha101.py源码会发现,每个因子函数都叠了两个装饰器:
@assert_auth # 校验是否已认证 @hashable_lru(maxsize=3) # 最近使用的3个结果缓存 def alpha_001(enddate, index='all'): ...hashable_lru来自utils.py,它做了两件额外的事:一是把 list/dict 这类不可哈希的参数序列化后再进缓存,二是返回时copy.deepcopy一份,防止调用方意外修改缓存对象。实际使用中这意味着:在同一个进程中重复计算相同日期、相同股票池的因子,不会重复请求服务端。批量研究时尽量复用同一进程、同一批日期,能明显减少等待。
四个高频踩坑点
- 忘记认证:所有因子函数都被
assert_auth包裹,必须先auth或auth_by_token; - 日期格式混乱:源码里
to_date_str会把字符串、datetime、整数统一转成'YYYY-MM-DD'格式,所以传入'20230630'或datetime.date都能工作,但传 None 会有歧义,建议显式给日期; - alpha101 的 index 参数:默认
index='all'是全市场,想限定某个指数成分股时传入对应指数代码,注意返回的 Series 是当日截面,不是时间序列; - alpha191 忘传 fq:复权口径默认
fq='pre',如果研究里混用了不同复权方式的数据,因子值之间不可比。
进阶方向:把因子流程接到机器学习
因子研究走到后期,自然想引入机器学习。jqdatasdk 的因子结果直接就是 pandas Series/DataFrame,与 sklearn 的特征矩阵天然兼容。标准流程是:
- 用 alpha101/alpha191 滚动生成多期因子截面,堆叠成"样本 × 特征"矩阵;
- 用未来 N 日收益构造标签,按时间顺序划分训练集/验证集(务必避免随机切分导致的未来函数泄漏);
- 用标准化 + 随机森林或 LightGBM 做非线性因子组合;
- 用分层回测(第三步的方法)检验模型输出的打分是否仍有单调性。
这里要提醒:机器学习模型在因子研究里是把"低相关的因子组合"这件事自动化,它不会凭空创造信息。如果输入因子本身没有预测力,再复杂的模型也只是在拟合噪声。
写在最后
回看这次流程重构,我最大的体会是:因子的价值在于解释和验证,而不在于"实现"。把公式翻译、数据清洗、复权处理这些重复劳动交给 jqdatasdk 的量化因子库,把精力留给 IC 检验、分层回测和组合风控这些真正决定策略质量的事情上,才是这套工具最正确的打开方式。
如果你的研究也卡在"公式实现没问题但结果总对不上"的怪圈里,不妨按本文的路径重走一遍:先取截面,再做 IC 体检,然后分层回测,最后合成与风控。四个环节走完,你对每个因子的性格会有一个清晰的认识,那时候再谈策略优化,心里就有底了。
【免费下载链接】jqdatasdk简单易用的量化金融数据包(easy utility for getting financial market data of China)项目地址: https://gitcode.com/gh_mirrors/jq/jqdatasdk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考