# 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/realtime `BarSize` / `WhatToShow` enums (disambiguated as
`HistoricalBarSize` / `RealtimeBarSize` etc. — 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::*;
fn main() {
let client = Client::connect("127.0.0.1:4002", 100).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 Builder
For client methods with request IDs:
```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()
.check_version(server_versions::PNL, "PnL not supported")?;
let request = 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()
.check_version(server_versions::PNL, "PnL not supported")
.await?;
let request = encode_request_pnl(builder.request_id(), account, model_code)?;
builder.send(request).await
}
}
```
### Shared Request Builder
For requests using shared channels (no request ID):
```rust
// Sync mode (in domain/sync.rs)
impl Client {
pub fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
let request = encode_request_positions()?;
self.shared_request(OutgoingMessages::RequestPositions)
.send(request)
}
}
// Async mode (in domain/async.rs)
impl Client {
pub async fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
let request = encode_request_positions()?;
self.shared_request(OutgoingMessages::RequestPositions)
.send(request)
.await
}
}
```
### Order Request Builder
For order operations:
```rust
impl Client {
pub fn place_order(&self, contract: &Contract, order: &Order) -> Result<(), Error> {
let builder = self.order_request();
let request = encode_order(builder.order_id(), contract, order)?;
builder.send(request)?; // .await for async
Ok(())
}
}
```
### Subscription Builder
Create subscriptions with additional context:
```rust
impl Client {
pub fn market_depth(&self, contract: &Contract, num_rows: i32)
-> Result<Subscription<MarketDepth>, Error>
{
let request_id = self.next_request_id();
let request = encode_market_depth(request_id, contract, num_rows)?;
self.subscription::<MarketDepth>()
.with_smart_depth(true)
.send_with_request_id(request_id, request)
// .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
Use the protocol module for version-specific features:
```rust
use crate::protocol::{check_version, Features, is_supported};
// Check if feature is supported
pub fn tick_by_tick_trades(&self, contract: &Contract)
-> Result<Subscription<Trade>, Error>
{
check_version(self.server_version, Features::TICK_BY_TICK)?;
// ... implementation
}
// Conditional field encoding
pub fn encode_order(order: &Order, server_version: i32) -> RequestMessage {
let mut message = RequestMessage::new();
// Always included
message.push_field(&order.order_id);
// Conditionally included based on server version
if is_supported(server_version, Features::DECISION_MAKER) {
message.push_field(&order.decision_maker);
}
message
}
```
## 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://interactivebrokers.github.io/tws-api/tick_types.html> (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
match client.market_data(contract).subscribe() {
Ok(subscription) => process_data(subscription),
Err(Error::NotConnected) => {
// Wait for reconnection
while !client.is_connected() {
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(Error::ConnectionReset) => {
// Resubscribe after reconnection
break;
},
Err(e) => log::error!("Error: {}", e),
}
}
```
## Common Patterns
### 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
```rust
use std::time::{Duration, Instant};
struct RateLimiter {
last_request: Instant,
min_interval: Duration,
}
impl RateLimiter {
fn wait_if_needed(&mut self) {
let elapsed = self.last_request.elapsed();
if elapsed < self.min_interval {
thread::sleep(self.min_interval - elapsed);
}
self.last_request = Instant::now();
}
}
```
### Reconnection Handling
```rust
loop {
if !client.is_connected() {
log::info!("Waiting for reconnection...");
thread::sleep(Duration::from_secs(5));
continue;
}
match perform_operation(&client) {
Ok(result) => return Ok(result),
Err(Error::NotConnected) => 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, message: &mut RequestMessage) -> Result<(), Error>;
}
pub trait Decodable: Sized {
fn decode(fields: &mut FieldIter) -> 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 Constraints
```rust
// Bad: raw i32 allows invalid IDs and type confusion
fn lookup(contract_id: i32, order_id: i32) -> Contract { /* ... */ } // easy to swap args
// Good: newtype wrappers prevent mistakes
// Use newtype wrappers when:
// - A primitive has domain constraints (non-zero, positive, etc.)
// - Type confusion is possible (ContractId vs OrderId)
pub struct ContractId(i32);
impl ContractId {
pub fn new(id: i32) -> Result<Self, Error> {
if id <= 0 { return Err(Error::InvalidContractId); }
Ok(Self(id))
}
}
// Type system prevents invalid states
fn lookup(id: ContractId) -> Contract { /* ... */ } // Can't pass raw i32
```