Merge pull request #2923 from hroff-1902/status-strategies

Add printing statuses for enlisted strategies and hyperopts
This commit is contained in:
Matthias 2020-02-15 19:43:02 +01:00 committed by GitHub
commit 44ac2409ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 28 deletions

View File

@ -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. 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]
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--strategy-path PATH] [-1] [-d PATH] [--userdir PATH]
[--strategy-path PATH] [-1] [--no-color]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
--strategy-path PATH Specify additional strategy lookup path. --strategy-path PATH Specify additional strategy lookup path.
-1, --one-column Print output in one column. -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: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -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 -V, --version show program's version number and exit
-c PATH, --config PATH -c PATH, --config PATH
Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-` Specify configuration file (default: `config.json`).
to read config from stdin. Multiple --config options may be used. Can be set to
`-` to read config from stdin.
-d PATH, --datadir PATH -d PATH, --datadir PATH
Path to directory with historical backtesting data. Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH --userdir PATH, --user-data-dir PATH
Path to userdata directory. Path to userdata directory.
``` ```
``` ```
freqtrade list-hyperopts --help
usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH] usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-d PATH] [--userdir PATH]
[--hyperopt-path PATH] [-1] [--hyperopt-path PATH] [-1] [--no-color]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
--hyperopt-path PATH Specify additional lookup path for Hyperopt and --hyperopt-path PATH Specify additional lookup path for Hyperopt and
Hyperopt Loss functions. Hyperopt Loss functions.
-1, --one-column Print output in one column. -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: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).

View File

@ -30,9 +30,9 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] 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"] ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]

View File

@ -3,8 +3,10 @@ import logging
import sys import sys
from collections import OrderedDict from collections import OrderedDict
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict, List
from colorama import init as colorama_init
from colorama import Fore, Style
import rapidjson import rapidjson
from tabulate import tabulate from tabulate import tabulate
@ -36,6 +38,29 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}") print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}")
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': (red + "LOAD FAILED" + reset if s['class'] is None
else "OK" if names.count(s['name']) == 1
else yellow + "DUPLICATE NAME" + reset)
} for s in objs]
print(tabulate(objss_to_print, headers='keys', tablefmt='pipe'))
def start_list_strategies(args: Dict[str, Any]) -> None: def start_list_strategies(args: Dict[str, Any]) -> None:
""" """
Print files with Strategy custom classes available in the directory Print files with Strategy custom classes available in the directory
@ -43,15 +68,14 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES)) directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES))
strategies = StrategyResolver.search_all_objects(directory) strategy_objs = StrategyResolver.search_all_objects(directory, not args['print_one_column'])
# Sort alphabetically # Sort alphabetically
strategies = sorted(strategies, key=lambda x: x['name']) strategy_objs = sorted(strategy_objs, key=lambda x: x['name'])
strats_to_print = [{'name': s['name'], 'location': s['location'].name} for s in strategies]
if args['print_one_column']: 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: else:
print(tabulate(strats_to_print, headers='keys', tablefmt='pipe')) _print_objs_tabular(strategy_objs, config.get('print_colorized', False))
def start_list_hyperopts(args: Dict[str, Any]) -> None: def start_list_hyperopts(args: Dict[str, Any]) -> None:
@ -63,15 +87,14 @@ def start_list_hyperopts(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
directory = Path(config.get('hyperopt_path', config['user_data_dir'] / USERPATH_HYPEROPTS)) 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 # Sort alphabetically
hyperopts = sorted(hyperopts, key=lambda x: x['name']) hyperopt_objs = sorted(hyperopt_objs, key=lambda x: x['name'])
hyperopts_to_print = [{'name': s['name'], 'location': s['location'].name} for s in hyperopts]
if args['print_one_column']: 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: else:
print(tabulate(hyperopts_to_print, headers='keys', tablefmt='pipe')) _print_objs_tabular(hyperopt_objs, config.get('print_colorized', False))
def start_list_timeframes(args: Dict[str, Any]) -> None: def start_list_timeframes(args: Dict[str, Any]) -> None:

View File

@ -7,7 +7,7 @@ import importlib.util
import inspect import inspect
import logging import logging
from pathlib import Path 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 from freqtrade.exceptions import OperationalException
@ -40,12 +40,14 @@ class IResolver:
return abs_paths return abs_paths
@classmethod @classmethod
def _get_valid_object(cls, module_path: Path, def _get_valid_object(cls, module_path: Path, object_name: Optional[str],
object_name: Optional[str]) -> Generator[Any, None, None]: enum_failed: bool = False) -> Iterator[Any]:
""" """
Generator returning objects with matching object_type and object_name in the path given. Generator returning objects with matching object_type and object_name in the path given.
:param module_path: absolute path to the module :param module_path: absolute path to the module
:param object_name: Class name of the object :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 :return: generator containing matching objects
""" """
@ -58,6 +60,8 @@ class IResolver:
except (ModuleNotFoundError, SyntaxError) as err: except (ModuleNotFoundError, SyntaxError) as err:
# Catch errors in case a specific module is not installed # Catch errors in case a specific module is not installed
logger.warning(f"Could not import {module_path} due to '{err}'") logger.warning(f"Could not import {module_path} due to '{err}'")
if enum_failed:
return iter([None])
valid_objects_gen = ( valid_objects_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass) obj for name, obj in inspect.getmembers(module, inspect.isclass)
@ -136,10 +140,13 @@ class IResolver:
) )
@classmethod @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 Searches a directory for valid objects
:param directory: Path to search :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 :return: List of dicts containing 'name', 'class' and 'location' entires
""" """
logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'") logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'")
@ -151,9 +158,10 @@ class IResolver:
continue continue
module_path = entry.resolve() module_path = entry.resolve()
logger.debug(f"Path {module_path}") 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( objects.append(
{'name': obj.__name__, {'name': obj.__name__ if obj is not None else '',
'class': obj, 'class': obj,
'location': entry, 'location': entry,
}) })

View File

@ -0,0 +1,9 @@
# The strategy which fails to load due to non-existent dependency
import nonexiting_module # noqa
from freqtrade.strategy.interface import IStrategy
class TestStrategyLegacy(IStrategy):
pass

View File

@ -30,14 +30,25 @@ def test_search_strategy():
assert s is None assert s is None
def test_search_all_strategies(): def test_search_all_strategies_no_failed():
directory = Path(__file__).parent directory = Path(__file__).parent
strategies = StrategyResolver.search_all_objects(directory) strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
assert isinstance(strategies, list) assert isinstance(strategies, list)
assert len(strategies) == 3 assert len(strategies) == 3
assert isinstance(strategies[0], dict) 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
# 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): def test_load_strategy(default_conf, result):
default_conf.update({'strategy': 'SampleStrategy', default_conf.update({'strategy': 'SampleStrategy',
'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates') 'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates')