# API Patterns
## Prelude
`use ibapi::prelude::*;` is the canonical front door. It re-exports the
crate-root `Client` (async when the `async` feature is on, blocking otherwise),
`Contract` + the typed contract wrappers, the order-side `Action` enum, the
`Subscription` / `SubscriptionItem` / `Notice` / `NoticeCategory` types, and
the historical `BarSize` and the historical/realtime `WhatToShow` enums
(disambiguated as `HistoricalBarSize` / `HistoricalWhatToShow` /
`RealtimeWhatToShow` — see the
[`prelude` rustdoc](https://docs.rs/ibapi/latest/ibapi/prelude/) for the full
list and the naming convention for the BarSize/WhatToShow disambiguation).
Use the prelude in examples, integration tests, and idiomatic user code:
```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();
// ...
}
```
When both `sync` and `async` features are enabled, the blocking client lives at
`ibapi::client::blocking::Client`; the prelude's `Client` is still the async
one. See [`src/client/mod.rs`](https://docs.rs/ibapi/latest/ibapi/client/) for
the canonical-path convention.
## Builder Patterns
The library provides unified builder patterns to simplify common operations in both sync and async modes.
### Contract Builder
Contracts are constructed via type-state builders that enforce required fields
at compile time and provide strongly-typed wrappers for exchanges, currencies,
and option rights:
```rust
use ibapi::contracts::Contract;
let stock = Contract::stock("AAPL").build();
let option = Contract::call("AAPL").strike(150.0).expires_on(2024, 12, 20).build();
```
The full per-asset-type reference (stocks, options, futures, forex, crypto,
bonds, spreads, etc.) lives in the [Contract Builder Guide](contract-builder.md);
that's the canonical home for builder usage. This section is just the pointer.
### Request-ID Requests
For a streaming request routed by request id, build on `client.request()`: the
`RequestBuilder` holds the minted id (`builder.request_id()` for the encoder).
Gate a feature newer than the connection floor with `check_version` (see
[Protocol Version Checking](#protocol-version-checking)) first:
```rust
// Sync mode (in domain/sync.rs as impl Client block)
impl Client {
pub fn pnl(&self, account: &AccountId, model_code: Option<&ModelCode>) -> Result<Subscription<PnL>, Error> {
let builder = self.request();
let request = encoders::encode_request_pnl(builder.request_id(), account, model_code)?;
builder.send(request)
}
}
// Async mode (in domain/async.rs as impl Client block)
impl Client {
pub async fn pnl(&self, account: &AccountId, model_code: Option<&ModelCode>) -> Result<Subscription<PnL>, Error> {
let builder = self.request();
let request = encoders::encode_request_pnl(builder.request_id(), account, model_code)?;
builder.send(request).await
}
}
```
Besides `send`, the builder sends with `send_capped` or `send_with_context`.
`send_capped` takes a builder's `buffer_limit` and reads the cap's end markers
from the decoder's `StreamDecoder::END_MESSAGES` (a test checks the list
matches the `decode` arms that end the stream). With none, only an error ends
a capped stream. A builder that exposes `request_id()` before sending mints
the id in its constructor and sends with `client.request_with_id(id)` instead.
### Shared-Channel Requests
For requests routed by message type (no request id), send through
`client.subscription()`:
```rust
// Sync mode (in domain/sync.rs)
impl Client {
pub fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
let request = encoders::encode_request_positions()?;
self.subscription().send_shared(OutgoingMessages::RequestPositions, request)
}
}
// Async mode (in domain/async.rs)
impl Client {
pub async fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
let request = encoders::encode_request_positions()?;
self.subscription().send_shared(OutgoingMessages::RequestPositions, request).await
}
}
```
### Order Requests
An order id must be checked against the request-id range before it reaches the
encoder or the bus (see [id partition](rules/wire/id-partition.md)). Check every
id the order carries, then send through `send_order`, which registers the
order's routing entry:
```rust
impl Client {
pub fn place_order(&self, order_id: impl Into<OrderId>, contract: &Contract, order: &Order) -> Result<Subscription<PlaceOrder>, Error> {
let checked_id = verify::verify_order_ids(order_id.into(), order)?;
let request = encoders::encode_place_order(checked_id.value(), contract, order)?;
let subscription = self.send_order(checked_id, request)?; // .await for async
Ok(Subscription::new(Arc::clone(&self.message_bus), subscription, self.decoder_context()))
}
}
```
A bare id with nothing attached (`cancel_order`) uses `order_id.into().checked()?`.
### Subscription Context
A decoder that needs request-time settings (smart depth, the request type) gets
them through a `DecoderContext` passed with the request:
```rust
pub(crate) fn market_depth(client: &Client, contract: &Contract, number_of_rows: i32, is_smart_depth: bool)
-> Result<Subscription<MarketDepths>, Error>
{
let builder = client.request();
let request = encoders::encode_request_market_depth(builder.request_id(), contract, number_of_rows, is_smart_depth)?;
builder.send_with_context(request, client.decoder_context().with_smart_depth(is_smart_depth))
// .await for async version
}
```
### Conditional Order Builder Pattern
The library provides a fluent API for building conditional orders with type-safe condition builders and ergonomic helper functions. Conditions chain off the canonical `client.order(&contract).…` builder.
#### Helper Functions
Helper functions provide a concise way to create condition builders:
```rust
use ibapi::orders::builder::{price, time, margin, volume, execution, percent_change};
// Helper functions return partially-built condition builders.
let price_cond = price(265598, "SMART").greater_than(150.0);
let time_cond = time().greater_than("20251230 14:30:00 US/Eastern");
let margin_cond = margin().less_than(30);
let volume_cond = volume(76792991, "SMART").greater_than(50_000_000);
let pct_change_cond = percent_change(756733, "SMART").greater_than(2.0);
// Execution condition returns OrderCondition directly (no threshold).
let exec_cond = execution("TSLA", "STK", "SMART");
// Attach a condition via the fluent order builder; submit() allocates the id.
let order_id = client.order(&contract)
.buy(100)
.market()
.condition(price_cond)
.submit()?;
```
#### Fluent Condition Chaining
The `OrderBuilder` provides methods for chaining conditions with AND/OR logic:
```rust
use ibapi::orders::builder::{price, time, volume, margin};
// Multiple conditions with AND logic (all must be true).
let order_id = client.order(&contract)
.buy(100)
.limit(151.0)
.condition(price(265598, "SMART").greater_than(150.0))
.and_condition(volume(265598, "SMART").greater_than(80_000_000))
.and_condition(time().greater_than("20251230 10:00:00 US/Eastern"))
.submit()?;
// Multiple conditions with OR logic (any can trigger).
let order_id = client.order(&contract)
.sell(100)
.market()
.condition(margin().less_than(25))
.or_condition(price(265598, "SMART").less_than(140.0))
.or_condition(time().greater_than("20251230 15:55:00 US/Eastern"))
.submit()?;
// Mixed AND/OR logic.
let order_id = client.order(&contract)
.buy(50)
.limit(452.0)
.condition(price(265598, "SMART").greater_than(150.0))
.and_condition(volume(265598, "SMART").greater_than(50_000_000)) // Price AND Volume
.or_condition(time().greater_than("20251230 14:00:00 US/Eastern")) // OR Time
.submit()?;
```
#### Type-State Pattern for Conditions
Each condition builder uses the type-state pattern to ensure valid configuration:
```rust
use ibapi::orders::conditions::{PriceCondition, TriggerMethod};
// Builder starts with required fields only
let builder = PriceCondition::builder(265598, "SMART");
// Threshold and direction set together (type-safe)
let condition = builder.greater_than(150.0); // Sets price + direction
// or
let condition = builder.less_than(140.0); // Sets price + direction
// Optional configuration
let condition = condition
.trigger_method(TriggerMethod::Last) // Use last price
.conjunction(true); // AND with next condition
// Convert to OrderCondition
let condition = condition.build();
```
All condition builders follow this pattern:
- **Required parameters** in the constructor (contract ID, exchange, etc.)
- **Threshold and direction** set together via `greater_than()` or `less_than()`
- **Optional parameters** via chainable methods
- **Type conversion** via `.build()` or automatic `Into<OrderCondition>`
#### Old vs New API Comparison
**Before (v0.x):**
```rust
// Threshold in constructor, separate trigger direction method
let price_cond = PriceCondition::builder(265598, "SMART", 150.0)
.trigger_above()
.build();
let time_cond = TimeCondition::builder("20251230 14:30:00 US/Eastern")
.trigger_after()
.build();
// Manual condition assignment
let mut order = order_builder::market_order(Action::Buy, 100.0);
order.conditions = vec![
OrderCondition::Price(price_cond),
OrderCondition::Time(time_cond),
];
```
**After (v3.0+):**
```rust
// Threshold and direction combined; conditions chain off the fluent order builder.
let order_id = client.order(&contract)
.buy(100)
.market()
.condition(price(265598, "SMART").greater_than(150.0))
.and_condition(time().greater_than("20251230 14:30:00 US/Eastern"))
.submit()?;
// Or build the condition explicitly, then attach.
let price_cond = PriceCondition::builder(265598, "SMART")
.greater_than(150.0)
.build();
let time_cond = TimeCondition::builder()
.greater_than("20251230 14:30:00 US/Eastern")
.build();
```
Key improvements:
- **More ergonomic**: Helper functions reduce boilerplate
- **Type-safe**: Threshold and direction set atomically
- **Fluent**: Method chaining for AND/OR logic
- **Explicit**: `greater_than()` vs `less_than()` is clearer than `trigger_above()` vs `trigger_below()`
- **Consistent**: All condition types follow the same pattern
#### Sync and Async Usage
Conditional orders work identically in both sync and async modes:
**Sync Mode:**
```rust
use ibapi::client::blocking::Client;
use ibapi::contracts::Contract;
use ibapi::orders::builder::price;
let client = Client::connect("127.0.0.1:7497", 100)?;
let contract = Contract::stock("AAPL").build();
let order_id = client.order(&contract)
.buy(100)
.market()
.condition(price(265598, "SMART").greater_than(150.0))
.submit()?;
```
**Async Mode:**
```rust
use ibapi::Client;
use ibapi::contracts::Contract;
use ibapi::orders::builder::price;
let client = Client::connect("127.0.0.1:4002", 100).await?;
let contract = Contract::stock("AAPL").build();
let order_id = client.order(&contract)
.buy(100)
.market()
.condition(price(265598, "SMART").greater_than(150.0))
.submit().await?;
```
The only difference is the `.await` calls on client methods. The order building logic is identical.
#### Advanced Pattern: Reusable Condition Components
Create reusable condition components for common trading scenarios:
```rust
use ibapi::orders::builder::{price, time, volume, margin};
use ibapi::orders::conditions::OrderCondition;
// Reusable condition builders
fn liquidity_check(contract_id: i32, min_volume: i32) -> impl Into<OrderCondition> {
volume(contract_id, "SMART").greater_than(min_volume)
}
fn trading_hours_only() -> impl Into<OrderCondition> {
time()
.greater_than("20251230 09:30:00 US/Eastern")
.conjunction(true)
}
fn risk_guard() -> impl Into<OrderCondition> {
margin().less_than(30)
}
// Compose conditions onto the fluent order builder.
let order_id = client.order(&contract)
.buy(100)
.market()
.condition(liquidity_check(265598, 50_000_000))
.and_condition(trading_hours_only())
.and_condition(risk_guard())
.submit()?;
```
For comprehensive conditional order documentation, see [Order Types - Conditional Orders](order-types.md#conditional-orders-with-conditions).
## Protocol Version Checking
The handshake refuses servers below version 213 (`PROTOBUF_REST_MESSAGES_3`), so
a feature at or below it needs no check; those `Features` constants are
deprecated. Check only a feature above the floor, on the line before the request:
```rust
use crate::protocol::{check_version, is_supported, Features};
// Gate a request (from `config/sync.rs`)
pub fn config(&self) -> Result<Config, Error> {
check_version(self.server_version, Features::CONFIG)?;
// ... implementation
}
// Conditional field encoding: a field gated above the floor stays `None`
// when the server is too old
if is_supported(server_version, Features::MY_FEATURE) {
proto.my_field = Some(request.my_field.clone());
}
```
Order fields gated above the floor are checked in `verify_order`
(`orders/common/verify.rs`), mirroring C# `ValidateOrderParameters`.
## Subscription Patterns
### Sync Mode (Iterator)
```rust
let positions = client.positions()?;
// `iter_data()` strips notice items; `update?` yields the `PositionUpdate`
// directly. Pattern-match on `iter()` instead if you also want to observe
// non-fatal notices via `SubscriptionItem::Notice`.
for update in positions.iter_data() {
match update? {
PositionUpdate::Position(p) => println!("Position: {p:?}"),
PositionUpdate::PositionEnd => break,
}
}
```
### Async Mode (Stream)
```rust
use futures::StreamExt;
let positions = client.positions().await?;
let mut positions = positions.filter_data(); // strips notice items
// Stream until completion
while let Some(update) = positions.next().await {
match update? {
PositionUpdate::Position(p) => println!("Position: {p:?}"),
PositionUpdate::PositionEnd => break,
}
}
```
## Trading Hours Support
The `TradingHours` enum controls whether data includes extended hours (pre-market and after-hours). However, not all market data APIs support this parameter at the TWS protocol level.
| `realtime_bars()` | ✓ Yes | Server-side filtering via `use_rth` |
| `historical_data()` | ✓ Yes | Server-side filtering via `use_rth` |
| `market_data()` | ✗ No | TWS protocol doesn't support filtering |
### Realtime Bars (Extended Hours Supported)
```rust
use ibapi::market_data::TradingHours;
// Regular trading hours only (default)
let bars = client.realtime_bars(&contract).subscribe().await?;
// Include extended hours
let bars = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.await?;
```
### Market Data Subscriptions (No Extended Hours Filtering)
The TWS API's `reqMktData` does not support a `useRth` parameter. Streaming tick data automatically includes all available data, including pre-market and after-hours quotes when the exchange reports them.
```rust
// Market data subscriptions receive ALL available data
// including extended hours - no filtering option exists
let ticks = client.market_data(&contract)
.subscribe()
.await?;
```
To filter for regular trading hours only, you must filter client-side based on timestamp and the trading session times for your specific exchange.
### Generic Tick Request IDs
`market_data()` accepts a list of *generic tick request IDs* — the
comma-separated values passed via the `genericTickList` parameter on
`reqMktData`. Each ID opts the stream into one or more *received* tick types
that arrive on the `tickPrice` / `tickSize` / `tickString` / `tickGeneric`
callbacks. Prefer the named constants in
[`market_data::realtime::generic_tick`] over raw numeric strings:
```rust
use ibapi::market_data::realtime::generic_tick;
let ticks = client.market_data(&contract)
.generic_ticks(&[generic_tick::RT_VOLUME, generic_tick::SHORTABLE])
.subscribe()?;
```
Generic tick request IDs (e.g. `100`, `233`, `236`) are **not** the same as
received tick IDs (e.g. `BID_SIZE` = 0, `RT_VOLUME` = 48). Received tick IDs
live on [`contracts::tick_types::TickType`]; each generic-tick constant's
doc-comment names which received-tick types it subscribes to.
See: <https://www.interactivebrokers.com/docs/tws-api/doc/market-data-live/available-tick-types/introduction> (the
*Generic tick required* column on the IB docs page).
[`market_data::realtime::generic_tick`]: https://docs.rs/ibapi/latest/ibapi/market_data/realtime/generic_tick/index.html
[`contracts::tick_types::TickType`]: https://docs.rs/ibapi/latest/ibapi/contracts/tick_types/enum.TickType.html
## Error Handling Patterns
### Connection Errors
```rust
// The client reconnects on its own. A request sent while the connection is
// down is refused with Error::ConnectionReset, which is_connection_lost()
// matches. If reconnection is exhausted, is_connected() stays false and every
// send keeps getting ConnectionReset, so bound the wait.
match client.market_data(contract).subscribe() {
Ok(subscription) => process_data(subscription),
Err(e) if e.is_connection_lost() => {
let deadline = Instant::now() + Duration::from_secs(60);
while !client.is_connected() {
if Instant::now() >= deadline { return Err(e); }
thread::sleep(Duration::from_secs(1));
}
// Retry
},
Err(e) => return Err(e),
}
```
### Subscription Errors
```rust
for result in subscription {
match result {
Ok(data) => process(data),
Err(e) if e.is_connection_lost() => {
// Resubscribe after reconnection
break;
},
Err(e) => log::error!("Error: {}", e),
}
}
```
### Order Update Stream Across Reconnects
`order_update_stream()` survives the client's automatic reconnects — the same
subscription keeps delivering once the connection returns. Updates TWS emitted
during the outage are **not** replayed, and no marker appears in the stream
itself, so a quiet stream is indistinguishable from missed activity. Detect
the gap on the notice stream and reconcile:
```rust
let notices = client.notice_stream()?;
for notice in notices.iter() {
// 1100: connectivity lost; 1101/1102: restored (data lost / maintained);
// 1300: socket port reset.
if matches!(notice.code, 1101 | 1102) {
// The order update stream did not replay the outage window:
// rebuild open-order state and diff against your book.
let snapshot = client.open_orders()?;
reconcile(snapshot)?;
}
}
```
## Common Patterns
### Correlating Commissions with Executions
`CommissionReport` joins to its `ExecutionData` by `execution_id` — the same value
carried on `Execution::execution_id`. TWS sends the execution first and the commission
shortly after, but order-status and open-order frames can land between them, so index
commissions by `execution_id` rather than assuming the two are adjacent. Both arrive on
the same streams (`executions`, `place_order`, `order_update_stream`).
```rust
use ibapi::orders::{ExecutionData, OrderUpdate};
use std::collections::HashMap;
let mut executions: HashMap<String, ExecutionData> = HashMap::new();
let updates = client.order_update_stream()?;
for update in updates.iter_data() {
match update? {
OrderUpdate::ExecutionData(exec) => {
// The execution arrives first: index it by execution_id.
executions.insert(exec.execution.execution_id.clone(), exec);
}
OrderUpdate::CommissionReport(report) => {
// The commission follows: join it to the execution it belongs to.
if let Some(exec) = executions.remove(&report.execution_id) {
println!("{} x {} commission {}", exec.execution.shares, exec.contract.symbol, report.commission);
}
}
_ => {}
}
}
```
### Concurrent Subscriptions
```rust
use std::sync::Arc;
use std::thread;
let client = Arc::new(client);
let handles: Vec<_> = contracts
.into_iter()
.map(|contract| {
let client = Arc::clone(&client);
thread::spawn(move || {
let data = client.market_data(&contract).subscribe()?;
process_market_data(data)
})
})
.collect();
```
### Rate Limiting
Install a `RateLimiter` on the client instead of pacing requests by hand. Pass
clones of one limiter to every client on the same gateway; see the
`RateLimiter` docs for what is counted, how cancels are treated and the
default.
```rust
use ibapi::{Client, RateLimiter};
let client = Client::builder()
.address("127.0.0.1:4002")
.client_id(100)
.rate_limiter(RateLimiter::default())
.connect()
.await?;
```
Historical-data pacing (error 162) is a separate limit the library does not enforce.
### Reconnection Handling
```rust
let deadline = Instant::now() + Duration::from_secs(60);
loop {
if !client.is_connected() {
// Exhausted reconnection leaves this false, so bound the wait; the
// error returned is the one every send gets in that state
if Instant::now() >= deadline { return Err(Error::ConnectionReset); }
log::info!("Waiting for reconnection...");
thread::sleep(Duration::from_secs(5));
continue;
}
match perform_operation(&client) {
Ok(result) => return Ok(result),
// Lost mid-operation; the client is reconnecting
Err(e) if e.is_connection_lost() => continue,
Err(e) => return Err(e),
}
}
```
## Trait Composition Patterns
### Domain Traits for Shared Behavior
```rust
// Use domain traits when:
// - 2+ types need the same operation (encode, decode, validate)
// - You want to write generic functions over those types
pub trait Encodable {
fn encode(&self) -> Result<Vec<u8>, Error>;
}
pub trait Decodable: Sized {
fn decode(bytes: &[u8]) -> Result<Self, Error>;
}
// Implement for types that need this behavior
impl Encodable for Order { /* ... */ }
impl Encodable for Contract { /* ... */ }
```
### Extension via Composition
```rust
// Use composition when:
// - A type needs capabilities from multiple sources
// - Behavior should be added without modifying the original type
pub struct Subscription<T> {
receiver: Receiver<T>,
cancel_fn: Box<dyn Fn() + Send>,
}
// Add behavior via trait impls
impl<T> Iterator for Subscription<T> { /* ... */ }
impl<T> Drop for Subscription<T> { /* ... */ }
```
### Newtype Wrappers for Domain Ids
```rust
// Bad: raw i32 allows type confusion
fn lookup(contract_id: i32, order_id: i32) -> Contract { /* ... */ } // easy to swap args
// Good: newtype wrappers prevent mistakes
// Use newtype wrappers when type confusion is possible (ContractId vs OrderId).
// The crate's id newtypes (ContractId, OrderId, AccountId, ...) are transparent:
// a public field, a From impl and Display. Construction does not validate the
// value (an OrderId in the request-id range is rejected when an order uses it).
pub struct ContractId(pub i32);
impl From<i32> for ContractId {
fn from(id: i32) -> Self { Self(id) }
}
// Type system prevents swapped arguments
fn lookup(id: ContractId) -> Contract { /* ... */ } // Can't pass an OrderId
```