From 250ae2d0061cd60bd121a4658a5013b3930feeb0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 08:28:44 +0200 Subject: [PATCH] Enhance list-exchanges with more information --- freqtrade/commands/list_commands.py | 34 +++++++++++++++++------- freqtrade/exchange/exchange_utils.py | 31 ++++++++++++++++++--- freqtrade/resolvers/exchange_resolver.py | 26 +++++++++++++++++- freqtrade/resolvers/iresolver.py | 2 +- 4 files changed, 79 insertions(+), 14 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index d81800896..dadab7b9b 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -30,18 +30,34 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: if args['print_one_column']: print('\n'.join([e['name'] for e in exchanges])) else: - if args['list_exchanges_all']: - print("All exchanges supported by the ccxt library:") - else: - print("Exchanges available for Freqtrade:") - exchanges = [e for e in exchanges if e['valid'] is not False] - headers = { 'name': 'Exchange name', 'valid': 'Valid', - 'comment': 'reason', - } - print(tabulate(exchanges, headers=headers)) + 'supported': 'Supported', + 'trade_modes': 'Markets', + 'comment': 'Reason', + } + + def build_entry(exchange, valid): + valid_entry = {'valid': exchange['valid']} if valid else {} + result = { + 'name': exchange['name'], + **valid_entry, + 'supported': 'Official' if exchange['supported'] else '', + 'trade_modes': ', '.join(exchange['trade_modes']), + 'comment': exchange['comment'], + } + + return result + + if args['list_exchanges_all']: + print("All exchanges supported by the ccxt library:") + exchanges = [build_entry(e, True) for e in exchanges] + else: + print("Exchanges available for Freqtrade:") + exchanges = [build_entry(e, False) for e in exchanges if e['valid'] is not False] + + print(tabulate(exchanges, headers=headers, )) def _print_objs_tabular(objs: List, print_colorized: bool) -> None: diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 32a68a959..05f701136 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -9,7 +9,8 @@ import ccxt from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGITS, TICK_SIZE, TRUNCATE, decimal_to_precision) -from freqtrade.exchange.common import BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED +from freqtrade.exchange.common import (BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED, + SUPPORTED_EXCHANGES) from freqtrade.exchange.types import ValidExchangesType from freqtrade.util import FtPrecise from freqtrade.util.datetime_helpers import dt_from_ts, dt_ts @@ -56,15 +57,39 @@ def validate_exchange(exchange: str) -> Tuple[bool, str]: return True, '' +def build_exchange_list_entry( + exchange_name: str, exchangeClasses: Dict[str, Any]) -> ValidExchangesType: + valid, comment = validate_exchange(exchange_name) + result = { + 'name': exchange_name, + 'valid': valid, + 'supported': exchange_name.lower() in SUPPORTED_EXCHANGES, + 'comment': comment, + 'trade_modes': ['spot'], + } + if resolved := exchangeClasses.get(exchange_name.lower()): + supported_modes = ['spot'] + [ + f"{mm.value} {tm.value}" + for tm, mm in resolved['class']._supported_trading_mode_margin_pairs + ] + result.update({ + 'trade_modes': supported_modes, + }) + + return result + + def validate_exchanges(all_exchanges: bool) -> List[ValidExchangesType]: """ :return: List of tuples with exchangename, valid, reason. """ exchanges = ccxt_exchanges() if all_exchanges else available_exchanges() + from freqtrade.resolvers.exchange_resolver import ExchangeResolver + + subclassed = {e['name'].lower(): e for e in ExchangeResolver.search_all_objects({}, False)} exchanges_valid: List[ValidExchangesType] = [ - {'name': e, 'valid': valid, 'comment': comment} - for e, valid, comment in ((e, *validate_exchange(e)) for e in exchanges) + build_exchange_list_entry(e, subclassed) for e in exchanges ] return exchanges_valid diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index c5c4e1a68..2f912c4ab 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -2,7 +2,8 @@ This module loads custom exchanges """ import logging -from typing import Optional +from inspect import isclass +from typing import Any, Dict, List, Optional import freqtrade.exchange as exchanges from freqtrade.constants import Config, ExchangeConfig @@ -72,3 +73,26 @@ class ExchangeResolver(IResolver): f"Impossible to load Exchange '{exchange_name}'. This class does not exist " "or contains Python code errors." ) + + @classmethod + def search_all_objects(cls, config: Config, enum_failed: bool, + recursive: bool = False) -> List[Dict[str, Any]]: + """ + Searches for valid objects + :param config: Config object + :param enum_failed: If True, will return None for modules which fail. + Otherwise, failing modules are skipped. + :param recursive: Recursively walk directory tree searching for strategies + :return: List of dicts containing 'name', 'class' and 'location' entries + """ + result = [] + for exchange_name in dir(exchanges): + exchange = getattr(exchanges, exchange_name) + if isclass(exchange) and issubclass(exchange, Exchange): + result.append({ + 'name': exchange_name, + 'class': exchange, + 'location': exchange.__module__, + 'location_rel: ': exchange.__module__.replace('freqtrade.', ''), + }) + return result diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 2b20560e2..1557f0f35 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -41,7 +41,7 @@ class IResolver: object_type: Type[Any] object_type_str: str user_subdir: Optional[str] = None - initial_search_path: Optional[Path] + initial_search_path: Optional[Path] = None # Optional config setting containing a path (strategy_path, freqaimodel_path) extra_path: Optional[str] = None