implement '--strategy-path' argument

This commit is contained in:
gcarq 2018-03-25 16:28:04 +02:00
parent 5fb6fa38aa
commit a356edb117
6 changed files with 76 additions and 38 deletions

View File

@ -86,6 +86,13 @@ class Arguments(object):
type=str, type=str,
metavar='NAME', 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( self.parser.add_argument(
'--dynamic-whitelist', '--dynamic-whitelist',
help='dynamically generate and update whitelist \ help='dynamically generate and update whitelist \

View File

@ -36,6 +36,9 @@ class Configuration(object):
# Add the strategy file to use # Add the strategy file to use
config.update({'strategy': self.args.strategy}) config.update({'strategy': self.args.strategy})
if self.args.strategy_path:
config.update({'strategy_path': self.args.strategy_path})
# Load Common configuration # Load Common configuration
config = self._load_common_config(config) config = self._load_common_config(config)

View File

@ -3,7 +3,6 @@
Main Freqtrade bot script. Main Freqtrade bot script.
Read the documentation to know what cli arguments you need. Read the documentation to know what cli arguments you need.
""" """
import logging import logging
import sys import sys
from typing import List from typing import List

View File

@ -33,7 +33,8 @@ class StrategyResolver(object):
config = config or {} config = config or {}
# Verify the strategy is in the configuration, otherwise fallback to the default strategy # 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 # Set attributes
# Check if we need to override configuration # Check if we need to override configuration
@ -61,33 +62,34 @@ class StrategyResolver(object):
self.strategy.stoploss = float(self.strategy.stoploss) self.strategy.stoploss = float(self.strategy.stoploss)
self.strategy.ticker_interval = int(self.strategy.ticker_interval) 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. Search and loads the specified strategy.
:param strategy_name: name of the module to import :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 :return: Strategy instance or None
""" """
try:
current_path = os.path.dirname(os.path.realpath(__file__)) current_path = os.path.dirname(os.path.realpath(__file__))
abs_paths = [ abs_paths = [
os.path.join(current_path, '..', '..', 'user_data', 'strategies'), os.path.join(current_path, '..', '..', 'user_data', 'strategies'),
current_path, current_path,
] ]
if extra_dir:
# Add extra strategy directory on top of search paths
abs_paths.insert(0, extra_dir)
for path in abs_paths: for path in abs_paths:
strategy = self._search_strategy(path, strategy_name) strategy = self._search_strategy(path, strategy_name)
if strategy: if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
return strategy return strategy
raise ImportError('not found') raise ImportError(
# Fallback to the default strategy "Impossible to load Strategy '{}'. This class does not exist"
except (ImportError, TypeError): " or contains Python code errors".format(strategy_name)
logger.exception(
"Impossible to load Strategy '%s'. This class does not exist"
" or contains Python code errors",
strategy_name
) )
return None
@staticmethod @staticmethod
def _get_valid_strategies(module_path: str, strategy_name: str) -> Optional[Type[IStrategy]]: def _get_valid_strategies(module_path: str, strategy_name: str) -> Optional[Type[IStrategy]]:

View File

@ -1,9 +1,10 @@
# pragma pylint: disable=missing-docstring, protected-access, C0103 # pragma pylint: disable=missing-docstring, protected-access, C0103
import logging import logging
import os import os
import pytest
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.resolver import StrategyResolver from freqtrade.strategy.resolver import StrategyResolver
@ -30,15 +31,29 @@ def test_load_strategy(result):
assert 'adx' in resolver.strategy.populate_indicators(result) assert 'adx' in resolver.strategy.populate_indicators(result)
def test_load_not_found_strategy(caplog): def test_load_strategy_custom_directory(result):
strategy = StrategyResolver() resolver = StrategyResolver()
assert not hasattr(StrategyResolver, 'custom_strategy') assert not hasattr(StrategyResolver, 'custom_strategy')
strategy._load_strategy('NotFoundStrategy')
error_msg = "Impossible to load Strategy '{}'. This class does not " \ extra_dir = os.path.join('some', 'path')
"exist or contains Python code errors".format('NotFoundStrategy') with pytest.raises(
assert ('freqtrade.strategy.resolver', logging.ERROR, error_msg) in caplog.record_tuples 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): def test_strategy(result):
@ -111,11 +126,3 @@ def test_strategy_override_ticker_interval(caplog):
logging.INFO, logging.INFO,
'Override strategy \'ticker_interval\' with value in config file: 60.' 'Override strategy \'ticker_interval\' with value in config file: 60.'
) in caplog.record_tuples ) 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')

View File

@ -71,6 +71,26 @@ def test_parse_args_invalid() -> None:
Arguments(['-c'], '').get_parsed_arg() 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: def test_parse_args_dynamic_whitelist() -> None:
args = Arguments(['--dynamic-whitelist'], '').get_parsed_arg() args = Arguments(['--dynamic-whitelist'], '').get_parsed_arg()
assert args.dynamic_whitelist == 20 assert args.dynamic_whitelist == 20