async def execute_cancel(self, trading_pair: str, client_order_id: str): try: params = { "origClientOrderId": client_order_id, "symbol": convert_to_exchange_trading_pair(trading_pair) } response = await self.request( path="/fapi/v1/order", params=params, method=MethodType.DELETE, is_signed=True, add_timestamp = True, return_err=True ) if response.get("code") == -2011 or "Unknown order sent" in response.get("msg", ""): self.logger().debug(f"The order {client_order_id} does not exist on Binance Perpetuals. " f"No cancellation needed.") self.stop_tracking_order(client_order_id) self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG, OrderCancelledEvent(self.current_timestamp, client_order_id)) return { "origClientOrderId": client_order_id } except Exception as e: self.logger().error(f"Could not cancel order {client_order_id} (on Binance Perp. {trading_pair})") raise e if response.get("status", None) == "CANCELED": self.logger().info(f"Successfully canceled order {client_order_id}") self.stop_tracking_order(client_order_id) self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG, OrderCancelledEvent(self.current_timestamp, client_order_id)) return response
def _process_order_message(self, order_msg: Dict[str, Any]): """ Updates in-flight order and triggers cancellation or failure event if needed. :param order_msg: The order response from either REST or web socket API (they are of the same format) """ client_order_id = order_msg["client_oid"] if client_order_id not in self._in_flight_orders: return tracked_order = self._in_flight_orders[client_order_id] # Update order execution status tracked_order.last_state = order_msg["status"] if tracked_order.is_cancelled: self.logger().info( f"Successfully cancelled order {client_order_id}.") self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, client_order_id)) tracked_order.cancelled_event.set() self.stop_tracking_order(client_order_id) elif tracked_order.is_failure: self.logger().info( f"The market order {client_order_id} has failed according to order status API. " f"Reason: {crypto_com_utils.get_api_reason(order_msg['reason'])}" ) self.trigger_event( MarketEvent.OrderFailure, MarketOrderFailureEvent(self.current_timestamp, client_order_id, tracked_order.order_type)) self.stop_tracking_order(client_order_id)
def test_hanging_order_removed_when_cancelled(self): strategy_active_orders = [] strategy_logs = [] app_notifications = [] type(self.strategy).active_orders = PropertyMock(return_value=strategy_active_orders) self.strategy.log_with_clock.side_effect = lambda log_type, message: strategy_logs.append((log_type, message)) self.strategy.notify_hb_app.side_effect = lambda message: app_notifications.append(message) new_order = LimitOrder("Order-1234567890000000", "BTC-USDT", True, "BTC", "USDT", Decimal(101), Decimal(1)) self.tracker.add_order(new_order) strategy_active_orders.append(new_order) self.tracker.update_strategy_orders_with_equivalent_orders() # Now we simulate the order is cancelled self.tracker._did_cancel_order(MarketEvent.OrderCancelled, self, OrderCancelledEvent(new_order.client_order_id, new_order.client_order_id)) self.assertIn((logging.INFO, "(BTC-USDT) Hanging order Order-1234567890000000 cancelled."), strategy_logs) self.assertIn("(BTC-USDT) Hanging order Order-1234567890000000 cancelled.", app_notifications) self.assertTrue(len(self.tracker.strategy_current_hanging_orders) == 0) self.assertNotIn(new_order, self.tracker.original_orders)
def _process_order_message(self, order_msg: Dict[str, Any]): """ Updates in-flight order and triggers cancellation or failure event if needed. :param order_msg: The order response from either REST or web socket API (they are of the same format) """ exchange_order_id = str(order_msg['id']) """ Currently wazirx api are not supporting client_order_id, so looping through each in flight order and matching exchange order id. """ for order in list(self._in_flight_orders.values()): if str(order.exchange_order_id) == str(exchange_order_id): client_order_id = order.client_order_id tracked_order = self._in_flight_orders[client_order_id] tracked_order.last_state = order_msg["status"] if tracked_order.is_cancelled: self.logger().info( f"Successfully cancelled order {client_order_id}.") self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, client_order_id)) tracked_order.cancelled_event.set() self.stop_tracking_order(client_order_id) elif tracked_order.is_failure: self.logger().info( f"The market order {client_order_id} has failed according to order status API. " ) self.trigger_event( MarketEvent.OrderFailure, MarketOrderFailureEvent(self.current_timestamp, client_order_id, tracked_order.order_type)) self.stop_tracking_order(client_order_id)
async def _execute_cancel(self, o: DigifinexInFlightOrder) -> str: """ Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether the cancellation is successful, it simply states it receives the request. :param trading_pair: The market trading pair :param order_id: The internal order id order.last_state to change to CANCELED """ try: await self._global.rest_api.request( "post", "spot/order/cancel", {"order_id": o.exchange_order_id}, True) if o.client_order_id in self._in_flight_orders: self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, o.client_order_id)) del self._in_flight_orders[o.client_order_id] return o.exchange_order_id except asyncio.CancelledError: raise except Exception as e: self.logger().network( f"Failed to cancel order {o.exchange_order_id}: {str(e)}", exc_info=True, app_warning_msg= f"Failed to cancel the order {o.exchange_order_id} on Digifinex. " f"Check API key and network connection.")
def test_hanging_order_removed_when_cancelled(self): strategy_active_orders = [] type(self.strategy).active_orders = PropertyMock( return_value=strategy_active_orders) new_order = LimitOrder("Order-1234567890000000", "BTC-USDT", True, "BTC", "USDT", Decimal(101), Decimal(1), creation_timestamp=1234567890000000) self.tracker.add_order(new_order) strategy_active_orders.append(new_order) self.tracker.update_strategy_orders_with_equivalent_orders() # Now we simulate the order is cancelled self.tracker._did_cancel_order( MarketEvent.OrderCancelled.value, self, OrderCancelledEvent(datetime.now().timestamp(), new_order.client_order_id, new_order.client_order_id)) self.assertTrue( self._is_logged( "INFO", "(BTC-USDT) Hanging order Order-1234567890000000 canceled.")) self.assertTrue(len(self.tracker.strategy_current_hanging_orders) == 0) self.assertNotIn(new_order, self.tracker.original_orders)
def _process_order_event_message(self, order_msg: Dict[str, Any]): """ Updates in-flight order and triggers cancellation or failure event if needed. :param order_msg: The order event message payload """ client_order_id = str(order_msg["ClientOrderId"]) if client_order_id in self.in_flight_orders: tracked_order = self.in_flight_orders[client_order_id] was_locally_working = tracked_order.is_locally_working # Update order execution status tracked_order.last_state = order_msg["OrderState"] if was_locally_working and tracked_order.is_working: self.trigger_order_created_event(tracked_order) elif tracked_order.is_cancelled: self.logger().info( f"Successfully cancelled order {client_order_id}") self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, client_order_id)) self.stop_tracking_order(client_order_id) elif tracked_order.is_failure: self.logger().info( f"The market order {client_order_id} has failed according to order status event. " f"Reason: {order_msg['ChangeReason']}") self.trigger_event( MarketEvent.OrderFailure, MarketOrderFailureEvent(self.current_timestamp, client_order_id, tracked_order.order_type)) self.stop_tracking_order(client_order_id)
async def cancel_all(self, timeout_seconds: float): """ Cancels all in-flight orders and waits for cancellation results. Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) :param timeout_seconds: The timeout at which the operation will be canceled. :returns List of CancellationResult which indicates whether each order is successfully cancelled. """ cancellation_results = [] try: await self._api_request( "delete", "cash/order/all", {}, True, force_auth_path_url="order/all" ) open_orders = await self.get_open_orders() for cl_order_id, tracked_order in self._in_flight_orders.copy().items(): open_order = [o for o in open_orders if o.client_order_id == cl_order_id] if not open_order: cancellation_results.append(CancellationResult(cl_order_id, True)) self.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, cl_order_id)) self.stop_tracking_order(cl_order_id) else: cancellation_results.append(CancellationResult(cl_order_id, False)) except Exception: self.logger().network( "Failed to cancel all orders.", exc_info=True, app_warning_msg="Failed to cancel all orders on AscendEx. Check API key and network connection." ) return cancellation_results
async def cancel_all(self, timeout_sec: float) -> List[CancellationResult]: """ Cancels all in-flight orders and waits for cancellation results. Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) :param timeout_sec: The timeout at which the operation will be canceled. :returns List of CancellationResult which indicates whether each order is successfully cancelled. """ # Note: NDAX's CancelOrder endpoint simply indicates if the cancel requests has been succesfully received. cancellation_results = [] tracked_orders = self.in_flight_orders try: for order in tracked_orders.values(): self.cancel(trading_pair=order.trading_pair, order_id=order.client_order_id) open_orders = await self.get_open_orders() for client_oid, tracked_order in tracked_orders.items(): matched_order = [o for o in open_orders if o.client_order_id == client_oid] if not matched_order: cancellation_results.append(CancellationResult(client_oid, True)) self.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, client_oid)) else: cancellation_results.append(CancellationResult(client_oid, False)) except Exception as ex: self.logger().network( f"Failed to cancel all orders ({ex})", exc_info=True, app_warning_msg="Failed to cancel all orders on NDAX. Check API key and network connection." ) return cancellation_results
def test_remaining_quantity_updated_after_cancel_order_event(self): self.limit_buy_strategy.logger().setLevel(1) self.limit_buy_strategy.logger().addHandler(self) self.clock.add_iterator(self.limit_buy_strategy) # check no orders are placed before time delay self.clock.backtest_til(self.start_timestamp) self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) # one order created after first tick self.clock.backtest_til(self.start_timestamp + math.ceil(self.clock_tick_size / self.order_delay_time)) self.assertEqual(1, len(self.limit_buy_strategy.active_bids)) bid_order: LimitOrder = self.limit_buy_strategy.active_bids[0][1] self.assertEqual(1, bid_order.quantity) self.assertEqual(self.limit_buy_strategy._quantity_remaining, 1) # Simulate order cancel self.market.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.market.current_timestamp, bid_order.client_order_id)) self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) self.assertEqual(self.limit_buy_strategy._quantity_remaining, 2) self.assertTrue( self._is_logged( 'INFO', f"Updating status after order cancel (id: {bid_order.client_order_id})" ))
def simulate_cancel_order(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]): market_info.market.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent( int(time.time() * 1e3), order.client_order_id if isinstance(order, LimitOrder) else order.order_id, ) )
async def _execute_cancel(self, trading_pair: str, order_id: str) -> CancellationResult: """ Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether the cancellation is successful, it simply states it receives the request. :param trading_pair: The market trading pair (Unused during cancel on AltMarkets.io) :param order_id: The internal order id order.last_state to change to CANCELED """ order_state, errors_found = None, {} try: tracked_order = self._in_flight_orders.get(order_id) if tracked_order is None: self.logger().warning(f"Failed to cancel order {order_id}. Order not found in inflight orders.") elif not tracked_order.is_local: if tracked_order.exchange_order_id is None: try: async with timeout(6): await tracked_order.get_exchange_order_id() except Exception: order_state = "reject" exchange_order_id = tracked_order.exchange_order_id response = await self._api_request("POST", Constants.ENDPOINT["ORDER_DELETE"].format(id=exchange_order_id), is_auth_required=True, limit_id=Constants.RL_ID_ORDER_DELETE) if isinstance(response, dict): order_state = response.get("state", None) except asyncio.CancelledError: raise except asyncio.TimeoutError: self.logger().info(f"The order {order_id} could not be cancelled due to a timeout." " The action will be retried later.") errors_found = {"message": "Timeout during order cancellation"} except AltmarketsAPIError as e: errors_found = e.error_payload.get('errors', e.error_payload) if isinstance(errors_found, dict): order_state = errors_found.get("state", None) if order_state is None or 'market.order.invaild_id_or_uuid' in errors_found: self._order_not_found_records[order_id] = self._order_not_found_records.get(order_id, 0) + 1 if order_state in Constants.ORDER_STATES['CANCEL_WAIT'] or \ self._order_not_found_records.get(order_id, 0) >= self.ORDER_NOT_EXIST_CANCEL_COUNT: self.logger().info(f"Successfully cancelled order {order_id} on {Constants.EXCHANGE_NAME}.") self.stop_tracking_order(order_id) self.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, order_id)) tracked_order.cancelled_event.set() return CancellationResult(order_id, True) else: if not tracked_order or not tracked_order.is_local: err_msg = errors_found.get('message', errors_found) if isinstance(errors_found, dict) else errors_found self.logger().network( f"Failed to cancel order - {order_id}: {err_msg}", exc_info=True, app_warning_msg=f"Failed to cancel the order {order_id} on {Constants.EXCHANGE_NAME}. " f"Check API key and network connection." ) return CancellationResult(order_id, False)
async def _execute_cancel(self, trading_pair: str, order_id: str) -> str: """ To determine if an order is successfully cancelled, we either call the GetOrderStatus/GetOpenOrders endpoint or wait for a OrderStateEvent/OrderTradeEvent from the WS. :param trading_pair: The market (e.g. BTC-CAD) the order is in. :param order_id: The client_order_id of the order to be cancelled. """ try: tracked_order: Optional[ NdaxInFlightOrder] = self._in_flight_orders.get( order_id, None) if tracked_order is None: raise ValueError( f"Failed to cancel order - {order_id}. Order not being tracked." ) if tracked_order.is_locally_working: raise NdaxInFlightOrderNotCreated( f"Failed to cancel order - {order_id}. Order not yet created." f" This is most likely due to rate-limiting.") body_params = { "OMSId": 1, "AccountId": await self.initialized_account_id(), "OrderId": await tracked_order.get_exchange_order_id() } # The API response simply verifies that the API request have been received by the API servers. await self._api_request(method="POST", path_url=CONSTANTS.CANCEL_ORDER_PATH_URL, data=body_params, is_auth_required=True) return order_id except asyncio.CancelledError: raise except NdaxInFlightOrderNotCreated: raise except Exception as e: self.logger().error(f"Failed to cancel order {order_id}: {str(e)}") self.logger().network( f"Failed to cancel order {order_id}: {str(e)}", exc_info=True, app_warning_msg=f"Failed to cancel order {order_id} on NDAX. " f"Check API key and network connection.") if RESOURCE_NOT_FOUND_ERR in str(e): self._order_not_found_records[ order_id] = self._order_not_found_records.get(order_id, 0) + 1 if self._order_not_found_records[ order_id] >= self.ORDER_EXCEED_NOT_FOUND_COUNT: self.logger().warning( f"Order {order_id} does not seem to be active, will stop tracking order..." ) self.stop_tracking_order(order_id) self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, order_id))
async def _execute_cancel(self, trading_pair: str, order_id: str) -> str: """ Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether the cancellation is successful, it simply states it receives the request. :param trading_pair: The market trading pair (Unused during cancel on Gate.io) :param order_id: The internal order id order.last_state to change to CANCELED """ order_was_cancelled = False try: tracked_order = self._in_flight_orders.get(order_id) if tracked_order is None: raise ValueError( f"Failed to cancel order - {order_id}. Order not found.") if tracked_order.exchange_order_id is None: await tracked_order.get_exchange_order_id() ex_order_id = tracked_order.exchange_order_id await self._api_request( "DELETE", Constants.ENDPOINT["ORDER_DELETE"].format(id=ex_order_id), params={ 'currency_pair': convert_to_exchange_trading_pair(trading_pair) }, is_auth_required=True) order_was_cancelled = True except asyncio.CancelledError: raise except (asyncio.TimeoutError, GateIoAPIError) as e: if isinstance(e, asyncio.TimeoutError): err_msg = 'Order not tracked.' err_lbl = 'ORDER_NOT_FOUND' else: err_msg = e.error_message err_lbl = e.error_label self._order_not_found_records[ order_id] = self._order_not_found_records.get(order_id, 0) + 1 if err_lbl == 'ORDER_NOT_FOUND' and \ self._order_not_found_records[order_id] >= self.ORDER_NOT_EXIST_CANCEL_COUNT: order_was_cancelled = True if order_was_cancelled: self.logger().info( f"Successfully cancelled order {order_id} on {Constants.EXCHANGE_NAME}." ) self.stop_tracking_order(order_id) self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, order_id)) tracked_order.cancelled_event.set() return CancellationResult(order_id, True) else: self.logger().network( f"Failed to cancel order {order_id}: {err_msg}", exc_info=True, app_warning_msg= f"Failed to cancel the order {order_id} on {Constants.EXCHANGE_NAME}. " f"Check API key and network connection.") return CancellationResult(order_id, False)
async def cancel_all(self, timeout_seconds: float): """ Cancels all in-flight orders and waits for cancellation results. Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) :param timeout_seconds: The timeout at which the operation will be canceled. :returns List of CancellationResult which indicates whether each order is successfully cancelled. """ if self._trading_pairs is None: raise Exception( "cancel_all can only be used when trading_pairs are specified." ) tracked_orders: Dict[ str, CryptoComInFlightOrder] = self._in_flight_orders.copy().items() cancellation_results = [] try: tasks = [] for _, order in tracked_orders: api_params = { "instrument_name": crypto_com_utils.convert_to_exchange_trading_pair( order.trading_pair), "order_id": order.exchange_order_id, } tasks.append( self._api_request(method="post", path_url=CONSTANTS.CANCEL_ORDER_PATH_URL, params=api_params, is_auth_required=True)) await safe_gather(*tasks) open_orders = await self.get_open_orders() for cl_order_id, tracked_order in tracked_orders: open_order = [ o for o in open_orders if o.client_order_id == cl_order_id ] if not open_order: cancellation_results.append( CancellationResult(cl_order_id, True)) self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, cl_order_id)) self.stop_tracking_order(cl_order_id) else: cancellation_results.append( CancellationResult(cl_order_id, False)) except Exception: self.logger().network( "Failed to cancel all orders.", exc_info=True, app_warning_msg= "Failed to cancel all orders on Crypto.com. Check API key and network connection." ) return cancellation_results
def _trigger_cancelled_event(self, order: InFlightOrder): self._connector.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent( timestamp=self.current_timestamp, order_id=order.client_order_id, exchange_order_id=order.exchange_order_id, ), )
async def cancel_all(self, timeout_seconds: float): """ Cancels all in-flight orders and waits for cancellation results. Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) :param timeout_seconds: The timeout at which the operation will be canceled. :returns List of CancellationResult which indicates whether each order is successfully cancelled. """ cancellation_results = [] try: tracked_orders: Dict[ str, AscendExInFlightOrder] = self._in_flight_orders.copy() api_params = { "orders": [{ 'id': ascend_ex_utils.uuid32(), "orderId": await order.get_exchange_order_id(), "symbol": ascend_ex_utils.convert_to_exchange_trading_pair( order.trading_pair), "time": int(time.time() * 1e3) } for order in tracked_orders.values()] } await self._api_request(method="delete", path_url=CONSTANTS.ORDER_BATCH_PATH_URL, data=api_params, is_auth_required=True, force_auth_path_url="order/batch") open_orders = await self.get_open_orders() for cl_order_id, tracked_order in tracked_orders.items(): open_order = [ o for o in open_orders if o.client_order_id == cl_order_id ] if not open_order: cancellation_results.append( CancellationResult(cl_order_id, True)) self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, cl_order_id)) self.stop_tracking_order(cl_order_id) else: cancellation_results.append( CancellationResult(cl_order_id, False)) except Exception: self.logger().network( "Failed to cancel all orders.", exc_info=True, app_warning_msg= "Failed to cancel all orders on AscendEx. Check API key and network connection." ) return cancellation_results
def test_renew_hanging_orders_past_max_order_age(self): cancelled_orders_ids = [] strategy_active_orders = [] type(self.strategy).current_timestamp = PropertyMock( return_value=1234967891) type(self.strategy).active_orders = PropertyMock( return_value=strategy_active_orders) self.strategy.cancel_order.side_effect = lambda order_id: cancelled_orders_ids.append( order_id) self.strategy.buy_with_specific_market.return_value = "Order-1234569990000000" # Order just executed new_order = LimitOrder("Order-1234567890000000", "BTC-USDT", True, "BTC", "USDT", Decimal(101), Decimal(1)) # Order executed 1900 seconds ago old_order = LimitOrder("Order-1234565991000000", "BTC-USDT", True, "BTC", "USDT", Decimal(105), Decimal(1)) self.tracker.add_order(new_order) strategy_active_orders.append(new_order) self.tracker.add_order(old_order) strategy_active_orders.append(old_order) self.tracker.update_strategy_orders_with_equivalent_orders() self.assertTrue( any(order.trading_pair == "BTC-USDT" and order.price == Decimal(105) for order in self.tracker.strategy_current_hanging_orders)) # When calling the renew logic, the old order should start the renew process (it should be canceled) # but it will only stop being a current hanging order once the cancel confirmation arrives self.tracker.process_tick() self.assertTrue(old_order.client_order_id in cancelled_orders_ids) self.assertTrue( any(order.trading_pair == "BTC-USDT" and order.price == Decimal(105) for order in self.tracker.strategy_current_hanging_orders)) # When the cancel is confirmed the order should no longer be considered a hanging order strategy_active_orders.remove(old_order) self.tracker._did_cancel_order( MarketEvent.OrderCancelled, self, OrderCancelledEvent(old_order.client_order_id, old_order.client_order_id)) self.assertTrue( self._is_logged( "INFO", f"(BTC-USDT) Hanging order {old_order.client_order_id} " f"has been cancelled as part of the renew process. " f"Now the replacing order will be created.")) self.assertFalse( any(order.order_id == old_order.client_order_id for order in self.tracker.strategy_current_hanging_orders)) self.assertTrue( any(order.order_id == "Order-1234569990000000" for order in self.tracker.strategy_current_hanging_orders))
async def _execute_cancel(self, trading_pair: str, order_id: str) -> CancellationResult: """ Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether the cancellation is successful, it simply states it receives the request. :param trading_pair: The market trading pair (Unused during cancel on CoinZoom) :param order_id: The internal order id order.last_state to change to CANCELED """ order_was_cancelled = False try: tracked_order = self._in_flight_orders.get(order_id) if tracked_order is None: raise ValueError(f"Failed to cancel order - {order_id}. Order not found.") if not tracked_order.is_local: if tracked_order.exchange_order_id is None: await tracked_order.get_exchange_order_id() ex_order_id = tracked_order.exchange_order_id api_params = { "orderId": ex_order_id, "symbol": convert_to_exchange_trading_pair(trading_pair) } await self._api_request("POST", Constants.ENDPOINT["ORDER_DELETE"], api_params, is_auth_required=True) order_was_cancelled = True except asyncio.CancelledError: raise except asyncio.TimeoutError: self.logger().info(f"The order {order_id} could not be cancelled due to a timeout." " The action will be retried later.") err = {"message": "Timeout during order cancellation"} except CoinzoomAPIError as e: err = e.error_payload.get('error', e.error_payload) self.logger().error(f"Order Cancel API Error: {err}") # CoinZoom doesn't report any error if the order wasn't found so we can only handle API failures here. self._order_not_found_records[order_id] = self._order_not_found_records.get(order_id, 0) + 1 if self._order_not_found_records[order_id] >= self.ORDER_NOT_EXIST_CANCEL_COUNT: order_was_cancelled = True if order_was_cancelled: self.logger().info(f"Successfully cancelled order {order_id} on {Constants.EXCHANGE_NAME}.") self.stop_tracking_order(order_id) self.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, order_id)) tracked_order.cancelled_event.set() return CancellationResult(order_id, True) else: if not tracked_order.is_local: self.logger().network( f"Failed to cancel order {order_id}: {err.get('message', str(err))}", exc_info=True, app_warning_msg=f"Failed to cancel the order {order_id} on {Constants.EXCHANGE_NAME}. " f"Check API key and network connection." ) return CancellationResult(order_id, False)
async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: orders_by_trading_pair = {} for order in self._in_flight_orders.values(): orders_by_trading_pair[order.trading_pair] = orders_by_trading_pair.get(order.trading_pair, []) orders_by_trading_pair[order.trading_pair].append(order) if len(orders_by_trading_pair) == 0: return [] for trading_pair in orders_by_trading_pair: cancel_order_ids = [o.exchange_order_id for o in orders_by_trading_pair[trading_pair]] is_need_loop = True while is_need_loop: if len(cancel_order_ids) > self.ORDER_LEN_LIMIT: is_need_loop = True this_turn_cancel_order_ids = cancel_order_ids[:self.ORDER_LEN_LIMIT] cancel_order_ids = cancel_order_ids[self.ORDER_LEN_LIMIT:] else: this_turn_cancel_order_ids = cancel_order_ids is_need_loop = False self.logger().debug( f"cancel_order_ids {this_turn_cancel_order_ids} orders_by_trading_pair[trading_pair]") params = { 'order_ids': quote(','.join([o for o in this_turn_cancel_order_ids])), } cancellation_results = [] try: cancel_all_results = await self._api_request( "DELETE", path_url=CONSTANTS.MEXC_ORDER_CANCEL, params=params, is_auth_required=True ) for order_result_client_order_id, order_result_value in cancel_all_results['data'].items(): for o in orders_by_trading_pair[trading_pair]: if o.client_order_id == order_result_client_order_id: result_bool = True if order_result_value == "invalid order state" or order_result_value == "success" else False cancellation_results.append(CancellationResult(o.client_order_id, result_bool)) if result_bool: self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG, OrderCancelledEvent(self.current_timestamp, order_id=o.client_order_id, exchange_order_id=o.exchange_order_id)) self.stop_tracking_order(o.client_order_id) except Exception as ex: self.logger().network( f"Failed to cancel all orders: {this_turn_cancel_order_ids}" + repr(ex), exc_info=True, app_warning_msg="Failed to cancel all orders on Mexc. Check API key and network connection." ) return cancellation_results
async def update_canceling_transactions( self, canceled_tracked_orders: List[GatewayInFlightOrder]): """ Update tracked orders that have a cancel_tx_hash. :param canceled_tracked_orders: Canceled tracked_orders (cancel_tx_has is not None). """ if len(canceled_tracked_orders) < 1: return self.logger().debug( "Polling for order status updates of %d canceled orders.", len(canceled_tracked_orders)) update_results: List[Union[Dict[ str, Any], Exception]] = await safe_gather(*[ GatewayHttpClient.get_instance().get_transaction_status( self.chain, self.network, tx_hash) for tx_hash in [t.cancel_tx_hash for t in canceled_tracked_orders] ], return_exceptions=True) for tracked_order, update_result in zip(canceled_tracked_orders, update_results): if isinstance(update_result, Exception): raise update_result if "txHash" not in update_result: self.logger().error( f"No txHash field for transaction status of {tracked_order.client_order_id}: " f"{update_result}.") continue if update_result["txStatus"] == 1: if update_result["txReceipt"]["status"] == 1: if tracked_order.last_state == "CANCELING": if self.is_amm_order(tracked_order): self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent( self.current_timestamp, tracked_order.client_order_id, tracked_order.exchange_order_id, )) self.logger().info( f"The {tracked_order.trade_type.name} order " f"{tracked_order.client_order_id} has been canceled " f"according to the order status API.") elif self.is_approval_order(tracked_order): token_symbol: str = self.get_token_symbol_from_approval_order_id( tracked_order.client_order_id) self.trigger_event( TokenApprovalEvent.ApprovalCancelled, TokenApprovalCancelledEvent( self.current_timestamp, self.connector_name, token_symbol)) self.logger().info( f"Token approval for {tracked_order.client_order_id} on " f"{self.connector_name} has been canceled.") tracked_order.last_state = "CANCELED" self.stop_tracking_order(tracked_order.client_order_id)
async def cancel_all(self, timeout_seconds: float): """ Cancels all in-flight orders and waits for cancellation results. Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) :param timeout_seconds: The timeout at which the operation will be canceled. :returns List of CancellationResult which indicates whether each order is successfully cancelled. """ if self._trading_pairs is None: raise Exception( "cancel_all can only be used when trading_pairs are specified." ) cancellation_results = [] for trading_pair in self._trading_pairs: order_ids = [ await o.get_exchange_order_id() for _, o in self.in_flight_orders.items() if o.trading_pair == trading_pair ] api_params = { "symbol": k2_utils.convert_to_exchange_trading_pair(trading_pair), "orderids": ",".join(order_ids) } try: await self._api_request(method="POST", path_url=constants.CANCEL_ALL_ORDERS, params=api_params, is_auth_required=True) open_orders = await self.get_open_orders() for cl_order_id, tracked_order in self._in_flight_orders.items( ): open_order = [ o for o in open_orders if o.client_order_id == cl_order_id ] if not open_order: cancellation_results.append( CancellationResult(cl_order_id, True)) self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, cl_order_id)) else: cancellation_results.append( CancellationResult(cl_order_id, False)) except Exception: self.logger().network( "Failed to cancel all orders.", exc_info=True, app_warning_msg= "Failed to cancel all orders on K2. Check API key and network connection." ) return cancellation_results
def _process_order_message(self, order_msg: Dict[str, Any]): """ Updates in-flight order and triggers cancellation or failure event if needed. :param order_msg: The order response from either REST or web socket API (they are of the same format) Example Order: { "id": 9401, "market": "rogerbtc", "kind": "ask", "side": "sell", "ord_type": "limit", "price": "0.00000099", "avg_price": "0.00000099", "state": "wait", "origin_volume": "7000.0", "remaining_volume": "2810.1", "executed_volume": "4189.9", "at": 1596481983, "created_at": 1596481983, "updated_at": 1596553643, "trades_count": 272 } """ exchange_order_id = str(order_msg["id"]) tracked_orders = list(self._in_flight_orders.values()) track_order = [o for o in tracked_orders if exchange_order_id == o.exchange_order_id] if not track_order: return tracked_order = track_order[0] # Estimate fee order_msg["trade_fee"] = self.estimate_fee_pct(tracked_order.order_type is OrderType.LIMIT_MAKER) try: updated = tracked_order.update_with_order_update(order_msg) except Exception as e: self.logger().error(f"Error in order update for {tracked_order.exchange_order_id}. Message: {order_msg}\n{e}") traceback.print_exc() raise e if updated: safe_ensure_future(self._trigger_order_fill(tracked_order, order_msg)) if tracked_order.is_cancelled: self.logger().info(f"Successfully cancelled order {tracked_order.client_order_id}.") self.stop_tracking_order(tracked_order.client_order_id) self.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, tracked_order.client_order_id)) tracked_order.cancelled_event.set() elif tracked_order.is_failure: self.logger().info(f"The market order {tracked_order.client_order_id} has failed according to order status API. ") self.trigger_event(MarketEvent.OrderFailure, MarketOrderFailureEvent( self.current_timestamp, tracked_order.client_order_id, tracked_order.order_type)) tracked_order.last_state = "fail" self.stop_tracking_order(tracked_order.client_order_id)
async def cancel_all(self, timeout_seconds: float): """ Cancels all in-flight orders and waits for cancellation results. Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) :param timeout_seconds: The timeout at which the operation will be canceled. :returns List of CancellationResult which indicates whether each order is successfully cancelled. """ if self._trading_pairs is None: raise Exception( "cancel_all can only be used when trading_pairs are specified." ) cancellation_results = [] try: # ProBit does not have cancel_all_order endpoint tasks = [] for tracked_order in self.in_flight_orders.values(): body_params = { "market_id": tracked_order.trading_pair, "order_id": tracked_order.exchange_order_id } tasks.append( self._api_request(method="POST", path_url=CONSTANTS.CANCEL_ORDER_URL, data=body_params, is_auth_required=True)) await safe_gather(*tasks) open_orders = await self.get_open_orders() for cl_order_id, tracked_order in self._in_flight_orders.items(): open_order = [ o for o in open_orders if o.client_order_id == cl_order_id ] if not open_order: cancellation_results.append( CancellationResult(cl_order_id, True)) self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, cl_order_id)) else: cancellation_results.append( CancellationResult(cl_order_id, False)) except Exception: self.logger().network( "Failed to cancel all orders.", exc_info=True, app_warning_msg= "Failed to cancel all orders on ProBit. Check API key and network connection." ) return cancellation_results
def _process_order_message(self, order_msg: Dict[str, Any]): """ Updates in-flight order and triggers cancellation or failure event if needed. :param order_msg: The order response from either REST or web socket API (they are of the same format) Example Order: { "id": "4345613661", "clientOrderId": "57d5525562c945448e3cbd559bd068c3", "symbol": "BCCBTC", "side": "sell", "status": "new", "type": "limit", "timeInForce": "GTC", "quantity": "0.013", "price": "0.100000", "cumQuantity": "0.000", "postOnly": false, "createdAt": "2017-10-20T12:17:12.245Z", "updatedAt": "2017-10-20T12:17:12.245Z", "reportType": "status" } """ client_order_id = order_msg["clientOrderId"] if client_order_id not in self._in_flight_orders: return tracked_order = self._in_flight_orders[client_order_id] # Update order execution status tracked_order.last_state = order_msg["status"] # update order tracked_order.executed_amount_base = Decimal(order_msg["cumQuantity"]) tracked_order.executed_amount_quote = Decimal( order_msg["price"]) * Decimal(order_msg["cumQuantity"]) if tracked_order.is_cancelled: self.logger().info( f"Successfully canceled order {client_order_id}.") self.stop_tracking_order(client_order_id) self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, client_order_id)) tracked_order.cancelled_event.set() elif tracked_order.is_failure: self.logger().info( f"The market order {client_order_id} has failed according to order status API. " ) self.trigger_event( MarketEvent.OrderFailure, MarketOrderFailureEvent(self.current_timestamp, client_order_id, tracked_order.order_type)) self.stop_tracking_order(client_order_id)
async def cancel_order(self, client_order_id: str): in_flight_order = self._in_flight_orders.get(client_order_id) cancellation_event = OrderCancelledEvent(now(), client_order_id) exchange_order_id = in_flight_order.exchange_order_id if in_flight_order is None: self.logger().warning( "Cancelled an untracked order {client_order_id}") self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) return False try: if exchange_order_id is None: # Note, we have no way of canceling an order or querying for information about the order # without an exchange_order_id if in_flight_order.creation_timestamp < ( self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE): # We'll just have to assume that this order doesn't exist self.stop_tracking_order(in_flight_order.client_order_id) self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) return False params = {"clOrdID": client_order_id} await self._api_request(path=CONSTANTS.ORDER_URL, is_auth_required=True, params=params, method=RESTMethod.DELETE) return True except Exception as e: if "Not Found" in str(e): if in_flight_order.creation_timestamp < ( self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE): # Order didn't exist on exchange, mark this as canceled self.stop_tracking_order(in_flight_order.client_order_id) self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) return False else: raise Exception( f"order {client_order_id} does not yet exist on the exchange and could not be cancelled." ) except Exception as e: self.logger().warning(f"Failed to cancel order {client_order_id}") self.logger().info(e) return False
def _process_order_status(self, exchange_order_id: str, status: int): """ Updates in-flight order and triggers cancellation or failure event if needed. """ tracked_order = self.find_exchange_order(exchange_order_id) if tracked_order is None: return client_order_id = tracked_order.client_order_id # Update order execution status tracked_order.last_state = str(status) if tracked_order.is_cancelled: self.logger().info( f"Successfully cancelled order {client_order_id}.") self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, client_order_id)) tracked_order.cancelled_event.set() self.stop_tracking_order(client_order_id)
async def _process_order_message(self, order_msg: Dict[str, Any]): """ Updates in-flight order and triggers cancellation or failure event if needed. :param order_msg: The order response from either REST or web socket API (they are of the same format) """ for order in self._in_flight_orders.values(): await order.get_exchange_order_id() exchange_order_id = str(order_msg["order_id"]) tracked_orders = list(self._in_flight_orders.values()) tracked_order = [ order for order in tracked_orders if exchange_order_id == order.exchange_order_id ] if not tracked_order: return tracked_order = tracked_order[0] client_order_id = tracked_order.client_order_id # Update order execution status if "status" in order_msg: # REST API tracked_order.last_state = CONSTANTS.ORDER_STATUS[int( order_msg["status"])] elif "state" in order_msg: # WebSocket tracked_order.last_state = CONSTANTS.ORDER_STATUS[int( order_msg["state"])] if tracked_order.is_cancelled: self.logger().info( f"Successfully cancelled order {client_order_id}.") self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, client_order_id)) tracked_order.cancelled_event.set() self.stop_tracking_order(client_order_id) elif tracked_order.is_failure: self.logger().info( f"The market order {client_order_id} has failed according to order status API. " ) self.trigger_event( MarketEvent.OrderFailure, MarketOrderFailureEvent(self.current_timestamp, client_order_id, tracked_order.order_type)) self.stop_tracking_order(client_order_id)
async def cancel_all(self, timeout_seconds: float): """ Cancels all in-flight orders and waits for cancellation results. Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) :param timeout_seconds: The timeout at which the operation will be canceled. :returns List of CancellationResult which indicates whether each order is successfully cancelled. """ if self._trading_pairs is None: raise Exception( "cancel_all can only be used when trading_pairs are specified." ) cancellation_results = [] try: for trading_pair in self._trading_pairs: await self._api_request( "post", "private/cancel-all-orders", { "instrument_name": crypto_com_utils.convert_to_exchange_trading_pair( trading_pair) }, True) open_orders = await self.get_open_orders() for cl_order_id, tracked_order in self._in_flight_orders.items(): open_order = [ o for o in open_orders if o.client_order_id == cl_order_id ] if not open_order: cancellation_results.append( CancellationResult(cl_order_id, True)) self.trigger_event( MarketEvent.OrderCancelled, OrderCancelledEvent(self.current_timestamp, cl_order_id)) else: cancellation_results.append( CancellationResult(cl_order_id, False)) except Exception: self.logger().network( "Failed to cancel all orders.", exc_info=True, app_warning_msg= "Failed to cancel all orders on Crypto.com. Check API key and network connection." ) return cancellation_results
def _process_order_message(self, order_msg: Dict[str, Any]): """ Updates in-flight order and triggers trade, cancellation or failure event if needed. :param order_msg: The order response from either REST or web socket API (they are of the same format) """ client_order_id = order_msg["client_order_id"] if client_order_id not in self._in_flight_orders: return tracked_order = self._in_flight_orders[client_order_id] # Update order execution status tracked_order.last_state = order_msg["status"] # NOTE: In ProBit partially-filled orders will retain "filled" status when canceled. if tracked_order.is_cancelled or Decimal(str(order_msg["cancelled_quantity"])) > Decimal("0"): self.logger().info(f"Successfully cancelled order {client_order_id}.") self.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent( self.current_timestamp, client_order_id)) tracked_order.cancelled_event.set() self.stop_tracking_order(client_order_id)