Example #1
0
    async def _update_order_status(self):
        """
        Calls REST API to get status update for each in-flight order.
        """
        last_tick = int(self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL)
        current_tick = int(self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL)

        if current_tick > last_tick and len(self._in_flight_orders) > 0:
            tracked_orders = list(self._in_flight_orders.values())
            tasks = []
            for tracked_order in tracked_orders:
                order_id = await tracked_order.get_exchange_order_id()
                trading_pair = tracked_order.trading_pair
                tasks.append(self._api_request("get",
                                               CONSTANTS.GET_ORDER_DETAIL_PATH_URL,
                                               {"order_id": int(order_id),
                                                "symbol": bitmart_utils.convert_to_exchange_trading_pair(trading_pair)},
                                               "KEYED"
                                               ))
            self.logger().debug(f"Polling for order status updates of {len(tasks)} orders.")
            responses = await safe_gather(*tasks, return_exceptions=True)
            for response in responses:
                if isinstance(response, Exception):
                    raise response
                if "data" not in response:
                    self.logger().info(f"_update_order_status data not in resp: {response}")
                    continue
                result = response["data"]
                await self._process_trade_message_rest(result)
                await self._process_order_message(result)
Example #2
0
    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."
            )
        for order in self._in_flight_orders.values():
            await order.get_exchange_order_id()
        tracked_orders: Dict[
            str, BitmartInFlightOrder] = self._in_flight_orders.copy().items()
        cancellation_results = []
        try:
            tasks = []

            for _, order in tracked_orders:
                api_params = {
                    "symbol":
                    bitmart_utils.convert_to_exchange_trading_pair(
                        order.trading_pair),
                    "order_id":
                    int(order.exchange_order_id),
                }
                tasks.append(
                    self._api_request("post", CONSTANTS.CANCEL_ORDER_PATH_URL,
                                      api_params, "SIGNED"))

            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 BitMart. Check API key and network connection."
            )
        return cancellation_results
 def test_trading_pair_convertion(self):
     hbot_trading_pair = "BTC-USDT"
     exchange_trading_pair = "BTC_USDT"
     self.assertEqual(
         exchange_trading_pair,
         utils.convert_to_exchange_trading_pair(hbot_trading_pair))
     self.assertEqual(
         hbot_trading_pair,
         utils.convert_from_exchange_trading_pair(exchange_trading_pair))
Example #4
0
    async def get_open_orders(self) -> List[OpenOrder]:
        if self._trading_pairs is None:
            raise Exception("get_open_orders can only be used when trading_pairs are specified.")

        page_len = 100
        responses = []
        for trading_pair in self._trading_pairs:
            page = 1
            while True:
                response = await self._api_request("get", CONSTANTS.GET_OPEN_ORDERS_PATH_URL,
                                                   {"symbol": bitmart_utils.convert_to_exchange_trading_pair(trading_pair),
                                                    "offset": page,
                                                    "limit": page_len,
                                                    "status": "9"},
                                                   "KEYED")
                responses.append(response)
                count = len(response["data"]["orders"])
                if count < page_len:
                    break
                else:
                    page += 1

        for order in self._in_flight_orders.values():
            await order.get_exchange_order_id()

        ret_val = []
        for response in responses:
            for order in response["data"]["orders"]:
                exchange_order_id = str(order["order_id"])
                tracked_orders = list(self._in_flight_orders.values())
                tracked_order = [o for o in tracked_orders if exchange_order_id == o.exchange_order_id]
                if not tracked_order:
                    continue
                tracked_order = tracked_order[0]
                if order["type"] != "limit":
                    raise Exception(f"Unsupported order type {order['type']}")
                ret_val.append(
                    OpenOrder(
                        client_order_id=tracked_order.client_order_id,
                        trading_pair=bitmart_utils.convert_from_exchange_trading_pair(order["symbol"]),
                        price=Decimal(str(order["price"])),
                        amount=Decimal(str(order["size"])),
                        executed_amount=Decimal(str(order["filled_size"])),
                        status=CONSTANTS.ORDER_STATUS[int(order["status"])],
                        order_type=OrderType.LIMIT,
                        is_buy=True if order["side"].lower() == "buy" else False,
                        time=int(order["create_time"]),
                        exchange_order_id=str(order["order_id"])
                    )
                )
        return ret_val
Example #5
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
        :param order_id: The internal order id
        order.last_state to change to CANCELED
        """
        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
            response = await self._api_request(
                "post", CONSTANTS.CANCEL_ORDER_PATH_URL, {
                    "symbol":
                    bitmart_utils.convert_to_exchange_trading_pair(
                        trading_pair),
                    "order_id":
                    int(ex_order_id)
                }, "SIGNED")

            # result = True is a successful cancel, False indicates fcancel failed due to already cancelled or matched
            if "result" in response["data"] and not response["data"]["result"]:
                raise ValueError(
                    f"Failed to cancel order - {order_id}. Order was already matched or cancelled on the exchange."
                )
            return order_id
        except asyncio.CancelledError:
            raise
        except Exception as e:
            self.logger().network(
                f"Failed to cancel order {order_id}: {str(e)}",
                exc_info=True,
                app_warning_msg=
                f"Failed to cancel the order {order_id} on Bitmart. "
                f"Check API key and network connection.")
Example #6
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
        """
        if not order_type.is_limit_type():
            raise Exception(f"Unsupported order type: {order_type}")
        trading_rule = self._trading_rules[trading_pair]

        try:
            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"Buy order amount {amount} is lower than the minimum order size "
                                 f"{trading_rule.min_order_size}.")
            api_params = {"symbol": bitmart_utils.convert_to_exchange_trading_pair(trading_pair),
                          "side": trade_type.name.lower(),
                          "type": "limit",
                          "size": f"{amount:f}",
                          "price": f"{price:f}"
                          }
            self.start_tracking_order(order_id,
                                      None,
                                      trading_pair,
                                      trade_type,
                                      price,
                                      amount,
                                      order_type
                                      )

            order_result = await self._api_request("post", CONSTANTS.CREATE_ORDER_PATH_URL, api_params, "SIGNED")
            exchange_order_id = str(order_result["data"]["order_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)

            event_tag = MarketEvent.BuyOrderCreated if trade_type is TradeType.BUY else MarketEvent.SellOrderCreated
            event_class = BuyOrderCreatedEvent if trade_type is TradeType.BUY else SellOrderCreatedEvent
            self.trigger_event(event_tag,
                               event_class(
                                   self.current_timestamp,
                                   order_type,
                                   trading_pair,
                                   amount,
                                   price,
                                   order_id,
                                   tracked_order.creation_timestamp
                               ))
        except asyncio.CancelledError:
            raise
        except Exception as e:
            self.stop_tracking_order(order_id)
            self.logger().network(
                f"Error submitting {trade_type.name} {order_type.name} order to BitMart for "
                f"{amount} {trading_pair} "
                f"{price}.",
                exc_info=True,
                app_warning_msg=str(e)
            )
            self.trigger_event(MarketEvent.OrderFailure,
                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
Example #7
0
    async def get_open_orders(self) -> List[OpenOrder]:
        """
        Listens to message in _user_stream_tracker.user_stream queue. The messages are put in by
        BitmartAPIUserStreamDataSource.
        """

        if self._trading_pairs is None:
            raise Exception(
                "get_open_orders can only be used when trading_pairs are specified."
            )

        tasks = []
        for trading_pair in self._trading_pairs:
            for page in range(1, 6):
                tasks.append(
                    self._api_request(
                        "get", CONSTANTS.GET_OPEN_ORDERS_PATH_URL, {
                            "symbol":
                            bitmart_utils.convert_to_exchange_trading_pair(
                                trading_pair),
                            "offset":
                            page,
                            "limit":
                            100,
                            "status":
                            "9"
                        }, "KEYED"))
        responses = await safe_gather(*tasks, return_exceptions=True)

        for order in self._in_flight_orders.values():
            await order.get_exchange_order_id()

        ret_val = []
        for response in responses:
            for order in response["data"]["orders"]:
                exchange_order_id = str(order["order_id"])
                tracked_orders = list(self._in_flight_orders.values())
                tracked_order = [
                    o for o in tracked_orders
                    if exchange_order_id == o.exchange_order_id
                ]
                if not tracked_order:
                    continue
                tracked_order = tracked_order[0]
                if order["type"] != "limit":
                    raise Exception(f"Unsupported order type {order['type']}")
                ret_val.append(
                    OpenOrder(
                        client_order_id=tracked_order.client_order_id,
                        trading_pair=bitmart_utils.
                        convert_from_exchange_trading_pair(order["symbol"]),
                        price=Decimal(str(order["price"])),
                        amount=Decimal(str(order["size"])),
                        executed_amount=Decimal(str(order["filled_size"])),
                        status=CONSTANTS.ORDER_STATUS[int(order["status"])],
                        order_type=OrderType.LIMIT,
                        is_buy=True
                        if order["side"].lower() == "buy" else False,
                        time=int(order["create_time"]),
                        exchange_order_id=str(order["order_id"])))
        return ret_val