第四节:网格搜索与随机搜索——参数优化的两把刷子
说实话,做暗池交易这些年,我踩过最大的坑就是参数调优。你辛辛苦苦写了个冰山订单策略,结果参数没设对,跑出来的曲线跟心电图似的。今天咱们就聊聊参数优化的两个基本功——网格搜索和随机搜索。
核心观点:参数优化不是玄学,是有方法论支撑的工程实践。网格搜索适合低维空间,随机搜索在高维场景下更高效。
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}")
实战技巧:我在做冰山订单优化时,发现价格偏移这个参数特别敏感。稍微调一点,滑点成本就天差地别。所以我会在随机搜索阶段多采样几次,确保找到的"最优区域"是可靠的。
最后,咱们用一张图来总结整个参数优化的流程:
嗯,这套流程我用了好几年,效果一直很稳定。记住一个原则:不要一上来就搞网格搜索,先让随机搜索帮你探探路,找到大概方向,再用网格搜索做精细调整。这样既省时间,又不会错过好参数。