Merge pull request #4596 from rokups/rk/hyper-strategy

Support for creating auto-hyperoptable strategies.
This commit is contained in:
Matthias
2021-04-05 13:55:32 +02:00
committed by GitHub
17 changed files with 1140 additions and 312 deletions

View File

@@ -26,6 +26,7 @@ from freqtrade.data.history import get_timerange
from freqtrade.misc import file_dump_json, plural
from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F401
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F401
from freqtrade.optimize.hyperopt_tools import HyperoptTools
@@ -61,14 +62,18 @@ class Hyperopt:
hyperopt = Hyperopt(config)
hyperopt.start()
"""
custom_hyperopt: IHyperOpt
def __init__(self, config: Dict[str, Any]) -> None:
self.config = config
self.backtesting = Backtesting(self.config)
self.custom_hyperopt = HyperOptResolver.load_hyperopt(self.config)
self.custom_hyperopt.__class__.strategy = self.backtesting.strategy
if not self.config.get('hyperopt'):
self.custom_hyperopt = HyperOptAuto(self.config)
else:
self.custom_hyperopt = HyperOptResolver.load_hyperopt(self.config)
self.custom_hyperopt.strategy = self.backtesting.strategy
self.custom_hyperoptloss = HyperOptLossResolver.load_hyperoptloss(self.config)
self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function

View File

@@ -0,0 +1,89 @@
"""
HyperOptAuto class.
This module implements a convenience auto-hyperopt class, which can be used together with strategies
that implement IHyperStrategy interface.
"""
from contextlib import suppress
from typing import Any, Callable, Dict, List
from pandas import DataFrame
with suppress(ImportError):
from skopt.space import Dimension
from freqtrade.optimize.hyperopt_interface import IHyperOpt
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.
"""
def buy_strategy_generator(self, params: Dict[str, Any]) -> Callable:
def populate_buy_trend(dataframe: DataFrame, metadata: dict):
for attr_name, attr in self.strategy.enumerate_parameters('buy'):
if attr.optimize:
# noinspection PyProtectedMember
attr._set_value(params[attr_name])
return self.strategy.populate_buy_trend(dataframe, metadata)
return populate_buy_trend
def sell_strategy_generator(self, params: Dict[str, Any]) -> Callable:
def populate_buy_trend(dataframe: DataFrame, metadata: dict):
for attr_name, attr in self.strategy.enumerate_parameters('sell'):
if attr.optimize:
# noinspection PyProtectedMember
attr._set_value(params[attr_name])
return self.strategy.populate_sell_trend(dataframe, metadata)
return populate_buy_trend
def _get_func(self, name) -> Callable:
"""
Return a function defined in Strategy.HyperOpt class, or one defined in super() class.
:param name: function name.
:return: a requested function.
"""
hyperopt_cls = getattr(self.strategy, 'HyperOpt', None)
default_func = getattr(super(), name)
if hyperopt_cls:
return getattr(hyperopt_cls, name, default_func)
else:
return default_func
def _generate_indicator_space(self, category):
for attr_name, attr in self.strategy.enumerate_parameters(category):
if attr.optimize:
yield attr.get_space(attr_name)
def _get_indicator_space(self, category, fallback_method_name):
indicator_space = list(self._generate_indicator_space(category))
if len(indicator_space) > 0:
return indicator_space
else:
return self._get_func(fallback_method_name)()
def indicator_space(self) -> List['Dimension']:
return self._get_indicator_space('buy', 'indicator_space')
def sell_indicator_space(self) -> List['Dimension']:
return self._get_indicator_space('sell', 'sell_indicator_space')
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
return self._get_func('generate_roi_table')(params)
def roi_space(self) -> List['Dimension']:
return self._get_func('roi_space')()
def stoploss_space(self) -> List['Dimension']:
return self._get_func('stoploss_space')()
def generate_trailing_params(self, params: Dict) -> Dict:
return self._get_func('generate_trailing_params')(params)
def trailing_space(self) -> List['Dimension']:
return self._get_func('trailing_space')()

View File

@@ -44,36 +44,31 @@ class IHyperOpt(ABC):
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
IHyperOpt.timeframe = str(config['timeframe'])
@staticmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
def buy_strategy_generator(self, params: Dict[str, Any]) -> Callable:
"""
Create a buy strategy generator.
"""
raise OperationalException(_format_exception_message('buy_strategy_generator', 'buy'))
@staticmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
def sell_strategy_generator(self, params: Dict[str, Any]) -> Callable:
"""
Create a sell strategy generator.
"""
raise OperationalException(_format_exception_message('sell_strategy_generator', 'sell'))
@staticmethod
def indicator_space() -> List[Dimension]:
def indicator_space(self) -> List[Dimension]:
"""
Create an indicator space.
"""
raise OperationalException(_format_exception_message('indicator_space', 'buy'))
@staticmethod
def sell_indicator_space() -> List[Dimension]:
def sell_indicator_space(self) -> List[Dimension]:
"""
Create a sell indicator space.
"""
raise OperationalException(_format_exception_message('sell_indicator_space', 'sell'))
@staticmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
"""
Create a ROI table.
@@ -88,8 +83,7 @@ class IHyperOpt(ABC):
return roi_table
@staticmethod
def roi_space() -> List[Dimension]:
def roi_space(self) -> List[Dimension]:
"""
Create a ROI space.
@@ -109,7 +103,7 @@ class IHyperOpt(ABC):
roi_t_alpha = 1.0
roi_p_alpha = 1.0
timeframe_min = timeframe_to_minutes(IHyperOpt.ticker_interval)
timeframe_min = timeframe_to_minutes(self.timeframe)
# We define here limits for the ROI space parameters automagically adapted to the
# timeframe used by the bot:
@@ -145,7 +139,7 @@ class IHyperOpt(ABC):
'roi_p2': roi_limits['roi_p2_min'],
'roi_p3': roi_limits['roi_p3_min'],
}
logger.info(f"Min roi table: {round_dict(IHyperOpt.generate_roi_table(p), 5)}")
logger.info(f"Min roi table: {round_dict(self.generate_roi_table(p), 5)}")
p = {
'roi_t1': roi_limits['roi_t1_max'],
'roi_t2': roi_limits['roi_t2_max'],
@@ -154,7 +148,7 @@ class IHyperOpt(ABC):
'roi_p2': roi_limits['roi_p2_max'],
'roi_p3': roi_limits['roi_p3_max'],
}
logger.info(f"Max roi table: {round_dict(IHyperOpt.generate_roi_table(p), 5)}")
logger.info(f"Max roi table: {round_dict(self.generate_roi_table(p), 5)}")
return [
Integer(roi_limits['roi_t1_min'], roi_limits['roi_t1_max'], name='roi_t1'),
@@ -165,8 +159,7 @@ class IHyperOpt(ABC):
Real(roi_limits['roi_p3_min'], roi_limits['roi_p3_max'], name='roi_p3'),
]
@staticmethod
def stoploss_space() -> List[Dimension]:
def stoploss_space(self) -> List[Dimension]:
"""
Create a stoploss space.
@@ -177,8 +170,7 @@ class IHyperOpt(ABC):
Real(-0.35, -0.02, name='stoploss'),
]
@staticmethod
def generate_trailing_params(params: Dict) -> Dict:
def generate_trailing_params(self, params: Dict) -> Dict:
"""
Create dict with trailing stop parameters.
"""
@@ -190,8 +182,7 @@ class IHyperOpt(ABC):
'trailing_only_offset_is_reached': params['trailing_only_offset_is_reached'],
}
@staticmethod
def trailing_space() -> List[Dimension]:
def trailing_space(self) -> List[Dimension]:
"""
Create a trailing stoploss space.