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 实现步骤
网格搜索的实现步骤其实很清晰,我一般按下面这五步走:
- 定义参数空间:确定哪些参数需要优化,以及每个参数的取值范围和步长。
- 生成参数组合:用笛卡尔积生成所有可能的参数组合。
- 回测评估:对每组参数执行回测,计算评价指标。
- 结果排序:按评价指标从高到低排序,找出最优参数。
- 验证稳定性:对最优参数进行样本外测试,防止过拟合。
我曾经犯过一个错误,就是直接拿最优参数上实盘,结果亏得一塌糊涂。后来才明白,网格搜索找到的最优参数,很可能只是「过拟合」了历史数据。所以第五步的样本外验证,绝对不能省。
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开始,一步步往下走,最后到步骤5。我个人觉得,步骤5是最容易被忽略的,但恰恰是最重要的。
小技巧:如果你用的是 Jupyter Notebook,建议把网格搜索的结果保存到 CSV 文件里。这样下次调参时,可以直接加载历史结果,不用重新跑一遍。我经常这么干,省了不少时间。
好了,网格搜索的内容就这些。说白了,它就是个「笨办法」,但笨办法有时候反而最管用。尤其是参数少的时候,网格搜索比那些花里胡哨的优化算法靠谱得多。
公众号:蓝海资料掘金营,微信deep3321