change from skopt.space.Real to optuna.distributions.FloatDistribution

This commit is contained in:
viotemp1
2025-03-31 13:48:12 +03:00
parent 85f4a8daea
commit 3fcf6559ab
5 changed files with 73 additions and 55 deletions
@@ -9,13 +9,17 @@ from abc import ABC
from typing import TypeAlias from typing import TypeAlias
from sklearn.base import RegressorMixin 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.constants import Config
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import round_dict from freqtrade.misc import round_dict
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
from freqtrade.strategy import IStrategy from freqtrade.strategy import IStrategy
from freqtrade.strategy.parameters import (
ft_CategoricalDistribution,
ft_IntDistribution,
)
logger = logging.getLogger(__name__) 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)}") logger.info(f"Max roi table: {round_dict(self.generate_roi_table(p), 3)}")
return [ return [
Integer(roi_limits["roi_t1_min"], roi_limits["roi_t1_max"], name="roi_t1"), # 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"), ft_IntDistribution("roi_t1", roi_limits["roi_t1_min"], roi_limits["roi_t1_max"]),
Integer(roi_limits["roi_t3_min"], roi_limits["roi_t3_max"], name="roi_t3"), # 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( SKDecimal(
roi_limits["roi_p1_min"], roi_limits["roi_p1_max"], decimals=3, name="roi_p1" 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 # 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 # it explicitly in the code in order to have it printed in the results along with
# other 'trailing' hyperspace parameters. # 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"), SKDecimal(0.01, 0.35, decimals=3, name="trailing_stop_positive"),
# 'trailing_stop_positive_offset' should be greater than '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 # so this intermediate parameter is used as the value of the difference between
@@ -192,7 +200,8 @@ class IHyperOpt(ABC):
# generate_trailing_params() method. # generate_trailing_params() method.
# This is similar to the hyperspace dimensions used for constructing the ROI tables. # 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"), 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]: def max_open_trades_space(self) -> list[Dimension]:
@@ -201,9 +210,10 @@ class IHyperOpt(ABC):
You may override it in your custom Hyperopt class. You may override it in your custom Hyperopt class.
""" """
return [ # return [
Integer(-1, 10, name="max_open_trades"), # 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 # This is needed for proper unpickling the class attribute timeframe
# which is set to the actual value by the resolver. # which is set to the actual value by the resolver.
@@ -36,10 +36,14 @@ with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=FutureWarning) warnings.filterwarnings("ignore", category=FutureWarning)
# from skopt import Optimizer # from skopt import Optimizer
import optuna import optuna
from skopt.space import Categorical, Dimension, Integer, Real from skopt.space import Dimension
from freqtrade.optimize.space.decimalspace import SKDecimal 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__) logger = logging.getLogger(__name__)
@@ -407,27 +411,18 @@ class HyperOptimizer:
log=False, log=False,
step=1 / pow(10, original_dim.decimals), 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 # for preparing to remove old skopt spaces
elif isinstance(original_dim, ft_CategoricalDistribution) or isinstance( elif (
original_dim, ft_IntDistribution isinstance(original_dim, ft_CategoricalDistribution)
or isinstance(original_dim, ft_IntDistribution)
or isinstance(original_dim, ft_FloatDistribution)
): ):
o_dimensions[original_dim.name] = original_dim o_dimensions[original_dim.name] = original_dim
else: 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}") # logger.info(f"convert_dimensions_to_optuna_space: {s_dimensions} - {o_dimensions}")
return o_dimensions return o_dimensions
+33 -22
View File
@@ -14,9 +14,9 @@ from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer
with suppress(ImportError): with suppress(ImportError):
from optuna.distributions import CategoricalDistribution, IntDistribution from optuna.distributions import CategoricalDistribution, FloatDistribution, IntDistribution
from skopt.space import Integer, Real # Categorical
# from skopt.space import Integer, Real # Categorical
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@@ -26,25 +26,37 @@ logger = logging.getLogger(__name__)
class ft_CategoricalDistribution(CategoricalDistribution): class ft_CategoricalDistribution(CategoricalDistribution):
name: str
def __init__( def __init__(
self, self,
name: str,
categories: Sequence[Any], categories: Sequence[Any],
**kwargs, **kwargs,
): ):
self.name = name
return super().__init__(categories) return super().__init__(categories)
class ft_IntDistribution(IntDistribution): class ft_IntDistribution(IntDistribution):
name: str
def __init__( def __init__(
self, self,
low: int, name: str,
high: int, low: int | float,
high: int | float,
**kwargs, **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) return super().__init__(low, high, **kwargs)
@@ -76,7 +88,7 @@ class BaseParameter(ABC):
:param optimize: Include parameter in hyperopt optimizations. :param optimize: Include parameter in hyperopt optimizations.
:param load: Load parameter value from {space}_params. :param load: Load parameter value from {space}_params.
:param kwargs: Extra parameters to optuna.distributions. :param kwargs: Extra parameters to optuna.distributions.
(IntDistribution|Real|CategoricalDistribution). (IntDistribution|FloatDistribution|CategoricalDistribution).
""" """
if "name" in kwargs: if "name" in kwargs:
raise OperationalException( raise OperationalException(
@@ -94,7 +106,9 @@ class BaseParameter(ABC):
@abstractmethod @abstractmethod
def get_space( def get_space(
self, name: str 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 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_'. parameter fieldname is prefixed with 'buy_' or 'sell_'.
:param optimize: Include parameter in hyperopt optimizations. :param optimize: Include parameter in hyperopt optimizations.
:param load: Load parameter value from {space}_params. :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): if high is not None and isinstance(low, Sequence):
raise OperationalException(f"{self.__class__.__name__} space invalid.") 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 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. Create optuna distribution space.
:param name: A name of parameter field. :param name: A name of parameter field.
""" """
# return Integer(low=self.low, high=self.high, name=name, **self._space_params) # return Integer(low=self.low, high=self.high, name=name, **self._space_params)
result = ft_IntDistribution(self.low, self.high, **self._space_params) return ft_IntDistribution(name, self.low, self.high, **self._space_params)
result.name = name
return result
@property @property
def range(self): def range(self):
@@ -235,18 +247,19 @@ class RealParameter(NumericParameter):
parameter fieldname is prefixed with 'buy_' or 'sell_'. parameter fieldname is prefixed with 'buy_' or 'sell_'.
:param optimize: Include parameter in hyperopt optimizations. :param optimize: Include parameter in hyperopt optimizations.
:param load: Load parameter value from {space}_params. :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__( super().__init__(
low=low, high=high, default=default, space=space, optimize=optimize, load=load, **kwargs 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. Create skopt optimization space.
:param name: A name of parameter field. :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): class DecimalParameter(NumericParameter):
@@ -349,10 +362,8 @@ class CategoricalParameter(BaseParameter):
Create optuna distribution space. Create optuna distribution space.
:param name: A name of parameter field. :param name: A name of parameter field.
""" """
# Categorical(self.opt_range, name=name, **self._space_params) # return Categorical(self.opt_range, name=name, **self._space_params)
result = ft_CategoricalDistribution(self.opt_range) return ft_CategoricalDistribution(name, self.opt_range)
result.name = name
return result
@property @property
def range(self): def range(self):
+5 -2
View File
@@ -7,7 +7,6 @@ from unittest.mock import ANY, MagicMock, PropertyMock
import pandas as pd import pandas as pd
import pytest import pytest
from filelock import Timeout from filelock import Timeout
from skopt.space import Integer
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
from freqtrade.data.history import load_data 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.optimize_reports import generate_strategy_stats
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
from freqtrade.strategy import IntParameter from freqtrade.strategy import IntParameter
# from skopt.space import Integer
from freqtrade.strategy.parameters import ft_IntDistribution
from freqtrade.util import dt_utc from freqtrade.util import dt_utc
from tests.conftest import ( from tests.conftest import (
CURRENT_TEST_STRATEGY, 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) assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
hyperopt.hyperopter.custom_hyperopt.max_open_trades_space = lambda: [ 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 first_time_evaluated = False
+2 -3
View File
@@ -895,8 +895,7 @@ def test_is_informative_pairs_callback(default_conf):
def test_hyperopt_parameters(): def test_hyperopt_parameters():
HyperoptStateContainer.set_state(HyperoptState.INDICATORS) HyperoptStateContainer.set_state(HyperoptState.INDICATORS)
from optuna.distributions import CategoricalDistribution, IntDistribution from optuna.distributions import CategoricalDistribution, FloatDistribution, IntDistribution
from skopt.space import Real
with pytest.raises(OperationalException, match=r"Name is determined.*"): with pytest.raises(OperationalException, match=r"Name is determined.*"):
IntParameter(low=0, high=5, default=1, name="hello") 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") fltpar = RealParameter(low=0.0, high=5.5, default=1.0, space="buy")
assert fltpar.value == 1 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") fltpar = DecimalParameter(low=0.0, high=0.5, default=0.14, decimals=1, space="buy")
assert fltpar.value == 0.1 assert fltpar.value == 0.1