Skip to content

高级超参数优化

本页解释了一些高级的超参数优化主题,可能需要比创建一个普通的超参数优化类更高的编程技能和Python知识。

创建和使用自定义损失函数

要使用自定义损失函数类,请确保在您的自定义超参数优化损失类中定义了函数hyperopt_loss_function。 对于下面的示例,您需要在调用hyperopt时添加命令行参数--hyperopt-loss SuperDuperHyperOptLoss, 以便使用此函数。

下面是一个示例,它与默认的Hyperopt损失实现相同。完整的示例可以在userdata/hyperopts中找到。

from datetime import datetime
from typing import Any, Dict

from pandas import DataFrame

from freqtrade.constants import Config
from freqtrade.optimize.hyperopt import IHyperOptLoss

TARGET_TRADES = 600
EXPECTED_MAX_PROFIT = 3.0
MAX_ACCEPTED_TRADE_DURATION = 300

class SuperDuperHyperOptLoss(IHyperOptLoss):
    """
    定义用于超参数优化的默认损失函数
    """

    @staticmethod
    def hyperopt_loss_function(results: DataFrame, trade_count: int,
                               min_date: datetime, max_date: datetime,
                               config: Config, processed: Dict[str, DataFrame],
                               backtest_stats: Dict[str, Any],
                               *args, **kwargs) -> float:
        """
        目标函数,更好的结果返回较小的数字
        这是以前的算法(直到现在在 freqtrade 中仍在使用)。
        权重如下分配:
        * 0.4 给交易持续时间
        * 0.25:避免交易损失
        * 1.0 给总利润,与上面定义的 `EXPECTED_MAX_PROFIT` 期望值比较
        """
        total_profit = results['profit_ratio'].sum()
        trade_duration = results['trade_duration'].mean()

        trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
        profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
        duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
        result = trade_loss + profit_loss + duration_loss
        return result

目前,这些参数是:

  • results:包含结果交易的 DataFrame。 在 results 中,有以下列(与使用 --export trades 导出的回测输出文件对应):
    pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date, close_rate, fee_close, amount, trade_duration, is_open, exit_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs
  • trade_count:交易数量(与 len(results) 相同)
  • min_date:使用的时间范围的起始日期
  • max_date:使用的时间范围的结束日期
  • config:使用的 Config 对象(注意:如果某些与策略相关的参数是超参数空间的一部分,则不会在此更新)。
  • processed:使用的数据 DataFrame 字典,键为交易对,包含用于回测的数据。
  • backtest_stats:使用与回测文件 "strategy" 细节结构中相同格式的回测统计信息。可用字段可以在 optimize_reports.py 中的 generate_strategy_stats() 中查看。

此函数需要返回一个浮点数(float)。较小的数字会被解释为更好的结果。参数和平衡控制由您自己决定。

注意

该函数每个 epoch 调用一次,请确保此函数尽可能优化,以避免不必要地减慢 hyperopt 的速度。

*args**kwargs

请在接口中保留参数 *args**kwargs 以便允许我们在未来扩展此接口。

覆盖预定义的空间

要覆盖预定义的空间(roi_spacegenerate_roi_tablestoploss_spacetrailing_spacemax_open_trades_space),请定义一个名为 Hyperopt 的嵌套类,并按以下方式定义所需的空间:

from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal

class MyAwesomeStrategy(IStrategy):
    class HyperOpt:
        # 定义自定义的止损空间。
        def stoploss_space():
            return [SKDecimal(-0.05, -0.01, decimals=3, name='止损')]

        # 定义自定义的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: Dict) -> Dict[int, float]:

            roi_table = {}
            roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
            roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2']
            roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1']
            roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0

            return roi_table

        def trailing_space() -> List[Dimension]:
            # 这里的所有参数都是必需的,你只能修改它们的类型或范围。
            return [
                # 如果优化trailing_stop,则将其设为True,我们假设始终使用trailing stop。
                Categorical([True], name='是否使用trailing_stop'),

                SKDecimal(0.01, 0.35, decimals=3, name='trailing_stop_positive'),
                # 'trailing_stop_positive_offset' 应大于 'trailing_stop_positive',
                # 所以这个中间参数被用作它们之间差值的值。
                # 'trailing_stop_positive_offset' 的值在 generate_trailing_params() 方法中构造。
                # 这类似于用于构造ROI表的超空间维度。
                SKDecimal(0.001, 0.1, decimals=3, name='trailing_stop_positive_offset_p1'),

                Categorical([True, False], name='trailing_only_offset_is_reached'),
            ]

        # 定义自定义的最大开放交易数空间
        def max_open_trades_space(self) -> List[Dimension]:
            return [
                Integer(-1, 10, name='max_open_trades'),
            ]

注意

所有覆盖是可选的,可以根据需要混合/匹配。### 动态参数

参数也可以动态定义,但是在 bot_start() 回调函数 被调用之后,必须对实例可用。

class MyAwesomeStrategy(IStrategy):

    def bot_start(self, **kwargs) -> None:
        self.buy_adx = IntParameter(20, 30, default=30, optimize=True)

    # ...

警告

以这种方式创建的参数将不会显示在 list-strategies 的参数计数中。

覆盖基本估算器

您可以通过在 Hyperopt 子类中实现 generate_estimator() 来定义自己的估算器。

class MyAwesomeStrategy(IStrategy):
    class HyperOpt:
        def generate_estimator(dimensions: List['Dimension'], **kwargs):
            return "RF"

可选的值可以是 "GP"、"RF"、"ET"、"GBRT"(详细信息可以在scikit-optimize文档中找到),或者是 "继承自RegressorMixin类的实例(来自sklearn),其中predict方法具有可选的return_std参数, 可以返回std(Y|x)以及E[Y|x]

需要进行一些调研才能找到其他回归器。

使用额外参数的 ExtraTreesRegressor("ET")的示例:

class MyAwesomeStrategy(IStrategy):
    class HyperOpt:
        def generate_estimator(dimensions: List['Dimension'], **kwargs):
            from skopt.learning import ExtraTreesRegressor
            # 对应于 "ET",但允许额外的参数。
            return ExtraTreesRegressor(n_estimators=100)

dimensions参数是要进行优化的参数对应的 skopt.space.Dimension 对象的列表。它可用于为 skopt.learning.GaussianProcessRegressor 回归器创建等向性核函数。以下是一个示例:

class MyAwesomeStrategy(IStrategy):
    class HyperOpt:
        def generate_estimator(dimensions: List['Dimension'], **kwargs):
            from skopt.utils import cook_estimator
            from skopt.learning.gaussian_process.kernels import (Matern, ConstantKernel)
            kernel_bounds = (0.0001, 10000)
            kernel = (
                ConstantKernel(1.0, kernel_bounds) * 
                Matern(length_scale=np.ones(len(dimensions)), length_scale_bounds=[kernel_bounds for d in dimensions], nu=2.5)
            )
            kernel += (
                ConstantKernel(1.0, kernel_bounds) * 
                Matern(length_scale=np.ones(len(dimensions)), length_scale_bounds=[kernel_bounds for d in dimensions], nu=1.5)
            )

            return cook_estimator("GP", space=dimensions, kernel=kernel, n_restarts_optimizer=2)

注意

虽然可以提供自定义的回归器,但是你作为用户需要进行调研来了解可能的参数,并分析/了解应该使用哪些参数。 如果对此不确定,最好使用其中一个默认值("ET"已被证明是最通用的),而不使用其他参数。

空间选项

对于额外的空间,scikit-optimize(与Freqtrade结合使用)提供以下空间类型:

  • Categorical - 从一组类别中选择(例如 Categorical(['a', 'b', 'c'], name="cat")
  • Integer - 从一个整数范围中选择(例如 Integer(1, 10, name='rsi')
  • SKDecimal - 从一个具有有限精度的十进制数范围中选择(例如 SKDecimal(0.1, 0.5, decimals=3, name='adx'))。仅在Freqtrade中可用
  • Real - 从一个具有完整精度的十进制数范围中选择(例如 Real(0.1, 0.5, name='adx')

您可以从freqtrade.optimize.space导入所有这些空间,尽管CategoricalIntegerReal只是它们对应的scikit-optimize空间的别名。SKDecimal由Freqtrade提供,用于加速优化过程。

from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal, Real  # noqa

SKDecimal vs. Real

我们建议在几乎所有情况下使用SKDecimal而不是Real空间。虽然Real空间提供了完整的精度(高达约16位小数),但这种精度很少需要,并且会导致不必要的超时时间。

假设定义了一个相当小的空间(SKDecimal(0.10, 0.15, decimals=2, name='xxx')),SKDecimal将有5个可能的取值([0.10, 0.11, 0.12, 0.13, 0.14, 0.15])。

另一方面,对应的Real空间Real(0.10, 0.15 name='xxx')几乎有无限多的可能([0.10, 0.010000000001, 0.010000000002, ... 0.014999999999, 0.01500000000])。