pub trait ExchangeClient:
Send
+ Sync
+ 'static {
// Required methods
fn name(&self) -> &str;
fn place_order<'life0, 'life1, 'async_trait>(
&'life0 self,
order: &'life1 Order,
) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait;
fn cancel_all<'life0, 'life1, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<usize, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait;
fn close_position<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
position: &'life2 Position,
) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait;
fn get_position<'life0, 'life1, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<Position, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait;
fn get_balance<'life0, 'life1, 'async_trait>(
&'life0 self,
currency: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<f64, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait;
// Provided methods
fn supports(&self, _capability: Capability) -> bool { ... }
fn contract_value(&self, _symbol: &Symbol) -> f64 { ... }
fn instrument_spec(&self, symbol: &Symbol) -> InstrumentSpec { ... }
fn get_open_orders<'life0, 'life1, 'async_trait>(
&'life0 self,
_symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<Vec<OpenOrder>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
fn cancel_order<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_symbol: &'life1 Symbol,
_order_id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
}Expand description
What the bot framework needs from an exchange to trade.
This trait is intentionally narrow — the full surface of a real exchange client (ws token management, stop orders, funding history, account tiers) belongs in the concrete adapter crate, not here. The framework only needs to: place orders, close positions, read balance and position state.
§Async + object-safe
async_trait is used so Arc<dyn ExchangeClient> works — downstream
code can swap concrete exchanges at runtime without generics propagating
through the whole system.
§Example
A stub adapter useful for examples and tests. Real adapters connect to a network and report actual state.
use async_trait::async_trait;
use rustrade_core::{Capability, ExchangeClient, Order, Position, Result, Symbol};
struct StubExchange;
#[async_trait]
impl ExchangeClient for StubExchange {
fn name(&self) -> &str { "stub" }
async fn place_order(&self, _order: &Order) -> Result<String> {
Ok("order-1".into())
}
async fn cancel_all(&self, _symbol: &Symbol) -> Result<usize> { Ok(0) }
async fn close_position(&self, _symbol: &Symbol, _p: &Position) -> Result<String> {
Ok("close-1".into())
}
async fn get_position(&self, _symbol: &Symbol) -> Result<Position> {
Ok(Position::FLAT)
}
async fn get_balance(&self, _currency: &str) -> Result<f64> { Ok(0.0) }
fn supports(&self, c: Capability) -> bool {
matches!(c, Capability::ReduceOnly)
}
}Required Methods§
Sourcefn place_order<'life0, 'life1, 'async_trait>(
&'life0 self,
order: &'life1 Order,
) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn place_order<'life0, 'life1, 'async_trait>(
&'life0 self,
order: &'life1 Order,
) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Place an order. Returns the exchange-assigned order id.
Sourcefn cancel_all<'life0, 'life1, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<usize, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn cancel_all<'life0, 'life1, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<usize, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Cancel all open orders for a symbol. Returns the count cancelled.
Sourcefn close_position<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
position: &'life2 Position,
) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn close_position<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
position: &'life2 Position,
) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Close the given position with a market order. Returns the exchange order id of the close.
Sourcefn get_position<'life0, 'life1, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<Position, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn get_position<'life0, 'life1, 'async_trait>(
&'life0 self,
symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<Position, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Fetch the current position for a symbol (or Position::FLAT if flat).
Provided Methods§
Sourcefn supports(&self, _capability: Capability) -> bool
fn supports(&self, _capability: Capability) -> bool
Does this adapter support the given optional capability?
The default returns false for every variant — adapters opt in by
overriding. This is intentionally conservative: a new adapter that
forgets to override won’t quietly accept orders it can’t execute.
Sourcefn contract_value(&self, _symbol: &Symbol) -> f64
fn contract_value(&self, _symbol: &Symbol) -> f64
Base-asset units per one contract for the given symbol.
For spot exchanges this is 1.0 for every symbol — one unit traded
equals one unit of the base asset. For futures, it’s the contract
multiplier (e.g. 0.001 for KuCoin XBTUSDTM where one contract is
0.001 BTC). The risk layer’s
PositionSizer
uses this to convert margin × leverage into a contract count.
The default returns 1.0 — appropriate for spot adapters. Futures
adapters override.
Sourcefn instrument_spec(&self, symbol: &Symbol) -> InstrumentSpec
fn instrument_spec(&self, symbol: &Symbol) -> InstrumentSpec
Full instrument metadata for symbol: contract size, price tick,
quantity lot, minimum notional, and AssetClass.
The framework uses this to round orders to the venue’s increments,
enforce a minimum order notional, and apply class-aware risk rules.
The default derives an InstrumentSpec from Self::contract_value
with no other constraints, so adapters that only override
contract_value keep working unchanged; adapters with real venue
metadata (tick/lot/min-notional) override this and should make
contract_value agree with instrument_spec(symbol).contract_value.
Sourcefn get_open_orders<'life0, 'life1, 'async_trait>(
&'life0 self,
_symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<Vec<OpenOrder>, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn get_open_orders<'life0, 'life1, 'async_trait>(
&'life0 self,
_symbol: &'life1 Symbol,
) -> Pin<Box<dyn Future<Output = Result<Vec<OpenOrder>, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
List the currently-resting (non-terminal) orders for a symbol.
Used by the framework’s order-tracking layer to age out stale limit
orders and to reconcile after a reconnect. The default returns an
empty list — an adapter that doesn’t advertise
Capability::OrderTracking is assumed to have no resting orders
the framework should manage (e.g. a market-only or fire-and-forget
adapter). Adapters that support resting orders must override this
and advertise the capability.
Sourcefn cancel_order<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_symbol: &'life1 Symbol,
_order_id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn cancel_order<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_symbol: &'life1 Symbol,
_order_id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Cancel a single resting order by its exchange-assigned id.
Returns Ok(true) if the order was cancelled, Ok(false) if it was
already gone (filled or cancelled) — a benign no-op the caller can
ignore. The default errors, so the tracking layer never believes it
cancelled an order against an adapter that can’t actually do so;
adapters advertising Capability::OrderTracking override it.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".