diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 69f13ed4d..35d388432 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -335,12 +335,25 @@ class Arguments(object): metavar='INT', ) + @staticmethod + def list_exchanges_options(parser: argparse.ArgumentParser) -> None: + """ + Parses given arguments for the list-exchanges command. + """ + parser.add_argument( + '-1', '--one-column', + help='Print exchanges in one column', + action='store_true', + dest='print_one_column', + ) + def _build_subcommands(self) -> None: """ Builds and attaches all subcommands :return: None """ from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge + from freqtrade.utils import start_list_exchanges subparsers = self.parser.add_subparsers(dest='subparser') @@ -362,6 +375,14 @@ class Arguments(object): self.optimizer_shared_options(hyperopt_cmd) self.hyperopt_options(hyperopt_cmd) + # Add list-exchanges subcommand + list_exchanges_cmd = subparsers.add_parser( + 'list-exchanges', + help='Print available exchanges.' + ) + list_exchanges_cmd.set_defaults(func=start_list_exchanges) + self.list_exchanges_options(list_exchanges_cmd) + @staticmethod def parse_timerange(text: Optional[str]) -> TimeRange: """ diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index dd0148bd8..034cb5f8b 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -5,6 +5,7 @@ import re from copy import deepcopy from datetime import datetime from functools import reduce +from typing import List from unittest.mock import MagicMock, PropertyMock import arrow @@ -12,6 +13,7 @@ import pytest from telegram import Chat, Message, Update from freqtrade import constants, persistence +from freqtrade.arguments import Arguments from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.edge import Edge, PairInfo from freqtrade.exchange import Exchange @@ -36,6 +38,10 @@ def log_has_re(line, logs): False) +def get_args(args) -> List[str]: + return Arguments(args, '').get_parsed_arg() + + def patch_exchange(mocker, api_mock=None, id='bittrex') -> None: mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 3f88a8d6c..cf32934c7 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -3,7 +3,6 @@ import json import math import random -from typing import List from unittest.mock import MagicMock import numpy as np @@ -12,7 +11,7 @@ import pytest from arrow import Arrow from freqtrade import DependencyException, constants -from freqtrade.arguments import Arguments, TimeRange +from freqtrade.arguments import TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import evaluate_result_multi from freqtrade.data.converter import parse_ticker_dataframe @@ -23,11 +22,7 @@ from freqtrade.optimize.backtesting import Backtesting from freqtrade.state import RunMode from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.interface import SellType -from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange - - -def get_args(args) -> List[str]: - return Arguments(args, '').get_parsed_arg() +from freqtrade.tests.conftest import get_args, log_has, log_has_re, patch_exchange def trim_dictlist(dict_list, num): diff --git a/freqtrade/tests/optimize/test_edge_cli.py b/freqtrade/tests/optimize/test_edge_cli.py index 49d3cdd55..6b527543f 100644 --- a/freqtrade/tests/optimize/test_edge_cli.py +++ b/freqtrade/tests/optimize/test_edge_cli.py @@ -2,19 +2,13 @@ # pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments import json -from typing import List from unittest.mock import MagicMock -from freqtrade.arguments import Arguments from freqtrade.edge import PairInfo -from freqtrade.optimize import start_edge, setup_configuration +from freqtrade.optimize import setup_configuration, start_edge from freqtrade.optimize.edge_cli import EdgeCli from freqtrade.state import RunMode -from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange - - -def get_args(args) -> List[str]: - return Arguments(args, '').get_parsed_arg() +from freqtrade.tests.conftest import get_args, log_has, log_has_re, patch_exchange def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None: diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index a51d74dbb..baa5da545 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -16,8 +16,7 @@ from freqtrade.optimize.hyperopt import Hyperopt, HYPEROPT_LOCKFILE from freqtrade.optimize import setup_configuration, start_hyperopt from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver from freqtrade.state import RunMode -from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange -from freqtrade.tests.optimize.test_backtesting import get_args +from freqtrade.tests.conftest import get_args, log_has, log_has_re, patch_exchange @pytest.fixture(scope='function') diff --git a/freqtrade/tests/test_utils.py b/freqtrade/tests/test_utils.py new file mode 100644 index 000000000..7550efb23 --- /dev/null +++ b/freqtrade/tests/test_utils.py @@ -0,0 +1,41 @@ +from freqtrade.utils import setup_configuration, start_list_exchanges +from freqtrade.tests.conftest import get_args +from freqtrade.state import RunMode + +import re + + +def test_setup_configuration(): + args = [ + '--config', 'config.json.example', + ] + + config = setup_configuration(get_args(args), RunMode.OTHER) + assert "exchange" in config + assert config['exchange']['key'] == '' + assert config['exchange']['secret'] == '' + + +def test_list_exchanges(capsys): + + args = [ + "list-exchanges", + ] + + start_list_exchanges(get_args(args)) + captured = capsys.readouterr() + assert re.match(r"Exchanges supported by ccxt and available.*", captured.out) + assert re.match(r".*binance,.*", captured.out) + assert re.match(r".*bittrex,.*", captured.out) + + # Test with --one-column + args = [ + "list-exchanges", + "--one-column", + ] + + start_list_exchanges(get_args(args)) + captured = capsys.readouterr() + assert not re.match(r"Exchanges supported by ccxt and available.*", captured.out) + assert re.search(r"^binance$", captured.out, re.MULTILINE) + assert re.search(r"^bittrex$", captured.out, re.MULTILINE) diff --git a/freqtrade/utils.py b/freqtrade/utils.py new file mode 100644 index 000000000..324b54a4e --- /dev/null +++ b/freqtrade/utils.py @@ -0,0 +1,40 @@ +import logging +from argparse import Namespace +from typing import Any, Dict + +from freqtrade.configuration import Configuration +from freqtrade.exchange import available_exchanges +from freqtrade.state import RunMode + + +logger = logging.getLogger(__name__) + + +def setup_configuration(args: Namespace, method: RunMode) -> Dict[str, Any]: + """ + Prepare the configuration for the Hyperopt module + :param args: Cli args from Arguments() + :return: Configuration + """ + configuration = Configuration(args, method) + config = configuration.load_config() + + # Ensure we do not use Exchange credentials + config['exchange']['key'] = '' + config['exchange']['secret'] = '' + + return config + + +def start_list_exchanges(args: Namespace) -> None: + """ + Print available exchanges + :param args: Cli args from Arguments() + :return: None + """ + + if args.print_one_column: + print('\n'.join(available_exchanges())) + else: + print(f"Exchanges supported by ccxt and available for Freqtrade: " + f"{', '.join(available_exchanges())}")