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 网格搜索流程与复杂度可视化

下图展示了网格搜索的完整执行流程,以及不同参数维度下的计算复杂度对比:

网格搜索执行流程与复杂度对比 执行流程 定义参数网格 生成所有组合 (itertools.product) 循环遍历:回测 + 评估 汇总结果 DataFrame 排序输出最优参数 不同维度组合数对比 (每维10个值) k=2 100 k=3 1K k=4 10K k=5 100K k=6 1M k≥4 时组合数爆炸,建议改用随机搜索

4.5 网格搜索的优缺点总结

维度 优点 缺点
完备性 保证找到网格内的全局最优解 网格粒度影响结果精度,过粗可能遗漏最优
可解释性 结果直观,可绘制参数-绩效热力图 无法提供参数重要性排序
实现难度 代码简单,易于并行化 高维时计算成本不可接受
过拟合风险 配合交叉验证可降低过拟合 直接选取最优参数易导致样本内过拟合
最佳实践:先用粗粒度网格(大步长)快速定位参数大致区域,再在最优区域附近进行细粒度二次搜索。这种“粗搜+精搜”两阶段策略可大幅降低计算量。