<reponame>Fractate/freqbot<gh_stars>1-10
"""
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 Callable, Dict, List

from freqtrade.exceptions import OperationalException


with suppress(ImportError):
    from skopt.space import Dimension

from freqtrade.optimize.hyperopt_interface import EstimatorType, IHyperOpt


def _format_exception_message(space: str) -> str:
    raise OperationalException(
        f"The '{space}' space is included into the hyperoptimization "
        f"but no parameter for this space was not found in your Strategy. "
        f"Please make sure to have parameters for this space enabled for optimization "
        f"or remove the '{space}' space from hyperoptimization.")


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 _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):
        # TODO: is this necessary, or can we call "generate_space" directly?
        indicator_space = list(self._generate_indicator_space(category))
        if len(indicator_space) > 0:
            return indicator_space
        else:
            _format_exception_message(category)

    def buy_indicator_space(self) -> List['Dimension']:
        return self._get_indicator_space('buy')

    def sell_indicator_space(self) -> List['Dimension']:
        return self._get_indicator_space('sell')

    def protection_space(self) -> List['Dimension']:
        return self._get_indicator_space('protection')

    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')()

    def generate_estimator(self) -> EstimatorType:
        return self._get_func('generate_estimator')()
