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, IbkrConnectError>
pub fn connect_sync(config: IbkrConfig) -> Result<Self, IbkrConnectError>
Connect to TWS/Gateway and register every contract in IbkrConfig::contracts
(sync, blocking).
Each contract is built from its config, resolved as
resolve_contract does and registered as
register_contract does. If any of them fails, the
connection is dropped and IbkrConnectError::Contracts lists every contract that
failed, not just the first, so that a client never starts without a contract its caller
configured. To connect anyway and get the failures back, use
connect_sync_lenient.
The library does not retry. If every contract in IbkrConnectError::Contracts has a
reason that is_transient,
connecting again may succeed. Otherwise fix the config, or connect with
connect_sync_lenient.
§Errors
IbkrConnectError::Connectif TWS/Gateway cannot be reached or refuses the connection.IbkrConnectError::Contractsif any configured contract cannot be registered.
Sourcepub fn connect_sync_lenient(
config: IbkrConfig,
) -> Result<ConnectOutcome, UnindexedClientError>
pub fn connect_sync_lenient( config: IbkrConfig, ) -> Result<ConnectOutcome, UnindexedClientError>
Connect to TWS/Gateway as connect_sync does, but without each
configured contract that cannot be registered, returning those in
ConnectOutcome::skipped.
Orders for a skipped contract’s instrument are refused as for any unregistered
instrument, so check skipped. To retry one, call
resolve_contract and then
register_contract on the connected client.
Blocking, like connect_sync: it waits for the connection and for
one contract details request per configured contract, each of which can take up to 10
seconds if IB stops answering. From async code, call it inside
tokio::task::spawn_blocking.
§Errors
Returns an error if TWS/Gateway cannot be reached or refuses the connection.
Sourcepub async fn resolve_contract(
&self,
contract: &Contract,
) -> Result<Contract, ResolveContractError>
pub async fn resolve_contract( &self, contract: &Contract, ) -> Result<Contract, ResolveContractError>
Resolve a contract description through IB’s contract details, on this client’s connection, into the one IB contract it matches.
A contract built locally, such as stock_contract("AAPL", "SMART", "USD"), has no IB
contract id until it is resolved, and register_contract
refuses it. Resolve it here first:
use rustrade_execution::client::ibkr::contract::stock_contract;
let aapl = client
.resolve_contract(&stock_contract("AAPL", "SMART", "USD"))
.await?;
client.register_contract("AAPL".into(), aapl)?;§Errors
ResolveContractError::is_transient tells the failures worth retrying from the rest.
ResolveContractError::NoMatchwhen no IB contract matches the description.ResolveContractError::Ambiguouswhen several do. Narrow the description, or pick one of the matches it carries.ResolveContractError::Refusedwhen IB answers the request with another error, such as a description it cannot validate. The same request is usually refused again; tell the errors worth retrying apart by IB’scode.ResolveContractError::Connectivitywhen the connection drops (ConnectivityError::Socket) or IB sends nothing for 10 s before finishing its answer (ConnectivityError::Timeout). Transient: retry onceibapihas reconnected.ResolveContractError::Failedwhen the request cannot be made or read for any other reason, such as a client that has shut down or given up reconnecting.
Sourcepub fn register_contract(
&self,
name: InstrumentNameExchange,
contract: Contract,
) -> Result<(), ContractRegistryError>
pub fn register_contract( &self, name: InstrumentNameExchange, contract: Contract, ) -> Result<(), ContractRegistryError>
Register a contract IB has resolved for an instrument, so that orders can be placed on
it and everything IB reports for it (fills, order listings, positions) is attributed to
name.
Resolve the contract first with resolve_contract, or list it
in IbkrConfig::contracts for connect_sync to resolve.
§Errors
Leaves the registry unchanged and returns:
ContractRegistryError::Unresolvedwhen the contract has no IB contract id. IB reports everything about a contract by that id, so without it the orders would fill with no fill ever reported.ContractRegistryError::ContractIdTakenwhen the contract is already registered under another name.
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. So a Filled status frees the
order’s client order id but keeps its IB order ID’s entry, and an order
whose terminal status never arrives keeps both: its id cannot name a new
order (see open_order’s “Client order ids”) until this clears it.
Call this periodically alongside clear_stale_executions(). A reasonable
interval is 5-10 minutes with a max_age of 1 hour. An order still working
past max_age is cleared too, and its id freed.
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.
Shuts the ibapi client down for every clone sharing it: requests in flight fail, any
active account_stream() ends with StreamTerminated, and later requests are refused.
§Client ID release
This closes the TCP connection, even while other clones of this client (IbkrClient
implements Clone and shares one connection) or their subscriptions are still alive.
TWS/Gateway frees the API client ID once it has processed the close, which can take a
moment: connect_sync under the same ID right after this call may be
refused with IB error 326 (“client id is already in use”), and succeeds on a retry. The
clones need not be dropped first. A clone that outlives the call is shut down, not
reconnected.
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.
§Failure Semantics
If any leg is rejected by IB (e.g., insufficient margin, invalid price),
or cannot be sent, this method cancels every leg already sent and
returns the legs as Inactive, except for legs of unknown fate (below).
The same all-legs cancellation applies if any leg fails to report a status within the placement timeout, or the transport drops while waiting (an unknown/no-status outcome): leaving part of a bracket working while the rest is in an unknown state is unsafe, so every leg is cancelled and an error is returned.
If any leg’s client order id (the parent’s, or the _tp/_sl id
derived from it) is held by a live order, nothing is sent and every leg
comes back Inactive with ApiError::DuplicateClientOrderId; the live
order is unaffected. See ExecutionClient::open_order’s “Client order
ids” for when an id is held.
§Legs of Unknown Fate
Cancelling does not settle a leg whose fate is unknown: a leg that
reported no status, or whose rollback cancel could not be sent (ibapi
refuses sends while its transport is down), may still be live or held
at TWS. Such a leg comes back Open with zero fill and keeps its
order-id mapping, as a no-status single order does, so the account
stream can report how it ends. The other legs come back Inactive with
the error. A leg IB rejected is settled even if its cancel was refused.
So a failed bracket can return a mix of Open and Inactive legs, and
every leg can be Open when none reported a status: an all-Open
result is then indistinguishable from success, and the failure shows
only in the log and on the account stream. Treat an Open leg as
working until the account stream or
ExecutionClient::fetch_open_orders says otherwise.
§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§impl Debug for IbkrClient
impl Debug for IbkrClient
Source§impl ExecutionClient for IbkrClient
impl ExecutionClient for IbkrClient
Source§fn validate_config(
config: &Self::Config,
instruments: &[ClientInstrument<'_>],
) -> Result<(), String>
fn validate_config( config: &Self::Config, instruments: &[ClientInstrument<'_>], ) -> Result<(), String>
Rejects a ContractConfig that connect_sync would refuse as invalid,
or whose security_type contradicts the kind of the instrument it is keyed to by name:
STK or CASH for a Spot, FUT for a Future, OPT for an Option. Every problem
found is reported, not just the first.
Spot covers both a stock and a currency pair, so STK and CASH cannot be told apart
here: either is accepted for any Spot instrument.
An entry keyed to no instrument on this exchange, and an instrument with no entry, are both
accepted: a contract can also be registered later with
register_contract, which this cannot see.
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 IbkrClient::connect_sync fails: when TWS/Gateway cannot be reached or
refuses the connection, or when any contract in IbkrConfig::contracts cannot be
registered. The ExecutionClient trait doesn’t allow new() to return Result. Call
IbkrClient::connect_sync or IbkrClient::connect_sync_lenient 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 the position and open orders of each registered instrument IB reports either in.
§Positions
Each instrument IB reports a position or an open order of this API client’s in, and that
is registered (and in instruments, when that is not empty), gets an
InstrumentAccountSnapshot. Its position is
PositionReport::Open, carrying:
quantity: IB’s signed position, negative when short, in shares for a stock and in contracts for a future or an option.ibapihands it over as anf64, converted withDecimal::try_from, which rounds to the float’s precision of about 15 significant digits (0.1 stays 0.1) rather than keeping its exact binary expansion.entry_price: IB’s average cost divided by the contract multiplier, so a future or an option is quoted as its orders are priced. It includes commissions.Nonewhen IB sends no average cost, or no multiplier for a contract other than a stock or a forex pair.time_exchange: the time of the call, since IB does not timestamp positions.
unrealized_pnl and the margin fields are None: IB’s positions subscription does not
carry them.
A zero quantity, which IB reports for a position closed today, is
PositionReport::Flat. So is each instrument in
instruments that is registered with its contract ID but that IB did not list, provided IB
marked the end of its listing (PositionEnd) during the read. Without that marker such
instruments are left out, with a warning, since the listing may be incomplete.
A requested instrument registered without a contract ID, or whose ID was registered again
under another name, cannot be matched to IB’s reports and is left out too.
Several accounts. This client reads the positions of every account the login manages,
one account at a time. When more than one account holds the same instrument, the first account to report a
non-zero quantity is kept and the others are dropped with a warning; they are never summed.
A consumer comparing the reported quantity with its own, as the rustrade engine’s position
drift check does, therefore compares against that one account.
Which account comes first depends on the order IB lists the login’s accounts in, so a
caller holding the same instrument in several accounts should not rely on it.
An instrument with an open order whose position the read could not establish, because IB
did not mark the end of its listing, reports it
PositionReport::Unreported.
§Orders
Each instrument’s orders are this API client’s open orders in it, read as
fetch_open_orders reads them, from one listing of
the account’s open orders. An order this client does not track but placed under a client
order id, such as one placed before a restart, is listed under that id, and tracked from
then on.
orders_complete is true when IB listed every open order to the end of the listing and
each of the instrument’s was read back under the client order id it was placed with and
with its status. So it is false on an instrument with an order of this API client’s
that carries no client order id, which is listed under its IB order id: one placed by an
earlier version of this client, or, for a client connected as 0, one entered in TWS. So it
is too with an order whose shape this client never sends, one IB listed without its
status, or one whose client order id names another order this client tracks. When the
listing cannot be read, no instrument’s orders are complete, with a warning, and the
snapshot is still returned.
§Errors
ConnectivityError::Timeoutif IB does not answer the request for the login’s accounts within 30 seconds. Transient: retry.UnindexedClientError::Internalif a position quantity does not convert to aDecimal, if the request for the login’s accounts fails otherwise or yields none (as it does when the session ends during it), if a positions subscription fails, or asfetch_balances.
§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
Each account’s positions read ends at IB’s end-of-listing marker. If IB sends nothing for 5 seconds before it, that account’s read gives up and the next one starts, so a login with N accounts can wait up to N × 5 seconds. The positions received are returned rather than blocking indefinitely, without the flat reports that need every account’s listing to be complete.
Each read uses IB’s positions-multi request for that account, whose replies carry a request ID, so concurrent calls on clones of this client never read each other’s listing.
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 two background threads: ibkr-order-stream reads the blocking IB
subscription, and ibkr-recovery watches for gaps in event delivery
(see below). The watcher exits within about a second of the returned
BoxStream being dropped or the stream ending, or once the recovery or
order check in progress then ends. The reader exits when:
- The returned
BoxStreamis dropped (channel closes), or the stream has ended, and the next IB event arrives - The IB subscription ends
Important: If IB is stalled (no events flowing), the reader thread blocks on the iterator. Dropping the stream signals termination, but the thread won’t observe it until the next IB event arrives. A shutdown does end the subscription, so the thread exits then.
§Reconnects and Recovery
ibapi reconnects its socket to TWS/Gateway by itself, and this stream
stays open across that: a transient drop produces neither an error nor
StreamTerminated. TWS does not resend what it sent while the socket was
down, and TWS losing its own link to IB’s servers (notice 1100) opens the
same kind of gap. Once delivery is restored, this stream asks TWS for the
last seven days of executions and emits the ones from the gap as Trade events. The
gap is taken to start up to a poll interval before the drop was noticed,
minus a lookback margin. Trades the stream already delivered are not
emitted twice. The log line Recovered IBKR fills after a gap in event delivery marks one.
When that read fails three times for a reason other than the transport
dropping again, the gap is given up: the stream sends one
AccountEventKind::FillRecoveryGaveUp covering every instrument, from the
start of the gap to when it gave up, and stays open. Read that span with
ExecutionClient::fetch_trades.
A gap that starts before the span IB surely holds (see fetch_trades’ held_since) is
reported too, even though the read succeeds: after the recovered trades, one
FillRecoveryGaveUp with FillRecoveryFailure::VenueRetention covers the part before
that span, ending the moment before held_since. fetch_trades cannot read that part
either, so reconcile it from another source.
Orders that ended during the gap. After the fills, recovered or given
up, the stream checks how each order the client holds as live ended:
those placed through it, listed by fetch_open_orders or
account_snapshot, or reported live on this stream, and not yet seen to
end. It lists the open orders once, and looks up each held order the
listing no longer shows in IB’s completed orders, as
OrderStatusClient::fetch_ended_orders does. Each that ended is sent
as an OrderSnapshot of its inactive state, under StrategyId::unknown,
and its client order id is freed, as when the stream reports an order
ending itself. An order IB lists as neither is dropped from the set with a
warning. A check that fails is retried after 1, 2, 4, 8 and 16 minutes
while connected, then given up with an error log: an order that ended
then stays live in engine state until the next gap’s check, so reconcile
with fetch_open_orders. The client holds at most 4,096 orders as live,
forgetting the oldest beyond that. The check reads IB’s listings on the
recovery thread, each bounded by a 10-second stall timeout, and the thread
reads notices again once it ends.
Call this within a Tokio runtime with its time driver enabled, as
#[tokio::main] and Runtime::new build: the recovery thread runs the
order check on the runtime this was called on.
While TWS reports its link to IB’s servers lost (1100), nothing marks the gap on this stream until the link is restored and the recovered fills arrive.
The stream ends with StreamTerminated when the client shuts down for
good, because ibapi gave up reconnecting, IbkrClient::disconnect was
called, or TWS/Gateway ended the API session. Called on a client that has
already shut down, this method fails, so replace the client.
§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.
Trade events are delivered once each. Executions that answer an
executions request, such as ExecutionClient::fetch_trades, are not
reported here as fills.
§Corrections
IB reports a corrected execution as a further execution whose id differs
only in the digits after the final period (….01.02 corrects ….01.01).
It is sent as AccountEventKind::TradeAmended, Corrected, with the
correction as the replacement Trade, once its commission report arrives.
Its original is the revision this stream delivered before, or, for an
execution from before the stream, the revision the correction’s id says
it corrects. A revision older than one already delivered is dropped.
Fills recovered after a gap are delivered originals first, so a
correction recovered with them comes after every original in the gap,
not in time order.
Each correction is logged at warn! when it arrives, so one that never
gets a commission report is still seen. IB documents no busts.
The revisions delivered are remembered in a bounded cache, like the dedup cache. A correction of an execution the cache has forgotten names the previous revision by its id, which is the original unless an intermediate revision was delivered and forgotten too.
One case loses a fill: a correction that arrives live after a reconnect, before recovery has delivered its original from the gap. The correction is sent naming the original, and recovery then drops the original as older than a revision already delivered, so the trade reaches the stream only as the amendment.
§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>,
) -> UnindexedOrderResponseCancel
async fn cancel_order( &self, request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>, ) -> 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>,
) -> Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>
async fn open_order( &self, request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>, ) -> Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>
Submit an order to IB.
§Client order ids
The client order id is sent as the order’s IB order reference, so it must be at most 128
ASCII characters; any other is refused before anything is sent. TWS does not check the
reference’s uniqueness, so this client keeps each id unique itself: an open
under an id that a live order holds is refused before anything is sent, as
ApiError::DuplicateClientOrderId, and the live order is unaffected. An order stops
holding its id when the account stream reports it Filled, Cancelled or Inactive, or
when clear_stale_order_ids reaps it, or when the
account stream’s check after a gap finds it ended. Without a running
account_stream, no order is reported ended, and an id is held
until reaped.
An open whose outcome is unknown (OrderError::is_outcome_unknown) keeps holding its id,
as TWS may hold the order. If the order was never placed, the id is held until reaped, so
retry such an open under a new id.
A cancel names the live order under its id, so cancelling a filled order’s id once it is freed is refused locally as not found, rather than at TWS.
§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 - The client order id stays held, so a retry under it is refused as
ApiError::DuplicateClientOrderIduntil the account stream reports the order ended orclear_stale_order_idsreaps it. Retry under a fresh id. The same holds when this returnsOpenwithout a status from TWS.
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.
§Errors
UnindexedClientError::Internal if the request fails, IB answers with an
error, IB sends nothing for 10 seconds before the end of the listing, or the
connection drops during it.
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 the open orders this API client placed.
IB numbers orders per API client, so orders other clients placed, and orders entered in TWS, are left out: their ids could name an order of this client’s. IB reports orders entered in TWS under API client id 0, so a client connected as 0 keeps them.
Each order is returned under the client order id it was placed with: the one this client
tracks it under, with the kind, price and time in force it was placed with, or else the
one its IB order reference carries, with those read back from IB’s listing. An order
this client does not track but placed under a client order id, such as one placed before
a restart, is tracked from then on, so its fills and status reach the account stream and
it can be cancelled. Every listed order of this client’s is adopted so, whatever
instruments asks for. An order without a reference, such as one placed by an earlier
version of this client, or, for a client connected as 0, one entered in TWS, is returned
under its IB order id; so is one whose reference names another order this client tracks.
One whose order type or time in force this client never sends is left out, with a
warning. Every order’s filled quantity is the one IB reports with it.
Each returned order named by the id it was placed with is held as live, for the account stream’s check of how it ended after a gap.
IB lists every client’s open orders to every request for them, so this client and its clones read the listing one at a time.
§Errors
UnindexedClientError::Internal if the request fails, IB answers with an
error, IB sends nothing for 10 seconds before the end of the listing, or the
connection drops during it.
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>
Fetch historical trades (executions).
§Limitations
- IB lists executions from the last 7 days at most, counted in a time zone of its own. One
request reads them all, the span is applied client-side, and the read is always
complete (
resume: None) for what IB returned. A span starting more than 5 days ago hasheld_sinceset to 5 days ago, the most the read is sure to reach: fills before it may be missing. For older executions, use IB’s Flex Query or Activity Statements. - Only the executions of orders this API client placed are returned, plus any
execution IB flags as an IB-initiated liquidation, whoever placed its order, as a
Liquidationtrade. WithIbkrConfig::other_clients_fillsset, every other client’s execution IB sends is returned too, as anExternaltrade under IB’s permanent id for its order. An execution in a contract this client has not registered is skipped either way. - Each trade’s fees come from the commission report IB sends with its
execution (see Fill fees for what that figure includes).
An execution whose report IB did not send is returned with a zero
fee in
UNKNOWN_FEE_ASSET, with a warning. - Trades are returned in
time_exchangeorder, then by id. A corrected execution keeps the position of its first revision.
§Errors
UnindexedClientError::Internal if the request fails, IB answers with an
error, IB sends nothing for 10 seconds before the end of the listing, or the
connection drops during it.
§Corrections
IB reports a corrected execution as a further execution whose id differs
only in the digits after the final period. Each execution is returned
once, at its latest revision, under that revision’s id: the replacement a
TradeAmended on ExecutionClient::account_stream names.
const EXCHANGE: ExchangeId = ExchangeId::Ibkr
Source§const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant]
const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant]
InstrumentKindDiscriminants this client can trade. Read moretype 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 = 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 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.
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§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 moreSource§impl ShortabilityClient for IbkrClient
impl ShortabilityClient for IbkrClient
Source§async fn fetch_shortability(
&self,
instrument: &InstrumentNameExchange,
) -> Result<Shortability, UnindexedClientError>
async fn fetch_shortability( &self, instrument: &InstrumentNameExchange, ) -> Result<Shortability, UnindexedClientError>
Reads IB’s shortability ticks with a streaming market data request for generic tick 236, which IB answers with:
- tick 46, a level: above 2.5, at least 1000 shares are available to borrow, so the stock
is
shortableandeasy_to_borrow; above 1.5, it isshortableonce shares are located, so not easy to borrow; 1.5 or below, it is notshortable, andeasy_to_borrowis left unknown. - tick 89, the shares IB has to lend, as
available.
A stock that is shortable but not easy_to_borrow needs a locate. IB accepts a short
sale of it and holds the order while it looks for shares (IB’s code 404, which this client
reports as a working order, not a rejection), and may never find them. So shortable
says a short sale may be tried, not that shares are on hand.
fee_rate is unknown: IB reports no borrow fee to the API. delisting_at is unknown too.
The request is read until both ticks have arrived, or for
IbkrConfig::shortability_timeout (10 s by
default); one that arrives alone is returned on its own. Once tick 46 has arrived, tick 89
is waited for at most 2 s more: IB sends both together for a stock it has shares of, and
can send no tick 89 at all for one it has few or none of, which then comes back with
available unknown. While it runs it holds one of the account’s market data lines, and it
is cancelled when the read ends. Calls made at once each hold their own line, so checking
many stocks together can fill the account’s lines. The read runs on a blocking thread, so dropping the
returned future does not end it early: it runs to its end, holding the line until then.
The request asks for IB’s delayed market data, which IB serves only where the account has no live subscription for the stock, so an account without one still gets the ticks. IB does not say how far behind delayed shortability ticks are.
§Errors
ApiError::InstrumentInvalidfor an instrument whose contract is not registered, and for one that is not a stock, as only a stock is sold short by borrowing it.ConnectivityError::Timeoutif neither tick arrives in time, orApiError::RequestRejectedwhen IB sent notices instead. The message carries IB’s notices. A notice that the account may not have the stock’s market data, live or delayed, ends the read at once.ApiError::CapacityExceededif every one of the account’s market data lines is in use, by this client, another API client or TWS. Waiting does not free one; cancelling a subscription does.ApiError::RequestRejectedif IB refuses the request, with IB’s code and message.ConnectivityError::Socketif the connection to TWS/Gateway drops.
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.