diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f2f8cd69f..412a60386 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -937,6 +937,36 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + def is_cancel_order_result_suitable(self, corder) -> bool: + if not isinstance(corder, dict): + return False + + required = ('fee', 'status', 'amount') + return all(k in corder for k in required) + + def cancel_order_with_result(self, order_id: str, pair: str, amount: float) -> Dict: + """ + Cancel order returning a result. + Creates a fake result if cancel order returns a non-usable result + and get_order does not work (certain exchanges don't return cancelled orders) + :param order_id: Orderid to cancel + :param pair: Pair corresponding to order_id + :param amount: Amount to use for fake response + :return: Result from either cancel_order if usable, or fetch_order + """ + if self._config['dry_run']: + return {'fee': {}, 'status': 'canceled', 'amount': amount, 'info': {}} + corder = self.cancel_order(order_id, pair) + if self.is_cancel_order_result_suitable(corder): + return corder + try: + order = self.get_order(order_id, pair) + except InvalidOrderException: + logger.warning(f"Could not fetch cancelled order {order_id}.") + order = {'fee': {}, 'status': 'canceled', 'amount': amount, 'info': {}} + + return order + @retrier def get_order(self, order_id: str, pair: str) -> Dict: if self._config['dry_run']: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b74ea1a6b..1f0df5aa5 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1745,6 +1745,20 @@ def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, assert exchange.check_order_canceled_empty(order) == result +@pytest.mark.parametrize("exchange_name", EXCHANGES) +@pytest.mark.parametrize("order,result", [ + ({'status': 'closed', 'amount': 10, 'fee': {}}, True), + ({'status': 'closed', 'amount': 0.0, 'fee': {}}, True), + ({'status': 'canceled', 'amount': 0.0, 'fee': {}}, True), + ({'status': 'canceled', 'amount': 10.0}, False), + ({'amount': 10.0, 'fee': {}}, False), + ({'result': 'testest123'}, False), + ('hello_world', False), +]) +def test_is_cancel_order_result_suitable(mocker, default_conf, exchange_name, order, result): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + assert exchange.is_cancel_order_result_suitable(order) == result + # Ensure that if not dry_run, we should call API @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_cancel_order(default_conf, mocker, exchange_name):