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
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
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
# And the regular "stoploss" function would not apply to that case
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()
def _load_protections(self, strategy: IStrategy):

View File

@ -4,9 +4,8 @@ This module defines a base class for auto-hyperoptable strategies.
"""
import logging
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.misc import deep_merge_dicts, json_load
from freqtrade.optimize.hyperopt_tools import HyperoptTools
@ -34,9 +33,7 @@ class HyperStrategyMixin:
params = self.load_params_from_file()
params = params.get('params', {})
self._ft_params_from_file = params
if config.get('runmode') != RunMode.BACKTEST:
self.ft_load_hyper_params(config.get('runmode') == RunMode.HYPEROPT)
# Init/loading of parameters is done as part of ft_bot_start().
def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]:
"""
@ -56,28 +53,13 @@ class HyperStrategyMixin:
for par in params:
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
def detect_all_parameters(cls) -> Dict:
""" Detect all parameters and return them as a list"""
params: Dict[str, Any] = {
'buy': list(cls.detect_parameters('buy')),
'sell': list(cls.detect_parameters('sell')),
'protection': list(cls.detect_parameters('protection')),
'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'])
@ -159,7 +141,7 @@ class HyperStrategyMixin:
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 self.detect_parameters(space):
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:
@ -190,3 +172,25 @@ class HyperStrategyMixin:
if not p.optimize or not p.in_space:
params[p.category][name] = p.value
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.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType,
SignalType, TradingMode)
from freqtrade.enums.runmode import RunMode
from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
from freqtrade.persistence import Order, PairLocks, Trade
@ -151,6 +152,8 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
strategy_safe_wrapper(self.bot_start)()
self.ft_load_hyper_params(self.config.get('runmode') == RunMode.HYPEROPT)
@abstractmethod
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.max_date = Arrow(2017, 12, 13)
hyperopt.init_spaces()
hyperopt.dimensions = hyperopt.dimensions
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
assert generate_optimizer_value == response_expected

View File

@ -27,7 +27,6 @@ class HyperoptableStrategy(StrategyTestV2):
'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')
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',
@ -45,6 +44,12 @@ class HyperoptableStrategy(StrategyTestV2):
})
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):
"""
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.persistence import PairLocks, Trade
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.hyper import detect_parameters
from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter,
DecimalParameter, IntParameter, RealParameter)
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'})
PairLocks.timeframe = default_conf['timeframe']
strategy = StrategyResolver.load_strategy(default_conf)
strategy.ft_bot_start()
with pytest.raises(OperationalException):
next(strategy.enumerate_parameters('deadBeef'))
@ -908,15 +909,18 @@ def test_auto_hyperopt_interface(default_conf):
assert strategy.sell_minusdi.value == 0.5
all_params = strategy.detect_all_parameters()
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
# 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')
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):