[](https://github.com/wboayue/rust-ibapi/actions/workflows/ci.yml)
[](https://opensource.org/licenses/MIT)
[](https://crates.io/crates/ibapi)
[](https://docs.rs/ibapi/latest/ibapi/)
[](https://coveralls.io/github/wboayue/rust-ibapi?branch=main)
## Introduction
This library provides a comprehensive Rust implementation of the Interactive Brokers [TWS API](https://ibkrcampus.com/campus/ibkr-api-page/twsapi-doc/), offering a robust and user-friendly interface for TWS and IB Gateway. Designed with performance and simplicity in mind,Β `ibapi`Β is a good fit for automated trading systems, market analysis, real-time data collection and portfolio management tools.
With this fully featured API, you can retrieve account information, access real-time and historical market data, manage orders, perform market scans, and access news and Wall Street Horizons (WSH) event data.
> **π Migrating?** See the [v4.x β v5.0](docs/migration-5.0.md) and [v3.x β v4.x](docs/migration-4.0.md) guides for the latest breaking changes, the [v2.x β v3.0 guide](docs/migration-3.0.md) for the `Subscription` shape and notification handling, or the [v1.x β v2.0 guide](MIGRATION.md) for the older transition.
## Sync/Async Architecture
rust-ibapi ships both asynchronous (Tokio) and blocking (threaded) clients. The async client is enabled by default; opt into the blocking client with the `sync` feature and use both together when you need to mix execution models.
- **async** *(default)*: Non-blocking client using Tokio tasks and broadcast channels. Available as `ibapi::Client`.
- **sync**: Blocking client using crossbeam channels. Available as `ibapi::client::blocking::Client` (or `ibapi::Client` when `async` is disabled).
Add to your `Cargo.toml`:
```toml
# Async only (default features)
ibapi = "5.2"
# Blocking only
ibapi = { version = "5.2", default-features = false, features = ["sync"] }
# Async + blocking together
ibapi = { version = "5.2", default-features = false, features = ["sync", "async"] }
```
```bash
# Async client (default configuration)
cargo test
cargo run --example async_connect
# Blocking client only
cargo test --no-default-features --features sync
# Validate both clients together
cargo test --all-features
```
When both features are enabled, import the blocking types explicitly:
```rust
use ibapi::Client; // async client
use ibapi::client::blocking::Client; // blocking client
```
## Available APIs
The [Client documentation](https://docs.rs/ibapi/latest/ibapi/struct.Client.html) provides comprehensive details on all currently available APIs, including trading, account management, and market data features, along with examples to help you get started.
## Examples
These examples demonstrate key features of the `ibapi` API.
### Connecting to TWS
#### Sync Example
```rust
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
fn main() {
let connection_url = "127.0.0.1:4002";
let client = Client::connect(connection_url, 100).expect("connection to TWS failed!");
println!("Successfully connected to TWS at {connection_url}");
}
```
#### Async Example
```rust
use ibapi::prelude::*;
#[tokio::main]
async fn main() {
let connection_url = "127.0.0.1:4002";
let client = Client::connect(connection_url, 100).await.expect("connection to TWS failed!");
println!("Successfully connected to TWS at {connection_url}");
}
```
> **Note**: Use `127.0.0.1` instead of `localhost` for the connection. On some systems, `localhost` resolves to an IPv6 address, which TWS may block. TWS only allows specifying IPv4 addresses in the allowed IP addresses list.
### Creating Contracts
The library provides a powerful type-safe contract builder API. Here's how to create a stock contract for TSLA:
```rust
use ibapi::prelude::*;
// Simple stock contract with defaults (USD, SMART routing)
let contract = Contract::stock("TSLA").build();
// Stock with customization - accepts string literals directly
let contract = Contract::stock("7203")
.on_exchange("TSEJ")
.in_currency("JPY")
.build();
```
The builder API provides type-safe construction for all contract types with compile-time validation:
```rust
// Options - required fields enforced at compile time
let option = Contract::call("AAPL")
.strike(150.0)
.expires_on(2024, 12, 20)
.build();
// Futures with convenience methods
let futures = Contract::futures("ES")
.front_month() // Next expiring contract
.build();
// Forex pairs
let forex = Contract::forex("EUR", "USD").build();
// Bonds - simplified API for CUSIP and ISIN
let treasury = Contract::bond_cusip("912810RN0");
let euro_bond = Contract::bond_isin("DE0001102309");
```
See the [Contract Builder Guide](docs/contract-builder.md) for comprehensive documentation on all contract types.
For a complete list of contract attributes, explore the [Contract documentation](https://docs.rs/ibapi/latest/ibapi/contracts/struct.Contract.html).
### Requesting Historical Market Data
#### Sync Example
```rust
use time::macros::datetime;
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
fn main() {
let connection_url = "127.0.0.1:4002";
let client = Client::connect(connection_url, 100).expect("connection to TWS failed!");
let contract = Contract::stock("AAPL").build();
let historical_data = client
.historical_data(&contract, HistoricalBarSize::Hour)
.ending(datetime!(2023-04-11 20:00 UTC))
.duration(1.days())
.what_to_show(HistoricalWhatToShow::Trades)
.trading_hours(TradingHours::Regular)
.fetch()
.expect("historical data request failed");
println!("start: {:?}, end: {:?}", historical_data.start, historical_data.end);
for bar in &historical_data.bars {
println!("{bar:?}");
}
}
```
#### Async Example
```rust
use time::macros::datetime;
use ibapi::prelude::*;
#[tokio::main]
async fn main() {
let connection_url = "127.0.0.1:4002";
let client = Client::connect(connection_url, 100).await.expect("connection to TWS failed!");
let contract = Contract::stock("AAPL").build();
let historical_data = client
.historical_data(&contract, HistoricalBarSize::Hour)
.ending(datetime!(2023-04-11 20:00 UTC))
.duration(1.days())
.what_to_show(HistoricalWhatToShow::Trades)
.trading_hours(TradingHours::Regular)
.fetch()
.await
.expect("historical data request failed");
println!("start: {:?}, end: {:?}", historical_data.start, historical_data.end);
for bar in &historical_data.bars {
println!("{bar:?}");
}
}
```
### Requesting Realtime Market Data
These examples use `iter_data()` (sync) / `filter_data()` (async) to focus on bars; per-subscription notices are filtered (and logged at `warn!`). See [Handling notifications](#handling-notifications) for the pattern when you also want to observe `Notice` items.
#### Sync Example
```rust
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
fn main() {
let client = Client::connect("127.0.0.1:4002", 100).expect("connection to TWS failed!");
// Request real-time bars data for AAPL with 5-second intervals
let contract = Contract::stock("AAPL").build();
let subscription = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.expect("realtime bars request failed!");
for bar in subscription.iter_data() {
match bar {
Ok(bar) => println!("bar: {bar:?}"),
Err(e) => {
eprintln!("error: {e:?}");
break;
}
}
}
}
```
#### Async Example
```rust
use ibapi::prelude::*;
#[tokio::main]
async fn main() {
let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection to TWS failed!");
// Request real-time bars data for AAPL with 5-second intervals
let contract = Contract::stock("AAPL").build();
let mut subscription = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.await
.expect("realtime bars request failed!");
let mut data = subscription.filter_data();
while let Some(item) = data.next().await {
match item {
Ok(bar) => println!("bar: {bar:?}"),
Err(e) => { eprintln!("error: {e}"); break; }
}
}
}
```
In both examples, the request for realtime bars returns a [Subscription](https://docs.rs/ibapi/latest/ibapi/subscriptions/struct.Subscription.html) that can be used as an iterator (sync) or stream (async). The subscription is automatically cancelled when it goes out of scope.
#### Non-blocking Iteration (Sync)
Continuing from the sync example above, `try_iter_data()` polls without blocking:
```rust
use std::time::Duration;
// Example of non-blocking iteration in sync mode
loop {
match subscription.try_iter_data().next() {
Some(Ok(bar)) => println!("bar: {bar:?}"),
Some(Err(e)) => { eprintln!("error: {e}"); break; }
None => {
// No new data yet; perform other tasks or sleep
std::thread::sleep(Duration::from_millis(100));
}
}
}
```
Explore the [Subscription documentation](https://docs.rs/ibapi/latest/ibapi/subscriptions/struct.Subscription.html) for more details.
#### One-Shot Snapshot
For a single snapshot rather than a streaming subscription, `snapshot_once(timeout)` requests snapshot mode, collects the ticks until the snapshot completes (or the timeout elapses), and returns them as a `Vec<TickTypes>` β no hand-written collect loop. The same `collect_for` / `collect_until` terminals are available on any `Subscription<T>`. For requests that end, such as contract details, `collect_to_end_within(timeout)` collects the whole result or fails with `Error::Timeout`; `collect_for` returns whatever arrived in time instead.
```rust
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
use std::time::Duration;
fn main() {
let client = Client::connect("127.0.0.1:4002", 100).expect("connection to TWS failed!");
let contract = Contract::stock("AAPL").build();
let ticks = client
.market_data(&contract)
.snapshot_once(Duration::from_secs(5))
.expect("snapshot request failed!");
for tick in ticks {
println!("tick: {tick:?}");
}
}
```
Since subscriptions can be converted to iterators, it is easy to iterate over multiple contracts.
```rust
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
fn main() {
let client = Client::connect("127.0.0.1:4002", 100).expect("connection to TWS failed!");
let contract_aapl = Contract::stock("AAPL").build();
let contract_nvda = Contract::stock("NVDA").build();
let subscription_aapl = client
.realtime_bars(&contract_aapl)
.trading_hours(TradingHours::Extended)
.subscribe()
.expect("realtime bars request failed!");
let subscription_nvda = client
.realtime_bars(&contract_nvda)
.trading_hours(TradingHours::Extended)
.subscribe()
.expect("realtime bars request failed!");
for (bar_aapl, bar_nvda) in subscription_aapl.iter_data().zip(subscription_nvda.iter_data()) {
let (bar_aapl, bar_nvda) = match (bar_aapl, bar_nvda) {
(Ok(a), Ok(n)) => (a, n),
(Err(e), _) | (_, Err(e)) => {
eprintln!("error: {e}");
break;
}
};
println!("AAPL {}, NVDA {}", bar_aapl.close, bar_nvda.close);
}
}
```
> **Note:** When using `zip`, the iteration will stop if either subscription ends. For independent processing, consider handling each subscription separately.
### Placing Orders
For a comprehensive guide on all supported order types and their usage, see the [Order Types Guide](docs/order-types.md).
#### Sync Example
```rust
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
pub fn main() {
let connection_url = "127.0.0.1:4002";
let client = Client::connect(connection_url, 100).expect("connection to TWS failed!");
let contract = Contract::stock("AAPL").build();
// Create and submit a market order to purchase 100 shares using the fluent API
let order_id = client.order(&contract)
.buy(100)
.market()
.submit()
.expect("order submission failed!");
println!("Order submitted with ID: {}", order_id);
// Example of a more complex order: limit order with time in force
let order_id = client.order(&contract)
.sell(50)
.limit(150.00)
.good_till_canceled()
.outside_rth()
.submit()
.expect("order submission failed!");
println!("Limit order submitted with ID: {}", order_id);
}
```
#### Async Example
```rust
use ibapi::prelude::*;
#[tokio::main]
async fn main() {
let connection_url = "127.0.0.1:4002";
let client = Client::connect(connection_url, 100).await.expect("connection to TWS failed!");
let contract = Contract::stock("AAPL").build();
// Create and submit a market order to purchase 100 shares using the fluent API
let order_id = client.order(&contract)
.buy(100)
.market()
.submit()
.await
.expect("order submission failed!");
println!("Order submitted with ID: {}", order_id);
// Example of a bracket order: entry with take profit and stop loss
let bracket_ids = client.order(&contract)
.buy(100)
.bracket()
.entry_limit(150.00)
.take_profit(160.00)
.stop_loss(145.00)
.submit_all()
.await
.expect("bracket order submission failed!");
println!("Bracket order IDs - Parent: {}, TP: {}, SL: {}",
bracket_ids.parent, bracket_ids.take_profit, bracket_ids.stop_loss);
}
```
#### Monitoring Order Updates
For real-time monitoring of order status, executions, and commissions, set up an order update stream before submitting orders:
##### Sync Example
```rust
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
use std::thread;
use std::sync::Arc;
fn main() {
let connection_url = "127.0.0.1:4002";
let client = Arc::new(Client::connect(connection_url, 100).expect("connection to TWS failed!"));
// Start background thread to monitor order updates
let monitor_client = client.clone();
let _monitor_handle = thread::spawn(move || {
let stream = monitor_client.order_update_stream().expect("failed to create stream");
// `iter_data()` strips notice items; the inner item is `Result<OrderUpdate, Error>`.
// Use `stream.iter()` with `Ok(SubscriptionItem::Data(_))` if you also want to
// observe non-fatal notices.
for update in stream.iter_data() {
match update {
Ok(OrderUpdate::OrderStatus(status)) => {
println!("Order {} Status: {}", status.order_id, status.status);
println!(" Filled: {}, Remaining: {}", status.filled, status.remaining);
if status.status.is_terminal() {
break;
}
}
Ok(OrderUpdate::OpenOrder(data)) => {
println!("Open Order {}: {} {}",
data.order_id, data.order.action, data.contract.symbol);
}
Ok(OrderUpdate::ExecutionData(exec)) => {
println!("Execution: {} shares @ {}",
exec.execution.shares, exec.execution.price);
}
Ok(OrderUpdate::OrderBound(binding)) => println!("Order binding: {binding:?}"),
Ok(OrderUpdate::CommissionReport(report)) => {
println!("Commission: ${}", report.commission);
}
Err(e) => {
eprintln!("order update stream error: {e:?}");
break;
}
}
}
});
// Give monitor time to start
thread::sleep(std::time::Duration::from_millis(100));
// Now submit orders - updates will be received by the monitoring thread
let contract = Contract::stock("AAPL").build();
let order_id = client.order(&contract)
.buy(100)
.market()
.submit()
.expect("order submission failed!");
println!("Order {} submitted", order_id);
// Keep main thread alive to receive updates
thread::sleep(std::time::Duration::from_secs(10));
}
```
##### Async Example
```rust
use ibapi::prelude::*;
use futures::StreamExt;
#[tokio::main]
async fn main() {
let connection_url = "127.0.0.1:4002";
let client = Client::connect(connection_url, 100).await.expect("connection to TWS failed!");
// Create order update stream before submitting orders
let order_stream = client.order_update_stream().await.expect("failed to create stream");
// Spawn task to monitor updates. `filter_data()` strips non-fatal notices so
// the loop body only sees data items; switch to `order_stream.next()` and a
// `Ok(SubscriptionItem::Data(_))` arm if you want to observe notices too.
let monitor_handle = tokio::spawn(async move {
let mut order_stream = order_stream.filter_data();
while let Some(update) = order_stream.next().await {
match update {
Ok(OrderUpdate::OrderStatus(status)) => {
println!("Order {} Status: {}", status.order_id, status.status);
println!(" Filled: {}, Remaining: {}", status.filled, status.remaining);
if status.status.is_terminal() {
break;
}
}
Ok(OrderUpdate::OpenOrder(data)) => {
println!("Open Order {}: {} {}",
data.order_id, data.order.action, data.contract.symbol);
}
Ok(OrderUpdate::ExecutionData(exec)) => {
println!("Execution: {} shares @ {}",
exec.execution.shares, exec.execution.price);
}
Ok(OrderUpdate::OrderBound(binding)) => println!("Order binding: {binding:?}"),
Ok(OrderUpdate::CommissionReport(report)) => {
println!("Commission: ${}", report.commission);
}
Err(e) => {
eprintln!("Error: {:?}", e);
break;
}
}
}
});
// Give monitor time to start
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
// Now submit orders - updates will be received by the monitoring task
let contract = Contract::stock("AAPL").build();
let order_id = client.order(&contract)
.buy(100)
.market()
.submit()
.await
.expect("order submission failed!");
println!("Order {} submitted", order_id);
// Wait for updates
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
// Clean up
monitor_handle.abort();
}
```
The order update stream provides real-time notifications for:
- **OrderStatus**: Status changes typed as [`OrderStatusKind`](https://docs.rs/ibapi/latest/ibapi/orders/enum.OrderStatusKind.html) (`Submitted`, `Filled`, `Cancelled`, β¦) with `is_active()` / `is_terminal()` helpers
- **OpenOrder**: Order details when opened or modified
- **ExecutionData**: Fill notifications with price and quantity
- **CommissionReport**: Commission charges for executions
Per-order TWS warnings (e.g. quote-throttling 2100 codes scoped to an order) flow through `SubscriptionItem::Notice` on the per-order subscription returned by `place_order`, not through `OrderUpdate`. See [Handling notifications](#handling-notifications).
## Handling notifications
TWS emits two flavors of notification alongside subscription data:
- **Per-subscription notices** β warning codes 2100..=2199 and order-cancel code
202 carry a `request_id` that maps back to a specific subscription. They arrive
on that subscription as `SubscriptionItem::Notice(_)`; the stream stays open.
- **Scanner "no items retrieved"** β an empty scan (common before regular
hours) reports error code 165 with that text. A scanner subscription
delivers it as `SubscriptionItem::Notice(_)` and stays open; later batches,
including empty ones, still arrive. The notice's `category()` is still
`Error`, so don't end a scanner loop on `!notice.is_informational()` alone.
On other requests, and with any other text, 165 still ends the stream.
- **Globally routed notices** β connectivity codes 1100/1101/1102 and farm-status
codes (2104/2105/2106/2107/2108) have no `request_id`. They are not delivered
to any subscription; subscribe via [`Client::notice_stream()`](https://docs.rs/ibapi/latest/ibapi/struct.Client.html#method.notice_stream) instead.
### Per-subscription notices
`Subscription<T>::next()` returns `Option<Result<SubscriptionItem<T>, Error>>`,
so a single match handles data, notices, and terminal errors:
#### Sync example
```rust
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
fn main() {
let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
let contract = Contract::stock("AAPL").build();
let subscription = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.expect("realtime bars request failed");
for item in &subscription {
match item {
Ok(SubscriptionItem::Data(bar)) => println!("bar: {bar:?}"),
Ok(SubscriptionItem::Notice(note)) => eprintln!("notice: {note}"),
Err(e) => { eprintln!("error: {e}"); break; }
}
}
}
```
#### Async example
```rust
use ibapi::prelude::*;
#[tokio::main]
async fn main() {
let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
let contract = Contract::stock("AAPL").build();
let mut subscription = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.await
.expect("realtime bars request failed");
while let Some(item) = subscription.next().await {
match item {
Ok(SubscriptionItem::Data(bar)) => println!("bar: {bar:?}"),
Ok(SubscriptionItem::Notice(note)) => eprintln!("notice: {note}"),
Err(e) => { eprintln!("error: {e}"); break; }
}
}
}
```
### Filtering vs. observing
Pick the iterator/stream shape based on whether the call site cares about
notices:
| Want | Sync | Async |
|---------------------------------------|---------------------------------|--------------------------------|
| **Data only** (notices logged at warn) | `subscription.iter_data()` / `next_data()` | `subscription.filter_data()` (then `.next().await`) |
| **Data + notices** (full visibility) | `subscription.iter()` / `next()` | `subscription.next().await` (matches on `SubscriptionItem`) |
Most call sites (downstream business logic, indicators, paper-trading loops)
want `iter_data()` / `filter_data()` β notices are observability concerns and
already log at `warn!`. UI status indicators, custom logging, and audit pipelines
match on `SubscriptionItem::Data` vs `SubscriptionItem::Notice` directly.
For a complete data-only example, see the sync example under
[Requesting Realtime Market Data](#requesting-realtime-market-data) above β its
`iter_data()` loop is exactly this shape.
### Handshake-time notices
Farm-status codes (2104/2106/2158, etc.) typically arrive *before*
`Client::connect` returns, so a `notice_stream` registered afterwards will not
see that initial burst. To capture handshake-time notices β useful for
connection-state UIs and startup telemetry β use the builder's
`connect_with_notice_stream` terminal instead of `connect`:
```rust
use ibapi::client::blocking::Client;
use ibapi::ConnectivityStatus;
fn main() {
let (client, notices) = Client::builder()
.address("127.0.0.1:4002")
.client_id(100)
.connect_with_notice_stream()
.expect("connection failed");
for n in notices.iter() {
match n.connectivity_status() {
// Data-farm sub-state, refined from the 2100..=2199 warning band.
Some(ConnectivityStatus::Ok) => println!("farm online: {n}"),
Some(ConnectivityStatus::Broken) => println!("farm down: {n}"),
Some(ConnectivityStatus::Inactive | ConnectivityStatus::Connecting) => println!("farm idle: {n}"),
// Connectivity codes 1100/1101/1102 and everything else.
None if n.is_system_message() => println!("connectivity: {n}"),
None => println!("notice: {n}"),
}
}
drop(client);
}
```
The pre-bound stream survives auto-reconnects (the broadcaster lives on
`Connection`, not on the bus), so a single subscription covers both paths.
For runtime-only unrouted notices once the client is up, you can also call
[`Client::notice_stream`](https://docs.rs/ibapi/latest/ibapi/struct.Client.html#method.notice_stream)
post-connect β both subscribe to the same broadcaster.
## Multi-Threading
The [Client](https://docs.rs/ibapi/latest/ibapi/struct.Client.html) can be shared between threads to support concurrent operations. The following example demonstrates valid multi-threaded usage of [Client](https://docs.rs/ibapi/latest/ibapi/struct.Client.html).
```rust
use std::sync::Arc;
use std::thread;
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
fn main() {
let connection_url = "127.0.0.1:4002";
let client = Arc::new(Client::connect(connection_url, 100).expect("connection to TWS failed!"));
let symbols = vec!["AAPL", "NVDA"];
let mut handles = vec![];
for symbol in symbols {
let client = Arc::clone(&client);
let handle = thread::spawn(move || {
let contract = Contract::stock(symbol).build();
let subscription = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.expect("realtime bars request failed!");
for bar in subscription.iter_data() {
match bar {
Ok(bar) => println!("bar: {bar:?}"),
Err(e) => {
eprintln!("error: {e:?}");
break;
}
}
}
});
handles.push(handle);
}
handles.into_iter().for_each(|handle| handle.join().unwrap());
}
```
Some TWS API calls do not have a unique request ID and are mapped back to the initiating request by message type instead. Since the message type is not unique, every live subscription mapped to a response type receives every response of that type: concurrent requests of the same message type (if not synchronized by the application) also receive the responses to each other, and so do requests that share response types, such as `open_orders` and `all_open_orders`. Responses cannot be attributed to the request that caused them, so an application that issues such requests concurrently β with different arguments, or of kinds whose response types overlap β has to serialize them itself.
To avoid this issue, you can use a model of one client per thread. This ensures that each client instance handles only its own messages, reducing potential conflicts:
```rust
use std::thread;
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
fn main() {
let symbols = vec![("AAPL", 100), ("NVDA", 101)];
let mut handles = vec![];
for (symbol, client_id) in symbols {
let handle = thread::spawn(move || {
let connection_url = "127.0.0.1:4002";
let client = Client::connect(connection_url, client_id).expect("connection to TWS failed!");
let contract = Contract::stock(symbol).build();
let subscription = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.expect("realtime bars request failed!");
for bar in subscription.iter_data() {
match bar {
Ok(bar) => println!("bar: {bar:?}"),
Err(e) => {
eprintln!("error: {e:?}");
break;
}
}
}
});
handles.push(handle);
}
handles.into_iter().for_each(|handle| handle.join().unwrap());
}
```
In this model, each client instance handles only the requests it initiates, improving the reliability of concurrent operations.
## Fault Tolerance
The API will automatically attempt to reconnect to the TWS server if a disconnection is detected. The API will attempt to reconnect up to 20 times (by default β see `ClientBuilder::max_reconnect_attempts` and `ClientBuilder::reconnect_forever`) using a Fibonacci backoff strategy. In some cases, it will retry the request in progress. The market data type set with `switch_market_data_type` is re-sent after a reconnect, since TWS does not keep it across reconnects. When receiving responses via a `Subscription`, the application may need to handle retries manually. Since v3.0, terminal errors surface as `Some(Err(_))` from `Subscription::next()` (no separate `error()` accessor); inspect the error and decide whether to resubscribe:
```rust
use ibapi::client::blocking::Client;
use ibapi::prelude::*;
fn main() {
let client = Client::connect("127.0.0.1:4002", 100).expect("connection to TWS failed!");
let contract = Contract::stock("AAPL").build();
'outer: loop {
let subscription = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.expect("realtime bars request failed!");
for item in &subscription {
match item {
Ok(SubscriptionItem::Data(bar)) => println!("bar: {bar:?}"),
Ok(SubscriptionItem::Notice(note)) => eprintln!("notice: {note}"),
Err(e) if e.is_connection_lost() => {
eprintln!("Connection lost. Retrying stream...");
continue 'outer;
}
// Everything else β including terminal ConnectionFailed (reconnect
// exhausted) β is not recoverable here, so stop.
Err(e) => {
eprintln!("error: {e}");
break 'outer;
}
}
}
break;
}
}
```
## Contributions
We welcome contributions of all kinds. Feel free to propose new ideas, share bug fixes, or enhance the documentation. If you'd like to contribute, please start by reviewing our [contributor documentation](https://github.com/wboayue/rust-ibapi/blob/main/CONTRIBUTING.md).
If you encounter a bug or require a missing feature, please review the [issues list](https://github.com/wboayue/rust-ibapi/issues) before submitting a new one.
For questions or discussions about contributions, feel free to open an issue or reach out via our [GitHub discussions page](https://github.com/wboayue/rust-ibapi/discussions).