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. """ incomplete_orders = [o for o in self._in_flight_orders.values() if not o.is_done] tasks = [self._execute_cancel(o.trading_pair, o.client_order_id, True) for o in incomplete_orders] order_id_set = set([o.client_order_id for o in incomplete_orders]) successful_cancellations = [] try: async with timeout(timeout_seconds): results = await safe_gather(*tasks, return_exceptions=True) for result in results: if result is not None and not isinstance(result, Exception): order_id_set.remove(result) successful_cancellations.append(CancellationResult(result, True)) except Exception: self.logger().error("Cancel all failed.", exc_info=True) self.logger().network( "Unexpected error cancelling orders.", exc_info=True, app_warning_msg="Failed to cancel order on Probit. Check API key and network connection." ) failed_cancellations = [CancellationResult(oid, False) for oid in order_id_set] return successful_cancellations + failed_cancellations
async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: """ Cancels all currently active orders. The cancellations are performed in parallel tasks. :param timeout_seconds: the maximum time (in seconds) the cancel logic should run :return: a list of CancellationResult instances, one for each of the orders to be cancelled """ incomplete_orders = [o for o in self.in_flight_orders.values() if not o.is_done] tasks = [self._execute_cancel(o.trading_pair, o.client_order_id) for o in incomplete_orders] order_id_set = set([o.client_order_id for o in incomplete_orders]) successful_cancellations = [] try: async with timeout(timeout_seconds): cancellation_results = await safe_gather(*tasks, return_exceptions=True) for cr in cancellation_results: if isinstance(cr, Exception): continue if cr is not None: client_order_id = cr order_id_set.remove(client_order_id) successful_cancellations.append(CancellationResult(client_order_id, True)) except Exception: self.logger().network( "Unexpected error canceling orders.", exc_info=True, app_warning_msg="Failed to cancel order. Check API key and network connection." ) failed_cancellations = [CancellationResult(oid, False) for oid in order_id_set] return successful_cancellations + failed_cancellations
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
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 _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 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 test_limit_buy_and_sell_and_cancel_all(self): symbol: str = self.base_token_symbol + "-" + self.quote_token_symbol current_price: Decimal = self.market.get_price(symbol, True) amount = Decimal("0.001") expires = int(time.time() + 60 * 3) quantized_amount: Decimal = self.market.quantize_order_amount( symbol, amount) buy_order_id = self.market.buy(symbol=symbol, amount=amount, order_type=OrderType.LIMIT, price=current_price - Decimal("0.2") * current_price, expiration_ts=expires) [buy_order_opened_event] = self.run_parallel( self.market_logger.wait_for(BuyOrderCreatedEvent)) self.assertEqual(buy_order_id, buy_order_opened_event.order_id) self.assertEqual(float(quantized_amount), float(buy_order_opened_event.amount)) self.assertEqual( self.base_token_symbol + "-" + self.quote_token_symbol, buy_order_opened_event.symbol) self.assertEqual(OrderType.LIMIT, buy_order_opened_event.type) # Reset the logs self.market_logger.clear() current_price: Decimal = self.market.get_price(symbol, False) sell_order_id = self.market.sell(symbol=symbol, amount=amount, order_type=OrderType.LIMIT, price=current_price + Decimal("0.2") * current_price, expiration_ts=expires) [sell_order_opened_event] = self.run_parallel( self.market_logger.wait_for(SellOrderCreatedEvent)) self.assertEqual(sell_order_id, sell_order_opened_event.order_id) self.assertEqual(float(quantized_amount), float(sell_order_opened_event.amount)) self.assertEqual( self.base_token_symbol + "-" + self.quote_token_symbol, sell_order_opened_event.symbol) self.assertEqual(OrderType.LIMIT, sell_order_opened_event.type) [cancellation_results, order_cancelled_event] = self.run_parallel( self.market.cancel_all(60 * 3), self.market_logger.wait_for(OrderCancelledEvent)) self.assertEqual(cancellation_results[0], CancellationResult(buy_order_id, True)) self.assertEqual(cancellation_results[1], CancellationResult(sell_order_id, True)) # Wait for the order book source to also register the cancellation self.assertTrue( (buy_order_opened_event.order_id == order_cancelled_event.order_id or sell_order_opened_event.order_id == order_cancelled_event.order_id)) # Reset the logs self.market_logger.clear()
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
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): """ 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
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 test_limit_buy_and_sell_and_cancel_all(self): trading_pair: str = self.base_token_asset + "-" + self.quote_token_asset current_price: Decimal = self.market.get_price(trading_pair, True) amount = Decimal("0.001") expires = int(time.time() + 60 * 3) quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) buy_order_id = self.market.buy(trading_pair=trading_pair, amount=amount, order_type=OrderType.LIMIT, price=current_price - Decimal("0.2") * current_price, expiration_ts=expires) [buy_order_opened_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) self.assertEqual(buy_order_id, buy_order_opened_event.order_id) self.assertEqual(float(quantized_amount), float(buy_order_opened_event.amount)) self.assertEqual(self.base_token_asset + "-" + self.quote_token_asset, buy_order_opened_event.trading_pair) self.assertEqual(OrderType.LIMIT, buy_order_opened_event.type) # Reset the logs self.market_logger.clear() current_price: Decimal = self.market.get_price(trading_pair, False) sell_order_id = self.market.sell(trading_pair=trading_pair, amount=amount, order_type=OrderType.LIMIT, price=current_price + Decimal("0.2") * current_price, expiration_ts=expires) [sell_order_opened_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) self.assertEqual(sell_order_id, sell_order_opened_event.order_id) self.assertEqual(float(quantized_amount), float(sell_order_opened_event.amount)) self.assertEqual(self.base_token_asset + "-" + self.quote_token_asset, sell_order_opened_event.trading_pair) self.assertEqual(OrderType.LIMIT, sell_order_opened_event.type) [cancellation_results, order_cancelled_event] = self.run_parallel(self.market.cancel_all(60 * 3), self.market_logger.wait_for(OrderCancelledEvent)) is_buy_cancelled = False is_sell_cancelled = False for cancellation_result in cancellation_results: if cancellation_result == CancellationResult(buy_order_id, True): is_buy_cancelled = True if cancellation_result == CancellationResult(sell_order_id, True): is_sell_cancelled = True self.assertEqual(is_buy_cancelled, True) self.assertEqual(is_sell_cancelled, True) # Wait for the order book source to also register the cancellation self.assertTrue((buy_order_opened_event.order_id == order_cancelled_event.order_id or sell_order_opened_event.order_id == order_cancelled_event.order_id)) # Reset the logs self.market_logger.clear()
async def cancel_outdated_orders( self, cancel_age: int) -> List[CancellationResult]: """ Iterate through all known orders and cancel them if their age is greater than cancel_age. """ incomplete_orders: List[GatewayInFlightOrder] = [] # Incomplete Approval Requests incomplete_orders.extend( [o for o in self.approval_orders if o.is_pending_approval]) # Incomplete Active Orders incomplete_orders.extend([o for o in self.amm_orders if not o.is_done]) if len(incomplete_orders) < 1: return [] timeout_seconds: float = 30.0 canceling_id_set: Set[str] = set( [o.client_order_id for o in incomplete_orders]) sent_cancellations: List[CancellationResult] = [] try: async with timeout(timeout_seconds): for incomplete_order in incomplete_orders: try: canceling_order_id: Optional[ str] = await self._execute_cancel( incomplete_order.client_order_id, cancel_age) except Exception: continue if canceling_order_id is not None: canceling_id_set.remove(canceling_order_id) sent_cancellations.append( CancellationResult(canceling_order_id, True)) except asyncio.CancelledError: raise except Exception: self.logger().network( "Unexpected error cancelling outdated orders.", exc_info=True, app_warning_msg= f"Failed to cancel orders on {self.chain}-{self.network}.") skipped_cancellations: List[CancellationResult] = [ CancellationResult(oid, False) for oid in canceling_id_set ] return sent_cancellations + skipped_cancellations
def test_limit_buy_and_sell_and_cancel_all(self): symbol: str = "ZRX-WETH" current_price: float = self.market.get_price(symbol, True) amount: float = 10 expires = int(time.time() + 60 * 5) quantized_amount: Decimal = self.market.quantize_order_amount( symbol, amount) buy_order_id = self.market.buy(symbol=symbol, amount=amount, order_type=OrderType.LIMIT, price=current_price - 0.2 * current_price, expiration_ts=expires) [buy_order_opened_event] = self.run_parallel( self.market_logger.wait_for(BuyOrderCreatedEvent)) self.assertEqual(buy_order_id, buy_order_opened_event.order_id) self.assertEqual(quantized_amount, Decimal(buy_order_opened_event.amount)) self.assertEqual("ZRX-WETH", buy_order_opened_event.symbol) self.assertEqual(OrderType.LIMIT, buy_order_opened_event.type) # Reset the logs self.market_logger.clear() sell_order_id = self.market.sell(symbol=symbol, amount=amount, order_type=OrderType.LIMIT, price=current_price + 0.2 * current_price, expiration_ts=expires) [sell_order_opened_event] = self.run_parallel( self.market_logger.wait_for(SellOrderCreatedEvent)) self.assertEqual(sell_order_id, sell_order_opened_event.order_id) self.assertEqual(quantized_amount, Decimal(sell_order_opened_event.amount)) self.assertEqual("ZRX-WETH", sell_order_opened_event.symbol) self.assertEqual(OrderType.LIMIT, sell_order_opened_event.type) [cancellation_results ] = self.run_parallel(self.market.cancel_all(60 * 5)) self.assertEqual(cancellation_results[0], CancellationResult(buy_order_id, True)) self.assertEqual(cancellation_results[1], CancellationResult(sell_order_id, True)) # Reset the logs self.market_logger.clear()
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._global.rest_api.request( # "post", # "private/cancel-all-orders", # {"instrument_name": digifinex_utils.convert_to_exchange_trading_pair(trading_pair)}, # True # ) open_orders = list(self._in_flight_orders.values()) for o in open_orders: await self._execute_cancel(o) for cl_order_id, tracked_order in self._in_flight_orders.items(): open_order = [ o for o in open_orders if o.exchange_order_id == tracked_order.exchange_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 Digifinex. Check API key and network connection." ) return cancellation_results
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. """ order_ids_to_cancel = [] cancel_payloads = [] successful_cancellations = [] failed_cancellations = [] for order in filter(lambda active_order: not active_order.is_done, self._in_flight_order_tracker.active_orders.values()): if order.exchange_order_id is not None: cancel_payloads.append({ "id": ascend_ex_utils.uuid32(), "orderId": order.exchange_order_id, "symbol": ascend_ex_utils.convert_to_exchange_trading_pair(order.trading_pair), "time": int(time.time() * 1e3), }) order_ids_to_cancel.append(order.client_order_id) else: failed_cancellations.append(CancellationResult(order.client_order_id, False)) if cancel_payloads: try: api_params = {"orders": cancel_payloads} 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", ) successful_cancellations = [CancellationResult(order_id, True) for order_id in order_ids_to_cancel] 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 successful_cancellations + failed_cancellations
async def cancel_outdated_orders( self, cancel_age: int) -> List[CancellationResult]: """ Iterate through all known orders and cancel them if their age is greater than cancel_age. """ incomplete_orders: List[GatewayInFlightOrder] = [ o for o in self._in_flight_orders.values() if not (o.is_done or o.is_cancelling) ] if len(incomplete_orders) < 1: return [] timeout_seconds: float = 30.0 canceling_id_set: Set[str] = set( [o.client_order_id for o in incomplete_orders]) sent_cancellations: List[CancellationResult] = [] try: async with timeout(timeout_seconds): # XXX (martin_kou): We CANNOT perform parallel transactions before the nonce architecture is fixed. # See: https://app.shortcut.com/coinalpha/story/24553/nonce-architecture-in-current-amm-trade-and-evm-approve-apis-is-incorrect-and-causes-trouble-with-concurrent-requests for incomplete_order in incomplete_orders: try: canceling_order_id: Optional[ str] = await self._execute_cancel( incomplete_order.client_order_id, cancel_age) except Exception: continue if canceling_order_id is not None: canceling_id_set.remove(canceling_order_id) sent_cancellations.append( CancellationResult(canceling_order_id, True)) except asyncio.CancelledError: raise except Exception: self.logger().network( "Unexpected error cancelling outdated orders.", exc_info=True, app_warning_msg= f"Failed to cancel orders on {self.chain}-{self.network}.") skipped_cancellations: List[CancellationResult] = [ CancellationResult(oid, False) for oid in canceling_id_set ] return sent_cancellations + skipped_cancellations
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 cancel_all(self, timeout_seconds: float): incomplete_orders = [order for order in self._in_flight_orders.values() if not order.is_done] tasks = [self.execute_cancel(order.trading_pair, order.client_order_id) for order in incomplete_orders] order_id_set = set([order.client_order_id for order in incomplete_orders]) successful_cancellations = [] try: async with timeout(timeout_seconds): cancellation_results = await safe_gather(*tasks, return_exceptions=True) for cancel_result in cancellation_results: # TODO: QUESTION --- SHOULD I CHECK FOR THE BinanceAPIException CONSIDERING WE ARE MOVING AWAY FROM BINANCE-CLIENT? if isinstance(cancel_result, dict) and "clientOrderId" in cancel_result: client_order_id = cancel_result.get("clientOrderId") order_id_set.remove(client_order_id) successful_cancellations.append(CancellationResult(client_order_id, True)) except Exception: self.logger().network( "Unexpected error cancelling orders.", exc_info=True, app_warning_msg="Failed to cancel order with Binance Perpetual. Check API key and network connection." ) failed_cancellations = [CancellationResult(order_id, False) for order_id in order_id_set] return successful_cancellations + failed_cancellations
def test_cancel_all(self): self._simulate_trading_rules_initialized() self.exchange.start_tracking_order( order_side=TradeType.BUY, client_order_id="OID1", order_type=OrderType.LIMIT, created_at=1640001112.223, hash="hashcode", trading_pair=self.trading_pair, price=Decimal(1000), amount=Decimal(1), leverage=Decimal(1), position="position", ) result = self.async_run_with_timeout(self.exchange.cancel_all( timeout_seconds=1 )) self.assertEqual(result, [CancellationResult(order_id='OID1', success=True)])
async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: cancellation_queue = self._in_flight_orders.copy() if len(cancellation_queue) == 0: return [] order_status = { o.client_order_id: o.is_done for o in cancellation_queue.values() } def set_cancellation_status(oce: OrderCancelledEvent): if oce.order_id in order_status: order_status[oce.order_id] = True return True return False cancel_verifier = LatchingEventResponder(set_cancellation_status, len(cancellation_queue)) self.add_listener(ORDER_CANCELLED_EVENT, cancel_verifier) for order_id, in_flight in cancellation_queue.items(): try: if order_status[order_id]: cancel_verifier.cancel_one() elif not await self.cancel_order(order_id): # this order did not exist on the exchange cancel_verifier.cancel_one() order_status[order_id] = True except Exception: cancel_verifier.cancel_one() order_status[order_id] = True await cancel_verifier.wait_for_completion(timeout_seconds) self.remove_listener(ORDER_CANCELLED_EVENT, cancel_verifier) return [ CancellationResult(order_id=order_id, success=success) for order_id, success in order_status.items() ]