第 7 章 · 04 高级优化与产出


文档摘要

第 7 章 · 04 高级优化与产出 本节摘要:本节是 Hyperopt 章的收尾,讲三块进阶内容。第一是采样器选择——freqtrade 默认用 NSGAIIISampler(多目标遗传算法),但你可以在策略里通过 内嵌类改用 TPESampler、CmaEsSampler、GPSampler 等,不同采样器适合不同的搜索空间形状。第二是产出怎么用——Hyperopt 跑完会自动生成 JSON 参数文件(策略名.json),下次回测/Dry-Run 自动加载;也可以把结果手动拷进策略类的 字典;还要理解配置 > 参数文件 > 类内 > default 的优先级链。

第 7 章 · 04 高级优化与产出

本节摘要:本节是 Hyperopt 章的收尾,讲三块进阶内容。第一是采样器选择——freqtrade 默认用 NSGAIIISampler(多目标遗传算法),但你可以在策略里通过 HyperOpt 内嵌类改用 TPESampler、CmaEsSampler、GPSampler 等,不同采样器适合不同的搜索空间形状。第二是产出怎么用——Hyperopt 跑完会自动生成 JSON 参数文件(策略名.json),下次回测/Dry-Run 自动加载;也可以把结果手动拷进策略类的 *_params 字典;还要理解配置 > 参数文件 > 类内 > default 的优先级链。第三是避免过拟合的实战清单——这是 Hyperopt 最危险的陷阱,包括样本外验证(训练/测试切分)、参数空间尽量小、多 random-state 交叉验证、警惕「Objective 已在此点评估过」警告等。读完本节,你能把一次 Hyperopt 的产出安全地变成可上线的策略参数。

内容来源:原项目文档 docs/advanced-hyperopt.mddocs/hyperopt.md(Understand results / Reproducible / OOM),汉化并套用体系化模板。

⚠️ 风险提示:Hyperopt 找到的是「历史最优」参数,极易过拟合。一个在历史数据上 Sharpe=5 的策略,实盘可能 Sharpe=0.5。本节的「避免过拟合清单」不是可选项,是必读。

学习目标

阅读完本节,你应当能够:

  1. 区分 Optuna 的采样器(NSGAIII、TPE、CmaEs、QMCSampler)及适用场景。
  2. 在策略里用 HyperOpt 内嵌类切换采样器或覆盖预定义 space。
  3. 把 Hyperopt 产出导入策略(自动 JSON / 手动 *_params)。
  4. 说清参数的优先级链(config > 参数文件 > 类内 > default)。
  5. 应用避免过拟合清单(样本外验证、小空间、多随机种子)。

一、采样器:Bayesian 优化的具体算法

第 01 节说过,Hyperopt 用 Bayesian 优化——但「Bayesian」是一大类,具体用哪种采样器决定它怎么选下一组参数。freqtrade 默认用 NSGAIIISampler(多目标遗传算法),它被证明「最通用」。

内置可选项

在策略里嵌一个 HyperOpt 内部类,用 generate_estimator() 切换采样器:

class MyStrategy(IStrategy): class HyperOpt: def generate_estimator(dimensions, **kwargs): return "TPESampler" # 或其他字符串

可选值:

采样器 特点 适用
NSGAIIISampler(默认) 多目标遗传算法,通用 多数场景,多目标优化
TPESampler Tree-structured Parzen Estimator,单目标经典 单目标、参数空间结构化
CmaEsSampler 协方差矩阵自适应进化 连续参数空间
GPSampler 高斯过程贝叶斯优化 小空间、昂贵评估
QMCSampler 准随机蒙特卡洛 需要均匀覆盖空间
NSGAIISampler NSGA-III 的前代 多目标,旧版

💡 不懂就别换:官方明确「While custom estimators can be provided, it's up to you to do research」。不确定就用默认 NSGAIIISampler,它已经过充分验证。

用 Optunahub 的 AutoSampler

更激进的话,可以从 Optunahub 加载第三方采样器:

import optunahub class MyStrategy(IStrategy): class HyperOpt: def generate_estimator(dimensions, **kwargs): if "random_state" in kwargs: return optunahub.load_module("samplers/auto_sampler").AutoSampler(seed=kwargs["random_state"]) return optunahub.load_module("samplers/auto_sampler").AutoSampler()

需要 pip install optunahub cmaes torch scipy。AutoSampler 会自动选合适的采样器,但依赖重。

二、覆盖预定义 Space

freqtrade 为 roi / stoploss / trailing / max_open_trades 内置了默认搜索空间(比如 stoploss 默认在 -0.35 ~ -0.02 之间搜)。如果你想改这些范围,在 HyperOpt 内嵌类里覆盖对应方法:

from freqtrade.optimize.space import Categorical, Integer, SKDecimal, Dimension from typing import List class MyStrategy(IStrategy): class HyperOpt: # 自定义 stoploss 搜索范围(-0.05 ~ -0.01,比默认 -0.35 ~ -0.02 更窄) def stoploss_space(): return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')] # 自定义 ROI 搜索空间 + 生成方式(从参数拼成 ROI 字典) def roi_space() -> List[Dimension]: return [ Integer(10, 120, name='roi_t1'), Integer(10, 60, name='roi_t2'), Integer(10, 40, name='roi_t3'), SKDecimal(0.01, 0.04, decimals=3, name='roi_p1'), SKDecimal(0.01, 0.07, decimals=3, name='roi_p2'), SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'), ] def generate_roi_table(params): roi_table = { 0: params['roi_p1'] + params['roi_p2'] + params['roi_p3'], params['roi_t3']: params['roi_p1'] + params['roi_p2'], params['roi_t3'] + params['roi_t2']: params['roi_p1'], params['roi_t3'] + params['roi_t2'] + params['roi_t1']: 0, } return roi_table # trailing_space() / max_open_trades_space() 同理覆盖

所有覆盖都是可选的,可以混搭——只改你需要的那个。trailing_space()max_open_trades_space() 同理,完整签名见 docs/advanced-hyperopt.md

Space 类型(进阶)

freqtrade.optimize.space 提供的空间类型:

类型 等同于 用途
Categorical skopt Categorical 枚举
Integer skopt Integer 整数
Real skopt Real 全精度小数(少用)
SKDecimal freqtrade 自定义 限位数小数(推荐)

💡 SKDecimal vs Real:SKDecimal(0.10, 0.15, decimals=2) 只有 6 个候选值(0.10~0.15),而 Real(0.10, 0.15) 有几乎无限个。用 SKDecimal 能让搜索空间可控、避免过拟合、跑得快。

三、产出怎么用:JSON 自动加载

Hyperopt 跑完后,最佳参数会自动写入策略旁的 JSON 文件——策略叫 MyStrategy.py,文件就是 MyStrategy.json:

{ "buy_params": { "buy_adx": 44, "buy_rsi": 29, "buy_adx_enabled": false, "buy_rsi_enabled": true, "buy_trigger": "bb_lower" }, "sell_params": { "sell_rsi": 70 }, "roi_params": { "0": 0.10674, "21": 0.09158, "78": 0.03634, "118": 0 }, "stoploss": -0.27996 }

下次跑回测或 Dry-Run 时,freqtrade 自动加载这个 JSON,把里面的值注入到对应参数的 .value你什么都不用做

手动拷贝(可选)

如果你想把这些值「写死」在策略里(便于版本管理),可以把 *_params 拷成策略类的类属性:

class MyStrategy(IStrategy): # 从 Hyperopt 结果拷过来 buy_params = { 'buy_adx': 44, 'buy_rsi': 29, 'buy_adx_enabled': False, 'buy_rsi_enabled': True, 'buy_trigger': 'bb_lower', } sell_params = { 'sell_rsi': 70, } minimal_roi = { 0: 0.10674, 21: 0.09158, 78: 0.03634, 118: 0, } stoploss = -0.27996

注意拷完后,把同名的 JSON 文件删掉或重命名,否则 JSON 会覆盖类内的值。

优先级链(必记)

参数值的生效优先级(高 → 低):

config.json > 策略 JSON 参数文件 > 策略类内的 *_params > 参数声明时的 default

也就是说:

  • config.json 里写了 stoploss,那 Hyperopt 找的 stoploss 不生效。
  • JSON 参数文件存在,会覆盖策略类里手写的 *_params
  • 都没有,用声明时的 default=

⚠️ 常见困惑:Hyperopt 找到了 stoploss=-0.28,但回测时好像没用上?先查:(1) JSON 文件在不在;(2) config.json 里有没有 stoploss 覆盖;(3) 参数声明的 load 是不是 False

四、--disable-param-export:别自动写 JSON

如果你不想 Hyperopt 自动覆盖已有的 JSON(比如你在多个时间段分别优化、想手动挑),加 --disable-param-export:

freqtrade hyperopt --strategy MyStrategy --disable-param-export -e 500

这样结果只打印在终端、写进 hyperopt 结果文件,不动策略旁的 JSON。hyperopt-show 命令同样支持这个开关。

五、查看历史结果:hyperopt-list / hyperopt-show

跑完多次 Hyperopt 后,所有结果存在 user_data/hyperopt_results/ 下。用这两个命令回看:

# 列出某次运行的所有 epoch freqtrade hyperopt-list --best # 看某个 epoch 的详情 freqtrade hyperopt-show --best freqtrade hyperopt-show -n 42 # 第 42 个 epoch

可以用 --hyperopt-filename 指定读取哪个历史结果文件。

六、避免过拟合实战清单(最重要)

Hyperopt 最大的陷阱是过拟合:参数完美拟合了历史,却对未知未来毫无预测力。一份必读清单:

1. 样本外验证(训练/测试切分)

永远不要用全部数据做 Hyperopt。切成两段:

# 训练集(优化) freqtrade hyperopt --strategy MyStrategy --timerange 20240101-20250101 -e 500 # 测试集(验证,不参与优化) freqtrade backtesting --strategy MyStrategy --timerange 20250101-20260101

如果训练集 Sharpe=3、测试集 Sharpe=0.5,就是过拟合了。可接受的标志是测试集表现是训练集的 50% 以上且仍为正。

2. 参数空间尽量小

IntParameter(5, 20)IntParameter(1, 200) 过拟合风险小得多——空间越大,「碰巧在历史上好」的参数组合越多。先用领域知识把范围收窄,再让 Hyperopt 在小空间里精调。

3. 多 random-state 交叉验证

用不同随机种子多跑几轮,看结果是否稳定:

freqtrade hyperopt --strategy MyStrategy -e 500 --random-state 1 freqtrade hyperopt --strategy MyStrategy -e 500 --random-state 2 freqtrade hyperopt --strategy MyStrategy -e 500 --random-state 3

如果三轮找出的参数相近,说明稳健;如果差异巨大,说明结果是「碰运气」。

4. 警惕「Objective 已在此点评估过」

日志里大量出现 The objective has been evaluated at this point before.,说明搜索空间已被穷尽——所有点都试过了,再跑也是这个结果。例如 IntParameter(5, 20) 只有 15 个值,跑 500 epoch 毫无意义。扩大范围或减少 epoch。

5. 交易数要够

少于 50~100 笔交易的回测,统计意义可疑。Hyperopt 找出的「3 笔全胜」参数,实盘可能直接亏。用损失函数里的「交易数惩罚」(MultiMetric 自带)或在自定义损失里加。

6. 别同时优化所有空间

分阶段优化,先粗后细:--spaces buysellroi stoplosstrailing,每阶段几百 epoch。同时优化所有空间,维度爆炸,过拟合风险倍增。

7. 优化完务必回测验证

把产出导入策略后,用完全相同的配置回测,确认结果一致。不一致时排查:参数是否放在了 populate_indicators(第 02 节的坑)、JSON 是否加载、config 是否有意外覆盖(stoploss/max_open_trades/trailing_stop)、是否有意外参数文件。

8. 最后回到第 6 章

优化后的策略,必须重新跑 lookahead-analysisrecursive-analysis。Hyperopt 有可能找到「恰好作弊」的参数组合——只有通过了第 6 章的全部诊断,才考虑上线。

七、OOM 与性能调优

Hyperopt 极耗内存(每个并行进程都加载全量数据)。OOM 应对手段,按优先级:

  1. 减少交易对(-p 限定)。
  2. 缩短 timerange
  3. 避免 --timeframe-detail(加载额外数据)。
  4. 减小 -j(并行进程数)。
  5. 加内存(最直接)。
  6. --analyze-per-epoch(用 .range 多时)。

本节要点回顾

  1. 采样器:默认 NSGAIIISampler(通用);在 HyperOpt 内嵌类用 generate_estimator() 切换;不懂就别换。
  2. 覆盖 space:HyperOpt 内嵌类覆盖 roi_space / stoploss_space / trailing_space / max_open_trades_space / generate_roi_table;用 SKDecimal 限位数。
  3. JSON 自动加载:Hyperopt 写策略旁 <策略名>.json,回测/Dry-Run 自动注入;可手动拷成类内 *_params
  4. 优先级链:*config > 参数文件 > 类内 _params > default;Hyperopt 结果「没用上」先查这条链。
  5. --disable-param-export:不自动写 JSON,便于多段优化手动挑。
  6. hyperopt-list / hyperopt-show:回看历史结果。
  7. 避免过拟合清单:样本外切分、小空间、多 random-state、警惕空间饱和、交易数≥50、分阶段优化、优化后回测对齐、最后回第 6 章做 lookahead/recursive。
  8. OOM:减对/缩短/避 timeframe-detail/降 -j/加内存/--analyze-per-epoch。

至此第 7 章结束。下一章(第 8 章)我们进入 FreqAI——用机器学习模型预测价格方向,把策略从「规则驱动」推向「数据驱动」。


发布者: 作者: 灏天文库 转发
评论区 (0)
U