diff --git a/freqtrade/optimize/hyperopt/hyperopt.py b/freqtrade/optimize/hyperopt/hyperopt.py index 5e31cf84b..d86252a49 100644 --- a/freqtrade/optimize/hyperopt/hyperopt.py +++ b/freqtrade/optimize/hyperopt/hyperopt.py @@ -331,6 +331,7 @@ class Hyperopt: self.config, self.hyperopter.get_strategy_name(), self.current_best_epoch, + self.hyperopter.o_dimensions, ) HyperoptTools.show_epoch_details( diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index ff4747781..f2178d63e 100644 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -7,6 +7,7 @@ from typing import Any import numpy as np import rapidjson +from optuna.distributions import BaseDistribution from pandas import isna, json_normalize from freqtrade.constants import FTHYPT_FILEVERSION, Config @@ -14,6 +15,7 @@ from freqtrade.enums import HyperoptState from freqtrade.exceptions import OperationalException from freqtrade.misc import deep_merge_dicts, round_dict, safe_value_fallback2 from freqtrade.optimize.hyperopt_epoch_filters import hyperopt_filter_epochs +from freqtrade.optimize.space import SKDecimal, _adjust_discrete_uniform logger = logging.getLogger(__name__) @@ -61,11 +63,26 @@ class HyperoptTools: return None @staticmethod - def export_params(params, strategy_name: str, filename: Path): + def export_params( + params, + strategy_name: str, + filename: Path, + o_dimensions: dict[str, BaseDistribution] | None = None, + ): """ Generate files """ final_params = deepcopy(params["params_not_optimized"]) + if o_dimensions: + for key, val in params["params_details"].items(): + if isinstance(val, dict): + for key1, val1 in val.items(): + if isinstance(o_dimensions.get(key1), SKDecimal): + step = getattr(o_dimensions.get(key1), "step", None) + if step: + params["params_details"][key][key1] = _adjust_discrete_uniform( + val1, step + ) final_params = deep_merge_dicts(params["params_details"], final_params) final_params = { "strategy_name": strategy_name, @@ -73,6 +90,7 @@ class HyperoptTools: "ft_stratparam_v": 1, "export_time": datetime.now(timezone.utc), } + logger.info(f"Dumping parameters to {filename}") with filename.open("w") as f: rapidjson.dump( @@ -93,12 +111,19 @@ class HyperoptTools: return params @staticmethod - def try_export_params(config: Config, strategy_name: str, params: dict): + def try_export_params( + config: Config, + strategy_name: str, + params: dict, + o_dimensions: dict[str, BaseDistribution] | None = None, + ): if params.get(FTHYPT_FILEVERSION, 1) >= 2 and not config.get("disableparamexport", False): # Export parameters ... fn = HyperoptTools.get_strategy_filename(config, strategy_name) if fn: - HyperoptTools.export_params(params, strategy_name, fn.with_suffix(".json")) + HyperoptTools.export_params( + params, strategy_name, fn.with_suffix(".json"), o_dimensions + ) else: logger.warning("Strategy not found, not exporting parameter file.") diff --git a/freqtrade/optimize/space/__init__.py b/freqtrade/optimize/space/__init__.py index 77552b9f2..a32937864 100644 --- a/freqtrade/optimize/space/__init__.py +++ b/freqtrade/optimize/space/__init__.py @@ -1,4 +1,4 @@ -from .decimalspace import SKDecimal +from .decimalspace import SKDecimal, _adjust_discrete_uniform from .optunaspaces import ( DimensionProtocol, ft_CategoricalDistribution, @@ -13,10 +13,4 @@ Categorical = ft_CategoricalDistribution Integer = ft_IntDistribution Real = ft_FloatDistribution -__all__ = [ - "Categorical", - "Dimension", - "Integer", - "Real", - "SKDecimal", -] +__all__ = ["Categorical", "Dimension", "Integer", "Real", "SKDecimal", "_adjust_discrete_uniform"] diff --git a/freqtrade/optimize/space/decimalspace.py b/freqtrade/optimize/space/decimalspace.py index c93e49c01..adb90eaf7 100644 --- a/freqtrade/optimize/space/decimalspace.py +++ b/freqtrade/optimize/space/decimalspace.py @@ -1,3 +1,5 @@ +import decimal + from optuna.distributions import FloatDistribution @@ -20,7 +22,32 @@ class SKDecimal(FloatDistribution): self.name = name super().__init__( - low=low, - high=high, + low=_adjust_discrete_uniform(low, self.step), + high=_adjust_discrete_uniform_high(low, high, self.step), step=self.step, ) + + +def _adjust_discrete_uniform_high(low: float, high: float, step: float | None) -> float: + if step: + d_high = decimal.Decimal(str(high)) + d_low = decimal.Decimal(str(low)) + d_step = decimal.Decimal(str(step)) + + d_r = d_high - d_low + + if d_r % d_step != decimal.Decimal("0"): + high = float((d_r // d_step) * d_step + d_low) + + return high + + +def _adjust_discrete_uniform(val: float, step: float | None) -> float: + if step: + d_val = decimal.Decimal(str(val)) + d_step = decimal.Decimal(str(step)) + + if d_val % d_step != decimal.Decimal("0"): + val = float((d_val // d_step) * d_step) + + return val