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

View File

@@ -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.

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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