第四节:网格搜索与随机搜索——参数优化的两把刷子

说实话,做暗池交易这些年,我踩过最大的坑就是参数调优。你辛辛苦苦写了个冰山订单策略,结果参数没设对,跑出来的曲线跟心电图似的。今天咱们就聊聊参数优化的两个基本功——网格搜索和随机搜索。

核心观点:参数优化不是玄学,是有方法论支撑的工程实践。网格搜索适合低维空间,随机搜索在高维场景下更高效。

4.1 网格搜索原理与实现

网格搜索,说白了就是穷举。你把每个参数的可能取值列出来,然后组合成一张大网,挨个试一遍。我刚开始做量化那会儿,觉得这方法太笨了。后来发现,在参数维度少、取值范围明确的情况下,这反而是最靠谱的方式。

举个例子,冰山订单有两个关键参数:显示数量价格偏移。假设显示数量取 [100, 200, 300],价格偏移取 [0.01, 0.02, 0.03],那网格就是 3×3=9 种组合。

# 网格搜索的简单实现
import itertools
import numpy as np

def grid_search(param_grid, objective_func):
    """
    param_grid: 字典,键是参数名,值是候选值列表
    objective_func: 评估函数,输入参数字典,返回得分
    """
    best_score = -np.inf
    best_params = None
    
    # 生成所有参数组合
    keys = param_grid.keys()
    values = param_grid.values()
    
    for combination in itertools.product(*values):
        params = dict(zip(keys, combination))
        score = objective_func(params)
        
        if score > best_score:
            best_score = score
            best_params = params
            
    return best_params, best_score

# 冰山订单参数网格
param_grid = {
    'display_size': [100, 200, 300, 500],
    'price_offset': [0.005, 0.01, 0.02, 0.03],
    'time_interval': [1, 2, 5, 10]  # 单位:秒
}

# 总共 4×4×4 = 64 种组合
print(f"总组合数:{len(list(itertools.product(*param_grid.values())))}")

我的经验:网格搜索的步长很关键。步长太小,计算量爆炸;步长太大,容易错过最优解。我一般先粗搜确定大致范围,再细搜精确定位。

4.2 随机搜索原理与实现

随机搜索的思路就不同了。它不穷举,而是在参数空间里随机采样。你想想看,当参数维度超过3个时,网格搜索的组合数会指数级增长。比如5个参数,每个取10个值,那就是10万种组合——跑一遍得等到猴年马月。

随机搜索的好处是:采样效率高。它不需要遍历所有组合,而是用随机采样的方式探索空间。理论上,只要采样次数足够多,找到最优解的概率会趋近于1。

# 随机搜索的简单实现
import random
import numpy as np

def random_search(param_ranges, objective_func, n_iter=100):
    """
    param_ranges: 字典,键是参数名,值是 (min, max) 范围
    objective_func: 评估函数
    n_iter: 采样次数
    """
    best_score = -np.inf
    best_params = None
    
    for _ in range(n_iter):
        params = {}
        for key, (low, high) in param_ranges.items():
            # 连续参数用均匀分布采样
            if isinstance(low, float) or isinstance(high, float):
                params[key] = random.uniform(low, high)
            else:
                params[key] = random.randint(low, high)
        
        score = objective_func(params)
        
        if score > best_score:
            best_score = score
            best_params = params
            
    return best_params, best_score

# 冰山订单参数范围
param_ranges = {
    'display_size': (100, 1000),      # 整数范围
    'price_offset': (0.001, 0.05),    # 浮点数范围
    'time_interval': (1, 30)          # 整数范围
}

# 采样100次
best_params, best_score = random_search(param_ranges, evaluate_strategy, n_iter=100)
print(f"最优参数:{best_params},得分:{best_score}")

注意:随机搜索不是瞎蒙。采样次数太少,结果可能不稳定。我建议至少采样 50-100 次,具体看参数空间的复杂度。

4.3 两种方法的对比与适用场景

咱们来做个对比,这样你心里就有数了。

维度 网格搜索 随机搜索
搜索方式 穷举所有组合 随机采样
计算量 随维度指数增长 线性增长
适用维度 ≤3 维 ≥3 维
结果确定性 确定找到最优 概率性找到最优
实现难度 简单 简单
典型场景 参数少、范围明确 参数多、范围模糊

我个人习惯是:先用随机搜索探路,再用网格搜索精调。比如冰山订单,我会先用随机搜索跑100次,找到大概的最优区域,然后在这个区域里用网格搜索做精细调整。

避坑指南:我曾经在一个5维参数空间里直接用网格搜索,每个参数取10个值,结果跑了整整两天。后来换成随机搜索,只用了2小时就找到了差不多的结果。嗯,从那以后我再也不盲目用网格搜索了。

4.4 实战:优化冰山订单参数

好了,理论说完了,咱们来真刀真枪干一场。假设我们要优化冰山订单的三个参数:

  • 显示数量(display_size):每次在订单簿上显示的数量
  • 价格偏移(price_offset):相对于最优价格的偏移量
  • 时间间隔(time_interval):每次撤单重发的时间间隔

评估指标我们用滑点成本成交率的加权组合。滑点越低越好,成交率越高越好。

# 完整的参数优化流程
import numpy as np
import random
from datetime import datetime

def evaluate_iceberg_params(params, market_data):
    """
    评估冰山订单参数的效果
    params: {'display_size': int, 'price_offset': float, 'time_interval': int}
    market_data: 历史市场数据
    """
    display_size = params['display_size']
    price_offset = params['price_offset']
    time_interval = params['time_interval']
    
    # 模拟冰山订单执行
    total_slippage = 0
    total_filled = 0
    total_orders = 0
    
    for i in range(0, len(market_data), time_interval):
        # 获取当前市场状态
        best_bid = market_data[i]['bid']
        best_ask = market_data[i]['ask']
        mid_price = (best_bid + best_ask) / 2
        
        # 计算订单价格
        order_price = mid_price * (1 - price_offset)
        
        # 模拟成交(简化版)
        available_volume = market_data[i]['volume']
        filled = min(display_size, available_volume)
        
        # 计算滑点
        slippage = abs(order_price - mid_price) / mid_price
        
        total_slippage += slippage * filled
        total_filled += filled
        total_orders += 1
    
    # 计算综合得分(滑点越低越好,成交率越高越好)
    avg_slippage = total_slippage / max(total_filled, 1)
    fill_rate = total_filled / (display_size * total_orders)
    
    # 得分 = 成交率 - 滑点惩罚
    score = fill_rate - avg_slippage * 100
    
    return score

# 第一步:随机搜索探路
print("开始随机搜索...")
param_ranges = {
    'display_size': (100, 1000),
    'price_offset': (0.001, 0.05),
    'time_interval': (1, 30)
}

random_results = []
for _ in range(100):
    params = {
        'display_size': random.randint(100, 1000),
        'price_offset': random.uniform(0.001, 0.05),
        'time_interval': random.randint(1, 30)
    }
    score = evaluate_iceberg_params(params, market_data)
    random_results.append((score, params))

# 找出随机搜索的最优结果
random_results.sort(reverse=True)
best_random = random_results[0]
print(f"随机搜索最优:{best_random[1]},得分:{best_random[0]:.4f}")

# 第二步:在最优区域做网格精调
print("\n开始网格精调...")
best_params = best_random[1]

# 在最优参数附近构建网格
grid_params = {
    'display_size': range(max(100, best_params['display_size']-100), 
                          min(1000, best_params['display_size']+100), 50),
    'price_offset': [best_params['price_offset'] * (1 + i*0.1) for i in range(-3, 4)],
    'time_interval': range(max(1, best_params['time_interval']-5),
                           min(30, best_params['time_interval']+5), 1)
}

# 网格搜索
import itertools
best_grid_score = -np.inf
best_grid_params = None

for display_size in grid_params['display_size']:
    for price_offset in grid_params['price_offset']:
        for time_interval in grid_params['time_interval']:
            params = {
                'display_size': display_size,
                'price_offset': price_offset,
                'time_interval': time_interval
            }
            score = evaluate_iceberg_params(params, market_data)
            
            if score > best_grid_score:
                best_grid_score = score
                best_grid_params = params

print(f"网格精调最优:{best_grid_params},得分:{best_grid_score:.4f}")

实战技巧:我在做冰山订单优化时,发现价格偏移这个参数特别敏感。稍微调一点,滑点成本就天差地别。所以我会在随机搜索阶段多采样几次,确保找到的"最优区域"是可靠的。

最后,咱们用一张图来总结整个参数优化的流程:

冰山订单参数优化流程 定义参数空间 显示数量、价格偏移、时间间隔 随机搜索探路 采样100次,找到最优区域 网格搜索精调 在最优区域精细搜索 评估 满意 不满意 输出最优参数 调整范围,重新搜索 先随机搜索确定最优区域,再网格搜索精确定位

嗯,这套流程我用了好几年,效果一直很稳定。记住一个原则:不要一上来就搞网格搜索,先让随机搜索帮你探探路,找到大概方向,再用网格搜索做精细调整。这样既省时间,又不会错过好参数。

专注资料整理