diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8a2db84a9..0526ef425 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -21,9 +21,9 @@ from freqtrade.wallets import Wallets from freqtrade.edge import Edge from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType +from freqtrade.resolvers import StrategyResolver from freqtrade.state import State -from freqtrade.strategy.interface import SellType -from freqtrade.strategy.resolver import IStrategy, StrategyResolver +from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.exchange.exchange_helpers import order_book_to_dataframe diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6fcde64fa..c6cf7276f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -20,8 +20,8 @@ from freqtrade.configuration import Configuration from freqtrade.exchange import Exchange from freqtrade.misc import file_dump_json from freqtrade.persistence import Trade -from freqtrade.strategy.interface import SellType -from freqtrade.strategy.resolver import IStrategy, StrategyResolver +from freqtrade.resolvers import StrategyResolver +from freqtrade.strategy.interface import SellType, IStrategy logger = logging.getLogger(__name__) diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 59bcbc098..a2189f6c1 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -12,7 +12,7 @@ from freqtrade.edge import Edge from freqtrade.configuration import Configuration from freqtrade.arguments import Arguments from freqtrade.exchange import Exchange -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver logger = logging.getLogger(__name__) diff --git a/freqtrade/resolvers/__init__.py b/freqtrade/resolvers/__init__.py new file mode 100644 index 000000000..fe81b7712 --- /dev/null +++ b/freqtrade/resolvers/__init__.py @@ -0,0 +1,2 @@ +from freqtrade.resolvers.iresolver import IResolver # noqa: F401 +from freqtrade.resolvers.strategyresolver import StrategyResolver # noqa: F401 diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py new file mode 100644 index 000000000..ea38b30c2 --- /dev/null +++ b/freqtrade/resolvers/iresolver.py @@ -0,0 +1,72 @@ +# 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, Any + +from freqtrade.constants import DEFAULT_HYPEROPT +from freqtrade.optimize.hyperopt_interface import IHyperOpt + + +logger = logging.getLogger(__name__) + + +class IResolver(object): + """ + This class contains all the logic to load custom hyperopt class + """ + + def __init__(self, object_type, config: Optional[Dict] = None) -> None: + """ + Load the custom class from config parameter + :param config: configuration dictionary or None + """ + config = config or {} + + @staticmethod + def _get_valid_objects(object_type, module_path: str, + object_name: str) -> Optional[Type[Any]]: + """ + Returns a list of all possible objects for the given module_path of type oject_type + :param object_type: object_type (class) + :param module_path: absolute path to the module + :param object_name: Class name of the object + :return: Tuple with (name, class) or None + """ + + # Generate spec based on absolute path + spec = importlib.util.spec_from_file_location('unknown', module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) # type: ignore # importlib does not use typehints + + valid_objects_gen = ( + obj for name, obj in inspect.getmembers(module, inspect.isclass) + if object_name == name and object_type in obj.__bases__ + ) + return next(valid_objects_gen, None) + + @staticmethod + def _search_object(directory: str, object_type, object_name: str, + kwargs: dict) -> Optional[Any]: + """ + Search for the objectname in the given directory + :param directory: relative or absolute directory path + :return: object instance + """ + logger.debug('Searching for %s %s in \'%s\'', object_type.__name__, object_name, directory) + for entry in os.listdir(directory): + # Only consider python files + if not entry.endswith('.py'): + logger.debug('Ignoring %s', entry) + continue + obj = IResolver._get_valid_objects( + object_type, os.path.abspath(os.path.join(directory, entry)), object_name + ) + if obj: + return obj(**kwargs) + return None diff --git a/freqtrade/strategy/resolver.py b/freqtrade/resolvers/strategyresolver.py similarity index 74% rename from freqtrade/strategy/resolver.py rename to freqtrade/resolvers/strategyresolver.py index 3f25e4838..a7f3b2c25 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/resolvers/strategyresolver.py @@ -3,7 +3,6 @@ """ This module load custom strategies """ -import importlib.util import inspect import logging import os @@ -11,16 +10,17 @@ import tempfile from base64 import urlsafe_b64decode from collections import OrderedDict from pathlib import Path -from typing import Dict, Optional, Type +from typing import Dict, Optional from freqtrade import constants +from freqtrade.resolvers import IResolver from freqtrade.strategy import import_strategy from freqtrade.strategy.interface import IStrategy logger = logging.getLogger(__name__) -class StrategyResolver(object): +class StrategyResolver(IResolver): """ This class contains all the logic to load custom strategy class """ @@ -103,7 +103,8 @@ class StrategyResolver(object): :param extra_dir: additional directory to search for the given strategy :return: Strategy instance or None """ - current_path = os.path.dirname(os.path.realpath(__file__)) + current_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'strategy') + abs_paths = [ os.path.join(os.getcwd(), 'user_data', 'strategies'), current_path, @@ -131,7 +132,8 @@ class StrategyResolver(object): for path in abs_paths: try: - strategy = self._search_strategy(path, strategy_name=strategy_name, config=config) + strategy = self._search_object(directory=path, object_type=IStrategy, + object_name=strategy_name, kwargs={'config': config}) if strategy: logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) strategy._populate_fun_len = len( @@ -149,43 +151,3 @@ class StrategyResolver(object): "Impossible to load Strategy '{}'. This class does not exist" " or contains Python code errors".format(strategy_name) ) - - @staticmethod - def _get_valid_strategies(module_path: str, strategy_name: str) -> Optional[Type[IStrategy]]: - """ - Returns a list of all possible strategies for the given module_path - :param module_path: absolute path to the module - :param strategy_name: Class name of the strategy - :return: Tuple with (name, class) or None - """ - - # Generate spec based on absolute path - spec = importlib.util.spec_from_file_location('unknown', module_path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) # type: ignore # importlib does not use typehints - - valid_strategies_gen = ( - obj for name, obj in inspect.getmembers(module, inspect.isclass) - if strategy_name == name and IStrategy in obj.__bases__ - ) - return next(valid_strategies_gen, None) - - @staticmethod - def _search_strategy(directory: str, strategy_name: str, config: dict) -> Optional[IStrategy]: - """ - Search for the strategy_name in the given directory - :param directory: relative or absolute directory path - :return: name of the strategy class - """ - logger.debug('Searching for strategy %s in \'%s\'', strategy_name, directory) - for entry in os.listdir(directory): - # Only consider python files - if not entry.endswith('.py'): - logger.debug('Ignoring %s', entry) - continue - strategy = StrategyResolver._get_valid_strategies( - os.path.abspath(os.path.join(directory, entry)), strategy_name - ) - if strategy: - return strategy(config) - return None diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 85d140b6d..01d2e8b17 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -7,7 +7,7 @@ import pytest from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.optimize.hyperopt import Hyperopt, start -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver from freqtrade.tests.conftest import log_has, patch_exchange from freqtrade.tests.optimize.test_backtesting import get_args diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index a38050f24..ac20c9cab 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -10,7 +10,7 @@ from pandas import DataFrame from freqtrade.strategy import import_strategy from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.interface import IStrategy -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver def test_import_strategy(caplog): @@ -44,17 +44,19 @@ def test_search_strategy(): path.realpath(__file__)), '..', '..', 'strategy' ) assert isinstance( - StrategyResolver._search_strategy( - default_location, - config=default_config, - strategy_name='DefaultStrategy' + StrategyResolver._search_object( + directory=default_location, + object_type=IStrategy, + kwargs={'config': default_config}, + object_name='DefaultStrategy' ), IStrategy ) - assert StrategyResolver._search_strategy( - default_location, - config=default_config, - strategy_name='NotFoundStrategy' + assert StrategyResolver._search_object( + directory=default_location, + object_type=IStrategy, + kwargs={'config': default_config}, + object_name='NotFoundStrategy' ) is None @@ -77,7 +79,7 @@ def test_load_strategy_invalid_directory(result, caplog): resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) assert ( - 'freqtrade.strategy.resolver', + 'freqtrade.resolvers.strategyresolver', logging.WARNING, 'Path "{}" does not exist'.format(extra_dir), ) in caplog.record_tuples @@ -128,7 +130,7 @@ def test_strategy_override_minimal_roi(caplog): resolver = StrategyResolver(config) assert resolver.strategy.minimal_roi[0] == 0.5 - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategyresolver', logging.INFO, "Override strategy 'minimal_roi' with value in config file: {'0': 0.5}." ) in caplog.record_tuples @@ -143,7 +145,7 @@ def test_strategy_override_stoploss(caplog): resolver = StrategyResolver(config) assert resolver.strategy.stoploss == -0.5 - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategyresolver', logging.INFO, "Override strategy 'stoploss' with value in config file: -0.5." ) in caplog.record_tuples @@ -159,7 +161,7 @@ def test_strategy_override_ticker_interval(caplog): resolver = StrategyResolver(config) assert resolver.strategy.ticker_interval == 60 - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategyresolver', logging.INFO, "Override strategy 'ticker_interval' with value in config file: 60." ) in caplog.record_tuples @@ -175,7 +177,7 @@ def test_strategy_override_process_only_new_candles(caplog): resolver = StrategyResolver(config) assert resolver.strategy.process_only_new_candles - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategyresolver', logging.INFO, "Override process_only_new_candles 'process_only_new_candles' " "with value in config file: True." @@ -201,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.strategy.resolver', + assert ('freqtrade.resolvers.strategyresolver', logging.INFO, "Override strategy 'order_types' with value in config file:" " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'}." diff --git a/freqtrade/tests/test_dataframe.py b/freqtrade/tests/test_dataframe.py index dc030d630..6afb83a3f 100644 --- a/freqtrade/tests/test_dataframe.py +++ b/freqtrade/tests/test_dataframe.py @@ -3,7 +3,7 @@ import pandas from freqtrade.optimize import load_data -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver _pairs = ['ETH/BTC'] diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 68713f296..8fd3a43bd 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -44,7 +44,7 @@ from freqtrade.arguments import Arguments, TimeRange from freqtrade.exchange import Exchange from freqtrade.optimize.backtesting import setup_configuration from freqtrade.persistence import Trade -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver logger = logging.getLogger(__name__) _CONF: Dict[str, Any] = {} diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index 9c3468c74..53f14ca3c 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -27,7 +27,7 @@ import plotly.graph_objs as go from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade import constants -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver import freqtrade.optimize as optimize import freqtrade.misc as misc