Merge pull request #6906 from freqtrade/params_to_instance

Params to instance
This commit is contained in:
Matthias 2022-05-31 16:18:48 +02:00 committed by GitHub
commit eee337c764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 33 deletions

View File

@ -98,6 +98,23 @@ class MyAwesomeStrategy(IStrategy):
!!! Note !!! Note
All overrides are optional and can be mixed/matched as necessary. All overrides are optional and can be mixed/matched as necessary.
### Dynamic parameters
Parameters can also be defined dynamically, but must be available to the instance once the * [`bot_start()` callback](strategy-callbacks.md#bot-start) has been called.
``` python
class MyAwesomeStrategy(IStrategy):
def bot_start(self, **kwargs) -> None:
self.buy_adx = IntParameter(20, 30, default=30, optimize=True)
# ...
```
!!! Warning
Parameters created this way will not show up in the `list-strategies` parameter count.
### Overriding Base estimator ### Overriding Base estimator
You can define your own estimator for Hyperopt by implementing `generate_estimator()` in the Hyperopt subclass. You can define your own estimator for Hyperopt by implementing `generate_estimator()` in the Hyperopt subclass.

View File

@ -187,9 +187,7 @@ class Backtesting:
# since a "perfect" stoploss-exit is assumed anyway # since a "perfect" stoploss-exit is assumed anyway
# And the regular "stoploss" function would not apply to that case # And the regular "stoploss" function would not apply to that case
self.strategy.order_types['stoploss_on_exchange'] = False self.strategy.order_types['stoploss_on_exchange'] = False
if self.dataprovider.runmode == RunMode.BACKTEST:
# in hyperopt mode - don't re-init params
self.strategy.ft_load_hyper_params(False)
self.strategy.ft_bot_start() self.strategy.ft_bot_start()
def _load_protections(self, strategy: IStrategy): def _load_protections(self, strategy: IStrategy):

View File

@ -4,9 +4,8 @@ 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.enums import RunMode
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
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
@ -34,9 +33,7 @@ class HyperStrategyMixin:
params = self.load_params_from_file() params = self.load_params_from_file()
params = params.get('params', {}) params = params.get('params', {})
self._ft_params_from_file = params self._ft_params_from_file = params
# Init/loading of parameters is done as part of ft_bot_start().
if config.get('runmode') != RunMode.BACKTEST:
self.ft_load_hyper_params(config.get('runmode') == RunMode.HYPEROPT)
def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]: def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]:
""" """
@ -56,28 +53,13 @@ class HyperStrategyMixin:
for par in params: for par in params:
yield par.name, par yield par.name, par
@classmethod
def detect_parameters(cls, category: str) -> Iterator[Tuple[str, BaseParameter]]:
""" Detect all parameters for 'category' """
for attr_name in dir(cls):
if not attr_name.startswith('__'): # Ignore internals, not strictly necessary.
attr = getattr(cls, 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'])
@ -159,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:
@ -190,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

@ -14,6 +14,7 @@ from freqtrade.constants import ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType,
SignalType, TradingMode) SignalType, TradingMode)
from freqtrade.enums.runmode import RunMode
from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.persistence import Order, PairLocks, Trade
@ -151,6 +152,8 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
strategy_safe_wrapper(self.bot_start)() strategy_safe_wrapper(self.bot_start)()
self.ft_load_hyper_params(self.config.get('runmode') == RunMode.HYPEROPT)
@abstractmethod @abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """

View File

@ -509,7 +509,6 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
hyperopt.min_date = Arrow(2017, 12, 10) hyperopt.min_date = Arrow(2017, 12, 10)
hyperopt.max_date = Arrow(2017, 12, 13) hyperopt.max_date = Arrow(2017, 12, 13)
hyperopt.init_spaces() hyperopt.init_spaces()
hyperopt.dimensions = hyperopt.dimensions
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values())) generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
assert generate_optimizer_value == response_expected assert generate_optimizer_value == response_expected

View File

@ -27,7 +27,6 @@ class HyperoptableStrategy(StrategyTestV2):
'sell_minusdi': 0.4 'sell_minusdi': 0.4
} }
buy_rsi = IntParameter([0, 50], default=30, space='buy')
buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy') buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy')
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_rsi = IntParameter(low=50, high=100, default=70, space='sell')
sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell', sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell',
@ -45,6 +44,12 @@ class HyperoptableStrategy(StrategyTestV2):
}) })
return prot return prot
def bot_start(self, **kwargs) -> None:
"""
Parameters can also be defined here ...
"""
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
def informative_pairs(self): def informative_pairs(self):
""" """
Define additional, informative pair/interval combinations to be cached from the exchange. Define additional, informative pair/interval combinations to be cached from the exchange.

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
@ -893,7 +894,7 @@ def test_auto_hyperopt_interface(default_conf):
default_conf.update({'strategy': 'HyperoptableStrategy'}) default_conf.update({'strategy': 'HyperoptableStrategy'})
PairLocks.timeframe = default_conf['timeframe'] PairLocks.timeframe = default_conf['timeframe']
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
strategy.ft_bot_start()
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
next(strategy.enumerate_parameters('deadBeef')) next(strategy.enumerate_parameters('deadBeef'))
@ -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):