From 14c5f435aa68bcdad12dd75a64893cae56dcf319 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Sep 2023 20:00:39 +0200 Subject: [PATCH] okx: Use proper history endpoint for fetch_orders --- freqtrade/exchange/okx.py | 17 ++++++++++ tests/exchange/test_okx.py | 67 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index c9f7c52cb..b99911994 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -1,4 +1,5 @@ import logging +from datetime import timedelta from typing import Any, Dict, List, Optional, Tuple import ccxt @@ -10,6 +11,7 @@ from freqtrade.exceptions import (DDosProtection, OperationalException, Retryabl from freqtrade.exchange import Exchange, date_minus_candles from freqtrade.exchange.common import retrier from freqtrade.misc import safe_value_fallback2 +from freqtrade.util import dt_now, dt_ts logger = logging.getLogger(__name__) @@ -240,3 +242,18 @@ class Okx(Exchange): pair=pair, params=params1, ) + + def _fetch_orders_emulate(self, pair: str, since_ms: int) -> List[Dict]: + orders = [] + + orders = self._api.fetch_closed_orders(pair, since=since_ms) + if (since_ms < dt_ts(dt_now() - timedelta(days=6, hours=23))): + # Regular fetch_closed_orders only returns 7 days of data. + # Force usage of "archive" endpoint, which returns 3 months of data. + params = {'method': 'privateGetTradeOrdersHistoryArchive'} + orders_hist = self._api.fetch_closed_orders(pair, since=since_ms, params=params) + orders.extend(orders_hist) + + orders_open = self._api.fetch_open_orders(pair, since=since_ms) + orders.extend(orders_open) + return orders diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index e8f059118..736c630e0 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -618,3 +618,70 @@ def test__get_stop_params_okx(mocker, default_conf): assert params['tdMode'] == 'isolated' assert params['posSide'] == 'net' + + +def test_fetch_orders_okx(default_conf, mocker, limit_order): + + api_mock = MagicMock() + api_mock.fetch_orders = MagicMock(return_value=[ + 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=20) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id='okx') + # 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='okx') + + 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) + + history_params = {'method': 'privateGetTradeOrdersHistoryArchive'} + + # happy path without fetchOrders + 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 == 2 + assert 'params' not in api_mock.fetch_closed_orders.call_args_list[0][1] + assert api_mock.fetch_closed_orders.call_args_list[1][1]['params'] == history_params + + api_mock.fetch_open_orders.reset_mock() + api_mock.fetch_closed_orders.reset_mock() + + # regular closed_orders endpoint only has history for 7 days. + exchange.fetch_orders('mocked', datetime.now(timezone.utc) - timedelta(days=6)) + 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 + assert 'params' not in api_mock.fetch_closed_orders.call_args_list[0][1] + + mocker.patch(f'{EXMS}.exchange_has', return_value=True) + + # 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() + + 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 == 2 + assert 'params' not in api_mock.fetch_closed_orders.call_args_list[0][1] + assert api_mock.fetch_closed_orders.call_args_list[1][1]['params'] == history_params