async def _cancel_order(self, command: CancelOrder) -> None: self._log.debug(f"Canceling order {command.client_order_id.value}.") self.generate_order_pending_cancel( strategy_id=command.strategy_id, instrument_id=command.instrument_id, client_order_id=command.client_order_id, venue_order_id=command.venue_order_id, ts_event=self._clock.timestamp_ns(), ) try: if command.venue_order_id is not None: await self._http_account.cancel_order( symbol=format_symbol(command.instrument_id.symbol.value), order_id=command.venue_order_id.value, ) else: await self._http_account.cancel_order( symbol=format_symbol(command.instrument_id.symbol.value), orig_client_order_id=command.client_order_id.value, ) except BinanceError as ex: self._log.exception( f"Cannot cancel order " f"ClientOrderId({command.client_order_id}), " f"VenueOrderId{command.venue_order_id}: ", ex, )
async def avg_price(self, symbol: str) -> Dict[str, Any]: """ Get the current average price for the given symbol. `GET /api/v3/avgPrice` Parameters ---------- symbol : str The trading pair. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#current-average-price """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} raw: bytes = await self.client.query( url_path=self.BASE_ENDPOINT + "avgPrice", payload=payload, ) return orjson.loads(raw)
async def create_listen_key_isolated_margin(self, symbol: str) -> Dict[str, Any]: """ Create a new listen key for the ISOLATED MARGIN API. Start a new user data stream. The stream will close after 60 minutes unless a keepalive is sent. If the account has an active listenKey, that listenKey will be returned and its validity will be extended for 60 minutes. Create a ListenKey (USER_STREAM). `POST /api/v3/userDataStream `. Parameters ---------- symbol : str The symbol for the listen key request. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin """ raw: bytes = await self.client.send_request( http_method="POST", url_path="/sapi/v1/userDataStream/isolated", payload={"symbol": format_symbol(symbol)}, ) return orjson.loads(raw)
async def close_listen_key_isolated_margin(self, symbol: str, key: str) -> Dict[str, Any]: """ Close a listen key for the ISOLATED MARGIN API. Close a ListenKey (USER_STREAM). `DELETE /sapi/v1/userDataStream`. Parameters ---------- symbol : str The symbol for the listen key request. key : str The listen key for the request. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin """ raw: bytes = await self.client.send_request( http_method="DELETE", url_path="/sapi/v1/userDataStream/isolated", payload={"listenKey": key, "symbol": format_symbol(symbol)}, ) return orjson.loads(raw)
async def ticker_24hr(self, symbol: str = None) -> Dict[str, Any]: """ 24hr Ticker Price Change Statistics. `GET /api/v3/ticker/24hr` Parameters ---------- symbol : str, optional The trading pair. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics """ payload: Dict[str, str] = {} if symbol is not None: payload["symbol"] = format_symbol(symbol) raw: bytes = await self.client.query( url_path=self.BASE_ENDPOINT + "ticker/24hr", payload=payload, ) return orjson.loads(raw)
async def _submit_trailing_stop_market_order( self, order: TrailingStopMarketOrder) -> None: if order.trigger_type in (TriggerType.DEFAULT, TriggerType.LAST): working_type = "CONTRACT_PRICE" elif order.trigger_type == TriggerType.MARK: working_type = "MARK_PRICE" else: self._log.error( f"Cannot submit order: invalid `order.trigger_type`, was " f"{TriggerTypeParser.to_str_py(order.trigger_price)}. {order}", ) return if order.offset_type not in (TrailingOffsetType.DEFAULT, TrailingOffsetType.BASIS_POINTS): self._log.error( f"Cannot submit order: invalid `order.offset_type`, was " f"{TrailingOffsetTypeParser.to_str_py(order.offset_type)} (use `BASIS_POINTS`). " f"{order}", ) return await self._http_account.new_order( symbol=format_symbol(order.instrument_id.symbol.value), side=OrderSideParser.to_str_py(order.side), type=binance_order_type(order), time_in_force=TimeInForceParser.to_str_py(order.time_in_force), quantity=str(order.quantity), activation_price=str(order.trigger_price), callback_rate=str(order.trailing_offset / 100), working_type=working_type, reduce_only=order. is_reduce_only, # Cannot be sent with Hedge-Mode or closePosition new_client_order_id=order.client_order_id.value, recv_window=5000, )
async def book_ticker(self, symbol: str = None) -> Dict[str, Any]: """ Symbol Order Book Ticker. `GET /api/v3/ticker/bookTicker` Parameters ---------- symbol : str, optional The trading pair. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#symbol-order-book-ticker """ payload: Dict[str, str] = {} if symbol is not None: payload["symbol"] = format_symbol(symbol).upper() raw: bytes = await self.client.query( url_path=self.BASE_ENDPOINT + "ticker/bookTicker", payload=payload, ) return orjson.loads(raw)
async def trades(self, symbol: str, limit: Optional[int] = None) -> List[Dict[str, Any]]: """ Get recent market trades. Recent Trades List. `GET /api/v3/trades` Parameters ---------- symbol : str The trading pair. limit : int, optional The limit for the response. Default 500; max 1000. Returns ------- list[dict[str, Any]] References ---------- https://binance-docs.github.io/apidocs/spot/en/#recent-trades-list """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if limit is not None: payload["limit"] = str(limit) raw: bytes = await self.client.query( url_path=self.BASE_ENDPOINT + "trades", payload=payload, ) return orjson.loads(raw)
async def ping_listen_key_isolated_margin(self, symbol: str, key: str) -> Dict[str, Any]: """ Ping/Keep-alive a listen key for the ISOLATED MARGIN API. Keep-alive a user data stream to prevent a time-out. User data streams will close after 60 minutes. It's recommended to send a ping about every 30 minutes. Ping/Keep-alive a ListenKey (USER_STREAM). `PUT /api/v3/userDataStream`. Parameters ---------- symbol : str The symbol for the listen key request. key : str The listen key for the request. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin """ raw: bytes = await self.client.send_request( http_method="PUT", url_path="/sapi/v1/userDataStream/isolated", payload={"listenKey": key, "symbol": format_symbol(symbol)}, ) return orjson.loads(raw)
async def depth(self, symbol: str, limit: Optional[int] = None) -> Dict[str, Any]: """ Get orderbook. `GET /api/v3/depth` Parameters ---------- symbol : str The trading pair. limit : int, optional, default 100 The limit for the response. Default 100; max 5000. Valid limits:[5, 10, 20, 50, 100, 500, 1000, 5000]. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#order-book """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if limit is not None: payload["limit"] = str(limit) raw: bytes = await self.client.query( url_path=self.BASE_ENDPOINT + "depth", payload=payload, ) return orjson.loads(raw)
async def get_orders( self, symbol: str, order_id: Optional[str] = None, start_time: Optional[int] = None, end_time: Optional[int] = None, limit: Optional[int] = None, recv_window: Optional[int] = None, ) -> List[Dict[str, Any]]: """ Get all account orders (open, or closed). All Orders (USER_DATA). Parameters ---------- symbol : str The symbol for the request. order_id : str, optional The order ID for the request. start_time : int, optional The start time (UNIX milliseconds) filter for the request. end_time : int, optional The end time (UNIX milliseconds) filter for the request. limit : int, optional The limit for the response. recv_window : int, optional The response receive window for the request (cannot be greater than 60000). Returns ------- list[dict[str, Any]] References ---------- https://binance-docs.github.io/apidocs/spot/en/#all-orders-user_data https://binance-docs.github.io/apidocs/futures/en/#all-orders-user_data """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if order_id is not None: payload["orderId"] = order_id if start_time is not None: payload["startTime"] = str(start_time) if end_time is not None: payload["endTime"] = str(end_time) if limit is not None: payload["limit"] = str(limit) if recv_window is not None: payload["recvWindow"] = str(recv_window) raw: bytes = await self.client.sign_request( http_method="GET", url_path=self.BASE_ENDPOINT + "allOrders", payload=payload, ) return orjson.loads(raw)
async def _submit_market_order(self, order: MarketOrder) -> None: await self._http_account.new_order( symbol=format_symbol(order.instrument_id.symbol.value), side=OrderSideParser.to_str_py(order.side), type="MARKET", quantity=str(order.quantity), new_client_order_id=order.client_order_id.value, recv_window=5000, )
def test_format_symbol(self): # Arrange symbol = "ethusdt-perp" # Act result = format_symbol(symbol) # Assert assert result == "ETHUSDT"
async def cancel_oco_order( self, symbol: str, order_list_id: Optional[str] = None, list_client_order_id: Optional[str] = None, new_client_order_id: Optional[str] = None, recv_window: Optional[int] = None, ) -> Dict[str, Any]: """ Cancel an entire Order List. Either `order_list_id` or `list_client_order_id` must be provided. Cancel OCO (TRADE). `DELETE /api/v3/orderList`. Parameters ---------- symbol : str The symbol for the request. order_list_id : str, optional The order list ID for the request. list_client_order_id : str, optional The list client order ID for the request. new_client_order_id : str, optional The new client order ID to uniquely identify this request. recv_window : int, optional The response receive window for the request (cannot be greater than 60000). Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#cancel-oco-trade """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if order_list_id is not None: payload["orderListId"] = order_list_id if list_client_order_id is not None: payload["listClientOrderId"] = list_client_order_id if new_client_order_id is not None: payload["newClientOrderId"] = new_client_order_id if recv_window is not None: payload["recvWindow"] = str(recv_window) raw: bytes = await self.client.sign_request( http_method="DELETE", url_path=self.BASE_ENDPOINT + "orderList", payload=payload, ) return orjson.loads(raw)
async def klines( self, symbol: str, interval: str, start_time_ms: Optional[int] = None, end_time_ms: Optional[int] = None, limit: Optional[int] = None, ) -> List[List[Any]]: """ Kline/Candlestick Data. `GET /api/v3/klines` Parameters ---------- symbol : str The trading pair. interval : str The interval of kline, e.g 1m, 5m, 1h, 1d, etc. start_time_ms : int, optional The UNIX timestamp (milliseconds) to get aggregate trades from INCLUSIVE. end_time_ms: int, optional The UNIX timestamp (milliseconds) to get aggregate trades until INCLUSIVE. limit : int, optional The limit for the response. Default 500; max 1000. Returns ------- list[list[Any]] References ---------- https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-data """ payload: Dict[str, str] = { "symbol": format_symbol(symbol), "interval": interval, } if start_time_ms is not None: payload["startTime"] = str(start_time_ms) if end_time_ms is not None: payload["endTime"] = str(end_time_ms) if limit is not None: payload["limit"] = str(limit) raw: bytes = await self.client.query( url_path=self.BASE_ENDPOINT + "klines", payload=payload, ) return orjson.loads(raw)
async def agg_trades( self, symbol: str, from_id: Optional[int] = None, start_time_ms: Optional[int] = None, end_time_ms: Optional[int] = None, limit: Optional[int] = None, ) -> Dict[str, Any]: """ Get recent aggregated market trades. Compressed/Aggregate Trades List. `GET /api/v3/aggTrades` Parameters ---------- symbol : str The trading pair. from_id : int, optional The trade ID to fetch from. Default gets most recent trades. start_time_ms : int, optional The UNIX timestamp (milliseconds) to get aggregate trades from INCLUSIVE. end_time_ms: int, optional The UNIX timestamp (milliseconds) to get aggregate trades until INCLUSIVE. limit : int, optional The limit for the response. Default 500; max 1000. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#compressed-aggregate-trades-list """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if from_id is not None: payload["fromId"] = str(from_id) if start_time_ms is not None: payload["startTime"] = str(start_time_ms) if end_time_ms is not None: payload["endTime"] = str(end_time_ms) if limit is not None: payload["limit"] = str(limit) raw: bytes = await self.client.query( url_path=self.BASE_ENDPOINT + "aggTrades", payload=payload, ) return orjson.loads(raw)
async def _submit_stop_limit_order(self, order: StopLimitOrder) -> None: await self._http_account.new_order( symbol=format_symbol(order.instrument_id.symbol.value), side=OrderSideParser.to_str_py(order.side), type=binance_order_type(order), time_in_force=TimeInForceParser.to_str_py(order.time_in_force), quantity=str(order.quantity), price=str(order.price), stop_price=str(order.trigger_price), iceberg_qty=str(order.display_qty) if order.display_qty is not None else None, new_client_order_id=order.client_order_id.value, recv_window=5000, )
async def get_order( self, symbol: str, order_id: Optional[str] = None, orig_client_order_id: Optional[str] = None, recv_window: Optional[int] = None, ) -> Optional[BinanceFuturesOrder]: """ Check an order's status. Query Order (USER_DATA). `GET TBD`. Parameters ---------- symbol : str The symbol for the request. order_id : str, optional The order ID for the request. orig_client_order_id : str, optional The original client order ID for the request. recv_window : int, optional The response receive window for the request (cannot be greater than 60000). Returns ------- BinanceFuturesOrderMsg or None References ---------- TBD """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if order_id is not None: payload["orderId"] = order_id if orig_client_order_id is not None: payload["origClientOrderId"] = orig_client_order_id if recv_window is not None: payload["recvWindow"] = str(recv_window) raw: bytes = await self.client.sign_request( http_method="GET", url_path=self.BASE_ENDPOINT + "order", payload=payload, ) if raw is None: return None return msgspec.json.decode(raw, type=BinanceFuturesOrder)
async def get_order( self, symbol: str, order_id: Optional[str] = None, orig_client_order_id: Optional[str] = None, recv_window: Optional[int] = None, ) -> Dict[str, Any]: """ Check an order's status. Query Order (USER_DATA). `GET /api/v3/order`. Parameters ---------- symbol : str The symbol for the request. order_id : str, optional The order ID for the request. orig_client_order_id : str, optional The original client order ID for the request. recv_window : int, optional The response receive window for the request (cannot be greater than 60000). Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#query-order-user_data """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if order_id is not None: payload["orderId"] = order_id if orig_client_order_id is not None: payload["origClientOrderId"] = orig_client_order_id if recv_window is not None: payload["recvWindow"] = str(recv_window) raw: bytes = await self.client.sign_request( http_method="GET", url_path=self.BASE_ENDPOINT + "order", payload=payload, ) return orjson.loads(raw)
async def _submit_limit_order(self, order: LimitOrder) -> None: time_in_force = TimeInForceParser.to_str_py(order.time_in_force) if order.is_post_only: time_in_force = "GTX" await self._http_account.new_order( symbol=format_symbol(order.instrument_id.symbol.value), side=OrderSideParser.to_str_py(order.side), type=binance_order_type(order), time_in_force=time_in_force, quantity=str(order.quantity), price=str(order.price), reduce_only=order. is_reduce_only, # Cannot be sent with Hedge-Mode or closePosition new_client_order_id=order.client_order_id.value, recv_window=5000, )
async def historical_trades( self, symbol: str, from_id: Optional[int] = None, limit: Optional[int] = None, ) -> Dict[str, Any]: """ Get older market trades. Old Trade Lookup. `GET /api/v3/historicalTrades` Parameters ---------- symbol : str The trading pair. from_id : int, optional The trade ID to fetch from. Default gets most recent trades. limit : int, optional The limit for the response. Default 500; max 1000. Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#old-trade-lookup """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if limit is not None: payload["limit"] = str(limit) if from_id is not None: payload["fromId"] = str(from_id) raw: bytes = await self.client.limit_request( http_method="GET", url_path=self.BASE_ENDPOINT + "historicalTrades", payload=payload, ) return orjson.loads(raw)
async def exchange_info( self, symbol: str = None, symbols: List[str] = None, ) -> BinanceFuturesExchangeInfo: """ Get current exchange trading rules and symbol information. Only either `symbol` or `symbols` should be passed. Exchange Information. `GET /api/v3/exchangeinfo` Parameters ---------- symbol : str, optional The trading pair. symbols : List[str], optional The list of trading pairs. Returns ------- BinanceFuturesExchangeInfo References ---------- https://binance-docs.github.io/apidocs/spot/en/#exchange-information """ if symbol and symbols: raise ValueError("`symbol` and `symbols` cannot be sent together") payload: Dict[str, str] = {} if symbol is not None: payload["symbol"] = format_symbol(symbol) if symbols is not None: payload["symbols"] = convert_symbols_list_to_json_array(symbols) raw: bytes = await self.client.query( url_path=self.BASE_ENDPOINT + "exchangeInfo", payload=payload, ) return self._decoder_exchange_info.decode(raw)
async def get_position_risk( self, symbol: Optional[str] = None, recv_window: Optional[int] = None, ) -> List[BinanceFuturesPositionRisk]: """ Get current position information. Position Information V2 (USER_DATA)** `GET /fapi/v2/positionRisk` Parameters ---------- symbol : str, optional The trading pair. If None then queries for all symbols. recv_window : int, optional The acceptable receive window for the response. Returns ------- List[BinanceFuturesPositionRisk] References ---------- https://binance-docs.github.io/apidocs/futures/en/#position-information-v2-user_data """ payload: Dict[str, str] = {} if symbol is not None: payload["symbol"] = format_symbol(symbol) if recv_window is not None: payload["recv_window"] = str(recv_window) raw: bytes = await self.client.sign_request( http_method="GET", url_path=self.BASE_ENDPOINT + "positionRisk", payload=payload, ) return self._decoder_position.decode(raw)
async def get_open_orders( self, symbol: Optional[str] = None, recv_window: Optional[int] = None, ) -> List[Dict[str, Any]]: """ Get all open orders for a symbol. Query Current Open Orders (USER_DATA). Parameters ---------- symbol : str, optional The symbol for the request. recv_window : int, optional The response receive window for the request (cannot be greater than 60000). Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#current-open-orders-user_data https://binance-docs.github.io/apidocs/futures/en/#current-open-orders-user_data """ payload: Dict[str, str] = {} if symbol is not None: payload["symbol"] = format_symbol(symbol) if recv_window is not None: payload["recvWindow"] = str(recv_window) raw: bytes = await self.client.sign_request( http_method="GET", url_path=self.BASE_ENDPOINT + "openOrders", payload=payload, ) return orjson.loads(raw)
async def cancel_open_orders( self, symbol: str, recv_window: Optional[int] = None, ) -> Dict[str, Any]: """ Cancel all open orders for a symbol. This includes OCO orders. Cancel all Open Orders for a Symbol (TRADE). `DELETE /fapi/v1/allOpenOrders (HMAC SHA256)`. Parameters ---------- symbol : str The symbol for the request. recv_window : int, optional The response receive window for the request (cannot be greater than 60000). Returns ------- dict[str, Any] References ---------- https://binance-docs.github.io/apidocs/spot/en/#cancel-all-open-orders-on-a-symbol-trade """ payload: Dict[str, str] = {"symbol": format_symbol(symbol)} if recv_window is not None: payload["recvWindow"] = str(recv_window) raw: bytes = await self.client.sign_request( http_method="DELETE", url_path=self.BASE_ENDPOINT + "allOpenOrders", payload=payload, ) return orjson.loads(raw)
async def _cancel_all_orders(self, command: CancelAllOrders) -> None: self._log.debug(f"Canceling all orders for {command.instrument_id.value}.") # Cancel all in-flight orders inflight_orders = self._cache.orders_inflight( instrument_id=command.instrument_id, strategy_id=command.strategy_id, ) for order in inflight_orders: self.generate_order_pending_cancel( strategy_id=order.strategy_id, instrument_id=order.instrument_id, client_order_id=order.client_order_id, venue_order_id=order.venue_order_id, ts_event=self._clock.timestamp_ns(), ) # Cancel all open orders open_orders = self._cache.orders_open( instrument_id=command.instrument_id, strategy_id=command.strategy_id, ) for order in open_orders: self.generate_order_pending_cancel( strategy_id=order.strategy_id, instrument_id=order.instrument_id, client_order_id=order.client_order_id, venue_order_id=order.venue_order_id, ts_event=self._clock.timestamp_ns(), ) try: await self._http_account.cancel_open_orders( symbol=format_symbol(command.instrument_id.symbol.value), ) except BinanceError as ex: self._log.exception("Cannot cancel open orders: ", ex)
async def generate_trade_reports( # noqa (C901 too complex) self, instrument_id: InstrumentId = None, venue_order_id: VenueOrderId = None, start: datetime = None, end: datetime = None, ) -> List[TradeReport]: """ Generate a list of trade reports with optional query filters. The returned list may be empty if no trades match the given parameters. Parameters ---------- instrument_id : InstrumentId, optional The instrument ID query filter. venue_order_id : VenueOrderId, optional The venue order ID (assigned by the venue) query filter. start : datetime, optional The start datetime query filter. end : datetime, optional The end datetime query filter. Returns ------- list[TradeReport] """ self._log.info(f"Generating TradeReports for {self.id}...") open_orders = self._cache.orders_open(venue=self.venue) active_symbols: Set[str] = { format_symbol(o.instrument_id.symbol.value) for o in open_orders } reports_raw: List[Dict[str, Any]] = [] reports: List[TradeReport] = [] try: for symbol in active_symbols: response = await self._http_account.get_account_trades( symbol=symbol, start_time=int(start.timestamp() * 1000) if start is not None else None, end_time=int(end.timestamp() * 1000) if end is not None else None, ) reports_raw.extend(response) except BinanceError as ex: self._log.exception("Cannot generate trade report: ", ex) return [] for data in reports_raw: # Apply filter # TODO(cs): Time filter is WIP # timestamp = pd.to_datetime(data["time"], utc=True) # if start is not None and timestamp < start: # continue # if end is not None and timestamp > end: # continue report: TradeReport = parse_trade_report_http( account_id=self.account_id, instrument_id=self._get_cached_instrument_id(data["symbol"]), data=data, report_id=self._uuid_factory.generate(), ts_init=self._clock.timestamp_ns(), ) self._log.debug(f"Received {report}.") reports.append(report) # Sort in ascending order reports = sorted(reports, key=lambda x: x.trade_id) len_reports = len(reports) plural = "" if len_reports == 1 else "s" self._log.info(f"Generated {len(reports)} TradeReport{plural}.") return reports
async def generate_order_status_reports( # noqa (C901 too complex) self, instrument_id: InstrumentId = None, start: datetime = None, end: datetime = None, open_only: bool = False, ) -> List[OrderStatusReport]: """ Generate a list of order status reports with optional query filters. The returned list may be empty if no orders match the given parameters. Parameters ---------- instrument_id : InstrumentId, optional The instrument ID query filter. start : datetime, optional The start datetime query filter. end : datetime, optional The end datetime query filter. open_only : bool, default False If the query is for open orders only. Returns ------- list[OrderStatusReport] """ self._log.info(f"Generating OrderStatusReports for {self.id}...") open_orders = self._cache.orders_open(venue=self.venue) active_symbols: Set[str] = { format_symbol(o.instrument_id.symbol.value) for o in open_orders } order_msgs = [] reports: Dict[VenueOrderId, OrderStatusReport] = {} try: open_order_msgs: List[Dict[str, Any]] = await self._http_account.get_open_orders( symbol=instrument_id.symbol.value if instrument_id is not None else None, ) if open_order_msgs: order_msgs.extend(open_order_msgs) # Add to active symbols for o in open_order_msgs: active_symbols.add(o["symbol"]) for symbol in active_symbols: response = await self._http_account.get_orders( symbol=symbol, start_time=int(start.timestamp() * 1000) if start is not None else None, end_time=int(end.timestamp() * 1000) if end is not None else None, ) order_msgs.extend(response) except BinanceError as ex: self._log.exception("Cannot generate order status report: ", ex) return [] for msg in order_msgs: # Apply filter (always report open orders regardless of start, end filter) # TODO(cs): Time filter is WIP # timestamp = pd.to_datetime(data["time"], utc=True) # if data["status"] not in ("NEW", "PARTIALLY_FILLED", "PENDING_CANCEL"): # if start is not None and timestamp < start: # continue # if end is not None and timestamp > end: # continue report: OrderStatusReport = parse_order_report_http( account_id=self.account_id, instrument_id=self._get_cached_instrument_id(msg["symbol"]), data=msg, report_id=self._uuid_factory.generate(), ts_init=self._clock.timestamp_ns(), ) self._log.debug(f"Received {report}.") reports[report.venue_order_id] = report # One report per order len_reports = len(reports) plural = "" if len_reports == 1 else "s" self._log.info(f"Generated {len(reports)} OrderStatusReport{plural}.") return list(reports.values())
async def generate_trade_reports( # noqa (C901 too complex) self, instrument_id: InstrumentId = None, venue_order_id: VenueOrderId = None, start: datetime = None, end: datetime = None, ) -> List[TradeReport]: """ Generate a list of trade reports with optional query filters. The returned list may be empty if no trades match the given parameters. Parameters ---------- instrument_id : InstrumentId, optional The instrument ID query filter. venue_order_id : VenueOrderId, optional The venue order ID (assigned by the venue) query filter. start : datetime, optional The start datetime query filter. end : datetime, optional The end datetime query filter. Returns ------- list[TradeReport] """ self._log.info(f"Generating TradeReports for {self.id}...") # Check cache for all active symbols open_orders: List[Order] = self._cache.orders_open(venue=self.venue) open_positions: List[Position] = self._cache.positions_open( venue=self.venue) active_symbols: Set[str] = set() for o in open_orders: active_symbols.add(format_symbol(o.instrument_id.symbol.value)) for p in open_positions: active_symbols.add(format_symbol(p.instrument_id.symbol.value)) binance_trades: List[BinanceFuturesAccountTrade] = [] reports: List[TradeReport] = [] try: # Check Binance for all active positions binance_positions: List[BinanceFuturesPositionRisk] binance_positions = await self._http_account.get_position_risk() for data in binance_positions: if Decimal(data.positionAmt) == 0: continue # Flat position # Add active symbol active_symbols.add(data.symbol) # Check Binance for trades on all active symbols for symbol in active_symbols: symbol_trades = await self._http_account.get_account_trades( symbol=symbol, start_time=int(start.timestamp() * 1000) if start is not None else None, end_time=int(end.timestamp() * 1000) if end is not None else None, ) binance_trades.extend(symbol_trades) except BinanceError as ex: self._log.exception("Cannot generate trade report: ", ex) return [] # Parse all Binance trades for data in binance_trades: report = parse_trade_report_http( account_id=self.account_id, instrument_id=self._get_cached_instrument_id(data.symbol), data=data, report_id=self._uuid_factory.generate(), ts_init=self._clock.timestamp_ns(), ) self._log.debug(f"Received {report}.") reports.append(report) # Confirm sorting in ascending order reports = sorted(reports, key=lambda x: x.trade_id) len_reports = len(reports) plural = "" if len_reports == 1 else "s" self._log.info(f"Generated {len(reports)} TradeReport{plural}.") return reports
async def generate_order_status_reports( # noqa (C901 too complex) self, instrument_id: InstrumentId = None, start: datetime = None, end: datetime = None, open_only: bool = False, ) -> List[OrderStatusReport]: """ Generate a list of order status reports with optional query filters. The returned list may be empty if no orders match the given parameters. Parameters ---------- instrument_id : InstrumentId, optional The instrument ID query filter. start : datetime, optional The start datetime query filter. end : datetime, optional The end datetime query filter. open_only : bool, default False If the query is for open orders only. Returns ------- list[OrderStatusReport] """ self._log.info(f"Generating OrderStatusReports for {self.id}...") # Check cache for all active symbols open_orders: List[Order] = self._cache.orders_open(venue=self.venue) open_positions: List[Position] = self._cache.positions_open( venue=self.venue) active_symbols: Set[str] = set() for o in open_orders: active_symbols.add(format_symbol(o.instrument_id.symbol.value)) for p in open_positions: active_symbols.add(format_symbol(p.instrument_id.symbol.value)) binance_orders: List[BinanceFuturesOrder] = [] reports: Dict[VenueOrderId, OrderStatusReport] = {} try: # Check Binance for all active positions binance_positions: List[BinanceFuturesPositionRisk] binance_positions = await self._http_account.get_position_risk() for data in binance_positions: if Decimal(data.positionAmt) == 0: continue # Flat position # Add active symbol active_symbols.add(data.symbol) # Check Binance for all open orders binance_open_orders: List[BinanceFuturesOrder] binance_open_orders = await self._http_account.get_open_orders( symbol=instrument_id.symbol.value if instrument_id is not None else None, ) binance_orders.extend(binance_open_orders) # Add active symbol for data in binance_orders: active_symbols.add(data.symbol) # Check Binance for all orders for active symbols for symbol in active_symbols: response = await self._http_account.get_orders( symbol=symbol, start_time=int(start.timestamp() * 1000) if start is not None else None, end_time=int(end.timestamp() * 1000) if end is not None else None, ) binance_orders.extend(response) except BinanceError as ex: self._log.exception("Cannot generate order status report: ", ex) return [] # Parse all Binance orders for data in binance_orders: report = parse_order_report_http( account_id=self.account_id, instrument_id=self._get_cached_instrument_id(data.symbol), data=data, report_id=self._uuid_factory.generate(), ts_init=self._clock.timestamp_ns(), ) self._log.debug(f"Received {report}.") reports[report.venue_order_id] = report # One report per order len_reports = len(reports) plural = "" if len_reports == 1 else "s" self._log.info(f"Generated {len(reports)} OrderStatusReport{plural}.") return list(reports.values())