From 448b09d7b68ef7ae25e33757e3472c8cb7dc60aa Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 29 Sep 2019 11:54:20 +0300 Subject: [PATCH 1/8] Add list-timeframes subcommand --- freqtrade/configuration/arguments.py | 16 ++++++++++++++-- freqtrade/utils.py | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 6e2ecea2e..c2cde6090 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -31,6 +31,8 @@ ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] ARGS_LIST_EXCHANGES = ["print_one_column"] +ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"] + ARGS_CREATE_USERDIR = ["user_data_dir"] ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "exchange", "timeframes", "erase"] @@ -41,7 +43,8 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_ ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"] -NO_CONF_REQURIED = ["create-userdir", "download-data", "plot-dataframe", "plot-profit"] +NO_CONF_REQURIED = ["create-userdir", "download-data", "list-timeframes", "plot-dataframe", + "plot-profit"] class Arguments: @@ -98,7 +101,8 @@ class Arguments: :return: None """ from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge - from freqtrade.utils import start_create_userdir, start_download_data, start_list_exchanges + from freqtrade.utils import (start_create_userdir, start_download_data, + start_list_exchanges, start_list_timeframes) subparsers = self.parser.add_subparsers(dest='subparser') @@ -131,6 +135,14 @@ class Arguments: list_exchanges_cmd.set_defaults(func=start_list_exchanges) self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd) + # Add list-timeframes subcommand + list_timeframes_cmd = subparsers.add_parser( + 'list-timeframes', + help='Print available ticker intervals (timeframes) for the exchange.' + ) + list_timeframes_cmd.set_defaults(func=start_list_timeframes) + self._build_args(optionlist=ARGS_LIST_TIMEFRAMES, parser=list_timeframes_cmd) + # Add download-data subcommand download_data_cmd = subparsers.add_parser( 'download-data', diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 6ce5e888c..559fef0a5 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -97,3 +97,26 @@ def start_download_data(args: Dict[str, Any]) -> None: if pairs_not_available: logger.info(f"Pairs [{','.join(pairs_not_available)}] not available " f"on exchange {config['exchange']['name']}.") + + +def start_list_timeframes(args: Dict[str, Any]) -> None: + """ + Print ticker intervals (timeframes) available on Exchange + """ + config = setup_utils_configuration(args, RunMode.OTHER) + + # Init exchange + exchange = ExchangeResolver(config['exchange']['name'], config).exchange + + timeframes = list((exchange._api.timeframes or {}).keys()) + + if not timeframes: + logger.warning("List of timeframes available for exchange " + f"`{config['exchange']['name']}` is empty. " + "This exchange does not support fetching OHLCV data.") + else: + if args['print_one_column']: + print('\n'.join(timeframes)) + else: + print(f"Timeframes available for the exchange `{config['exchange']['name']}`: " + f"{', '.join(timeframes)}") From 75446d8195b58b2b4180a65d81f7452868a698c1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 29 Sep 2019 23:08:11 +0300 Subject: [PATCH 2/8] Refactor list-timeframes command with the use of the Exchange class methods --- freqtrade/exchange/exchange.py | 18 ++++++++++-------- freqtrade/utils.py | 17 ++++++----------- tests/exchange/test_exchange.py | 3 ++- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e2819bb59..6d68cef87 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -133,6 +133,9 @@ class Exchange: logger.info('Using Exchange "%s"', self.name) + # Check if timeframe is available + self.validate_timeframes(config.get('ticker_interval')) + # Converts the interval provided in minutes in config to seconds self.markets_refresh_interval: int = exchange_config.get( "markets_refresh_interval", 60) * 60 @@ -144,10 +147,6 @@ class Exchange: self.validate_ordertypes(config.get('order_types', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {})) - if config.get('ticker_interval'): - # Check if timeframe is available - self.validate_timeframes(config['ticker_interval']) - def __del__(self): """ Destructor - clean up async stuff @@ -199,6 +198,10 @@ class Exchange: """exchange ccxt id""" return self._api.id + @property + def timeframes(self) -> List[str]: + return list((self._api.timeframes or {}).keys()) + @property def markets(self) -> Dict: """exchange ccxt markets""" @@ -291,7 +294,7 @@ class Exchange: return pair raise DependencyException(f"Could not combine {curr_1} and {curr_2} to get a valid pair.") - def validate_timeframes(self, timeframe: List[str]) -> None: + def validate_timeframes(self, timeframe: Optional[str]) -> None: """ Checks if ticker interval from config is a supported timeframe on the exchange """ @@ -304,10 +307,9 @@ class Exchange: f"for the exchange \"{self.name}\" and this exchange " f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}") - timeframes = self._api.timeframes - if timeframe not in timeframes: + if timeframe and (timeframe not in self.timeframes): raise OperationalException( - f'Invalid ticker {timeframe}, this Exchange supports {timeframes}') + f"Invalid ticker interval '{timeframe}'. This exchange supports: {self.timeframes}") def validate_ordertypes(self, order_types: Dict) -> None: """ diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 559fef0a5..1f297b7ac 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -104,19 +104,14 @@ def start_list_timeframes(args: Dict[str, Any]) -> None: Print ticker intervals (timeframes) available on Exchange """ config = setup_utils_configuration(args, RunMode.OTHER) + # Do not use ticker_interval set in the config + config['ticker_interval'] = None # Init exchange exchange = ExchangeResolver(config['exchange']['name'], config).exchange - timeframes = list((exchange._api.timeframes or {}).keys()) - - if not timeframes: - logger.warning("List of timeframes available for exchange " - f"`{config['exchange']['name']}` is empty. " - "This exchange does not support fetching OHLCV data.") + if args['print_one_column']: + print('\n'.join(exchange.timeframes)) else: - if args['print_one_column']: - print('\n'.join(timeframes)) - else: - print(f"Timeframes available for the exchange `{config['exchange']['name']}`: " - f"{', '.join(timeframes)}") + print(f"Timeframes available for the exchange `{config['exchange']['name']}`: " + f"{', '.join(exchange.timeframes)}") diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index f8c556aeb..bf6025322 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -409,7 +409,8 @@ def test_validate_timeframes_failed(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) - with pytest.raises(OperationalException, match=r'Invalid ticker 3m, this Exchange supports.*'): + with pytest.raises(OperationalException, + match=r"Invalid ticker interval '3m'. This exchange supports.*"): Exchange(default_conf) From cd0e813a858660eac36f48ea615ce356e68ae5cb Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 30 Sep 2019 21:36:52 +0300 Subject: [PATCH 3/8] Docs adjusted, utils.md added --- docs/bot-usage.md | 10 ++++++++-- docs/utils.md | 26 ++++++++++++++++++++++++++ mkdocs.yml | 9 +++++---- 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 docs/utils.md diff --git a/docs/bot-usage.md b/docs/bot-usage.md index f44400e32..795f5f07d 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -12,17 +12,23 @@ This page explains the different parameters of the bot and how to run it. usage: freqtrade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH] [--db-url PATH] [--sd-notify] - {backtesting,edge,hyperopt,create-userdir,list-exchanges} ... + {backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit} + ... Free, open source crypto trading bot positional arguments: - {backtesting,edge,hyperopt,create-userdir,list-exchanges} + {backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit} backtesting Backtesting module. edge Edge module. hyperopt Hyperopt module. create-userdir Create user-data directory. list-exchanges Print available exchanges. + list-timeframes Print available ticker intervals (timeframes) for the + exchange. + download-data Download backtesting data. + plot-dataframe Plot candles with indicators. + plot-profit Generate plot showing profits. optional arguments: -h, --help show this help message and exit diff --git a/docs/utils.md b/docs/utils.md new file mode 100644 index 000000000..cfed604bd --- /dev/null +++ b/docs/utils.md @@ -0,0 +1,26 @@ +# Utility Subcommands + +Besides the Live-Trade and Dry-Run run modes, the `backtesting`, `edge` and `hyperopt` optimization subcommands, and the `download-data` subcommand which prepares historical data, the bot contains a number of utility subcommands. They are described in this section. + +## List Exchanges + +``` +usage: freqtrade list-exchanges [-h] [-1] + +optional arguments: + -h, --help show this help message and exit + -1, --one-column Print exchanges in one column. +``` + +## List Timeframes + +``` +usage: freqtrade list-timeframes [-h] [--exchange EXCHANGE] [-1] + +optional arguments: + -h, --help show this help message and exit + --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no + config is provided. + -1, --one-column Print exchanges in one column. + +``` diff --git a/mkdocs.yml b/mkdocs.yml index 7aedb4bba..e1b754962 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,14 +14,15 @@ nav: - Data Downloading: data-download.md - Backtesting: backtesting.md - Hyperopt: hyperopt.md - - Edge positioning: edge.md + - Edge Positioning: edge.md + - Utility Subcommands: utils.md - FAQ: faq.md - Data Analysis: data-analysis.md - Plotting: plotting.md - SQL Cheatsheet: sql_cheatsheet.md - - Sandbox testing: sandbox-testing.md - - Deprecated features: deprecated.md - - Contributors guide: developer.md + - Sandbox Testing: sandbox-testing.md + - Deprecated Features: deprecated.md + - Contributors Guide: developer.md theme: name: material logo: 'images/logo.png' From f2e878d9ec47e0342a5c45ad9c51ab342b387206 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 1 Oct 2019 16:57:35 +0300 Subject: [PATCH 4/8] Update helpstring for list-exchanges --- docs/utils.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/utils.md b/docs/utils.md index cfed604bd..fe22cd50d 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -5,11 +5,12 @@ Besides the Live-Trade and Dry-Run run modes, the `backtesting`, `edge` and `hyp ## List Exchanges ``` -usage: freqtrade list-exchanges [-h] [-1] +usage: freqtrade list-exchanges [-h] [-1] [-a] optional arguments: -h, --help show this help message and exit -1, --one-column Print exchanges in one column. + -a, --all Print all exchanges known to the ccxt library. ``` ## List Timeframes From c57d5ef1cd84fdfaa37b0cc6fe3760e81dcda1dc Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 1 Oct 2019 20:26:44 +0300 Subject: [PATCH 5/8] Added short descriptions and examples in utils.md --- docs/utils.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/utils.md b/docs/utils.md index fe22cd50d..459960fac 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -4,6 +4,8 @@ Besides the Live-Trade and Dry-Run run modes, the `backtesting`, `edge` and `hyp ## List Exchanges +Use the `list-exchanges` subcommand to see the exchanges available for the bot. + ``` usage: freqtrade list-exchanges [-h] [-1] [-a] @@ -13,8 +15,22 @@ optional arguments: -a, --all Print all exchanges known to the ccxt library. ``` +* Example: see exchanges available for the bot: +``` +$ freqtrade list-exchanges +Exchanges available for Freqtrade: _1btcxe, acx, allcoin, bequant, bibox, binance, binanceje, binanceus, bitbank, bitfinex, bitfinex2, bitkk, bitlish, bitmart, bittrex, bitz, bleutrade, btcalpha, btcmarkets, btcturk, buda, cex, cobinhood, coinbaseprime, coinbasepro, coinex, cointiger, coss, crex24, digifinex, dsx, dx, ethfinex, fcoin, fcoinjp, gateio, gdax, gemini, hitbtc2, huobipro, huobiru, idex, kkex, kraken, kucoin, kucoin2, kuna, lbank, mandala, mercado, oceanex, okcoincny, okcoinusd, okex, okex3, poloniex, rightbtc, theocean, tidebit, upbit, zb +``` + +* Example: see all exchanges supported by the ccxt library (including 'bad' ones, i.e. those that are known to not work with Freqtrade): +``` +$ freqtrade list-exchanges -a +All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpro, bcex, bequant, bibox, bigone, binance, binanceje, binanceus, bit2c, bitbank, bitbay, bitfinex, bitfinex2, bitflyer, bitforex, bithumb, bitkk, bitlish, bitmart, bitmex, bitso, bitstamp, bitstamp1, bittrex, bitz, bl3p, bleutrade, braziliex, btcalpha, btcbox, btcchina, btcmarkets, btctradeim, btctradeua, btcturk, buda, bxinth, cex, chilebit, cobinhood, coinbase, coinbaseprime, coinbasepro, coincheck, coinegg, coinex, coinexchange, coinfalcon, coinfloor, coingi, coinmarketcap, coinmate, coinone, coinspot, cointiger, coolcoin, coss, crex24, crypton, deribit, digifinex, dsx, dx, ethfinex, exmo, exx, fcoin, fcoinjp, flowbtc, foxbit, fybse, gateio, gdax, gemini, hitbtc, hitbtc2, huobipro, huobiru, ice3x, idex, independentreserve, indodax, itbit, kkex, kraken, kucoin, kucoin2, kuna, lakebtc, latoken, lbank, liquid, livecoin, luno, lykke, mandala, mercado, mixcoins, negociecoins, nova, oceanex, okcoincny, okcoinusd, okex, okex3, paymium, poloniex, rightbtc, southxchange, stronghold, surbitcoin, theocean, therock, tidebit, tidex, upbit, vaultoro, vbtc, virwox, xbtce, yobit, zaif, zb +``` + ## List Timeframes +Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange. + ``` usage: freqtrade list-timeframes [-h] [--exchange EXCHANGE] [-1] @@ -25,3 +41,16 @@ optional arguments: -1, --one-column Print exchanges in one column. ``` + +* Example: see the timeframes for the 'binance' exchange, set in the configuration file: + +``` +$ freqtrade -c config_binance.json list-timeframes +... +Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M +``` + +* Example: enumerate exchanges available for Freqtrade and print timeframes supported by each of them: +``` +$ for i in `freqtrade list-exchanges -1`; do freqtrade list-timeframes --exchange $i; done +``` From f95b0ccdab3e5c40e795e9f5c4ef64149838238a Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 4 Oct 2019 02:01:44 +0300 Subject: [PATCH 6/8] Tests added --- tests/test_utils.py | 74 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index c99044610..6168706a2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -7,7 +7,8 @@ import pytest from freqtrade import OperationalException from freqtrade.state import RunMode from freqtrade.utils import (setup_utils_configuration, start_create_userdir, - start_download_data, start_list_exchanges) + start_download_data, start_list_exchanges, + start_list_timeframes) from tests.conftest import get_args, log_has, patch_exchange @@ -48,6 +49,77 @@ def test_list_exchanges(capsys): assert re.search(r"^bittrex$", captured.out, re.MULTILINE) +def test_list_timeframes(capsys): + + args = [ + "list-timeframes", + ] + pargs = get_args(args) + pargs['config'] = None + with pytest.raises(OperationalException, + match=r"This command requires a configured exchange.*"): + start_list_timeframes(pargs) + + # Test with --config config.json.example + args = [ + '--config', 'config.json.example', + "list-timeframes", + ] + start_list_timeframes(get_args(args)) + captured = capsys.readouterr() + assert re.match("Timeframes available for the exchange `bittrex`: " + "1m, 5m, 30m, 1h, 1d", + captured.out) + + # Test with --exchange bittrex + args = [ + "list-timeframes", + "--exchange", "bittrex", + ] + start_list_timeframes(get_args(args)) + captured = capsys.readouterr() + assert re.match("Timeframes available for the exchange `bittrex`: " + "1m, 5m, 30m, 1h, 1d", + captured.out) + + # Test with --exchange binance + args = [ + "list-timeframes", + "--exchange", "binance", + ] + start_list_timeframes(get_args(args)) + captured = capsys.readouterr() + assert re.match("Timeframes available for the exchange `binance`: " + "1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M", + captured.out) + + # Test with --one-column + args = [ + '--config', 'config.json.example', + "list-timeframes", + "--one-column", + ] + start_list_timeframes(get_args(args)) + captured = capsys.readouterr() + assert re.search(r"^1m$", captured.out, re.MULTILINE) + assert re.search(r"^5m$", captured.out, re.MULTILINE) + assert re.search(r"^1h$", captured.out, re.MULTILINE) + assert re.search(r"^1d$", captured.out, re.MULTILINE) + + # Test with --exchange binance --one-column + args = [ + "list-timeframes", + "--exchange", "binance", + "--one-column", + ] + start_list_timeframes(get_args(args)) + captured = capsys.readouterr() + assert re.search(r"^1m$", captured.out, re.MULTILINE) + assert re.search(r"^5m$", captured.out, re.MULTILINE) + assert re.search(r"^1h$", captured.out, re.MULTILINE) + assert re.search(r"^1d$", captured.out, re.MULTILINE) + + def test_create_datadir_failed(caplog): args = [ From d2589c4415830fd94294b0f90bdb47f26fa22d86 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 6 Oct 2019 10:32:19 +0200 Subject: [PATCH 7/8] Make test exchange-independent --- tests/test_utils.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 6168706a2..103a25d6d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -49,8 +49,16 @@ def test_list_exchanges(capsys): assert re.search(r"^bittrex$", captured.out, re.MULTILINE) -def test_list_timeframes(capsys): +def test_list_timeframes(mocker, capsys): + api_mock = MagicMock() + api_mock.timeframes = {'1m': '1m', + '5m': '5m', + '30m': '30m', + '1h': '1h', + '1d': '1d', + } + patch_exchange(mocker, api_mock=api_mock) args = [ "list-timeframes", ] @@ -82,6 +90,17 @@ def test_list_timeframes(capsys): "1m, 5m, 30m, 1h, 1d", captured.out) + api_mock.timeframes = {'1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + } + patch_exchange(mocker, api_mock=api_mock) # Test with --exchange binance args = [ "list-timeframes", @@ -90,7 +109,7 @@ def test_list_timeframes(capsys): start_list_timeframes(get_args(args)) captured = capsys.readouterr() assert re.match("Timeframes available for the exchange `binance`: " - "1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M", + "1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d, 3d", captured.out) # Test with --one-column From 33940ae66bc18467047ef5dfea09e0525eddb959 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 6 Oct 2019 14:33:23 +0200 Subject: [PATCH 8/8] Use different keys and values --- tests/test_utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 103a25d6d..ca8cbcc0c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -52,11 +52,11 @@ def test_list_exchanges(capsys): def test_list_timeframes(mocker, capsys): api_mock = MagicMock() - api_mock.timeframes = {'1m': '1m', - '5m': '5m', - '30m': '30m', - '1h': '1h', - '1d': '1d', + api_mock.timeframes = {'1m': 'oneMin', + '5m': 'fiveMin', + '30m': 'thirtyMin', + '1h': 'hour', + '1d': 'day', } patch_exchange(mocker, api_mock=api_mock) args = [