超参数优化
本页解释如何通过寻找最佳参数来调整您的策略,这一过程称为超参数优化。
机器人使用 optuna 包中的算法来实现这一点。搜索将占用所有 CPU 核心,使您的笔记本电脑听起来像喷气式飞机,并且仍然需要很长时间。
通常,寻找最佳参数的过程从一些随机组合开始(有关更多详细信息,请参阅下文),然后使用 optuna 的采样器算法(目前是 TPESampler)快速找到在搜索超空间中最小化损失函数值的参数组合。
安装超参数优化依赖项
由于超参数优化的依赖项不是运行机器人本身所需的,因此默认情况下不会安装。
在运行超参数优化之前,您需要安装相应的依赖项。
ARM 设备
由于超参数优化是一个资源密集型的过程,不建议也不支持在 ARM 设备(如树莓派)上运行。
Docker
Docker 镜像已包含超参数优化的依赖项,无需进一步操作。
本地安装
source .venv/bin/activate
pip install -r requirements-hyperopt.txt
超参数优化命令
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[--recursive-strategy-search] [--freqaimodel NAME]
[--freqaimodel-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,feather,parquet}]
[--max-open-trades INT] [--stake-amount STAKE_AMOUNT]
[--fee FLOAT] [--hyperopt-loss NAME] [--hyperopt-path PATH]
[-e INT] [--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]]
[--print-all] [--print-colorized] [--print-json]
[--export-csv FILE] [--random-state INT] [--min-trades INT]
[--hyperopt-filename FILENAME] [--eps]
[--dmmp] [--enable-protections] [--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL] [-j JOBS]
主要参数
--hyperopt-loss NAME- 指定超参数优化损失类-e INT, --epochs INT- 指定优化迭代次数--spaces- 指定要优化的参数空间--min-trades INT- 过滤结果的最小交易数--print-all- 打印所有结果,不仅仅是最佳结果--print-colorized- 彩色输出结果--export-csv FILE- 将结果导出到 CSV 文件--random-state INT- 设置随机种子以获得可重现的结果-j JOBS, --job-workers JOBS- 并行工作进程数
基本超参数优化
简单示例
# 基本超参数优化
freqtrade hyperopt \
--config user_data/config.json \
--strategy SampleStrategy \
--hyperopt-loss SharpeHyperOptLoss \
--epochs 100
指定优化空间
# 只优化 ROI 和止损
freqtrade hyperopt \
--config user_data/config.json \
--strategy SampleStrategy \
--hyperopt-loss SharpeHyperOptLoss \
--spaces roi stoploss \
--epochs 100
优化空间说明
roi- 最小 ROI 表stoploss- 止损值trailing- 跟踪止损参数buy- 买入信号参数(已弃用,使用 entry)sell- 卖出信号参数(已弃用,使用 exit)entry- 入场信号参数exit- 出场信号参数protection- 保护参数all- 所有可用空间default- 默认空间(roi, stoploss, trailing)
损失函数
损失函数定义了优化目标。Freqtrade 提供多种内置损失函数:
内置损失函数
- SharpeHyperOptLoss - 优化 Sharpe 比率(推荐)
- SortinoHyperOptLoss - 优化 Sortino 比率
- CalmarHyperOptLoss - 优化 Calmar 比率
- MaxDrawDownHyperOptLoss - 最小化最大回撤
- MaxDrawDownRelativeHyperOptLoss - 最小化相对最大回撤
- ProfitLoss - 最大化总利润
- ShortTradeDurHyperOptLoss - 最小化交易持续时间
使用不同损失函数
# 优化 Sharpe 比率
freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss
# 优化 Sortino 比率
freqtrade hyperopt --hyperopt-loss SortinoHyperOptLoss
# 最小化最大回撤
freqtrade hyperopt --hyperopt-loss MaxDrawDownHyperOptLoss
# 最大化利润
freqtrade hyperopt --hyperopt-loss ProfitLoss
策略中的超参数空间
定义参数空间
from skopt.space import Categorical, Dimension, Integer, Real
class MyStrategy(IStrategy):
# 买入参数
buy_rsi_enabled = CategoricalParameter([True, False], default=True, space='buy')
buy_rsi = IntParameter(20, 40, default=30, space='buy')
buy_ema_short = IntParameter(5, 20, default=10, space='buy')
buy_ema_long = IntParameter(50, 200, default=100, space='buy')
# 卖出参数
sell_rsi = IntParameter(60, 80, default=70, space='sell')
sell_ema_short = IntParameter(5, 20, default=10, space='sell')
# ROI 参数
roi_t1 = IntParameter(10, 120, default=60, space='roi')
roi_t2 = IntParameter(10, 60, default=30, space='roi')
roi_t3 = IntParameter(10, 40, default=20, space='roi')
roi_p1 = DecimalParameter(0.01, 0.04, default=0.01, space='roi')
roi_p2 = DecimalParameter(0.01, 0.07, default=0.02, space='roi')
roi_p3 = DecimalParameter(0.01, 0.20, default=0.03, space='roi')
# 止损参数
stoploss = DecimalParameter(-0.35, -0.07, default=-0.10, space='stoploss')
# 跟踪止损参数
trailing_stop = CategoricalParameter([True, False], default=False, space='trailing')
trailing_stop_positive = DecimalParameter(0.01, 0.35, default=0.05, space='trailing')
trailing_stop_positive_offset = DecimalParameter(0.01, 0.35, default=0.07, space='trailing')
trailing_only_offset_is_reached = CategoricalParameter([True, False], default=False, space='trailing')
@property
def minimal_roi(self):
"""
动态生成 ROI 表
"""
return {
"0": self.roi_p1.value,
str(self.roi_t3.value): self.roi_p2.value,
str(self.roi_t2.value): self.roi_p3.value,
str(self.roi_t1.value): 0
}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['ema_short'] = ta.EMA(dataframe, timeperiod=self.buy_ema_short.value)
dataframe['ema_long'] = ta.EMA(dataframe, timeperiod=self.buy_ema_long.value)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = []
if self.buy_rsi_enabled.value:
conditions.append(dataframe['rsi'] < self.buy_rsi.value)
conditions.append(dataframe['ema_short'] > dataframe['ema_long'])
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
['enter_long', 'enter_tag']
] = (1, 'hyperopt_entry')
return dataframe
参数类型
- CategoricalParameter - 分类参数(True/False, 字符串列表等)
- IntParameter - 整数参数
- DecimalParameter - 小数参数
- RealParameter - 实数参数(与 DecimalParameter 相同)
运行超参数优化
基本运行
# 运行 100 次迭代
freqtrade hyperopt \
--config user_data/config.json \
--strategy SampleStrategy \
--hyperopt-loss SharpeHyperOptLoss \
--epochs 100 \
--spaces roi stoploss
高级运行选项
# 使用多个 CPU 核心
freqtrade hyperopt \
--config user_data/config.json \
--strategy SampleStrategy \
--hyperopt-loss SharpeHyperOptLoss \
--epochs 500 \
--spaces all \
--jobs 4 \
--min-trades 10 \
--timerange 20230101-20231231
导出结果
# 导出结果到 CSV
freqtrade hyperopt \
--config user_data/config.json \
--strategy SampleStrategy \
--hyperopt-loss SharpeHyperOptLoss \
--epochs 100 \
--export-csv user_data/hyperopt_results/results.csv
查看超参数优化结果
列出结果
# 列出所有超参数优化结果
freqtrade hyperopt-list
# 列出最佳结果
freqtrade hyperopt-list --best
# 列出盈利结果
freqtrade hyperopt-list --profitable
# 按损失排序
freqtrade hyperopt-list --sort-by loss
显示详细结果
# 显示最佳结果的详细信息
freqtrade hyperopt-show -n -1
# 显示特定结果
freqtrade hyperopt-show -n 123
# 显示前 5 个结果
freqtrade hyperopt-show --best --no-header -n 1 2 3 4 5
导出最佳参数
# 导出最佳参数到策略文件
freqtrade hyperopt-show -n -1 --print-json --no-header > best_params.json
超参数优化策略
1. 分阶段优化
# 第一阶段:优化基本参数
freqtrade hyperopt --spaces roi stoploss --epochs 100
# 第二阶段:优化入场参数
freqtrade hyperopt --spaces entry --epochs 200
# 第三阶段:优化出场参数
freqtrade hyperopt --spaces exit --epochs 200
# 第四阶段:优化跟踪止损
freqtrade hyperopt --spaces trailing --epochs 100
2. 渐进式优化
# 从少量迭代开始
freqtrade hyperopt --epochs 50
# 根据结果增加迭代次数
freqtrade hyperopt --epochs 200
# 最终精细调整
freqtrade hyperopt --epochs 500
3. 时间段分割
# 在不同时间段优化
freqtrade hyperopt --timerange 20230101-20230630 # 上半年
freqtrade hyperopt --timerange 20230701-20231231 # 下半年
# 交叉验证
freqtrade hyperopt --timerange 20220101-20221231 # 训练期
freqtrade backtesting --timerange 20230101-20231231 # 验证期
自定义损失函数
创建自定义损失函数
from freqtrade.optimize.hyperopt import IHyperOptLoss
from datetime import datetime
from pandas import DataFrame
class CustomHyperOptLoss(IHyperOptLoss):
"""
自定义损失函数示例
"""
@staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime,
config: Dict, processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any], **kwargs) -> float:
"""
自定义损失计算
"""
# 获取关键指标
total_profit = results['profit_ratio'].sum()
max_drawdown = backtest_stats['max_drawdown']
trade_count = len(results)
# 自定义损失计算
if trade_count < 10:
return 1000 # 惩罚交易太少的策略
# 结合利润和回撤的损失函数
risk_adjusted_return = total_profit / max_drawdown if max_drawdown > 0 else 0
return -risk_adjusted_return # 负数因为我们要最小化损失
使用自定义损失函数
# 使用自定义损失函数
freqtrade hyperopt \
--config user_data/config.json \
--strategy SampleStrategy \
--hyperopt-loss CustomHyperOptLoss \
--hyperopt-path user_data/hyperopts \
--epochs 100
超参数优化结果分析
结果统计
# 显示超参数优化统计
freqtrade hyperopt-list --best --min-trades 10
# 显示详细统计
freqtrade hyperopt-show -n -1 --print-json
结果可视化
# 使用 Python 分析结果
import pandas as pd
import matplotlib.pyplot as plt
# 加载超参数优化结果
results = pd.read_csv('user_data/hyperopt_results/results.csv')
# 绘制损失分布
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.hist(results['loss'], bins=50)
plt.title('损失分布')
plt.xlabel('损失值')
plt.ylabel('频次')
# 绘制利润 vs 回撤
plt.subplot(1, 2, 2)
plt.scatter(results['profit'], results['max_drawdown'])
plt.title('利润 vs 最大回撤')
plt.xlabel('总利润')
plt.ylabel('最大回撤')
plt.show()
高级超参数优化
多目标优化
from freqtrade.optimize.hyperopt import IHyperOptLoss
class MultiObjectiveLoss(IHyperOptLoss):
"""
多目标优化损失函数
"""
@staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime,
config: Dict, processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any], **kwargs) -> float:
# 获取多个目标
profit = backtest_stats['profit_total']
max_drawdown = backtest_stats['max_drawdown']
sharpe = backtest_stats.get('sharpe', 0)
trade_count = backtest_stats['trade_count']
# 多目标组合
if trade_count < 10:
return 1000
# 权重组合
profit_weight = 0.4
drawdown_weight = 0.3
sharpe_weight = 0.3
normalized_profit = profit / 100 # 假设 100% 为最大期望利润
normalized_drawdown = max_drawdown / 50 # 假设 50% 为最大可接受回撤
normalized_sharpe = sharpe / 3 # 假设 3 为优秀的 Sharpe 比率
# 组合损失(要最小化)
loss = -(profit_weight * normalized_profit -
drawdown_weight * normalized_drawdown +
sharpe_weight * normalized_sharpe)
return loss
条件参数空间
class AdvancedStrategy(IStrategy):
# 条件参数
use_rsi = CategoricalParameter([True, False], default=True, space='buy')
rsi_period = IntParameter(10, 30, default=14, space='buy', load=True)
rsi_buy_threshold = IntParameter(20, 40, default=30, space='buy', load=True)
use_macd = CategoricalParameter([True, False], default=False, space='buy')
macd_fast = IntParameter(8, 20, default=12, space='buy', load=True)
macd_slow = IntParameter(20, 50, default=26, space='buy', load=True)
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions = []
# 条件性使用 RSI
if self.use_rsi.value:
conditions.append(dataframe['rsi'] < self.rsi_buy_threshold.value)
# 条件性使用 MACD
if self.use_macd.value:
conditions.append(dataframe['macd'] > dataframe['macdsignal'])
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
['enter_long', 'enter_tag']
] = (1, 'hyperopt_entry')
return dataframe
超参数优化最佳实践
1. 合理设置迭代次数
# 快速测试:50-100 次迭代
freqtrade hyperopt --epochs 100
# 正常优化:200-500 次迭代
freqtrade hyperopt --epochs 300
# 精细优化:500-1000 次迭代
freqtrade hyperopt --epochs 1000
2. 使用足够的数据
# 使用至少 6 个月的数据
freqtrade hyperopt --timerange 20230101-20230630
# 更好:使用 1 年或更多数据
freqtrade hyperopt --timerange 20220101-20231231
3. 设置最小交易数
# 过滤交易数太少的结果
freqtrade hyperopt --min-trades 20
4. 并行处理
# 使用多核加速
freqtrade hyperopt --jobs 4 # 使用 4 个 CPU 核心
超参数优化后的验证
1. 样本外测试
# 在训练期优化
freqtrade hyperopt --timerange 20220101-20221231
# 在测试期验证
freqtrade backtesting \
--strategy OptimizedStrategy \
--timerange 20230101-20231231
2. 交叉验证
# 分割数据进行交叉验证
freqtrade hyperopt --timerange 20220101-20220630 # 训练集 1
freqtrade backtesting --timerange 20220701-20221231 # 测试集 1
freqtrade hyperopt --timerange 20220701-20221231 # 训练集 2
freqtrade backtesting --timerange 20220101-20220630 # 测试集 2
3. 干跑验证
# 使用优化后的参数进行干跑
freqtrade trade \
--config user_data/config.json \
--strategy OptimizedStrategy \
--dry-run
性能优化技巧
1. 减少搜索空间
# 限制参数范围
buy_rsi = IntParameter(25, 35, default=30, space='buy') # 而不是 (10, 50)
2. 使用缓存
# 利用回测缓存
freqtrade hyperopt --cache day
3. 选择性优化
# 只优化最重要的参数
freqtrade hyperopt --spaces roi stoploss # 而不是 --spaces all
4. 分批运行
# 分批运行长时间优化
freqtrade hyperopt --epochs 100 --hyperopt-filename batch1
freqtrade hyperopt --epochs 100 --hyperopt-filename batch2
freqtrade hyperopt --epochs 100 --hyperopt-filename batch3
常见超参数优化错误
1. 过度拟合
过度拟合风险
- 使用太少的历史数据
- 设置太多的参数
- 运行太多的迭代次数
- 忽略样本外验证
2. 数据偏差
# 错误:只在牛市数据上优化
freqtrade hyperopt --timerange 20210101-20211231
# 改进:包含不同市场条件
freqtrade hyperopt --timerange 20200101-20231231
3. 忽略交易成本
# 确保包含现实的费用
freqtrade hyperopt --fee 0.001 # 0.1% 费用
结果应用
应用最佳参数
# 根据超参数优化结果更新策略
class OptimizedStrategy(IStrategy):
# 应用优化后的参数
minimal_roi = {
"0": 0.0234,
"23": 0.0456,
"89": 0.0123,
"234": 0
}
stoploss = -0.0987
trailing_stop = True
trailing_stop_positive = 0.0123
trailing_stop_positive_offset = 0.0234
监控优化效果
# 在新数据上验证优化结果
freqtrade backtesting \
--strategy OptimizedStrategy \
--timerange 20240101-20240331 # 新的时间段
下一步
完成超参数优化后,您可能想要学习 FreqAI 功能或开始实盘交易。建议先在干跑模式下验证优化后的策略,然后再考虑实盘部署。