Merge pull request #6906 from freqtrade/params_to_instance
Params to instance
This commit is contained in:
commit
eee337c764
@ -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.
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user