From 95b35e452d9a2043e2b82742adc389afc1bae546 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Apr 2023 17:13:02 +0200 Subject: [PATCH] Emulate fetch_orders if it ain't supported natively --- freqtrade/exchange/exchange.py | 22 ++++++++++++++++-- tests/exchange/test_exchange.py | 41 ++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 822d1074b..07abb489f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1439,11 +1439,29 @@ class Exchange: :param pair: Pair for the query :param since: Starting time for the query """ - if self._config['dry_run'] or not self.exchange_has('fetchOrders'): + if self._config['dry_run']: return [] + + def fetch_orders_emulate() -> List[Dict]: + orders = [] + if self.exchange_has('fetchClosedOrders'): + orders = self._api.fetch_closed_orders(pair, since=since_ms) + if self.exchange_has('fetchOpenOrders'): + orders_open = self._api.fetch_open_orders(pair, since=since_ms) + orders.extend(orders_open) + return orders + try: since_ms = int((since.timestamp() - 10) * 1000) - orders: List[Dict] = self._api.fetch_orders(pair, since=since_ms) + if self.exchange_has('fetchOrders'): + try: + orders: List[Dict] = self._api.fetch_orders(pair, since=since_ms) + except ccxt.NotSupported: + # Some exchanges don't support fetchOrders + # attempt to fetch open and closed orders separately + orders = fetch_orders_emulate() + else: + orders = fetch_orders_emulate() self._log_exchange_response('fetch_orders', orders) orders = [self._order_contracts_to_amount(o) for o in orders] return orders diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 0452f70e3..5994c56e0 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1781,23 +1781,62 @@ def test_fetch_orders(default_conf, mocker, exchange_name, limit_order): limit_order['buy'], limit_order['sell'], ]) + api_mock.fetch_open_orders = MagicMock(return_value=[limit_order['buy']]) + api_mock.fetch_closed_orders = MagicMock(return_value=[limit_order['buy']]) + mocker.patch(f'{EXMS}.exchange_has', return_value=True) start_time = datetime.now(timezone.utc) - timedelta(days=5) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # Not available in dry-run assert exchange.fetch_orders('mocked', start_time) == [] - + assert api_mock.fetch_orders.call_count == 0 default_conf['dry_run'] = False exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) res = exchange.fetch_orders('mocked', start_time) + assert api_mock.fetch_orders.call_count == 1 + assert api_mock.fetch_open_orders.call_count == 0 + assert api_mock.fetch_closed_orders.call_count == 0 assert len(res) == 2 + res = exchange.fetch_orders('mocked', start_time) + + api_mock.fetch_orders.reset_mock() + + def has_resp(_, endpoint): + if endpoint == 'fetchOrders': + return False + if endpoint == 'fetchClosedOrders': + return True + if endpoint == 'fetchOpenOrders': + return True + + mocker.patch(f'{EXMS}.exchange_has', has_resp) + + # happy path without fetchOrders + res = exchange.fetch_orders('mocked', start_time) + assert api_mock.fetch_orders.call_count == 0 + assert api_mock.fetch_open_orders.call_count == 1 + assert api_mock.fetch_closed_orders.call_count == 1 + + mocker.patch(f'{EXMS}.exchange_has', return_value=True) + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "fetch_orders", "fetch_orders", retries=1, pair='mocked', since=start_time) + # Unhappy path - first fetch-orders call fails. + api_mock.fetch_orders = MagicMock(side_effect=ccxt.NotSupported()) + api_mock.fetch_open_orders.reset_mock() + api_mock.fetch_closed_orders.reset_mock() + + res = exchange.fetch_orders('mocked', start_time) + + assert api_mock.fetch_orders.call_count == 1 + assert api_mock.fetch_open_orders.call_count == 1 + assert api_mock.fetch_closed_orders.call_count == 1 + def test_fetch_trading_fees(default_conf, mocker): api_mock = MagicMock()