4、网格搜索法:网格搜索原理、全网格遍历实现、计算复杂度分析
4.1 网格搜索原理
网格搜索(Grid Search)是最直观的参数优化方法,其核心思想是:穷举所有候选参数组合,逐一运行回测并评估绩效,最终选出最优参数集。该方法不依赖梯度信息,适用于离散型、非连续或不可微的参数空间。
适用场景:参数维度 ≤ 3、参数取值范围明确、回测速度较快(单次回测 < 1 秒)的策略原型验证阶段。
4.1.1 参数空间定义
假设策略有 k 个待优化参数,每个参数 pi 在区间 [mini, maxi] 内按步长 si 离散化,则参数空间大小为:
N = ∏_{i=1}^{k} ( floor((max_i - min_i) / s_i) + 1 )
例如:移动平均窗口 n ∈ [10, 50] 步长 5,止损比例 stop ∈ [0.01, 0.05] 步长 0.01,则组合数 = (9) × (5) = 45 组。
4.1.2 评估指标选择
| 指标类别 | 常用指标 | 适用目标 |
|---|---|---|
| 收益类 | 累计收益率、年化收益率 | 最大化绝对收益 |
| 风险调整类 | 夏普比率、卡玛比率 | 平衡收益与波动 |
| 回撤类 | 最大回撤、回撤恢复天数 | 控制下行风险 |
| 稳定性类 | 胜率、盈亏比、交易次数 | 评估策略鲁棒性 |
关键原则:网格搜索应使用单一综合指标(如夏普比率)作为排序依据,避免多指标人工加权导致过拟合。
4.2 全网格遍历实现
以下为事件驱动回测框架下的网格搜索实现模板,采用嵌套循环遍历所有参数组合:
import itertools
import pandas as pd
def grid_search(strategy_class, param_grid, data, **backtest_kwargs):
"""
全网格遍历实现
:param strategy_class: 策略类(需继承 BaseStrategy)
:param param_grid: dict,格式 { 'param_name': [value1, value2, ...] }
:param data: pd.DataFrame,历史行情数据
:param backtest_kwargs: 回测引擎其他参数
:return: pd.DataFrame,包含所有参数组合及评估指标
"""
keys = list(param_grid.keys())
values = list(param_grid.values())
results = []
for combination in itertools.product(*values):
params = dict(zip(keys, combination))
# 实例化策略并注入参数
strategy = strategy_class(**params)
# 运行事件驱动回测
engine = BacktestEngine(data, strategy, **backtest_kwargs)
engine.run()
# 提取评估指标
metrics = engine.get_metrics()
metrics.update(params)
results.append(metrics)
result_df = pd.DataFrame(results)
# 按夏普比率降序排列
result_df.sort_values('sharpe_ratio', ascending=False, inplace=True)
return result_df
# 使用示例
param_grid = {
'fast_ma': [5, 10, 20],
'slow_ma': [30, 50, 100],
'stop_loss': [0.02, 0.03, 0.05]
}
best_params = grid_search(MovingAverageCrossStrategy, param_grid, ohlc_data)
print(best_params.head(5))
性能警告:上述代码为串行执行。当参数组合数超过 1000 时,建议使用
multiprocessing.Pool 或 joblib.Parallel 进行并行加速,否则单次网格搜索可能耗时数小时。
4.3 计算复杂度分析
网格搜索的计算复杂度由参数维度 k 和每个维度的粒度 mi 共同决定:
- 时间复杂度:O(N × T),其中 N = ∏ mi 为组合总数,T 为单次回测耗时。
- 空间复杂度:O(N × M),M 为每个组合存储的指标数量(通常 10~20 个)。
4.3.1 维度灾难
当参数维度增加时,组合数呈指数级增长,称为“维度灾难”。以下表格展示了不同维度下的组合爆炸情况:
| 参数维度 k | 每维粒度 m | 组合总数 N | 单次回测 0.1s | 单次回测 1s |
|---|---|---|---|---|
| 2 | 10 | 100 | 10 s | 1.7 min |
| 3 | 10 | 1,000 | 1.7 min | 16.7 min |
| 4 | 10 | 10,000 | 16.7 min | 2.8 h |
| 5 | 10 | 100,000 | 2.8 h | 27.8 h |
| 6 | 10 | 1,000,000 | 27.8 h | 11.6 d |
工程建议:当 k ≥ 4 或 N > 10,000 时,应放弃全网格搜索,改用随机搜索、贝叶斯优化或遗传算法等更高效的搜索策略。
4.4 网格搜索流程与复杂度可视化
下图展示了网格搜索的完整执行流程,以及不同参数维度下的计算复杂度对比:
4.5 网格搜索的优缺点总结
| 维度 | 优点 | 缺点 |
|---|---|---|
| 完备性 | 保证找到网格内的全局最优解 | 网格粒度影响结果精度,过粗可能遗漏最优 |
| 可解释性 | 结果直观,可绘制参数-绩效热力图 | 无法提供参数重要性排序 |
| 实现难度 | 代码简单,易于并行化 | 高维时计算成本不可接受 |
| 过拟合风险 | 配合交叉验证可降低过拟合 | 直接选取最优参数易导致样本内过拟合 |
最佳实践:先用粗粒度网格(大步长)快速定位参数大致区域,再在最优区域附近进行细粒度二次搜索。这种“粗搜+精搜”两阶段策略可大幅降低计算量。