第二十七讲:做市算法参数优化——网格搜索、贝叶斯优化、遗传算法、在线学习与自适应参数调整

做市算法的参数调优,说白了就是给策略找一套最舒服的“姿势”。

我刚开始做做市的时候,总觉得参数设得越精细越好。后来被市场狠狠教育了几次才明白——参数不是越复杂越好,而是越“适应”越好。今天咱们就把几种主流的参数优化方法掰开揉碎了聊一聊。

一、参数优化的本质问题

做市策略的参数,比如报价深度、价差宽度、库存阈值、撤单频率等等,它们之间是相互耦合的。你调高了一个参数,可能另一个参数的效果就变了。

我习惯把参数优化看作一个“黑盒寻优”问题:

  • 输入:一组参数向量 θ = (θ₁, θ₂, ..., θₙ)
  • 输出:某个评价指标,比如夏普比率、PnL、库存风险等
  • 目标:找到使评价指标最优的那组参数

但这里有个坑——做市策略的评价函数通常是非凸的、多峰的,甚至带噪声。所以简单的梯度下降法基本没用。

核心观点:参数优化不是一次性工作,而是一个持续迭代的过程。市场变了,最优参数也会变。

二、网格搜索(Grid Search)

网格搜索是最朴素的方法。你把每个参数的可能取值列出来,然后穷举所有组合,跑一遍回测,选最好的。

我在项目中遇到过这种情况:一个策略有5个参数,每个参数取10个值,那就是10⁵ = 10万次回测。如果一次回测需要1分钟,那就是将近70天。嗯,显然不现实。

适用场景

  • 参数数量少(≤3个)
  • 参数取值范围明确
  • 回测速度快
# 网格搜索示例(伪代码)
import itertools

param_grid = {
    'spread_multiplier': [0.5, 1.0, 1.5, 2.0],
    'order_size': [100, 200, 500, 1000],
    'max_inventory': [500, 1000, 2000]
}

best_score = -float('inf')
best_params = None

for spread, size, inv in itertools.product(
    param_grid['spread_multiplier'],
    param_grid['order_size'],
    param_grid['max_inventory']
):
    score = backtest(spread, size, inv)
    if score > best_score:
        best_score = score
        best_params = (spread, size, inv)

避坑指南:我曾经用网格搜索优化一个高频做市策略,结果发现最优参数全在边界上。这说明我的参数范围设窄了,或者策略本身有问题。网格搜索只能告诉你“哪个最好”,不能告诉你“为什么最好”。

三、贝叶斯优化(Bayesian Optimization)

贝叶斯优化比网格搜索聪明得多。它不会盲目地试所有组合,而是根据已有的结果,推测哪里可能更优,然后“有目的”地去试。

说白了,它像是一个有经验的交易员——试了几次之后,大概知道哪个方向可能赚钱,就往那个方向多试试。

核心思想

  • 用高斯过程(GP)建模目标函数的后验分布
  • 用采集函数(如EI、UCB)决定下一个采样点
  • 在探索(exploration)和利用(exploitation)之间平衡
# 使用scikit-optimize进行贝叶斯优化
from skopt import gp_minimize
from skopt.space import Real, Integer

def objective(params):
    spread_mult, order_size, max_inv = params
    return -backtest(spread_mult, order_size, max_inv)  # 最小化负收益

space = [
    Real(0.5, 2.0, name='spread_multiplier'),
    Integer(100, 1000, name='order_size'),
    Integer(500, 2000, name='max_inventory')
]

result = gp_minimize(objective, space, n_calls=50, random_state=42)
print("最优参数:", result.x)
print("最优得分:", -result.fun)

个人经验:贝叶斯优化在参数数量5-10个时效果最好。少于3个,网格搜索就够了;多于15个,贝叶斯优化的收敛速度也会变慢。我一般先用贝叶斯优化跑50-100次,找到大致区域,再用网格搜索做局部精细调优。

四、遗传算法(Genetic Algorithm)

遗传算法模拟了自然选择的过程。你想想看,一群参数组合就像一群生物,好的基因(参数)会遗传下去,差的会被淘汰。

基本流程

  1. 初始化:随机生成一批参数组合(种群)
  2. 评估:计算每个个体的适应度(回测得分)
  3. 选择:适应度高的个体有更大概率被选中
  4. 交叉:选中的个体交换部分参数(类似基因重组)
  5. 变异:以一定概率随机改变某些参数
  6. 迭代:重复2-5步,直到收敛
# 遗传算法核心逻辑(简化版)
import random

def genetic_algorithm(pop_size=50, generations=30):
    # 初始化种群
    population = [random_params() for _ in range(pop_size)]
    
    for gen in range(generations):
        # 评估适应度
        scores = [backtest(p) for p in population]
        
        # 选择(轮盘赌)
        selected = []
        for _ in range(pop_size):
            idx = random.choices(range(pop_size), weights=scores, k=1)[0]
            selected.append(population[idx])
        
        # 交叉和变异
        new_population = []
        for i in range(0, pop_size, 2):
            p1, p2 = selected[i], selected[i+1]
            child1 = crossover(p1, p2)
            child2 = crossover(p2, p1)
            child1 = mutate(child1, mutation_rate=0.1)
            child2 = mutate(child2, mutation_rate=0.1)
            new_population.extend([child1, child2])
        
        population = new_population
    
    # 返回最优个体
    best_idx = max(range(pop_size), key=lambda i: backtest(population[i]))
    return population[best_idx]

为什么我喜欢遗传算法? 因为它不容易陷入局部最优。网格搜索和贝叶斯优化都可能被某个局部峰值“骗”住,但遗传算法通过种群多样性和变异操作,有更大机会跳出局部陷阱。

五、在线学习与自适应参数调整

前面几种方法都是离线优化——你先跑回测,找到最优参数,然后上线用。但市场是动态的,今天的最优参数,明天可能就变成最差的了。

所以,真正的做市系统需要在线学习能力。我把它叫做“边做边学”。

自适应调整的常见方法

  • 滑动窗口重优化:每隔一段时间(比如每小时),用最近的数据重新跑一次优化
  • 在线梯度下降:根据实时反馈,对参数做微调
  • 强化学习:把参数调整看作一个动作,用Q-learning或策略梯度来学习
  • 贝叶斯在线学习:用贝叶斯方法实时更新参数的后验分布
# 滑动窗口自适应调整示例
class AdaptiveMarketMaker:
    def __init__(self, window_size=1000):
        self.window_size = window_size
        self.data_buffer = []
        self.current_params = self.initial_params()
    
    def on_tick(self, tick):
        self.data_buffer.append(tick)
        if len(self.data_buffer) > self.window_size:
            self.data_buffer.pop(0)
        
        # 每500个tick重新优化一次
        if len(self.data_buffer) % 500 == 0:
            self.optimize_params()
    
    def optimize_params(self):
        # 用最近的数据做贝叶斯优化
        result = gp_minimize(
            lambda p: -self.backtest_on_buffer(p),
            self.param_space,
            n_calls=20
        )
        self.current_params = result.x

我曾经踩过的坑:在线学习最怕的是“过度适应”。有一次我让系统每10分钟就重新优化一次参数,结果它完全跟着短期噪声走,反而亏得更惨。后来我加了一个“冷却期”——参数调整后至少运行30分钟,才能再次调整。这样既保持了适应性,又避免了过度反应。

六、四种方法的对比与选择

方法 适用参数数量 计算成本 全局搜索能力 在线适应性
网格搜索 1-3 低(但随参数数量指数增长) 中等
贝叶斯优化 3-10 中等 中等(可扩展为在线版本)
遗传算法 5-20 非常好 差(通常离线使用)
在线学习 1-5(实时调整) 低(每次调整) 依赖基础方法 非常好

我个人习惯的组合方式是:先用遗传算法做一次全局搜索,找到大致的最优区域;然后用贝叶斯优化在这个区域做精细调优;最后上线时,配合在线学习做实时微调。这样既保证了全局最优性,又兼顾了实时适应性。

七、知识体系图

下面这张图展示了参数优化的完整知识体系,以及各种方法之间的关系:

做市算法参数优化知识体系 参数优化方法 网格搜索 贝叶斯优化 遗传算法 在线学习 穷举所有组合 参数≤3个时适用 高斯过程建模 采集函数选点 探索vs利用平衡 选择-交叉-变异 种群多样性 避免局部最优 滑动窗口重优化 在线梯度下降 强化学习 组合策略:遗传算法全局搜索 → 贝叶斯精细调优 → 在线学习实时微调 没有万能方法,只有最适合当前场景的方法

最后说一句:参数优化不是一劳永逸的事。市场结构在变,流动性在变,波动率在变,你的参数也得跟着变。我见过太多团队花三个月调出一套“完美参数”,上线后一个月就失效了。保持敬畏,保持迭代,这才是做市算法的生存之道。


无相订单流研究社 微信Lucian808555