3. 遗传算法入门:基因编码、选择、交叉、变异,一个简单的交易策略优化案例

好,咱们今天聊点硬核的——遗传算法。

说实话,我第一次接触这东西是在做CTA策略参数优化的时候。那时候我还在用网格搜索,跑一次要三天。后来一个老前辈跟我说:“你试试遗传算法,别傻乎乎地全跑一遍。” 嗯,试完之后,真香。

遗传算法(GA)说白了,就是模拟生物进化过程来找最优解。你想想看,大自然几亿年进化出人类这么复杂的生物,靠的就是“优胜劣汰、适者生存”那套逻辑。我们做量化交易,不也是在找那个最能赚钱的策略参数吗?

核心思想: 把策略参数看成“基因”,把策略表现看成“适应度”。好的基因留下来,差的淘汰掉,再通过交叉、变异产生新个体。一代代迭代下去,最终收敛到最优解。

3.1 基因编码:怎么把策略参数变成“染色体”

先解决第一个问题:怎么把策略参数表示成计算机能理解的形式?

我个人习惯用二进制编码。举个例子,假设我们有个简单的双均线策略,需要优化两个参数:

  • 短期均线周期:范围 5~50
  • 长期均线周期:范围 20~200

每个参数用10位二进制表示,两个参数拼起来就是一条20位的染色体。比如:

短期均线(10位) | 长期均线(10位)
0000011111    | 0010101010
→ 短期 = 31, 长期 = 42

小技巧: 我在项目中遇到过参数范围跨度很大的情况,比如一个是1~10,另一个是100~1000。这时候建议用不同位数的二进制编码,或者直接用实数编码。别死磕二进制。

当然,也有用实数编码的,直接把参数值当基因。我个人觉得,二进制编码在交叉和变异时更自然,但实数编码更直观。看个人喜好。

3.2 选择:谁活下来,谁被淘汰

有了初始种群(比如随机生成100组参数),接下来就要算适应度了。

适应度函数就是你的目标函数。在交易策略里,可以是夏普比率、年化收益率、最大回撤的倒数…… 我一般用夏普比率 × 年化收益的组合,既考虑收益又考虑风险。

算完适应度,就要选择了。常用的方法有:

  • 轮盘赌选择: 适应度越高,被选中的概率越大。就像转盘,大块头的区域更容易被指针点到。
  • 锦标赛选择: 随机挑几个个体,选里面最好的。我比较喜欢这个,简单粗暴,不容易早熟收敛。
  • 精英保留: 把最好的几个直接复制到下一代,保证最优解不会丢失。

注意: 我曾经犯过一个错误——只用轮盘赌,结果适应度最高的个体占了绝对优势,种群迅速失去多样性,陷入局部最优。后来我加了精英保留和锦标赛选择,效果好了很多。

3.3 交叉:让好基因组合起来

选择完父代,就要让它们“生孩子”了。交叉操作就是模拟生物的有性繁殖。

最常用的是单点交叉:随机选一个交叉点,交换两个父代染色体后半段。比如:

父代A: 0000011111 | 0010101010
父代B: 1111100000 | 0101010101
交叉点在第10位之后
子代1: 0000011111 | 0101010101
子代2: 1111100000 | 0010101010

还有多点交叉均匀交叉等变种。我个人经验是,单点交叉在大多数情况下够用了。交叉概率一般设在0.6~0.9之间,太高容易破坏好基因,太低进化太慢。

3.4 变异:引入随机性,防止陷入局部最优

交叉之后,还要加点“随机扰动”——这就是变异。

二进制编码的变异很简单:随机选几个位,0变1,1变0。变异概率通常设得很低,0.01~0.1之间。太高了就成了随机搜索,太低了又容易早熟。

为什么需要变异? 你想想看,如果所有个体都差不多,交叉也产生不了新东西,那算法就卡住了。变异就像给种群打了一针“兴奋剂”,让它跳出局部最优。

我记得有一次优化一个多因子选股模型,种群迭代到50代时适应度就不动了。我检查了一下,发现所有个体的参数都集中在某个小区域。后来我把变异概率从0.05提高到0.15,又跑了20代,找到了一个更好的解。

3.5 完整流程:一个简单的交易策略优化案例

好,理论说完了,咱们来点实战。假设我们要优化一个简单的双均线金叉死叉策略

  • 短期均线上穿长期均线 → 买入
  • 短期均线下穿长期均线 → 卖出

需要优化的参数:短期周期(5~50)、长期周期(20~200)。适应度函数用夏普比率。

完整流程如下:

  1. 初始化: 随机生成50组参数,每组用20位二进制编码
  2. 评估: 对每组参数回测,计算夏普比率
  3. 选择: 用锦标赛选择(每次随机挑3个,选最好的)选出25对父代
  4. 交叉: 每对父代以0.8的概率进行单点交叉,生成50个子代
  5. 变异: 每个子代的每个位以0.05的概率翻转
  6. 精英保留: 把上一代最好的2个个体直接复制到下一代
  7. 重复: 回到步骤2,直到达到最大代数(比如100代)或适应度不再提升

下面我用Python伪代码展示核心逻辑:

# 伪代码,实际实现需要更多细节
def genetic_algorithm():
    # 初始化种群
    population = [random_chromosome() for _ in range(50)]
    
    for generation in range(100):
        # 评估适应度
        fitness = [evaluate(chrom) for chrom in population]
        
        # 精英保留
        elites = select_elites(population, fitness, 2)
        
        # 选择父代
        parents = tournament_selection(population, fitness, 25)
        
        # 交叉生成子代
        offspring = []
        for i in range(0, len(parents), 2):
            if random() < 0.8:
                child1, child2 = crossover(parents[i], parents[i+1])
            else:
                child1, child2 = parents[i], parents[i+1]
            offspring.extend([child1, child2])
        
        # 变异
        for child in offspring:
            mutate(child, rate=0.05)
        
        # 新种群 = 精英 + 子代
        population = elites + offspring
    
    return best_chromosome(population, fitness)

避坑指南: 我曾经把精英保留数量设得太大(比如10个),结果种群多样性急剧下降。建议精英数量不超过种群总数的5%。另外,记得在每次迭代时记录最佳适应度,方便调试。

3.6 遗传算法的优缺点

优点 缺点
不需要梯度信息,适用于非连续、非线性问题 计算量大,每次迭代都要评估整个种群
天然支持并行计算 容易早熟收敛,陷入局部最优
可以处理大量参数 参数(种群大小、交叉率、变异率)调起来麻烦
不容易陷入局部最优(相比梯度下降) 没有理论保证收敛到全局最优

嗯,说白了,遗传算法不是万能的。但它确实是个好工具,尤其在参数空间大、目标函数复杂的时候。我现在的习惯是:先用遗传算法跑一遍,找到大概的最优区域,再用局部搜索精调。两相结合,效果最好。

最后,送你一张遗传算法的流程图,方便你理解整个进化过程:

遗传算法流程图 开始:初始化种群 评估适应度 选择父代 交叉生成子代 变异操作 终止条件? 否(继续迭代) 是(输出最优解) 输出最优解

这张图把整个流程串起来了。你照着这个逻辑写代码,基本不会跑偏。


公众号:蓝海资料掘金营,微信deep3321