Enhance list-exchanges with more information

This commit is contained in:
Matthias 2023-06-03 08:28:44 +02:00
parent b5d1017779
commit 250ae2d006
4 changed files with 79 additions and 14 deletions

View File

@ -30,18 +30,34 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
if args['print_one_column']: if args['print_one_column']:
print('\n'.join([e['name'] for e in exchanges])) print('\n'.join([e['name'] for e in exchanges]))
else: 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 = { headers = {
'name': 'Exchange name', 'name': 'Exchange name',
'valid': 'Valid', 'valid': 'Valid',
'comment': 'reason', 'supported': 'Supported',
} 'trade_modes': 'Markets',
print(tabulate(exchanges, headers=headers)) '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: def _print_objs_tabular(objs: List, print_colorized: bool) -> None:

View File

@ -9,7 +9,8 @@ import ccxt
from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGITS, TICK_SIZE, from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGITS, TICK_SIZE,
TRUNCATE, decimal_to_precision) 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.exchange.types import ValidExchangesType
from freqtrade.util import FtPrecise from freqtrade.util import FtPrecise
from freqtrade.util.datetime_helpers import dt_from_ts, dt_ts 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, '' 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]: def validate_exchanges(all_exchanges: bool) -> List[ValidExchangesType]:
""" """
:return: List of tuples with exchangename, valid, reason. :return: List of tuples with exchangename, valid, reason.
""" """
exchanges = ccxt_exchanges() if all_exchanges else available_exchanges() 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] = [ exchanges_valid: List[ValidExchangesType] = [
{'name': e, 'valid': valid, 'comment': comment} build_exchange_list_entry(e, subclassed) for e in exchanges
for e, valid, comment in ((e, *validate_exchange(e)) for e in exchanges)
] ]
return exchanges_valid return exchanges_valid

View File

@ -2,7 +2,8 @@
This module loads custom exchanges This module loads custom exchanges
""" """
import logging import logging
from typing import Optional from inspect import isclass
from typing import Any, Dict, List, Optional
import freqtrade.exchange as exchanges import freqtrade.exchange as exchanges
from freqtrade.constants import Config, ExchangeConfig 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 " f"Impossible to load Exchange '{exchange_name}'. This class does not exist "
"or contains Python code errors." "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

View File

@ -41,7 +41,7 @@ class IResolver:
object_type: Type[Any] object_type: Type[Any]
object_type_str: str object_type_str: str
user_subdir: Optional[str] = None 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) # Optional config setting containing a path (strategy_path, freqaimodel_path)
extra_path: Optional[str] = None extra_path: Optional[str] = None