extract detect_parameters to separate function

This commit is contained in:
Matthias 2022-05-30 07:22:16 +02:00
parent 5bf021be2e
commit 8e2c7e1298
2 changed files with 34 additions and 22 deletions

View File

@ -4,7 +4,7 @@ This module defines a base class for auto-hyperoptable strategies.
""" """
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Iterator, List, Tuple from typing import Any, Dict, Iterator, List, Tuple, Type, Union
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, json_load from freqtrade.misc import deep_merge_dicts, json_load
@ -53,27 +53,13 @@ class HyperStrategyMixin:
for par in params: for par in params:
yield par.name, par yield par.name, par
def detect_parameters(self, category: str) -> Iterator[Tuple[str, BaseParameter]]:
""" Detect all parameters for 'category' """
for attr_name in dir(self):
if not attr_name.startswith('__'): # Ignore internals, not strictly necessary.
attr = getattr(self, 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 (category == attr.category or
(attr_name.startswith(category + '_') and attr.category is None)):
yield attr_name, attr
@classmethod @classmethod
def detect_all_parameters(cls) -> Dict: def detect_all_parameters(cls) -> Dict:
""" Detect all parameters and return them as a list""" """ Detect all parameters and return them as a list"""
params: Dict[str, Any] = { params: Dict[str, Any] = {
'buy': list(cls.detect_parameters('buy')), 'buy': list(detect_parameters(cls, 'buy')),
'sell': list(cls.detect_parameters('sell')), 'sell': list(detect_parameters(cls, 'sell')),
'protection': list(cls.detect_parameters('protection')), 'protection': list(detect_parameters(cls, 'protection')),
} }
params.update({ params.update({
'count': len(params['buy'] + params['sell'] + params['protection']) 'count': len(params['buy'] + params['sell'] + params['protection'])
@ -155,7 +141,7 @@ class HyperStrategyMixin:
logger.info(f"No params for {space} found, using default values.") logger.info(f"No params for {space} found, using default values.")
param_container: List[BaseParameter] = getattr(self, f"ft_{space}_params") param_container: List[BaseParameter] = getattr(self, f"ft_{space}_params")
for attr_name, attr in self.detect_parameters(space): for attr_name, attr in detect_parameters(self, space):
attr.name = attr_name attr.name = attr_name
attr.in_space = hyperopt and HyperoptTools.has_space(self.config, space) attr.in_space = hyperopt and HyperoptTools.has_space(self.config, space)
if not attr.category: if not attr.category:
@ -186,3 +172,25 @@ class HyperStrategyMixin:
if not p.optimize or not p.in_space: if not p.optimize or not p.in_space:
params[p.category][name] = p.value params[p.category][name] = p.value
return params return params
def detect_parameters(
obj: Union[HyperStrategyMixin, Type[HyperStrategyMixin]],
category: str
) -> Iterator[Tuple[str, BaseParameter]]:
"""
Detect all parameters for 'category' for "obj"
:param obj: Strategy object or class
:param category: category - usually `'buy', 'sell', 'protection',...
"""
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 (category == attr.category or
(attr_name.startswith(category + '_') and attr.category is None)):
yield attr_name, attr

View File

@ -16,6 +16,7 @@ from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.hyper import detect_parameters
from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter, from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter,
DecimalParameter, IntParameter, RealParameter) DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
@ -908,15 +909,18 @@ def test_auto_hyperopt_interface(default_conf):
assert strategy.sell_minusdi.value == 0.5 assert strategy.sell_minusdi.value == 0.5
all_params = strategy.detect_all_parameters() all_params = strategy.detect_all_parameters()
assert isinstance(all_params, dict) assert isinstance(all_params, dict)
assert len(all_params['buy']) == 2 # Only one buy param at class level
assert len(all_params['buy']) == 1
# Running detect params at instance level reveals both parameters.
assert len(list(detect_parameters(strategy, 'buy'))) == 2
assert len(all_params['sell']) == 2 assert len(all_params['sell']) == 2
# Number of Hyperoptable parameters # Number of Hyperoptable parameters
assert all_params['count'] == 6 assert all_params['count'] == 5
strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy') strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy')
with pytest.raises(OperationalException, match=r"Inconclusive parameter.*"): with pytest.raises(OperationalException, match=r"Inconclusive parameter.*"):
[x for x in strategy.detect_parameters('sell')] [x for x in detect_parameters(strategy, 'sell')]
def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog): def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):