From a356edb117c07e3a4c4311cd45d20441bcefeef6 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sun, 25 Mar 2018 16:28:04 +0200 Subject: [PATCH] implement '--strategy-path' argument --- freqtrade/arguments.py | 7 ++++ freqtrade/configuration.py | 3 ++ freqtrade/main.py | 1 - freqtrade/strategy/resolver.py | 46 ++++++++++++----------- freqtrade/tests/strategy/test_strategy.py | 37 ++++++++++-------- freqtrade/tests/test_arguments.py | 20 ++++++++++ 6 files changed, 76 insertions(+), 38 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 052b4534e..35f8c6609 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -86,6 +86,13 @@ class Arguments(object): type=str, metavar='NAME', ) + self.parser.add_argument( + '--strategy-path', + help='specify additional strategy lookup path', + dest='strategy_path', + type=str, + metavar='PATH', + ) self.parser.add_argument( '--dynamic-whitelist', help='dynamically generate and update whitelist \ diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 1314f624e..8e395e21d 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -36,6 +36,9 @@ class Configuration(object): # Add the strategy file to use config.update({'strategy': self.args.strategy}) + if self.args.strategy_path: + config.update({'strategy_path': self.args.strategy_path}) + # Load Common configuration config = self._load_common_config(config) diff --git a/freqtrade/main.py b/freqtrade/main.py index d62c92b9f..9639922f9 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -3,7 +3,6 @@ Main Freqtrade bot script. Read the documentation to know what cli arguments you need. """ - import logging import sys from typing import List diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 18a03ea58..c5a7cd577 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -33,7 +33,8 @@ class StrategyResolver(object): config = config or {} # Verify the strategy is in the configuration, otherwise fallback to the default strategy - self.strategy = self._load_strategy(config.get('strategy') or Constants.DEFAULT_STRATEGY) + strategy_name = config.get('strategy') or Constants.DEFAULT_STRATEGY + self.strategy = self._load_strategy(strategy_name, extra_dir=config.get('strategy_path')) # Set attributes # Check if we need to override configuration @@ -61,33 +62,34 @@ class StrategyResolver(object): self.strategy.stoploss = float(self.strategy.stoploss) self.strategy.ticker_interval = int(self.strategy.ticker_interval) - def _load_strategy(self, strategy_name: str) -> Optional[IStrategy]: + def _load_strategy( + self, strategy_name: str, extra_dir: Optional[str] = None) -> Optional[IStrategy]: """ Search and loads the specified strategy. :param strategy_name: name of the module to import + :param extra_dir: additional directory to search for the given strategy :return: Strategy instance or None """ - try: - current_path = os.path.dirname(os.path.realpath(__file__)) - abs_paths = [ - os.path.join(current_path, '..', '..', 'user_data', 'strategies'), - current_path, - ] - for path in abs_paths: - strategy = self._search_strategy(path, strategy_name) - if strategy: - logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) - return strategy + current_path = os.path.dirname(os.path.realpath(__file__)) + abs_paths = [ + os.path.join(current_path, '..', '..', 'user_data', 'strategies'), + current_path, + ] - raise ImportError('not found') - # Fallback to the default strategy - except (ImportError, TypeError): - logger.exception( - "Impossible to load Strategy '%s'. This class does not exist" - " or contains Python code errors", - strategy_name - ) - return None + if extra_dir: + # Add extra strategy directory on top of search paths + abs_paths.insert(0, extra_dir) + + for path in abs_paths: + strategy = self._search_strategy(path, strategy_name) + if strategy: + logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) + return strategy + + raise ImportError( + "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]]: diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 87818b05f..0f45d8d04 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -1,9 +1,10 @@ # pragma pylint: disable=missing-docstring, protected-access, C0103 import logging - import os +import pytest + from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.resolver import StrategyResolver @@ -30,15 +31,29 @@ def test_load_strategy(result): assert 'adx' in resolver.strategy.populate_indicators(result) -def test_load_not_found_strategy(caplog): - strategy = StrategyResolver() +def test_load_strategy_custom_directory(result): + resolver = StrategyResolver() assert not hasattr(StrategyResolver, 'custom_strategy') - strategy._load_strategy('NotFoundStrategy') - error_msg = "Impossible to load Strategy '{}'. This class does not " \ - "exist or contains Python code errors".format('NotFoundStrategy') - assert ('freqtrade.strategy.resolver', logging.ERROR, error_msg) in caplog.record_tuples + extra_dir = os.path.join('some', 'path') + with pytest.raises( + FileNotFoundError, + match=r".*No such file or directory: '{}'".format(extra_dir)): + resolver._load_strategy('TestStrategy', extra_dir) + + assert not hasattr(StrategyResolver, 'custom_strategy') + + assert hasattr(resolver.strategy, 'populate_indicators') + assert 'adx' in resolver.strategy.populate_indicators(result) + + +def test_load_not_found_strategy(): + strategy = StrategyResolver() + with pytest.raises(ImportError, + match=r'Impossible to load Strategy \'NotFoundStrategy\'.' + r' This class does not exist or contains Python code errors'): + strategy._load_strategy('NotFoundStrategy') def test_strategy(result): @@ -111,11 +126,3 @@ def test_strategy_override_ticker_interval(caplog): logging.INFO, 'Override strategy \'ticker_interval\' with value in config file: 60.' ) in caplog.record_tuples - - -def test_strategy_fallback_default_strategy(): - strategy = StrategyResolver() - - assert not hasattr(StrategyResolver, 'custom_strategy') - strategy._load_strategy('../../super_duper') - assert not hasattr(StrategyResolver, 'custom_strategy') diff --git a/freqtrade/tests/test_arguments.py b/freqtrade/tests/test_arguments.py index 3e0639304..3377746b7 100644 --- a/freqtrade/tests/test_arguments.py +++ b/freqtrade/tests/test_arguments.py @@ -71,6 +71,26 @@ def test_parse_args_invalid() -> None: Arguments(['-c'], '').get_parsed_arg() +def test_parse_args_strategy() -> None: + args = Arguments(['--strategy', 'SomeStrategy'], '').get_parsed_arg() + assert args.strategy == 'SomeStrategy' + + +def test_parse_args_strategy_invalid() -> None: + with pytest.raises(SystemExit, match=r'2'): + Arguments(['--strategy'], '').get_parsed_arg() + + +def test_parse_args_strategy_path() -> None: + args = Arguments(['--strategy-path', '/some/path'], '').get_parsed_arg() + assert args.strategy_path == '/some/path' + + +def test_parse_args_strategy_path_invalid() -> None: + with pytest.raises(SystemExit, match=r'2'): + Arguments(['--strategy-path'], '').get_parsed_arg() + + def test_parse_args_dynamic_whitelist() -> None: args = Arguments(['--dynamic-whitelist'], '').get_parsed_arg() assert args.dynamic_whitelist == 20