From 8a17615b5a96fd59ed39ba317e4acb741d0f881b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 24 Aug 2019 19:41:11 +0200 Subject: [PATCH 1/8] move exceptionhandling from create_order() to calling functions --- freqtrade/exchange/exchange.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a8e974991..53723ad51 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -320,7 +320,7 @@ class Exchange(object): if (order_types.get("stoploss_on_exchange") and not self._ft_has.get("stoploss_on_exchange", False)): raise OperationalException( - 'On exchange stoploss is not supported for %s.' % self.name + f'On exchange stoploss is not supported for {self.name}.' ) def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: @@ -469,11 +469,26 @@ class Exchange(object): params = self._params.copy() params.update({'stopPrice': stop_price}) - - order = self.create_order(pair, ordertype, 'sell', amount, rate, params) - logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s', pair, stop_price, rate) - return order + try: + order = self.create_order(pair, ordertype, 'sell', amount, rate, params) + logger.info('stoploss limit order added for %s. ' + 'stop price: %s. limit: %s', pair, stop_price, rate) + return order + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to create {ordertype} sell order on market {pair}.' + f'Tried to sell amount {amount} at rate {rate}.' + f'Message: {e}') from e + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}.' + f'Message: {e}') from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e @retrier def get_balance(self, currency: str) -> float: From ea179a8e38a2dbaaf38e7b6a1904c174215aab83 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Aug 2019 09:43:10 +0200 Subject: [PATCH 2/8] stoploss_limit shall not use `create_order()` It needs to handle exceptions differently --- freqtrade/exchange/exchange.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 53723ad51..7a384b7cc 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -470,7 +470,12 @@ class Exchange(object): params = self._params.copy() params.update({'stopPrice': stop_price}) try: - order = self.create_order(pair, ordertype, 'sell', amount, rate, params) + amount = self.symbol_amount_prec(pair, amount) + + rate = self.symbol_price_prec(pair, rate) + + order = self._api.create_order(pair, ordertype, 'sell', + amount, rate, params) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s', pair, stop_price, rate) return order From defa1c027d911c348586fbf7c0419113a122d796 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Aug 2019 09:50:37 +0200 Subject: [PATCH 3/8] Move stoploss_limit to binance subclass --- freqtrade/exchange/binance.py | 52 ++++++++++++++++++++++++++++++++++ freqtrade/exchange/exchange.py | 48 ++++--------------------------- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 18e754e3f..b63ef59c8 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -2,6 +2,9 @@ import logging from typing import Dict +import ccxt + +from freqtrade import DependencyException, OperationalException, TemporaryError from freqtrade.exchange import Exchange logger = logging.getLogger(__name__) @@ -25,3 +28,52 @@ class Binance(Exchange): limit = min(list(filter(lambda x: limit <= x, limit_range))) return super().get_order_book(pair, limit) + + def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: + """ + creates a stoploss limit order. + this stoploss-limit is binance-specific. + It may work with a limited number of other exchanges, but this has not been tested yet. + + """ + ordertype = "stop_loss_limit" + + stop_price = self.symbol_price_prec(pair, stop_price) + + # Ensure rate is less than stop price + if stop_price <= rate: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') + + if self._config['dry_run']: + dry_order = self.dry_run_order( + pair, ordertype, "sell", amount, stop_price) + return dry_order + + params = self._params.copy() + params.update({'stopPrice': stop_price}) + try: + amount = self.symbol_amount_prec(pair, amount) + + rate = self.symbol_price_prec(pair, rate) + + order = self._api.create_order(pair, ordertype, 'sell', + amount, rate, params) + logger.info('stoploss limit order added for %s. ' + 'stop price: %s. limit: %s', pair, stop_price, rate) + return order + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to create {ordertype} sell order on market {pair}.' + f'Tried to sell amount {amount} at rate {rate}.' + f'Message: {e}') from e + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}.' + f'Message: {e}') from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7a384b7cc..534e2ea65 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -450,50 +450,14 @@ class Exchange(object): def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: """ creates a stoploss limit order. - NOTICE: it is not supported by all exchanges. only binance is tested for now. - TODO: implementation maybe needs to be moved to the binance subclass + Since ccxt does not unify stoploss-limit orders yet, this needs to be implemented in each + exchange's subclass. + The exception below should never raise, since we disallow + starting the bot in validate_ordertypes() + Note: Changes to this interface need to be applied to all sub-classes too. """ - ordertype = "stop_loss_limit" - stop_price = self.symbol_price_prec(pair, stop_price) - - # Ensure rate is less than stop price - if stop_price <= rate: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') - - if self._config['dry_run']: - dry_order = self.dry_run_order( - pair, ordertype, "sell", amount, stop_price) - return dry_order - - params = self._params.copy() - params.update({'stopPrice': stop_price}) - try: - amount = self.symbol_amount_prec(pair, amount) - - rate = self.symbol_price_prec(pair, rate) - - order = self._api.create_order(pair, ordertype, 'sell', - amount, rate, params) - logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s', pair, stop_price, rate) - return order - except ccxt.InsufficientFunds as e: - raise DependencyException( - f'Insufficient funds to create {ordertype} sell order on market {pair}.' - f'Tried to sell amount {amount} at rate {rate}.' - f'Message: {e}') from e - except ccxt.InvalidOrder as e: - raise DependencyException( - f'Could not create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}.' - f'Message: {e}') from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e + raise OperationalException(f"stoploss_limit not implemented for {self.name}.") @retrier def get_balance(self, currency: str) -> float: From 067c122bf3e4e53d45eee4b48e6083a531ef5e66 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Aug 2019 09:52:14 +0200 Subject: [PATCH 4/8] Adapt test to use Binance class --- freqtrade/tests/test_freqtradebot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index dab7a9ff7..1119157c4 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2414,7 +2414,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) + mocker.patch('freqtrade.exchange.Binance.stoploss_limit', stoploss_limit) freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -2454,7 +2454,6 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, freqtrade.process_maybe_execute_sell(trade) assert trade.stoploss_order_id is None assert trade.is_open is False - print(trade.sell_reason) assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value assert rpc_mock.call_count == 2 From 2c66b33fd107e0829cba668002d549a56bac4c56 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Aug 2019 09:57:21 +0200 Subject: [PATCH 5/8] Adapt some tests to use Binance subclass for stoplosslimit --- freqtrade/exchange/exchange.py | 2 +- freqtrade/tests/exchange/test_exchange.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 534e2ea65..6c93f9128 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -457,7 +457,7 @@ class Exchange(object): Note: Changes to this interface need to be applied to all sub-classes too. """ - raise OperationalException(f"stoploss_limit not implemented for {self.name}.") + raise OperationalException(f"stoploss_limit is not implemented for {self.name}.") @retrier def get_balance(self, currency: str) -> float: diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index e453b5dca..0dc07e409 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1474,17 +1474,17 @@ def test_stoploss_limit_order(default_conf, mocker): # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection")) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) with pytest.raises(OperationalException): @@ -1493,6 +1493,12 @@ def test_stoploss_limit_order(default_conf, mocker): exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) +def test_stoploss_limit_order_unsupported_exchange(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, 'bittrex') + with pytest.raises(OperationalException, match=r"stoploss_limit is not implemented .*"): + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + def test_stoploss_limit_order_dry_run(default_conf, mocker): api_mock = MagicMock() order_type = 'stop_loss_limit' From cbf09b5ad9781f42b0b836d45fbbd3936576c59f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Aug 2019 10:07:47 +0200 Subject: [PATCH 6/8] Improve docstring for Exception --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 14f0bb819..5ccc2ff3c 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -11,7 +11,7 @@ class DependencyException(Exception): class OperationalException(Exception): """ - Requires manual intervention. + Requires manual intervention and will usually stop the bot. This happens when an exchange returns an unexpected error during runtime or given configuration is invalid. """ From a4c8b5bf5da8228c3300e3d4c83c763e290b0a57 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Aug 2019 10:08:06 +0200 Subject: [PATCH 7/8] Move binance-specific test to test_binance.py --- freqtrade/exchange/binance.py | 5 +- freqtrade/tests/exchange/test_binance.py | 90 +++++++++++++++++++++++ freqtrade/tests/exchange/test_exchange.py | 78 -------------------- 3 files changed, 93 insertions(+), 80 deletions(-) create mode 100644 freqtrade/tests/exchange/test_binance.py diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index b63ef59c8..5834f26cd 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -50,9 +50,10 @@ class Binance(Exchange): pair, ordertype, "sell", amount, stop_price) return dry_order - params = self._params.copy() - params.update({'stopPrice': stop_price}) try: + params = self._params.copy() + params.update({'stopPrice': stop_price}) + amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) diff --git a/freqtrade/tests/exchange/test_binance.py b/freqtrade/tests/exchange/test_binance.py new file mode 100644 index 000000000..4afb7fcc4 --- /dev/null +++ b/freqtrade/tests/exchange/test_binance.py @@ -0,0 +1,90 @@ +from random import randint +from unittest.mock import MagicMock + +import ccxt +import pytest + +from freqtrade import DependencyException, OperationalException, TemporaryError +from freqtrade.tests.conftest import get_patched_exchange + + +def test_stoploss_limit_order(default_conf, mocker): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + order_type = 'stop_loss_limit' + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + + default_conf['dry_run'] = False + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + + with pytest.raises(OperationalException): + order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200) + + api_mock.create_order.reset_mock() + + order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + assert api_mock.create_order.call_args[0][1] == order_type + assert api_mock.create_order.call_args[0][2] == 'sell' + assert api_mock.create_order.call_args[0][3] == 1 + assert api_mock.create_order.call_args[0][4] == 200 + assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220} + + # test exception handling + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + with pytest.raises(TemporaryError): + api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + with pytest.raises(OperationalException, match=r".*DeadBeef.*"): + api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + +def test_stoploss_limit_order_dry_run(default_conf, mocker): + api_mock = MagicMock() + order_type = 'stop_loss_limit' + default_conf['dry_run'] = True + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + + with pytest.raises(OperationalException): + order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200) + + api_mock.create_order.reset_mock() + + order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) + + assert 'id' in order + assert 'info' in order + assert 'type' in order + + assert order['type'] == order_type + assert order['price'] == 220 + assert order['amount'] == 1 diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 0dc07e409..afd7b441a 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1436,62 +1436,6 @@ def test_get_fee(default_conf, mocker, exchange_name): 'get_fee', 'calculate_fee') -def test_stoploss_limit_order(default_conf, mocker): - api_mock = MagicMock() - order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) - order_type = 'stop_loss_limit' - - api_mock.create_order = MagicMock(return_value={ - 'id': order_id, - 'info': { - 'foo': 'bar' - } - }) - - default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) - - exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - - with pytest.raises(OperationalException): - order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200) - - api_mock.create_order.reset_mock() - - order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) - - assert 'id' in order - assert 'info' in order - assert order['id'] == order_id - assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' - assert api_mock.create_order.call_args[0][1] == order_type - assert api_mock.create_order.call_args[0][2] == 'sell' - assert api_mock.create_order.call_args[0][3] == 1 - assert api_mock.create_order.call_args[0][4] == 200 - assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220} - - # test exception handling - with pytest.raises(DependencyException): - api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) - - with pytest.raises(DependencyException): - api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) - - with pytest.raises(TemporaryError): - api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection")) - exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) - - with pytest.raises(OperationalException): - api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) - exchange = get_patched_exchange(mocker, default_conf, api_mock) - exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) - def test_stoploss_limit_order_unsupported_exchange(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, 'bittrex') @@ -1499,29 +1443,7 @@ def test_stoploss_limit_order_unsupported_exchange(default_conf, mocker): exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) -def test_stoploss_limit_order_dry_run(default_conf, mocker): - api_mock = MagicMock() - order_type = 'stop_loss_limit' - default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) - exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') - - with pytest.raises(OperationalException): - order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200) - - api_mock.create_order.reset_mock() - - order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) - - assert 'id' in order - assert 'info' in order - assert 'type' in order - - assert order['type'] == order_type - assert order['price'] == 220 - assert order['amount'] == 1 def test_merge_ft_has_dict(default_conf, mocker): From 5e12b05424df197a5d724d9bb164b139f775076a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Aug 2019 10:13:35 +0200 Subject: [PATCH 8/8] Improve test coverage --- freqtrade/tests/exchange/test_exchange.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index afd7b441a..d9f350c7b 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -101,18 +101,21 @@ def test_destroy(default_conf, mocker, caplog): def test_init_exception(default_conf, mocker): default_conf['exchange']['name'] = 'wrong_exchange_name' - with pytest.raises( - OperationalException, - match='Exchange {} is not supported'.format(default_conf['exchange']['name'])): + with pytest.raises(OperationalException, + match=f"Exchange {default_conf['exchange']['name']} is not supported"): Exchange(default_conf) default_conf['exchange']['name'] = 'binance' - with pytest.raises( - OperationalException, - match='Exchange {} is not supported'.format(default_conf['exchange']['name'])): + with pytest.raises(OperationalException, + match=f"Exchange {default_conf['exchange']['name']} is not supported"): mocker.patch("ccxt.binance", MagicMock(side_effect=AttributeError)) Exchange(default_conf) + with pytest.raises(OperationalException, + match=r"Initialization of ccxt failed. Reason: DeadBeef"): + mocker.patch("ccxt.binance", MagicMock(side_effect=ccxt.BaseError("DeadBeef"))) + Exchange(default_conf) + def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock())) @@ -1436,16 +1439,12 @@ def test_get_fee(default_conf, mocker, exchange_name): 'get_fee', 'calculate_fee') - def test_stoploss_limit_order_unsupported_exchange(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, 'bittrex') with pytest.raises(OperationalException, match=r"stoploss_limit is not implemented .*"): exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) - - - def test_merge_ft_has_dict(default_conf, mocker): mocker.patch.multiple('freqtrade.exchange.Exchange', _init_ccxt=MagicMock(return_value=MagicMock()),