From 61182f849bd252b5b38a91c48fe29b25e7f01ebb Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 00:45:10 -0600 Subject: [PATCH 01/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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 7825d855cd10e589103afd614199c042e85fc3a1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 11 Mar 2022 19:28:15 +0100 Subject: [PATCH 08/39] Fix flake8 error in tests --- tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 2bd84942d..ff8383997 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1695,7 +1695,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: exchange._api_async.fetch_ohlcv.reset_mock() caplog.clear() # Call with invalid timeframe - res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')],cache=False) + res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')], cache=False) assert not res assert len(res) == 0 assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog) From f343036e6695be138d07be3ca8e59c507379b8c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Mar 2022 19:23:20 +0100 Subject: [PATCH 09/39] 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 10/39] 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 11/39] 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 12/39] 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 13/39] 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) From 9107819c9518a9075e8feeab43b896171ead0bfe Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 14:42:15 +0100 Subject: [PATCH 14/39] Fix order migration "forgetting" average --- freqtrade/persistence/migrations.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index ef64a2b27..2da24b748 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -174,16 +174,17 @@ def drop_orders_table(engine, table_back_name: str): def migrate_orders_table(engine, table_back_name: str, cols_order: List): ft_fee_base = get_column_def(cols_order, 'ft_fee_base', 'null') + average = get_column_def(cols_order, 'average', 'null') # let SQLAlchemy create the schema as required with engine.begin() as connection: connection.execute(text(f""" insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, - status, symbol, order_type, side, price, amount, filled, average, remaining, cost, - order_date, order_filled_date, order_update_date, ft_fee_base) + status, symbol, order_type, side, price, amount, filled, average, remaining, + cost, order_date, order_filled_date, order_update_date, ft_fee_base) select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, - status, symbol, order_type, side, price, amount, filled, null average, remaining, cost, - order_date, order_filled_date, order_update_date, {ft_fee_base} + status, symbol, order_type, side, price, amount, filled, {average} average, remaining, + cost, order_date, order_filled_date, order_update_date, {ft_fee_base} ft_fee_base from {table_back_name} """)) From b8b56d95f39164256f61f7aee5bb146816e5dfc9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 14:57:32 +0100 Subject: [PATCH 15/39] Update missleading docstring --- freqtrade/plugins/pairlist/VolatilityFilter.py | 2 +- freqtrade/plugins/pairlist/rangestabilityfilter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index 20b899c5f..8a7eeeca8 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -90,7 +90,7 @@ class VolatilityFilter(IPairList): """ Validate trading range :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 314056fbb..e17ec2dab 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -88,7 +88,7 @@ class RangeStabilityFilter(IPairList): """ Validate trading range :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache From 7146122f4adc97b4534428300a5e34c3b025879e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 15:06:32 +0100 Subject: [PATCH 16/39] Update docstring --- freqtrade/plugins/pairlist/AgeFilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py index 5627d82ce..a6d5ec79b 100644 --- a/freqtrade/plugins/pairlist/AgeFilter.py +++ b/freqtrade/plugins/pairlist/AgeFilter.py @@ -98,7 +98,7 @@ class AgeFilter(IPairList): """ Validate age for the ticker :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache From 3133be19e33077968cd069344f4e860b10132091 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 15:23:01 +0100 Subject: [PATCH 17/39] Update precisionfilter to use last instead of ask or bid. --- freqtrade/plugins/pairlist/PrecisionFilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py index a3c262e8c..521f38635 100644 --- a/freqtrade/plugins/pairlist/PrecisionFilter.py +++ b/freqtrade/plugins/pairlist/PrecisionFilter.py @@ -51,7 +51,7 @@ class PrecisionFilter(IPairList): :param ticker: ticker dict as returned from ccxt.fetch_tickers() :return: True if the pair can stay, false if it should be removed """ - stop_price = ticker['ask'] * self._stoploss + stop_price = ticker['last'] * self._stoploss # Adjust stop-prices to precision sp = self._exchange.price_to_precision(pair, stop_price) From 32c06f4a05c4c804f98be8a923f8fda7f53f13b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 16:45:11 +0100 Subject: [PATCH 18/39] Improve test --- tests/test_periodiccache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_periodiccache.py b/tests/test_periodiccache.py index f874f9041..b2bd8ba2b 100644 --- a/tests/test_periodiccache.py +++ b/tests/test_periodiccache.py @@ -26,7 +26,9 @@ def test_ttl_cache(): assert 'a' in cache1h t.move_to("2021-09-01 05:59:59 +00:00") + assert 'a' not in cache assert 'a' in cache1h t.move_to("2021-09-01 06:00:00 +00:00") + assert 'a' not in cache assert 'a' not in cache1h From c63b5fbbbf22807526da4eb34411287eeb415be9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 17:53:52 +0100 Subject: [PATCH 19/39] Use last to get rates for /balance endpoints --- freqtrade/rpc/rpc.py | 2 +- tests/rpc/test_rpc.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 7a602978e..3d4fffbc9 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -582,7 +582,7 @@ class RPC: else: try: pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) - rate = tickers.get(pair, {}).get('bid', None) + rate = tickers.get(pair, {}).get('last', None) if rate: if pair.startswith(stake_currency) and not pair.endswith(stake_currency): rate = 1.0 / rate diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 6bfee8e86..d738760be 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -605,8 +605,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) - assert prec_satoshi(result['total'], 12.309096315) - assert prec_satoshi(result['value'], 184636.44472997) + assert prec_satoshi(result['total'], 12.30909624) + assert prec_satoshi(result['value'], 184636.443606915) assert tickers.call_count == 1 assert tickers.call_args_list[0][1]['cached'] is True assert 'USD' == result['symbol'] @@ -624,17 +624,16 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): 'est_stake': 0.30794, 'used': 4.0, 'stake': 'BTC', - }, {'free': 5.0, 'balance': 10.0, 'currency': 'USDT', - 'est_stake': 0.0011563153318162476, + 'est_stake': 0.0011562404610161968, 'used': 5.0, 'stake': 'BTC', } ] - assert result['total'] == 12.309096315331816 + assert result['total'] == 12.309096240461017 def test_rpc_start(mocker, default_conf) -> None: From be5b0acfbda471a966b967cea1a6c6c213641cbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:00 +0000 Subject: [PATCH 20/39] Bump pytest from 7.0.1 to 7.1.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.0.1 to 7.1.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.0.1...7.1.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5c94ac7c8..1d393538b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 mypy==0.931 -pytest==7.0.1 +pytest==7.1.0 pytest-asyncio==0.18.2 pytest-cov==3.0.0 pytest-mock==3.7.0 From 7764ad1541fcc81e40ab3b94ad33ca4a0612ea6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:03 +0000 Subject: [PATCH 21/39] Bump types-cachetools from 4.2.10 to 5.0.0 Bumps [types-cachetools](https://github.com/python/typeshed) from 4.2.10 to 5.0.0. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5c94ac7c8..0f9d4d105 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,7 +20,7 @@ time-machine==2.6.0 nbconvert==6.4.2 # mypy types -types-cachetools==4.2.10 +types-cachetools==5.0.0 types-filelock==3.2.5 types-requests==2.27.11 types-tabulate==0.8.5 From 3a0ad2f26e2b96a2210f4da44ce08e767c4054cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:07 +0000 Subject: [PATCH 22/39] Bump uvicorn from 0.17.5 to 0.17.6 Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.17.5 to 0.17.6. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.17.5...0.17.6) --- updated-dependencies: - dependency-name: uvicorn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f8b6049f1..095735180 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ sdnotify==0.3.2 # API Server fastapi==0.75.0 -uvicorn==0.17.5 +uvicorn==0.17.6 pyjwt==2.3.0 aiofiles==0.8.0 psutil==5.9.0 From 3fc1c94aba6bdb7045153d793aab9b1427c73c8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:14 +0000 Subject: [PATCH 23/39] Bump ccxt from 1.75.12 to 1.76.5 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.75.12 to 1.76.5. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.75.12...1.76.5) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f8b6049f1..245fa63d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.2 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.75.12 +ccxt==1.76.5 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From 4cbdc9a74ffe6cc39002b03739335f4a4d58cbb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:17 +0000 Subject: [PATCH 24/39] Bump types-requests from 2.27.11 to 2.27.12 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.11 to 2.27.12. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5c94ac7c8..beae48283 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ nbconvert==6.4.2 # mypy types types-cachetools==4.2.10 types-filelock==3.2.5 -types-requests==2.27.11 +types-requests==2.27.12 types-tabulate==0.8.5 # Extensions to datetime library From a7133f1974bf3f873eef7eb441ea1c924a85b10b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:20 +0000 Subject: [PATCH 25/39] Bump nbconvert from 6.4.2 to 6.4.4 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 6.4.2 to 6.4.4. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Commits](https://github.com/jupyter/nbconvert/compare/6.4.2...6.4.4) --- updated-dependencies: - dependency-name: nbconvert dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5c94ac7c8..26c873b1f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -17,7 +17,7 @@ isort==5.10.1 time-machine==2.6.0 # Convert jupyter notebooks to markdown documents -nbconvert==6.4.2 +nbconvert==6.4.4 # mypy types types-cachetools==4.2.10 From 3fbe4a9944369ad9b1e829838709ac53bf5a6914 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:30 +0000 Subject: [PATCH 26/39] Bump numpy from 1.22.2 to 1.22.3 Bumps [numpy](https://github.com/numpy/numpy) from 1.22.2 to 1.22.3. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/HOWTO_RELEASE.rst.txt) - [Commits](https://github.com/numpy/numpy/compare/v1.22.2...v1.22.3) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f8b6049f1..b9c6d3d06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.22.2 +numpy==1.22.3 pandas==1.4.1 pandas-ta==0.3.14b From 404d700a74b919cfefb467bae9b1910d5a0f056a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Mar 2022 06:23:48 +0100 Subject: [PATCH 27/39] Raise min-requirement for ccxt --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ec41228c1..a89e717a1 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=1.74.17', + 'ccxt>=1.76.5', 'SQLAlchemy', 'python-telegram-bot>=13.4', 'arrow>=0.17.0', From 5462ff0ebc9f6ad09af17db0bc46d6be3bfd23f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 05:25:16 +0000 Subject: [PATCH 28/39] Bump mypy from 0.931 to 0.940 Bumps [mypy](https://github.com/python/mypy) from 0.931 to 0.940. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.931...v0.940) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0c3622f72..c2f3eae8a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 -mypy==0.931 +mypy==0.940 pytest==7.1.0 pytest-asyncio==0.18.2 pytest-cov==3.0.0 From 18030a30e7d4e081a893eb37ef9501e0a20648d1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Mar 2022 19:21:58 +0100 Subject: [PATCH 29/39] Add exchange parameter to test-pairlist command This will allow for quick tests of the same pairlist config against multiple exchanges. --- docs/utils.md | 15 ++++++++++----- freqtrade/commands/arguments.py | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index c6e795e60..a28a0f456 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -517,20 +517,25 @@ Requires a configuration with specified `pairlists` attribute. Can be used to generate static pairlists to be used during backtesting / hyperopt. ``` -usage: freqtrade test-pairlist [-h] [-c PATH] +usage: freqtrade test-pairlist [-h] [-v] [-c PATH] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] - [-1] [--print-json] + [-1] [--print-json] [--exchange EXCHANGE] optional arguments: -h, --help show this help message and exit + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. --quote QUOTE_CURRENCY [QUOTE_CURRENCY ...] Specify quote currency(-ies). Space-separated list. -1, --one-column Print output in one column. --print-json Print list of pairs or market symbols in JSON format. + --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no + config is provided. + ``` ### Examples diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 201ec09bf..28f7d7148 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -51,7 +51,7 @@ ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one "print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column", - "list_pairs_print_json"] + "list_pairs_print_json", "exchange"] ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] From 6024fa482e1b09bae8e85b3afc9fc58e483c1512 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Mar 2022 07:41:08 +0100 Subject: [PATCH 30/39] Use brackets to break IF lines --- freqtrade/edge/edge_positioning.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 1950f0d08..08f43598d 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -219,9 +219,11 @@ class Edge: """ final = [] for pair, info in self._cached_pairs.items(): - if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ - info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)) and \ - pair in pairs: + if ( + info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) + and info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)) + and pair in pairs + ): final.append(pair) if self._final_pairs != final: @@ -246,8 +248,8 @@ class Edge: """ final = [] for pair, info in self._cached_pairs.items(): - if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ - info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): + if (info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and + info.winrate > float(self.edge_config.get('minimum_winrate', 0.60))): final.append({ 'Pair': pair, 'Winrate': info.winrate, From 96bf82dbc64b824617f75ca8556f2f2d0e4d9446 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Mar 2022 17:06:10 +0100 Subject: [PATCH 31/39] Remove gateio broker program --- freqtrade/exchange/gateio.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index bfe996e86..d0fd787b7 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -26,8 +26,6 @@ class Gateio(Exchange): "stoploss_on_exchange": True, } - _headers = {'X-Gate-Channel-Id': 'freqtrade'} - def validate_ordertypes(self, order_types: Dict) -> None: super().validate_ordertypes(order_types) From f55db8e262f2f64758682a9a5b96abbab6051d0b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Mar 2022 20:21:10 +0100 Subject: [PATCH 32/39] Spreadfilter should fail to start if fetchTickers is not supported --- freqtrade/plugins/pairlist/SpreadFilter.py | 7 +++++++ tests/plugins/test_pairlist.py | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py index ad0c0f0be..d1f88d2a5 100644 --- a/freqtrade/plugins/pairlist/SpreadFilter.py +++ b/freqtrade/plugins/pairlist/SpreadFilter.py @@ -4,6 +4,7 @@ Spread pair list filter import logging from typing import Any, Dict +from freqtrade.exceptions import OperationalException from freqtrade.plugins.pairlist.IPairList import IPairList @@ -20,6 +21,12 @@ class SpreadFilter(IPairList): self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005) self._enabled = self._max_spread_ratio != 0 + if not self._exchange.exchange_has('fetchTickers'): + raise OperationalException( + 'Exchange does not support fetchTickers, therefore SpreadFilter cannot be used.' + 'Please edit your config and restart the bot.' + ) + @property def needstickers(self) -> bool: """ diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 52158a889..346bf1792 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -782,6 +782,18 @@ def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None get_patched_freqtradebot(mocker, default_conf) +def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> None: + default_conf['pairlists'] = [{'method': 'StaticPairList'}, {'method': 'SpreadFilter'}] + + mocker.patch.multiple('freqtrade.exchange.Exchange', + get_tickers=tickers, + exchange_has=MagicMock(return_value=False), + ) + + with pytest.raises(OperationalException, + match=r'Exchange does not support fetchTickers, .*'): + get_patched_freqtradebot(mocker, default_conf) + @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): whitelist_conf['pairlists'][0]['method'] = pairlist From 73fc344eb1d666cbf7f435abc8b5505e9f952798 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 18 Mar 2022 06:38:54 +0100 Subject: [PATCH 33/39] Improve wording in docs --- docs/bot-basics.md | 2 +- tests/plugins/test_pairlist.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 8c6303063..4b5ba3a5b 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -24,7 +24,7 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Fetch open trades from persistence. * Calculate current list of tradable pairs. -* Download ohlcv data for the pairlist including all [informative pairs](strategy-customization.md#get-data-for-non-tradeable-pairs) +* Download OHLCV data for the pairlist including all [informative pairs](strategy-customization.md#get-data-for-non-tradeable-pairs) This step is only executed once per Candle to avoid unnecessary network traffic. * Call `bot_loop_start()` strategy callback. * Analyze strategy per pair. diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 346bf1792..08ba892fe 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -794,6 +794,7 @@ def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> N match=r'Exchange does not support fetchTickers, .*'): get_patched_freqtradebot(mocker, default_conf) + @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): whitelist_conf['pairlists'][0]['method'] = pairlist From 95f69b905aec70c2e13b9def10dae6c0275df8ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:00:53 +0100 Subject: [PATCH 34/39] Remove ticker_interval support --- docs/backtesting.md | 4 ++-- docs/configuration.md | 2 +- docs/edge.md | 2 +- docs/hyperopt.md | 2 +- docs/plotting.md | 4 ++-- docs/strategy-customization.md | 2 +- freqtrade/commands/cli_options.py | 4 ++-- freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/hyperopt_interface.py | 7 ++----- freqtrade/resolvers/hyperopt_resolver.py | 1 - freqtrade/resolvers/strategy_resolver.py | 4 ---- freqtrade/strategy/interface.py | 3 +-- 12 files changed, 14 insertions(+), 23 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index e7846b1f8..11608aad9 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -26,7 +26,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. @@ -63,7 +63,7 @@ optional arguments: `30m`, `1h`, `1d`). --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] Provide a space-separated list of strategies to - backtest. Please note that ticker-interval needs to be + backtest. Please note that timeframe needs to be set either in config or via command line. When using this together with `--export trades`, the strategy- name is injected into the filename (so `backtest- diff --git a/docs/configuration.md b/docs/configuration.md index d702fe8f9..2cb5dfa93 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -86,7 +86,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade).
*Defaults to `false`.*
**Datatype:** Boolean | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade).
*Defaults to `0.5`.*
**Datatype:** Float (as ratio) | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
**Datatype:** Positive Float as ratio. -| `timeframe` | The timeframe (former ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String +| `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
**Datatype:** String | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
**Datatype:** Boolean | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float diff --git a/docs/edge.md b/docs/edge.md index 4402d767f..e92abf40f 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -222,7 +222,7 @@ usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 19d8cd692..c8cb118f7 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -55,7 +55,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. diff --git a/docs/plotting.md b/docs/plotting.md index ccfbb12cb..004dd7821 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -65,7 +65,7 @@ optional arguments: _today.json` --timerange TIMERANGE Specify what timerange of data to use. - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --no-trades Skip using trades from backtesting file and DB. @@ -330,7 +330,7 @@ optional arguments: --trade-source {DB,file} Specify the source for trades (Can be DB or file (backtest file)) Default: file - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --auto-open Automatically open generated plot. diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index e6eff2416..045a55c5b 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -325,7 +325,7 @@ stoploss = -0.10 For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md). -### Timeframe (formerly ticker interval) +### Timeframe This is the set of candles the bot should download and use for the analysis. Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work. diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 11fcc6b81..f30c25ba1 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -117,7 +117,7 @@ AVAILABLE_CLI_OPTIONS = { ), # Optimize common "timeframe": Arg( - '-i', '--timeframe', '--ticker-interval', + '-i', '--timeframe', help='Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).', ), "timerange": Arg( @@ -169,7 +169,7 @@ AVAILABLE_CLI_OPTIONS = { "strategy_list": Arg( '--strategy-list', help='Provide a space-separated list of strategies to backtest. ' - 'Please note that ticker-interval needs to be set either in config ' + 'Please note that timeframe needs to be set either in config ' 'or via command line. When using this together with `--export trades`, ' 'the strategy-name is injected into the filename ' '(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`', diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index eca643732..76f0e68f4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -87,7 +87,7 @@ class Backtesting: validate_config_consistency(self.config) if "timeframe" not in self.config: - raise OperationalException("Timeframe (ticker interval) needs to be set in either " + raise OperationalException("Timeframe needs to be set in either " "configuration or as cli argument `--timeframe 5m`") self.timeframe = str(self.config.get('timeframe')) self.timeframe_min = timeframe_to_minutes(self.timeframe) diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 01ffd7844..b1c68caca 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -29,15 +29,13 @@ class IHyperOpt(ABC): Class attributes you can use: timeframe -> int: value of the timeframe to use for the strategy """ - ticker_interval: str # DEPRECATED timeframe: str strategy: IStrategy def __init__(self, config: dict) -> None: self.config = config - # Assign ticker_interval to be used in hyperopt - IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED + # Assign timeframe to be used in hyperopt IHyperOpt.timeframe = str(config['timeframe']) def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType: @@ -192,7 +190,7 @@ class IHyperOpt(ABC): Categorical([True, False], name='trailing_only_offset_is_reached'), ] - # This is needed for proper unpickling the class attribute ticker_interval + # This is needed for proper unpickling the class attribute timeframe # which is set to the actual value by the resolver. # Why do I still need such shamanic mantras in modern python? def __getstate__(self): @@ -202,5 +200,4 @@ class IHyperOpt(ABC): def __setstate__(self, state): self.__dict__.update(state) - IHyperOpt.ticker_interval = state['timeframe'] IHyperOpt.timeframe = state['timeframe'] diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 6f0263e93..e3c234f60 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -44,7 +44,6 @@ class HyperOptLossResolver(IResolver): extra_dir=config.get('hyperopt_path')) # Assign timeframe to be used in hyperopt - hyperoptloss.__class__.ticker_interval = str(config['timeframe']) hyperoptloss.__class__.timeframe = str(config['timeframe']) return hyperoptloss diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index e9fcc3496..48cbd03cf 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -145,10 +145,6 @@ class StrategyResolver(IResolver): """ Normalize attributes to have the correct type. """ - # Assign deprecated variable - to not break users code relying on this. - if hasattr(strategy, 'timeframe'): - strategy.ticker_interval = strategy.timeframe - # Sort and apply type conversions if hasattr(strategy, 'minimal_roi'): strategy.minimal_roi = dict(sorted( diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 2f3657059..1eea84676 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -55,7 +55,7 @@ class IStrategy(ABC, HyperStrategyMixin): Attributes you can use: minimal_roi -> Dict: Minimal ROI designed for the strategy stoploss -> float: optimal stoploss designed for the strategy - timeframe -> str: value of the timeframe (ticker interval) to use with the strategy + timeframe -> str: value of the timeframe to use with the strategy """ # Strategy interface version # Default to version 2 @@ -81,7 +81,6 @@ class IStrategy(ABC, HyperStrategyMixin): use_custom_stoploss: bool = False # associated timeframe - ticker_interval: str # DEPRECATED timeframe: str # Optional order types From eb08b921802a6db6c2faa53df36e501a4a64e4af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:01:18 +0100 Subject: [PATCH 35/39] Raise exception when ticker_interval is set. --- freqtrade/configuration/deprecated_settings.py | 11 +++-------- tests/optimize/test_backtesting.py | 9 ++++----- tests/optimize/test_edge_cli.py | 4 +--- tests/optimize/test_hyperopt.py | 1 - tests/strategy/test_strategy_loading.py | 2 -- tests/test_arguments.py | 6 +++--- tests/test_configuration.py | 16 ++++------------ 7 files changed, 15 insertions(+), 34 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 5efe26bd2..cafa8957b 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -100,16 +100,11 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: "from the edge configuration." ) if 'ticker_interval' in config: - logger.warning( - "DEPRECATED: " + + raise OperationalException( + "DEPRECATED: 'ticker_interval' detected. " "Please use 'timeframe' instead of 'ticker_interval." ) - if 'timeframe' in config: - raise OperationalException( - "Both 'timeframe' and 'ticker_interval' detected." - "Please remove 'ticker_interval' from your configuration to continue operating." - ) - config['timeframe'] = config['ticker_interval'] if 'protections' in config: logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.") diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index a8998eb63..7a72747c0 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -314,16 +314,15 @@ def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: patch_exchange(mocker) del default_conf['timeframe'] default_conf['strategy_list'] = ['StrategyTestV2', - 'SampleStrategy'] + 'HyperoptableStrategy'] mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) - with pytest.raises(OperationalException): + with pytest.raises(OperationalException, + match=r"Timeframe needs to be set in either configuration"): Backtesting(default_conf) - log_has("Ticker-interval needs to be set in either configuration " - "or as cli argument `--ticker-interval 5m`", caplog) -def test_data_with_fee(default_conf, mocker, testdatadir) -> None: +def test_data_with_fee(default_conf, mocker) -> None: patch_exchange(mocker) default_conf['fee'] = 0.1234 diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 18d5f1c76..466a5f1cd 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -6,8 +6,7 @@ from unittest.mock import MagicMock from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge from freqtrade.enums import RunMode from freqtrade.optimize.edge_cli import EdgeCli -from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, - patched_configuration_load_config_file) +from tests.conftest import get_args, log_has, patch_exchange, patched_configuration_load_config_file def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None: @@ -30,7 +29,6 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'timeframe' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'timerange' not in config assert 'stoploss_range' not in config diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 2328585dd..cc551277a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -63,7 +63,6 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'timeframe' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'position_stacking' not in config assert not log_has('Parameter --enable-position-stacking detected ...', caplog) diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 3590c3e01..3fe14a8de 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -111,7 +111,6 @@ def test_strategy(result, default_conf): assert default_conf['stoploss'] == -0.10 assert strategy.timeframe == '5m' - assert strategy.ticker_interval == '5m' assert default_conf['timeframe'] == '5m' df_indicators = strategy.advise_indicators(result, metadata=metadata) @@ -376,7 +375,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert strategy._sell_fun_len == 2 assert strategy.INTERFACE_VERSION == 1 assert strategy.timeframe == '5m' - assert strategy.ticker_interval == '5m' indicator_df = strategy.advise_indicators(result, metadata=metadata) assert isinstance(indicator_df, DataFrame) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index fca5c6ab9..ba9d154e5 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -111,17 +111,17 @@ def test_parse_args_strategy_path_invalid() -> None: def test_parse_args_backtesting_invalid() -> None: with pytest.raises(SystemExit, match=r'2'): - Arguments(['backtesting --ticker-interval']).get_parsed_arg() + Arguments(['backtesting --timeframe']).get_parsed_arg() with pytest.raises(SystemExit, match=r'2'): - Arguments(['backtesting --ticker-interval', 'abc']).get_parsed_arg() + Arguments(['backtesting --timeframe', 'abc']).get_parsed_arg() def test_parse_args_backtesting_custom() -> None: args = [ 'backtesting', '-c', 'test_conf.json', - '--ticker-interval', '1m', + '--timeframe', '1m', '--strategy-list', 'StrategyTestV2', 'SampleStrategy' diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 0a6935649..1cd9b0ff7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -443,7 +443,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non '--strategy', 'StrategyTestV2', '--datadir', '/foo/bar', '--userdir', "/tmp/freqtrade", - '--ticker-interval', '1m', + '--timeframe', '1m', '--enable-position-stacking', '--disable-max-market-positions', '--timerange', ':100', @@ -494,7 +494,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non arglist = [ 'backtesting', '--config', 'config.json', - '--ticker-interval', '1m', + '--timeframe', '1m', '--export', 'trades', '--strategy-list', 'StrategyTestV2', @@ -1320,22 +1320,14 @@ def test_process_removed_setting(mocker, default_conf, caplog): def test_process_deprecated_ticker_interval(default_conf, caplog): message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval." config = deepcopy(default_conf) + process_temporary_deprecated_settings(config) assert not log_has(message, caplog) del config['timeframe'] config['ticker_interval'] = '15m' - process_temporary_deprecated_settings(config) - assert log_has(message, caplog) - assert config['ticker_interval'] == '15m' - - config = deepcopy(default_conf) - # Have both timeframe and ticker interval in config - # Can also happen when using ticker_interval in configuration, and --timeframe as cli argument - config['timeframe'] = '5m' - config['ticker_interval'] = '4h' with pytest.raises(OperationalException, - match=r"Both 'timeframe' and 'ticker_interval' detected."): + match=r"DEPRECATED: 'ticker_interval' detected. Please use.*"): process_temporary_deprecated_settings(config) From 0f76b2373380ff30772ffaa07d940cfc9d71c6df Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:03:43 +0100 Subject: [PATCH 36/39] update deprecation message for ticker_interval --- docs/deprecated.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/deprecated.md b/docs/deprecated.md index eaf85bfbf..87c8a2b38 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -24,6 +24,10 @@ Please refer to [pairlists](plugins.md#pairlists-and-pairlist-handlers) instead. Did only download the latest 500 candles, so was ineffective in getting good backtest data. Removed in 2019-7-dev (develop branch) and in freqtrade 2019.8. +### `ticker_interval` (now `timeframe`) + +Support for `ticker_interval` terminology was deprecated in 2020.6 in favor of `timeframe` - and compatibility code was removed in 2022.3. + ### Allow running multiple pairlists in sequence The former `"pairlist"` section in the configuration has been removed, and is replaced by `"pairlists"` - being a list to specify a sequence of pairlists. From aceaa3faec80bba3ec7de35bcc50e0aee6cff58e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:30:54 +0100 Subject: [PATCH 37/39] remove last ticker_interval compatibility shim --- freqtrade/resolvers/strategy_resolver.py | 8 -------- tests/strategy/strats/legacy_strategy_v1.py | 4 +--- tests/strategy/test_strategy_loading.py | 3 --- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 48cbd03cf..8ad7cdb59 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -45,14 +45,6 @@ class StrategyResolver(IResolver): strategy_name, config=config, extra_dir=config.get('strategy_path')) - if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'): - # Assign ticker_interval to timeframe to keep compatibility - if 'timeframe' not in config: - logger.warning( - "DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'." - ) - strategy.timeframe = strategy.ticker_interval - if strategy._ft_params_from_file: # Set parameters from Hyperopt results file params = strategy._ft_params_from_file diff --git a/tests/strategy/strats/legacy_strategy_v1.py b/tests/strategy/strats/legacy_strategy_v1.py index ebfce632b..d7ed2014b 100644 --- a/tests/strategy/strats/legacy_strategy_v1.py +++ b/tests/strategy/strats/legacy_strategy_v1.py @@ -31,9 +31,7 @@ class TestStrategyLegacyV1(IStrategy): # This attribute will be overridden if the config file contains "stoploss" stoploss = -0.10 - # Optimal timeframe for the strategy - # Keep the legacy value here to test compatibility - ticker_interval = '5m' + timeframe = '5m' def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 3fe14a8de..cc924d1c2 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -388,9 +388,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert isinstance(selldf, DataFrame) assert 'sell' in selldf - assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.", - caplog) - def test_strategy_interface_versioning(result, monkeypatch, default_conf): default_conf.update({'strategy': 'StrategyTestV2'}) From 8556e6a0534e0e1e1ab1aeee53e99f23c078fb03 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:30:11 +0100 Subject: [PATCH 38/39] Automatically assign buy-tag to force-buys closes #6544 --- docs/telegram-usage.md | 1 + freqtrade/rpc/api_server/api_v1.py | 2 +- freqtrade/rpc/rpc.py | 2 +- tests/rpc/test_rpc.py | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index c7f9c58f6..232885ed2 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -277,6 +277,7 @@ Starting capital is either taken from the `available_capital` setting, or calcul > **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) Omitting the pair will open a query asking for the pair to buy (based on the current whitelist). +Trades crated through `/forcebuy` will have the buy-tag of `forceentry`. ![Telegram force-buy screenshot](assets/telegram_forcebuy.png) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 6379150ee..5a34385da 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -137,7 +137,7 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None stake_amount = payload.stakeamount if payload.stakeamount else None - entry_tag = payload.entry_tag if payload.entry_tag else None + entry_tag = payload.entry_tag if payload.entry_tag else 'forceentry' trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, entry_tag) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3d4fffbc9..7fd419a5b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -713,7 +713,7 @@ class RPC: def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None, stake_amount: Optional[float] = None, - buy_tag: Optional[str] = None) -> Optional[Trade]: + buy_tag: Optional[str] = 'forceentry') -> Optional[Trade]: """ Handler for forcebuy Buys a pair trade at the given or current price diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d738760be..003b43ad2 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1147,6 +1147,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> pair = 'LTC/BTC' trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05) assert trade.stake_amount == 0.05 + assert trade.buy_tag == 'forceentry' # Test not buying pair = 'XRP/BTC' From fcec071a0804574dc9e03d8a45ebf8dcc8fa665b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:42:34 +0100 Subject: [PATCH 39/39] Use order date to fetch trades using the trade open-date may fail in case of several trade-entries spread over a longer timeperiod. closes #6551 --- freqtrade/freqtradebot.py | 15 ++++++++------- tests/test_freqtradebot.py | 31 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 16864f814..1c2b7208f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1428,14 +1428,14 @@ class FreqtradeBot(LoggingMixin): def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None: # Try update amount (binance-fix) try: - new_amount = self.get_real_amount(trade, order) + new_amount = self.get_real_amount(trade, order, order_obj) if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, abs_tol=constants.MATH_CLOSE_PREC): order_obj.ft_fee_base = trade.amount - new_amount except DependencyException as exception: logger.warning("Could not update trade amount: %s", exception) - def get_real_amount(self, trade: Trade, order: Dict) -> float: + def get_real_amount(self, trade: Trade, order: Dict, order_obj: Order) -> float: """ Detect and update trade fee. Calls trade.update_fee() upon correct detection. @@ -1453,7 +1453,7 @@ class FreqtradeBot(LoggingMixin): # use fee from order-dict if possible if self.exchange.order_has_fee(order): fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order) - logger.info(f"Fee for Trade {trade} [{order.get('side')}]: " + logger.info(f"Fee for Trade {trade} [{order_obj.ft_order_side}]: " f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}") if fee_rate is None or fee_rate < 0.02: # Reject all fees that report as > 2%. @@ -1465,17 +1465,18 @@ class FreqtradeBot(LoggingMixin): return self.apply_fee_conditional(trade, trade_base_currency, amount=order_amount, fee_abs=fee_cost) return order_amount - return self.fee_detection_from_trades(trade, order, order_amount, order.get('trades', [])) + return self.fee_detection_from_trades( + trade, order, order_obj, order_amount, order.get('trades', [])) - def fee_detection_from_trades(self, trade: Trade, order: Dict, order_amount: float, - trades: List) -> float: + def fee_detection_from_trades(self, trade: Trade, order: Dict, order_obj: Order, + order_amount: float, trades: List) -> float: """ fee-detection fallback to Trades. Either uses provided trades list or the result of fetch_my_trades to get correct fee. """ if not trades: trades = self.exchange.get_trades_for_order( - self.exchange.get_order_id_conditional(order), trade.pair, trade.open_date) + self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date) if len(trades) == 0: logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 1aeb56cdd..bafed8488 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3568,9 +3568,9 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe open_order_id="123456" ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is reduced by "fee" - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount - (amount * 0.001) assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).', caplog) @@ -3594,8 +3594,9 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) walletmock.reset_mock() + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is kept as is - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount assert walletmock.call_count == 1 assert log_has_re(r'Fee amount for Trade.* was in base currency ' '- Eating Fee 0.008 into dust', caplog) @@ -3616,8 +3617,9 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is reduced by "fee" - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found', caplog) @@ -3668,7 +3670,8 @@ def test_get_real_amount( mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError) caplog.clear() - assert freqtrade.get_real_amount(trade, buy_order) == amount - fee_reduction_amount + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, buy_order, order_obj) == amount - fee_reduction_amount if expected_log: assert log_has(expected_log, caplog) @@ -3715,7 +3718,8 @@ def test_get_real_amount_multi( # Amount is reduced by "fee" expected_amount = amount - (amount * fee_reduction_amount) - assert freqtrade.get_real_amount(trade, buy_order_fee) == expected_amount + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == expected_amount assert log_has( ( 'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' @@ -3750,8 +3754,9 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_ ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount does not change - assert freqtrade.get_real_amount(trade, limit_buy_order_usdt) == amount + assert freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) == amount def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee, @@ -3773,7 +3778,8 @@ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_dou # Amount does not change assert trade.fee_open == 0.0025 - assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee) == 30.0 + order_obj = Order.parse_from_ccxt_object(market_buy_order_usdt_doublefee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee, order_obj) == 30.0 assert tfo_mock.call_count == 0 # Fetch fees from trades dict if available to get "proper" values assert round(trade.fee_open, 4) == 0.001 @@ -3797,9 +3803,10 @@ def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_o ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount does not change with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"): - freqtrade.get_real_amount(trade, limit_buy_order_usdt) + freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_order, buy_order_fee, @@ -3821,9 +3828,10 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount changes by fee amount. assert isclose( - freqtrade.get_real_amount(trade, limit_buy_order_usdt), + freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj), amount - (amount * 0.001), abs_tol=MATH_CLOSE_PREC, ) @@ -3847,7 +3855,8 @@ def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker): 'side': 'buy', } freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - assert freqtrade.get_real_amount(trade, order) == amount + order_obj = Order.parse_from_ccxt_object(order, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, order, order_obj) == amount @pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [