2. 参数优化基础:参数优化的目标、过拟合与欠拟合、优化方法论
好,咱们进入第二章。这一章,说白了就是给策略「调参」打地基。
很多人一上来就写循环、跑网格,结果跑出来一堆漂亮曲线,实盘一跑就崩。为什么?因为没搞懂参数优化的本质。我个人习惯,在动手写代码之前,先花半小时想清楚三个问题:我要优化什么?怎么避免优化过头?用什么方法找最优解?
2.1 参数优化的目标:不只是找最高收益
参数优化,目标到底是什么?很多人第一反应是「让夏普比率最大」或者「让年化收益最高」。嗯,这没错,但太片面了。
我在项目中遇到过这样一个案例:一个价差回归策略,把参数调到极致,回测年化收益80%,夏普3.5。结果实盘第一个月就亏了12%。为什么?因为那个最优参数恰好拟合了历史数据中的某段特殊行情。
所以,参数优化的真正目标应该是:找到一组参数,让策略在「未来未知数据」上表现稳定,而不是在「过去已知数据」上刷出高分。
核心目标三要素:
- 稳健性:参数微调时,绩效不会剧烈波动
- 泛化能力:在不同市场环境下都能有效
- 可解释性:参数值背后有逻辑支撑,不是纯统计巧合
你想想看,如果一组参数只在某一年有效,换一年就失效,那它跟「蒙对」有什么区别?
2.2 过拟合与欠拟合:两个极端都要防
这两个概念,搞机器学习的同学应该很熟。但在量化交易里,它们有更具体的表现。
2.2.1 过拟合(Overfitting)
说白了就是「记性太好」。策略把历史数据里的噪声都记住了,换到新数据就懵了。
典型症状:
- 回测曲线完美向上,几乎不回撤
- 参数稍微变一点,绩效就大幅下滑
- 不同时间段回测结果差异巨大
避坑指南:我曾经犯过一个低级错误——用500个参数组合跑网格搜索,选出了夏普最高的那组。结果发现那个参数组合在样本外测试中直接腰斩。后来我才意识到,参数越多,过拟合的风险就越大。这就是所谓的「维数灾难」。
2.2.2 欠拟合(Underfitting)
欠拟合就是「记性太差」。策略连历史数据里的基本规律都没学到。
典型症状:
- 回测收益接近零,甚至跑不赢基准
- 参数怎么调,绩效都差不多
- 策略逻辑本身可能就有问题
怎么平衡?我个人习惯用「样本内 vs 样本外」测试。把数据分成两段:前70%用来找参数,后30%用来验证。如果样本外表现和样本内差不多,说明参数靠谱。如果样本外差很多,那八成是过拟合了。
2.3 优化方法论:三种主流方法
好,目标明确了,陷阱也知道了。那具体怎么找参数?有三种主流方法:网格搜索、随机搜索、贝叶斯优化。我一个个说。
2.3.1 网格搜索(Grid Search)
这是最基础的方法。说白了就是「穷举」——把所有可能的参数组合都试一遍。
# 网格搜索示例:优化价差回归策略的入场阈值和止损阈值
import numpy as np
import itertools
# 定义参数范围
entry_thresholds = np.arange(1.5, 3.5, 0.5) # 入场阈值:1.5, 2.0, 2.5, 3.0
stop_loss_thresholds = np.arange(0.5, 2.0, 0.5) # 止损阈值:0.5, 1.0, 1.5
best_sharpe = -np.inf
best_params = None
for entry, stop in itertools.product(entry_thresholds, stop_loss_thresholds):
# 假设 run_backtest 是回测函数
sharpe = run_backtest(entry_threshold=entry, stop_loss=stop)
if sharpe > best_sharpe:
best_sharpe = sharpe
best_params = (entry, stop)
print(f"最优参数:入场阈值={best_params[0]}, 止损阈值={best_params[1]}")
print(f"最优夏普比率:{best_sharpe:.2f}")
优点:简单、直观、一定能找到网格内的最优解。
缺点:计算量随参数数量指数增长。3个参数各10个值,就是1000次回测。5个参数就是10万次。
我的经验:网格搜索适合参数少(2-3个)、范围明确的情况。参数多了,别用,跑死你。
2.3.2 随机搜索(Random Search)
随机搜索不穷举,而是在参数空间里随机采样。听起来不靠谱?其实效果往往比网格搜索好。
# 随机搜索示例
import random
n_iterations = 100 # 采样100次
best_sharpe = -np.inf
best_params = None
for _ in range(n_iterations):
entry = random.uniform(1.5, 3.5) # 连续值,随机采样
stop = random.uniform(0.5, 2.0)
sharpe = run_backtest(entry_threshold=entry, stop_loss=stop)
if sharpe > best_sharpe:
best_sharpe = sharpe
best_params = (entry, stop)
print(f"随机搜索最优参数:入场阈值={best_params[0]:.2f}, 止损阈值={best_params[1]:.2f}")
为什么随机搜索有时比网格搜索好?因为网格搜索在「不重要的维度」上浪费了大量计算。而随机搜索能更均匀地覆盖整个参数空间。我做过对比:同样是100次回测,随机搜索找到的最优解往往比网格搜索更好。
2.3.3 贝叶斯优化(Bayesian Optimization)
这是目前最先进的方法。它不盲目搜索,而是「边试边学」——根据已有的结果,推测哪里最有可能找到最优参数。
# 贝叶斯优化示例(使用 scikit-optimize 库)
from skopt import gp_minimize
from skopt.space import Real
# 定义目标函数(要最小化的函数,所以取负夏普)
def objective(params):
entry, stop = params
sharpe = run_backtest(entry_threshold=entry, stop_loss=stop)
return -sharpe # 贝叶斯优化默认求最小值
# 定义参数空间
space = [Real(1.5, 3.5, name='entry_threshold'),
Real(0.5, 2.0, name='stop_loss')]
# 执行贝叶斯优化
result = gp_minimize(objective, space, n_calls=50, random_state=42)
best_entry = result.x[0]
best_stop = result.x[1]
best_sharpe = -result.fun
print(f"贝叶斯优化最优参数:入场阈值={best_entry:.2f}, 止损阈值={best_stop:.2f}")
print(f"最优夏普比率:{best_sharpe:.2f}")
核心思想:贝叶斯优化用高斯过程(Gaussian Process)来建模「参数 → 绩效」的关系。每次采样后,模型会更新,然后选择「最有潜力」的点去试。说白了,它知道哪里可能有好东西,就重点去挖那里。
三种方法对比:
| 方法 | 适用场景 | 计算量 | 找到最优解的概率 |
|---|---|---|---|
| 网格搜索 | 参数少(≤3个),范围明确 | 高(指数增长) | 高(在网格内) |
| 随机搜索 | 参数中等(3-6个) | 中(线性增长) | 中高 |
| 贝叶斯优化 | 参数多(≥5个),计算昂贵 | 低(智能采样) | 高(理论上) |
2.4 本章知识体系总览
说了这么多,我画张图帮你理清思路。这张图展示了参数优化的完整逻辑链条:从目标出发,避开过拟合和欠拟合的坑,再根据实际情况选择优化方法。
嗯,这张图把本章的核心逻辑串起来了。从上到下看:先明确目标,再警惕陷阱,最后选择合适的方法。每一步都踩实了,参数优化才不会变成「瞎猫碰死耗子」。
实战建议:我个人习惯先用随机搜索跑一轮,找到大概的「好区域」,再用贝叶斯优化精调。这样既省时间,又不容易陷入局部最优。你可以试试看。
公众号:蓝海资料掘金营,微信deep3321