Cleanup some code and add option

This commit is contained in:
Matthias 2019-12-24 15:28:35 +01:00
parent 5a11ca86bb
commit 2ab989e274
3 changed files with 42 additions and 9 deletions

View File

@ -30,7 +30,7 @@ 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"] ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column"]
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]

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, Generator, List, Optional, Tuple, Type, Union from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union
from freqtrade import OperationalException from freqtrade import OperationalException
@ -41,7 +41,7 @@ class IResolver:
@classmethod @classmethod
def _get_valid_object(cls, module_path: Path, def _get_valid_object(cls, module_path: Path,
object_name: str) -> Generator[Any, None, None]: object_name: Optional[str]) -> Generator[Any, None, None]:
""" """
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
@ -51,7 +51,7 @@ class IResolver:
# Generate spec based on absolute path # Generate spec based on absolute path
# Pass object_name as first argument to have logging print a reasonable name. # Pass object_name as first argument to have logging print a reasonable name.
spec = importlib.util.spec_from_file_location(object_name, str(module_path)) spec = importlib.util.spec_from_file_location(object_name or "", str(module_path))
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
try: try:
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
@ -61,7 +61,7 @@ class IResolver:
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)
if object_name == name and cls.object_type in obj.__bases__ if (object_name is None or object_name == name) and cls.object_type in obj.__bases__
) )
return valid_objects_gen return valid_objects_gen
@ -74,8 +74,7 @@ class IResolver:
:param object_name: ClassName of the object to load :param object_name: ClassName of the object to load
:return: object class :return: object class
""" """
logger.debug("Searching for %s %s in '%s'", logger.debug(f"Searching for {cls.object_type.__name__} {object_name} in '{directory}'")
cls.object_type.__name__, object_name, directory)
for entry in directory.iterdir(): for entry in directory.iterdir():
# Only consider python files # Only consider python files
if not str(entry).endswith('.py'): if not str(entry).endswith('.py'):
@ -134,3 +133,27 @@ class IResolver:
f"Impossible to load {cls.object_type_str} '{object_name}'. This class does not exist " f"Impossible to load {cls.object_type_str} '{object_name}'. This class does not exist "
"or contains Python code errors." "or contains Python code errors."
) )
@classmethod
def search_all_objects(cls, directory: Path) -> List[Dict[str, Any]]:
"""
Searches a directory for valid objects
:param directory: Path to search
:return: List of dicts containing 'name', 'class' and 'location' entires
"""
logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'")
objects = []
for entry in directory.iterdir():
# Only consider python files
if not str(entry).endswith('.py'):
logger.debug('Ignoring %s', entry)
continue
module_path = entry.resolve()
logger.info(f"Path {module_path}")
for obj in cls._get_valid_object(module_path, object_name=None):
objects.append(
{'name': obj.__name__,
'class': obj,
'location': entry,
})
return objects

View File

@ -23,7 +23,7 @@ from freqtrade.data.history import (convert_trades_to_ohlcv,
from freqtrade.exchange import (available_exchanges, ccxt_exchanges, from freqtrade.exchange import (available_exchanges, ccxt_exchanges,
market_is_active, symbol_is_pair) market_is_active, symbol_is_pair)
from freqtrade.misc import plural, render_template from freqtrade.misc import plural, render_template
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.state import RunMode from freqtrade.state import RunMode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -229,7 +229,17 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
Print Strategies available in a folder Print Strategies available in a folder
""" """
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
print(config)
directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGY))
strategies = StrategyResolver.search_all_objects(directory)
# Sort alphabetically
strategies = sorted(strategies, key=lambda x: x['name'])
strats_to_print = [{'name': s['name'], 'location': s['location']} 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'))
def start_list_timeframes(args: Dict[str, Any]) -> None: def start_list_timeframes(args: Dict[str, Any]) -> None: