diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 550f13e28..89e1accdd 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -23,7 +23,8 @@ from skopt.space import Dimension from freqtrade.configuration import Arguments from freqtrade.data.history import load_data, get_timeframe from freqtrade.optimize.backtesting import Backtesting -# Import IHyperOptLoss to allow users import from this file +# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules +from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F4 from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4 from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver, HyperOptLossResolver diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index f1f123653..b81dc25e3 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -2,6 +2,8 @@ IHyperOpt interface This module defines the interface to apply for hyperopts """ +import logging +import math from abc import ABC, abstractmethod from typing import Dict, Any, Callable, List @@ -9,15 +11,18 @@ from typing import Dict, Any, Callable, List from pandas import DataFrame from skopt.space import Dimension, Integer, Real +from freqtrade.exchange import timeframe_to_minutes + + +logger = logging.getLogger(__name__) + class IHyperOpt(ABC): """ Interface for freqtrade hyperopts - Defines the mandatory structure must follow any custom strategies + Defines the mandatory structure must follow any custom hyperopts - Attributes you can use: - minimal_roi -> Dict: Minimal ROI designed for the strategy - stoploss -> float: optimal stoploss designed for the strategy + Class attributes you can use: ticker_interval -> int: value of the ticker interval to use for the strategy """ ticker_interval: str @@ -75,6 +80,83 @@ class IHyperOpt(ABC): return roi_table + @staticmethod + def roi_space() -> List[Dimension]: + """ + Create a ROI space. + + Defines values to search for each ROI steps. + + This method implements adaptive roi hyperspace with varied + ranges for parameters which automatically adapts to the + ticker interval used. + + It's used by Freqtrade by default, if no custom roi_space method is defined. + """ + + # Default scaling coefficients for the roi hyperspace. Can be changed + # to adjust resulting ranges of the ROI tables. + # Increase if you need wider ranges in the roi hyperspace, decrease if shorter + # ranges are needed. + roi_t_alpha = 1.0 + roi_p_alpha = 1.0 + + ticker_interval_mins = timeframe_to_minutes(IHyperOpt.ticker_interval) + + # We define here limits for the ROI space parameters automagically adapted to the + # ticker_interval used by the bot: + # + # * 'roi_t' (limits for the time intervals in the ROI tables) components + # are scaled linearly. + # * 'roi_p' (limits for the ROI value steps) components are scaled logarithmically. + # + # The scaling is designed so that it maps exactly to the legacy Freqtrade roi_space() + # method for the 5m ticker interval. + roi_t_scale = ticker_interval_mins / 5 + roi_p_scale = math.log1p(ticker_interval_mins) / math.log1p(5) + roi_limits = { + 'roi_t1_min': int(10 * roi_t_scale * roi_t_alpha), + 'roi_t1_max': int(120 * roi_t_scale * roi_t_alpha), + 'roi_t2_min': int(10 * roi_t_scale * roi_t_alpha), + 'roi_t2_max': int(60 * roi_t_scale * roi_t_alpha), + 'roi_t3_min': int(10 * roi_t_scale * roi_t_alpha), + 'roi_t3_max': int(40 * roi_t_scale * roi_t_alpha), + 'roi_p1_min': 0.01 * roi_p_scale * roi_p_alpha, + 'roi_p1_max': 0.04 * roi_p_scale * roi_p_alpha, + 'roi_p2_min': 0.01 * roi_p_scale * roi_p_alpha, + 'roi_p2_max': 0.07 * roi_p_scale * roi_p_alpha, + 'roi_p3_min': 0.01 * roi_p_scale * roi_p_alpha, + 'roi_p3_max': 0.20 * roi_p_scale * roi_p_alpha, + } + logger.debug(f"Using roi space limits: {roi_limits}") + p = { + 'roi_t1': roi_limits['roi_t1_min'], + 'roi_t2': roi_limits['roi_t2_min'], + 'roi_t3': roi_limits['roi_t3_min'], + 'roi_p1': roi_limits['roi_p1_min'], + 'roi_p2': roi_limits['roi_p2_min'], + 'roi_p3': roi_limits['roi_p3_min'], + } + logger.info(f"Min roi table: {IHyperOpt.generate_roi_table(p)}") + p = { + 'roi_t1': roi_limits['roi_t1_max'], + 'roi_t2': roi_limits['roi_t2_max'], + 'roi_t3': roi_limits['roi_t3_max'], + 'roi_p1': roi_limits['roi_p1_max'], + 'roi_p2': roi_limits['roi_p2_max'], + 'roi_p3': roi_limits['roi_p3_max'], + } + logger.info(f"Max roi table: {IHyperOpt.generate_roi_table(p)}") + + return [ + Integer(roi_limits['roi_t1_min'], roi_limits['roi_t1_max'], name='roi_t1'), + Integer(roi_limits['roi_t2_min'], roi_limits['roi_t2_max'], name='roi_t2'), + Integer(roi_limits['roi_t3_min'], roi_limits['roi_t3_max'], name='roi_t3'), + Real(roi_limits['roi_p1_min'], roi_limits['roi_p1_max'], name='roi_p1'), + Real(roi_limits['roi_p2_min'], roi_limits['roi_p2_max'], name='roi_p2'), + Real(roi_limits['roi_p3_min'], roi_limits['roi_p3_max'], name='roi_p3'), + ] + @staticmethod def stoploss_space() -> List[Dimension]: """ @@ -87,19 +169,14 @@ class IHyperOpt(ABC): Real(-0.5, -0.02, name='stoploss'), ] - @staticmethod - def roi_space() -> List[Dimension]: - """ - Create a ROI space. + # This is needed for proper unpickling the class attribute ticker_interval + # which is set to the actual value by the resolver. + # Why do I still need such shamanic mantras in modern python? + def __getstate__(self): + state = self.__dict__.copy() + state['ticker_interval'] = self.ticker_interval + return state - Defines values to search for each ROI steps. - You may override it in your custom Hyperopt class. - """ - return [ - Integer(10, 120, name='roi_t1'), - Integer(10, 60, name='roi_t2'), - Integer(10, 40, name='roi_t3'), - Real(0.01, 0.04, name='roi_p1'), - Real(0.01, 0.07, name='roi_p2'), - Real(0.01, 0.20, name='roi_p3'), - ] + def __setstate__(self, state): + self.__dict__.update(state) + IHyperOpt.ticker_interval = state['ticker_interval'] diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 5027d7ddf..540e1ca55 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -34,7 +34,7 @@ class HyperOptResolver(IResolver): self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path')) # Assign ticker_interval to be used in hyperopt - self.hyperopt.__class__.ticker_interval = str(config['ticker_interval']) + IHyperOpt.ticker_interval = str(config['ticker_interval']) if not hasattr(self.hyperopt, 'populate_buy_trend'): logger.warning("Custom Hyperopt does not provide populate_buy_trend. "