diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index 6fb12a65f..d7c8144cf 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -14,6 +14,7 @@ class ExchangeResolver(IResolver): """ This class contains all the logic to load a custom exchange class """ + type_name = "Exchange" __slots__ = ['exchange'] diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 30e097f3f..229fc7547 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -19,6 +19,7 @@ class HyperOptResolver(IResolver): """ This class contains all the logic to load custom hyperopt class """ + type_name = "Hyperopt" __slots__ = ['hyperopt'] diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 1065abba7..b4aeebb25 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -9,6 +9,9 @@ import logging from pathlib import Path from typing import Any, Optional, Tuple, Type, Union +from freqtrade import OperationalException + + logger = logging.getLogger(__name__) @@ -16,6 +19,7 @@ class IResolver(object): """ This class contains all the logic to load custom classes """ + type_name = "Unknown" @staticmethod def _get_valid_object(object_type, module_path: Path, @@ -43,8 +47,8 @@ class IResolver(object): ) return next(valid_objects_gen, None) - @staticmethod - def _search_object(directory: Path, object_type, object_name: str, + @classmethod + def _search_object(self, directory: Path, object_type, object_name: str, kwargs: dict = {}) -> Union[Tuple[Any, Path], Tuple[None, None]]: """ Search for the objectname in the given directory @@ -52,6 +56,7 @@ class IResolver(object): :return: object instance """ logger.debug("Searching for %s %s in '%s'", object_type.__name__, object_name, directory) + objs = [] for entry in directory.iterdir(): # Only consider python files if not str(entry).endswith('.py'): @@ -62,5 +67,16 @@ class IResolver(object): object_type, module_path, object_name ) if obj: - return (obj(**kwargs), module_path) - return (None, None) + objs.append((obj, module_path)) + if len(objs) == 0: + return (None, None) + elif len(objs) == 1: + obj, module_path = objs[0] + return (obj(**kwargs), module_path) + else: + raise OperationalException( + f"Cannot resolve object: found more than one objects of type " + f"`{self.type_name}` with name `{object_name}`. " + "Use unique names for custom strategies, hyperopts and other custom objects " + "so that Freqtrade can be able to resolve them. " + f"Found in modules: {[str(m) for (_, m) in objs]}") diff --git a/freqtrade/resolvers/pairlist_resolver.py b/freqtrade/resolvers/pairlist_resolver.py index 651a7d33f..0cf8e4a7d 100644 --- a/freqtrade/resolvers/pairlist_resolver.py +++ b/freqtrade/resolvers/pairlist_resolver.py @@ -17,6 +17,7 @@ class PairListResolver(IResolver): """ This class contains all the logic to load custom hyperopt class """ + type_name = "PairList" __slots__ = ['pairlist'] diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 4a5604db8..27baa179e 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -23,6 +23,7 @@ class StrategyResolver(IResolver): """ This class contains all the logic to load custom strategy class """ + type_name = "Strategy" __slots__ = ['strategy'] diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index c62bfe5dc..fd16fc67f 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -2,9 +2,8 @@ import logging import sys from copy import deepcopy +from freqtrade import constants from freqtrade.strategy.interface import IStrategy -# Import Default-Strategy to have hyperopt correctly resolve -from freqtrade.strategy.default_strategy import DefaultStrategy # noqa: F401 logger = logging.getLogger(__name__) @@ -28,7 +27,10 @@ def import_strategy(strategy: IStrategy, config: dict) -> IStrategy: attr = deepcopy(comb) # Adjust module name - attr['__module__'] = 'freqtrade.strategy' + attr['__module__'] = ( + 'freqtrade.strategy.default_strategy' + if config.get('strategy') == constants.DEFAULT_STRATEGY + else 'freqtrade.strategy') name = strategy.__class__.__name__ clazz = type(name, (IStrategy,), attr) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 9a2c950e5..62f066d48 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -44,7 +44,8 @@ def test_import_strategy(caplog): def test_search_strategy(): default_config = {} - default_location = Path(__file__).parent.parent.joinpath('strategy').resolve() + # Use default strategy from freqtrade/strategy, not from freqtrade/tests/strategy + default_location = Path(__file__).parent.parent.parent.joinpath('strategy').resolve() s, _ = StrategyResolver._search_object( directory=default_location,