简介:本资源是一套基于遗传编程(GP)的Python实现,专为量化投资领域设计,面向具备Python基础与金融建模经验的开发者、量化研究员及因子策略爱好者,解决传统阿尔法因子同质化、失效快的核心痛点。它将符号回归技术应用于多因子选股场景,支持时间序列维度的二维数据输入(如跨股票、跨时点的调整后价格),可自动演化出具有预测能力的原创因子表达式,显著提升因子挖掘效率与独特性。压缩包共8个文件,含7个核心Python模块(如genetic.py、fitness.py、utils.py等负责演化流程、适应度评估与工具函数)及1个README.md说明文档,整体仅28KB,轻量易部署。目前已有700人学习下载,提供完整可运行示例(demo.py)、清晰模块分工与开箱即用的遗传规划框架,助读者快速理解算法逻辑、调试因子生成过程并拓展至自有数据集。
1. 遗传规划生成阿尔法因子:不是调参,是让机器自己“发明”预测公式
你有没有试过——把市盈率、换手率、动量、波动率这些常见因子塞进线性回归,结果回测一跑,夏普比0.8还飘?不是数据不行,是因子结构太僵硬。传统多因子策略卡在“人定规则”的瓶颈上:我们总在已知函数空间里打转(比如return = a*pe + b*vol + c),但真实市场关系可能是return = log(pe) * sqrt(vol) / (1 + turnover^2)这种非线性、带交互、含阈值的黑匣子。Genetic-Alpha 干的就是这事:它不预设函数形式,而是用遗传规划(GP)从零演化出能拟合历史收益的数学表达式——不是拟合参数,是拟合整个公式结构。它专为时间序列因子设计,支持股票池×时间维度的二维输入(比如 3000 只股 × 240 天的 OHLCV 衍生指标),输出的是可解释、可落地、带自然泛化能力的 Alpha 公式源码。适合量化研究员、因子工程师、以及想摆脱“因子内卷”的中高频策略开发者。如果你还在手动拼凑因子组合、靠网格搜索调权重,这份 Python 实现就是你的后悔药。
2. 遗传规划原理与 Genetic-Alpha 架构设计:为什么不用 XGBoost,而选符号回归?
2.1 遗传规划 vs 传统机器学习:因子可解释性的生死线
多因子策略最怕什么?不是回测收益低,而是上线后突然失效且找不到原因。XGBoost、LSTM 这类黑盒模型能拟合复杂关系,但输出的是权重向量或神经元激活路径,无法回答“为什么这只股今天该涨”。而 Genetic-Alpha 基于遗传规划(Genetic Programming),本质是符号回归(Symbolic Regression):它在函数空间(如{+, -, *, /, sin, log, if_then_else})和终端集(如{pe, vol, mom, rank(eps), delta(close, 5)})中,通过树状结构编码公式,再用选择、交叉、变异演化出最优表达式。最终输出不是数值权重,而是类似if_then_else(rank(pe) < 0.3, log(mom) * vol, -sqrt(abs(eps)))的 Python 可执行字符串。这直接解决两个痛点:① 因子逻辑可审计(监管/风控要求);② 新增变量只需扩展终端集,无需重训全模型。
2.2 Genetic-Alpha 的模块化设计:从基因编码到因子落地
项目采用清晰分层架构,每个.py文件职责明确,避免“万能函数”式混乱:
| 文件名 | 核心职责 | 关键技术点 |
|---|---|---|
genetic.py | 主演化循环:初始化种群、评估适应度、选择/交叉/变异 | 使用deap库实现 GP 框架,自定义树形结构剪枝策略防止表达式爆炸 |
_program.py | 公式树节点定义与执行引擎 | 支持protected_div(防除零)、safe_log(防负数取对数)、rank等金融专用算子 |
fitness.py | 适应度函数:以 IC(信息系数)为核心,支持滚动窗口加权 | 默认用 Spearman 秩相关系数,可替换为 IR 或 Sharpe Ratio 代理目标 |
utils.py | 时间序列预处理:跨股票标准化、缺失值插补、滞后特征生成 | 内置cross_sectional_rank和time_series_zscore,适配宽表(stock × date)格式 |
demo.py | 端到端演示:加载示例数据 → 运行 GP → 生成因子 → 回测验证 | 使用pandas读取 CSV,输出因子值 DataFrame 和 Top/Bottom 组合收益曲线 |
提示:
_program.py中的eval()执行并非裸用——所有终端变量(如pe,vol)在运行前已被映射为 NumPy 数组,且运算全程在np向量化上下文中进行,避免 Python 循环拖慢速度。这是它能处理 3000×240 数据的关键。
2.3 为什么支持时间序列数据是重大突破?
原始 GP 算法默认处理单点样本(如(x1,x2,...,xn) → y),但金融数据天然具有横截面(股票间)和时间序列(时序)双重结构。Genetic-Alpha 的创新在于:将每只股票的时序特征作为独立个体输入,同时在适应度计算中强制引入跨股票排序逻辑。具体实现是:在fitness.py中,evaluate_individual()函数接收一个公式树,对当前窗口内所有股票计算该公式值,再与下期收益做秩相关(IC)。这意味着演化过程天然偏好能捕捉“相对强弱”的因子(如rank(pe) < 0.2 & rank(mom) > 0.8),而非绝对数值预测。这种设计让生成的因子具备天然的多空配对能力,无需后期人工转换。
3. 快速上手:从 demo.py 运行到自定义因子生成的完整链路
3.1 环境准备与依赖安装(避坑版)
Genetic-Alpha 依赖numpy,pandas,deap,scipy,但版本冲突是高频翻车点。实测稳定组合为:
# 推荐使用 conda 创建干净环境(pip 安装 deap 在 Windows 上易报错) conda create -n gp_alpha python=3.9 conda activate gp_alpha pip install numpy==1.23.5 pandas==1.5.3 scipy==1.10.1 # deap 必须指定版本,1.4.1 修复了 GP 树深度控制 bug pip install deap==1.4.1注意:不要用
pip install deap默认最新版(2.x),其creator类接口变更会导致_program.py中的PrimitiveSet初始化失败。血泪经验:deap==1.4.1是唯一经过demo.py全流程验证的版本。
3.2 运行 demo.py:理解数据流与输出含义
进入解压后的Genetic-Alpha-main目录,执行:
python demo.py你会看到类似输出:
[INFO] Loading sample data: 100 stocks × 120 days [INFO] Starting GP evolution: population=50, generations=20 Generation 0 - Best IC: 0.124 Generation 5 - Best IC: 0.287 ... Generation 20 - Best IC: 0.412 [INFO] Best formula: if_then_else(rank(pe) < 0.25, log(mom) * vol, -abs(eps)) [INFO] Saving factor series to factor_output.csv关键解读:
Best IC: 0.412是滚动 60 天窗口的平均 IC,>0.3 即具备实战价值;- 输出公式是标准 Python 表达式,可直接用于实盘计算;
factor_output.csv是 100×120 的因子值矩阵,列名为股票代码,索引为日期。
3.3 自定义你的因子:修改 demo.py 的三处核心参数
要适配自己的数据,只需改demo.py开头的配置段(无需碰核心算法):
# demo.py 第 15 行起 DATA_PATH = "your_data.csv" # 格式:index=日期, columns=[股票代码1, 股票代码2, ...] FEATURES = ["pe", "vol", "mom", "eps"] # 你数据中的列名,必须与 CSV 列一致 TARGET_COL = "next_return" # 下期收益率列名,需提前计算好 WINDOW_SIZE = 60 # IC 计算滚动窗口,建议 40-120 MAX_DEPTH = 5 # 公式树最大深度,>5 易过拟合,<3 表达力不足 POPULATION_SIZE = 100 # 种群大小,增大提升搜索质量但耗时逻辑说明:
FEATURES列表定义了 GP 的终端集(Terminal Set),即公式中允许出现的变量。TARGET_COL是因变量,必须是与DATA_PATH同形状的 Series(已对齐日期和股票)。MAX_DEPTH是关键超参——深度为 3 的树最多含 7 个节点(满二叉树),对应简单组合如pe * mom + vol;深度为 5 可生成if(rank(pe)<0.3, log(mom), vol/eps)这类带条件分支的复杂逻辑。
3.4 生成因子并导出为可复用函数
demo.py默认只输出 CSV,但实际部署需要 Python 函数。在demo.py末尾添加:
# 获取最佳个体(公式树) best_ind = tools.selBest(pop, 1)[0] # 编译为可调用函数 factor_func = gp.compile(best_ind, pset) # 测试单日计算 sample_date = "2023-01-01" sample_features = df.loc[sample_date, FEATURES] # shape: (n_stocks,) factor_values = factor_func(sample_features.values) # 返回 numpy array print(f"Factor values for {sample_date}: {factor_values[:5]}")编译后的factor_func是纯 NumPy 函数,无外部依赖,可直接嵌入你的策略框架(如 vn.py、Backtrader)。
4. 避坑指南:Genetic-Alpha 实战中踩过的 5 个真实坑
4.1 现象:GP 演化几代后 IC 值停滞在 0.05,不再提升
原因:终端集(FEATURES)中存在高度共线性变量(如pe和pb相关性 >0.9),导致演化过程陷入局部最优,公式反复组合冗余特征。
解决:在utils.py的preprocess_data()中加入共线性过滤:
def remove_high_corr(features_df, threshold=0.8): corr_matrix = features_df.corr().abs() upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)) to_drop = [column for column in upper.columns if any(upper[column] > threshold)] return features_df.drop(columns=to_drop)并在demo.py数据加载后调用:df_features = remove_high_corr(df_features)。
4.2 现象:demo.py报错TypeError: 'float' object is not subscriptable
原因:TARGET_COL(下期收益)列中存在 NaN,fitness.py的 IC 计算未做缺失值剔除,导致scipy.stats.spearmanr输入含 NaN 的数组。
解决:在fitness.py的evaluate_individual()函数开头添加:
# 对齐因子值与目标值,剔除任一含 NaN 的行 valid_mask = ~(np.isnan(factor_values) | np.isnan(target_values)) factor_values = factor_values[valid_mask] target_values = target_values[valid_mask] if len(factor_values) < 10: # 至少需 10 个有效样本 return -0.01, # 低适应度惩罚4.3 现象:生成的公式含log(-1)或1/0,实盘运行报错
原因:_program.py中的safe_log和protected_div仅在 GP 演化期间生效,但gp.compile()生成的函数直接调用原生math.log。
解决:重写compile后的函数,注入安全算子:
import numpy as np def safe_log(x): return np.where(x > 0, np.log(x), 0) def protected_div(left, right): return np.where(right != 0, left / right, 0) # 替换编译后的函数体 compiled_func = gp.compile(best_ind, pset) # 手动包装 def robust_factor_func(x): return compiled_func(x).astype(float) # 强制 float 避免 int 除零4.4 现象:因子值全为 NaN,或分布极度偏斜(99% 值集中在 0 附近)
原因:utils.py的cross_sectional_rank默认使用pct=True(百分位排名),但当股票池中大量股票某期特征值相同时(如新股上市首日pe=0),排名会坍缩为相同值,导致后续if_then_else分支失效。
解决:在utils.py中修改排名函数,添加微小扰动:
def cross_sectional_rank(series, pct=True): # 添加随机噪声打破并列 noise = np.random.normal(0, 1e-8, size=len(series)) series_noisy = series + noise return series_noisy.rank(pct=pct, method='average')4.5 现象:Windows 下运行demo.py卡死,CPU 占用 100% 但无日志输出
原因:deap的multiprocessing在 Windows 上需if __name__ == '__main__':保护,而demo.py未加此 guard。
解决:在demo.py末尾添加:
if __name__ == '__main__': main() # 将原有主逻辑封装为 main() 函数并确保genetic.py中所有pool.map()调用均在if __name__ == '__main__':下。
5. 进阶技巧:用生成因子构建多空组合与实盘部署 checklist
5.1 从单因子到多空组合:三步生成可交易信号
Genetic-Alpha 输出的是原始因子值(连续型),需转化为多空信号。推荐做法:
- 横截面标准化:对每日因子值做 z-score(
factor_z = (factor - mean) / std),消除量纲影响; - 分组切割:按因子值将股票分为 5 组(Quintile),Top 组做多,Bottom 组做空;
- 等权配置:每组内股票等权,避免市值偏差。
# 在 demo.py 后续添加 def build_long_short_signal(factor_df, long_top=0.2, short_bottom=0.2): signals = pd.DataFrame(index=factor_df.index, columns=factor_df.columns) for date in factor_df.index: daily_factors = factor_df.loc[date].dropna() if len(daily_factors) < 10: continue # z-score 标准化 z_score = (daily_factors - daily_factors.mean()) / daily_factors.std() # 分位数切割 quantiles = z_score.quantile([short_bottom, 1-long_top]) signals.loc[date] = np.where(z_score <= quantiles.iloc[0], -1, # 做空 np.where(z_score >= quantiles.iloc[1], 1, 0)) # 做多 return signals # 调用 signals = build_long_short_signal(factor_output_df)5.2 实盘部署 checklist:从实验室到生产环境的 7 个必检项
| 检查项 | 检查方法 | 不通过后果 |
|---|---|---|
| 1. 因子新鲜度 | 检查factor_output.csv最新日期是否等于今日-1 | 使用 T-2 数据导致信号滞后 |
| 2. 股票覆盖度 | 统计每日非 NaN 股票数,应 > 全池 80% | 信号稀疏,组合容量不足 |
| 3. IC 稳定性 | 计算过去 12 个月滚动 IC 标准差,应 < 0.15 | 因子失效风险高 |
| 4. 换仓频率 | 统计信号矩阵相邻两日变化率,应 < 30% | 过度交易侵蚀收益 |
| 5. 极端值处理 | 查看因子值 99.5% 分位数,若 > 1000 则需 winsorize | 实盘下单异常 |
| 6. 行业中性 | 对信号矩阵做行业暴露分析(申万一级),各行业净多头比例应 < 10% | 风险集中暴露 |
| 7. 交易成本敏感性 | 在回测中加入 0.1% 单边手续费,夏普比下降应 < 0.2 | 成本吞噬利润 |
5.3 用functions.py扩展你的算子库:添加动量衰减因子
functions.py是用户自定义算子的入口。例如,添加一个“30 日动量衰减”算子,模拟趋势衰减效应:
# functions.py 新增 def momentum_decay(x, window=30, decay_rate=0.95): """ 计算加权动量:近期价格权重更高,远期按 decay_rate 衰减 x: time-series array of close prices """ weights = np.array([decay_rate**(window-i) for i in range(window)]) weights = weights / weights.sum() # 归一化 return np.sum(x[-window:] * weights) # 在 demo.py 中注册 pset.addPrimitive(momentum_decay, [list, int, float], float)然后在FEATURES中加入"mom_decay",GP 就能在演化中自动选用它。
从那以后我每次部署新因子,都强制走一遍 checklist 表格里的 7 项验证——哪怕只是跑 demo,也先确认 IC 稳定性和股票覆盖度。因为一次漏掉行业暴露检查,曾让我在消费股集体回调时单日回撤 3.2%,而那个因子在回测里夏普比高达 2.1。希望帮到你。
本文还有配套的精品资源,点击获取