From 61182f849bd252b5b38a91c48fe29b25e7f01ebb Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 00:45:10 -0600 Subject: [PATCH 01/12] exchange.fetch_order and exchange.cancel_order added params argument --- freqtrade/exchange/exchange.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index da89a7c8a..81d20fd21 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -376,7 +376,7 @@ class Exchange: raise OperationalException( 'Could not load markets, therefore cannot start. ' 'Please investigate the above error for more details.' - ) + ) quote_currencies = self.get_quote_currencies() if stake_currency not in quote_currencies: raise OperationalException( @@ -882,11 +882,11 @@ class Exchange: raise OperationalException(e) from e @retrier(retries=API_FETCH_ORDER_RETRY_COUNT) - def fetch_order(self, order_id: str, pair: str) -> Dict: + def fetch_order(self, order_id: str, pair: str, params={}) -> Dict: if self._config['dry_run']: return self.fetch_dry_run_order(order_id) try: - order = self._api.fetch_order(order_id, pair) + order = self._api.fetch_order(order_id, pair, params=params) self._log_exchange_response('fetch_order', order) return order except ccxt.OrderNotFound as e: @@ -929,7 +929,7 @@ class Exchange: and order.get('filled') == 0.0) @retrier - def cancel_order(self, order_id: str, pair: str) -> Dict: + def cancel_order(self, order_id: str, pair: str, params={}) -> Dict: if self._config['dry_run']: try: order = self.fetch_dry_run_order(order_id) @@ -940,7 +940,7 @@ class Exchange: return {} try: - order = self._api.cancel_order(order_id, pair) + order = self._api.cancel_order(order_id, pair, params=params) self._log_exchange_response('cancel_order', order) return order except ccxt.InvalidOrder as e: From e3ced55f5c2979c30d3ad849ef6e20a000bd12da Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 00:45:50 -0600 Subject: [PATCH 02/12] gateio.fetch_order and gateio.cancel_order --- freqtrade/exchange/gateio.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index 7e1f21921..7580281cf 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -31,4 +31,18 @@ class Gateio(Exchange): if any(v == 'market' for k, v in order_types.items()): raise OperationalException( - f'Exchange {self.name} does not support market orders.') + f'Exchange {self.name} does not support market orders.') + + def fetch_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict: + return self.fetch_order( + order_id=order_id, + pair=pair, + params={'stop': True} + ) + + def cancel_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict: + return self.cancel_order( + order_id=order_id, + pair=pair, + params={'stop': True} + ) From ae4742afcb78d58d30ec88125cdb526204780a62 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 00:59:28 -0600 Subject: [PATCH 03/12] test_fetch_stoploss_order_gateio and test_cancel_stoploss_order_gateio --- tests/exchange/test_gateio.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/exchange/test_gateio.py b/tests/exchange/test_gateio.py index 6f7862909..a3ffcad65 100644 --- a/tests/exchange/test_gateio.py +++ b/tests/exchange/test_gateio.py @@ -1,8 +1,11 @@ +from unittest.mock import MagicMock + import pytest from freqtrade.exceptions import OperationalException from freqtrade.exchange import Gateio from freqtrade.resolvers.exchange_resolver import ExchangeResolver +from tests.conftest import get_patched_exchange def test_validate_order_types_gateio(default_conf, mocker): @@ -26,3 +29,29 @@ def test_validate_order_types_gateio(default_conf, mocker): with pytest.raises(OperationalException, match=r'Exchange .* does not support market orders.'): ExchangeResolver.load_exchange('gateio', default_conf, True) + + +def test_fetch_stoploss_order_gateio(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + + fetch_order_mock = MagicMock() + exchange.fetch_order = fetch_order_mock + + exchange.fetch_stoploss_order('1234', 'ETH/BTC') + assert fetch_order_mock.call_count == 1 + assert fetch_order_mock.call_args_list[0][1]['order_id'] == '1234' + assert fetch_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC' + assert fetch_order_mock.call_args_list[0][1]['params'] == {'stop': True} + + +def test_cancel_stoploss_order_gateio(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + + cancel_order_mock = MagicMock() + exchange.cancel_order = cancel_order_mock + + exchange.cancel_stoploss_order('1234', 'ETH/BTC') + assert cancel_order_mock.call_count == 1 + assert cancel_order_mock.call_args_list[0][1]['order_id'] == '1234' + assert cancel_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC' + assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True} From d47274066ecdf09e5364773efd32853e2a763d7c Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 01:05:21 -0600 Subject: [PATCH 04/12] Added stoploss_on_exchange flag to gateio --- freqtrade/exchange/gateio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index 7580281cf..9f857c344 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -22,6 +22,7 @@ class Gateio(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, "ohlcv_volume_currency": "quote", + "stoploss_on_exchange": True, } _headers = {'X-Gate-Channel-Id': 'freqtrade'} From 6bb93bdc25d3a00c97aca3eb2ab6a8355f1ad268 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 15:47:16 -0600 Subject: [PATCH 05/12] moved binance.stoploss_adjust to exchange class --- freqtrade/exchange/binance.py | 7 ------- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 12 ++++++++++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 37ead6dd8..ecdd002b2 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,13 +23,6 @@ class Binance(Exchange): "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], } - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: - """ - Verify stop_loss against stoploss-order value (limit or price) - Returns True if adjustment is necessary. - """ - return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) - async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, is_new_pair: bool = False, raise_: bool = False diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 81d20fd21..8db261f63 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -795,7 +795,7 @@ class Exchange: Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - raise OperationalException(f"stoploss is not implemented for {self.name}.") + return stop_loss > float(order['stopPrice']) def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: params = self._params.copy() diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 527e8050b..1b33e836b 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3058,3 +3058,15 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r ]) def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected + + +@pytest.mark.parametrize('exchange_name', ['binance', 'gateio']) +def test_stoploss_adjust(mocker, default_conf, exchange_name): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + order = { + 'type': 'stop_loss_limit', + 'price': 1500, + 'stopPrice': 1500, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) From 7db28b1b1697b937af0106668250670a1c22ca94 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 15:54:17 -0600 Subject: [PATCH 06/12] gateio stoploss docs --- docs/exchanges.md | 3 +++ docs/stoploss.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 8adf19081..df11a5971 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -210,6 +210,9 @@ OKX requires a passphrase for each api key, you will therefore need to add this ## Gate.io +!!! Tip "Stoploss on Exchange" + Binance supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.. + Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0). The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value. diff --git a/docs/stoploss.md b/docs/stoploss.md index d0e106d8f..62081b540 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -24,7 +24,7 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) and kucoin (stop-limit and stop-market) as of now. + Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now. Do not set too low/tight stoploss value if using stop loss on exchange! If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. From 6f4d6079023fc75987f7c08087ea4d9e16020387 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 19:31:51 -0600 Subject: [PATCH 07/12] stoploss_adjust fixed breaking tests --- tests/exchange/test_binance.py | 14 -------------- tests/exchange/test_exchange.py | 3 --- tests/test_freqtradebot.py | 4 +--- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index d88ae9b1d..bf9340217 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -94,20 +94,6 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): assert order['amount'] == 1 -def test_stoploss_adjust_binance(mocker, default_conf): - exchange = get_patched_exchange(mocker, default_conf, id='binance') - order = { - 'type': 'stop_loss_limit', - 'price': 1500, - 'info': {'stopPrice': 1500}, - } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) - # Test with invalid order case - order['type'] = 'stop_loss' - assert not exchange.stoploss_adjust(1501, order) - - @pytest.mark.asyncio async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog): ohlcv = [ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 1b33e836b..fa9020ba4 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2698,9 +2698,6 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) - with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss_adjust(1, {}) - def test_merge_ft_has_dict(default_conf, mocker): mocker.patch.multiple('freqtrade.exchange.Exchange', diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e5f8d3694..492e55715 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1563,9 +1563,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, 'type': 'stop_loss_limit', 'price': 3, 'average': 2, - 'info': { - 'stopPrice': '2.178' - } + 'stopPrice': '2.178' }) mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging) From f343036e6695be138d07be3ca8e59c507379b8c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Mar 2022 19:23:20 +0100 Subject: [PATCH 08/12] Add stoploss-ordertypes mapping for gateio --- freqtrade/exchange/gateio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index 9f857c344..ca57f85b3 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -22,6 +22,7 @@ class Gateio(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, "ohlcv_volume_currency": "quote", + "stoploss_order_types": {"limit": "limit"}, "stoploss_on_exchange": True, } From 2ba79a32a02abb5a08b3fa3c18a296e6b3cb43ae Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 12 Mar 2022 19:42:40 -0600 Subject: [PATCH 09/12] Update docs/exchanges.md Co-authored-by: Matthias --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index df11a5971..c2368170d 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -211,7 +211,7 @@ OKX requires a passphrase for each api key, you will therefore need to add this ## Gate.io !!! Tip "Stoploss on Exchange" - Binance supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.. + Gate.io supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.. Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0). The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value. From 7e7e5963724e56ed052cb607df2cf4c145867eb0 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 12 Mar 2022 20:07:50 -0600 Subject: [PATCH 10/12] Revert "moved binance.stoploss_adjust to exchange class" This reverts commit 6bb93bdc25d3a00c97aca3eb2ab6a8355f1ad268. --- freqtrade/exchange/binance.py | 7 +++++++ freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 12 ------------ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index ecdd002b2..37ead6dd8 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,6 +23,13 @@ class Binance(Exchange): "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], } + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) + async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, is_new_pair: bool = False, raise_: bool = False diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 211531c34..a502ad034 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -795,7 +795,7 @@ class Exchange: Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - return stop_loss > float(order['stopPrice']) + raise OperationalException(f"stoploss is not implemented for {self.name}.") def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: params = self._params.copy() diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index db4d66fa5..b4b19a6d4 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3062,15 +3062,3 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r ]) def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected - - -@pytest.mark.parametrize('exchange_name', ['binance', 'gateio']) -def test_stoploss_adjust(mocker, default_conf, exchange_name): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - order = { - 'type': 'stop_loss_limit', - 'price': 1500, - 'stopPrice': 1500, - } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) From 91549d3254a481bc187865b4d48d4406cc68013b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 12 Mar 2022 20:07:56 -0600 Subject: [PATCH 11/12] Revert "stoploss_adjust fixed breaking tests" This reverts commit 6f4d6079023fc75987f7c08087ea4d9e16020387. --- tests/exchange/test_binance.py | 14 ++++++++++++++ tests/exchange/test_exchange.py | 3 +++ tests/test_freqtradebot.py | 4 +++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index bf9340217..d88ae9b1d 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -94,6 +94,20 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): assert order['amount'] == 1 +def test_stoploss_adjust_binance(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='binance') + order = { + 'type': 'stop_loss_limit', + 'price': 1500, + 'info': {'stopPrice': 1500}, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case + order['type'] = 'stop_loss' + assert not exchange.stoploss_adjust(1501, order) + + @pytest.mark.asyncio async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog): ohlcv = [ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b4b19a6d4..ff8383997 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2705,6 +2705,9 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): + exchange.stoploss_adjust(1, {}) + def test_merge_ft_has_dict(default_conf, mocker): mocker.patch.multiple('freqtrade.exchange.Exchange', diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index caa9a824f..1aeb56cdd 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1588,7 +1588,9 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, 'type': 'stop_loss_limit', 'price': 3, 'average': 2, - 'stopPrice': '2.178' + 'info': { + 'stopPrice': '2.178' + } }) mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging) From 843606c9cbd4563beba7fbf405f03ddc012508c1 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 12 Mar 2022 20:14:23 -0600 Subject: [PATCH 12/12] gateio stoploss adjust --- freqtrade/exchange/gateio.py | 7 +++++++ tests/exchange/test_gateio.py | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index ca57f85b3..bfe996e86 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -48,3 +48,10 @@ class Gateio(Exchange): pair=pair, params={'stop': True} ) + + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return stop_loss > float(order['stopPrice']) diff --git a/tests/exchange/test_gateio.py b/tests/exchange/test_gateio.py index a3ffcad65..ce356be8c 100644 --- a/tests/exchange/test_gateio.py +++ b/tests/exchange/test_gateio.py @@ -55,3 +55,13 @@ def test_cancel_stoploss_order_gateio(default_conf, mocker): assert cancel_order_mock.call_args_list[0][1]['order_id'] == '1234' assert cancel_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC' assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True} + + +def test_stoploss_adjust_gateio(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + order = { + 'price': 1500, + 'stopPrice': 1500, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order)