10、遗传算法调优:遗传算法原理、编码方式、选择算子、交叉算子、变异算子、遗传算法在参数调优中的应用

说到参数调优,前面我们聊了网格搜索、随机搜索,还有贝叶斯优化。这些方法各有千秋,但遇到高维、非连续、带噪声的搜索空间时,就容易力不从心。这时候,我通常会祭出遗传算法。

遗传算法,说白了就是模拟生物进化过程。你想想看,大自然几亿年进化出这么多精妙的物种,这套机制用在参数搜索上,效果能差吗?我个人习惯把它叫做「参数界的达尔文进化论」。

遗传算法的核心原理

遗传算法的基本思路很简单:

  • 种群:一组候选参数组合
  • 适应度:参数组合的好坏(比如夏普比率、年化收益)
  • 选择:好的参数组合有更大机会「繁殖」
  • 交叉:两个参数组合交换部分基因
  • 变异:随机改变某些参数值

一代代迭代下去,种群的整体适应度会越来越高。嗯,这里要注意:遗传算法不保证找到全局最优,但通常能找到「足够好」的解。

核心思想:遗传算法不是单点搜索,而是种群搜索。它天然具备并行性,不容易陷入局部最优。

编码方式:怎么把参数变成「基因」

编码是遗传算法的第一步。我做过不少项目,踩过不少坑,这里分享三种常用编码方式:

1. 二进制编码

每个参数用一串二进制位表示。比如移动平均线周期参数 range(5, 100),步长1,需要7个二进制位(2^7=128)。

# 二进制编码示例
# 参数:移动平均周期 5~100
# 编码:7位二进制
# 解码:5 + int(binary_str, 2) % 96

def decode_ma_period(binary_str):
    return 5 + int(binary_str, 2) % 96

我的经验:二进制编码适合离散参数,但连续参数用它会损失精度。我曾经在优化止损比例时用二进制编码,结果发现精度不够,后来改用了实数编码。

2. 实数编码

直接用浮点数表示参数。简单直接,适合连续参数。

# 实数编码示例
# 基因:[ma_period, stop_loss, take_profit]
# 范围:[5, 100], [0.01, 0.05], [0.02, 0.10]

individual = [25, 0.03, 0.06]  # 一个个体

3. 排列编码

用于参数之间有顺序关系的情况。比如多品种轮动策略中的品种顺序。

避坑指南:我曾经在优化多品种组合时,用实数编码表示品种权重,结果交叉后权重和不为1了。后来改用排列编码+归一化处理才解决。

选择算子:优胜劣汰

选择算子的作用是从当前种群中选出「父母」,用于产生下一代。常用的有:

轮盘赌选择

适应度越高的个体,被选中的概率越大。就像轮盘赌,面积大的格子更容易中奖。

def roulette_selection(population, fitness):
    total_fitness = sum(fitness)
    pick = random.uniform(0, total_fitness)
    current = 0
    for i, f in enumerate(fitness):
        current += f
        if current > pick:
            return population[i]

锦标赛选择

随机选k个个体,挑最好的那个。我比较喜欢这种方法,因为它不会让超级个体垄断繁殖权。

def tournament_selection(population, fitness, k=3):
    candidates = random.sample(range(len(population)), k)
    best_idx = max(candidates, key=lambda i: fitness[i])
    return population[best_idx]

交叉算子:基因重组

交叉就是两个父母交换部分基因,产生新个体。这是遗传算法产生新解的主要方式。

单点交叉

随机选一个交叉点,交换点之后的基因。

# 单点交叉
parent1 = [10, 0.02, 0.05, 20, 0.5]
parent2 = [30, 0.04, 0.08, 15, 0.3]
# 交叉点=2
child1 = [10, 0.02, 0.08, 15, 0.3]
child2 = [30, 0.04, 0.05, 20, 0.5]

算术交叉

适合实数编码,对两个父母的基因做加权平均。

def arithmetic_crossover(p1, p2, alpha=0.5):
    child1 = alpha * p1 + (1-alpha) * p2
    child2 = (1-alpha) * p1 + alpha * p2
    return child1, child2

变异算子:引入新基因

变异是为了防止种群过早收敛到局部最优。说白了,就是给搜索过程加点「随机扰动」。

均匀变异

每个基因以一定概率随机重置。

def uniform_mutation(individual, mutation_rate=0.1, bounds):
    for i in range(len(individual)):
        if random.random() < mutation_rate:
            low, high = bounds[i]
            individual[i] = random.uniform(low, high)
    return individual

高斯变异

在原有基因值上加一个高斯噪声。适合连续参数。

def gaussian_mutation(individual, mutation_rate=0.1, sigma=0.1):
    for i in range(len(individual)):
        if random.random() < mutation_rate:
            individual[i] += random.gauss(0, sigma)
    return individual

我的习惯:变异率一般设在0.01~0.1之间。太高了会变成随机搜索,太低了容易早熟。我通常先用0.05试跑几代,看看种群多样性再调整。

遗传算法在参数调优中的应用

好了,理论讲完了,咱们看看怎么用在基差交易策略上。

完整流程

# 遗传算法调优基差交易策略参数
import random
import numpy as np

# 参数范围
PARAM_BOUNDS = {
    'ma_short': (5, 50),      # 短期均线
    'ma_long': (20, 200),     # 长期均线
    'entry_zscore': (1.0, 3.0),  # 开仓阈值
    'exit_zscore': (0.0, 1.0),   # 平仓阈值
    'stop_loss': (0.01, 0.05)    # 止损比例
}

def create_individual():
    """生成随机个体"""
    return [
        random.randint(5, 50),
        random.randint(20, 200),
        random.uniform(1.0, 3.0),
        random.uniform(0.0, 1.0),
        random.uniform(0.01, 0.05)
    ]

def fitness(individual):
    """计算适应度 - 夏普比率"""
    params = {
        'ma_short': individual[0],
        'ma_long': individual[1],
        'entry_zscore': individual[2],
        'exit_zscore': individual[3],
        'stop_loss': individual[4]
    }
    # 回测策略,返回夏普比率
    sharpe = backtest_strategy(params)
    return sharpe

def genetic_algorithm(pop_size=50, generations=100):
    """遗传算法主循环"""
    # 初始化种群
    population = [create_individual() for _ in range(pop_size)]
    
    for gen in range(generations):
        # 计算适应度
        fitness_scores = [fitness(ind) for ind in population]
        
        # 选择
        new_population = []
        for _ in range(pop_size // 2):
            p1 = tournament_selection(population, fitness_scores)
            p2 = tournament_selection(population, fitness_scores)
            
            # 交叉
            c1, c2 = arithmetic_crossover(p1, p2)
            
            # 变异
            c1 = gaussian_mutation(c1, mutation_rate=0.05)
            c2 = gaussian_mutation(c2, mutation_rate=0.05)
            
            new_population.extend([c1, c2])
        
        population = new_population
        
        # 记录最佳个体
        best_idx = np.argmax(fitness_scores)
        print(f"第{gen+1}代,最佳适应度:{fitness_scores[best_idx]:.4f}")
    
    # 返回最佳参数
    best_idx = np.argmax([fitness(ind) for ind in population])
    return population[best_idx]

# 运行
best_params = genetic_algorithm()
print("最优参数:", best_params)

实际应用中的注意事项

问题 表现 解决方案
早熟收敛 种群多样性快速下降 增大变异率、使用自适应变异
收敛过慢 适应度提升缓慢 增大选择压力、使用精英保留策略
过拟合 回测好、实盘差 增加惩罚项、使用多周期验证

避坑指南:我曾经在优化一个股指期货的基差策略时,遗传算法跑了200代,回测夏普高达3.5。结果实盘一跑,直接亏了。后来发现是过拟合了历史数据中的某个特殊行情。从那以后,我每次都用样本外数据做验证。

知识体系总览

下面这张图,是我整理的本章节知识体系。你可以把它当作一个快速索引:

遗传算法参数调优知识体系 遗传算法调优 进化原理 编码方式:二进制 | 实数 | 排列 三大算子 选择:轮盘赌 / 锦标赛 交叉:单点 / 算术 变异:均匀 / 高斯 应用:基差策略参数优化 适应度函数 → 选择 → 交叉 → 变异 → 迭代 核心优势:并行搜索、不易陷入局部最优、适合高维空间

遗传算法这东西,上手不难,但调好需要经验。我建议你先从简单的参数开始试,比如只优化两三个参数,跑个几十代看看效果。等摸透了套路,再扩展到全参数优化。

记住一点:遗传算法不是银弹。它适合搜索空间大、目标函数复杂的情况。如果你的参数只有两三个,网格搜索可能更快。但遇到十几个参数、非凸目标函数时,遗传算法的优势就体现出来了。

总结:遗传算法调优的核心在于平衡「探索」和「利用」。探索靠变异和交叉,利用靠选择。找到这个平衡点,你的策略参数优化就成功了一大半。


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