From 280886104c0354a8b2c11ef24227a01e960e3ab7 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 23:20:21 +0100 Subject: [PATCH 01/13] strategy: remove unneeded population methods in resolver --- freqtrade/analyze.py | 2 +- freqtrade/strategy/resolver.py | 55 +++++---------------- freqtrade/tests/strategy/test_strategy.py | 59 +++++++++-------------- 3 files changed, 37 insertions(+), 79 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index e6eb01e93..ccdbb139e 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -36,7 +36,7 @@ class Analyze(object): :param config: Bot configuration (use the one from Configuration()) """ self.config = config - self.strategy = StrategyResolver(self.config) + self.strategy = StrategyResolver(self.config).strategy @staticmethod def parse_ticker_dataframe(ticker: list) -> DataFrame: diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 826a50513..5709d15a7 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -10,8 +10,6 @@ import os from collections import OrderedDict from typing import Optional, Dict, Type -from pandas import DataFrame - from freqtrade.constants import Constants from freqtrade.strategy.interface import IStrategy @@ -38,42 +36,37 @@ class StrategyResolver(object): strategy = Constants.DEFAULT_STRATEGY # Try to load the strategy - self._load_strategy(strategy) + self.strategy = self._load_strategy(strategy) # Set attributes # Check if we need to override configuration if 'minimal_roi' in config: - self.custom_strategy.minimal_roi = config['minimal_roi'] + self.strategy.minimal_roi = config['minimal_roi'] logger.info("Override strategy \'minimal_roi\' with value in config file.") if 'stoploss' in config: - self.custom_strategy.stoploss = config['stoploss'] + self.strategy.stoploss = float(config['stoploss']) logger.info( "Override strategy \'stoploss\' with value in config file: %s.", config['stoploss'] ) if 'ticker_interval' in config: - self.custom_strategy.ticker_interval = config['ticker_interval'] + self.strategy.ticker_interval = int(config['ticker_interval']) logger.info( "Override strategy \'ticker_interval\' with value in config file: %s.", config['ticker_interval'] ) # Minimal ROI designed for the strategy - self.minimal_roi = OrderedDict(sorted( - {int(key): value for (key, value) in self.custom_strategy.minimal_roi.items()}.items(), + self.strategy.minimal_roi = OrderedDict(sorted( + {int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(), key=lambda t: t[0])) # sort after converting to number - # Optimal stoploss designed for the strategy - self.stoploss = float(self.custom_strategy.stoploss) - - self.ticker_interval = int(self.custom_strategy.ticker_interval) - - def _load_strategy(self, strategy_name: str) -> None: + def _load_strategy(self, strategy_name: str) -> Optional[IStrategy]: """ Search and loads the specified strategy. :param strategy_name: name of the module to import - :return: None + :return: Strategy instance or None """ try: current_path = os.path.dirname(os.path.realpath(__file__)) @@ -82,10 +75,10 @@ class StrategyResolver(object): current_path, ] for path in abs_paths: - self.custom_strategy = self._search_strategy(path, strategy_name) - if self.custom_strategy: + strategy = self._search_strategy(path, strategy_name) + if strategy: logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) - return None + return strategy raise ImportError('not found') # Fallback to the default strategy @@ -99,6 +92,7 @@ class StrategyResolver(object): "The error is:\n%s.", error ) + return None @staticmethod def _get_valid_strategies(module_path: str, strategy_name: str) -> Optional[Type[IStrategy]]: @@ -139,28 +133,3 @@ class StrategyResolver(object): if strategy: return strategy() return None - - def populate_indicators(self, dataframe: DataFrame) -> DataFrame: - """ - Populate indicators that will be used in the Buy and Sell strategy - :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() - :return: a Dataframe with all mandatory indicators for the strategies - """ - return self.custom_strategy.populate_indicators(dataframe) - - def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: - """ - Based on TA indicators, populates the buy signal for the given dataframe - :param dataframe: DataFrame - :return: DataFrame with buy column - :return: - """ - return self.custom_strategy.populate_buy_trend(dataframe) - - def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: - """ - Based on TA indicators, populates the sell signal for the given dataframe - :param dataframe: DataFrame - :return: DataFrame with buy column - """ - return self.custom_strategy.populate_sell_trend(dataframe) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 075efd6ab..6e69e2b09 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -19,15 +19,15 @@ def test_search_strategy(): def test_load_strategy(result): - strategy = StrategyResolver() + resolver = StrategyResolver() assert not hasattr(StrategyResolver, 'custom_strategy') - strategy._load_strategy('TestStrategy') + resolver._load_strategy('TestStrategy') assert not hasattr(StrategyResolver, 'custom_strategy') - assert hasattr(strategy.custom_strategy, 'populate_indicators') - assert 'adx' in strategy.populate_indicators(result) + assert hasattr(resolver.strategy, 'populate_indicators') + assert 'adx' in resolver.strategy.populate_indicators(result) def test_load_not_found_strategy(caplog): @@ -42,23 +42,23 @@ def test_load_not_found_strategy(caplog): def test_strategy(result): - strategy = StrategyResolver({'strategy': 'DefaultStrategy'}) + resolver = StrategyResolver({'strategy': 'DefaultStrategy'}) - assert hasattr(strategy.custom_strategy, 'minimal_roi') - assert strategy.minimal_roi[0] == 0.04 + assert hasattr(resolver.strategy, 'minimal_roi') + assert resolver.strategy.minimal_roi[0] == 0.04 - assert hasattr(strategy.custom_strategy, 'stoploss') - assert strategy.stoploss == -0.10 + assert hasattr(resolver.strategy, 'stoploss') + assert resolver.strategy.stoploss == -0.10 - assert hasattr(strategy.custom_strategy, 'populate_indicators') - assert 'adx' in strategy.populate_indicators(result) + assert hasattr(resolver.strategy, 'populate_indicators') + assert 'adx' in resolver.strategy.populate_indicators(result) - assert hasattr(strategy.custom_strategy, 'populate_buy_trend') - dataframe = strategy.populate_buy_trend(strategy.populate_indicators(result)) + assert hasattr(resolver.strategy, 'populate_buy_trend') + dataframe = resolver.strategy.populate_buy_trend(resolver.strategy.populate_indicators(result)) assert 'buy' in dataframe.columns - assert hasattr(strategy.custom_strategy, 'populate_sell_trend') - dataframe = strategy.populate_sell_trend(strategy.populate_indicators(result)) + assert hasattr(resolver.strategy, 'populate_sell_trend') + dataframe = resolver.strategy.populate_sell_trend(resolver.strategy.populate_indicators(result)) assert 'sell' in dataframe.columns @@ -70,10 +70,10 @@ def test_strategy_override_minimal_roi(caplog): "0": 0.5 } } - strategy = StrategyResolver(config) + resolver = StrategyResolver(config) - assert hasattr(strategy.custom_strategy, 'minimal_roi') - assert strategy.minimal_roi[0] == 0.5 + assert hasattr(resolver.strategy, 'minimal_roi') + assert resolver.strategy.minimal_roi[0] == 0.5 assert ('freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'minimal_roi\' with value in config file.' @@ -86,10 +86,10 @@ def test_strategy_override_stoploss(caplog): 'strategy': 'DefaultStrategy', 'stoploss': -0.5 } - strategy = StrategyResolver(config) + resolver = StrategyResolver(config) - assert hasattr(strategy.custom_strategy, 'stoploss') - assert strategy.stoploss == -0.5 + assert hasattr(resolver.strategy, 'stoploss') + assert resolver.strategy.stoploss == -0.5 assert ('freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'stoploss\' with value in config file: -0.5.' @@ -103,10 +103,10 @@ def test_strategy_override_ticker_interval(caplog): 'strategy': 'DefaultStrategy', 'ticker_interval': 60 } - strategy = StrategyResolver(config) + resolver = StrategyResolver(config) - assert hasattr(strategy.custom_strategy, 'ticker_interval') - assert strategy.ticker_interval == 60 + assert hasattr(resolver.strategy, 'ticker_interval') + assert resolver.strategy.ticker_interval == 60 assert ('freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'ticker_interval\' with value in config file: 60.' @@ -120,14 +120,3 @@ def test_strategy_fallback_default_strategy(): assert not hasattr(StrategyResolver, 'custom_strategy') strategy._load_strategy('../../super_duper') assert not hasattr(StrategyResolver, 'custom_strategy') - - -def test_strategy_singleton(): - strategy1 = StrategyResolver({'strategy': 'DefaultStrategy'}) - - assert hasattr(strategy1.custom_strategy, 'minimal_roi') - assert strategy1.minimal_roi[0] == 0.04 - - strategy2 = StrategyResolver() - assert hasattr(strategy2.custom_strategy, 'minimal_roi') - assert strategy2.minimal_roi[0] == 0.04 From 99e890bc99c93685d0406dcf12617d41c494c8b0 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 23:22:31 +0100 Subject: [PATCH 02/13] simplify resolver constructor --- freqtrade/strategy/resolver.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 5709d15a7..d5e9e7b5c 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -30,13 +30,7 @@ class StrategyResolver(object): config = config or {} # Verify the strategy is in the configuration, otherwise fallback to the default strategy - if 'strategy' in config: - strategy = config['strategy'] - else: - strategy = Constants.DEFAULT_STRATEGY - - # Try to load the strategy - self.strategy = self._load_strategy(strategy) + self.strategy = self._load_strategy(config.get('strategy') or Constants.DEFAULT_STRATEGY) # Set attributes # Check if we need to override configuration From 5fb6fa38aae95d8540124016a8b9a9aa544b076e Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 23:32:17 +0100 Subject: [PATCH 03/13] apply __slots__ to resolver and reintroduce type conversations --- freqtrade/strategy/resolver.py | 21 +++++++++++---------- freqtrade/tests/strategy/test_strategy.py | 1 - 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index d5e9e7b5c..18a03ea58 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -21,6 +21,9 @@ class StrategyResolver(object): """ This class contains all the logic to load custom strategy class """ + + __slots__ = ['strategy'] + def __init__(self, config: Optional[Dict] = None) -> None: """ Load the custom class from config parameter @@ -39,22 +42,24 @@ class StrategyResolver(object): logger.info("Override strategy \'minimal_roi\' with value in config file.") if 'stoploss' in config: - self.strategy.stoploss = float(config['stoploss']) + self.strategy.stoploss = config['stoploss'] logger.info( "Override strategy \'stoploss\' with value in config file: %s.", config['stoploss'] ) if 'ticker_interval' in config: - self.strategy.ticker_interval = int(config['ticker_interval']) + self.strategy.ticker_interval = config['ticker_interval'] logger.info( "Override strategy \'ticker_interval\' with value in config file: %s.", config['ticker_interval'] ) - # Minimal ROI designed for the strategy + # Sort and apply type conversions self.strategy.minimal_roi = OrderedDict(sorted( {int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(), - key=lambda t: t[0])) # sort after converting to number + key=lambda t: t[0])) + 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]: """ @@ -76,16 +81,12 @@ class StrategyResolver(object): raise ImportError('not found') # Fallback to the default strategy - except (ImportError, TypeError) as error: - logger.error( + except (ImportError, TypeError): + logger.exception( "Impossible to load Strategy '%s'. This class does not exist" " or contains Python code errors", strategy_name ) - logger.error( - "The error is:\n%s.", - error - ) return None @staticmethod diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 6e69e2b09..87818b05f 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -115,7 +115,6 @@ def test_strategy_override_ticker_interval(caplog): def test_strategy_fallback_default_strategy(): strategy = StrategyResolver() - strategy.logger = logging.getLogger(__name__) assert not hasattr(StrategyResolver, 'custom_strategy') strategy._load_strategy('../../super_duper') From a356edb117c07e3a4c4311cd45d20441bcefeef6 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sun, 25 Mar 2018 16:28:04 +0200 Subject: [PATCH 04/13] 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 From 157f7da8cebf65da9a36763902910d717d62e977 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sun, 25 Mar 2018 16:30:49 +0200 Subject: [PATCH 05/13] remove obsolete assertions --- freqtrade/tests/strategy/test_strategy.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 0f45d8d04..244910790 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -21,29 +21,19 @@ def test_search_strategy(): def test_load_strategy(result): resolver = StrategyResolver() - - assert not hasattr(StrategyResolver, 'custom_strategy') resolver._load_strategy('TestStrategy') - - assert not hasattr(StrategyResolver, 'custom_strategy') - assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.populate_indicators(result) def test_load_strategy_custom_directory(result): resolver = StrategyResolver() - - assert not hasattr(StrategyResolver, 'custom_strategy') - 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) From f78044da6dff834aaa14e4386ca06c841f2d91d0 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sun, 25 Mar 2018 20:24:56 +0200 Subject: [PATCH 06/13] fix method docs --- freqtrade/strategy/interface.py | 3 +-- freqtrade/strategy/resolver.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 4eb73fb2e..dcf665a02 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -33,7 +33,6 @@ class IStrategy(ABC): Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame :return: DataFrame with buy column - :return: """ @abstractmethod @@ -41,5 +40,5 @@ class IStrategy(ABC): """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame - :return: DataFrame with buy column + :return: DataFrame with sell column """ diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index c5a7cd577..de38cf67a 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -27,8 +27,7 @@ class StrategyResolver(object): def __init__(self, config: Optional[Dict] = None) -> None: """ Load the custom class from config parameter - :param config: - :return: + :param config: configuration dictionary or None """ config = config or {} From df57c3207684efc8211600a0178affcf2a50d842 Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 27 Mar 2018 18:15:49 +0200 Subject: [PATCH 07/13] only override strategy if other than DEFAULT --- freqtrade/configuration.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 8e395e21d..71cbe78e9 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -33,8 +33,9 @@ class Configuration(object): logger.info('Using config: %s ...', self.args.config) config = self._load_config_file(self.args.config) - # Add the strategy file to use - config.update({'strategy': self.args.strategy}) + # Override strategy if specified + if self.args.strategy != Constants.DEFAULT_STRATEGY: + config.update({'strategy': self.args.strategy}) if self.args.strategy_path: config.update({'strategy_path': self.args.strategy_path}) From e7399b50465b734a65f1466b28a84638747e0f46 Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 27 Mar 2018 18:16:21 +0200 Subject: [PATCH 08/13] add strategy and strategy_path to config_full.json.example --- config_full.json.example | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config_full.json.example b/config_full.json.example index c74b59660..1d9a48762 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -48,5 +48,7 @@ "initial_state": "running", "internals": { "process_throttle_secs": 5 - } + }, + "strategy": "DefaultStrategy", + "strategy_path": "/some/folder/" } From 6a125912489c492c761cc2ec2dc22e5fc9e1474e Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 27 Mar 2018 18:20:15 +0200 Subject: [PATCH 09/13] change strategy override condition --- freqtrade/configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 71cbe78e9..4922bf402 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -33,8 +33,8 @@ class Configuration(object): logger.info('Using config: %s ...', self.args.config) config = self._load_config_file(self.args.config) - # Override strategy if specified - if self.args.strategy != Constants.DEFAULT_STRATEGY: + # Set strategy if not specified in config and or if it's non default + if self.args.strategy != Constants.DEFAULT_STRATEGY or not config.get('strategy'): config.update({'strategy': self.args.strategy}) if self.args.strategy_path: From 872bbadded690de1f99e47d4663e7794154d2faa Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 27 Mar 2018 18:29:51 +0200 Subject: [PATCH 10/13] add test_load_custom_strategy() --- freqtrade/tests/test_configuration.py | 38 ++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 1085b0060..22a6cb005 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -98,8 +98,8 @@ def test_load_config(default_conf, mocker) -> None: configuration = Configuration(args) validated_conf = configuration.load_config() - assert 'strategy' in validated_conf - assert validated_conf['strategy'] == 'DefaultStrategy' + assert validated_conf.get('strategy') == 'DefaultStrategy' + assert validated_conf.get('strategy_path') is None assert 'dynamic_whitelist' not in validated_conf assert 'dry_run_db' not in validated_conf @@ -115,19 +115,39 @@ def test_load_config_with_params(default_conf, mocker) -> None: args = [ '--dynamic-whitelist', '10', '--strategy', 'TestStrategy', - '--dry-run-db' + '--strategy-path', '/some/path', + '--dry-run-db', ] args = Arguments(args, '').get_parsed_arg() configuration = Configuration(args) validated_conf = configuration.load_config() - assert 'dynamic_whitelist' in validated_conf - assert validated_conf['dynamic_whitelist'] == 10 - assert 'strategy' in validated_conf - assert validated_conf['strategy'] == 'TestStrategy' - assert 'dry_run_db' in validated_conf - assert validated_conf['dry_run_db'] is True + assert validated_conf.get('dynamic_whitelist') == 10 + assert validated_conf.get('strategy') == 'TestStrategy' + assert validated_conf.get('strategy_path') == '/some/path' + assert validated_conf.get('dry_run_db') is True + + +def test_load_custom_strategy(default_conf, mocker) -> None: + """ + Test Configuration.load_config() without any cli params + """ + custom_conf = deepcopy(default_conf) + custom_conf.update({ + 'strategy': 'CustomStrategy', + 'strategy_path': '/tmp/strategies', + }) + mocker.patch('freqtrade.configuration.open', mocker.mock_open( + read_data=json.dumps(custom_conf) + )) + + args = Arguments([], '').get_parsed_arg() + configuration = Configuration(args) + validated_conf = configuration.load_config() + + assert validated_conf.get('strategy') == 'CustomStrategy' + assert validated_conf.get('strategy_path') == '/tmp/strategies' def test_show_info(default_conf, mocker, caplog) -> None: From ba5cbcbb3fb37ecdb922f21689e2dcf718cbef0e Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 27 Mar 2018 18:38:43 +0200 Subject: [PATCH 11/13] configuration.md: add strategy and strategy_path --- docs/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 5e3b15925..311205dad 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -35,6 +35,8 @@ The table below will list all configuration parameters. | `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`. | `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. | `initial_state` | running | No | Defines the initial application state. More information below. +| `strategy` | DefaultStrategy | No | Defines Strategy class to use. +| `strategy_path` | null | No | Adds an additional strategy lookup path (must be a folder). | `internals.process_throttle_secs` | 5 | Yes | Set the process throttle. Value in second. The definition of each config parameters is in From 06276e1d24d6c7394bbff7395499da405431bc13 Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 27 Mar 2018 18:39:49 +0200 Subject: [PATCH 12/13] bot-optimization.md: add strategy-path --- docs/bot-optimization.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 00938adbe..b9ff3fe40 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -42,6 +42,13 @@ You can test it with the parameter: `--strategy TestStrategy` python3 ./freqtrade/main.py --strategy AwesomeStrategy ``` +### Specify custom strategy location +If you want to use a strategy from a different folder you can pass `--strategy-path` + +```bash +python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder +``` + **For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py) file as reference.** From 004e0bb9a3c6c05f6cce50c8c96de9784ff69075 Mon Sep 17 00:00:00 2001 From: gcarq Date: Tue, 27 Mar 2018 18:46:42 +0200 Subject: [PATCH 13/13] bot-usage.md: add strategy-path --- docs/bot-usage.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index ea9b1e4d8..0ed073933 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -28,6 +28,7 @@ optional arguments: specify configuration file (default: config.json) -s NAME, --strategy NAME specify strategy class name (default: DefaultStrategy) + --strategy-path PATH specify additional strategy lookup path --dry-run-db Force dry run to use a local DB "tradesv3.dry_run.sqlite" instead of memory DB. Work only if dry_run is enabled. @@ -67,9 +68,16 @@ message the reason (File not found, or errors in your code). Learn more about strategy file in [optimize your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md). +### How to use --strategy-path? +This parameter allows you to add an additional strategy lookup path, which gets +checked before the default locations (The passed path must be a folder!): +```bash +python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder +``` + #### How to install a strategy? This is very simple. Copy paste your strategy file into the folder -`user_data/strategies`. And voila, the bot is ready to use it. +`user_data/strategies` or use `--strategy-path`. And voila, the bot is ready to use it. ### How to use --dynamic-whitelist? Per default `--dynamic-whitelist` will retrieve the 20 currencies based