pub struct BinanceSpot { /* private fields */ }Expand description
BinanceSpot execution client using the official binance-sdk.
- REST API: account snapshot, balance/order/trade queries (startup/cold paths)
- WebSocket API: order placement, order cancellation, user data stream (hot paths)
§Rate limits
REST and the WebSocket API share one per-IP request-weight limit per minute. Its value comes
from each WebSocket API response, and is Binance’s documented 6000 until the first one. Once
a response reports at least 90% of it used, REST queries wait for the next minute, so the
rest is left for orders and cancels, which never wait for it. A query, including a
fetch_open_orders after a reconnect, can therefore take up to
about a minute longer. A reconnect’s fill recovery does not wait for it either, since a pause
could outlast the recovery’s 30 s and delay its fills to a retry.
After Binance answers with a rate-limit error, every REST call, order and cancel waits out a
cooldown as long as Binance asked (REST’s Retry-After, the WebSocket API’s
retryAfter), or 10 s when it named no wait. A request facing more than 10 s of it is not
sent: it fails at once with ApiError::RateLimit carrying the time left, rather than hanging
through a ban that can last days. A REST 418 (an IP ban) is never retried in place. The
WebSocket API names the end of a ban as a time on Binance’s clock, which the client measures
against the local clock, plus 1 s, so keep the host’s clock synchronised.
The account stream follows the same rule. It does not open, or reconnect, during a cooldown:
it waits out one of 10 s or less, without spending a reconnect attempt, and a longer one fails
the open with ApiError::RateLimit, unsent, or ends a running stream with
StreamTerminationReason::RateLimited,
carrying the time left. A handshake or user-data subscribe Binance refuses for a rate limit
starts a cooldown, as an order’s refusal does. A refused handshake names no wait, so a 429 starts
the 10 s default, and handshakes refused that way spend the
reconnect budget as any failure does; a 418, an IP ban,
starts Binance’s shortest ban, 2 minutes, and so ends the stream.
§Order and cancel times
The time of a placed or cancelled order is the transactTime of Binance’s answer. An answer
without a readable one is still read, at the local time, and logged with a warn!.
§Fills of other clients’ orders
The account stream, fill recovery and fetch_trades report every fill on the account, whoever
placed the order, and Binance does not say who did. A consumer that also trades the account
outside the engine filters those fills itself, for example by
Trade::client_order_id. See the
module docs.
Implementations§
Source§impl BinanceSpot
impl BinanceSpot
Sourcepub async fn load_rules(
&self,
instruments: &[InstrumentNameExchange],
) -> Result<HashMap<InstrumentNameExchange, OrderRules>, UnindexedClientError>
pub async fn load_rules( &self, instruments: &[InstrumentNameExchange], ) -> Result<HashMap<InstrumentNameExchange, OrderRules>, UnindexedClientError>
Load the order rules of instruments from Binance now, and hold them for
open_order to check against.
Rules otherwise load on each symbol’s first order, which adds a request to that order.
Calling this ahead takes it off the first order, and shows a bad symbol or a failed load
here, not as an unchecked order. Binance refuses the whole request if one symbol is not
listed. Holds nothing back when BinanceSpotConfig::pre_send_rules is
PreSendRules::Off: the rules are returned but not used.
Trait Implementations§
Source§impl Clone for BinanceSpot
impl Clone for BinanceSpot
Source§impl Debug for BinanceSpot
impl Debug for BinanceSpot
Source§impl ExecutionClient for BinanceSpot
impl ExecutionClient for BinanceSpot
Source§fn new(config: Self::Config) -> Self
fn new(config: Self::Config) -> Self
§Panics
Panics if the binance-sdk REST or WebSocket configuration builder fails (e.g. empty or malformed API key/secret).
Source§async fn account_stream(
&self,
_assets: &[AssetNameExchange],
instruments: &[InstrumentNameExchange],
) -> Result<Self::AccountStream, UnindexedClientError>
async fn account_stream( &self, _assets: &[AssetNameExchange], instruments: &[InstrumentNameExchange], ) -> Result<Self::AccountStream, UnindexedClientError>
Returns a live stream of account events (fills, order updates, balance changes).
§Reconnection
The stream reconnects by itself, and ends once 10 reconnects in a row have failed or a rate-limit cooldown outlasts what it waits through. See the module docs.
§Startup Race Window
There is a brief window between the initial WS subscribe response and the
internal event listener being registered inside the spawned connection manager.
TRADE fills arriving in this gap are silently dropped. Callers that require fill
completeness at startup MUST call ExecutionClient::fetch_trades with a
1–2 second lookback unconditionally after account_stream returns, before
processing any events. Do not use the first event’s arrival as a readiness
signal — the first event may be a recovered fill sent before live WS events
start. The dedup cache absorbs any duplicates from the overlapping time window.
§A recovery that does not finish
Recovery after a reconnect is bounded by a 30 s timeout, and an instrument’s query can
fail. Each instrument’s gap, from the disconnect to just after the recovery began, is kept
until its fills are forwarded. A gap not read is retried after 1, 2, 4, 8 and 16 minutes,
whether the stream stays connected or reconnects in between. A retry reads only the gap,
not fills the stream delivered live after it; the few seconds at its end that overlap live
delivery are deduplicated. Each failed read is logged at warn with the instrument and the
gap. After five failed retries the gap is given up, logged at error, and its fills are not
delivered. The stream says so with an
AccountEventKind::FillRecoveryGaveUp naming the instrument and the gap: read its fills
with ExecutionClient::fetch_trades.
A recovered gap counts as read even when a fill in it could not be converted, which is logged, or its order’s cumulative could not be looked up (see below).
§A recovered fill advances the order too
A fill that arrives live over the WebSocket carries the order’s cumulative filled
quantity in Trade::order_filled_quantity (executionReport’s z), so it advances
the order by itself. A fill recovered after a disconnect comes from REST myTrades,
which reports executions only, so recovery rebuilds the same figure by reading each
recovered order’s executions from its first: one extra request per order (another per
further 1,000 executions), up to four orders at a time per instrument.
Those lookups have their own time budget inside the recovery timeout, so they can never
cost a fill. A fill whose order was not looked up in time, or whose lookup failed, goes
out with order_filled_quantity: None, logged at warn. It then advances the position
but not the order, which keeps whatever filled_quantity it held before the gap until
the order’s state is learned some other way.
§Orders that ended while disconnected
A reconnect also reports how each order the client holds as live on one of instruments
ended, where it did, as an AccountEventKind::OrderSnapshot of its inactive state:
filled, cancelled with what filled before, expired, or rejected. Like fill recovery, it
covers only the instruments the stream was opened with. Binance’s order history filters on when an
order was created, so such an order is found by asking about it, not by time.
- Which orders. The client holds an order as live from the response to placing it,
from a listing of open orders (
account_snapshot,fetch_open_orders), and from its live reports on any of its account streams, until it sees the order end. It holds up to 4,096 orders and forgets the oldest past that, logged atwarn. An order placed outside this client and never listed or reported to it is not covered. - Cost. One
openOrdersrequest per instrument with an order held (weight 6), then oneGET /api/v3/order(weight 4) for each held order the listing no longer shows. - Fills first. An instrument is checked only once its fill gap is recovered or given up, so an order’s recovered fills arrive before how it ended. A fill that brings an order to its full quantity ends it, and that order is not reported again.
- Keys. Each snapshot carries
StrategyId::unknown, since Binance records no strategy. The engine matches it to the order it tracks by client order id. - Failures. Each order’s lookup is settled as it ends, and each instrument as soon as its
check ends. One whose listing or any lookup fails, or whose check is still running when a
pass reaches 30 s, is retried on the fill gaps’ schedule (1, 2, 4, 8 and 16 minutes),
asking only about the orders still held, then given up, logged at
error. Its orders are asked about again at the next reconnect. An order Binance does not know (-2013) stops being held, logged atwarn; one it still reports live, or in a state this version cannot read, stays held.
The same lookup is public as OrderStatusClient::fetch_ended_orders.
Source§async fn fetch_open_orders(
&self,
instruments: &[InstrumentNameExchange],
) -> Result<Vec<Order<ExchangeId, InstrumentNameExchange, Open>>, UnindexedClientError>
async fn fetch_open_orders( &self, instruments: &[InstrumentNameExchange], ) -> Result<Vec<Order<ExchangeId, InstrumentNameExchange, Open>>, UnindexedClientError>
An empty instruments slice is the ExecutionClient “return all” sentinel: a single
no-symbol GET /api/v3/openOrders call returns open orders across every instrument (each
order’s instrument is recovered from its own symbol field). A non-empty slice fetches the
listed instruments concurrently, per-symbol.
Source§async fn fetch_trades(
&self,
start: DateTime<Utc>,
end: DateTime<Utc>,
instruments: &[InstrumentNameExchange],
) -> Result<TradesRead<AssetNameExchange, InstrumentNameExchange>, UnindexedClientError>
async fn fetch_trades( &self, start: DateTime<Utc>, end: DateTime<Utc>, instruments: &[InstrumentNameExchange], ) -> Result<TradesRead<AssetNameExchange, InstrumentNameExchange>, UnindexedClientError>
Documented deviation from the ExecutionClient::fetch_trades “return all” contract:
Binance’s myTrades endpoint requires a symbol — there is no no-symbol “all trades” query
(unlike open orders). An empty instruments slice therefore has nothing to query and
reads nothing; callers wanting all trades must enumerate instruments explicitly.
Each instrument is paged by trade id to the span’s end within the call, with no page cap,
so the read is always complete (resume: None).
const EXCHANGE: ExchangeId = ExchangeId::BinanceSpot
Source§const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant]
const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant]
InstrumentKindDiscriminants this client can trade. Read moretype Config = BinanceSpotConfig
type AccountStream = Pin<Box<dyn Stream<Item = AccountEvent<ExchangeId, AssetNameExchange, InstrumentNameExchange>> + Send>>
Source§async fn account_snapshot(
&self,
assets: &[AssetNameExchange],
instruments: &[InstrumentNameExchange],
) -> Result<UnindexedAccountSnapshot, UnindexedClientError>
async fn account_snapshot( &self, assets: &[AssetNameExchange], instruments: &[InstrumentNameExchange], ) -> Result<UnindexedAccountSnapshot, UnindexedClientError>
instruments, from the
venue. Read moreSource§async fn cancel_order(
&self,
request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>,
) -> UnindexedOrderResponseCancel
async fn cancel_order( &self, request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>, ) -> UnindexedOrderResponseCancel
Source§async fn open_order(
&self,
request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>,
) -> Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>
async fn open_order( &self, request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>, ) -> Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>
Source§async fn fetch_balances(
&self,
assets: &[AssetNameExchange],
) -> Result<Vec<AssetBalance<AssetNameExchange>>, UnindexedClientError>
async fn fetch_balances( &self, assets: &[AssetNameExchange], ) -> Result<Vec<AssetBalance<AssetNameExchange>>, UnindexedClientError>
Source§fn validate_config(
config: &Self::Config,
instruments: &[ClientInstrument<'_>],
) -> Result<(), String>
fn validate_config( config: &Self::Config, instruments: &[ClientInstrument<'_>], ) -> Result<(), String>
fn cancel_orders<'a>( &self, requests: impl IntoIterator<Item = OrderRequestCancel<ExchangeId, &'a InstrumentNameExchange>>, ) -> impl Stream<Item = UnindexedOrderResponseCancel> + Send
fn open_orders<'a>( &self, requests: impl IntoIterator<Item = OrderRequestOpen<ExchangeId, &'a InstrumentNameExchange>>, ) -> impl Stream<Item = Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>> + Send
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.
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§async fn fetch_ended_orders(
&self,
orders: &[UnindexedOrderKey],
) -> Result<Vec<UnindexedInactiveOrder>, UnindexedClientError>
async fn fetch_ended_orders( &self, orders: &[UnindexedOrderKey], ) -> Result<Vec<UnindexedInactiveOrder>, UnindexedClientError>
orders ended, for those that have. Read moreSource§fn fetch_order_status(
&self,
key: &UnindexedOrderKey,
) -> impl Future<Output = Result<VenueOrderStatus, UnindexedClientError>> + Sendwhere
Self: Sync,
fn fetch_order_status(
&self,
key: &UnindexedOrderKey,
) -> impl Future<Output = Result<VenueOrderStatus, UnindexedClientError>> + Sendwhere
Self: Sync,
key: live, ended, or nothing. Read moreAuto Trait Implementations§
impl !Freeze for BinanceSpot
impl !RefUnwindSafe for BinanceSpot
impl !UnwindSafe for BinanceSpot
impl Send for BinanceSpot
impl Sync for BinanceSpot
impl Unpin for BinanceSpot
impl UnsafeUnpin for BinanceSpot
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreimpl<T> JsonSchemaMaybe for T
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.