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