pub struct IbkrClient { /* private fields */ }Expand description
Interactive Brokers execution client.
§Clone Behavior
Cloning creates a shallow copy with shared Arc references to the underlying
IB connection, contract registry, order ID map, and execution buffer. All clones
share the same TWS/Gateway connection and state.
Implementations§
Source§impl IbkrClient
impl IbkrClient
Sourcepub fn connect_sync(config: IbkrConfig) -> Result<Self, UnindexedClientError>
pub fn connect_sync(config: IbkrConfig) -> Result<Self, UnindexedClientError>
Connect to TWS/Gateway and initialize the client (sync, blocking).
§Errors
Returns error if connection fails or contract resolution fails.
Sourcepub fn register_contract(
&self,
name: InstrumentNameExchange,
contract: Contract,
)
pub fn register_contract( &self, name: InstrumentNameExchange, contract: Contract, )
Register a contract for an instrument.
Sourcepub fn contract_registry(&self) -> &ContractRegistry
pub fn contract_registry(&self) -> &ContractRegistry
Get the contract registry.
Sourcepub fn pending_execution_count(&self) -> usize
pub fn pending_execution_count(&self) -> usize
Get the number of pending executions awaiting commission reports.
Useful for monitoring whether commission reports are being received. A growing count may indicate IB connection issues or delayed reports.
Sourcepub fn clear_stale_executions(&self, max_age: Duration) -> usize
pub fn clear_stale_executions(&self, max_age: Duration) -> usize
Clear stale executions older than the given duration.
Returns the number of cleared entries.
Call this periodically to prevent unbounded memory growth if commission reports are delayed or lost. A reasonable interval is 5-10 minutes with a max_age of 1 hour.
Sourcepub fn clear_stale_order_ids(&self, max_age: Duration) -> usize
pub fn clear_stale_order_ids(&self, max_age: Duration) -> usize
Clear order ID mappings older than the given duration.
Returns the number of cleared entries.
§Why This Is Needed
IB does not guarantee event ordering between OrderStatus("Filled") and
ExecutionData/CommissionReport. For fast-filling orders (especially
market orders), execution data may arrive AFTER the filled status — or
the filled status may not arrive at all. Removing mappings on terminal
status would cause data loss.
Call this periodically alongside clear_stale_executions(). A reasonable
interval is 5-10 minutes with a max_age of 1 hour.
Sourcepub fn clear_stale_pending_cancels(&self, max_age: Duration) -> usize
pub fn clear_stale_pending_cancels(&self, max_age: Duration) -> usize
Clear pending cancel entries older than the given duration.
Returns the number of cleared entries.
Pending cancels are tracked to differentiate user-initiated cancellation from time-based expiration. If a cancel request is submitted but the order never receives terminal status (e.g., network disconnect), the entry would remain indefinitely. Call this alongside other stale cleanup methods.
Sourcepub fn disconnect(&self)
pub fn disconnect(&self)
Disconnect from IB Gateway.
Signals the ibapi client to shut down and releases the client ID for reuse.
IbkrClient implements Clone and has no Drop impl: the underlying
connection is released automatically when the last Arc<Client> reference
is dropped. Calling disconnect() explicitly terminates the connection
immediately for all clones sharing this client.
Any active account_stream() iterators will receive errors on their
next iteration attempt.
This is idempotent — calling it multiple times is safe.
Sourcepub async fn open_bracket_order(
&self,
request: BracketOrderRequest,
) -> BracketOrderResult
pub async fn open_bracket_order( &self, request: BracketOrderRequest, ) -> BracketOrderResult
Place a bracket order (entry + take-profit + stop-loss) with OCA linking.
A bracket order consists of three linked orders:
- Entry: Limit order to enter the position
- Take Profit: Limit order to exit at profit target (OCA-linked to SL)
- Stop Loss: Stop order to exit at loss limit (OCA-linked to TP)
§OCA (One-Cancels-All) Behavior
When either exit order fills, IB automatically cancels the other. This prevents the dangerous scenario where take-profit fills but stop-loss remains open, potentially opening an unintended opposing position.
§All-or-Nothing Semantics
If any leg is rejected by IB (e.g., insufficient margin, invalid price),
this method cancels all other legs and returns all three as Inactive.
You will never receive a mix of active and inactive legs.
The same all-legs cancellation applies if any leg fails to report a
terminal status within the placement timeout (an unknown/no-status
outcome): leaving part of a bracket working while the rest is in an
unknown state is unsafe, so all three legs are cancelled and an error is
returned. This is intentionally stricter than single-order placement
(open_order), where a no-status outcome retains the order and defers
resolution to the account stream.
§Cancellation Safety
This future registers order ID mappings before submitting to IB. If
cancelled mid-flight, orders may still be submitted and mappings will
leak. Avoid cancelling this future; use IB’s native order timeout via
TimeInForce if timeout behavior is needed.
§Example
let request = BracketOrderRequest {
instrument: "AAPL".into(),
strategy: StrategyId::new("my-strategy"),
parent_cid: ClientOrderId::new("bracket-001"),
side: Side::Buy,
quantity: dec!(100),
entry_price: dec!(150.00),
take_profit_price: dec!(160.00), // +$10 profit target
stop_loss_price: dec!(145.00), // -$5 stop loss
time_in_force: TimeInForce::GoodUntilCancelled { post_only: false },
};
let result = client.open_bracket_order(request).await;Trait Implementations§
Source§impl BracketOrderClient for IbkrClient
impl BracketOrderClient for IbkrClient
Source§async fn open_bracket_order(
&self,
request: UnifiedBracketOrderRequest<ExchangeId, &InstrumentNameExchange>,
) -> UnifiedBracketOrderResult
async fn open_bracket_order( &self, request: UnifiedBracketOrderRequest<ExchangeId, &InstrumentNameExchange>, ) -> UnifiedBracketOrderResult
Source§impl Clone for IbkrClient
impl Clone for IbkrClient
Source§fn clone(&self) -> IbkrClient
fn clone(&self) -> IbkrClient
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for IbkrClient
impl Debug for IbkrClient
Source§impl ExecutionClient for IbkrClient
impl ExecutionClient for IbkrClient
Source§fn new(config: Self::Config) -> Self
fn new(config: Self::Config) -> Self
Create a new IBKR client by connecting to TWS/Gateway.
§Blocking
This method performs blocking TCP I/O and IB API handshake. If called
from an async context, wrap in tokio::task::spawn_blocking or call
from a dedicated thread.
§Panics
Panics if connection fails. The ExecutionClient trait doesn’t allow
new() to return Result. Use IbkrClient::connect_sync() directly
for fallible construction.
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>
Fetch account snapshot (balances and positions).
§Limitations
-
The
ordersfield in eachInstrumentAccountSnapshotis always empty. IB’s positions endpoint returns position data only, not open orders. Usefetch_open_orders()oraccount_stream()for order state. -
Position quantity and average cost from IB are not carried in the returned
InstrumentAccountSnapshot. The struct only indicates which instruments have positions, not the position sizes. This is a limitation of theInstrumentAccountSnapshottype, not the IB API.
§Known Issue: ibapi Decode Errors
You may see log errors like "error decoding message: error occurred: unexpected message: Error". This is an upstream ibapi limitation where
IB Error messages (type 4) on subscription channels aren’t properly
routed — they’re logged but don’t affect functionality.
§Timeout
Uses 5-second timeout between position updates. If IB stalls mid-stream
(e.g., due to the ibapi PositionEnd routing bug), returns partial results
after 5s of inactivity rather than blocking indefinitely.
For accounts with no positions, this method waits the full 5-second timeout before returning an empty position list.
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>
Stream account events (order updates, fills, commissions).
§Thread Lifecycle
Spawns a background thread to read from the blocking IB subscription. The thread terminates when:
- The returned
BoxStreamis dropped (channel closes) - The IB subscription ends (disconnect)
Important: If IB is stalled (no events flowing), the thread blocks on the iterator. Dropping the stream signals termination, but the thread won’t observe it until the next IB event arrives. For graceful shutdown during stalls, the caller should disconnect the IB connection.
§Duplicate Events
Orders submitted via open_order() will emit OrderSnapshot events on
this stream in addition to the response from open_order() itself. This
is inherent to IB’s API — both the per-order subscription and the global
order update stream receive the same OrderStatus events. Callers should
deduplicate if needed.
§Filter Parameters
The assets and instruments parameters are currently ignored. IB’s
order_update_stream() returns events for all orders on the account.
Callers subscribing for a specific instrument will receive events for
all instruments.
Source§async fn cancel_order(
&self,
request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>,
) -> Option<UnindexedOrderResponseCancel>
async fn cancel_order( &self, request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>, ) -> Option<UnindexedOrderResponseCancel>
Cancel an order.
§Async Cancel Semantics
Returns Ok(Cancelled) when the cancel request is submitted, not when
the order is confirmed cancelled. The actual cancellation confirmation comes
via account_stream as an OrderStatus::Cancelled event.
The order ID mapping is retained until terminal status is received via
account_stream, ensuring fill events arriving between cancel request and
confirmation are not lost.
Source§async fn open_order(
&self,
request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>,
) -> Option<Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>>
async fn open_order( &self, request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>, ) -> Option<Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>>
Submit an order to IB.
§Cancellation Safety
This future registers the order ID mapping before submitting to IB.
If the future is cancelled (e.g., via tokio::select! timeout) after
registration but before completion:
- The order may still be submitted to IB
- The order ID mapping will leak (not cleaned up)
- Subsequent fills will be processed via
account_stream
Callers should avoid cancelling this future mid-flight. If timeout
behavior is needed, prefer setting IB’s native order timeout via
TimeInForce instead.
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>
Fetch account balances.
§Limitations
The time_exchange field in returned balances uses Utc::now(), not
the actual IB server timestamp. IB’s account summary endpoint does not
provide timestamps per balance update.
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>
Fetch open orders.
§Limitations
- The
filled_qtyin returned orders is set to zero. IB’s open orders endpoint returns order definitions, not fill status. For accurate filled quantities, useaccount_streamwhich providesOrderStatusevents with fill progress. - The
time_in_forcedefaults toGoodUntilCancelled. IB’s open orders endpoint does not return the original TIF setting. - This method blocks on IB’s subscription until IB sends an end-of-data marker. If IB is stalled, this will block indefinitely.
Source§async fn fetch_trades(
&self,
time_since: DateTime<Utc>,
instruments: &[InstrumentNameExchange],
) -> Result<Vec<Trade<AssetNameExchange, InstrumentNameExchange>>, UnindexedClientError>
async fn fetch_trades( &self, time_since: DateTime<Utc>, instruments: &[InstrumentNameExchange], ) -> Result<Vec<Trade<AssetNameExchange, InstrumentNameExchange>>, UnindexedClientError>
Fetch historical trades (executions).
§Limitations
- IB only returns executions from the current trading day. The
time_sinceparameter is applied client-side to filter within that day. For historical executions beyond today, use IB’s Flex Query or Activity Statements. - Fees are always zero. IB’s executions endpoint doesn’t include commission
data. For trades with accurate fees, use
account_stream()which pairsExecutionDatawithCommissionReportevents. - This method blocks on IB’s executions subscription until IB sends an end-of-data marker. If IB is stalled, this will block indefinitely.
const EXCHANGE: ExchangeId = ExchangeId::Ibkr
type Config = IbkrConfig
type AccountStream = Pin<Box<dyn Stream<Item = AccountEvent<ExchangeId, AssetNameExchange, InstrumentNameExchange>> + Send>>
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
Auto Trait Implementations§
impl !RefUnwindSafe for IbkrClient
impl !UnwindSafe for IbkrClient
impl Freeze for IbkrClient
impl Send for IbkrClient
impl Sync for IbkrClient
impl Unpin for IbkrClient
impl UnsafeUnpin for IbkrClient
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.