From 3fcf6559abc646d5da7ab04b0cdbf8c55c2fd31d Mon Sep 17 00:00:00 2001 From: viotemp1 Date: Mon, 31 Mar 2025 13:48:12 +0300 Subject: [PATCH] change from skopt.space.Real to optuna.distributions.FloatDistribution --- .../optimize/hyperopt/hyperopt_interface.py | 28 +++++++--- .../optimize/hyperopt/hyperopt_optimizer.py | 33 +++++------ freqtrade/strategy/parameters.py | 55 +++++++++++-------- tests/optimize/test_hyperopt.py | 7 ++- tests/strategy/test_interface.py | 5 +- 5 files changed, 73 insertions(+), 55 deletions(-) diff --git a/freqtrade/optimize/hyperopt/hyperopt_interface.py b/freqtrade/optimize/hyperopt/hyperopt_interface.py index aae1d22e7..d88a0a27a 100644 --- a/freqtrade/optimize/hyperopt/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt/hyperopt_interface.py @@ -9,13 +9,17 @@ from abc import ABC from typing import TypeAlias from sklearn.base import RegressorMixin -from skopt.space import Categorical, Dimension, Integer +from skopt.space import Dimension # , Integer, Categorical, from freqtrade.constants import Config from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import round_dict from freqtrade.optimize.space import SKDecimal from freqtrade.strategy import IStrategy +from freqtrade.strategy.parameters import ( + ft_CategoricalDistribution, + ft_IntDistribution, +) logger = logging.getLogger(__name__) @@ -133,9 +137,12 @@ class IHyperOpt(ABC): logger.info(f"Max roi table: {round_dict(self.generate_roi_table(p), 3)}") return [ - Integer(roi_limits["roi_t1_min"], roi_limits["roi_t1_max"], name="roi_t1"), - Integer(roi_limits["roi_t2_min"], roi_limits["roi_t2_max"], name="roi_t2"), - Integer(roi_limits["roi_t3_min"], roi_limits["roi_t3_max"], name="roi_t3"), + # Integer(roi_limits["roi_t1_min"], roi_limits["roi_t1_max"], name="roi_t1"), + ft_IntDistribution("roi_t1", roi_limits["roi_t1_min"], roi_limits["roi_t1_max"]), + # Integer(roi_limits["roi_t2_min"], roi_limits["roi_t2_max"], name="roi_t2"), + ft_IntDistribution("roi_t2", roi_limits["roi_t2_min"], roi_limits["roi_t2_max"]), + # Integer(roi_limits["roi_t3_min"], roi_limits["roi_t3_max"], name="roi_t3"), + ft_IntDistribution("roi_t3", roi_limits["roi_t3_min"], roi_limits["roi_t3_max"]), SKDecimal( roi_limits["roi_p1_min"], roi_limits["roi_p1_max"], decimals=3, name="roi_p1" ), @@ -184,7 +191,8 @@ class IHyperOpt(ABC): # This parameter is included into the hyperspace dimensions rather than assigning # it explicitly in the code in order to have it printed in the results along with # other 'trailing' hyperspace parameters. - Categorical([True], name="trailing_stop"), + # Categorical([True], name="trailing_stop"), + ft_CategoricalDistribution("trailing_stop", [True]), SKDecimal(0.01, 0.35, decimals=3, name="trailing_stop_positive"), # 'trailing_stop_positive_offset' should be greater than 'trailing_stop_positive', # so this intermediate parameter is used as the value of the difference between @@ -192,7 +200,8 @@ class IHyperOpt(ABC): # generate_trailing_params() method. # This is similar to the hyperspace dimensions used for constructing the ROI tables. SKDecimal(0.001, 0.1, decimals=3, name="trailing_stop_positive_offset_p1"), - Categorical([True, False], name="trailing_only_offset_is_reached"), + # Categorical([True, False], name="trailing_only_offset_is_reached"), + ft_CategoricalDistribution("trailing_only_offset_is_reached", [True, False]), ] def max_open_trades_space(self) -> list[Dimension]: @@ -201,9 +210,10 @@ class IHyperOpt(ABC): You may override it in your custom Hyperopt class. """ - return [ - Integer(-1, 10, name="max_open_trades"), - ] + # return [ + # Integer(-1, 10, name="max_open_trades"), + # ] + return [ft_IntDistribution("max_open_trades", -1, 10)] # This is needed for proper unpickling the class attribute timeframe # which is set to the actual value by the resolver. diff --git a/freqtrade/optimize/hyperopt/hyperopt_optimizer.py b/freqtrade/optimize/hyperopt/hyperopt_optimizer.py index 944da5258..af42f486d 100644 --- a/freqtrade/optimize/hyperopt/hyperopt_optimizer.py +++ b/freqtrade/optimize/hyperopt/hyperopt_optimizer.py @@ -36,10 +36,14 @@ with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=FutureWarning) # from skopt import Optimizer import optuna - from skopt.space import Categorical, Dimension, Integer, Real + from skopt.space import Dimension from freqtrade.optimize.space.decimalspace import SKDecimal - from freqtrade.strategy.parameters import ft_CategoricalDistribution, ft_IntDistribution + from freqtrade.strategy.parameters import ( + ft_CategoricalDistribution, + ft_FloatDistribution, + ft_IntDistribution, + ) logger = logging.getLogger(__name__) @@ -407,27 +411,18 @@ class HyperOptimizer: log=False, step=1 / pow(10, original_dim.decimals), ) - elif isinstance(original_dim, Integer): - o_dimensions[original_dim.name] = optuna.distributions.IntDistribution( - original_dim.low, original_dim.high, log=False, step=1 - ) - elif isinstance(original_dim, Real): - o_dimensions[original_dim.name] = optuna.distributions.FloatDistribution( - original_dim.low, - original_dim.high, - log=False, - ) - elif isinstance(original_dim, Categorical): - o_dimensions[original_dim.name] = optuna.distributions.CategoricalDistribution( - list(original_dim.bounds) - ) # for preparing to remove old skopt spaces - elif isinstance(original_dim, ft_CategoricalDistribution) or isinstance( - original_dim, ft_IntDistribution + elif ( + isinstance(original_dim, ft_CategoricalDistribution) + or isinstance(original_dim, ft_IntDistribution) + or isinstance(original_dim, ft_FloatDistribution) ): o_dimensions[original_dim.name] = original_dim else: - raise Exception(f"Unknown search space {original_dim} / {type(original_dim)}") + raise Exception( + f"Unknown search space {original_dim.name} - {original_dim} / \ + {type(original_dim)}" + ) # logger.info(f"convert_dimensions_to_optuna_space: {s_dimensions} - {o_dimensions}") return o_dimensions diff --git a/freqtrade/strategy/parameters.py b/freqtrade/strategy/parameters.py index 24047c844..f1b025644 100644 --- a/freqtrade/strategy/parameters.py +++ b/freqtrade/strategy/parameters.py @@ -14,9 +14,9 @@ from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer with suppress(ImportError): - from optuna.distributions import CategoricalDistribution, IntDistribution - from skopt.space import Integer, Real # Categorical + from optuna.distributions import CategoricalDistribution, FloatDistribution, IntDistribution + # from skopt.space import Integer, Real # Categorical from freqtrade.optimize.space import SKDecimal from freqtrade.exceptions import OperationalException @@ -26,25 +26,37 @@ logger = logging.getLogger(__name__) class ft_CategoricalDistribution(CategoricalDistribution): - name: str - def __init__( self, + name: str, categories: Sequence[Any], **kwargs, ): + self.name = name return super().__init__(categories) class ft_IntDistribution(IntDistribution): - name: str - def __init__( self, - low: int, - high: int, + name: str, + low: int | float, + high: int | float, **kwargs, ): + self.name = name + return super().__init__(int(low), int(high), **kwargs) + + +class ft_FloatDistribution(FloatDistribution): + def __init__( + self, + name: str, + low: float, + high: float, + **kwargs, + ): + self.name = name return super().__init__(low, high, **kwargs) @@ -76,7 +88,7 @@ class BaseParameter(ABC): :param optimize: Include parameter in hyperopt optimizations. :param load: Load parameter value from {space}_params. :param kwargs: Extra parameters to optuna.distributions. - (IntDistribution|Real|CategoricalDistribution). + (IntDistribution|FloatDistribution|CategoricalDistribution). """ if "name" in kwargs: raise OperationalException( @@ -94,7 +106,9 @@ class BaseParameter(ABC): @abstractmethod def get_space( self, name: str - ) -> Union["ft_IntDistribution", "Real", "SKDecimal", "ft_CategoricalDistribution"]: + ) -> Union[ + "ft_IntDistribution", "ft_FloatDistribution", "SKDecimal", "ft_CategoricalDistribution" + ]: """ Get-space - will be used by Hyperopt to get the hyperopt Space """ @@ -136,7 +150,7 @@ class NumericParameter(BaseParameter): parameter fieldname is prefixed with 'buy_' or 'sell_'. :param optimize: Include parameter in hyperopt optimizations. :param load: Load parameter value from {space}_params. - :param kwargs: Extra parameters to skopt.space.*. + :param kwargs: Extra parameters to optuna.distributions.*. """ if high is not None and isinstance(low, Sequence): raise OperationalException(f"{self.__class__.__name__} space invalid.") @@ -185,15 +199,13 @@ class IntParameter(NumericParameter): low=low, high=high, default=default, space=space, optimize=optimize, load=load, **kwargs ) - def get_space(self, name: str) -> "Integer": + def get_space(self, name: str) -> "ft_IntDistribution": """ Create optuna distribution space. :param name: A name of parameter field. """ # return Integer(low=self.low, high=self.high, name=name, **self._space_params) - result = ft_IntDistribution(self.low, self.high, **self._space_params) - result.name = name - return result + return ft_IntDistribution(name, self.low, self.high, **self._space_params) @property def range(self): @@ -235,18 +247,19 @@ class RealParameter(NumericParameter): parameter fieldname is prefixed with 'buy_' or 'sell_'. :param optimize: Include parameter in hyperopt optimizations. :param load: Load parameter value from {space}_params. - :param kwargs: Extra parameters to skopt.space.Real. + :param kwargs: Extra parameters to optuna.distributions.FloatDistribution. """ super().__init__( low=low, high=high, default=default, space=space, optimize=optimize, load=load, **kwargs ) - def get_space(self, name: str) -> "Real": + def get_space(self, name: str) -> "ft_FloatDistribution": """ Create skopt optimization space. :param name: A name of parameter field. """ - return Real(low=self.low, high=self.high, name=name, **self._space_params) + return ft_FloatDistribution(name, self.low, self.high, **self._space_params) + # return Real(low=self.low, high=self.high, name=name, **self._space_params) class DecimalParameter(NumericParameter): @@ -349,10 +362,8 @@ class CategoricalParameter(BaseParameter): Create optuna distribution space. :param name: A name of parameter field. """ - # Categorical(self.opt_range, name=name, **self._space_params) - result = ft_CategoricalDistribution(self.opt_range) - result.name = name - return result + # return Categorical(self.opt_range, name=name, **self._space_params) + return ft_CategoricalDistribution(name, self.opt_range) @property def range(self): diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 3ca35ca04..ad684dd19 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -7,7 +7,6 @@ from unittest.mock import ANY, MagicMock, PropertyMock import pandas as pd import pytest from filelock import Timeout -from skopt.space import Integer from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt from freqtrade.data.history import load_data @@ -19,6 +18,9 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.space import SKDecimal from freqtrade.strategy import IntParameter + +# from skopt.space import Integer +from freqtrade.strategy.parameters import ft_IntDistribution from freqtrade.util import dt_utc from tests.conftest import ( CURRENT_TEST_STRATEGY, @@ -1304,7 +1306,8 @@ def test_max_open_trades_consistency(mocker, hyperopt_conf, tmp_path, fee) -> No assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto) hyperopt.hyperopter.custom_hyperopt.max_open_trades_space = lambda: [ - Integer(1, 10, name="max_open_trades") + # Integer(1, 10, name="max_open_trades") + ft_IntDistribution("max_open_trades", 1, 10) ] first_time_evaluated = False diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 8cbe40d13..b512e578c 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -895,8 +895,7 @@ def test_is_informative_pairs_callback(default_conf): def test_hyperopt_parameters(): HyperoptStateContainer.set_state(HyperoptState.INDICATORS) - from optuna.distributions import CategoricalDistribution, IntDistribution - from skopt.space import Real + from optuna.distributions import CategoricalDistribution, FloatDistribution, IntDistribution with pytest.raises(OperationalException, match=r"Name is determined.*"): IntParameter(low=0, high=5, default=1, name="hello") @@ -939,7 +938,7 @@ def test_hyperopt_parameters(): fltpar = RealParameter(low=0.0, high=5.5, default=1.0, space="buy") assert fltpar.value == 1 - assert isinstance(fltpar.get_space(""), Real) + assert isinstance(fltpar.get_space(""), FloatDistribution) fltpar = DecimalParameter(low=0.0, high=0.5, default=0.14, decimals=1, space="buy") assert fltpar.value == 0.1