def from_json(cls, data: Dict[str, Any]) -> "GatewayInFlightOrder":
        """
        Initialize an InFlightOrder using a JSON object
        :param data: JSON data
        :return: Formatted InFlightOrder
        """
        order = GatewayInFlightOrder(
            client_order_id=data["client_order_id"],
            trading_pair=data["trading_pair"],
            order_type=getattr(OrderType, data["order_type"]),
            trade_type=getattr(TradeType, data["trade_type"]),
            amount=Decimal(data["amount"]),
            price=Decimal(data["price"]),
            exchange_order_id=data["exchange_order_id"],
            initial_state=OrderState(int(data["last_state"])),
            creation_timestamp=data.get("creation_timestamp", -1),
        )
        order.executed_amount_base = Decimal(data["executed_amount_base"])
        order.executed_amount_quote = Decimal(data["executed_amount_quote"])
        order.order_fills.update({
            key: TradeUpdate.from_json(value)
            for key, value in data.get("order_fills", {}).items()
        })
        order._nonce = data["nonce"]
        order._cancel_tx_hash = data["cancel_tx_hash"]
        order._gas_price = Decimal(data["gas_price"])

        order.check_filled_condition()

        return order
Ejemplo n.º 2
0
    def test_trade_update_does_not_change_exchange_order_id(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )

        trade_update: TradeUpdate = TradeUpdate(
            trade_id="someTradeId",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            fill_price=Decimal("1.0"),
            fill_base_amount=Decimal("500.0"),
            fill_quote_amount=Decimal("500.0"),
            fee=AddedToCostTradeFee(flat_fees=[
                TokenAmount(token=self.quote_asset,
                            amount=self.trade_fee_percent * Decimal("500.0"))
            ]),
            fill_timestamp=1,
        )

        self.assertTrue(order.update_with_trade_update(trade_update))
        self.assertIsNone(order.exchange_order_id)
        self.assertFalse(order.exchange_order_id_update_event.is_set())
    async def _process_order_fill_update(self, order: InFlightOrder,
                                         fill_update: Dict[str, Any]):
        fills_data = fill_update["data"]["trades"]

        for fill_data in fills_data:
            fee = TradeFeeBase.new_spot_fee(
                fee_schema=self.trade_fee_schema(),
                trade_type=order.trade_type,
                percent_token=fill_data["fee_coin_name"],
                flat_fees=[
                    TokenAmount(amount=Decimal(fill_data["fees"]),
                                token=fill_data["fee_coin_name"])
                ])
            trade_update = TradeUpdate(
                trade_id=str(fill_data["detail_id"]),
                client_order_id=order.client_order_id,
                exchange_order_id=str(fill_data["order_id"]),
                trading_pair=order.trading_pair,
                fee=fee,
                fill_base_amount=Decimal(fill_data["size"]),
                fill_quote_amount=Decimal(fill_data["size"]) *
                Decimal(fill_data["price_avg"]),
                fill_price=Decimal(fill_data["price_avg"]),
                fill_timestamp=int(fill_data["create_time"]) * 1e-3,
            )
            self._order_tracker.process_trade_update(trade_update)
Ejemplo n.º 4
0
    def test_update_with_trade_update_duplicate_trade_update(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )

        trade_update: TradeUpdate = TradeUpdate(
            trade_id="someTradeId",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            fill_price=Decimal("1.0"),
            fill_base_amount=Decimal("500.0"),
            fill_quote_amount=Decimal("500.0"),
            fee=AddedToCostTradeFee(
                flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * Decimal("500.0"))]),
            fill_timestamp=1,
        )

        self.assertTrue(order.update_with_trade_update(trade_update))
        self.assertEqual(order.executed_amount_base, trade_update.fill_base_amount)
        self.assertEqual(order.executed_amount_quote, trade_update.fill_quote_amount)
        self.assertEqual(order.last_update_timestamp, trade_update.fill_timestamp)
        self.assertEqual(1, len(order.order_fills))
        self.assertIn(trade_update.trade_id, order.order_fills)

        # Ignores duplicate trade update
        self.assertFalse(order.update_with_trade_update(trade_update))
Ejemplo n.º 5
0
    def test_update_with_trade_update_trade_update_with_trade_fee_percent(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
        )

        trade_update: TradeUpdate = TradeUpdate(
            trade_id="someTradeId",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            fill_price=Decimal("1.0"),
            fill_base_amount=Decimal("500.0"),
            fill_quote_amount=Decimal("500.0"),
            fee_asset=self.base_asset,
            trade_fee_percent=self.trade_fee_percent,
            fill_timestamp=1,
        )

        self.assertTrue(order.update_with_trade_update(trade_update))
        self.assertIsNotNone(order.exchange_order_id)
        self.assertTrue(order.exchange_order_id_update_event.is_set())
        self.assertEqual(order.executed_amount_base, trade_update.fill_base_amount)
        self.assertEqual(order.executed_amount_quote, trade_update.fill_quote_amount)
        self.assertEqual(order.fee_asset, trade_update.fee_asset)
        self.assertEqual(order.cumulative_fee_paid, self.trade_fee_percent * Decimal("500.0"))
        self.assertEqual(order.last_filled_price, trade_update.fill_price)
        self.assertEqual(order.last_filled_amount, trade_update.fill_base_amount)
        self.assertEqual(order.last_update_timestamp, trade_update.fill_timestamp)
        self.assertEqual(1, len(order.order_fills))
        self.assertIn(trade_update.trade_id, order.order_fills)
Ejemplo n.º 6
0
    def test_to_json(self):
        fee = AddedToCostTradeFee(
            percent=Decimal("0.5"),
            percent_token=self.quote_asset
        )
        trade_update = TradeUpdate(
            trade_id="12345",
            client_order_id=self.client_order_id,
            exchange_order_id="EOID1",
            trading_pair=self.trading_pair,
            fill_timestamp=1640001112,
            fill_price=Decimal("1000.11"),
            fill_base_amount=Decimal("2"),
            fill_quote_amount=Decimal("2000.22"),
            fee=fee,
        )

        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )
        order.order_fills["1"] = trade_update

        order_json = order.to_json()

        self.assertIsInstance(order_json, dict)

        self.assertEqual(order_json["client_order_id"], order.client_order_id)
        self.assertEqual(order_json["exchange_order_id"], order.exchange_order_id)
        self.assertEqual(order_json["trading_pair"], order.trading_pair)
        self.assertEqual(order_json["order_type"], order.order_type.name)
        self.assertEqual(order_json["trade_type"], order.trade_type.name)
        self.assertEqual(order_json["price"], str(order.price))
        self.assertEqual(order_json["amount"], str(order.amount))
        self.assertEqual(order_json["executed_amount_base"], str(order.executed_amount_base))
        self.assertEqual(order_json["executed_amount_quote"], str(order.executed_amount_quote))
        self.assertEqual(order_json["last_state"], str(order.current_state.value))
        self.assertEqual(order_json["leverage"], str(order.leverage))
        self.assertEqual(order_json["position"], order.position.value)
        self.assertEqual(order_json["creation_timestamp"], order.creation_timestamp)
        self.assertEqual(order_json["order_fills"], {"1": trade_update.to_json()})
    def test_updating_order_states_with_both_process_order_update_and_process_trade_update(
            self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )
        self.tracker.start_tracking_order(order)

        order_creation_update: OrderUpdate = OrderUpdate(
            client_order_id=order.client_order_id,
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        update_future = self.tracker.process_order_update(
            order_creation_update)
        self.async_run_with_timeout(update_future)

        open_order: InFlightOrder = self.tracker.fetch_tracked_order(
            order.client_order_id)

        # Check order_creation_update has been successfully applied
        self.assertEqual(open_order.exchange_order_id,
                         order_creation_update.exchange_order_id)
        self.assertTrue(open_order.exchange_order_id_update_event.is_set())
        self.assertEqual(open_order.current_state,
                         order_creation_update.new_state)
        self.assertTrue(open_order.is_open)
        self.assertEqual(0, len(open_order.order_fills))

        trade_filled_price: Decimal = order.price
        trade_filled_amount: Decimal = order.amount
        fee_paid: Decimal = self.trade_fee_percent * trade_filled_amount
        trade_update: TradeUpdate = TradeUpdate(
            trade_id=1,
            client_order_id=order.client_order_id,
            exchange_order_id=order.exchange_order_id,
            trading_pair=order.trading_pair,
            fill_price=trade_filled_price,
            fill_base_amount=trade_filled_amount,
            fill_quote_amount=trade_filled_price * trade_filled_amount,
            fee=AddedToCostTradeFee(flat_fees=[
                TokenAmount(token=self.quote_asset, amount=fee_paid)
            ]),
            fill_timestamp=2,
        )

        self.tracker.process_trade_update(trade_update)
        self.assertEqual(1, len(self.tracker.active_orders))
        self.assertEqual(0, len(self.tracker.cached_orders))
Ejemplo n.º 8
0
    def test_json_deserialization(self):
        token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6"))
        fee = DeductedFromReturnsTradeFee(percent=Decimal("0.5"),
                                          percent_token="COINALPHA",
                                          flat_fees=[token_amount])
        trade_update = TradeUpdate(
            trade_id="12345",
            client_order_id="OID1",
            exchange_order_id="EOID1",
            trading_pair="HBOT-COINALPHA",
            fill_timestamp=1640001112,
            fill_price=Decimal("1000.11"),
            fill_base_amount=Decimal("2"),
            fill_quote_amount=Decimal("2000.22"),
            fee=fee,
        )

        self.assertEqual(trade_update,
                         TradeUpdate.from_json(trade_update.to_json()))
Ejemplo n.º 9
0
    def test_process_trade_update_does_not_trigger_filled_event_update_status_when_completely_filled(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )
        self.tracker.start_tracking_order(order)

        fee_paid: Decimal = self.trade_fee_percent * order.amount
        trade_update: TradeUpdate = TradeUpdate(
            trade_id=1,
            client_order_id=order.client_order_id,
            exchange_order_id=order.exchange_order_id,
            trading_pair=order.trading_pair,
            fill_price=order.price,
            fill_base_amount=order.amount,
            fill_quote_amount=order.price * order.amount,
            fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote_asset, amount=fee_paid)]),
            fill_timestamp=1,
        )

        self.tracker.process_trade_update(trade_update)

        fetched_order: InFlightOrder = self.tracker.fetch_order(order.client_order_id)
        self.assertTrue(fetched_order.is_filled)
        self.assertIn(fetched_order.client_order_id, self.tracker.active_orders)
        self.assertNotIn(fetched_order.client_order_id, self.tracker.cached_orders)

        self.assertTrue(
            self._is_logged(
                "INFO",
                f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to "
                f"{order.amount}/{order.amount} {order.base_asset} has been filled.",
            )
        )

        self.assertEqual(1, len(self.order_filled_logger.event_log))
        self.assertEqual(0, len(self.buy_order_completed_logger.event_log))

        order_filled_event: OrderFilledEvent = self.order_filled_logger.event_log[0]
        self.assertIsNotNone(order_filled_event)

        self.assertEqual(order_filled_event.order_id, order.client_order_id)
        self.assertEqual(order_filled_event.price, trade_update.fill_price)
        self.assertEqual(order_filled_event.amount, trade_update.fill_base_amount)
        self.assertEqual(
            order_filled_event.trade_fee, AddedToCostTradeFee(flat_fees=[TokenAmount(self.quote_asset, fee_paid)])
        )
    def test_process_trade_update_trigger_filled_event_flat_fee(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )
        self.tracker.start_tracking_order(order)

        trade_filled_price: Decimal = Decimal("0.5")
        trade_filled_amount: Decimal = order.amount / Decimal("2.0")
        fee_paid: Decimal = self.trade_fee_percent * trade_filled_amount
        trade_update: TradeUpdate = TradeUpdate(
            trade_id=1,
            client_order_id=order.client_order_id,
            exchange_order_id=order.exchange_order_id,
            trading_pair=order.trading_pair,
            fill_price=trade_filled_price,
            fill_base_amount=trade_filled_amount,
            fill_quote_amount=trade_filled_price * trade_filled_amount,
            fee_asset=self.base_asset,
            fee_paid=fee_paid,
            fill_timestamp=1,
        )

        self.tracker.process_trade_update(trade_update)

        self.assertTrue(
            self._is_logged(
                "INFO",
                f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to "
                f"{trade_filled_amount}/{order.amount} {order.base_asset} has been filled.",
            ))

        self.assertEqual(1, len(self.connector.event_logs))
        order_filled_event: OrderFilledEvent = self.connector.event_logs[0]

        self.assertEqual(order_filled_event.order_id, order.client_order_id)
        self.assertEqual(order_filled_event.price, trade_update.fill_price)
        self.assertEqual(order_filled_event.amount,
                         trade_update.fill_base_amount)
        self.assertEqual(
            order_filled_event.trade_fee,
            AddedToCostTradeFee(
                flat_fees=[TokenAmount(self.base_asset, fee_paid)]))
Ejemplo n.º 11
0
    def _process_order_message(self, order_msg: AscendExOrder):
        """
        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)
        """
        tracked_order = self._in_flight_order_tracker.fetch_order(exchange_order_id=order_msg.orderId)

        if tracked_order is not None:
            order_status = CONSTANTS.ORDER_STATE[order_msg.status]
            cumulative_filled_amount = Decimal(order_msg.cumFilledQty)
            if (order_status in [OrderState.PARTIALLY_FILLED, OrderState.FILLED]
                    and cumulative_filled_amount > tracked_order.executed_amount_base):
                filled_amount = cumulative_filled_amount - tracked_order.executed_amount_base
                cumulative_fee = Decimal(order_msg.cumFee)
                fee_already_paid = tracked_order.cumulative_fee_paid(token=order_msg.feeAsset, exchange=self)
                if cumulative_fee > fee_already_paid:
                    fee = TradeFeeBase.new_spot_fee(
                        fee_schema=self.trade_fee_schema(),
                        trade_type=tracked_order.trade_type,
                        percent_token=order_msg.feeAsset,
                        flat_fees=[TokenAmount(amount=cumulative_fee - fee_already_paid, token=order_msg.feeAsset)]
                    )
                else:
                    fee = TradeFeeBase.new_spot_fee(
                        fee_schema=self.trade_fee_schema(),
                        trade_type=tracked_order.trade_type)
                trade_update = TradeUpdate(
                    trade_id=str(order_msg.lastExecTime),
                    client_order_id=tracked_order.client_order_id,
                    exchange_order_id=tracked_order.exchange_order_id,
                    trading_pair=tracked_order.trading_pair,
                    fee=fee,
                    fill_base_amount=filled_amount,
                    fill_quote_amount=filled_amount * Decimal(order_msg.avgPx),
                    fill_price=Decimal(order_msg.avgPx),
                    fill_timestamp=int(order_msg.lastExecTime),
                )
                self._in_flight_order_tracker.process_trade_update(trade_update)

            order_update = OrderUpdate(
                exchange_order_id=order_msg.orderId,
                trading_pair=ascend_ex_utils.convert_to_exchange_trading_pair(order_msg.symbol),
                update_timestamp=order_msg.lastExecTime * 1e-3,
                new_state=order_status,
            )

            self._in_flight_order_tracker.process_order_update(order_update=order_update)
Ejemplo n.º 12
0
 async def _update_order_fills_from_trades(self, tracked_order,
                                           order_update):
     """
     This is intended to be a backup measure to get filled events from order status
     in case CoinFLEX's user stream events are not working.
     """
     fee_collected = False
     for match_data in order_update["matchIds"]:
         for trade_id in match_data.keys():
             trade_data = match_data[trade_id]
             exec_amt_base = coinflex_utils.decimal_val_or_none(
                 trade_data.get("matchQuantity"))
             fill_price = coinflex_utils.decimal_val_or_none(
                 trade_data.get("matchPrice"))
             exec_amt_quote = exec_amt_base * fill_price if exec_amt_base and fill_price else None
             if not fee_collected and len(order_update.get("fees", {})):
                 fee_collected = True
                 fee_data = order_update.get("fees")
                 fee_token = list(fee_data.keys())[0]
                 fee_paid = coinflex_utils.decimal_val_or_none(
                     fee_data[fee_token])
             else:
                 fee_token = tracked_order.quote_asset
                 fee_paid = s_decimal_0
             fee = TradeFeeBase.new_spot_fee(
                 fee_schema=self.trade_fee_schema(),
                 trade_type=tracked_order.trade_type,
                 percent_token=fee_token,
                 flat_fees=[TokenAmount(amount=fee_paid, token=fee_token)])
             trade_update = TradeUpdate(
                 trading_pair=tracked_order.trading_pair,
                 trade_id=int(trade_id),
                 client_order_id=tracked_order.client_order_id,
                 exchange_order_id=str(order_update["orderId"]),
                 fill_timestamp=int(trade_data["timestamp"]) * 1e-3,
                 fill_price=fill_price,
                 fill_base_amount=exec_amt_base,
                 fill_quote_amount=exec_amt_quote,
                 fee=fee,
             )
             self._order_tracker.process_trade_update(
                 trade_update=trade_update)
Ejemplo n.º 13
0
    async def _update_order_fills_from_event_or_create(self, tracked_order, order_data):
        """
        Used to update fills from user stream events or order creation.
        """
        client_order_id = order_data.get("clientOrderId")
        exec_amt_base = coinflex_utils.decimal_val_or_none(order_data.get("matchQuantity"))
        if not exec_amt_base:
            return

        if not tracked_order:
            tracked_order = self.in_flight_orders.get(client_order_id)

        fill_price = coinflex_utils.decimal_val_or_none(order_data.get("matchPrice", order_data.get("price")))
        exec_amt_quote = exec_amt_base * fill_price if exec_amt_base and fill_price else None
        fee_paid = coinflex_utils.decimal_val_or_none(order_data.get("fees"))
        if fee_paid:
            fee = TradeFeeBase.new_spot_fee(
                fee_schema=self.trade_fee_schema(),
                trade_type=tracked_order.trade_type,
                percent_token=order_data.get("feeInstrumentId"),
                flat_fees=[TokenAmount(amount=fee_paid, token=order_data.get("feeInstrumentId"))]
            )
        else:
            fee = self.get_fee(base_currency=tracked_order.base_asset,
                               quote_currency=tracked_order.quote_asset,
                               order_type=tracked_order.order_type,
                               order_side=tracked_order.trade_type,
                               amount=tracked_order.amount,
                               price=tracked_order.price,
                               is_maker=True)
        trade_update = TradeUpdate(
            trading_pair=tracked_order.trading_pair,
            trade_id=int(order_data["matchId"]),
            client_order_id=client_order_id,
            exchange_order_id=str(order_data["orderId"]),
            fill_timestamp=int(order_data["timestamp"]) * 1e-3,
            fill_price=fill_price,
            fill_base_amount=exec_amt_base,
            fill_quote_amount=exec_amt_quote,
            fee=fee,
        )
        self._order_tracker.process_trade_update(trade_update=trade_update)
    def _process_trade_message(self,
                               trade: Dict[str, Any],
                               client_order_id: Optional[str] = None):
        """
        Updates in-flight order and trigger order filled event for trade message received. Triggers order completed
        event if the total executed amount equals to the specified order amount.
        Example Trade:
        https://www.gate.io/docs/apiv4/en/#retrieve-market-trades
        """
        client_order_id = client_order_id or str(trade["text"])
        tracked_order = self.in_flight_orders.get(client_order_id, None)
        if not tracked_order:
            self.logger().debug(
                f"Ignoring trade message with id {client_order_id}: not in in_flight_orders."
            )
            return

        fee = TradeFeeBase.new_spot_fee(fee_schema=self.trade_fee_schema(),
                                        trade_type=tracked_order.trade_type,
                                        percent_token=trade["fee_currency"],
                                        flat_fees=[
                                            TokenAmount(
                                                amount=Decimal(trade["fee"]),
                                                token=trade["fee_currency"])
                                        ])
        trade_update = TradeUpdate(
            trade_id=str(trade["id"]),
            client_order_id=tracked_order.client_order_id,
            exchange_order_id=tracked_order.exchange_order_id,
            trading_pair=tracked_order.trading_pair,
            fee=fee,
            fill_base_amount=Decimal(trade["amount"]),
            fill_quote_amount=Decimal(trade["amount"]) *
            Decimal(trade["price"]),
            fill_price=Decimal(trade["price"]),
            fill_timestamp=trade["create_time"],
        )
        self._order_tracker.process_trade_update(trade_update)
Ejemplo n.º 15
0
    async def _process_trade_update(self, trade: Dict[str, Any]):
        symbol = f"{trade['baseCurrency']}/{trade['quoteCurrency']}"
        trading_pair = await self.trading_pair_associated_to_exchange_symbol(
            symbol=symbol)

        base_currency, quote_currency = trading_pair.split('-')
        trade_type = TradeType.BUY if trade["makerBuyer"] else TradeType.SELL
        timestamp = float(trade["timestamp"]) * 1e-3
        quantity = Decimal(trade["quantity"])
        price = Decimal(trade["price"])
        trade_id = trade["id"]
        exchange_order_id = trade["order"]
        tracked_order = self._order_tracker.fetch_order(
            exchange_order_id=exchange_order_id)
        client_order_id = tracked_order.client_order_id if tracked_order else None

        absolute_fee = Decimal(trade["fee"])
        fee = TradeFeeBase.new_spot_fee(
            fee_schema=self.trade_fee_schema(),
            trade_type=trade_type,
            percent_token=quote_currency,
            flat_fees=[TokenAmount(amount=absolute_fee, token=quote_currency)])

        trade_update = TradeUpdate(
            trade_id=trade_id,
            exchange_order_id=exchange_order_id,
            client_order_id=client_order_id,
            trading_pair=trading_pair,  # or tracked_order.trading_pair
            fill_timestamp=timestamp,
            fill_price=price,
            fill_base_amount=quantity,
            fill_quote_amount=Decimal(trade["cost"]),
            fee=fee,
        )

        self._order_tracker.process_trade_update(trade_update=trade_update)
Ejemplo n.º 16
0
    def test_update_with_trade_update_multiple_trade_updates(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )

        initial_fill_price: Decimal = Decimal("0.5")
        initial_fill_amount: Decimal = Decimal("500.0")
        trade_update_1: TradeUpdate = TradeUpdate(
            trade_id="someTradeId_1",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            fill_price=initial_fill_price,
            fill_base_amount=initial_fill_amount,
            fill_quote_amount=initial_fill_price * initial_fill_amount,
            fee=AddedToCostTradeFee(
                flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * initial_fill_amount)]),
            fill_timestamp=1,
        )

        subsequent_fill_price: Decimal = Decimal("1.0")
        subsequent_fill_amount: Decimal = Decimal("500.0")
        trade_update_2: TradeUpdate = TradeUpdate(
            trade_id="someTradeId_2",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            fill_price=subsequent_fill_price,
            fill_base_amount=subsequent_fill_amount,
            fill_quote_amount=subsequent_fill_price * subsequent_fill_amount,
            fee=AddedToCostTradeFee(
                flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * subsequent_fill_amount)]),
            fill_timestamp=2,
        )

        self.assertTrue(order.update_with_trade_update(trade_update_1))
        self.assertIn(trade_update_1.trade_id, order.order_fills)
        self.assertEqual(order.executed_amount_base, trade_update_1.fill_base_amount)
        self.assertEqual(order.executed_amount_quote, trade_update_1.fill_quote_amount)
        self.assertEqual(order.last_update_timestamp, trade_update_1.fill_timestamp)
        self.assertEqual(1, len(order.order_fills))

        self.assertTrue(order.is_open)

        self.assertTrue(order.update_with_trade_update(trade_update_2))
        self.assertIn(trade_update_2.trade_id, order.order_fills)
        self.assertEqual(order.executed_amount_base, order.amount)
        self.assertEqual(
            order.executed_amount_quote, trade_update_1.fill_quote_amount + trade_update_2.fill_quote_amount
        )
        self.assertEqual(order.last_update_timestamp, trade_update_2.fill_timestamp)
        self.assertEqual(2, len(order.order_fills))
        self.assertEqual(
            order.average_executed_price,
            (trade_update_1.fill_quote_amount + trade_update_2.fill_quote_amount) / order.amount,
        )

        self.assertTrue(order.is_filled)
        self.assertEqual(order.current_state, OrderState.PENDING_CREATE)
Ejemplo n.º 17
0
    async def _user_stream_event_listener(self):
        """
        This functions runs in background continuously processing the events received from the exchange by the user
        stream data source. It keeps reading events from the queue until the task is interrupted.
        The events received are balance updates, order updates and trade events.
        """
        async for event_message in self._iter_user_event_queue():
            try:
                event_type = event_message.get("type")
                event_subject = event_message.get("subject")
                execution_data = event_message.get("data")

                # Refer to https://docs.kucoin.com/#private-order-change-events
                if event_type == "message" and event_subject == CONSTANTS.ORDER_CHANGE_EVENT_TYPE:
                    order_event_type = execution_data["type"]
                    client_order_id: Optional[str] = execution_data.get("clientOid")

                    tracked_order = self._order_tracker.fetch_order(client_order_id=client_order_id)

                    if tracked_order is not None:
                        event_timestamp = execution_data["ts"] * 1e-9

                        if order_event_type == "match":
                            execute_amount_diff = Decimal(execution_data["matchSize"])
                            execute_price = Decimal(execution_data["matchPrice"])

                            fee = self.get_fee(
                                tracked_order.base_asset,
                                tracked_order.quote_asset,
                                tracked_order.order_type,
                                tracked_order.trade_type,
                                execute_price,
                                execute_amount_diff,
                            )

                            trade_update = TradeUpdate(
                                trade_id=execution_data["tradeId"],
                                client_order_id=client_order_id,
                                exchange_order_id=execution_data["orderId"],
                                trading_pair=tracked_order.trading_pair,
                                fee=fee,
                                fill_base_amount=execute_amount_diff,
                                fill_quote_amount=execute_amount_diff * execute_price,
                                fill_price=execute_price,
                                fill_timestamp=event_timestamp,
                            )
                            self._order_tracker.process_trade_update(trade_update)

                        updated_status = tracked_order.current_state
                        if order_event_type == "open":
                            updated_status = OrderState.OPEN
                        elif order_event_type == "match":
                            updated_status = OrderState.PARTIALLY_FILLED
                        elif order_event_type == "filled":
                            updated_status = OrderState.FILLED
                        elif order_event_type == "canceled":
                            updated_status = OrderState.CANCELED

                        order_update = OrderUpdate(
                            trading_pair=tracked_order.trading_pair,
                            update_timestamp=event_timestamp,
                            new_state=updated_status,
                            client_order_id=client_order_id,
                            exchange_order_id=execution_data["orderId"],
                        )
                        self._order_tracker.process_order_update(order_update=order_update)

                elif event_type == "message" and event_subject == CONSTANTS.BALANCE_EVENT_TYPE:
                    currency = execution_data["currency"]
                    available_balance = Decimal(execution_data["available"])
                    total_balance = Decimal(execution_data["total"])
                    self._account_balances.update({currency: total_balance})
                    self._account_available_balances.update({currency: available_balance})

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().exception("Unexpected error in user stream listener loop.")
                await self._sleep(5.0)
Ejemplo n.º 18
0
    async def _user_stream_event_listener(self):
        """
        This functions runs in background continuously processing the events received from the exchange by the user
        stream data source. It keeps reading events from the queue until the task is interrupted.
        The events received are balance updates, order updates and trade events.
        """
        async for event_message in self._iter_user_event_queue():
            try:
                event_type = event_message.get("table")
                if event_type == "order":
                    order_data = event_message["data"][0]
                    client_order_id = order_data.get("clientOrderId")

                    tracked_order = self.in_flight_orders.get(client_order_id)
                    if tracked_order is not None:
                        async with timeout(self._sleep_time(5)):
                            await tracked_order.get_exchange_order_id()
                        exec_amt_base = coinflex_utils.decimal_val_or_none(
                            order_data.get("matchQuantity"))
                        if exec_amt_base:
                            fill_price = coinflex_utils.decimal_val_or_none(
                                order_data.get("matchPrice"))
                            exec_amt_quote = exec_amt_base * fill_price if exec_amt_base and fill_price else None
                            fee_paid = coinflex_utils.decimal_val_or_none(
                                order_data.get("fees"))
                            if fee_paid:
                                fee = TradeFeeBase.new_spot_fee(
                                    fee_schema=self.trade_fee_schema(),
                                    trade_type=tracked_order.trade_type,
                                    percent_token=order_data.get(
                                        "feeInstrumentId"),
                                    flat_fees=[
                                        TokenAmount(amount=fee_paid,
                                                    token=order_data.get(
                                                        "feeInstrumentId"))
                                    ])
                            else:
                                fee = self.get_fee(
                                    base_currency=tracked_order.base_asset,
                                    quote_currency=tracked_order.quote_asset,
                                    order_type=tracked_order.order_type,
                                    order_side=tracked_order.trade_type,
                                    amount=tracked_order.amount,
                                    price=tracked_order.price,
                                    is_maker=True)
                            trade_update = TradeUpdate(
                                trading_pair=tracked_order.trading_pair,
                                trade_id=int(order_data["matchId"]),
                                client_order_id=client_order_id,
                                exchange_order_id=str(order_data["orderId"]),
                                fill_timestamp=int(order_data["timestamp"]) *
                                1e-3,
                                fill_price=fill_price,
                                fill_base_amount=exec_amt_base,
                                fill_quote_amount=exec_amt_quote,
                                fee=fee,
                            )
                            self._order_tracker.process_trade_update(
                                trade_update=trade_update)
                        order_update = OrderUpdate(
                            trading_pair=tracked_order.trading_pair,
                            update_timestamp=int(order_data["timestamp"]) *
                            1e-3,
                            new_state=CONSTANTS.ORDER_STATE[
                                order_data["status"]],
                            client_order_id=client_order_id,
                            exchange_order_id=str(order_data["orderId"]),
                        )
                        self._order_tracker.process_order_update(
                            order_update=order_update)

                elif event_type == "balance":
                    self._process_balance_message(event_message)

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().error(
                    "Unexpected error in user stream listener loop.",
                    exc_info=True)
                await asyncio.sleep(5.0)
Ejemplo n.º 19
0
    async def _user_stream_event_listener(self):
        """
        This functions runs in background continuously processing the events received from the exchange by the user
        stream data source. It keeps reading events from the queue until the task is interrupted.
        The events received are balance updates, order updates and trade events.
        """
        async for event_message in self._iter_user_event_queue():
            try:
                event_type = event_message.get("e")
                if event_type == "executionReport":
                    execution_type = event_message.get("X")
                    client_order_id = event_message.get("c")
                    tracked_order = self._order_tracker.fetch_order(
                        client_order_id=client_order_id)
                    if tracked_order is not None:
                        if execution_type in ["PARTIALLY_FILLED", "FILLED"]:
                            fee = TradeFeeBase.new_spot_fee(
                                fee_schema=self.trade_fee_schema(),
                                trade_type=tracked_order.trade_type,
                                flat_fees=[
                                    TokenAmount(amount=Decimal(
                                        event_message["n"]),
                                                token=event_message["N"])
                                ])
                            trade_update = TradeUpdate(
                                trade_id=str(event_message["E"]),
                                client_order_id=client_order_id,
                                exchange_order_id=str(event_message["i"]),
                                trading_pair=tracked_order.trading_pair,
                                fee=fee,
                                fill_base_amount=Decimal(event_message["l"]),
                                fill_quote_amount=Decimal(event_message["l"]) *
                                Decimal(event_message["L"]),
                                fill_price=Decimal(event_message["L"]),
                                fill_timestamp=int(event_message["E"]) * 1e-3,
                            )
                            self._order_tracker.process_trade_update(
                                trade_update)

                        order_update = OrderUpdate(
                            trading_pair=tracked_order.trading_pair,
                            update_timestamp=int(event_message["E"]) * 1e-3,
                            new_state=CONSTANTS.ORDER_STATE[
                                event_message["X"]],
                            client_order_id=client_order_id,
                            exchange_order_id=str(event_message["i"]),
                        )
                        self._order_tracker.process_order_update(
                            order_update=order_update)

                elif event_type == "outboundAccountInfo":
                    balances = event_message["B"]
                    for balance_entry in balances:
                        asset_name = balance_entry["a"]
                        free_balance = Decimal(balance_entry["f"])
                        total_balance = Decimal(balance_entry["f"]) + Decimal(
                            balance_entry["l"])
                        self._account_available_balances[
                            asset_name] = free_balance
                        self._account_balances[asset_name] = total_balance

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().error(
                    "Unexpected error in user stream listener loop.",
                    exc_info=True)
                await self._sleep(5.0)
    def test_update_to_close_order_is_not_processed_until_order_completelly_filled(
            self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
            creation_timestamp=1640001112.223,
        )
        self.tracker.start_tracking_order(order)

        order_creation_update: OrderUpdate = OrderUpdate(
            client_order_id=order.client_order_id,
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        trade_update: TradeUpdate = TradeUpdate(
            trade_id="1",
            client_order_id=order.client_order_id,
            exchange_order_id=order.exchange_order_id,
            trading_pair=order.trading_pair,
            fill_price=Decimal("1100"),
            fill_base_amount=order.amount,
            fill_quote_amount=order.amount * Decimal("1100"),
            fee=AddedToCostTradeFee(flat_fees=[
                TokenAmount(token=self.quote_asset, amount=Decimal("10"))
            ]),
            fill_timestamp=10,
        )

        order_completion_update: OrderUpdate = OrderUpdate(
            client_order_id=order.client_order_id,
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=2,
            new_state=OrderState.FILLED,
        )

        # We invert the orders update processing on purpose, to force the test scenario without using sleeps
        self.connector._set_current_timestamp(1640001100)
        completion_update_future = self.tracker.process_order_update(
            order_completion_update)

        self.connector._set_current_timestamp(1640001105)
        creation_update_future = self.tracker.process_order_update(
            order_creation_update)
        self.async_run_with_timeout(creation_update_future)

        order: InFlightOrder = self.tracker.fetch_order(
            client_order_id=order.client_order_id)

        # Check order_creation_update has been successfully applied
        self.assertFalse(order.is_done)
        self.assertFalse(order.is_filled)
        self.assertFalse(order.completely_filled_event.is_set())

        fill_timetamp = 1640001115
        self.connector._set_current_timestamp(fill_timetamp)
        self.tracker.process_trade_update(trade_update)
        self.assertTrue(order.completely_filled_event.is_set())

        self.connector._set_current_timestamp(1640001120)
        self.async_run_with_timeout(completion_update_future)

        self.assertTrue(order.is_filled)
        fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0]
        self.assertEqual(fill_timetamp, fill_event.timestamp)

        complete_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[
            0]
        self.assertGreaterEqual(complete_event.timestamp, 1640001120)
Ejemplo n.º 21
0
    def test_average_executed_price(self):
        order_0: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )

        self.assertIsNone(order_0.average_executed_price)

        trade_update_0: TradeUpdate = TradeUpdate(
            trade_id="someTradeId",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=order_0.trading_pair,
            fill_price=order_0.price,
            fill_base_amount=order_0.amount,
            fill_quote_amount=(order_0.price * order_0.amount),
            fee=AddedToCostTradeFee(flat_fees=[TokenAmount(self.base_asset, Decimal(0.01) * order_0.amount)]),
            fill_timestamp=time.time(),
        )
        # Order completely filled after single trade update
        order_0.order_fills.update({trade_update_0.trade_id: trade_update_0})

        self.assertEqual(order_0.price, order_0.average_executed_price)

        order_1: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )

        trade_update_1: TradeUpdate = TradeUpdate(
            trade_id="someTradeId_1",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=order_1.trading_pair,
            fill_price=Decimal("0.5"),
            fill_base_amount=(order_1.amount / Decimal("2.0")),
            fill_quote_amount=(order_1.price * (order_1.amount / Decimal("2.0"))),
            fee=AddedToCostTradeFee(
                flat_fees=[TokenAmount(self.base_asset, Decimal(0.01) * (order_1.amount / Decimal("2.0")))]),
            fill_timestamp=time.time(),
        )

        trade_update_2: TradeUpdate = TradeUpdate(
            trade_id="someTradeId_2",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=order_1.trading_pair,
            fill_price=order_1.price,
            fill_base_amount=(order_1.amount / Decimal("2.0")),
            fill_quote_amount=(order_1.price * (order_1.amount / Decimal("2.0"))),
            fee=AddedToCostTradeFee(
                flat_fees=[TokenAmount(self.base_asset, Decimal(0.01) * (order_1.amount / Decimal("2.0")))]),
            fill_timestamp=time.time(),
        )

        # Order completely filled after 2 trade updates
        order_1.order_fills.update(
            {
                trade_update_1.trade_id: trade_update_1,
                trade_update_2.trade_id: trade_update_2,
            }
        )
        expected_average_price = (
            sum([order_fill.fill_price * order_fill.fill_base_amount for order_fill in order_1.order_fills.values()])
            / order_1.amount
        )
        self.assertEqual(expected_average_price, order_1.average_executed_price)
Ejemplo n.º 22
0
    async def _user_stream_event_listener(self):
        """
        This functions runs in background continuously processing the events received from the exchange by the user
        stream data source. It keeps reading events from the queue until the task is interrupted.
        The events received are balance updates, order updates and trade events.
        """
        async for event_message in self._iter_user_event_queue():
            try:
                event_type = event_message.get("e")
                # Refer to https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md
                # As per the order update section in Binance the ID of the order being cancelled is under the "C" key
                if event_type == "executionReport":
                    execution_type = event_message.get("x")
                    if execution_type != "CANCELED":
                        client_order_id = event_message.get("c")
                    else:
                        client_order_id = event_message.get("C")

                    if execution_type == "TRADE":
                        tracked_order = self._order_tracker.fetch_order(
                            client_order_id=client_order_id)
                        if tracked_order is not None:
                            trade_update = TradeUpdate(
                                trade_id=str(event_message["t"]),
                                client_order_id=client_order_id,
                                exchange_order_id=str(event_message["i"]),
                                trading_pair=tracked_order.trading_pair,
                                fee_asset=event_message["N"],
                                fee_paid=Decimal(event_message["n"]),
                                fill_base_amount=Decimal(event_message["l"]),
                                fill_quote_amount=Decimal(event_message["l"]) *
                                Decimal(event_message["L"]),
                                fill_price=Decimal(event_message["L"]),
                                fill_timestamp=int(event_message["T"]),
                            )
                            self._order_tracker.process_trade_update(
                                trade_update)

                    tracked_order = self.in_flight_orders.get(client_order_id)
                    if tracked_order is not None:
                        order_update = OrderUpdate(
                            trading_pair=tracked_order.trading_pair,
                            update_timestamp=int(event_message["E"]),
                            new_state=CONSTANTS.ORDER_STATE[
                                event_message["X"]],
                            client_order_id=client_order_id,
                            exchange_order_id=str(event_message["i"]),
                        )
                        self._order_tracker.process_order_update(
                            order_update=order_update)

                elif event_type == "outboundAccountPosition":
                    balances = event_message["B"]
                    for balance_entry in balances:
                        asset_name = balance_entry["a"]
                        free_balance = Decimal(balance_entry["f"])
                        total_balance = Decimal(balance_entry["f"]) + Decimal(
                            balance_entry["l"])
                        self._account_available_balances[
                            asset_name] = free_balance
                        self._account_balances[asset_name] = total_balance

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().error(
                    "Unexpected error in user stream listener loop.",
                    exc_info=True)
                await asyncio.sleep(5.0)
Ejemplo n.º 23
0
    async def _user_stream_event_listener(self):
        async for stream_message in self._iter_user_event_queue():
            try:
                args = stream_message.get("arg", {})
                channel = args.get("channel", None)

                if channel == CONSTANTS.OKX_WS_ORDERS_CHANNEL:
                    for data in stream_message.get("data", []):
                        order_status = CONSTANTS.ORDER_STATE[data["state"]]
                        tracked_order = self._order_tracker.fetch_order(
                            client_order_id=data["clOrdId"])

                        if tracked_order is not None:
                            if order_status in [
                                    OrderState.PARTIALLY_FILLED,
                                    OrderState.FILLED
                            ]:
                                fee = TradeFeeBase.new_spot_fee(
                                    fee_schema=self.trade_fee_schema(),
                                    trade_type=tracked_order.trade_type,
                                    percent_token=data["fillFeeCcy"],
                                    flat_fees=[
                                        TokenAmount(amount=Decimal(
                                            data["fillFee"]),
                                                    token=data["fillFeeCcy"])
                                    ])
                                trade_update = TradeUpdate(
                                    trade_id=str(data["tradeId"]),
                                    client_order_id=tracked_order.
                                    client_order_id,
                                    exchange_order_id=str(data["ordId"]),
                                    trading_pair=tracked_order.trading_pair,
                                    fee=fee,
                                    fill_base_amount=Decimal(data["fillSz"]),
                                    fill_quote_amount=Decimal(data["fillSz"]) *
                                    Decimal(data["fillPx"]),
                                    fill_price=Decimal(data["fillPx"]),
                                    fill_timestamp=int(data["uTime"]) * 1e-3,
                                )
                                self._order_tracker.process_trade_update(
                                    trade_update)

                            order_update = OrderUpdate(
                                trading_pair=tracked_order.trading_pair,
                                update_timestamp=int(data["uTime"]) * 1e-3,
                                new_state=order_status,
                                client_order_id=tracked_order.client_order_id,
                                exchange_order_id=str(data["ordId"]),
                            )
                            self._order_tracker.process_order_update(
                                order_update=order_update)

                elif channel == CONSTANTS.OKX_WS_ACCOUNT_CHANNEL:
                    for data in stream_message.get("data", []):
                        for details in data.get("details", []):
                            self._update_balance_from_details(
                                balance_details=details)

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().exception(
                    "Unexpected error in user stream listener loop.")
                await self._sleep(5.0)
Ejemplo n.º 24
0
    async def _update_order_status(self):
        tracked_orders = list(self.in_flight_orders.values())
        order_tasks = []
        order_fills_tasks = []

        if tracked_orders:
            # OKX was failing randomly to do the order update because the connector was creating a rejected signature.
            # To avoid the issue we need to make sure the time synchronizer is updated before doing the requests.

            # Also we add a retry logic in case there are problems with the signature

            for retry_count in range(3):
                invalid_signature_happened = False
                try:
                    await self._update_time_synchronizer()
                except asyncio.CancelledError:
                    raise
                except Exception:
                    pass

                for order in tracked_orders:
                    order_tasks.append(
                        asyncio.create_task(
                            self._request_order_update(order=order)))
                    order_fills_tasks.append(
                        asyncio.create_task(
                            self._request_order_fills(order=order)))
                self.logger().debug(
                    f"Polling for order status updates of {len(order_tasks)} orders."
                )

                order_updates = await safe_gather(*order_tasks,
                                                  return_exceptions=True)
                order_fills = await safe_gather(*order_fills_tasks,
                                                return_exceptions=True)
                for order_update, order_fill, tracked_order in zip(
                        order_updates, order_fills, tracked_orders):
                    client_order_id = tracked_order.client_order_id

                    # If the order has already been cancelled or has failed do nothing
                    if client_order_id not in self.in_flight_orders:
                        continue

                    if isinstance(order_fill, Exception):
                        if '"code":"50113"' in str(order_fill):
                            invalid_signature_happened = True
                        else:
                            self.logger().network(
                                f"Error fetching order fills for the order {client_order_id}: {order_fill}.",
                                app_warning_msg=
                                f"Failed to fetch status update for the order {client_order_id}."
                            )

                    else:
                        fills_data = order_fill["data"]

                        for fill_data in fills_data:
                            fee = TradeFeeBase.new_spot_fee(
                                fee_schema=self.trade_fee_schema(),
                                trade_type=tracked_order.trade_type,
                                percent_token=fill_data["feeCcy"],
                                flat_fees=[
                                    TokenAmount(amount=Decimal(
                                        fill_data["fee"]),
                                                token=fill_data["feeCcy"])
                                ])
                            trade_update = TradeUpdate(
                                trade_id=str(fill_data["tradeId"]),
                                client_order_id=client_order_id,
                                exchange_order_id=str(fill_data["ordId"]),
                                trading_pair=tracked_order.trading_pair,
                                fee=fee,
                                fill_base_amount=Decimal(fill_data["fillSz"]),
                                fill_quote_amount=Decimal(fill_data["fillSz"])
                                * Decimal(fill_data["fillPx"]),
                                fill_price=Decimal(fill_data["fillPx"]),
                                fill_timestamp=int(fill_data["ts"]) * 1e-3,
                            )
                            self._order_tracker.process_trade_update(
                                trade_update)

                    if isinstance(order_update, Exception):
                        if '"code":"50113"' in str(order_fill):
                            invalid_signature_happened = True
                        else:
                            self.logger().network(
                                f"Error fetching status update for the order {client_order_id}: {order_update}.",
                                app_warning_msg=
                                f"Failed to fetch status update for the order {client_order_id}."
                            )
                            await self._order_tracker.process_order_not_found(
                                client_order_id)

                    else:
                        # Update order execution status
                        order_data = order_update["data"][0]
                        new_state = CONSTANTS.ORDER_STATE[order_data["state"]]

                        update = OrderUpdate(
                            client_order_id=client_order_id,
                            exchange_order_id=str(order_data["ordId"]),
                            trading_pair=tracked_order.trading_pair,
                            update_timestamp=int(order_data["uTime"]) * 1e-3,
                            new_state=new_state,
                        )
                        self._order_tracker.process_order_update(update)

                if invalid_signature_happened:
                    self._time_synchronizer.clear_time_offset_ms_samples()
                else:
                    return  # Stop retries if there were no errors
Ejemplo n.º 25
0
    def test_update_with_trade_update_multiple_trade_updates(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
        )

        initial_fill_price: Decimal = Decimal("0.5")
        initial_fill_amount: Decimal = Decimal("500.0")
        trade_update_1: TradeUpdate = TradeUpdate(
            trade_id="someTradeId_1",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            fill_price=initial_fill_price,
            fill_base_amount=initial_fill_amount,
            fill_quote_amount=initial_fill_price * initial_fill_amount,
            fee_asset=self.base_asset,
            fee_paid=self.trade_fee_percent * initial_fill_amount,
            fill_timestamp=1,
        )

        subsequent_fill_price: Decimal = Decimal("1.0")
        subsequent_fill_amount: Decimal = Decimal("500.0")
        trade_update_2: TradeUpdate = TradeUpdate(
            trade_id="someTradeId_2",
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            fill_price=subsequent_fill_price,
            fill_base_amount=subsequent_fill_amount,
            fill_quote_amount=subsequent_fill_price * subsequent_fill_amount,
            fee_asset=self.base_asset,
            fee_paid=self.trade_fee_percent * subsequent_fill_amount,
            fill_timestamp=2,
        )

        self.assertTrue(order.update_with_trade_update(trade_update_1))
        self.assertIn(trade_update_1.trade_id, order.order_fills)
        self.assertIsNotNone(order.exchange_order_id)
        self.assertTrue(order.exchange_order_id_update_event.is_set())
        self.assertEqual(order.executed_amount_base, trade_update_1.fill_base_amount)
        self.assertEqual(order.executed_amount_quote, trade_update_1.fill_quote_amount)
        self.assertEqual(order.fee_asset, trade_update_1.fee_asset)
        self.assertEqual(order.cumulative_fee_paid, trade_update_1.fee_paid)
        self.assertEqual(order.last_filled_price, trade_update_1.fill_price)
        self.assertEqual(order.last_filled_amount, trade_update_1.fill_base_amount)
        self.assertEqual(order.last_update_timestamp, trade_update_1.fill_timestamp)
        self.assertEqual(1, len(order.order_fills))

        self.assertTrue(order.is_open)

        self.assertTrue(order.update_with_trade_update(trade_update_2))
        self.assertIn(trade_update_2.trade_id, order.order_fills)
        self.assertEqual(order.executed_amount_base, order.amount)
        self.assertEqual(
            order.executed_amount_quote, trade_update_1.fill_quote_amount + trade_update_2.fill_quote_amount
        )
        self.assertEqual(order.fee_asset, trade_update_2.fee_asset)
        self.assertEqual(order.cumulative_fee_paid, trade_update_1.fee_paid + trade_update_2.fee_paid)
        self.assertEqual(order.last_filled_price, trade_update_2.fill_price)
        self.assertEqual(order.last_filled_amount, trade_update_2.fill_base_amount)
        self.assertEqual(order.last_update_timestamp, trade_update_2.fill_timestamp)
        self.assertEqual(2, len(order.order_fills))
        self.assertEqual(
            order.average_executed_price,
            (trade_update_1.fill_quote_amount + trade_update_2.fill_quote_amount) / order.amount,
        )

        self.assertTrue(order.is_filled)
        self.assertEqual(order.current_state, OrderState.FILLED)
    def test_process_trade_update_trigger_filled_event_update_status_when_completely_filled(
            self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )
        self.tracker.start_tracking_order(order)

        fee_paid: Decimal = self.trade_fee_percent * order.amount
        trade_update: TradeUpdate = TradeUpdate(
            trade_id=1,
            client_order_id=order.client_order_id,
            exchange_order_id=order.exchange_order_id,
            trading_pair=order.trading_pair,
            fill_price=order.price,
            fill_base_amount=order.amount,
            fill_quote_amount=order.price * order.amount,
            fee_asset=self.base_asset,
            fee_paid=fee_paid,
            fill_timestamp=1,
        )

        self.tracker.process_trade_update(trade_update)

        fetched_order: InFlightOrder = self.tracker.fetch_order(
            order.client_order_id)
        self.assertTrue(fetched_order.is_filled)
        self.assertNotIn(fetched_order.client_order_id,
                         self.tracker.active_orders)
        self.assertIn(fetched_order.client_order_id,
                      self.tracker.cached_orders)

        self.assertTrue(
            self._is_logged(
                "INFO",
                f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to "
                f"{order.amount}/{order.amount} {order.base_asset} has been filled.",
            ))

        self.assertEqual(2, len(self.connector.event_logs))

        order_filled_event: Optional[OrderFilledEvent] = None
        order_completed_event: Optional[BuyOrderCompletedEvent] = None
        for event in self.connector.event_logs:
            if isinstance(event, OrderFilledEvent):
                order_filled_event = event
            if isinstance(event, BuyOrderCompletedEvent):
                order_completed_event = event

        self.assertIsNotNone(order_filled_event)
        self.assertIsNotNone(order_completed_event)

        self.assertEqual(order_filled_event.order_id, order.client_order_id)
        self.assertEqual(order_filled_event.price, trade_update.fill_price)
        self.assertEqual(order_filled_event.amount,
                         trade_update.fill_base_amount)
        self.assertEqual(
            order_filled_event.trade_fee,
            AddedToCostTradeFee(
                flat_fees=[TokenAmount(self.base_asset, fee_paid)]))

        self.assertEqual(order_completed_event.order_id, order.client_order_id)
        self.assertEqual(order_completed_event.exchange_order_id,
                         order.exchange_order_id)
        self.assertEqual(order_completed_event.base_asset_amount, order.amount)
        self.assertEqual(
            order_completed_event.quote_asset_amount,
            trade_update.fill_price * trade_update.fill_base_amount)
        self.assertEqual(order_completed_event.fee_amount,
                         trade_update.fee_paid)
Ejemplo n.º 27
0
    async def _update_order_fills_from_trades(self):
        """
        This is intended to be a backup measure to get filled events with trade ID for orders,
        in case Binance's user stream events are not working.
        NOTE: It is not required to copy this functionality in other connectors.
        This is separated from _update_order_status which only updates the order status without producing filled
        events, since Binance's get order endpoint does not return trade IDs.
        The minimum poll interval for order status is 10 seconds.
        """
        small_interval_last_tick = self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL
        small_interval_current_tick = self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL
        long_interval_last_tick = self._last_poll_timestamp / self.LONG_POLL_INTERVAL
        long_interval_current_tick = self.current_timestamp / self.LONG_POLL_INTERVAL

        if (long_interval_current_tick > long_interval_last_tick or
            (self.in_flight_orders
             and small_interval_current_tick > small_interval_last_tick)):
            query_time = int(self._last_trades_poll_binance_timestamp * 1e3)
            self._last_trades_poll_binance_timestamp = self._binance_time_synchronizer.time(
            )
            order_by_exchange_id_map = {}
            for order in self._order_tracker.all_orders.values():
                order_by_exchange_id_map[order.exchange_order_id] = order

            tasks = []
            trading_pairs = self._order_book_tracker._trading_pairs
            for trading_pair in trading_pairs:
                params = {
                    "symbol":
                    await BinanceAPIOrderBookDataSource.
                    exchange_symbol_associated_to_pair(
                        trading_pair=trading_pair,
                        domain=self._domain,
                        api_factory=self._api_factory,
                        throttler=self._throttler)
                }
                if self._last_poll_timestamp > 0:
                    params["startTime"] = query_time
                tasks.append(
                    self._api_request(method=RESTMethod.GET,
                                      path_url=CONSTANTS.MY_TRADES_PATH_URL,
                                      params=params,
                                      is_auth_required=True))

            self.logger().debug(
                f"Polling for order fills of {len(tasks)} trading pairs.")
            results = await safe_gather(*tasks, return_exceptions=True)

            for trades, trading_pair in zip(results, trading_pairs):

                if isinstance(trades, Exception):
                    self.logger().network(
                        f"Error fetching trades update for the order {trading_pair}: {trades}.",
                        app_warning_msg=
                        f"Failed to fetch trade update for {trading_pair}.")
                    continue
                for trade in trades:
                    exchange_order_id = str(trade["orderId"])
                    if exchange_order_id in order_by_exchange_id_map:
                        # This is a fill for a tracked order
                        tracked_order = order_by_exchange_id_map[
                            exchange_order_id]
                        trade_update = TradeUpdate(
                            trade_id=str(trade["id"]),
                            client_order_id=tracked_order.client_order_id,
                            exchange_order_id=exchange_order_id,
                            trading_pair=trading_pair,
                            fee_asset=trade["commissionAsset"],
                            fee_paid=Decimal(trade["commission"]),
                            fill_base_amount=Decimal(trade["qty"]),
                            fill_quote_amount=Decimal(trade["quoteQty"]),
                            fill_price=Decimal(trade["price"]),
                            fill_timestamp=int(trade["time"]),
                        )
                        self._order_tracker.process_trade_update(trade_update)
                    elif self.is_confirmed_new_order_filled_event(
                            str(trade["id"]), exchange_order_id, trading_pair):
                        # This is a fill of an order registered in the DB but not tracked any more
                        self._current_trade_fills.add(
                            TradeFillOrderDetails(market=self.display_name,
                                                  exchange_trade_id=str(
                                                      trade["id"]),
                                                  symbol=trading_pair))
                        self.trigger_event(
                            MarketEvent.OrderFilled,
                            OrderFilledEvent(
                                timestamp=float(trade["time"]) * 1e-3,
                                order_id=self._exchange_order_ids.get(
                                    str(trade["orderId"]), None),
                                trading_pair=trading_pair,
                                trade_type=TradeType.BUY
                                if trade["isBuyer"] else TradeType.SELL,
                                order_type=OrderType.LIMIT_MAKER
                                if trade["isMaker"] else OrderType.LIMIT,
                                price=Decimal(trade["price"]),
                                amount=Decimal(trade["qty"]),
                                trade_fee=DeductedFromReturnsTradeFee(
                                    flat_fees=[
                                        TokenAmount(
                                            trade["commissionAsset"],
                                            Decimal(trade["commission"]))
                                    ]),
                                exchange_trade_id=str(trade["id"])))
                        self.logger().info(
                            f"Recreating missing trade in TradeFill: {trade}")
Ejemplo n.º 28
0
    async def update_order_status(self,
                                  tracked_orders: List[GatewayInFlightOrder]):
        """
        Calls REST API to get status update for each in-flight amm orders.
        """
        if len(tracked_orders) < 1:
            return

        # split canceled and non-canceled orders
        tx_hash_list: List[str] = await safe_gather(*[
            tracked_order.get_exchange_order_id()
            for tracked_order in tracked_orders
        ])
        self.logger().debug("Polling for order status updates of %d orders.",
                            len(tracked_orders))
        update_results: List[Union[Dict[
            str, Any], Exception]] = await safe_gather(*[
                self._get_gateway_instance().get_transaction_status(
                    self.chain, self.network, tx_hash)
                for tx_hash in tx_hash_list
            ],
                                                       return_exceptions=True)
        for tracked_order, tx_details in zip(tracked_orders, update_results):
            if isinstance(tx_details, Exception):
                self.logger().error(
                    f"An error occurred fetching transaction status of {tracked_order.client_order_id}"
                )
                continue
            if "txHash" not in tx_details:
                self.logger().error(
                    f"No txHash field for transaction status of {tracked_order.client_order_id}: "
                    f"{tx_details}.")
                continue
            tx_status: int = tx_details["txStatus"]
            tx_receipt: Optional[Dict[str, Any]] = tx_details["txReceipt"]
            if tx_status == 1 and (tx_receipt is not None
                                   and tx_receipt.get("status") == 1):
                gas_used: int = tx_receipt["gasUsed"]
                gas_price: Decimal = tracked_order.gas_price
                fee: Decimal = Decimal(str(gas_used)) * Decimal(
                    str(gas_price)) / Decimal(str(1e9))

                trade_fee: TradeFeeBase = AddedToCostTradeFee(flat_fees=[
                    TokenAmount(tracked_order.fee_asset, Decimal(str(fee)))
                ])
                trade_update: TradeUpdate = TradeUpdate(
                    trade_id=tracked_order.exchange_order_id,
                    client_order_id=tracked_order.client_order_id,
                    exchange_order_id=tracked_order.exchange_order_id,
                    trading_pair=tracked_order.trading_pair,
                    fill_timestamp=self.current_timestamp,
                    fill_price=tracked_order.price,
                    fill_base_amount=tracked_order.amount,
                    fill_quote_amount=tracked_order.amount *
                    tracked_order.price,
                    fee=trade_fee)

                self._order_tracker.process_trade_update(trade_update)

                order_update: OrderUpdate = OrderUpdate(
                    client_order_id=tracked_order.client_order_id,
                    trading_pair=tracked_order.trading_pair,
                    update_timestamp=self.current_timestamp,
                    new_state=OrderState.FILLED,
                )
                self._order_tracker.process_order_update(order_update)
            elif tx_status in [0, 2, 3]:
                # 0: in the mempool but we dont have data to guess its status
                # 2: in the mempool and likely to succeed
                # 3: in the mempool and likely to fail
                pass

            elif tx_status == -1 or (tx_receipt is not None
                                     and tx_receipt.get("status") == 0):
                self.logger().network(
                    f"Error fetching transaction status for the order {tracked_order.client_order_id}: {tx_details}.",
                    app_warning_msg=
                    f"Failed to fetch transaction status for the order {tracked_order.client_order_id}."
                )
                await self._order_tracker.process_order_not_found(
                    tracked_order.client_order_id)