Skip to main content

ExecutionClient

Trait ExecutionClient 

Source
pub trait ExecutionClient
where Self: Clone,
{ type Config: Clone; type AccountStream: Stream<Item = UnindexedAccountEvent> + Send; const EXCHANGE: ExchangeId; const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant]; // Required methods fn new(config: Self::Config) -> Self; fn account_snapshot( &self, assets: &[AssetNameExchange], instruments: &[InstrumentNameExchange], ) -> impl Future<Output = Result<UnindexedAccountSnapshot, UnindexedClientError>> + Send; fn account_stream( &self, assets: &[AssetNameExchange], instruments: &[InstrumentNameExchange], ) -> impl Future<Output = Result<Self::AccountStream, UnindexedClientError>> + Send; fn cancel_order( &self, request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>, ) -> impl Future<Output = Option<UnindexedOrderResponseCancel>> + Send; fn open_order( &self, request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>, ) -> impl Future<Output = Option<Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>>> + Send; fn fetch_balances( &self, assets: &[AssetNameExchange], ) -> impl Future<Output = Result<Vec<AssetBalance<AssetNameExchange>>, UnindexedClientError>> + Send; fn fetch_open_orders( &self, instruments: &[InstrumentNameExchange], ) -> impl Future<Output = Result<Vec<Order<ExchangeId, InstrumentNameExchange, Open>>, UnindexedClientError>> + Send; fn fetch_trades( &self, time_since: DateTime<Utc>, instruments: &[InstrumentNameExchange], ) -> impl Future<Output = Result<Vec<Trade<AssetNameExchange, InstrumentNameExchange>>, UnindexedClientError>> + Send; // Provided methods fn cancel_orders<'a>( &self, requests: impl IntoIterator<Item = OrderRequestCancel<ExchangeId, &'a InstrumentNameExchange>>, ) -> impl Stream<Item = Option<UnindexedOrderResponseCancel>> + Send { ... } fn open_orders<'a>( &self, requests: impl IntoIterator<Item = OrderRequestOpen<ExchangeId, &'a InstrumentNameExchange>>, ) -> impl Stream<Item = Option<Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>>> + Send { ... } }

Required Associated Constants§

Source

const EXCHANGE: ExchangeId

Source

const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant]

The InstrumentKindDiscriminants this client can trade.

A client works off InstrumentNameExchange strings and never inspects an instrument’s kind, so nothing downstream can infer this from the implementation — routing a Future through a spot-only venue produces a rejected or, worse, misinterpreted order rather than a type error. Declaring the set here lets ExecutionBuilder reject an unsupported instrument set at build time, before any order is sent.

Declare what the venue and this implementation together can actually route, not what the exchange’s product catalogue advertises. A kind the exchange offers but this client builds no request for does not belong in the set.

There is deliberately no default. A new client must state its capabilities rather than inherit a permissive set that would silently admit every kind.

It is a const and not a fn supported_kinds(&self) because ExecutionBuilder validates the instrument set before Self::new is ever called — there is no instance to ask at the point the answer is needed, and deferring validation until after construction would mean standing up a client for a venue the engine is about to reject. A capability set that varies with Self::Config therefore cannot be expressed here; it would need a separate pre-construction hook on the config.

Required Associated Types§

Required Methods§

Source

fn new(config: Self::Config) -> Self

Source

fn account_snapshot( &self, assets: &[AssetNameExchange], instruments: &[InstrumentNameExchange], ) -> impl Future<Output = Result<UnindexedAccountSnapshot, UnindexedClientError>> + Send

Reads the account’s balances, and its open orders and positions for instruments, from the venue.

Besides startup, the engine’s ExecutionManager calls this again on every account-stream reconnect, and emits the result ahead of the new stream’s updates. It is how the engine catches up on what changed while the stream was down.

§Order completeness

Each InstrumentAccountSnapshot states through orders_complete whether its orders list is every order open at the venue for that instrument. Set it only when it is true: the engine retires a tracked order that a complete list omits, so a list that can miss an open order (a capped page, an order dropped on conversion, an order reported under an id other than the one it was placed with) must say false. false is always safe; it only forgoes that reconciliation.

Source

fn account_stream( &self, assets: &[AssetNameExchange], instruments: &[InstrumentNameExchange], ) -> impl Future<Output = Result<Self::AccountStream, UnindexedClientError>> + Send

Returns a live stream of account events (fills, order updates, balance changes).

§Startup race window

There is an unavoidable gap between the WebSocket subscribe response and the first event being delivered: fills arriving in this window (typically milliseconds, no sub-millisecond guarantee) are silently dropped. account_snapshot reconciles open-order state, but TRADE fills in this window are not recoverable from the stream alone. Callers that require fill completeness at startup must call ExecutionClient::fetch_trades with at least a 1-second lookback after this method returns.

§Backpressure

Implementations use unbounded internal channels. If the consumer cannot keep up, events queue in memory rather than being dropped — per library philosophy, OOM crashes are preferable to silent data loss. Consumers requiring backpressure should implement it at their boundary (e.g., bounded channel with overflow policy).

Source

fn cancel_order( &self, request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>, ) -> impl Future<Output = Option<UnindexedOrderResponseCancel>> + Send

Cancel an order at the venue.

§Return value
  • Some with Ok(Cancelled): the venue took the cancel. Whether the order has ended by then depends on the venue; see below.
  • Some with Err: the venue refused the cancel, or it could not be sent or answered. The order may still be open, or may have ended some other way, such as by filling. The account stream reports which.
  • None: nothing to report, so the engine’s ExecutionManager emits nothing for the request. No client in this crate returns it.
§A taken cancel is not always an ended order

Some venues answer a cancel before carrying it out. Until they do, the order is live and can still fill. From those venues Ok(Cancelled) means only that the cancel was accepted, and the account stream reports how the order actually ended, including any fill in between. The engine stops tracking an order on Ok(Cancelled) from any venue.

Binance, Hyperliquid and the mock venue answer once the order is cancelled. Alpaca answers once it has accepted the cancel, and IBKR once the cancel is submitted. The connector comparison lists the same for each connector; the mock venue is not one, so it has no row there.

§filled_quantity and time_exchange

Cancelled::filled_quantity is the quantity the venue reported filled where the answer carries one, which Binance’s and the mock venue’s do. Alpaca, IBKR and Hyperliquid answer without it and report zero, which does not mean nothing filled. The fill total comes from the account stream, or from Self::fetch_trades. Likewise time_exchange is the venue’s time where the answer carries one, and the local time the answer arrived otherwise.

Source

fn open_order( &self, request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>, ) -> impl Future<Output = Option<Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>>> + Send

Place an order on the exchange.

§Return value

Returns OrderState directly rather than Result<Open, OrderError>:

  • OrderState::Active(Open) - order is resting on the order book
  • OrderState::Inactive(FullyFilled) - order was immediately filled (includes avg_price when available)
  • OrderState::Inactive(OpenFailed) - order placement failed (API error, connectivity, etc.)

This design allows immediate fills to carry metadata (e.g., avg_price) that would be lost if we had to infer terminal state from Open::filled_quantity.

Source

fn fetch_balances( &self, assets: &[AssetNameExchange], ) -> impl Future<Output = Result<Vec<AssetBalance<AssetNameExchange>>, UnindexedClientError>> + Send

Fetch current balances for the specified assets.

An empty assets slice is the “return all” sentinel: implementations must return balances for every asset held. When non-empty, only the listed assets are returned.

Source

fn fetch_open_orders( &self, instruments: &[InstrumentNameExchange], ) -> impl Future<Output = Result<Vec<Order<ExchangeId, InstrumentNameExchange, Open>>, UnindexedClientError>> + Send

Fetch currently open orders, optionally filtered by instrument.

An empty instruments slice is the “return all” sentinel: implementations must return open orders across all instruments. When non-empty, only orders for the listed instruments are returned.

Source

fn fetch_trades( &self, time_since: DateTime<Utc>, instruments: &[InstrumentNameExchange], ) -> impl Future<Output = Result<Vec<Trade<AssetNameExchange, InstrumentNameExchange>>, UnindexedClientError>> + Send

Fetch trades (fills) since time_since, optionally filtered by instrument.

An empty instruments slice is the “return all” sentinel: implementations must return trades across all instruments. When non-empty, only trades for the listed instruments are returned.

The fee asset (AssetNameExchange) may be quote, base, or third-party (e.g., BNB). Use fees.fees_quote for quote-equivalent value when available.

Note: MockExecution currently ignores instruments and always returns all trades.

Provided Methods§

Dyn Compatibility§

This trait is not dyn compatible.

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

Implementors§

Source§

impl ExecutionClient for AlpacaClient

Source§

impl ExecutionClient for BinanceMargin

Source§

impl ExecutionClient for BinanceSpot

Source§

impl ExecutionClient for HyperliquidClient

Source§

impl ExecutionClient for HyperliquidSpotClient

Source§

impl ExecutionClient for IbkrClient

Source§

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