Merge pull request #12479 from freqtrade/feat/hyperopt_custom_spaces

Add support for custom hyperopt spaces
This commit is contained in:
Matthias
2025-11-08 16:01:42 +01:00
committed by GitHub
18 changed files with 332 additions and 284 deletions

View File

@@ -5,7 +5,10 @@ Definition of cli arguments used in arguments.py
from argparse import ArgumentTypeError
from freqtrade import constants
from freqtrade.constants import HYPEROPT_BUILTIN_SPACES, HYPEROPT_LOSS_BUILTIN
from freqtrade.constants import (
HYPEROPT_BUILTIN_SPACE_OPTIONS,
HYPEROPT_LOSS_BUILTIN,
)
from freqtrade.enums import CandleType
@@ -278,9 +281,12 @@ AVAILABLE_CLI_OPTIONS = {
),
"spaces": Arg(
"--spaces",
help="Specify which parameters to hyperopt. Space-separated list. Available options: "
f"{', '.join(HYPEROPT_BUILTIN_SPACES)}. Default: `default` - "
"which includes all spaces except for 'trailing', 'protection', and 'trades'.",
help=(
"Specify which parameters to hyperopt. Space-separated list. "
"Available builtin options (custom spaces will not be listed here): "
f"{', '.join(HYPEROPT_BUILTIN_SPACE_OPTIONS)}. Default: `default` - "
"which includes all spaces except for 'trailing', 'protection', and 'trades'."
),
nargs="+",
),
"analyze_per_epoch": Arg(

View File

@@ -101,7 +101,7 @@ def _print_objs_tabular(objs: list, print_colorized: bool) -> None:
names = [s["name"] for s in objs]
objs_to_print: list[dict[str, Text | str]] = [
{
"name": Text(s["name"] if s["name"] else "--"),
"Strategy name": Text(s["name"] if s["name"] else "--"),
"location": s["location_rel"],
"status": (
Text("LOAD FAILED", style="bold red")
@@ -115,11 +115,19 @@ def _print_objs_tabular(objs: list, print_colorized: bool) -> None:
]
for idx, s in enumerate(objs):
if "hyperoptable" in s:
custom_params = [
f"{space}: {len(params)}"
for space, params in s["hyperoptable"].items()
if space not in ["buy", "sell", "protection"]
]
hyp = s["hyperoptable"]
objs_to_print[idx].update(
{
"hyperoptable": "Yes" if s["hyperoptable"]["count"] > 0 else "No",
"buy-Params": str(len(s["hyperoptable"].get("buy", []))),
"sell-Params": str(len(s["hyperoptable"].get("sell", []))),
"hyperoptable": "Yes" if len(hyp) > 0 else "No",
"buy-Params": str(len(hyp.get("buy", []))),
"sell-Params": str(len(hyp.get("sell", []))),
"protection-Params": str(len(hyp.get("protection", []))),
"custom-Params": ", ".join(custom_params) if custom_params else "",
}
)
table = Table()
@@ -140,6 +148,7 @@ def start_list_strategies(args: dict[str, Any]) -> None:
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.hyper import detect_all_parameters
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -153,9 +162,9 @@ def start_list_strategies(args: dict[str, Any]) -> None:
strategy_objs = sorted(strategy_objs, key=lambda x: x["name"])
for obj in strategy_objs:
if obj["class"]:
obj["hyperoptable"] = obj["class"].detect_all_parameters()
obj["hyperoptable"] = detect_all_parameters(obj["class"])
else:
obj["hyperoptable"] = {"count": 0}
obj["hyperoptable"] = {}
if args["print_one_column"]:
print("\n".join([s["name"] for s in strategy_objs]))

View File

@@ -8,7 +8,6 @@ from freqtrade.constants import (
BACKTEST_CACHE_AGE,
DRY_RUN_WALLET,
EXPORT_OPTIONS,
HYPEROPT_BUILTIN_SPACES,
HYPEROPT_LOSS_BUILTIN,
MARGIN_MODES,
ORDERTIF_POSSIBILITIES,
@@ -260,7 +259,7 @@ CONF_SCHEMA = {
"includes all spaces except for 'trailing', 'protection', and 'trades'."
),
"type": "array",
"items": {"type": "string", "enum": HYPEROPT_BUILTIN_SPACES},
"items": {"type": "string"},
"default": ["default"],
},
"analyze_per_epoch": {

View File

@@ -42,16 +42,17 @@ HYPEROPT_LOSS_BUILTIN = [
"MultiMetricHyperOptLoss",
]
HYPEROPT_BUILTIN_SPACES = [
"all",
"buy",
"sell",
"enter",
"exit",
"roi",
"stoploss",
"trailing",
"protection",
"trades",
"default",
]
HYPEROPT_BUILTIN_SPACE_OPTIONS = ["default", "all"] + HYPEROPT_BUILTIN_SPACES
AVAILABLE_PAIRLISTS = [
"StaticPairList",

View File

@@ -7,6 +7,7 @@ This module implements a convenience auto-hyperopt class, which can be used toge
import logging
from collections.abc import Callable
from contextlib import suppress
from typing import Literal
from freqtrade.exceptions import OperationalException
@@ -37,10 +38,17 @@ def _format_exception_message(space: str, ignore_missing_space: bool) -> None:
class HyperOptAuto(IHyperOpt):
"""
This class delegates functionality to Strategy(IHyperStrategy) and Strategy.HyperOpt classes.
Most of the time Strategy.HyperOpt class would only implement indicator_space and
sell_indicator_space methods, but other hyperopt methods can be overridden as well.
Most of the time Strategy.HyperOpt class would only implement indicator_space and
sell_indicator_space methods, but other hyperopt methods can be overridden as well.
"""
def get_available_spaces(self) -> list[str]:
"""
Get list of available spaces defined in strategy.
:return: list of available spaces.
"""
return list(self.strategy._ft_hyper_params)
def _get_func(self, name) -> Callable:
"""
Return a function defined in Strategy.HyperOpt class, or one defined in super() class.
@@ -59,7 +67,13 @@ class HyperOptAuto(IHyperOpt):
if attr.optimize:
yield attr.get_space(attr_name)
def _get_indicator_space(self, category) -> list:
def get_indicator_space(
self, category: Literal["buy", "sell", "enter", "exit", "protection"] | str
) -> list:
"""
Get indicator space for a given space.
:param category: parameter space to get.
"""
# TODO: is this necessary, or can we call "generate_space" directly?
indicator_space = list(self._generate_indicator_space(category))
if len(indicator_space) > 0:
@@ -70,15 +84,6 @@ class HyperOptAuto(IHyperOpt):
)
return []
def buy_indicator_space(self) -> list["Dimension"]:
return self._get_indicator_space("buy")
def sell_indicator_space(self) -> list["Dimension"]:
return self._get_indicator_space("sell")
def protection_space(self) -> list["Dimension"]:
return self._get_indicator_space("protection")
def generate_roi_table(self, params: dict) -> dict[int, float]:
return self._get_func("generate_roi_table")(params)

View File

@@ -70,13 +70,7 @@ class HyperOptimizer:
"""
def __init__(self, config: Config, data_pickle_file: Path) -> None:
self.buy_space: list[DimensionProtocol] = []
self.sell_space: list[DimensionProtocol] = []
self.protection_space: list[DimensionProtocol] = []
self.roi_space: list[DimensionProtocol] = []
self.stoploss_space: list[DimensionProtocol] = []
self.trailing_space: list[DimensionProtocol] = []
self.max_open_trades_space: list[DimensionProtocol] = []
self.spaces: dict[str, list[DimensionProtocol]] = {}
self.dimensions: list[DimensionProtocol] = []
self.o_dimensions: dict = {}
@@ -167,37 +161,39 @@ class HyperOptimizer:
"""
result: dict = {}
if HyperoptTools.has_space(self.config, "buy"):
result["buy"] = round_dict({p.name: params.get(p.name) for p in self.buy_space}, 13)
if HyperoptTools.has_space(self.config, "sell"):
result["sell"] = round_dict({p.name: params.get(p.name) for p in self.sell_space}, 13)
if HyperoptTools.has_space(self.config, "protection"):
result["protection"] = round_dict(
{p.name: params.get(p.name) for p in self.protection_space}, 13
)
if HyperoptTools.has_space(self.config, "roi"):
result["roi"] = round_dict(
{str(k): v for k, v in self.custom_hyperopt.generate_roi_table(params).items()}, 13
)
if HyperoptTools.has_space(self.config, "stoploss"):
result["stoploss"] = round_dict(
{p.name: params.get(p.name) for p in self.stoploss_space}, 13
)
if HyperoptTools.has_space(self.config, "trailing"):
result["trailing"] = round_dict(
self.custom_hyperopt.generate_trailing_params(params), 13
)
if HyperoptTools.has_space(self.config, "trades"):
result["max_open_trades"] = round_dict(
{
"max_open_trades": (
self.backtesting.strategy.max_open_trades
if self.backtesting.strategy.max_open_trades != float("inf")
else -1
)
},
13,
)
for space in self.spaces.keys():
if space == "protection":
result["protection"] = round_dict(
{p.name: params.get(p.name) for p in self.spaces[space]}, 13
)
elif space == "roi":
result["roi"] = round_dict(
{str(k): v for k, v in self.custom_hyperopt.generate_roi_table(params).items()},
13,
)
elif space == "stoploss":
result["stoploss"] = round_dict(
{p.name: params.get(p.name) for p in self.spaces[space]}, 13
)
elif space == "trailing":
result["trailing"] = round_dict(
self.custom_hyperopt.generate_trailing_params(params), 13
)
elif space == "trades":
result["max_open_trades"] = round_dict(
{
"max_open_trades": (
self.backtesting.strategy.max_open_trades
if self.backtesting.strategy.max_open_trades != float("inf")
else -1
)
},
13,
)
else:
result[space] = round_dict(
{p.name: params.get(p.name) for p in self.spaces[space]}, 13
)
return result
@@ -226,56 +222,39 @@ class HyperOptimizer:
"""
Assign the dimensions in the hyperoptimization space.
"""
if HyperoptTools.has_space(self.config, "protection"):
# Protections can only be optimized when using the Parameter interface
logger.debug("Hyperopt has 'protection' space")
# Enable Protections if protection space is selected.
self.config["enable_protections"] = True
self.backtesting.enable_protections = True
self.protection_space = self.custom_hyperopt.protection_space()
spaces = ["buy", "sell", "protection", "roi", "stoploss", "trailing", "trades"]
spaces += [s for s in self.custom_hyperopt.get_available_spaces() if s not in spaces]
if HyperoptTools.has_space(self.config, "buy"):
logger.debug("Hyperopt has 'buy' space")
self.buy_space = self.custom_hyperopt.buy_indicator_space()
for space in spaces:
if not HyperoptTools.has_space(self.config, space):
continue
logger.debug(f"Hyperopt has '{space}' space")
if space == "protection":
# Protections can only be optimized when using the Parameter interface
# Enable Protections if protection space is selected.
self.config["enable_protections"] = True
self.backtesting.enable_protections = True
self.spaces[space] = self.custom_hyperopt.get_indicator_space(space)
elif space == "roi":
self.spaces[space] = self.custom_hyperopt.roi_space()
elif space == "stoploss":
self.spaces[space] = self.custom_hyperopt.stoploss_space()
elif space == "trailing":
self.spaces[space] = self.custom_hyperopt.trailing_space()
elif space == "trades":
self.spaces[space] = self.custom_hyperopt.max_open_trades_space()
else:
self.spaces[space] = self.custom_hyperopt.get_indicator_space(space)
if HyperoptTools.has_space(self.config, "sell"):
logger.debug("Hyperopt has 'sell' space")
self.sell_space = self.custom_hyperopt.sell_indicator_space()
if HyperoptTools.has_space(self.config, "roi"):
logger.debug("Hyperopt has 'roi' space")
self.roi_space = self.custom_hyperopt.roi_space()
if HyperoptTools.has_space(self.config, "stoploss"):
logger.debug("Hyperopt has 'stoploss' space")
self.stoploss_space = self.custom_hyperopt.stoploss_space()
if HyperoptTools.has_space(self.config, "trailing"):
logger.debug("Hyperopt has 'trailing' space")
self.trailing_space = self.custom_hyperopt.trailing_space()
if HyperoptTools.has_space(self.config, "trades"):
logger.debug("Hyperopt has 'trades' space")
self.max_open_trades_space = self.custom_hyperopt.max_open_trades_space()
self.dimensions = (
self.buy_space
+ self.sell_space
+ self.protection_space
+ self.roi_space
+ self.stoploss_space
+ self.trailing_space
+ self.max_open_trades_space
)
def assign_params(self, params_dict: dict[str, Any], category: str) -> None:
"""
Assign hyperoptable parameters
"""
for attr_name, attr in self.backtesting.strategy.enumerate_parameters(category):
if attr.optimize:
# noinspection PyProtectedMember
attr.value = params_dict[attr_name]
self.dimensions = [s for space in self.spaces.values() for s in space]
if len(self.dimensions) == 0:
raise OperationalException(
"No hyperopt parameters found to optimize."
f"Available spaces: {', '.join(spaces)}. "
"Check your strategy's parameter definitions or verify the configured spaces "
"in your command."
)
self.o_dimensions = self.convert_dimensions_to_optuna_space(self.dimensions)
@delayed
@wrap_non_picklable_objects
@@ -292,15 +271,9 @@ class HyperOptimizer:
HyperoptStateContainer.set_state(HyperoptState.OPTIMIZE)
backtest_start_time = datetime.now(UTC)
# Apply parameters
if HyperoptTools.has_space(self.config, "buy"):
self.assign_params(params_dict, "buy")
if HyperoptTools.has_space(self.config, "sell"):
self.assign_params(params_dict, "sell")
if HyperoptTools.has_space(self.config, "protection"):
self.assign_params(params_dict, "protection")
for attr_name, attr in self.backtesting.strategy.enumerate_parameters():
if attr.in_space and attr.optimize:
attr.value = params_dict[attr_name]
if HyperoptTools.has_space(self.config, "roi"):
self.backtesting.strategy.minimal_roi = self.custom_hyperopt.generate_roi_table(
@@ -436,7 +409,6 @@ class HyperOptimizer:
o_sampler = self.custom_hyperopt.generate_estimator(
dimensions=self.dimensions, random_state=random_state
)
self.o_dimensions = self.convert_dimensions_to_optuna_space(self.dimensions)
if isinstance(o_sampler, str):
if o_sampler not in optuna_samplers_dict.keys():

View File

@@ -9,7 +9,7 @@ import numpy as np
import rapidjson
from pandas import isna, json_normalize
from freqtrade.constants import FTHYPT_FILEVERSION, Config
from freqtrade.constants import FTHYPT_FILEVERSION, HYPEROPT_BUILTIN_SPACES, Config
from freqtrade.enums import HyperoptState
from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, round_dict, safe_value_fallback2
@@ -219,21 +219,22 @@ class HyperoptTools:
print(rapidjson.dumps(result_dict, default=str, number_mode=HYPER_PARAMS_FILE_FORMAT))
else:
HyperoptTools._params_pretty_print(
params, "buy", "Buy hyperspace params:", non_optimized
)
HyperoptTools._params_pretty_print(
params, "sell", "Sell hyperspace params:", non_optimized
)
HyperoptTools._params_pretty_print(
params, "protection", "Protection hyperspace params:", non_optimized
)
HyperoptTools._params_pretty_print(params, "roi", "ROI table:", non_optimized)
HyperoptTools._params_pretty_print(params, "stoploss", "Stoploss:", non_optimized)
HyperoptTools._params_pretty_print(params, "trailing", "Trailing stop:", non_optimized)
HyperoptTools._params_pretty_print(
params, "max_open_trades", "Max Open Trades:", non_optimized
)
all_spaces = list(params.keys() | non_optimized.keys())
# Explicitly listed to keep original sort order
spaces = ["buy", "sell", "protection", "roi", "stoploss", "trailing", "max_open_trades"]
spaces += [s for s in all_spaces if s not in spaces]
lookup = {
"roi": "ROI",
"trailing": "Trailing stop",
}
for space in spaces:
name = lookup.get(
space, space.capitalize() if space in HYPEROPT_BUILTIN_SPACES else space
)
HyperoptTools._params_pretty_print(
params, space, f"{name} parameters:", non_optimized
)
@staticmethod
def _params_update_for_json(result_dict, params, non_optimized, space: str) -> None:

View File

@@ -4,9 +4,9 @@ This module defines a base class for auto-hyperoptable strategies.
"""
import logging
from collections import defaultdict
from collections.abc import Iterator
from pathlib import Path
from typing import Any
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
@@ -18,6 +18,11 @@ from freqtrade.strategy.parameters import BaseParameter
logger = logging.getLogger(__name__)
# Type aliases
SpaceParams = dict[str, BaseParameter]
AllSpaceParams = dict[str, SpaceParams]
class HyperStrategyMixin:
"""
A helper base class which allows HyperOptAuto class to reuse implementations of buy/sell
@@ -29,9 +34,7 @@ class HyperStrategyMixin:
Initialize hyperoptable strategy mixin.
"""
self.config = config
self.ft_buy_params: list[BaseParameter] = []
self.ft_sell_params: list[BaseParameter] = []
self.ft_protection_params: list[BaseParameter] = []
self._ft_hyper_params: AllSpaceParams = {}
params = self.load_params_from_file()
params = params.get("params", {})
@@ -46,30 +49,9 @@ class HyperStrategyMixin:
:param category:
:return:
"""
if category not in ("buy", "sell", "protection", None):
raise OperationalException(
'Category must be one of: "buy", "sell", "protection", None.'
)
if category is None:
params = self.ft_buy_params + self.ft_sell_params + self.ft_protection_params
else:
params = getattr(self, f"ft_{category}_params")
for par in params:
yield par.name, par
@classmethod
def detect_all_parameters(cls) -> dict:
"""Detect all parameters and return them as a list"""
params: dict[str, Any] = {
"buy": list(detect_parameters(cls, "buy")),
"sell": list(detect_parameters(cls, "sell")),
"protection": list(detect_parameters(cls, "protection")),
}
params.update({"count": len(params["buy"] + params["sell"] + params["protection"])})
return params
for category in [c for c in self._ft_hyper_params if category is None or c == category]:
for par in self._ft_hyper_params[category].values():
yield par.name, par
def ft_load_params_from_file(self) -> None:
"""
@@ -110,20 +92,13 @@ class HyperStrategyMixin:
* Parameters defined in parameters objects (buy_params, sell_params, ...)
* Parameter defaults
"""
self._ft_hyper_params = detect_all_parameters(self)
buy_params = deep_merge_dicts(
self._ft_params_from_file.get("buy", {}), getattr(self, "buy_params", {})
)
sell_params = deep_merge_dicts(
self._ft_params_from_file.get("sell", {}), getattr(self, "sell_params", {})
)
protection_params = deep_merge_dicts(
self._ft_params_from_file.get("protection", {}), getattr(self, "protection_params", {})
)
self._ft_load_params(buy_params, "buy", hyperopt)
self._ft_load_params(sell_params, "sell", hyperopt)
self._ft_load_params(protection_params, "protection", hyperopt)
for space in self._ft_hyper_params.keys():
params_values = deep_merge_dicts(
self._ft_params_from_file.get(space, {}), getattr(self, f"{space}_params", {})
)
self._ft_load_params(self._ft_hyper_params[space], params_values, space, hyperopt)
def load_params_from_file(self) -> dict:
filename_str = getattr(self, "__file__", "")
@@ -145,72 +120,73 @@ class HyperStrategyMixin:
return {}
def _ft_load_params(self, params: dict, space: str, hyperopt: bool = False) -> None:
def _ft_load_params(
self, params: SpaceParams, param_values: dict, space: str, hyperopt: bool = False
) -> None:
"""
Set optimizable parameter values.
:param params: Dictionary with new parameter values.
"""
if not params:
if not param_values:
logger.info(f"No params for {space} found, using default values.")
param_container: list[BaseParameter] = getattr(self, f"ft_{space}_params")
for attr_name, attr in detect_parameters(self, space):
attr.name = attr_name
attr.in_space = hyperopt and HyperoptTools.has_space(self.config, space)
if not attr.category:
attr.category = space
for param_name, param in params.items():
param.in_space = hyperopt and HyperoptTools.has_space(self.config, space)
if not param.category:
param.category = space
param_container.append(attr)
if params and attr_name in params:
if attr.load:
attr.value = params[attr_name]
logger.info(f"Strategy Parameter: {attr_name} = {attr.value}")
if param_values and param_name in param_values:
if param.load:
param.value = param_values[param_name]
logger.info(f"Strategy Parameter: {param_name} = {param.value}")
else:
logger.warning(
f'Parameter "{attr_name}" exists, but is disabled. '
f'Default value "{attr.value}" used.'
f'Parameter "{param_name}" exists, but is disabled. '
f'Default value "{param.value}" used.'
)
else:
logger.info(f"Strategy Parameter(default): {attr_name} = {attr.value}")
logger.info(f"Strategy Parameter(default): {param_name} = {param.value}")
def get_no_optimize_params(self) -> dict[str, dict]:
"""
Returns list of Parameters that are not part of the current optimize job
"""
params: dict[str, dict] = {
"buy": {},
"sell": {},
"protection": {},
}
params: dict[str, dict] = defaultdict(dict)
for name, p in self.enumerate_parameters():
if p.category and (not p.optimize or not p.in_space):
params[p.category][name] = p.value
return params
def detect_parameters(
obj: HyperStrategyMixin | type[HyperStrategyMixin], category: str
) -> Iterator[tuple[str, BaseParameter]]:
def detect_all_parameters(
obj: HyperStrategyMixin | type[HyperStrategyMixin],
) -> AllSpaceParams:
"""
Detect all parameters for 'category' for "obj"
Detect all hyperoptable parameters for this object.
:param obj: Strategy object or class
:param category: category - usually `'buy', 'sell', 'protection',...
:return: Dictionary of detected parameters by space
"""
auto_categories = ["buy", "sell", "enter", "exit", "protection"]
result: AllSpaceParams = defaultdict(dict)
for attr_name in dir(obj):
if not attr_name.startswith("__"): # Ignore internals, not strictly necessary.
attr = getattr(obj, attr_name)
if issubclass(attr.__class__, BaseParameter):
if (
attr_name.startswith(category + "_")
and attr.category is not None
and attr.category != category
):
raise OperationalException(
f"Inconclusive parameter name {attr_name}, category: {attr.category}."
)
if attr_name.startswith("__"): # Ignore internals
continue
attr = getattr(obj, attr_name)
if not issubclass(attr.__class__, BaseParameter):
continue
if not attr.category:
# Category auto detection
for category in auto_categories:
if attr_name.startswith(category + "_"):
attr.category = category
break
if attr.category is None:
raise OperationalException(f"Cannot determine parameter space for {attr_name}.")
if category == attr.category or (
attr_name.startswith(category + "_") and attr.category is None
):
yield attr_name, attr
if attr.category in ("all", "default") or attr.category.isidentifier() is False:
raise OperationalException(
f"'{attr.category}' is not a valid space. Parameter: {attr_name}."
)
attr.name = attr_name
result[attr.category][attr_name] = attr
return result

View File

@@ -49,9 +49,9 @@ class BaseParameter(ABC):
):
"""
Initialize hyperopt-optimizable parameter.
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
parameter field
name is prefixed with 'buy_' or 'sell_'.
:param space: The parameter space. Can be 'buy', 'sell', or a string that's also a
valid python identifier.
This parameter is optional if parameter name 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 optuna.distributions.
@@ -109,8 +109,9 @@ class NumericParameter(BaseParameter):
:param high: Upper end (inclusive) of optimization space.
Must be none of entire range is passed first parameter.
:param default: A default value.
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
parameter fieldname is prefixed with 'buy_' or 'sell_'.
:param space: The parameter space. Can be 'buy', 'sell', or a string that's also a
valid python identifier.
This parameter is optional if parameter name 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 optuna.distributions.*.
@@ -151,8 +152,9 @@ class IntParameter(NumericParameter):
:param high: Upper end (inclusive) of optimization space.
Must be none of entire range is passed first parameter.
:param default: A default value.
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
parameter fieldname is prefixed with 'buy_' or 'sell_'.
:param space: The parameter space. Can be 'buy', 'sell', or a string that's also a
valid python identifier.
This parameter is optional if parameter name 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 optuna.distributions.IntDistribution.
@@ -205,8 +207,9 @@ class RealParameter(NumericParameter):
:param high: Upper end (inclusive) of optimization space.
Must be none if entire range is passed first parameter.
:param default: A default value.
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
parameter fieldname is prefixed with 'buy_' or 'sell_'.
:param space: The parameter space. Can be 'buy', 'sell', or a string that's also a
valid python identifier.
This parameter is optional if parameter name 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 optuna.distributions.FloatDistribution.
@@ -245,8 +248,9 @@ class DecimalParameter(NumericParameter):
Must be none if entire range is passed first parameter.
:param default: A default value.
:param decimals: A number of decimals after floating point to be included in testing.
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
parameter fieldname is prefixed with 'buy_' or 'sell_'.
:param space: The parameter space. Can be 'buy', 'sell', or a string that's also a
valid python identifier.
This parameter is optional if parameter name 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 optuna's NumericParameter.
@@ -310,10 +314,10 @@ class CategoricalParameter(BaseParameter):
Initialize hyperopt-optimizable parameter.
:param categories: Optimization space, [a, b, ...].
:param default: A default value. If not specified, first item from specified space will be
used.
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
parameter field
name is prefixed with 'buy_' or 'sell_'.
used.
:param space: The parameter space. Can be 'buy', 'sell', or a string that's also a
valid python identifier.
This parameter is optional if parameter name is prefixed with 'buy_' or 'sell_'.
:param optimize: Include parameter in hyperopt optimizations.
:param load: Load parameter value from {space}_params.
:param kwargs: Compatibility. Optuna's CategoricalDistribution does not
@@ -361,10 +365,10 @@ class BooleanParameter(CategoricalParameter):
Initialize hyperopt-optimizable Boolean Parameter.
It's a shortcut to `CategoricalParameter([True, False])`.
:param default: A default value. If not specified, first item from specified space will be
used.
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
parameter field
name is prefixed with 'buy_' or 'sell_'.
used.
:param space: The parameter space. Can be 'buy', 'sell', or a string that's also a
valid python identifier.
This parameter is optional if parameter name 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 optuna.distributions.CategoricalDistribution.

View File

@@ -96,7 +96,9 @@ class SampleStrategy(IStrategy):
buy_rsi = IntParameter(low=1, high=50, default=30, space="buy", optimize=True, load=True)
sell_rsi = IntParameter(low=50, high=100, default=70, space="sell", optimize=True, load=True)
short_rsi = IntParameter(low=51, high=100, default=70, space="sell", optimize=True, load=True)
exit_short_rsi = IntParameter(low=1, high=50, default=30, space="buy", optimize=True, load=True)
exit_short_rsi = IntParameter(
low=1, high=50, default=30, space="exit", optimize=True, load=True
)
# Number of candles the strategy requires before producing valid signals
startup_candle_count: int = 200