From 9cbf8c5f008520b99016a7a63fe299b4b0dcb821 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 21:15:36 +0300 Subject: [PATCH 01/15] Add status for listed strategies --- freqtrade/commands/list_commands.py | 11 +++++++++-- freqtrade/resolvers/iresolver.py | 20 +++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index f2b6bf995..b6ff682e6 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -43,10 +43,17 @@ def start_list_strategies(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES)) - strategies = StrategyResolver.search_all_objects(directory) + strategies = StrategyResolver.search_all_objects(directory, not args['print_one_column']) # Sort alphabetically strategies = sorted(strategies, key=lambda x: x['name']) - strats_to_print = [{'name': s['name'], 'location': s['location'].name} for s in strategies] + names = [s['name'] for s in strategies] + strats_to_print = [{ + 'name': s['name'] if s['name'] else "--", + 'location': s['location'].name, + 'status': ("LOAD FAILED" if s['class'] is None + else "OK" if names.count(s['name']) == 1 + else "DUPLICATED NAME") + } for s in strategies] if args['print_one_column']: print('\n'.join([s['name'] for s in strategies])) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index a75c45933..8b5aa1dff 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -41,11 +41,15 @@ class IResolver: @classmethod def _get_valid_object(cls, module_path: Path, - object_name: Optional[str]) -> Generator[Any, None, None]: + object_name: Optional[str], + enum_failed: bool = False) -> Union[Generator[Any, None, None], + Tuple[None]]: """ Generator returning objects with matching object_type and object_name in the path given. :param module_path: absolute path to the module :param object_name: Class name of the object + :param enum_failed: If True, will return None for modules which fail. + Otherwise, failing modules are skipped. :return: generator containing matching objects """ @@ -58,6 +62,8 @@ class IResolver: except (ModuleNotFoundError, SyntaxError) as err: # Catch errors in case a specific module is not installed logger.warning(f"Could not import {module_path} due to '{err}'") + if enum_failed: + return (None, ) valid_objects_gen = ( obj for name, obj in inspect.getmembers(module, inspect.isclass) @@ -136,10 +142,13 @@ class IResolver: ) @classmethod - def search_all_objects(cls, directory: Path) -> List[Dict[str, Any]]: + def search_all_objects(cls, directory: Path, + enum_failed: bool) -> List[Dict[str, Any]]: """ Searches a directory for valid objects :param directory: Path to search + :param enum_failed: If True, will return None for modules which fail. + Otherwise, failing modules are skipped. :return: List of dicts containing 'name', 'class' and 'location' entires """ logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'") @@ -151,10 +160,11 @@ class IResolver: continue module_path = entry.resolve() logger.debug(f"Path {module_path}") - for obj in cls._get_valid_object(module_path, object_name=None): + for obj in cls._get_valid_object(module_path, object_name=None, + enum_failed=enum_failed): objects.append( - {'name': obj.__name__, - 'class': obj, + {'name': obj.__name__ if obj is not None else '', + 'class': obj if obj is not None else None, 'location': entry, }) return objects From a2d7f8a70dc8e907c83ab4e958783708174ab07f Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 21:24:30 +0300 Subject: [PATCH 02/15] Split tabular printing into sep. helper function --- freqtrade/commands/list_commands.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index b6ff682e6..782cd074e 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -3,7 +3,7 @@ import logging import sys from collections import OrderedDict from pathlib import Path -from typing import Any, Dict +from typing import Any, Dict, List import rapidjson from tabulate import tabulate @@ -36,6 +36,19 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}") +def _print_objs_tabular(objs: List) -> None: + names = [s['name'] for s in objs] + strats_to_print = [{ + 'name': s['name'] if s['name'] else "--", + 'location': s['location'].name, + 'status': ("LOAD FAILED" if s['class'] is None + else "OK" if names.count(s['name']) == 1 + else "DUPLICATED NAME") + } for s in objs] + + print(tabulate(strats_to_print, headers='keys', tablefmt='pipe')) + + def start_list_strategies(args: Dict[str, Any]) -> None: """ Print files with Strategy custom classes available in the directory @@ -46,19 +59,11 @@ def start_list_strategies(args: Dict[str, Any]) -> None: strategies = StrategyResolver.search_all_objects(directory, not args['print_one_column']) # Sort alphabetically strategies = sorted(strategies, key=lambda x: x['name']) - names = [s['name'] for s in strategies] - strats_to_print = [{ - 'name': s['name'] if s['name'] else "--", - 'location': s['location'].name, - 'status': ("LOAD FAILED" if s['class'] is None - else "OK" if names.count(s['name']) == 1 - else "DUPLICATED NAME") - } for s in strategies] if args['print_one_column']: print('\n'.join([s['name'] for s in strategies])) else: - print(tabulate(strats_to_print, headers='keys', tablefmt='pipe')) + _print_objs_tabular(strategies) def start_list_hyperopts(args: Dict[str, Any]) -> None: From 1bc26fd07a11bf064098e544e4a42063d3294b90 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 21:46:22 +0300 Subject: [PATCH 03/15] Add printing statuses for list-hyperopts --- freqtrade/commands/list_commands.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 782cd074e..a2ac388b0 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -38,7 +38,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: def _print_objs_tabular(objs: List) -> None: names = [s['name'] for s in objs] - strats_to_print = [{ + objss_to_print = [{ 'name': s['name'] if s['name'] else "--", 'location': s['location'].name, 'status': ("LOAD FAILED" if s['class'] is None @@ -46,7 +46,7 @@ def _print_objs_tabular(objs: List) -> None: else "DUPLICATED NAME") } for s in objs] - print(tabulate(strats_to_print, headers='keys', tablefmt='pipe')) + print(tabulate(objss_to_print, headers='keys', tablefmt='pipe')) def start_list_strategies(args: Dict[str, Any]) -> None: @@ -56,14 +56,14 @@ def start_list_strategies(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES)) - strategies = StrategyResolver.search_all_objects(directory, not args['print_one_column']) + strategy_objs = StrategyResolver.search_all_objects(directory, not args['print_one_column']) # Sort alphabetically - strategies = sorted(strategies, key=lambda x: x['name']) + strategy_objs = sorted(strategy_objs, key=lambda x: x['name']) if args['print_one_column']: - print('\n'.join([s['name'] for s in strategies])) + print('\n'.join([s['name'] for s in strategy_objs])) else: - _print_objs_tabular(strategies) + _print_objs_tabular(strategy_objs) def start_list_hyperopts(args: Dict[str, Any]) -> None: @@ -75,15 +75,14 @@ def start_list_hyperopts(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) directory = Path(config.get('hyperopt_path', config['user_data_dir'] / USERPATH_HYPEROPTS)) - hyperopts = HyperOptResolver.search_all_objects(directory) + hyperopt_objs = HyperOptResolver.search_all_objects(directory, not args['print_one_column']) # Sort alphabetically - hyperopts = sorted(hyperopts, key=lambda x: x['name']) - hyperopts_to_print = [{'name': s['name'], 'location': s['location'].name} for s in hyperopts] + hyperopt_objs = sorted(hyperopt_objs, key=lambda x: x['name']) if args['print_one_column']: - print('\n'.join([s['name'] for s in hyperopts])) + print('\n'.join([s['name'] for s in hyperopt_objs])) else: - print(tabulate(hyperopts_to_print, headers='keys', tablefmt='pipe')) + _print_objs_tabular(hyperopt_objs) def start_list_timeframes(args: Dict[str, Any]) -> None: From c92e1d97d65b796a061d19081f701cac6aa59ce3 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 21:52:02 +0300 Subject: [PATCH 04/15] Attempt to make mypy happy --- freqtrade/resolvers/iresolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 8b5aa1dff..d674daa9a 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -89,7 +89,7 @@ class IResolver: continue module_path = entry.resolve() - obj = next(cls._get_valid_object(module_path, object_name), None) + obj = next(cls._get_valid_object(module_path, object_name), None) # noqa if obj: return (obj, module_path) From e598c769d448db0a03c7c7df5d470d3a389d8cee Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 22:28:49 +0300 Subject: [PATCH 05/15] Add colorization --- freqtrade/commands/arguments.py | 4 ++-- freqtrade/commands/list_commands.py | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index fe6f49039..063a152fe 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -30,9 +30,9 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] -ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column"] +ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"] -ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column"] +ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"] ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index a2ac388b0..6a2ccbfcf 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -5,6 +5,8 @@ from collections import OrderedDict from pathlib import Path from typing import Any, Dict, List +from colorama import init as colorama_init +from colorama import Fore, Style import rapidjson from tabulate import tabulate @@ -36,14 +38,23 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}") -def _print_objs_tabular(objs: List) -> None: +def _print_objs_tabular(objs: List, print_colorized: bool) -> None: + if print_colorized: + colorama_init(autoreset=True) + names = [s['name'] for s in objs] objss_to_print = [{ 'name': s['name'] if s['name'] else "--", 'location': s['location'].name, - 'status': ("LOAD FAILED" if s['class'] is None - else "OK" if names.count(s['name']) == 1 - else "DUPLICATED NAME") + 'status': (((Fore.RED if print_colorized else '') + + "LOAD FAILED" + (Style.RESET_ALL if print_colorized else '')) + if s['class'] is None + else ((Fore.GREEN if print_colorized else '') + + "OK" + (Style.RESET_ALL if print_colorized else '')) + if names.count(s['name']) == 1 + else ((Fore.YELLOW if print_colorized else '') + + "DUPLICATED NAME" + + (Style.RESET_ALL if print_colorized else ''))) } for s in objs] print(tabulate(objss_to_print, headers='keys', tablefmt='pipe')) @@ -63,7 +74,7 @@ def start_list_strategies(args: Dict[str, Any]) -> None: if args['print_one_column']: print('\n'.join([s['name'] for s in strategy_objs])) else: - _print_objs_tabular(strategy_objs) + _print_objs_tabular(strategy_objs, config.get('print_colorized', False)) def start_list_hyperopts(args: Dict[str, Any]) -> None: @@ -82,7 +93,7 @@ def start_list_hyperopts(args: Dict[str, Any]) -> None: if args['print_one_column']: print('\n'.join([s['name'] for s in hyperopt_objs])) else: - _print_objs_tabular(hyperopt_objs) + _print_objs_tabular(hyperopt_objs, config.get('print_colorized', False)) def start_list_timeframes(args: Dict[str, Any]) -> None: From 47a91c9d8ec0706add136b3b1713c586fadf4b5e Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 22:32:46 +0300 Subject: [PATCH 06/15] Remove green color --- freqtrade/commands/list_commands.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 6a2ccbfcf..67ee59375 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -49,9 +49,7 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None: 'status': (((Fore.RED if print_colorized else '') + "LOAD FAILED" + (Style.RESET_ALL if print_colorized else '')) if s['class'] is None - else ((Fore.GREEN if print_colorized else '') + - "OK" + (Style.RESET_ALL if print_colorized else '')) - if names.count(s['name']) == 1 + else "OK" if names.count(s['name']) == 1 else ((Fore.YELLOW if print_colorized else '') + "DUPLICATED NAME" + (Style.RESET_ALL if print_colorized else ''))) From 06b84b4086d6807bb6ac861973028e799dcaea91 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 14 Feb 2020 23:13:49 +0300 Subject: [PATCH 07/15] Remove redundant code --- freqtrade/resolvers/iresolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index d674daa9a..84dee85cd 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -164,7 +164,7 @@ class IResolver: enum_failed=enum_failed): objects.append( {'name': obj.__name__ if obj is not None else '', - 'class': obj if obj is not None else None, + 'class': obj, 'location': entry, }) return objects From 93f9ff1b636724ad729a88b0dc4cb35fa6612e35 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 04:22:21 +0300 Subject: [PATCH 08/15] Fix existing test --- tests/strategy/test_strategy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index d3977ae44..1b4b64f89 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -32,7 +32,7 @@ def test_search_strategy(): def test_search_all_strategies(): directory = Path(__file__).parent - strategies = StrategyResolver.search_all_objects(directory) + strategies = StrategyResolver.search_all_objects(directory, enum_failed=False) assert isinstance(strategies, list) assert len(strategies) == 3 assert isinstance(strategies[0], dict) From 29d9b6a46a94ed222a97bb1e14081c1948f2cca1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 04:32:10 +0300 Subject: [PATCH 09/15] Add test for enum failed --- tests/strategy/test_strategy.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 1b4b64f89..3d3f0f424 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -30,7 +30,7 @@ def test_search_strategy(): assert s is None -def test_search_all_strategies(): +def test_search_all_strategies_no_failed(): directory = Path(__file__).parent strategies = StrategyResolver.search_all_objects(directory, enum_failed=False) assert isinstance(strategies, list) @@ -38,6 +38,15 @@ def test_search_all_strategies(): assert isinstance(strategies[0], dict) +def test_search_all_strategies_with_failed(): + directory = Path(__file__).parent + strategies = StrategyResolver.search_all_objects(directory, enum_failed=True) + assert isinstance(strategies, list) + assert len(strategies) == 4 + assert isinstance(strategies[0], dict) + assert strategies[0]['class'] is None + + def test_load_strategy(default_conf, result): default_conf.update({'strategy': 'SampleStrategy', 'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates') From 1cf19133f47237fa42e693e96bbdc42f072740ca Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 05:17:34 +0300 Subject: [PATCH 10/15] Added missing failing strategy --- tests/strategy/failing_strategy.py | 87 ++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/strategy/failing_strategy.py diff --git a/tests/strategy/failing_strategy.py b/tests/strategy/failing_strategy.py new file mode 100644 index 000000000..57a8cc1ae --- /dev/null +++ b/tests/strategy/failing_strategy.py @@ -0,0 +1,87 @@ + +# --- Do not remove these libs --- +from freqtrade.strategy.interface import IStrategy +from pandas import DataFrame +# -------------------------------- + +# Add your lib to import here +import talib.abstract as ta + +import nonexiting_module # noqa + + +# This class is a sample. Feel free to customize it. +class TestStrategyLegacy(IStrategy): + """ + This is a test strategy using the legacy function headers, which will be + removed in a future update. + Please do not use this as a template, but refer to user_data/strategy/sample_strategy.py + for a uptodate version of this template. + """ + + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + } + + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.10 + + # Optimal ticker interval for the strategy + ticker_interval = '5m' + + def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + """ + Adds several different TA indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let uncomment only the indicator you are using in your strategies + or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + """ + + # Momentum Indicator + # ------------------------------------ + + # ADX + dataframe['adx'] = ta.ADX(dataframe) + + # TEMA - Triple Exponential Moving Average + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + return 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 + """ + dataframe.loc[ + ( + (dataframe['adx'] > 30) & + (dataframe['tema'] > dataframe['tema'].shift(1)) & + (dataframe['volume'] > 0) + ), + 'buy'] = 1 + + return 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 + """ + dataframe.loc[ + ( + (dataframe['adx'] > 70) & + (dataframe['tema'] < dataframe['tema'].shift(1)) & + (dataframe['volume'] > 0) + ), + 'sell'] = 1 + return dataframe From e8c0a0bcd3b242e49f02a620d489cf352b548aa3 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 06:18:00 +0300 Subject: [PATCH 11/15] Make mypy happy --- freqtrade/resolvers/iresolver.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 84dee85cd..34f3934b6 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -7,7 +7,7 @@ import importlib.util import inspect import logging from pathlib import Path -from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union +from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union from freqtrade.exceptions import OperationalException @@ -40,10 +40,8 @@ class IResolver: return abs_paths @classmethod - def _get_valid_object(cls, module_path: Path, - object_name: Optional[str], - enum_failed: bool = False) -> Union[Generator[Any, None, None], - Tuple[None]]: + def _get_valid_object(cls, module_path: Path, object_name: Optional[str], + enum_failed: bool = False) -> Iterator[Any]: """ Generator returning objects with matching object_type and object_name in the path given. :param module_path: absolute path to the module @@ -63,7 +61,7 @@ class IResolver: # Catch errors in case a specific module is not installed logger.warning(f"Could not import {module_path} due to '{err}'") if enum_failed: - return (None, ) + return iter([None]) valid_objects_gen = ( obj for name, obj in inspect.getmembers(module, inspect.isclass) From ddea4b9300f12fcd099f0e4f5724bf290ec17881 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 06:54:18 +0300 Subject: [PATCH 12/15] Fix test --- tests/strategy/test_strategy.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 3d3f0f424..379260599 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -43,8 +43,10 @@ def test_search_all_strategies_with_failed(): strategies = StrategyResolver.search_all_objects(directory, enum_failed=True) assert isinstance(strategies, list) assert len(strategies) == 4 - assert isinstance(strategies[0], dict) - assert strategies[0]['class'] is None + # with enum_failed=True search_all_objects() shall find 3 good strategies + # and 1 which fails to load + assert len([x for x in strategies if x['class'] is not None]) == 3 + assert len([x for x in strategies if x['class'] is None]) == 1 def test_load_strategy(default_conf, result): From 42a5d78e607f226e4e65cf5157f65bdf28ec54f5 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 07:19:24 +0300 Subject: [PATCH 13/15] Wording (duplicate, not duplicated) --- freqtrade/commands/list_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 67ee59375..0cfc78596 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -51,7 +51,7 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None: if s['class'] is None else "OK" if names.count(s['name']) == 1 else ((Fore.YELLOW if print_colorized else '') + - "DUPLICATED NAME" + + "DUPLICATE NAME" + (Style.RESET_ALL if print_colorized else ''))) } for s in objs] From fdd362299f5c7018f500b626f884f7dd27c17e62 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 07:34:39 +0300 Subject: [PATCH 14/15] Docs adjusted --- docs/utils.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index abb7fd0db..cdf0c31af 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -144,38 +144,47 @@ freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt Use the `list-strategies` subcommand to see all strategies in one particular directory and the `list-hyperopts` subcommand to list custom Hyperopts. +These subcommands are useful for finding problems in your environment with loading strategies or hyperopt classes: modules with strategies or hyperopt classes that contain errors and failed to load are printed in red (LOAD FAILED), while strategies or hyperopt classes with duplicate names are printed in yellow (DUPLICATE NAME). + ``` -freqtrade list-strategies --help -usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--strategy-path PATH] [-1] +usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] + [--strategy-path PATH] [-1] [--no-color] optional arguments: -h, --help show this help message and exit --strategy-path PATH Specify additional strategy lookup path. -1, --one-column Print output in one column. + --no-color Disable colorization of hyperopt results. May be + useful if you are redirecting output to a file. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). - --logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more details. + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` - to read config from stdin. + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH Path to userdata directory. ``` ``` -freqtrade list-hyperopts --help usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] - [--hyperopt-path PATH] [-1] + [--hyperopt-path PATH] [-1] [--no-color] optional arguments: -h, --help show this help message and exit --hyperopt-path PATH Specify additional lookup path for Hyperopt and Hyperopt Loss functions. -1, --one-column Print output in one column. + --no-color Disable colorization of hyperopt results. May be + useful if you are redirecting output to a file. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). From 6139239b863b36522a7d96efdb859e830403bad9 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 15 Feb 2020 20:43:11 +0300 Subject: [PATCH 15/15] Address points stated in comments --- freqtrade/commands/list_commands.py | 15 +++-- freqtrade/resolvers/iresolver.py | 2 +- tests/strategy/failing_strategy.py | 86 ++--------------------------- 3 files changed, 14 insertions(+), 89 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 0cfc78596..49674b81a 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -41,18 +41,21 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: def _print_objs_tabular(objs: List, print_colorized: bool) -> None: if print_colorized: colorama_init(autoreset=True) + red = Fore.RED + yellow = Fore.YELLOW + reset = Style.RESET_ALL + else: + red = '' + yellow = '' + reset = '' names = [s['name'] for s in objs] objss_to_print = [{ 'name': s['name'] if s['name'] else "--", 'location': s['location'].name, - 'status': (((Fore.RED if print_colorized else '') + - "LOAD FAILED" + (Style.RESET_ALL if print_colorized else '')) - if s['class'] is None + 'status': (red + "LOAD FAILED" + reset if s['class'] is None else "OK" if names.count(s['name']) == 1 - else ((Fore.YELLOW if print_colorized else '') + - "DUPLICATE NAME" + - (Style.RESET_ALL if print_colorized else ''))) + else yellow + "DUPLICATE NAME" + reset) } for s in objs] print(tabulate(objss_to_print, headers='keys', tablefmt='pipe')) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 34f3934b6..922a2700a 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -87,7 +87,7 @@ class IResolver: continue module_path = entry.resolve() - obj = next(cls._get_valid_object(module_path, object_name), None) # noqa + obj = next(cls._get_valid_object(module_path, object_name), None) if obj: return (obj, module_path) diff --git a/tests/strategy/failing_strategy.py b/tests/strategy/failing_strategy.py index 57a8cc1ae..f8eaac3c3 100644 --- a/tests/strategy/failing_strategy.py +++ b/tests/strategy/failing_strategy.py @@ -1,87 +1,9 @@ - -# --- Do not remove these libs --- -from freqtrade.strategy.interface import IStrategy -from pandas import DataFrame -# -------------------------------- - -# Add your lib to import here -import talib.abstract as ta +# The strategy which fails to load due to non-existent dependency import nonexiting_module # noqa +from freqtrade.strategy.interface import IStrategy + -# This class is a sample. Feel free to customize it. class TestStrategyLegacy(IStrategy): - """ - This is a test strategy using the legacy function headers, which will be - removed in a future update. - Please do not use this as a template, but refer to user_data/strategy/sample_strategy.py - for a uptodate version of this template. - """ - - # Minimal ROI designed for the strategy. - # This attribute will be overridden if the config file contains "minimal_roi" - minimal_roi = { - "40": 0.0, - "30": 0.01, - "20": 0.02, - "0": 0.04 - } - - # Optimal stoploss designed for the strategy - # This attribute will be overridden if the config file contains "stoploss" - stoploss = -0.10 - - # Optimal ticker interval for the strategy - ticker_interval = '5m' - - def populate_indicators(self, dataframe: DataFrame) -> DataFrame: - """ - Adds several different TA indicators to the given DataFrame - - Performance Note: For the best performance be frugal on the number of indicators - you are using. Let uncomment only the indicator you are using in your strategies - or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - """ - - # Momentum Indicator - # ------------------------------------ - - # ADX - dataframe['adx'] = ta.ADX(dataframe) - - # TEMA - Triple Exponential Moving Average - dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) - - return 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 - """ - dataframe.loc[ - ( - (dataframe['adx'] > 30) & - (dataframe['tema'] > dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'buy'] = 1 - - return 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 - """ - dataframe.loc[ - ( - (dataframe['adx'] > 70) & - (dataframe['tema'] < dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'sell'] = 1 - return dataframe + pass