跳至内容

超参数优化

本页面介绍如何通过寻找最优参数来优化你的策略,这一过程称为超参数优化。机器人使用 optuna 软件包中的算法来实现此功能。搜索过程会占用所有 CPU 核心,使你的笔记本电脑听起来像战斗机,并且仍需很长时间。

通常情况下,最佳参数的搜索从几组随机组合开始(详见下文),然后使用 optuna 的一种采样器算法(目前是 NSGAIIISampler)快速在搜索超空间中找到一组参数组合,以最小化损失函数的值。

Hyperopt 需要历史数据,就像回测一样(hyperopt 会使用不同参数多次运行回测)。要了解如何获取你感兴趣的交易对和交易所的数据,请前往文档中的数据下载部分。

缺陷

问题 #1133中所发现,当仅使用一个 CPU 核心时,Hyperopt 可能崩溃。

注意

自 2021.4 版本起,你不再需要编写单独的 hyperopt 类,而是可以直接在策略中配置参数。旧版方法支持到 2021.8 版本,并已在 2021.9 版本中移除。

安装 hyperopt 依赖项

由于 Hyperopt 依赖项并非运行机器人本身所必需,且体积较大,在某些平台(如树莓派)上难以构建,因此默认不会安装。在运行 Hyperopt 之前,你需要按照本节下方说明安装相应的依赖项。

注意

由于 Hyperopt 是一项资源密集型进程,不推荐也不支持在树莓派上运行。

Docker

Docker 镜像已包含 hyperopt 依赖项,无需额外操作。

简易安装脚本(setup.sh)/ 手动安装

source .venv/bin/activate
pip install -r requirements-hyperopt.txt

Hyperopt 命令参考

usage: freqtrade hyperopt [-h] [-v] [--no-color] [--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]
                          [-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
                          [--eps] [--enable-protections]
                          [--dry-run-wallet DRY_RUN_WALLET]
                          [--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
                          [--spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]]
                          [--print-all] [--print-json] [-j JOBS]
                          [--random-state INT] [--min-trades INT]
                          [--hyperopt-loss NAME] [--disable-param-export]
                          [--ignore-missing-spaces] [--analyze-per-epoch]
                          [--early-stop INT]

options:
  -h, --help            show this help message and exit
  -i TIMEFRAME, --timeframe TIMEFRAME
                        Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
  --timerange TIMERANGE
                        Specify what timerange of data to use.
  --data-format-ohlcv {json,jsongz,feather,parquet}
                        Storage format for downloaded candle (OHLCV) data.
                        (default: `feather`).
  --max-open-trades INT
                        Override the value of the `max_open_trades`
                        configuration setting.
  --stake-amount STAKE_AMOUNT
                        Override the value of the `stake_amount` configuration
                        setting.
  --fee FLOAT           Specify fee ratio. Will be applied twice (on trade
                        entry and exit).
  -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
                        Limit command to these pairs. Pairs are space-
                        separated.
  --hyperopt-path PATH  Specify additional lookup path for Hyperopt Loss
                        functions.
  --eps, --enable-position-stacking
                        Allow buying the same pair multiple times (position
                        stacking).
  --enable-protections, --enableprotections
                        Enable protections for backtesting. Will slow
                        backtesting down by a considerable amount, but will
                        include configured protections
  --dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
                        Starting balance, used for backtesting / hyperopt and
                        dry-runs.
  --timeframe-detail TIMEFRAME_DETAIL
                        Specify detail timeframe for backtesting (`1m`, `5m`,
                        `30m`, `1h`, `1d`).
  -e INT, --epochs INT  Specify number of epochs (default: 100).
  --spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]
                        Specify which parameters to hyperopt. Space-separated
                        list.
  --print-all           Print all results, not only the best ones.
  --print-json          Print output in JSON format.
  -j JOBS, --job-workers JOBS
                        The number of concurrently running jobs for
                        hyperoptimization (hyperopt worker processes). If -1
                        (default), all CPUs are used, for -2, all CPUs but one
                        are used, etc. If 1 is given, no parallel computing
                        code is used at all.
  --random-state INT    Set random state to some positive integer for
                        reproducible hyperopt results.
  --min-trades INT      Set minimal desired number of trades for evaluations
                        in the hyperopt optimization path (default: 1).
  --hyperopt-loss NAME, --hyperoptloss NAME
                        Specify the class name of the hyperopt loss function
                        class (IHyperOptLoss). Different functions can
                        generate completely different results, since the
                        target for optimization is different. Built-in
                        Hyperopt-loss-functions are:
                        ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss,
                        SharpeHyperOptLoss, SharpeHyperOptLossDaily,
                        SortinoHyperOptLoss, SortinoHyperOptLossDaily,
                        CalmarHyperOptLoss, MaxDrawDownHyperOptLoss,
                        MaxDrawDownRelativeHyperOptLoss,
                        MaxDrawDownPerPairHyperOptLoss,
                        ProfitDrawDownHyperOptLoss, MultiMetricHyperOptLoss
  --disable-param-export
                        Disable automatic hyperopt parameter export.
  --ignore-missing-spaces, --ignore-unparameterized-spaces
                        Suppress errors for any requested Hyperopt spaces that
                        do not contain any parameters.
  --analyze-per-epoch   Run populate_indicators once per epoch.
  --early-stop INT      Early stop hyperopt if no improvement after (default:
                        0) epochs.

Common arguments:
  -v, --verbose         Verbose mode (-vv for more, -vvv to get all messages).
  --no-color            Disable colorization of hyperopt results. May be
                        useful if you are redirecting output to a file.
  --logfile FILE, --log-file FILE
                        Log to the file specified. Special values are:
                        'syslog', 'journald'. See the documentation for more
                        details.
  -V, --version         show program's version number and exit
  -c PATH, --config PATH
                        Specify configuration file (default:
                        `userdir/config.json` or `config.json` whichever
                        exists). Multiple --config options may be used. Can be
                        set to `-` to read config from stdin.
  -d PATH, --datadir PATH, --data-dir PATH
                        Path to the base directory of the exchange with
                        historical backtesting data. To see futures data, use
                        trading-mode additionally.
  --userdir PATH, --user-data-dir PATH
                        Path to userdata directory.

Strategy arguments:
  -s NAME, --strategy NAME
                        Specify strategy class name which will be used by the
                        bot.
  --strategy-path PATH  Specify additional strategy lookup path.
  --recursive-strategy-search
                        Recursively search for a strategy in the strategies
                        folder.
  --freqaimodel NAME    Specify a custom freqaimodels.
  --freqaimodel-path PATH
                        Specify additional lookup path for freqaimodels.

Hyperopt 检查清单

涵盖 hyperopt 中所有任务/可能性的检查清单

根据你要优化的空间,以下内容中仅部分是必需的:

  • 定义带有 space='buy' 的参数——用于入场信号优化
  • 定义带有 space='sell' 的参数——用于出场信号优化

注意

populate_indicators 需要创建所有可能被任一空间使用的指标,否则 hyperopt 将无法工作。

极少数情况下,你还可能需要创建一个名为 HyperOpt嵌套类并实现

  • roi_space - 用于自定义 ROI 优化(如果需要 ROI 参数在优化超空间中的取值范围与默认值不同)
  • generate_roi_table - 用于自定义 ROI 优化(如果需要 ROI 表格中的数值范围与默认值不同,或条目数量(步数)与默认的 4 步不同)
  • stoploss_space - 用于自定义止损优化(如果需要止损参数在优化超空间中的取值范围与默认值不同)
  • trailing_space - 用于自定义移动止损优化(如果需要移动止损参数在优化超空间中的取值范围与默认值不同)
  • max_open_trades_space - 用于自定义最大开仓交易数优化(如果需要 max_open_trades 参数在优化超空间中的取值范围与默认值不同)

快速优化 ROI、止损和移动止损

你可以快速优化 roistoplosstrailing 空间,而无需更改策略中的任何内容。

# Have a working strategy at hand.
freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss trailing --strategy MyWorkingStrategy --config config.json -e 100

Hyperopt 执行逻辑

Hyperopt 会首先将你的数据加载到内存中,然后对每个交易对运行一次 populate_indicators() 来生成所有指标,除非指定了 --analyze-per-epoch

随后,Hyperopt 会派生出多个进程(进程数量等于处理器核心数,或由 -j <n> 指定),反复运行回测,并更改 --spaces 定义中涉及的参数。

对于每一组新参数,Freqtrade 会先运行 populate_entry_trend(),然后运行 populate_exit_trend(),接着执行常规回测流程来模拟交易。

回测结束后,结果会被传入损失函数,该函数将评估此次结果相比之前是更好还是更差。
根据损失函数的结果,Hyperopt 将决定下一轮回测应尝试的下一组参数。

配置你的保护条件和触发条件

你需要在策略文件中修改两个地方,以添加新的买入超参优化进行测试:

  • 在类级别定义 Hyperopt 应优化的参数。
  • populate_entry_trend() 中使用已定义的参数值,而不是原始常量。

这里有两种不同类型的指标:1. 保护条件(guards) 和 2. 触发条件(triggers)

  1. 保护条件是指类似“如果 ADX < 10 则永不买入”,或“当前价格高于 EMA10 时永不买入”这样的条件。
  2. 触发条件是指在特定时刻真正触发买入的条件,例如“当 EMA5 上穿 EMA10 时买入”或“当收盘价触及布林带下轨时买入”。

保护条件与触发条件

从技术上讲,保护条件和触发条件之间没有区别。
然而,本指南会做出这种区分,以强调信号不应“持续存在”。持续性信号是指在多个 K 线中持续有效的信号。这可能导致在信号即将消失前才入场(即信号末期),此时成功的概率远低于信号刚出现时。

Hyper-optimization 在每一轮迭代中,会为每个周期选择一个触发条件和可能的多个保护条件。

卖出信号优化

与上面的入场信号类似,出场信号也可以进行优化。将相应的设置放入以下方法中。

  • 在类级别定义需要由 hyperopt 优化的参数,参数名应以 sell_* 开头,或显式定义 space='sell'
  • populate_exit_trend() 中,使用已定义的参数值代替原始常量。

配置和规则与买入信号相同。

解开一个谜题

假设你很好奇:应该使用 MACD 交叉还是布林带下轨来触发你的多头入场?同时你也想知道是否该使用 RSI 或 ADX 来辅助决策。如果决定使用 RSI 或 ADX,它们的参数值又该设为多少?

那么让我们通过超参数优化来解开这个谜题。

定义要使用的指标

我们首先计算策略将用到的技术指标。

class MyAwesomeStrategy(IStrategy):

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Generate all indicators used by the strategy
        """
        dataframe['adx'] = ta.ADX(dataframe)
        dataframe['rsi'] = ta.RSI(dataframe)
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']

        bollinger = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        dataframe['bb_lowerband'] = bollinger['lowerband']
        dataframe['bb_middleband'] = bollinger['middleband']
        dataframe['bb_upperband'] = bollinger['upperband']
        return dataframe

可优化的超参数

接下来我们定义可由 hyperopt 优化的参数:

class MyAwesomeStrategy(IStrategy):
    buy_adx = DecimalParameter(20, 40, decimals=1, default=30.1, space="buy")
    buy_rsi = IntParameter(20, 40, default=30, space="buy")
    buy_adx_enabled = BooleanParameter(default=True, space="buy")
    buy_rsi_enabled = CategoricalParameter([True, False], default=False, space="buy")
    buy_trigger = CategoricalParameter(["bb_lower", "macd_cross_signal"], default="bb_lower", space="buy")

上述定义表示:我有五个参数希望随机组合,以找出最佳搭配。
buy_rsi 是一个整数参数,测试范围在 20 到 40 之间,搜索空间大小为 20。
buy_adx 是一个小数参数,在 20 到 40 之间以一位小数精度进行评估(即可能取值为 20.1、20.2 等),其搜索空间大小为 200。
接下来是三个类别型变量,前两个只能是 TrueFalse,用于启用或禁用 ADX 和 RSI 防护条件。最后一个变量名为 trigger,用来决定使用哪种买入触发信号。

参数空间分配

参数必须命名为 buy_*sell_*,或者包含 space='buy' | space='sell',才能正确分配到对应的空间。如果某个空间没有可用参数,在运行 hyperopt 时会报错提示找不到对应空间。
对于空间不明确的参数(例如:adx_period = IntParameter(4, 24, default=14) —— 没有显式或隐式的 space 定义),系统将无法识别并忽略这些参数。

现在我们使用这些值来编写买入策略:

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []
        # GUARDS AND TRENDS
        if self.buy_adx_enabled.value:
            conditions.append(dataframe['adx'] > self.buy_adx.value)
        if self.buy_rsi_enabled.value:
            conditions.append(dataframe['rsi'] < self.buy_rsi.value)

        # TRIGGERS
        if self.buy_trigger.value == 'bb_lower':
            conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
        if self.buy_trigger.value == 'macd_cross_signal':
            conditions.append(qtpylib.crossed_above(
                dataframe['macd'], dataframe['macdsignal']
            ))

        # Check that volume is not 0
        conditions.append(dataframe['volume'] > 0)

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x & y, conditions),
                'enter_long'] = 1

        return dataframe

hyperopt 现在会使用不同的参数组合多次调用 populate_entry_trend()(次数由 epochs 决定)。
它将基于历史数据,并根据上述函数生成的买入信号模拟交易。
根据回测结果,hyperopt 将告诉你哪组参数组合表现最优(依据所配置的损失函数评判)。

注意

上述设置要求在已计算的指标中包含 ADX、RSI 和布林带。如果你想要测试当前机器人未使用的某个新指标,请记得将其添加到策略或 hyperopt 文件的 populate_indicators() 方法中。

参数类型

共有四种参数类型,各自适用于不同场景。

  • IntParameter —— 定义具有上下限的整数型参数。
  • DecimalParameter —— 定义带有有限小数位数(默认 3 位)的浮点型参数。在大多数情况下应优先于 RealParameter 使用。
  • RealParameter - 定义一个具有上下边界且无精度限制的浮点型参数。由于会创建近乎无限可能性的搜索空间,因此很少使用。
  • CategoricalParameter - 定义一个具有预设选项数量的参数。
  • BooleanParameter - 相当于 CategoricalParameter([True, False]) 的简写形式,非常适合用于“启用/禁用”类参数。

参数选项

有两个参数选项可以帮助你快速测试各种想法:

  • optimize - 当设置为 False 时,该参数将不会参与优化过程。(默认值:True)
  • load - 当设置为 False 时,先前 hyperopt 运行的结果(无论是在策略中的 buy_paramssell_params,还是在 JSON 输出文件中)将不会作为后续 hyperopt 的初始值,而是使用参数中指定的默认值。(默认值:True)

load=False 对回测的影响

请注意,将 load 选项设置为 False 意味着回测也将使用参数中指定的默认值,而不会使用通过超参数优化找到的值。

警告

超参数优化参数不能在 populate_indicators 中使用——因为 hyperopt 不会在每次迭代时重新计算指标,因此在这种情况下将始终使用参数的初始值。

优化指标参数

假设你有一个简单的策略思路——EMA 交叉策略(两条移动平均线交叉),并希望找到该策略的最佳参数。默认情况下,我们假设止损为 5%,止盈(minimal_roi)为 10%,这意味着当收益达到 10% 时,freqtrade 将会卖出该交易。

from pandas import DataFrame
from functools import reduce

import talib.abstract as ta

from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, 
                                IStrategy, IntParameter)
import freqtrade.vendor.qtpylib.indicators as qtpylib

class MyAwesomeStrategy(IStrategy):
    stoploss = -0.05
    timeframe = '15m'
    minimal_roi = {
        "0":  0.10
    }
    # Define the parameter spaces
    buy_ema_short = IntParameter(3, 50, default=5)
    buy_ema_long = IntParameter(15, 200, default=50)


    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Generate all indicators used by the strategy"""

        # Calculate all ema_short values
        for val in self.buy_ema_short.range:
            dataframe[f'ema_short_{val}'] = ta.EMA(dataframe, timeperiod=val)

        # Calculate all ema_long values
        for val in self.buy_ema_long.range:
            dataframe[f'ema_long_{val}'] = ta.EMA(dataframe, timeperiod=val)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []
        conditions.append(qtpylib.crossed_above(
                dataframe[f'ema_short_{self.buy_ema_short.value}'], dataframe[f'ema_long_{self.buy_ema_long.value}']
            ))

        # Check that volume is not 0
        conditions.append(dataframe['volume'] > 0)

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x & y, conditions),
                'enter_long'] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []
        conditions.append(qtpylib.crossed_above(
                dataframe[f'ema_long_{self.buy_ema_long.value}'], dataframe[f'ema_short_{self.buy_ema_short.value}']
            ))

        # Check that volume is not 0
        conditions.append(dataframe['volume'] > 0)

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x & y, conditions),
                'exit_long'] = 1
        return dataframe

具体分析如下:

使用 self.buy_ema_short.range 将返回一个范围对象,包含参数最小值和最大值之间的所有取值。以本例(IntParameter(3, 50, default=5))而言,循环将遍历从 3 到 50 的所有整数([3, 4, 5, ... 49, 50])。在循环中使用此方法时,hyperopt 将生成 48 个新列(['buy_ema_3', 'buy_ema_4', ... , 'buy_ema_50'])。

hyperopt 本身随后会使用所选的值来生成买入和卖出信号。

尽管该策略很可能过于简单而无法持续盈利,但它可以作为优化指标参数方法的一个示例。

注意

self.buy_ema_short.range 在 hyperopt 模式和其他模式下的行为不同。在 hyperopt 中,上述示例可能生成 48 个新列;但在其他所有模式(回测、模拟/实盘)中,仅会生成对应所选值的那一列。因此,应避免使用显式数值来引用生成的列(只能使用 self.buy_ema_short.value 而非具体数值)。

注意

range 属性也可用于 DecimalParameterCategoricalParameterRealParameter 由于搜索空间无限,不提供此属性。

性能提示

在正常进行 hyperopt 时,指标仅计算一次,并线性地提供给每次迭代,这会导致随着核心数增加而线性增长内存使用量。由于这会影响性能,因此有两种替代方案可减少内存使用

  • ema_shortema_long 的计算从 populate_indicators() 移动到 populate_entry_trend()。由于 populate_entry_trend() 每个周期都会被重新计算,因此无需再使用 .range 功能。
  • hyperopt 提供了 --analyze-per-epoch 参数,它会将 populate_indicators() 的执行移至每个周期的处理过程中,对每组参数在每个周期内仅计算一个值,从而避免使用 .range 功能。在这种情况下,.range 功能只会返回实际使用的值。

这些替代方法会减少内存(RAM)使用量,但会增加 CPU 使用量。然而,你的 hyperopt 运行过程将更不容易因内存不足(OOM)问题而失败。

无论你使用的是 .range 功能还是上述替代方案,都应尽量使用尽可能小的参数搜索范围,因为这有助于提升 CPU/内存的使用效率。

优化保护机制

Freqtrade 还可以优化保护机制。如何优化保护机制取决于你自身的选择,以下内容仅供参考示例。

策略只需将 "protections" 条目定义为返回保护配置列表的属性即可。

from pandas import DataFrame
from functools import reduce

import talib.abstract as ta

from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, 
                                IStrategy, IntParameter)
import freqtrade.vendor.qtpylib.indicators as qtpylib

class MyAwesomeStrategy(IStrategy):
    stoploss = -0.05
    timeframe = '15m'
    # Define the parameter spaces
    cooldown_lookback = IntParameter(2, 48, default=5, space="protection", optimize=True)
    stop_duration = IntParameter(12, 200, default=5, space="protection", optimize=True)
    use_stop_protection = BooleanParameter(default=True, space="protection", optimize=True)


    @property
    def protections(self):
        prot = []

        prot.append({
            "method": "CooldownPeriod",
            "stop_duration_candles": self.cooldown_lookback.value
        })
        if self.use_stop_protection.value:
            prot.append({
                "method": "StoplossGuard",
                "lookback_period_candles": 24 * 3,
                "trade_limit": 4,
                "stop_duration_candles": self.stop_duration.value,
                "only_per_pair": False
            })

        return prot

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # ...

然后你可以按如下方式运行 hyperopt:freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy MyAwesomeStrategy --spaces protection

注意

保护空间不属于默认优化空间,并且仅在参数化 Hyperopt 接口下可用,不适用于旧版 hyperopt 接口(后者需要单独的 hyperopt 文件)。当选择保护空间时,Freqtrade 也会自动启用 "--enable-protections" 标志。

警告

如果保护机制被定义为属性,则配置文件中的相关条目将被忽略。因此建议不要在配置中定义保护机制。

从之前的属性设置迁移

从之前的设置迁移非常简单,只需将 protections 条目转换为属性即可。简单来说,以下配置将被转换为下方所示的形式。

class MyAwesomeStrategy(IStrategy):
    protections = [
        {
            "method": "CooldownPeriod",
            "stop_duration_candles": 4
        }
    ]

结果

class MyAwesomeStrategy(IStrategy):

    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 4
            }
        ]

显然,你还需要将可能感兴趣的条目改为可调参数,以便进行超参数优化。

优化 max_entry_position_adjustment

虽然 max_entry_position_adjustment 不是一个独立的优化空间,但仍可通过上述属性方式在 hyperopt 中使用。

from pandas import DataFrame
from functools import reduce

import talib.abstract as ta

from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, 
                                IStrategy, IntParameter)
import freqtrade.vendor.qtpylib.indicators as qtpylib

class MyAwesomeStrategy(IStrategy):
    stoploss = -0.05
    timeframe = '15m'

    # Define the parameter spaces
    max_epa = CategoricalParameter([-1, 0, 1, 3, 5, 10], default=1, space="buy", optimize=True)

    @property
    def max_entry_position_adjustment(self):
        return self.max_epa.value


    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # ...
使用 IntParameter

你也可以为此优化使用 IntParameter,但必须显式返回一个整数:

max_epa = IntParameter(-1, 10, default=1, space="buy", optimize=True)

@property
def max_entry_position_adjustment(self):
    return int(self.max_epa.value)

损失函数

每次超参数调优都需要一个目标。这通常由一个损失函数(有时也称为目标函数)来定义,该函数在结果更理想时应减小,在结果较差时应增大。

损失函数必须通过 --hyperopt-loss <类名> 参数指定(或可选地在配置文件中的 "hyperopt_loss" 键下指定)。该类应位于 user_data/hyperopts/ 目录下的独立文件中。

目前内置的损失函数包括:

  • ShortTradeDurHyperOptLoss - (默认的旧版 Freqtrade 超参优化损失函数)- 主要倾向于缩短交易持续时间并避免亏损。
  • OnlyProfitHyperOptLoss - 仅考虑盈利金额。
  • SharpeHyperOptLoss - 根据交易收益相对于标准差计算的夏普比率进行优化。
  • SharpeHyperOptLossDaily - 根据每日交易收益相对于标准差计算的夏普比率进行优化。
  • SortinoHyperOptLoss - 根据交易收益相对于下行标准差计算的索提诺比率进行优化。
  • SortinoHyperOptLossDaily - 根据每日交易收益相对于下行标准差计算的索提诺比率进行优化。
  • MaxDrawDownHyperOptLoss - 优化最大绝对回撤。
  • MaxDrawDownRelativeHyperOptLoss - 同时优化最大绝对回撤和最大相对回撤。
  • MaxDrawDownPerPairHyperOptLoss - 计算每个交易对的收益/回撤比率,并以最差结果作为优化目标,促使超参数优化器(hyperopt)对交易对列表中的所有交易对进行参数优化。这样可以防止少数表现良好的交易对夸大整体指标,而表现较差的交易对被忽略且未得到充分优化。
  • CalmarHyperOptLoss - 根据交易收益相对于最大回撤计算的卡玛比率进行优化。
  • ProfitDrawDownHyperOptLoss - 通过最大化收益并最小化回撤的目标进行优化。DRAWDOWN_MULT 变量可在超参数损失函数文件中调整,以更严格或更宽松地控制回撤权重。
  • MultiMetricHyperOptLoss - 通过多个关键指标实现均衡性能优化。主要目标是最大化收益并最小化回撤,同时考虑其他指标如盈利因子(Profit Factor)、期望值比率(Expectancy Ratio)和胜率(Winrate)。此外,该方法会对交易次数过少的训练周期施加惩罚,鼓励策略保持足够的交易频率。

自定义损失函数的创建方法在文档的高级超参数优化部分中介绍。

执行超参数优化

更新完你的超参数配置后即可运行。由于超参数优化会尝试大量组合以找到最佳参数,因此需要较长时间才能获得良好结果。

我们强烈建议使用 screentmux 来防止连接中断。

freqtrade hyperopt --config config.json --hyperopt-loss <hyperoptlossname> --strategy <strategyname> -e 500 --spaces all

-e 选项用于设置超参数优化将执行的评估次数。由于超参数优化采用贝叶斯搜索方法,一次性运行过多周期可能不会显著提升效果。经验表明,通常在 500-1000 次迭代后,效果提升已不明显。
--early-stop 选项用于设置在多少个周期无改进后停止优化。推荐值为总周期数的 20-30%。若设置值大于 0 但小于 20,则会被自动替换为 20。默认情况下早停功能是关闭的(--early-stop=0)。

进行多次运行(执行),每次使用数千个周期并设置不同的随机状态,很可能产生不同的结果。

--spaces all 选项表示应优化所有可能的参数。可选空间如下所列。

注意

超参数优化会以优化开始的时间戳存储结果。读取命令(hyperopt-listhyperopt-show)可通过 --hyperopt-filename <filename> 选项读取和显示旧的优化结果。你可以使用 ls -l user_data/hyperopt_results/ 命令查看可用的文件名列表。

使用不同的历史数据源执行 Hyperopt

如果你想使用本地磁盘上不同的历史数据集来优化参数,请使用 --datadir PATH 选项。默认情况下,hyperopt 使用 user_data/data 目录中的数据。

使用较小的测试集运行 Hyperopt

使用 --timerange 参数来调整你希望使用的测试集范围。例如,若要使用一个月的数据,可在调用 hyperopt 时传入 --timerange 20210101-20210201(即从 2021 年 1 月到 2021 年 2 月)。

完整命令:

freqtrade hyperopt --strategy <strategyname> --timerange 20210101-20210201

使用更小的搜索空间运行 Hyperopt

使用 --spaces 选项来限制 hyperopt 使用的搜索空间。让 hyperopt 优化所有参数会形成一个巨大的搜索空间。通常,更合理的做法是先专注于寻找初始买入策略。或者,你可能只想针对你那个出色的新买入策略,优化其止损或 ROI 表。

合法值包括:

  • all:优化所有参数
  • buy:仅搜索新的买入策略
  • sell:仅搜索新的卖出策略
  • roi:仅优化策略的最小利润表
  • stoploss:搜索最佳止损值
  • trailing:搜索最佳移动止损值
  • trades:搜索最佳最大开仓交易数
  • protection:搜索最佳保护参数(请阅读保护机制章节,了解如何正确定义这些参数)
  • default:包含 all 中除 trailingtradesprotection 之外的所有项
  • 上述值的空格分隔列表,例如 --spaces roi stoploss

默认的 Hyperopt 搜索空间(即未指定 --space 命令行选项时使用)不包含 trailing 超参数空间。我们建议你在其他超参数空间的最佳参数已被找到、验证并写入自定义策略后,再单独对 trailing 空间进行优化。

理解 Hyperopt 的结果

Hyperopt 完成后,你可以使用其结果来更新你的策略。假设 hyperopt 返回如下结果:

Best result:

    44/100:    135 trades. Avg profit  0.57%. Total profit  0.03871918 BTC (0.7722%). Avg duration 180.4 mins. Objective: 1.94367

    # Buy hyperspace params:
    buy_params = {
        'buy_adx': 44,
        'buy_rsi': 29,
        'buy_adx_enabled': False,
        'buy_rsi_enabled': True,
        'buy_trigger': 'bb_lower'
    }

你应该这样理解该结果:

  • 最有效的买入触发信号是 bb_lower
  • 你不应使用 ADX,因为 'buy_adx_enabled': False
  • 你应该考虑使用 RSI 指标('buy_rsi_enabled': True),且最佳值为 29.0'buy_rsi': 29.0

自动将参数应用到策略中

当使用可超参数优化的参数时,hyperopt 运行的结果会被写入与策略文件同目录的 JSON 文件中(例如,对于 MyAwesomeStrategy.py,文件名为 MyAwesomeStrategy.json)。
使用 hyperopt-show 子命令时也会更新此文件,除非在两个命令中都提供了 --disable-param-export 选项。

你的策略类也可以显式地包含这些结果。只需将 hyperopt 的结果块复制并粘贴到类级别,替换旧的参数(如果有)。下次执行策略时,新参数将自动加载。

将你的全部 hyperopt 结果应用到策略中时,代码如下所示:

class MyAwesomeStrategy(IStrategy):
    # Buy hyperspace params:
    buy_params = {
        'buy_adx': 44,
        'buy_rsi': 29,
        'buy_adx_enabled': False,
        'buy_rsi_enabled': True,
        'buy_trigger': 'bb_lower'
    }

注意

配置文件中的值会覆盖参数文件层级的参数,而参数文件和配置文件都会覆盖策略内的参数。因此优先级顺序为:配置文件 > 参数文件 > 策略中的 *_params > 参数默认值

理解 Hyperopt 的 ROI 结果

如果你正在优化 ROI(即优化搜索空间包含 'all'、'default' 或 'roi'),你的结果将如下所示,并包含一个 ROI 表格:

Best result:

    44/100:    135 trades. Avg profit  0.57%. Total profit  0.03871918 BTC (0.7722%). Avg duration 180.4 mins. Objective: 1.94367

    # ROI table:
    minimal_roi = {
        0: 0.10674,
        21: 0.09158,
        78: 0.03634,
        118: 0
    }

为了在回测以及实盘交易/干运行中使用 Hyperopt 找到的最佳 ROI 表格,请将其复制粘贴为你自定义策略中 minimal_roi 属性的值:

    # Minimal ROI designed for the strategy.
    # This attribute will be overridden if the config file contains "minimal_roi"
    minimal_roi = {
        0: 0.10674,
        21: 0.09158,
        78: 0.03634,
        118: 0
    }

正如注释中所述,你也可以将其用作配置文件中 minimal_roi 设置的值。

默认 ROI 搜索空间

当你优化 ROI 时,Freqtrade 会自动为你创建 'roi' 优化超参数空间——即用于生成 ROI 表格的组件空间。默认情况下,每个由 Freqtrade 生成的 ROI 表格包含 4 行(步骤)。Hyperopt 为 ROI 表格实现了自适应范围,其中各步骤的数值范围取决于所使用的 timeframe。默认情况下,这些值的变化范围如下(针对一些最常用的 timeframe,数值已保留小数点后三位):

# 步骤 1m 5m 1h 1d
1 0 0.011...0.119 0 0.03...0.31 0 0.068...0.711 0 0.121...1.258
2 2...8 0.007...0.042 10...40 0.02...0.11 120...480 0.045...0.252 2880...11520 0.081...0.446
3 4...20 0.003...0.015 20...100 0.01...0.04 240...1200 0.022...0.091 5760...28800 0.040...0.162
4 6...44 0.0 30...220 0.0 360...2640 0.0 8640...63360 0.0

这些范围在大多数情况下已经足够。步骤中的分钟数(ROI 字典的键)会根据所使用的时间框架进行线性缩放。步骤中的 ROI 值(ROI 字典的值)会根据所使用的时间框架进行对数缩放。

如果你的自定义 hyperopt 中包含 generate_roi_table()roi_space() 方法,请将它们移除,以便使用 Freqtrade 默认生成的自适应 ROI 表和 ROI 超参数优化空间。

如果你需要 ROI 表的某些部分在其他范围内变化,请重写 roi_space() 方法。如果你需要不同的 ROI 表结构或不同数量的行(步骤),请重写 generate_roi_table()roi_space() 方法,并实现你自己在超参数优化期间生成 ROI 表的自定义方法。

这些方法的示例可以在 重写预定义空间部分 找到。

缩小的搜索空间

为了进一步限制搜索空间,小数被限制为 3 位小数(精度为 0.001)。这通常已经足够,比此更精确的值通常会导致过拟合的结果。不过,你可以重写预定义空间以根据需要进行调整。

理解 Hyperopt 止损结果

如果你正在优化止损值(即优化搜索空间包含 'all'、'default' 或 'stoploss'),你的结果将如下所示,并包含止损值:

Best result:

    44/100:    135 trades. Avg profit  0.57%. Total profit  0.03871918 BTC (0.7722%). Avg duration 180.4 mins. Objective: 1.94367

    # Buy hyperspace params:
    buy_params = {
        'buy_adx': 44,
        'buy_rsi': 29,
        'buy_adx_enabled': False,
        'buy_rsi_enabled': True,
        'buy_trigger': 'bb_lower'
    }

    stoploss: -0.27996

为了在回测以及实盘交易/模拟交易中使用 Hyperopt 找到的最佳止损值,请将其复制粘贴为你自定义策略中 stoploss 属性的值:

    # Optimal stoploss designed for the strategy
    # This attribute will be overridden if the config file contains "stoploss"
    stoploss = -0.27996

正如注释中所述,你也可以将其用作配置文件中 stoploss 设置的值。

默认止损搜索空间

如果你正在优化止损值,Freqtrade 会为你创建 'stoploss' 超参数优化空间。默认情况下,该空间中的止损值范围为 -0.35...-0.02,这在大多数情况下已经足够。

如果你的自定义 hyperopt 文件中包含 stoploss_space() 方法,请将其移除,以便使用 Freqtrade 默认生成的止损超参数优化空间。

如果你需要在超参数优化期间止损值在其他范围内变化,请重写 stoploss_space() 方法并在其中定义所需范围。该方法的示例可在 重写预定义空间部分 找到。

缩小的搜索空间

为了进一步限制搜索空间,小数被限制为 3 位小数(精度为 0.001)。这通常已经足够,比此更精确的值通常会导致过拟合的结果。不过,你可以重写预定义空间以根据需要进行调整。

理解 Hyperopt 移动止损结果

如果你正在优化移动止损值(即优化搜索空间包含 'all' 或 'trailing'),你的结果将如下所示,并包含移动止损参数:

Best result:

    45/100:    606 trades. Avg profit  1.04%. Total profit  0.31555614 BTC ( 630.48%). Avg duration 150.3 mins. Objective: -1.10161

    # Trailing stop:
    trailing_stop = True
    trailing_stop_positive = 0.02001
    trailing_stop_positive_offset = 0.06038
    trailing_only_offset_is_reached = True

为了在回测以及实盘交易/模拟交易中使用 Hyperopt 找到的最佳移动止损参数,请将其复制粘贴为你自定义策略中对应属性的值:

    # Trailing stop
    # These attributes will be overridden if the config file contains corresponding values.
    trailing_stop = True
    trailing_stop_positive = 0.02001
    trailing_stop_positive_offset = 0.06038
    trailing_only_offset_is_reached = True

正如注释中所述,你也可以在配置文件中将其用作相应设置的值。

默认的追踪止损搜索空间

如果你正在优化追踪止损值,Freqtrade 会自动为你创建“trailing”优化超空间。默认情况下,该超空间中的 trailing_stop 参数始终设置为 True,trailing_only_offset_is_reached 的值在 True 和 False 之间变化,而 trailing_stop_positivetrailing_stop_positive_offset 参数的值分别在 0.02...0.35 和 0.01...0.1 范围内变化,这在大多数情况下已足够。

如果你需要在超优化期间让追踪止损参数在其他范围内变化,请重写 trailing_space() 方法并在其中定义所需的范围。该方法的示例可在重写预定义空间部分找到。

缩小的搜索空间

为了进一步限制搜索空间,小数被限制为 3 位小数(精度为 0.001)。这通常已经足够,比此更精确的值通常会导致过拟合的结果。不过,你可以重写预定义空间以根据需要进行调整。

可复现的结果

最优参数的搜索始于参数超空间中的若干组(目前为 30 组)随机组合,即随机的 Hyperopt 训练周期。这些随机周期在 Hyperopt 输出的第一列中以星号(*)标记。

这些随机值生成的初始状态(随机状态)由命令行选项 --random-state 的值控制。你可以将其设置为任意指定值,以获得可复现的结果。

如果你未在命令行选项中显式设置此值,Hyperopt 将为你使用某个随机值作为随机状态的种子。每次 Hyperopt 运行的随机状态值都会显示在日志中,因此你可以复制并粘贴该值到 --random-state 命令行选项中,以重复使用相同的初始随机周期集合。

如果你未更改命令行选项、配置、时间范围、策略和 Hyperopt 类、历史数据以及损失函数,则使用相同的随机状态值应能得到相同的超优化结果。

输出格式化

默认情况下,hyperopt 会以彩色打印结果——盈利为正的周期以绿色显示。这种高亮有助于你发现可能值得后续分析的周期。总盈利为零或为负(亏损)的周期则以正常颜色显示。如果你不需要结果着色(例如,当你将 hyperopt 输出重定向到文件时),可以在命令行中使用 --no-color 选项关闭着色功能。

如果你希望在 hyperopt 输出中看到所有结果而不仅仅是最佳结果,可以使用 --print-all 命令行选项。当使用 --print-all 时,默认情况下当前最佳结果也会被高亮显示——以粗体(亮色)样式打印。这也可以通过 --no-color 命令行选项关闭。

Windows 与彩色输出

Windows 不原生支持彩色输出,因此该功能会自动禁用。若要在 Windows 下运行 hyperopt 并获得彩色输出,请考虑使用 WSL。

仓位叠加与禁用最大市场仓位

在某些情况下,你可能需要使用 --eps/--enable-position-staking 参数来运行 Hyperopt(和回测),或者需要将 max_open_trades 设置为非常高的数值,以取消对同时开启交易数量的限制。

默认情况下,hyperopt 模拟 Freqtrade 实盘/模拟运行的行为,即每个交易对只允许一个未平仓交易。所有交易对的未平仓交易总数也受到 max_open_trades 设置的限制。在进行 Hyperopt/回测时,这可能导致一些潜在的交易被已有的未平仓交易遮蔽(或掩盖)。

--eps/--enable-position-stacking 参数允许模拟对同一交易对多次买入。通过将 --max-open-trades 设置为一个非常大的数值,可以取消未平仓交易数量的上限。

注意

实盘/模拟运行不会使用仓位叠加功能——因此在不启用此功能的情况下验证策略是有意义的,因为这样更接近真实情况。

你也可以在配置文件中显式设置 "position_stacking"=true 来启用仓位叠加。

内存不足错误

由于 hyperopt 会消耗大量内存(完整数据需要为每个并行回测进程在内存中保存一份),你很可能会遇到“内存不足”错误。为解决这些问题,你可以采取以下多种措施:

  • 减少交易对的数量。
  • 缩短使用的时间范围 (--timerange <timerange>)。
  • 避免使用 --timeframe-detail(这会将大量额外数据加载到内存中)。
  • 减少并行进程数量 (-j <n>)。
  • 增加机器的内存容量。
  • 如果你使用了大量带有 .range 功能的参数,请使用 --analyze-per-epoch

该目标点此前已被评估过。

如果你看到 The objective has been evaluated at this point before. 的提示,这意味着你的搜索空间已经耗尽或接近耗尽。换句话说,搜索空间中的所有点都已被尝试过(或已陷入局部最优),hyperopt 无法再找到尚未尝试过的多维空间中的新点。Freqtrade 会通过在此情况下引入新的随机点来尝试缓解“局部最优”问题。

示例:

buy_ema_short = IntParameter(5, 20, default=10, space="buy", optimize=True)
# This is the only parameter in the buy space

buy_ema_short 参数空间有 15 个可能的取值(5, 6, ... 19, 20)。如果你仅对买入空间运行 hyperopt,那么 hyperopt 在选项耗尽前只有 15 个值可尝试。因此,你的训练轮数(epochs)应与可能的取值数量相匹配,或者当你发现大量出现 The objective has been evaluated at this point before. 警告时,应准备中断运行。

显示 Hyperopt 结果的详细信息

在完成所需轮数的 Hyperopt 运行后,你可以后续列出所有结果进行分析,筛选出最佳或盈利的结果,并查看之前任意一轮训练的详细信息。这可以通过 hyperopt-listhyperopt-show 子命令实现。这些子命令的使用方法在 工具 章节中有详细说明。

从策略中输出调试信息

如果你想从策略中输出调试信息,可以使用 logging 模块。默认情况下,Freqtrade 会输出所有级别为 INFO 及以上的日志信息。

import logging


logger = logging.getLogger(__name__)


class MyAwesomeStrategy(IStrategy):
    ...

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        logger.info("This is a debug message")
        ...

使用 print

通过 print() 输出的信息不会显示在 hyperopt 的输出中,除非禁用了并行处理(-j 1)。建议使用 logging 模块代替。

验证回测结果

在将优化后的策略集成到你的策略中后,你应该对这个策略进行回测,以确保一切按预期工作。

为了获得与 Hyperopt 期间相同的结果(交易次数、持仓时间、收益等),请在回测时使用与 Hyperopt 相同的配置和参数(时间范围、时间周期等)。

为什么我的回测结果与 Hyperopt 结果不一致?

如果结果不一致,请检查以下因素:

  • 你可能在 populate_indicators() 中添加了需要进行优化的参数,而这些参数在整个优化过程中只会计算一次。例如,如果你正在尝试优化多个 SMA 周期值,应将可优化的周期参数放在 populate_entry_trend() 中,因为该函数会在每次迭代时重新计算。参见 优化指标参数
  • 如果你已禁用将 Hyperopt 参数自动导出到 JSON 参数文件的功能,请仔细检查以确保已正确地将所有优化后的值转移到你的策略中。
  • 检查日志,确认设置了哪些参数以及使用了哪些值。
  • 特别注意 stoploss(止损)、max_open_trades(最大同时开仓数)和 trailing stoploss(追踪止损)参数,因为这些参数通常在配置文件中设置,会覆盖策略中的更改。请检查你的回测日志,确保没有参数被配置文件意外覆盖(如 stoplossmax_open_tradestrailing_stop)。
  • 确认你没有使用意外的参数 JSON 文件覆盖策略中的参数或默认的 Hyperopt 设置。
  • 确认在回测中启用的任何保护机制在 Hyperopt 时也已启用,反之亦然。当使用 --space protection 时,保护机制会自动在 Hyperopt 中启用。