遗传规划入门:让计算机自己“进化”出因子

大家好,我是老蓝。今天咱们聊一个特别有意思的话题——遗传规划。说白了,就是让计算机像生物进化一样,自己“长”出有效的因子来。我第一次接触这个概念时,心里想的是:“这不就是让代码生孩子吗?”后来发现,还真差不多。

核心思想:遗传规划(Genetic Programming, GP)是进化算法的一种。它不直接给你答案,而是给你一套“进化规则”,让程序自己迭代出最优解。在因子挖掘里,GP 能自动组合各种基础算子,生成你根本想不到的复杂因子。

GP 的基本原理:达尔文在代码世界

遗传规划的核心流程,其实就四步:

  1. 初始化种群——随机生成一堆“因子表达式”,就像一堆随机生成的数学公式。
  2. 评估适应度——每个因子在历史数据上跑一遍,看谁预测能力最强。
  3. 选择与繁殖——表现好的因子被选出来,通过“交叉”(交换子树)和“变异”(随机修改节点)生成下一代。
  4. 迭代终止——重复几十上百代,直到找到满意的因子。

我刚开始做的时候,总觉得这玩意儿太随机了,能靠谱吗?后来发现,只要适应度函数设计得好,它真能挖出一些人类直觉想不到的因子。比如有一次,GP 生成了一个因子:(close - open) / (high - low) * volume,我一看,这不就是日内波动率加成交量的组合吗?但之前我从来没想过把它们这样乘起来。

我的经验:GP 的“进化”过程其实很暴力。你给它 1000 个随机因子,跑 50 代,最后可能只有 3-5 个是真正有效的。但就是这 3-5 个,往往能带来超额收益。别嫌少,质量比数量重要。

GP 在因子挖掘中的应用场景

你可能会问:为什么不用深度学习?嗯,深度学习是黑箱,GP 至少给你一个可解释的数学表达式。这在量化里太重要了——你总得知道你的因子为什么赚钱吧?

具体来说,GP 在因子挖掘中主要干三件事:

  • 特征组合——把基础数据(价格、成交量、财务指标)通过加减乘除、开方、取对数等操作组合起来。
  • 非线性关系发现——比如 log(volume) / sqrt(close) 这种人类不太容易想到的组合。
  • 自动特征选择——进化过程中,无效的表达式会被自然淘汰,留下的都是相对有效的。

我记得有一次做 A 股因子挖掘,GP 生成了一个因子:close / ma(close, 20) - 1。这不就是乖离率吗?但 GP 还生成了另一个:(close - ma(close, 5)) / (high - low),这个组合我之前从没见过,回测效果居然还不错。

避坑指南:我曾经犯过一个错误——让 GP 自由组合所有算子,结果生成了大量过拟合的因子。比如 close / close * close / close 这种毫无意义的表达式。后来我加了“复杂度惩罚”,表达式越长,适应度扣分越多。这才把那些“垃圾因子”过滤掉。

Python 库 gplearn 快速上手

说到工具,Python 里最成熟的 GP 库就是 gplearn。它封装了遗传规划的全流程,你只需要定义好输入数据和适应度函数就行。

先安装:

pip install gplearn

然后看一个最简单的例子:

import numpy as np
import pandas as pd
from gplearn.genetic import SymbolicRegressor
from gplearn.functions import _function_map

# 生成模拟数据
X = np.random.rand(100, 5)  # 5个基础特征
y = X[:, 0] ** 2 + np.sin(X[:, 1]) + 0.1 * np.random.randn(100)

# 定义GP模型
gp = SymbolicRegressor(
    population_size=500,      # 每代种群大小
    generations=20,           # 进化代数
    tournament_size=20,       # 锦标赛选择大小
    stopping_criteria=0.01,   # 提前停止条件
    p_crossover=0.7,          # 交叉概率
    p_subtree_mutation=0.1,   # 子树变异概率
    p_hoist_mutation=0.05,    # 提升变异概率
    p_point_mutation=0.1,     # 点变异概率
    max_samples=0.9,          # 每代使用90%样本
    verbose=1                 # 打印进化过程
)

# 训练
gp.fit(X, y)

# 查看最优表达式
print(gp._program)

# 预测
y_pred = gp.predict(X)

跑完之后,你会看到类似这样的输出:

    |   Population Average   |             Best Individual              |
---- ------------------------ ------------------------------------------ 
 Gen   Length   Fitness   Length   Fitness     OOB Fitness  Time Left
   0    12.34    0.2345      8     0.4567        0.4231      1.23m
   1    11.89    0.2567      8     0.4789        0.4456      1.15m
   ...
  19     8.45    0.3456      6     0.5678        0.5567      0.00m

Best expression: add(mul(X0, X0), sin(X1))

你看,它找到了 X0^2 + sin(X1),和我们的真实公式几乎一样。这就是 GP 的威力——它不需要你告诉它公式长什么样,它自己就能“进化”出来。

我的建议:刚开始用 gplearn 时,别把参数调得太激进。我一般先用 population_size=500generations=20 跑一轮,看看结果。如果效果不错,再慢慢增加代数。另外,max_samples 我习惯设成 0.8-0.9,这样能防止过拟合。

核心参数速查表

参数 默认值 我的推荐 说明
population_size 1000 500-2000 种群越大,搜索空间越广,但速度越慢
generations 20 20-50 代数太少可能没收敛,太多容易过拟合
p_crossover 0.8 0.7-0.9 交叉概率,太高会破坏优秀个体
p_subtree_mutation 0.1 0.05-0.15 子树变异,引入新结构的关键
max_samples 1.0 0.8-0.9 每代使用的样本比例,小于1可防过拟合
stopping_criteria 0.0 0.01-0.05 适应度提升小于此值时提前停止

GP 因子挖掘的完整流程

下面这张图,是我自己总结的 GP 因子挖掘全流程。你照着这个走,基本不会跑偏。

GP 因子挖掘全流程 数据准备 价格/成交量/财务数据 定义算子集 加减乘除/开方/取对数 初始化种群 随机生成因子表达式 评估适应度 IC/夏普/收益率 选择 锦标赛/轮盘赌选择 交叉 & 变异 生成新一代因子 终止判断 达到代数/收敛 输出最优因子 表达式 + 回测结果 未收敛则继续迭代 图:GP 因子挖掘的迭代流程,虚线表示未收敛时的回环

你看,整个流程其实就是一个循环:生成 -> 评估 -> 选择 -> 变异 -> 再评估。直到你满意为止。

重要提醒:GP 不是万能的。我见过有人把 GP 当成黑盒,扔进去一堆数据就等着收因子。结果呢?跑出来一堆过拟合的垃圾。记住:GP 只是工具,你的金融直觉和回测验证才是关键。因子挖出来后,一定要做样本外测试和逻辑检验。

好了,关于遗传规划入门,今天就聊到这儿。GP 这东西,上手不难,但用好需要经验。我建议你先拿模拟数据跑一跑,感受一下“进化”的过程。等熟悉了,再上真实数据。嗯,就这样。


蓝海数据掘金营,专注资料整理