diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 70d20673c..fcf35acfe 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -22,7 +22,7 @@ from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.optimize import load_data from freqtrade.optimize.backtesting import Backtesting -from freqtrade.optimize.hyperopt_resolver import HyperOptResolver +from freqtrade.resolvers import HyperOptResolver logger = logging.getLogger(__name__) diff --git a/freqtrade/optimize/hyperopt_resolver.py b/freqtrade/optimize/hyperopt_resolver.py deleted file mode 100644 index 3d019e8df..000000000 --- a/freqtrade/optimize/hyperopt_resolver.py +++ /dev/null @@ -1,104 +0,0 @@ -# pragma pylint: disable=attribute-defined-outside-init - -""" -This module load custom hyperopts -""" -import importlib.util -import inspect -import logging -import os -from typing import Optional, Dict, Type - -from freqtrade.constants import DEFAULT_HYPEROPT -from freqtrade.optimize.hyperopt_interface import IHyperOpt - - -logger = logging.getLogger(__name__) - - -class HyperOptResolver(object): - """ - This class contains all the logic to load custom hyperopt class - """ - - __slots__ = ['hyperopt'] - - def __init__(self, config: Optional[Dict] = None) -> None: - """ - Load the custom class from config parameter - :param config: configuration dictionary or None - """ - config = config or {} - - # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt - hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT - self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path')) - - def _load_hyperopt( - self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt: - """ - Search and loads the specified hyperopt. - :param hyperopt_name: name of the module to import - :param extra_dir: additional directory to search for the given hyperopt - :return: HyperOpt instance or None - """ - current_path = os.path.dirname(os.path.realpath(__file__)) - abs_paths = [ - os.path.join(current_path, '..', '..', 'user_data', 'hyperopts'), - current_path, - ] - - if extra_dir: - # Add extra hyperopt directory on top of search paths - abs_paths.insert(0, extra_dir) - - for path in abs_paths: - hyperopt = self._search_hyperopt(path, hyperopt_name) - if hyperopt: - logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, path) - return hyperopt - - raise ImportError( - "Impossible to load Hyperopt '{}'. This class does not exist" - " or contains Python code errors".format(hyperopt_name) - ) - - @staticmethod - def _get_valid_hyperopts(module_path: str, hyperopt_name: str) -> Optional[Type[IHyperOpt]]: - """ - Returns a list of all possible hyperopts for the given module_path - :param module_path: absolute path to the module - :param hyperopt_name: Class name of the hyperopt - :return: Tuple with (name, class) or None - """ - - # Generate spec based on absolute path - spec = importlib.util.spec_from_file_location('user_data.hyperopts', module_path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) # type: ignore # importlib does not use typehints - - valid_hyperopts_gen = ( - obj for name, obj in inspect.getmembers(module, inspect.isclass) - if hyperopt_name == name and IHyperOpt in obj.__bases__ - ) - return next(valid_hyperopts_gen, None) - - @staticmethod - def _search_hyperopt(directory: str, hyperopt_name: str) -> Optional[IHyperOpt]: - """ - Search for the hyperopt_name in the given directory - :param directory: relative or absolute directory path - :return: name of the hyperopt class - """ - logger.debug('Searching for hyperopt %s in \'%s\'', hyperopt_name, directory) - for entry in os.listdir(directory): - # Only consider python files - if not entry.endswith('.py'): - logger.debug('Ignoring %s', entry) - continue - hyperopt = HyperOptResolver._get_valid_hyperopts( - os.path.abspath(os.path.join(directory, entry)), hyperopt_name - ) - if hyperopt: - return hyperopt() - return None diff --git a/freqtrade/resolvers/__init__.py b/freqtrade/resolvers/__init__.py index fe81b7712..84e3bcdcd 100644 --- a/freqtrade/resolvers/__init__.py +++ b/freqtrade/resolvers/__init__.py @@ -1,2 +1,3 @@ from freqtrade.resolvers.iresolver import IResolver # noqa: F401 -from freqtrade.resolvers.strategyresolver import StrategyResolver # noqa: F401 +from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401 +from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401 diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py new file mode 100644 index 000000000..38cb683c9 --- /dev/null +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -0,0 +1,64 @@ +# pragma pylint: disable=attribute-defined-outside-init + +""" +This module load custom hyperopts +""" +import logging +from os import path +from typing import Optional, Dict + +from freqtrade.constants import DEFAULT_HYPEROPT +from freqtrade.optimize.hyperopt_interface import IHyperOpt +from freqtrade.resolvers import IResolver + +logger = logging.getLogger(__name__) + + +class HyperOptResolver(IResolver): + """ + This class contains all the logic to load custom hyperopt class + """ + + __slots__ = ['hyperopt'] + + def __init__(self, config: Optional[Dict] = None) -> None: + """ + Load the custom class from config parameter + :param config: configuration dictionary or None + """ + config = config or {} + + # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt + hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT + self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path')) + + def _load_hyperopt( + self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt: + """ + Search and loads the specified hyperopt. + :param hyperopt_name: name of the module to import + :param extra_dir: additional directory to search for the given hyperopt + :return: HyperOpt instance or None + """ + current_path = path.join(path.dirname(path.dirname(path.realpath(__file__))), 'optimize') + + abs_paths = [ + path.join(current_path, '..', '..', 'user_data', 'hyperopts'), + current_path, + ] + + if extra_dir: + # Add extra hyperopt directory on top of search paths + abs_paths.insert(0, extra_dir) + + for _path in abs_paths: + hyperopt = self._search_object(directory=_path, object_type=IHyperOpt, + object_name=hyperopt_name) + if hyperopt: + logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, _path) + return hyperopt + + raise ImportError( + "Impossible to load Hyperopt '{}'. This class does not exist" + " or contains Python code errors".format(hyperopt_name) + ) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 37230537e..5cf6a1bc2 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -48,7 +48,7 @@ class IResolver(object): @staticmethod def _search_object(directory: str, object_type, object_name: str, - kwargs: dict) -> Optional[Any]: + kwargs: dict = {}) -> Optional[Any]: """ Search for the objectname in the given directory :param directory: relative or absolute directory path diff --git a/freqtrade/resolvers/strategyresolver.py b/freqtrade/resolvers/strategy_resolver.py similarity index 98% rename from freqtrade/resolvers/strategyresolver.py rename to freqtrade/resolvers/strategy_resolver.py index 273effe2d..f950a6e41 100644 --- a/freqtrade/resolvers/strategyresolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -145,7 +145,7 @@ class StrategyResolver(IResolver): return import_strategy(strategy, config=config) except FileNotFoundError: - logger.warning('Path "%s" does not exist', path) + logger.warning('Path "%s" does not exist', _path) raise ImportError( "Impossible to load Strategy '{}'. This class does not exist" diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index ac20c9cab..230ec7ab7 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -79,7 +79,7 @@ def test_load_strategy_invalid_directory(result, caplog): resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) assert ( - 'freqtrade.resolvers.strategyresolver', + 'freqtrade.resolvers.strategy_resolver', logging.WARNING, 'Path "{}" does not exist'.format(extra_dir), ) in caplog.record_tuples @@ -130,7 +130,7 @@ def test_strategy_override_minimal_roi(caplog): resolver = StrategyResolver(config) assert resolver.strategy.minimal_roi[0] == 0.5 - assert ('freqtrade.resolvers.strategyresolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'minimal_roi' with value in config file: {'0': 0.5}." ) in caplog.record_tuples @@ -145,7 +145,7 @@ def test_strategy_override_stoploss(caplog): resolver = StrategyResolver(config) assert resolver.strategy.stoploss == -0.5 - assert ('freqtrade.resolvers.strategyresolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'stoploss' with value in config file: -0.5." ) in caplog.record_tuples @@ -161,7 +161,7 @@ def test_strategy_override_ticker_interval(caplog): resolver = StrategyResolver(config) assert resolver.strategy.ticker_interval == 60 - assert ('freqtrade.resolvers.strategyresolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'ticker_interval' with value in config file: 60." ) in caplog.record_tuples @@ -177,7 +177,7 @@ def test_strategy_override_process_only_new_candles(caplog): resolver = StrategyResolver(config) assert resolver.strategy.process_only_new_candles - assert ('freqtrade.resolvers.strategyresolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override process_only_new_candles 'process_only_new_candles' " "with value in config file: True." @@ -203,7 +203,7 @@ def test_strategy_override_order_types(caplog): for method in ['buy', 'sell', 'stoploss']: assert resolver.strategy.order_types[method] == order_types[method] - assert ('freqtrade.resolvers.strategyresolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'order_types' with value in config file:" " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'}."