六、参数空间定义:参数类型、范围、步长与组合爆炸
做参数调优,第一步不是跑回测,而是先把参数空间定义清楚。我见过太多人上来就写个双层循环,跑完发现结果根本没法用——要么参数范围太宽,要么步长选得不对,要么组合数量直接爆炸。
说白了,参数空间定义就是给你的策略画个圈。圈画大了,算力不够;圈画小了,可能错过最优解。今天我就把这块掰开了讲清楚。
6.1 参数类型:连续型 vs 离散型
参数类型其实就两种:连续型和离散型。但很多人一开始没区分清楚,导致后面代码写得很别扭。
连续型参数,比如移动平均的周期数、ATR的倍数、止损比例。这些参数理论上可以取任意实数。但实际中我们只能采样,所以就有了步长的概念。
离散型参数,比如选择哪种入场信号(金叉、死叉、突破)、是否启用过滤条件、使用哪种均线类型(SMA、EMA、WMA)。这些参数是枚举值,没有中间状态。
我个人习惯:在定义参数空间时,先把离散型参数单独拎出来。因为离散型参数没法做插值,也没法用梯度优化。我一般用网格搜索处理离散型,用随机搜索或贝叶斯优化处理连续型。
举个例子,一个基差交易策略的参数可能长这样:
# 参数空间定义示例
params = {
'lookback_period': (5, 60, 1), # 连续型:回看周期,范围5-60,步长1
'entry_zscore': (1.5, 3.0, 0.1), # 连续型:入场Z值,范围1.5-3.0,步长0.1
'exit_zscore': (0.0, 1.0, 0.1), # 连续型:出场Z值,范围0.0-1.0,步长0.1
'signal_type': ['cross', 'threshold', 'momentum'], # 离散型:信号类型
'use_filter': [True, False] # 离散型:是否启用过滤
}
6.2 参数范围设定:别拍脑袋
参数范围怎么定?我见过有人把移动平均周期设到1到500,结果跑出来全是无效结果。为什么?因为周期太长,交易次数太少,统计意义都没有。
设定参数范围,我建议遵循三个原则:
- 业务逻辑约束:比如基差回归周期,不可能超过合约存续期。你想想看,一个月的合约,你设个60天的回看周期,那不是扯淡吗?
- 统计意义约束:参数太小,样本量不够;参数太大,交易次数太少。我一般要求每个参数组合至少产生30笔交易,否则统计结果不可信。
- 计算资源约束:范围越宽,组合越多。这个后面会细说。
避坑指南:我曾经在回测一个跨期套利策略时,把入场阈值设到了0.1到5.0。结果发现0.1到0.5这个区间全是噪声交易,白白浪费了算力。后来我改成先做一次粗扫描,找到有效区间再细化。这个习惯我一直保留到现在。
6.3 参数步长选择:粗扫 vs 细扫
步长选多大?这是个平衡问题。步长太大,可能跳过最优解;步长太小,组合数量爆炸。
我的做法是分两步走:
- 粗扫描:步长设大一点,快速找到参数的热区。比如周期参数步长设5,阈值步长设0.5。
- 细扫描:在热区附近缩小范围,步长设小。比如周期步长设1,阈值步长设0.05。
这样既不会漏掉最优解,也不会一开始就把算力耗光。
| 参数 | 粗扫描范围 | 粗扫描步长 | 细扫描范围 | 细扫描步长 |
|---|---|---|---|---|
| 回看周期 | 10 - 100 | 10 | 30 - 50 | 2 |
| 入场Z值 | 1.0 - 3.0 | 0.5 | 1.5 - 2.5 | 0.1 |
| 出场Z值 | 0.0 - 1.5 | 0.3 | 0.3 - 0.8 | 0.05 |
6.4 参数组合爆炸问题
这是最要命的问题。假设你有3个连续参数,每个取20个值,那就是20×20×20=8000个组合。再加2个离散参数,每个取3个值,那就是8000×3×3=72000个组合。跑一次回测如果花1秒,那就是20个小时。
为什么会这样?因为组合数是乘起来的,不是加起来的。这就是所谓的「维度灾难」。
我常用的几种应对方法:
- 随机搜索:不遍历所有组合,而是随机采样。实践证明,随机搜索在1000次采样内就能找到接近最优的解。
- 贝叶斯优化:用高斯过程建模,每次迭代都选最有潜力的参数组合。效率比网格搜索高10倍以上。
- 参数分组:把参数分成几组,先优化一组,固定后再优化另一组。虽然可能错过交互效应,但胜在速度快。
- 降维处理:有些参数其实相关性很高,比如入场阈值和出场阈值。可以只优化它们的差值,减少一个维度。
注意:随机搜索和贝叶斯优化虽然效率高,但有个前提——你的回测函数必须是稳定的。如果每次跑同样的参数结果都不一样(比如用了随机种子没固定),那这些优化方法就废了。我踩过这个坑,后来每次回测前都固定随机种子。
6.5 参数约束条件
参数之间往往有约束关系。比如入场阈值必须大于出场阈值,否则逻辑上就不通。再比如短期均线周期必须小于长期均线周期。
这些约束条件如果不处理好,会产生大量无效组合。我一般用两种方式处理:
硬约束:在参数生成时直接过滤掉无效组合。比如:
# 硬约束示例
def is_valid_params(short_period, long_period):
return short_period < long_period # 短期必须小于长期
软约束:在回测结果中标记无效组合,但不直接过滤。这样可以看到边界附近的表现,有时候边界上的结果反而有启发。
我个人经验:硬约束适合逻辑上绝对不可能的情况,比如入场阈值大于出场阈值。软约束适合那些「理论上不合理但实际可能有效」的情况,比如短期均线略大于长期均线。我见过一些策略在边界附近反而表现更好,因为市场本身就不是完全理性的。
6.6 知识体系总览
下面这张图把参数空间定义的核心逻辑串起来了。你可以看到,从参数类型开始,到范围、步长、组合爆炸、约束条件,每一步都有对应的处理策略。
嗯,到这里参数空间定义这块就讲完了。你想想看,其实核心就三件事:搞清楚参数类型、定好范围和步长、处理好组合爆炸和约束。把这三点做好了,后面的调优工作才能顺利推进。