Skip to main content

OrderStatusClient

Trait OrderStatusClient 

Source
pub trait OrderStatusClient: ExecutionClient {
    // Required method
    fn fetch_ended_orders(
        &self,
        orders: &[UnindexedOrderKey],
    ) -> impl Future<Output = Result<Vec<UnindexedInactiveOrder>, UnindexedClientError>> + Send;

    // Provided method
    fn fetch_order_status(
        &self,
        key: &UnindexedOrderKey,
    ) -> impl Future<Output = Result<VenueOrderStatus, UnindexedClientError>> + Send
       where Self: Sync { ... }
}
Expand description

Extension trait for clients that can say how an order ended at the venue.

A client that loses its account stream recovers the fills it missed when it reconnects, but not an order that was cancelled, expired or rejected in the meantime: the event that said so went out while nothing was listening. The engine then keeps such an order as live until a complete account snapshot no longer lists it, and even then cannot tell how it ended. This trait is the lookup that answers that: given the orders a caller still holds as live, it returns how each one that has ended did end.

§By order, not by time

The obvious alternative, asking the venue for every order closed since the disconnect, does not work. Binance’s allOrders and Alpaca’s GET /v2/orders?status=closed both filter their time window on when an order was created, so a window opening at the disconnect cannot see an order placed before it and cancelled during it — which is the order that matters, because the caller is tracking it. Looking each order up by its client order id finds it whenever it was placed.

§Type-Level Capability

A supertrait of ExecutionClient, for the reason BracketOrderClient is one: a client that cannot look orders up does not implement it, rather than carrying a method that fails at run time. A caller that needs the lookup bounds on ExecutionClient + OrderStatusClient.

Required Methods§

Source

fn fetch_ended_orders( &self, orders: &[UnindexedOrderKey], ) -> impl Future<Output = Result<Vec<UnindexedInactiveOrder>, UnindexedClientError>> + Send

How each of orders ended, for those that have.

§Contract
  • One entry per order that has ended, in no particular order: FullyFilled, Cancelled carrying what filled before it, Expired, or OpenFailed for an order the venue accepted for processing and later rejected (Binance REJECTED, Alpaca rejected). An order rejected in the response to its open request was answered there and is not expected here.
  • An order still live is omitted, and so is one the venue does not know. Neither is an error. A caller compares the result with what it asked for.
  • An empty orders returns an empty list. Unlike ExecutionClient::fetch_open_orders’s empty slice, it does not mean “all”.
  • An order is found by its cid, and each returned order carries the key it was asked for, so a strategy the venue does not record survives.
  • A key whose instrument is not the one the order traded is treated as unknown. The exchange is not checked: a client answers for its own venue.
  • A cid asked about more than once is reported at most once, under the first key that finds it.
§Errors

Err if the venue could not answer for every order. A partial list is never returned, because an order missing from it would read as still live.

Provided Methods§

Source

fn fetch_order_status( &self, key: &UnindexedOrderKey, ) -> impl Future<Output = Result<VenueOrderStatus, UnindexedClientError>> + Send
where Self: Sync,

What the venue knows of the order under key: live, ended, or nothing.

This is how an open request whose outcome is unknown is settled (see OrderError::is_outcome_unknown).

§Contract
  • VenueOrderStatus::Unknown means the venue lists the order neither as live nor as ended, under the key’s instrument. A venue may take a moment to list an order it has just accepted, so one Unknown does not prove the order was never placed. A caller asks again before it concludes that.
  • The order is found by its cid, and a found order carries key.

The provided method asks fetch_ended_orders about key first, then lists the open orders of its instrument with ExecutionClient::fetch_open_orders. In that order, an order that is placed between the two reads is listed as live, so it is not missed. An order that ends between them is found by neither, and is reported Unknown once; the next lookup finds it ended. A client whose venue answers in one request may override it.

§Errors

Err if either read fails. A failed read is never reported as Unknown.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl OrderStatusClient for AlpacaClient

Source§

impl OrderStatusClient for BinanceMargin

Looks each order up with GET /sapi/v1/margin/order by symbol and client order id, in the cross or isolated account as BinanceMarginConfig::is_isolated says, weight 10 each, up to eight at a time. Binance’s FILLED is reported with its average price, CANCELED with what filled before it, EXPIRED and EXPIRED_IN_MATCH (self-trade prevention) as expired, and REJECTED as OpenFailed. An order Binance does not know under the key’s symbol (-2013, or -1121 for a symbol that does not exist) is omitted. A row whose status this version does not know is omitted too, with a warning, as is a PENDING_CANCEL one.

In isolated mode a key’s symbol is asked about whether or not it is one of isolated_symbols, since an order can be placed on any pair. Any error other than those two codes fails the call, including one Binance gives for a pair with no isolated account.

The account stream runs the same lookup itself after a reconnect; see account_stream.

Source§

impl OrderStatusClient for BinanceSpot

Looks each order up with GET /api/v3/order by symbol and client order id, weight 4 each, up to eight at a time. Binance’s FILLED is reported with its average price, CANCELED with what filled before it, EXPIRED and EXPIRED_IN_MATCH (self-trade prevention) as expired, and REJECTED as OpenFailed. An order Binance does not know under the key’s symbol (-2013, or -1121 for a symbol that does not exist) is omitted. A row whose status this version does not know is omitted too, with a warning, as is a PENDING_CANCEL one, which Binance does not use.

The account stream runs the same lookup itself after a reconnect; see account_stream.

Source§

impl OrderStatusClient for HyperliquidClient

Looks each order up with orderStatus (weight 2), by the cloid it was placed with, or by its oid for an order placed without one, which this client reports under its oid. Up to 8 run at once.

filled is reported as fully filled, without an average price, which the record does not carry; every status ending in Canceled, and scheduledCancel, as cancelled with what filled before it; and every status ending in Rejected as OpenFailed. Hyperliquid has no expiry. An order the venue does not know, one on another instrument than the key’s, and an id that is neither a canonical UUID nor an oid are unknown. An oid whose order carries a cloid is unknown too, since this client reports that order under its cloid.

The account stream runs the same lookup itself after a reconnect; see account_stream.

Source§

impl OrderStatusClient for HyperliquidSpotClient

Looks each order up with orderStatus (weight 2), as HyperliquidClient does; see its implementation for how each status is reported. An order on a spot coin missing from spotMeta even after it is read again (see Spot coins) fails the call, since it cannot be told apart from an order on the key’s instrument.

The account stream runs the same lookup itself after a reconnect; see account_stream.

Source§

impl OrderStatusClient for IbkrClient

Looks each order up in IB’s listing of the account’s completed orders, by the order reference that carries the client order id it was placed with, so it finds an order this client placed before a restart too. One listing is read per call, and, when an order ended cancelled or expired, one read of the last seven days’ executions for its fill.

A filled order is reported fully filled, without an average price; a cancelled or expired one with the fill its executions show, which is nothing when they show none of it and the order was placed through this client under five days ago, since the read then covers its whole life, and otherwise unknown (None); a placement IB accepted and then rejected, or left Inactive, as OpenFailed. A cancelled order is reported expired by the rule ExecutionClient::account_stream applies. An order IB lists under no client order id, such as one entered in TWS, cannot be found.

IB lists the orders completed before a Gateway restart under API client id 0, so a completed order is matched by its client order id whichever of this client’s id and 0 it is listed under. Client order ids must therefore be unique across the API clients on the account.

When IB lists several completed orders under one id, the one that completed last is reported. One that completed more than 5 seconds before this client began tracking the order now under that id is an earlier order’s, under a reused id, and the order is not reported ended, with a warning: should IB’s clock lag this host’s by more, an order that did end is held as live.

The account stream runs the same lookup itself after a gap in event delivery; see ExecutionClient::account_stream.

§Errors

UnindexedClientError::Internal when a listing fails, IB sends nothing for 10 seconds before the end of one, or the connection drops during it.

Source§

impl<FnTime> OrderStatusClient for MockExecution<FnTime>
where FnTime: Fn() -> DateTime<Utc> + Clone + Send + Sync,

Answered from the venue’s own ledger: an order it filled, cancelled or retired at its deadline is reported, and one still open or never held is omitted. The venue rejects an order only on arrival, in the response to its open request, so it never reports OpenFailed.