news 2026/9/24 19:09:37

遗传规划自动挖掘阿尔法因子:多因子策略代码包实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
遗传规划自动挖掘阿尔法因子:多因子策略代码包实战

简介:这份资源围绕遗传规划算法在多因子投资策略中生成阿尔法因子展开,面向具备一定Python基础、希望自主挖掘有效因子的量化投资者与研究者。它借助符号回归技术,先构建一组简单随机公式来刻画自变量与证券收益之间的关系,从而预测新数据,帮助使用者摆脱传统因子逐渐失效的困境。压缩包共8个文件,以7个py脚本和1个md说明文档为主,整体约28KB,代码按遗传算法主流程、适应度评估、函数集与工具模块拆分,结构清晰,便于按模块阅读与二次开发。该包特别支持时间序列数据,要求可变调整价格以二维数据框组织,第一维为股票代码、第二维为时间,这与原始遗传规划算法有明显差异,demo.py则提供了测试运行入口。目前已有699人学习下载,适合想快速上手遗传阿尔法、理解因子生成与回测思路的读者参考。

1. 遗传规划生成阿尔法因子:一份能跑通的多因子策略代码包

做多因子策略的人大多经历过这个阶段:手工构造的动量、估值、波动率因子,回测出来 IC 一年比一年低,因为大家都在用同一批因子,拥挤度上来了自然失效。想找新因子,靠人工试错效率太低。这份 Genetic-Alpha 代码包解决的正是这个问题——它用遗传规划(Genetic Programming)做符号回归,自动搜索自变量与证券收益之间的非线性公式,把「找因子」这件事从手工活变成可搜索的优化问题。

包体量不大,核心是genetic.py_program.pyfunctions.pyfitness.pyutils.py几个模块,外加demo.py作为入口。它和经典遗传规划最大的区别是支持时间序列数据:输入是一个二维 DataFrame,第一维是股票代码,第二维是时间。这意味着你可以直接喂面板数据进去,让算法在时序维度上评估公式的稳定性,而不是只做截面拟合。适合已经写过因子回测、想引入自动化因子挖掘的量化从业者,也适合想拿一个完整遗传规划实现来改的 Python 开发者。

2. 符号回归怎么变成因子挖掘:从树结构到适应度函数

2.1 遗传规划的个体表示与算子设计

遗传规划的核心是把一个数学公式编码成一棵树。叶子节点是变量(比如收盘价、成交量、换手率)或常数,内部节点是算子(加减乘除、对数、排名、滞后等)。这份代码里,_program.py负责定义这棵树的结构,functions.py提供可用的算子集合。

为什么用树而不是固定形式的线性组合?因为线性组合只能表达「因子 A 乘以权重 w」这种关系,而真实市场里因子与收益的关系往往是非线性的,比如「换手率的倒数乘以波动率的排名」这种复合结构。树结构天然支持任意嵌套,搜索空间大得多。

常见做法是给算子加约束,避免生成无意义的公式。比如除法算子要防止分母为零,对数算子要保证输入为正。这份代码在functions.py里对每个算子做了保护,返回 NaN 或裁剪到安全区间,避免整棵树因为一个节点出错而报废。

# functions.py 中算子定义的典型结构(示意) import numpy as np def protected_div(x1, x2): # 分母加一个极小值,避免除零;同时限制结果幅度 with np.errstate(divide='ignore', invalid='ignore'): result = np.where(np.abs(x2) > 1e-6, x1 / x2, 1.0) return np.clip(result, -1e6, 1e6) def ts_rank(x, window=20): # 时间序列排名:过去 window 期内的分位数 from scipy.stats import rankdata return x.rolling(window).apply(lambda s: rankdata(s)[-1] / len(s), raw=True)

上面两个算子代表了因子挖掘里最常用的两类操作:逐元素的算术运算和时序窗口运算。protected_div里的1e-6阈值和np.clip的上下限是可以调的——如果你处理的因子量纲很大(比如成交额),上限要相应放大,否则大量个体被裁剪到边界,搜索会失去梯度信息。ts_rankwindow参数默认 20,对应一个月的交易日,改成 60 就是季度排名,这个参数直接决定因子的换手率和衰减速度。

2.2 适应度函数:IC、RankIC 还是多空收益

fitness.py是整个算法里最需要你动手改的地方。遗传规划靠适应度函数来评价每个公式的好坏,适应度设计错了,搜出来的因子就是一堆过拟合的垃圾。

这份代码默认的适应度大概率是基于 IC(信息系数)或 RankIC 的。IC 是因子值与下期收益的截面相关系数,RankIC 是秩相关系数。两者的区别在于:IC 对极端值敏感,RankIC 更稳健。如果你处理的股票池里有大量小市值标的,收益分布厚尾严重,建议用 RankIC。

# fitness.py 中适应度计算的典型逻辑(示意) def calc_fitness(factor_values, forward_returns): # factor_values: 二维数组,行是股票,列是时间 # forward_returns: 同形状,下期收益 ic_list = [] for t in range(factor_values.shape[1] - 1): f = factor_values[:, t] r = forward_returns[:, t] mask = ~(np.isnan(f) | np.isnan(r)) if mask.sum() < 30: # 截面样本太少,跳过 continue ic = np.corrcoef(f[mask], r[mask])[0, 1] ic_list.append(ic) # 用 IC 的均值和标准差构造适应度:均值高且稳定才好 ic_mean = np.nanmean(ic_list) ic_std = np.nanstd(ic_list) return ic_mean / (ic_std + 1e-8) # 类似 ICIR

这段代码的关键参数是mask.sum() < 30这个阈值。截面股票数少于 30 时算出来的相关系数噪声极大,不如直接跳过。另一个重点是最后返回的ic_mean / ic_std,这是在模仿 ICIR(IC 信息比率)的思路——不光要因子有效,还要效果稳定。如果你只想最大化单期 IC,把返回值改成ic_mean就行,但那样搜出来的因子往往在样本外崩得很快。

2.3 时间序列数据的对齐与滚动窗口处理

这份包和原始遗传规划最大的不同就是支持时间序列。utils.py里应该包含了数据对齐和滚动窗口的工具函数。面板数据做因子挖掘,最容易翻车的地方就是时间对齐:因子计算用到的数据必须是 t 时刻及之前能拿到的,收益必须是 t+1 时刻的,中间不能有未来函数。

# utils.py 中数据准备的典型流程(示意) import pandas as pd def prepare_panel_data(price_df, volume_df, forward_period=1): # price_df: index 是日期,columns 是股票代码 # 构造因子候选变量 ret = price_df.pct_change() vol_rank = volume_df.rolling(20).apply(lambda x: x.rank().iloc[-1]) # 构造下期收益作为标签 forward_ret = price_df.pct_change(forward_period).shift(-forward_period) # 对齐:只保留因子和收益都存在的样本 factor_stack = ret.stack() forward_stack = forward_ret.stack() aligned = pd.concat([factor_stack, forward_stack], axis=1).dropna() return aligned

forward_period=1表示预测下一期收益,改成 5 就是预测未来一周。这个参数要和你的调仓频率匹配——如果你每周调仓,用 1 日收益做标签就是错的。rolling(20)的窗口同理,要和因子的预期持有期一致。shift(-forward_period)是把未来收益挪到当前行,这是构造标签的标准操作,但一定要确认挪完之后没有引入未来数据。

3. 从零跑通 demo:环境、数据格式与第一次搜索

3.1 环境准备与依赖安装

这份代码是纯 Python 实现,没有重型依赖。我一般会建一个干净的虚拟环境,避免和已有的量化库版本冲突。

# 创建虚拟环境(Linux/macOS) python -m venv venv_alpha source venv_alpha/bin/activate # Windows 下激活 # venv_alpha\Scripts\activate # 安装核心依赖 pip install numpy pandas scipy

如果你用的是国内网络,pip 安装慢的话可以临时指定镜像源,这个不多说,常规操作。装完之后进到代码目录,先别急着跑demo.py,先确认 Python 版本。遗传规划里用了不少递归和嵌套结构,Python 3.7 以上比较稳,3.9 或 3.10 更推荐。

3.2 数据格式:二维 DataFrame 的构造与检查

demo.py里应该自带了一份示例数据或者数据生成逻辑。但你要用自己的数据跑,就得先搞清楚输入格式。按照项目说明,数据是二维 DataFrame,第一维是股票代码,第二维是时间。注意这里的「第一维」指的是行索引还是列索引,不同实现习惯不一样,跑之前先打印一下 shape 确认。

import pandas as pd import numpy as np # 假设你有一份价格数据:index 是日期,columns 是股票代码 # 先转成算法要求的格式:index 是股票代码,columns 是日期 price_panel = price_df.T # 转置 # 检查数据完整性 print("股票数:", price_panel.shape[0]) print("时间长度:", price_panel.shape[1]) print("缺失值比例:", price_panel.isna().mean().mean()) # 如果缺失值太多,先做填充或截断 # 常见做法是前向填充,但要注意停牌期间的数据不能简单填充 price_panel = price_panel.fillna(method='ffill', limit=5)

limit=5表示最多前向填充 5 期,超过就保留 NaN。这是为了防止停牌很久的股票被错误地填充出连续价格。缺失值比例超过 30% 的股票建议直接剔除,否则遗传规划在评估适应度时会被大量 NaN 干扰,搜出来的公式可能只对少数样本有效。

3.3 运行 demo.py 并解读输出

数据准备好之后,跑demo.py。第一次跑建议把种群规模和迭代代数调小,先确认流程能通。

# demo.py 中关键参数(示意,具体名称以实际代码为准) from genetic import GeneticAlpha ga = GeneticAlpha( population_size=100, # 种群规模 generations=10, # 迭代代数 tournament_size=5, # 锦标赛选择规模 crossover_rate=0.7, # 交叉概率 mutation_rate=0.2, # 变异概率 max_depth=4, # 树最大深度 random_state=42 ) best_program = ga.fit(factor_data, forward_returns) print(best_program.expression) # 打印最优公式 print(best_program.fitness) # 打印适应度值

population_size=100generations=10是调试用的最小配置,跑通之后可以加到 500 和 50。max_depth=4限制树的深度,防止公式过于复杂导致过拟合。random_state=42固定随机种子,保证结果可复现——这在因子挖掘里很重要,不然你没法判断改进是来自参数调整还是随机波动。

跑完之后看best_program.expression,如果打印出来是一串你能看懂的公式(比如div(ts_rank(close, 20), ts_std(volume, 10))),说明流程正常。如果打印出来是一堆乱码或者全是常数,检查数据输入格式和适应度函数。

4. 避坑与排查:遗传规划做因子挖掘的五个血泪教训

4.1 现象:搜出来的因子 IC 很高但实盘亏钱

原因:过拟合。遗传规划在样本内搜索能力极强,给它足够多的算子和足够深的树,它能把噪声也拟合进去。常见表现是样本内 IC 0.15,样本外直接变负。

解决:把数据切成训练集和验证集,适应度函数里同时看两段的 IC,取最小值或者加权平均。另外限制max_depth不超过 5,算子集合里不要放太多高次幂和复杂三角函数。

4.2 现象:程序跑了几代之后种群多样性骤降,所有个体长得一样

原因:选择压力太大。锦标赛选择的tournament_size设得太大,或者精英保留比例太高,导致少数个体迅速占领整个种群。

解决:把tournament_size降到 3 到 5 之间,精英保留不超过种群的 5%。另外可以加一个随机移民机制,每代随机生成几个新个体替换掉最差的。

4.3 现象:适应度函数返回 NaN,整个搜索卡住

原因:某个算子对输入数据产生了非法值(除零、对数负数、开方负数),NaN 沿着树往上传播,导致整棵树的适应度无法计算。

解决:在functions.py里给每个算子加保护,返回 NaN 时用一个极差的适应度值代替(比如 -999),而不是让 NaN 参与比较。同时检查输入数据里有没有无穷大或极端异常值,提前做 winsorize。

4.4 现象:时间序列数据对齐后样本量骤减

原因:因子计算需要滚动窗口,收益需要向前平移,两者叠加之后首尾都会损失样本。如果股票池里停牌股多,损失更大。

解决:先确认滚动窗口和预测周期,然后从数据两端各截掉相应长度。如果样本量还是不够,考虑缩短窗口或者放宽缺失值容忍度。但不要为了凑样本量而用未来数据填充。

4.5 现象:同样的代码和数据,两次运行结果完全不同

原因:随机种子没固定,或者用了多线程/多进程导致执行顺序不确定。

解决:在GeneticAlpha初始化时传入固定的random_state,并且确保所有随机操作(选择、交叉、变异)都从这个种子派生。如果用了并行评估,把并行关掉再验证一次,确认是并行导致的不确定性还是算法本身的问题。

5. 进阶用法:把搜出来的公式变成可回测的因子

搜到公式只是第一步,真正要用起来,得把它转成能接入回测框架的因子值。best_program.expression是一个字符串或者树结构,你需要写一个求值函数,把原始数据代进去算出每只股票每天的因子值。

def evaluate_program(program, data_dict): # program 是遗传规划搜出来的树 # data_dict 是变量名到数据的映射,比如 {'close': close_df, 'volume': vol_df} if program.is_leaf(): if program.value in data_dict: return data_dict[program.value] else: return program.value # 常数 else: left_val = evaluate_program(program.left, data_dict) right_val = evaluate_program(program.right, data_dict) return program.operator(left_val, right_val) # 用法 factor_values = evaluate_program(best_program, {'close': close_panel, 'volume': volume_panel}) # factor_values 现在是一个和输入同形状的 DataFrame,可以直接送进回测

这个递归求值函数的关键是data_dict的键要和公式里的变量名一致。遗传规划在搜索时用的变量名可能是x0x1这种,你需要做一个映射表,把x0对应到收盘价、x1对应到成交量。映射错了,因子含义就完全变了。

另一个进阶技巧是给公式做简化。遗传规划搜出来的树往往有冗余分支,比如div(mul(x0, 1), 1)这种。可以写一个简单的符号化简函数,把常数乘除和重复子树合并掉,让公式更简洁,也更容易理解经济含义。

验证因子有效性,我一般会看三个指标:IC 均值、ICIR、多空分组单调性。IC 均值大于 0.03、ICIR 大于 0.5、多空收益单调,才算初步可用。然后还要做行业中性化和市值中性化,确认因子不是靠暴露在某个风格上赚钱。

从那以后我每次搜完因子,都会强制走一遍样本外验证和中性化回测,不看到这两步的结果,绝不敢把公式写进策略里。希望这份代码包和上面的流程能帮你少走点弯路,把精力花在真正有价值的因子逻辑上。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 19:08:15

Generated Code、BSW 源码、MCAL 和手写代码,到底应该怎么划分?

上一篇讲完 Generate 之后,一个 AUTOSAR 配置终于从 Configurator 进入了 C 代码。 于是打开工程目录,会突然看到大量文件: CanIf_Cfg.c CanIf_Cfg.h CanIf.cDem_Cfg.c Dem_Cfg.h Dem.cMcu.c Port.c Can.cRte_xxx.c Rte_xxx.hCtAp_xxx.c CtAp_xxx.h它们看起来都是: .c .…

作者头像 李华
网站建设 2026/9/24 19:06:30

152、MLIR的Dataflow(数据流)模型与静态调度

MLIR的Dataflow(数据流)模型与静态调度 从一次诡异的死锁说起 去年调一个AI加速器后端,跑一个简单的卷积+ReLU+池化流水线,结果在硬件仿真阶段卡死了。波形一看,某个PE(处理单元)的输入缓冲一直空着,但上游的DMA明明已经把数据写到了共享内存。查了三天,最后发现是M…

作者头像 李华
网站建设 2026/9/24 19:04:03

Python实现省际绿色全要素生产率测算:基于SBM-DDF模型的完整实操指南

做省际绿色全要素生产率&#xff08;GTFP&#xff09;测算&#xff0c;最近几年我在学术项目里几乎每周都要碰一次。单纯的TFP只考虑资本、劳动和产出&#xff0c;而GTFP还要把能源消耗和污染排放这类非期望产出拉进生产框架&#xff0c;这就得靠SBM-DDF模型来做前沿效率测算&a…

作者头像 李华
网站建设 2026/9/24 19:03:49

Linux用户组与附加组实战:从概念到命令彻底搞懂

1. 先把概念讲清楚&#xff1a;用户、用户组与附加组各司其职上个月我在一台新装好的 Rocky Linux 9 上给项目组搭建共享环境&#xff0c;原计划半小时搞定&#xff0c;结果在“用户——用户组——附加组”这三层概念上来回绕了好几圈。很多人平时敲useradd、chown、chmod都很溜…

作者头像 李华
网站建设 2026/9/24 19:03:29

北京昌平靠谱的婚礼策划服务商筛选名录 省心不踩坑选择指南

在北京昌平准备结婚&#xff0c;不少新人都会被找婚礼策划这件事难住&#xff1a;不知道该怎么筛选靠谱服务商&#xff0c;怕踩坑怕麻烦&#xff0c;更怕费心费力还得不到想要的婚礼效果。整理这份筛选指南&#xff0c;就是希望能帮昌平本地备婚新人理清思路&#xff0c;选到省…

作者头像 李华
网站建设 2026/9/24 19:01:56

SD2本地视频生成全流程实战:从ComfyUI部署到AnimateDiff动画扩展

先说一个结论&#xff1a;用 SD2&#xff08;Stable Diffusion 2.x 系列&#xff09;做视频生成&#xff0c;并不是让模型直接“吐”出一段视频&#xff0c;而是把它作为图像生成核心&#xff0c;配合动画模块、帧插值、运动控制等一整套流程&#xff0c;把静态扩散模型改造成帧…

作者头像 李华