async def get_open_orders(self) -> List[OpenOrder]: endpoint = CONSTANTS.USER_ORDERS_PATH_URL request = GateIORESTRequest( method=RESTMethod.GET, endpoint=endpoint, is_auth_required=True, throttler_limit_id=endpoint, ) result = await self._api_request(request) ret_val = [] for pair_orders in result: for order in pair_orders["orders"]: if CONSTANTS.HBOT_ORDER_ID not in order["text"]: continue if order["type"] != OrderType.LIMIT.name.lower(): self.logger().info( f"Unsupported order type found: {order['type']}") continue ret_val.append( OpenOrder(client_order_id=order["text"], trading_pair=convert_from_exchange_trading_pair( order["currency_pair"]), price=Decimal(str(order["price"])), amount=Decimal(str(order["amount"])), executed_amount=Decimal( str(order["filled_total"])), status=order["status"], order_type=OrderType.LIMIT, is_buy=True if order["side"].lower() == TradeType.BUY.name.lower() else False, time=int(order["create_time"]), exchange_order_id=order["id"])) return ret_val
def test_get_auth_url(self): endpoint = CONSTANTS.SYMBOL_PATH_URL request = GateIORESTRequest(method=RESTMethod.GET, endpoint=endpoint) auth_url = request.auth_url self.assertEqual(f"{CONSTANTS.REST_URL_AUTH}/{endpoint}", auth_url)
async def _update_trading_rules(self): endpoint = CONSTANTS.SYMBOL_PATH_URL request = GateIORESTRequest(method=RESTMethod.GET, endpoint=endpoint, throttler_limit_id=endpoint) symbols_info = await self._api_request(request) self._trading_rules.clear() self._trading_rules = self._format_trading_rules(symbols_info)
def test_get_auth_url_raises_on_no_endpoint(self): endpoint = CONSTANTS.SYMBOL_PATH_URL request = GateIORESTRequest( method=RESTMethod.GET, url=f"{CONSTANTS.REST_URL}/{endpoint}" ) with self.assertRaises(ValueError): _ = request.auth_url
async def check_network(self) -> NetworkStatus: """ This function is required by NetworkIterator base class and is called periodically to check the network connection. Simply ping the network (or call any light weight public API). """ try: # since there is no ping endpoint, the lowest rate call is to get BTC-USD symbol endpoint = CONSTANTS.NETWORK_CHECK_PATH_URL request = GateIORESTRequest(method=RESTMethod.GET, endpoint=endpoint, throttler_limit_id=endpoint) await self._api_request(request) except asyncio.CancelledError: raise except Exception: return NetworkStatus.NOT_CONNECTED return NetworkStatus.CONNECTED
async def _update_balances(self): """ Calls REST API to update total and available balances. """ try: # Check for in progress balance updates, queue if fetching and none already waiting, otherwise skip. if self._update_balances_fetching: if not self._update_balances_queued: self._update_balances_queued = True await self._update_balances_finished.wait() self._update_balances_queued = False self._update_balances_finished = asyncio.Event() else: return self._update_balances_fetching = True endpoint = CONSTANTS.USER_BALANCES_PATH_URL request = GateIORESTRequest( method=RESTMethod.GET, endpoint=endpoint, is_auth_required=True, throttler_limit_id=endpoint, ) account_info = await self._api_request(request) self._process_balance_message(account_info) self._update_balances_fetching = False # Set balance update finished event if there's one waiting. if self._update_balances_queued and not self._update_balances_finished.is_set( ): self._update_balances_finished.set() except Exception as e: if self._update_balances_queued: if self._update_balances_finished.is_set(): self._update_balances_finished = asyncio.Event() else: self._update_balances_finished.set() self._update_balances_queued = False if self._update_balances_fetching: self._update_balances_fetching = False warn_msg = ( f"Could not fetch balance update from {CONSTANTS.EXCHANGE_NAME}" ) self.logger().network( f"Unexpected error while fetching balance update - {str(e)}", exc_info=True, app_warning_msg=warn_msg)
async def _update_order_status(self): """ Calls REST API to get status update for each in-flight order. """ tracked_orders: List[GateIoInFlightOrder] = list( self._in_flight_orders.values()) order_status_tasks = [] order_trade_tasks = [] for tracked_order in tracked_orders: try: exchange_order_id = await tracked_order.get_exchange_order_id() except asyncio.TimeoutError: self.logger().network( f"Skipped order status update for {tracked_order.client_order_id} " "- waiting for exchange order id.") continue trading_pair = convert_to_exchange_trading_pair( tracked_order.trading_pair) params = { "currency_pair": trading_pair, "order_id": exchange_order_id } order_trade_request = GateIORESTRequest( method=RESTMethod.GET, endpoint=CONSTANTS.MY_TRADES_PATH_URL, params=params, is_auth_required=True, throttler_limit_id=CONSTANTS.MY_TRADES_PATH_URL, ) params = {"currency_pair": trading_pair} order_status_request = GateIORESTRequest( method=RESTMethod.GET, endpoint=CONSTANTS.ORDER_STATUS_PATH_URL.format( id=exchange_order_id), params=params, is_auth_required=True, throttler_limit_id=CONSTANTS.ORDER_STATUS_LIMIT_ID, ) order_status_tasks.append( asyncio.create_task(self._api_request(order_status_request))) order_trade_tasks.append( asyncio.create_task(self._api_request(order_trade_request))) self.logger().debug( f"Polling for order updates of {len(tracked_orders)} orders.") # Process order trades first before processing order statuses trade_responses = await safe_gather(*order_trade_tasks, return_exceptions=True) for response, tracked_order in zip(trade_responses, tracked_orders): if not isinstance(response, GateIoAPIError): if len(response) > 0: for trade_fills in response: self._process_trade_message( trade_fills, tracked_order.client_order_id) else: self.logger().warning( f"Failed to fetch trade updates for order {tracked_order.client_order_id}. " f"Response: {response}") if response.error_label == 'ORDER_NOT_FOUND': self.stop_tracking_order_exceed_not_found_limit( tracked_order=tracked_order) status_responses = await safe_gather(*order_status_tasks, return_exceptions=True) for response, tracked_order in zip(status_responses, tracked_orders): if not isinstance(response, GateIoAPIError): self._process_order_message(response) else: self.logger().warning( f"Failed to fetch order status updates for order {tracked_order.client_order_id}. " f"Response: {response}") if response.error_label == 'ORDER_NOT_FOUND': self.stop_tracking_order_exceed_not_found_limit( tracked_order=tracked_order)
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 err_msg = 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." ) else: if tracked_order.exchange_order_id is None: await tracked_order.get_exchange_order_id() ex_order_id = tracked_order.exchange_order_id endpoint = CONSTANTS.ORDER_DELETE_PATH_URL.format( id=ex_order_id) params = { 'currency_pair': convert_to_exchange_trading_pair(trading_pair) } request = GateIORESTRequest( method=RESTMethod.DELETE, endpoint=endpoint, params=params, is_auth_required=True, throttler_limit_id=CONSTANTS.ORDER_DELETE_LIMIT_ID, ) await self._api_request(request) 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: err_msg = err_msg or "(no details available)" 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 _create_order(self, trade_type: TradeType, order_id: str, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal): """ Calls create-order API end point to place an order, starts tracking the order and triggers order created event. :param trade_type: BUY or SELL :param order_id: Internal order id (also called client_order_id) :param trading_pair: The market to place order :param amount: The order amount (in base token value) :param order_type: The order type :param price: The order price """ try: if not order_type.is_limit_type(): raise Exception(f"Unsupported order type: {order_type}") trading_rule = self._trading_rules[trading_pair] amount = self.quantize_order_amount(trading_pair, amount) price = self.quantize_order_price(trading_pair, price) if amount < trading_rule.min_order_size: raise ValueError( f"{trade_type.name.title()} order amount {amount} is lower than the minimum order size " f"{trading_rule.min_order_size}.") order_type_str = order_type.name.lower().split("_")[0] api_params = { "text": order_id, "currency_pair": convert_to_exchange_trading_pair(trading_pair), "side": trade_type.name.lower(), "type": order_type_str, "price": f"{price:f}", "amount": f"{amount:f}", } self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, order_type) endpoint = CONSTANTS.ORDER_CREATE_PATH_URL request = GateIORESTRequest( method=RESTMethod.POST, endpoint=endpoint, data=api_params, is_auth_required=True, throttler_limit_id=endpoint, ) order_result = await self._api_request(request) if order_result.get('status') in { "cancelled", "expired", "failed" }: raise GateIoAPIError({ 'label': 'ORDER_REJECTED', 'message': 'Order rejected.' }) else: exchange_order_id = str(order_result["id"]) tracked_order = self._in_flight_orders.get(order_id) if tracked_order is not None: self.logger().info( f"Created {order_type.name} {trade_type.name} order {order_id} for " f"{amount} {trading_pair}.") tracked_order.update_exchange_order_id(exchange_order_id) if trade_type is TradeType.BUY: event_tag = MarketEvent.BuyOrderCreated event_cls = BuyOrderCreatedEvent else: event_tag = MarketEvent.SellOrderCreated event_cls = SellOrderCreatedEvent self.trigger_event( event_tag, event_cls(self.current_timestamp, order_type, trading_pair, amount, price, order_id, tracked_order.creation_timestamp, exchange_order_id)) except asyncio.CancelledError: raise except Exception as e: error_reason = e.error_message if isinstance( e, GateIoAPIError) else str(e) self.stop_tracking_order(order_id) self.logger().error( f"Error submitting {trade_type.name} {order_type.name} order to {CONSTANTS.EXCHANGE_NAME} for " f"{amount} {trading_pair} {price} - {error_reason}.", exc_info=True, ) self.trigger_event( MarketEvent.OrderFailure, MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))