ibapi 5.3.0

An unofficial Rust client for the Interactive Brokers TWS API: market data, orders, and account data, with async (Tokio) and blocking clients for TWS and IB Gateway.
Documentation
# Architecture Overview

The rust-ibapi crate is a Rust implementation of the Interactive Brokers TWS API. The architecture supports both synchronous (thread-based) and asynchronous (tokio-based) operation modes; the async client is enabled by default, and the blocking client can be added alongside it.

## System Architecture

```mermaid
graph TB
    User[User Application]
    Client[Client API]
    MessageBus[MessageBus]
    Connection[Connection Manager]
    TWS[TWS/IB Gateway]
    
    User -->|API Calls| Client
    Client -->|Requests| MessageBus
    MessageBus -->|Messages| Connection
    Connection <-->|TCP Socket| TWS
    TWS -->|Responses| Connection
    Connection -->|Messages| MessageBus
    MessageBus -->|Responses| Client
    Client -->|Results| User
    
    style User fill:#e1f5fe
    style Client fill:#b3e5fc
    style MessageBus fill:#81d4fa
    style Connection fill:#4fc3f7
    style TWS fill:#29b6f6
```

## Core Components

### 1. Client
Main interface for user interactions:
- **Sync mode**: Uses threads and crossbeam channels
- **Async mode**: Uses tokio tasks and mpsc channels
- Encodes user requests
- Sends requests to the MessageBus
- Receives responses via channels
- Decodes responses for the user

### 2. MessageBus
Handles connection and message routing:
- **Sync mode**: Runs on a dedicated thread
- **Async mode**: Runs as a tokio task
- Establishes and maintains connection to TWS/Gateway
- Sends messages from client to TWS
- Listens for messages from TWS
- Routes incoming messages to appropriate client channels
- Implements automatic reconnection with Fibonacci backoff

### 3. Connection
Low-level connection management:
- Handles TWS protocol handshake
- Manages socket I/O operations
- Implements reconnection logic
- Maintains connection metadata

## Request/Response Flow

```mermaid
sequenceDiagram
    participant U as User App
    participant C as Client
    participant MB as MessageBus
    participant CM as Connection
    participant TWS as TWS/Gateway
    
    U->>C: API Call (e.g., req_contract_details)
    C->>C: Generate Request ID
    C->>C: Encode Request
    C->>MB: Send Request Message
    MB->>MB: Create Response Channel
    MB->>CM: Forward Request
    CM->>TWS: Send via TCP Socket
    TWS-->>CM: Response Message(s)
    CM-->>MB: Parse Response
    MB-->>MB: Route by Request ID
    MB-->>C: Send to Response Channel
    C-->>C: Decode Response
    C-->>U: Return Result
```

### Message Routing Patterns

1. **Requests with IDs**: MessageBus creates dedicated channels for responses
2. **Requests without IDs**: MessageBus uses shared channels for responses
3. **Order-based requests**: Special handling for order tracking

## Module Structure

```mermaid
graph TD
    ModRoot[module/mod.rs<br/>Public Types & Exports]
    Common[module/common/<br/>Shared Logic]
    SyncImpl[module/sync.rs<br/>Sync Implementation]
    AsyncImpl[module/async.rs<br/>Async Implementation]
    
    ModRoot -->|Types| Common
    ModRoot -->|#cfg feature=sync| SyncImpl
    ModRoot -->|#cfg feature=async| AsyncImpl
    
    Common --> Encoders[encoders.rs<br/>Message Encoding]
    Common --> Decoders[decoders.rs<br/>Protobuf Decoding]
    
    SyncImpl -->|uses| Common
    AsyncImpl -->|uses| Common
    
    TestData[src/testdata/builders/module.rs<br/>Response Fixtures] -.->|feeds tests for| Decoders
    
    style ModRoot fill:#fff9c4
    style Common fill:#f0f4c3
    style SyncImpl fill:#dcedc8
    style AsyncImpl fill:#c5e1a5
    style TestData fill:#e1e1e1
```

Test fixtures live in the crate-wide `src/testdata/builders/<domain>.rs`, **not** under `module/common/` — one builder per domain, implementing `ResponseProtoEncoder`.

Each API module follows a consistent structure:

```
src/<module>/
├── mod.rs         # Public types and module exports
├── common/        # Shared implementation details
│   ├── mod.rs     # Export encoders/decoders
│   ├── encoders.rs # Message encoding functions
│   └── decoders.rs # Message decoding functions
├── sync.rs        # Synchronous implementation
└── async.rs       # Asynchronous implementation
```

The `impl Client` blocks for a domain live in that domain's `sync.rs` / `async.rs`, never in
`client/` — see [domain module layout](rules/style/domain-module-layout.md).

### Example Module Implementation

```rust
// src/accounts/mod.rs - Public types (always available)
#[derive(Debug)]
pub struct AccountSummary {
    pub account: String,
    pub tag: String,
    pub value: String,
    pub currency: String,
}

// Feature-specific implementations: each file holds an `impl Client` block
// with the domain's methods, so nothing needs re-exporting.
#[cfg(feature = "sync")]
mod sync;

#[cfg(feature = "async")]
mod r#async;
```

This structure ensures:
- Public types are always available regardless of feature flags
- Shared business logic in common/encoders and common/decoders
- Clear separation between public API (types) and implementation
- No code duplication for encoding/decoding
- Easy debugging and maintenance

## Key Modules

- **`accounts`**: Account management and portfolio information
- **`contracts`**: Contract definitions and search
- **`market_data`**: Real-time and historical market data
- **`orders`**: Order placement and management
- **`news`**: News bulletins and articles
- **`wsh`**: Wall Street Horizon event data
- **`transport`**: Connection and message handling (internal)
- **`messages`**: Message definitions and routing (internal)
- **`protocol`**: Protocol version constants and feature checking

## Multi-Threading Model

The `Client` can be shared between threads for concurrent operations:

1. **Thread-Safe Design**: Use `Arc<Client>` when sharing across threads
2. **Channel Isolation**: Each subscription gets its own channel. Subscriptions to a request without an id (`positions`, `open_orders`, ...) are routed by response type, and every live subscription of that type receives every response
3. **Subscription Model**: Subscriptions can be converted to iterators (sync) or streams (async)

## Connection Management

### Connection States
- **Connected**: Active connection to TWS/Gateway
- **Reconnecting**: Attempting to restore connection
- **Shutdown**: Connection closed, no reconnection attempts

### Reconnection Strategy
- Automatic reconnection on connection loss
- Fibonacci backoff (1, 2, 3, 5, 8... seconds)
- Maximum 20 reconnection attempts by default
- Configurable via `ClientBuilder::max_reconnect_attempts` / `ClientBuilder::reconnect_forever`
- Async subscriptions ride bounded broadcast channels; a lagging consumer gets an in-band `SUBSCRIPTION_LAG_CODE` notice for the evicted frames. Capacity depends on the channel class (#896): market data and other non-order streams use `ClientBuilder::channel_capacity` (default 1024), since freshness beats completeness; order channels have floors that `channel_capacity` can raise but not lower (1024 per order — `place_order`, `cancel_order`, `exercise_options` — and per `executions` request, 8192 for `order_update_stream` and the open/completed-order streams), so a lag there means a stalled consumer and is logged as an error. Sync channels are unbounded for every class; a stalled consumer triggers queue-depth `warn!` watermarks instead, the notice fan-out included. Both transports report falling behind; only async trades completeness for bounded memory. Request-scoped streams opened with `buffer_limit` are bounded, lossless and fail closed on both. The notice stream rides the same bounded fan-out at the fixed default capacity (`channel_capacity` does not reach it); a lagging notice consumer gets an in-band `NOTICE_STREAM_LAG_CODE` notice for the skipped notices
- On a socket drop, every live subscription and in-flight one-shot request fails with `Error::ConnectionReset` immediately, before the reconnect starts: the session that held them cannot answer, whether or not the reconnect succeeds
- While the session is down, sends are refused with `Error::ConnectionReset`, and `is_connected()` reports false until the replayed handshake completes (it is a snapshot: a call made right after the check can still be refused). One-shot requests (`server_time`, `managed_accounts`, `historical_data`, ...) wait out the reconnect and retry, giving up with `Error::Shutdown` if it fails
- The market data type set with `switch_market_data_type` is not kept by TWS across reconnects, so the reconnect re-sends the last one after the handshake, before the session is reported live
- Once the reconnected session is live, a `TRANSPORT_RECONNECT_CODE` notice is published to the notice stream: TWS never frames the reconnect itself and does not replay 1101/1102 on the new connection, so the notice stream is otherwise blind to the socket generation change. Resubscribing from its handler lands on the new session
- On shutdown (`disconnect()` or dropping the client), every live subscription yields `Err(Error::Shutdown)`, then ends; the notice stream just ends

### Connection Monitoring
Use `client.is_connected()` to check connection status:
```rust
if client.is_connected() {
    // Safe to make API calls
}
```

## Important Design Considerations

1. **Shared Channels**: Some TWS API calls don't have unique request IDs; their responses are routed by message type to every live subscription of that type, on both clients. Concurrent subscriptions mapped to the same response types therefore see each other's responses: a second `open_orders()` while the first is still open receives the first's `OpenOrder` frames as well as its own, and so does an `all_open_orders()`, which maps to the same response types. TWS keeps one such stream per client; its cancel is sent when the last subscription of the type ends.

2. **Message Ordering**: TWS doesn't guarantee message ordering for different request types.

3. **Rate Limiting**: TWS limits outbound messages. The library enforces this only when a client is built with `ClientBuilder::rate_limiter` (off by default); the limiter sits in the bus write funnel, so every message after the handshake is counted. See `RateLimiter` for the rules. Historical-data pacing and market-data lines remain the application's job.

4. **Error Recovery**: Connection errors trigger automatic reconnection. Application errors must be handled by the caller.

### Physical socket teardown

Stopping dispatch does not release a socket whose bus still has owners, so
shutdown closes it explicitly (`Io::close` / `AsyncIo::close`). Close is
idempotent and terminal: pending and later I/O fails with `Error::Shutdown`, and
reconnect refuses, so the retired socket can't be revived.

- **Async:** disconnect closes both owned TCP halves before joining dispatch;
  dispatcher exit performs the same close for best-effort client drop. Close
  latches a flag first, which wakes blocked reads and writes so their locks free
  up.
- **Blocking:** `request_shutdown`, which every disconnect, drop and dispatcher
  exit runs, shuts down both directions on a clone of the stream. That is
  socket-level, so it sends FIN and fails I/O on every clone. Reconnect
  re-checks the closed flag under the same lock before installing a new stream.