10. 策略优化技术

10.策略优化技术

策略开发过程中,一个让人头疼的问题是:到底该用 50 周期的均线还是 51 周期?止损设 1.5% 还是 2%?这些参数的选择往往决定了策略的生死。手动逐个测试不仅耗时,而且容易遗漏最优组合。Jesse 的优化模式就是为了解决这个问题而生,它能系统性地搜索参数空间,找出表现最佳的配置。

不过,优化模式不是魔法棒。它最适合用来微调已经具备盈利能力的策略,而不是把亏损策略变成赚钱机器。理解这一点很重要,否则容易陷入过度优化的陷阱。

定义优化参数

在 Jesse 中,可优化的参数被称为"超参数"(hyperparameters)。定义这些参数是优化的第一步。

从硬编码到超参数

传统写法中,指标周期直接写在代码里:

@property
def slow_sma(self):
    return ta.sma(self.candles, 200)

@property
def fast_sma(self):
    return ta.sma(self.candles, 50)

这种方式的问题在于,每次想测试不同周期都得改代码。更好的做法是使用超参数:

def hyperparameters(self):
    return [
        {'name': 'slow_sma_period', 'type': int, 'min': 150, 'max': 210, 'default': 200},
        {'name': 'fast_sma_period', 'type': int, 'min': 20, 'max': 100, 'default': 50},
    ]

然后在策略中这样访问:

@property
def slow_sma(self):
    return ta.sma(self.candles, self.hp['slow_sma_period'])

@property
def fast_sma(self):
    return ta.sma(self.candles, self.hp['fast_sma_period'])

self.hp 是 Jesse 自动注入的属性,包含了所有超参数的值。回测时,Jesse 会使用默认值,所以结果和硬编码版本一致。优化时,Jesse 会在 min 和 max 范围内搜索最佳值。

参数类型详解

Jesse 支持四种参数类型,灵活应对不同场景。

整数参数

最常用的类型,适合指标周期、阈值等离散值。

{'name': 'rsi_period', 'type': int, 'min': 5, 'max': 30, 'default': 14, 'step': 1}

关键字段:

  • name:参数名称,后续通过 self.hp['rsi_period'] 访问
  • min:最小值(包含)
  • max:最大值(包含)
  • default:默认值
  • step:步长,可选,默认为 1

step 参数很实用。比如想让周期只能是 5 的倍数,可以设 step: 5,这样优化器只会测试 5、10、15...这样的值,减少无意义的搜索。

浮点数参数

处理小数,适合百分比、价格偏移等连续值。

{'name': 'entry_threshold', 'type': float, 'min': 0.1, 'max': 5.0, 'default': 1.0, 'step': 0.1}

浮点数的 step 是可选的。如果不指定,优化器会在范围内尝试任意小数;如果指定了 step,则按步长离散化。对于 entry_threshold 这种参数,设 step 为 0.1 通常足够,没必要精确到 0.01。

分类参数

当参数不是数值,而是一组选项时,用分类类型。

{'name': 'indicator_type', 'type': 'categorical', 'options': ['rsi', 'macd', 'bollinger'], 'default': 'rsi'}

这允许优化器在多个指标间选择。策略代码中可以这样写:

def before(self):
    if self.hp['indicator_type'] == 'rsi':
        self.indicator = ta.rsi(self.candles, 14)
    elif self.hp['indicator_type'] == 'macd':
        self.indicator = ta.macd(self.candles)
    else:
        self.indicator = ta.bollinger_bands(self.candles)

分类参数的强大之处在于,它能优化策略结构本身,而不仅仅是数值。

布尔参数

布尔值可以用两种方式实现。

方法一:用分类类型

{'name': 'use_filter', 'type': 'categorical', 'options': [True, False], 'default': False}

方法二:用整数类型

{'name': 'use_filter', 'type': int, 'min': 0, 'max': 1, 'default': 0}

然后在代码中转换:

if bool(self.hp['use_filter']):
    # 应用过滤器逻辑
    pass

方法二更简洁,但方法一更直观。选择哪种取决于个人偏好。

参数命名建议

命名要清晰且具有描述性。slow_sma_period 比 p1 好得多,因为三个月后回头看代码,还能立刻明白含义。另外,避免使用 Python 关键字如 type、min、max 作为参数名,虽然 Jesse 能处理,但容易引起混淆。

执行优化模式

定义好超参数后,就可以运行优化了。操作方式与回测类似,但有一些重要区别需要注意。

基本操作流程

在 Dashboard 的优化页面,选择策略、交易对、时间框架,然后设置训练期和测试期。点击开始,Jesse 会自动搜索最佳参数组合。

优化过程中,界面会显示进度条、最佳试验表和目标函数曲线图。可以实时监控搜索进展,看到性能如何随试验次数提升。

关键设置详解

训练期与测试期划分

优化模式要求必须设置训练期和测试期,这是防止过拟合的核心机制。

  • 训练期:优化器在此区间内搜索参数,占数据的大部分,通常 70-80%
  • 测试期:优化器用未见过的数据验证参数效果,占剩余 20-30%

这种划分强制策略必须在两个时期都表现良好才算优秀。如果参数只在训练期有效,测试期表现会很差,优化器会自动淘汰这类组合。

举个例子,假设有 2020-2023 年的数据:

  • 训练期:2020-01-01 到 2022-06-30(30 个月)
  • 测试期:2022-07-01 到 2023-12-31(6 个月)

优化器会在训练期尝试数百种参数组合,然后用测试期验证。最终推荐的参数必须在两个时期都有稳健表现。

最优交易次数

这是一个极易被忽视但极其重要的参数。它告诉 Jesse 在该时间段内,策略应该产生多少笔交易才算合理。

为什么重要?考虑一个趋势跟踪策略,在 6 小时周期上通常一年交易 30-60 次。如果某组参数一年只交易 5 次,但胜率 100%,这可信吗?不可信。样本量太小,统计意义不足,很可能是过拟合的偶然结果。

设置最优交易次数可以过滤掉这类不靠谱的参数组合。如果设定期望值为 60,优化器会优先选择交易次数接近 60 的组合,淘汰那些交易次数过少的结果。

设置建议:取策略正常交易次数范围的上限。如果通常一年交易 30-60 次,就设为 60。设得稍高没问题,但设得太低(如 10)会破坏优化计算。

CPU 核心配置

Jesse 的优化系统基于 Ray 框架,支持多核并行处理。分配的 CPU 核心越多,优化速度越快。

在 Dashboard 的设置页面,可以调整优化模式的 CPU 核心数。对于 8 核 CPU,可以分配 6 核给优化任务,留 2 核保证系统流畅运行。

需要注意的是,并行加速存在边际效应。从 1 核到 4 核提升明显,但从 16 核到 32 核可能提升有限,因为参数搜索本身有串行依赖。对于大多数用户,4-8 核是性价比最高的选择。

目标函数选择

优化器需要明确目标:到底优化什么指标?Jesse 默认使用夏普比率(Sharpe Ratio),但可以在设置中更改:

  • 夏普比率:风险调整后收益,最常用
  • 卡尔马比率:收益与最大回撤的比值,适合回撤敏感型策略
  • 索提诺比率:类似夏普,但只惩罚下行波动
  • 欧米伽比率:收益与损失的概率加权比
  • 宁静指数:Jesse 自定义指标,平衡多因素

选择哪个指标取决于策略类型和风险偏好。趋势策略通常用夏普或卡尔马,套利策略可能更适合索提诺。可以在几次试验后,观察哪个指标与主观评估最吻合。

优化何时结束?

优化在达到预设的试验次数后自动结束。但实际操作中,不需要等到结束。当看到最佳试验表的排名稳定,目标函数曲线趋于平坦时,就可以手动停止。

优化器会保存每次试验的参数和性能指标。即使提前停止,已经找到的好参数不会丢失。可以在最佳试验表中选择排名靠前的几个组合,进一步验证。

重要限制与建议

优化模式有一些限制:

  • 只能有一个交易路由(但可以有多条额外路由用于指标计算)
  • 时间周期不能太短,至少几个月的数据才有意义
  • 优化前确保策略已有一定盈利能力,优化是微调而非改造

另外,优化结果不保证在未来有效。市场结构变化可能导致参数失效,所以需要定期重新优化或采用更稳健的参数。

DNA 结果应用

优化完成后,最佳试验表会显示表现最好的参数组合。有两种方式将这些结果应用到策略中。

方法一:DNA 字符串(传统方式)

DNA 字符串是 Jesse 早期版本使用的方式,将一组参数编码成短字符串,如 t4、aB3c。这种方式兼容性好,操作简单。

复制 DNA

在最佳试验表中找到目标试验,点击旁边的"复制"按钮,DNA 字符串会自动复制到剪贴板。

在策略中实现

在策略类中定义 dna() 方法:

def dna(self):
    return 't4'  # 替换为实际复制的 DNA 字符串

当策略包含 dna() 方法时,Jesse 会忽略 hyperparameters() 中的默认值,转而使用 DNA 编码的参数值。运行回测或实盘时,策略会自动加载这些优化后的参数。

这种方式的优点是简洁,一行代码就能应用优化结果。缺点是可读性差,看代码时无法直接知道具体参数值,必须回到优化结果中查找。

方法二:直接参数值(推荐方式)

更透明的方式是直接查看参数值,然后更新到策略中。

查看参数详情

点击试验旁的"信息"按钮,会显示该试验的所有指标和具体参数值。可以看到每个超参数的最终优化值,如 slow_sma_period: 180、fast_sma_period: 42。

更新策略代码

将这些值直接更新到 hyperparameters() 的 default 字段:

def hyperparameters(self):
    return [
        {'name': 'slow_sma_period', 'type': int, 'min': 150, 'max': 210, 'default': 180},  # 从 200 改为 180
        {'name': 'fast_sma_period', 'type': int, 'min': 20, 'max': 100, 'default': 42},    # 从 50 改为 42
    ]

这样,代码本身就记录了优化后的参数,任何人阅读都能清楚知道具体配置。这也是版本控制的友好做法,Git 提交时能明确看到参数变化。

验证优化结果

无论用哪种方法,验证都是必不可少的步骤。优化器使用的训练期和测试期已经"见过"数据,需要第三个验证期来确认结果的稳健性。

验证期应该是优化时未使用的数据段。例如,如果优化用了 2020-2022 年的数据,验证就用 2023 年的数据。在验证期运行回测,观察性能是否保持稳定。

如果验证期表现与训练/测试期差异很大,说明存在过拟合,需要重新优化或调整参数范围。只有三个时期都表现良好的参数,才值得用于实盘。

CPU 资源配置

优化是计算密集型任务,合理配置 CPU 资源能显著缩短时间。

核心分配策略

在 Dashboard 的设置页面,找到优化模式配置项。默认使用单核,可以根据机器配置调整。

对于 4 核 CPU,建议分配 3 核;对于 16 核 CPU,分配 12-14 核即可。保留部分核心给系统和其他应用,避免机器卡死。

需要注意的是,Jesse 的优化器基于 Ray,启动时会创建多个 Python 进程。每个进程都有内存开销,所以核心数不是越多越好。如果策略占用内存较大(如使用了大量历史数据),过多进程可能导致内存不足。

性能监控

优化时,可以打开系统监控工具观察 CPU 和内存使用率。如果 CPU 持续 100% 但内存还有余量,可以适当增加核心数。如果内存已接近上限,应该减少核心数或优化策略代码,减少内存占用。

对于云服务器,还需要考虑成本。更多核心意味着更高费用,需要在时间和成本间权衡。通常,在非交易时段运行 overnight 优化,用较少核心也能接受。

分布式优化

对于机构用户,Jesse 支持 Ray 的分布式集群。可以在多台机器上部署 Ray 集群,将优化任务分布到数十甚至数百核上。配置稍显复杂,需要设置 Ray 的 head 节点和 worker 节点,但在 Dashboard 中操作界面保持一致。

普通用户用单机多核已经足够。一个包含 5 个参数、每个参数 20 个取值空间的优化任务,在 8 核机器上通常 1-2 小时就能完成。

优化实践建议

基于实际使用经验,这里总结一些最佳实践。

参数范围设置

min 和 max 的范围不宜过大。比如均线周期,设 20-100 比 1-500 更合理。过大的范围会让优化器浪费时间在明显不靠谱的区域。

可以先手动回测几个极端值,确认策略在这些值下能正常运行,再设范围。如果周期为 1 时策略崩溃,就不该把 1 放进 min。

步长选择

step 不是越小越好。step 为 1 时,优化器会测试 20、21、22...这样的连续值。但对于均线周期,21 和 22 的差异通常没有统计意义,反而增加计算量。

根据参数特性设置 step。均线周期可以设 step 为 5 或 10,百分比阈值可以设 step 为 0.5 或 1.0。

分批优化

参数太多时,一次性优化所有参数效率低。可以采用分批策略:先优化最重要的 2-3 个参数,固定其他参数;找到最优值后,再优化次重要的参数。

这种方法虽然可能错过某些参数间的复杂交互,但通常能快速找到不错的解。对于 10 个以上参数的复杂策略,分批优化几乎是必需的。

记录与复现

每次优化后,保存以下信息:

  • 使用的数据时间段
  • 训练/测试/验证期划分
  • 超参数定义
  • 最佳试验的 DNA 和具体值
  • 验证期回测结果

这些信息在后续迭代中至关重要。几个月后想复现或调整策略,没有记录将无从下手。

总结

策略优化是量化交易中的重要环节,Jesse 提供了强大而易用的工具。从定义超参数到执行优化,再到应用结果,整个流程设计得相当流畅。

关键在于理解优化的本质:它是基于历史数据的参数搜索,不是预测未来的水晶球。过度优化是最大的风险,训练/测试/验证三期划分是防御手段。

下一章将深入探讨过拟合的预防与验证,介绍更严谨的方法如蒙特卡洛分析,帮助构建真正稳健的策略。优化找到好参数只是第一步,证明这些参数在未来依然有效才是终极目标。