diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index a04c03f7f..9d713fbcb 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -5,7 +5,9 @@ This module contains the argument manager class import argparse import os import re +from functools import partial from typing import List, NamedTuple, Optional + import arrow from freqtrade import __version__, constants @@ -255,6 +257,26 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', dest='print_one_column', ), + # List pairs / markets + "print_list": Arg( + '--print-list', + help='Print list of pairs or market symbols. By default data is' + 'printed in the tabular format.', + action='store_true', + ), + "quote_currency": Arg( + '--quote-currency', + help='Select quote currency.', + ), + "base_currency": Arg( + '--base-currency', + help='Select base currency.', + ), + "active_only": Arg( + '--active-only', + help='Print only active pairs or markets.', + action='store_true', + ), # Common script options "pairs": Arg( '-p', '--pairs', @@ -346,9 +368,11 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "position_stacking", "epochs ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] - ARGS_LIST_EXCHANGE = ["print_one_column"] +ARGS_LIST_PAIRS = ARGS_COMMON + ["exchange", "print_list", "base_currency", "quote_currency", + "active_only"] + ARGS_DOWNLOADER = ARGS_COMMON + ["pairs", "pairs_file", "days", "exchange", "timeframes", "erase"] ARGS_PLOT_DATAFRAME = (ARGS_COMMON + ARGS_STRATEGY + @@ -422,7 +446,7 @@ class Arguments(object): :return: None """ from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge - from freqtrade.utils import start_list_exchanges + from freqtrade.utils import start_list_exchanges, start_list_pairs subparsers = self.parser.add_subparsers(dest='subparser') @@ -443,12 +467,29 @@ class Arguments(object): # Add list-exchanges subcommand list_exchanges_cmd = subparsers.add_parser( - 'list-exchanges', - help='Print available exchanges.' + 'list-exchanges', + help='Print available exchanges.' ) list_exchanges_cmd.set_defaults(func=start_list_exchanges) self.build_args(optionlist=ARGS_LIST_EXCHANGE, parser=list_exchanges_cmd) + # Add list-markets subcommand + list_markets_cmd = subparsers.add_parser( + 'list-markets', + help='Print markets on exchange.' + ) + list_markets_cmd.set_defaults(func=partial(start_list_pairs, pairs_only=False)) + self.build_args(optionlist=ARGS_LIST_PAIRS, parser=list_markets_cmd) + + # Add list-pairs subcommand + list_pairs_cmd = subparsers.add_parser( + 'list-pairs', + help='Print pairs on exchange.' + ) + list_pairs_cmd.set_defaults(func=partial(start_list_pairs, pairs_only=True)) + self.build_args(optionlist=ARGS_LIST_PAIRS, parser=list_pairs_cmd) + + @staticmethod def parse_timerange(text: Optional[str]) -> TimeRange: """ diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b649a7b65..e55e01186 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -196,6 +196,23 @@ class Exchange(object): self._load_markets() return self._api.markets + def get_markets(self, base_currency: str = None, quote_currency: str = None, + pairs_only: bool = False, active_only: bool = False) -> Dict: + """ + Return exchange ccxt markets, filtered out by base currency and quote currency + if this was requested in parameters. + """ + markets = self.markets + if base_currency: + markets = {k: v for k, v in markets.items() if v['base'] == base_currency} + if quote_currency: + markets = {k: v for k, v in markets.items() if v['quote'] == quote_currency} + if pairs_only: + markets = {k: v for k, v in markets.items() if '/' in v['symbol']} + if active_only: + markets = {k: v for k, v in markets.items() if v['active']} + return markets + def klines(self, pair_interval: Tuple[str, str], copy=True) -> DataFrame: if pair_interval in self._klines: return self._klines[pair_interval].copy() if copy else self._klines[pair_interval] diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 460e20e91..15d5c2b18 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -133,3 +133,6 @@ def deep_merge_dicts(source, destination): destination[key] = value return destination + +def plural(num, singular: str, plural: str = None) -> str: + return singular if (num == 1 or num == -1) else plural or singular + 's' diff --git a/freqtrade/utils.py b/freqtrade/utils.py index d550ef43c..80e2a6430 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -3,9 +3,12 @@ from argparse import Namespace from typing import Any, Dict from freqtrade.configuration import Configuration -from freqtrade.exchange import available_exchanges +from freqtrade.exchange import available_exchanges, Exchange +from freqtrade.misc import plural from freqtrade.state import RunMode +from tabulate import tabulate + logger = logging.getLogger(__name__) @@ -39,3 +42,49 @@ def start_list_exchanges(args: Namespace) -> None: else: print(f"Exchanges supported by ccxt and available for Freqtrade: " f"{', '.join(available_exchanges())}") + + +def start_list_pairs(args: Namespace, pairs_only: bool = False) -> None: + """ + Print pairs on the exchange + :param args: Cli args from Arguments() + :return: None + """ + + # Initialize configuration + config = setup_utils_configuration(args, RunMode.OTHER) + + # Fetch exchange name from args, use bittrex as default exchange + config['exchange']['name'] = args.exchange or 'bittrex' + + # Init exchange + exchange = Exchange(config) + + logger.debug(f"Exchange markets: {exchange.markets}") + + pairs = exchange.get_markets(base_currency=args.base_currency, + quote_currency=args.quote_currency, + pairs_only=pairs_only, + active_only=args.active_only) + + if args.print_list: + # print data as a list + print(f"Exchange {exchange.name} has {len(pairs)} " + + (plural(len(pairs), "pair" if pairs_only else "market")) + + (f" with {args.base_currency} as base currency" if args.base_currency else "") + + (" and" if args.base_currency and args.quote_currency else "") + + (f" with {args.quote_currency} as quote currency" if args.quote_currency else "") + + (f": {sorted(pairs.keys())}" if len(pairs) else "") + ".") + else: +# # print a table of pairs (markets) +# print('{:<15} {:<15} {:<15} {:<15} {:<15}'.format('id', 'symbol', 'base', 'quote', 'active')) +# +# for (k, v) in pairs.items(): +# print('{:<15} {:<15} {:<15} {:<15} {:<15}'.format(v['id'], v['symbol'], v['base'], v['quote'], "Yes" if v['active'] else "No")) + tabular_data = [] + for _, v in pairs.items(): + tabular_data.append([v['id'], v['symbol'], v['base'], v['quote'], + "Yes" if v['active'] else "No"]) + + headers = ['Id', 'Symbol', 'Base', 'Quote', 'Active'] + print(tabulate(tabular_data, headers=headers, tablefmt='pipe'))