3、网格搜索法:全网格搜索原理、实现步骤、优缺点分析、Python代码实现

说到参数优化,很多新手第一反应就是「凭感觉调」。我刚开始做量化那会儿也这样,调个均线参数能折腾一整天。后来发现,这玩意儿其实有更科学的玩法——网格搜索。

说白了,网格搜索就是暴力枚举。你把所有可能的参数组合列出来,挨个跑一遍回测,最后挑出表现最好的那组。听起来简单粗暴,但实际用起来,效果往往比凭经验瞎调好得多。

3.1 全网格搜索的原理

全网格搜索的核心思想,就是穷举。你想想看,如果我们有两个参数需要优化,比如双均线策略的短期均线周期 short_window 和长期均线周期 long_window

假设 short_window 的取值范围是 [5, 10, 15, 20],long_window 的取值范围是 [30, 40, 50, 60]。那么所有可能的组合就是 4 × 4 = 16 种。

网格搜索会把这 16 种组合全部跑一遍,然后根据你设定的评价指标(比如夏普比率、年化收益率、最大回撤等),选出最优的那组参数。

核心公式:总组合数 = 参数1的取值个数 × 参数2的取值个数 × ... × 参数N的取值个数

举个例子:3个参数,每个参数取10个值,总组合数 = 10 × 10 × 10 = 1000 种。

嗯,这里要注意。组合数会随着参数数量和取值个数呈指数级增长。这就是所谓的「维度灾难」。我在项目中遇到过,有一次优化一个5参数的策略,每个参数取20个值,总组合数直接飙到 320 万种。单机跑的话,得跑好几天。

3.2 实现步骤

网格搜索的实现步骤其实很清晰,我一般按下面这五步走:

  1. 定义参数空间:确定哪些参数需要优化,以及每个参数的取值范围和步长。
  2. 生成参数组合:用笛卡尔积生成所有可能的参数组合。
  3. 回测评估:对每组参数执行回测,计算评价指标。
  4. 结果排序:按评价指标从高到低排序,找出最优参数。
  5. 验证稳定性:对最优参数进行样本外测试,防止过拟合。

我曾经犯过一个错误,就是直接拿最优参数上实盘,结果亏得一塌糊涂。后来才明白,网格搜索找到的最优参数,很可能只是「过拟合」了历史数据。所以第五步的样本外验证,绝对不能省。

3.3 优缺点分析

优点 缺点
实现简单,容易理解 计算量大,参数多时效率低
一定能找到全局最优(在给定参数空间内) 无法处理连续参数,只能离散化
结果可复现,便于调试 容易过拟合历史数据
适合参数较少的场景(≤3个) 参数多时组合数爆炸

我的建议:网格搜索最适合参数不超过3个的场景。如果参数超过3个,我建议先用随机搜索或贝叶斯优化缩小范围,再用网格搜索精细调优。

3.4 Python代码实现

下面是我写的一个网格搜索的完整实现。代码里我用了双均线策略作为例子,你可以直接替换成自己的策略逻辑。

import numpy as np
import pandas as pd
from itertools import product

def grid_search(data, short_range, long_range):
    """
    全网格搜索函数
    :param data: DataFrame,包含 'close' 列
    :param short_range: 短期均线参数范围,如 [5, 10, 15, 20]
    :param long_range: 长期均线参数范围,如 [30, 40, 50, 60]
    :return: 最优参数组合及对应的夏普比率
    """
    best_sharpe = -np.inf
    best_params = None
    results = []
    
    # 生成所有参数组合
    for short, long in product(short_range, long_range):
        # 跳过短期大于等于长期的情况
        if short >= long:
            continue
        
        # 计算均线
        data['short_ma'] = data['close'].rolling(window=short).mean()
        data['long_ma'] = data['close'].rolling(window=long).mean()
        
        # 生成交易信号
        data['signal'] = 0
        data.loc[data['short_ma'] > data['long_ma'], 'signal'] = 1
        data.loc[data['short_ma'] <= data['long_ma'], 'signal'] = -1
        
        # 计算收益率
        data['returns'] = data['close'].pct_change()
        data['strategy_returns'] = data['signal'].shift(1) * data['returns']
        
        # 计算夏普比率
        sharpe = np.sqrt(252) * data['strategy_returns'].mean() / data['strategy_returns'].std()
        
        results.append({
            'short_window': short,
            'long_window': long,
            'sharpe_ratio': sharpe
        })
        
        # 更新最优参数
        if sharpe > best_sharpe:
            best_sharpe = sharpe
            best_params = (short, long)
    
    # 转换为DataFrame便于查看
    results_df = pd.DataFrame(results)
    results_df = results_df.sort_values('sharpe_ratio', ascending=False)
    
    return best_params, best_sharpe, results_df

# 使用示例
if __name__ == "__main__":
    # 模拟价格数据
    np.random.seed(42)
    dates = pd.date_range('2020-01-01', periods=1000)
    prices = 100 + np.cumsum(np.random.randn(1000) * 0.5)
    data = pd.DataFrame({'close': prices}, index=dates)
    
    # 定义参数范围
    short_range = [5, 10, 15, 20, 25]
    long_range = [30, 40, 50, 60, 70]
    
    # 执行网格搜索
    best_params, best_sharpe, results_df = grid_search(data, short_range, long_range)
    
    print(f"最优参数: short_window={best_params[0]}, long_window={best_params[1]}")
    print(f"最优夏普比率: {best_sharpe:.4f}")
    print("\n前5名参数组合:")
    print(results_df.head())

避坑指南:我曾经在代码里忘记跳过 short >= long 的情况,结果跑出来一堆无效组合。另外,回测时一定要用 shift(1) 处理信号,避免未来函数。这个坑我踩过不止一次。

3.5 可视化结果

光看数字不够直观,我习惯把网格搜索的结果画成热力图。这样一眼就能看出哪些参数区域表现好。

import matplotlib.pyplot as plt
import seaborn as sns

def plot_grid_results(results_df):
    """
    绘制网格搜索结果热力图
    """
    pivot_table = results_df.pivot_table(
        values='sharpe_ratio', 
        index='short_window', 
        columns='long_window'
    )
    
    plt.figure(figsize=(10, 8))
    sns.heatmap(pivot_table, annot=True, fmt='.3f', cmap='RdYlGn')
    plt.title('网格搜索 - 夏普比率热力图')
    plt.xlabel('长期均线窗口')
    plt.ylabel('短期均线窗口')
    plt.show()

# 调用可视化
plot_grid_results(results_df)

热力图上颜色越绿,代表夏普比率越高。你会发现,最优参数往往集中在某个区域,而不是孤立的点。这其实是个好信号,说明策略对参数有一定的鲁棒性。

3.6 核心逻辑流程图

下面我用 SVG 画了一张流程图,把网格搜索的整个流程串起来。你一看就明白了。

网格搜索核心流程 步骤1:定义参数空间 步骤2:生成参数组合(笛卡尔积) 步骤3:循环回测每组参数 步骤4:计算评价指标(夏普比率等) 步骤5:排序选优 + 样本外验证 注意:步骤5的样本外验证是防止过拟合的关键

这张图把网格搜索的五个步骤串起来了。你从步骤1开始,一步步往下走,最后到步骤5。我个人觉得,步骤5是最容易被忽略的,但恰恰是最重要的。

小技巧:如果你用的是 Jupyter Notebook,建议把网格搜索的结果保存到 CSV 文件里。这样下次调参时,可以直接加载历史结果,不用重新跑一遍。我经常这么干,省了不少时间。

好了,网格搜索的内容就这些。说白了,它就是个「笨办法」,但笨办法有时候反而最管用。尤其是参数少的时候,网格搜索比那些花里胡哨的优化算法靠谱得多。

公众号:蓝海资料掘金营,微信deep3321