Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
 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
Ejemplo n.º 6
0
 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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
 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)
Ejemplo n.º 9
0
    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))