rust-ibapi

repository·main·Indexed 43 days ago

https://github.com/wboayue/rust-ibapi

A Rust implementation of the Interactive Brokers TWS API (version 4.1.0) for high-performance automated trading, market data analysis, and portfolio management. It provides both synchronous (blocking) and asynchronous (Tokio-based) clients to interact with TWS and IB Gateway, supporting account information retrieval, real-time and historical market data, order management, and market scans.

Tokens
91.9K
Snippets
255
Records
335
Agent score
56%

What's inside rust-ibapi

  1. Introduction to rust-ibapi

    main

    The ibapi library is a comprehensive Rust implementation of the Interactive Brokers TWS API. It is designed for automated trading systems, market analysis, real-time data collection, and portfolio management.

    Key capabilities include:

    • Retrieving account information.
    • Accessing real-time and historical market data.
    • Managing orders.
    • Performing market scans.
    • Accessing news and Wall Street Horizons (WSH) event data.
  2. Understand the integration test crate layout

    main

    Integration tests are organized as separate workspace crates in the integration/ directory. They depend on ibapi and run against a live IB Gateway or TWS instance.

    CratePathPurpose
    ibapi-testintegration/common/Shared helpers (ClientId pool, rate limiter)
    ibapi-integration-syncintegration/sync/Sync client tests and binaries
    ibapi-integration-asyncintegration/async/Async client tests and binaries

    Tests are located in tests/ and standalone binaries are in bin/ within these crates.

  3. Understand the request_id routing mechanism

    main

    Routing for request_id-keyed subscriptions relies on a deliberate allow-list called text_request_id_field (located in src/messages.rs).

    Core Concepts:

    • The Allow-list: text_request_id_field is not a sentinel; it is an explicit allow-list. This prevents misrouting messages where an integer at tag 1 might represent something other than a request ID (e.g., MarketRule.market_rule_id).
    • Routing Logic: routes_by_request_id() is a thin wrapper over this table. The table must contain the correct text-frame field index (e.g., 1 vs 2).
    • Validation: The function debug_assert_request_id_routable in src/subscriptions/common.rs validates this during subscription construction. It will panic if a request_id-keyed subscription is built for a decoder whose RESPONSE_MESSAGE_IDS includes a type that the dispatcher cannot route (i.e., a type missing from the text_request_id_field table).
  4. Construct contracts using the Contract Builder

    main

    Contracts are built using type-state builders that enforce required fields at compile time. This ensures you provide necessary information for specific asset types (stocks, options, futures, etc.) and provides strongly-typed wrappers for exchanges, currencies, and option rights.

    use ibapi::contracts::Contract;
    
    let stock = Contract::stock("AAPL").build();
    let option = Contract::call("AAPL").strike(150.0).expires_on(2024, 12, 20).build();
  5. Understand the Type-State pattern for conditions

    main

    Condition builders (like PriceCondition) use the type-state pattern to ensure valid configuration. This prevents invalid states (like setting a threshold without a direction) at compile time:

    1. Constructor: Initialize with required fields (e.g., PriceCondition::builder(contract_id, exchange)).
    2. Threshold & Direction: Set these atomically using methods like .greater_than(value) or .less_than(value). This prevents the error of setting a threshold but forgetting the direction.
    3. Optional Config: Chain methods like .trigger_method(TriggerMethod::Last) or .conjunction(true).
    4. Conversion: Finalize the builder using .build() or rely on automatic Into<OrderCondition> conversion.
    use ibapi::orders::conditions::{PriceCondition, TriggerMethod};
    
    let condition = PriceCondition::builder(265598, "SMART")
        .greater_than(150.0)
        .trigger_method(TriggerMethod::Last)
        .build();
  6. Understand the ID Partitioning and Typed IDs design

    main

    The project uses a partitioned ID space to prevent collisions between Request IDs and Order IDs. A specific floor value, REQUEST_ID_FLOOR (set to $2^{30}$ or 1_073_741_824), is used to separate these domains.

    Key Concepts:

    • Partitioning: Request IDs and Order IDs are separated by a floor. Request IDs typically reside in the lower range, while Order IDs are moved to the range above the floor.
    • Typed IDs: The library uses specific types RequestId and OrderId instead of raw i32 to prevent logic errors (like using a request ID where an order ID is expected).
    • Validation: Order entry points validate that IDs do not cross into the request ID partition. If an ID is invalid (e.g., an Order ID is provided that is below the floor or is a reserved value), the API will return an InvalidArgument error.

    ID Behavior:

    • next_request_id() returns values at or above 1_073_741_824.
    • Order IDs provided to place_order, submit_order, or cancel_order are checked against the floor. If they violate the partition, they are rejected with Error::InvalidArgument before being sent to the wire.
  7. Use Conditional Orders to trigger trades

    main

    Conditional orders allow you to specify market conditions that must be met before an order is activated. You can combine multiple conditions using .and_condition() (requires all conditions to be met) or .or_condition() (triggers when any condition is met) to create complex trading strategies.

    // Example: (price > 10 AND margin < 20) OR time > X OR volume > Y
    let order_id = client.order(&contract)
        .buy(100)
        .market()
        .condition(price(123445, "SMART").greater_than(10.0))
        .and_condition(margin().less_than(20))
        .or_condition(time().greater_than("20251010 09:30:00 US/Eastern"))
        .or_condition(volume(123445, "SMART").greater_than(10_000_000))
        .submit()?;
  8. Use Newtype Wrappers for domain constraints

    main

    To prevent type confusion (e.g., accidentally swapping a ContractId with an OrderId) and to enforce domain constraints (e.g., IDs must be positive), use the Newtype pattern. Instead of passing raw primitives like i32, wrap them in a tuple struct.

    // Use newtype wrappers to prevent mistakes
    pub struct ContractId(i32);
    
    impl ContractId {
        pub fn new(id: i32) -> Result<Self, Error> {
            if id <= 0 { return Err(Error::InvalidContractId); }
            Ok(Self(id))
        }
    }
    
    // The type system now prevents passing a raw i32 where a ContractId is required
    fn lookup(id: ContractId) -> Contract { ... }
  9. Understand the difference between Sync and Async modes

    main

    The library is split into two primary execution modes:

    Synchronous (sync/)

    • Uses blocking calls and threads.
    • Returns values directly or via iterators.
    • Best for simple, sequential operations.

    Asynchronous (async/)

    • Uses async/await with the Tokio runtime.
    • Returns Futures and Streams.
    • Best for high-concurrency operations.

    Async Pattern Example

    When working with asynchronous market data, you typically subscribe to a stream and process it using a loop:

    use futures::StreamExt;
    
    let mut stream = client.market_data(&contract)
        .subscribe()
        .await?;
    while let Some(tick) = stream.next().await {
        // Process tick
    }
    use futures::StreamExt;
    
    let mut stream = client.market_data(&contract)
        .subscribe()
        .await?;
    while let Some(tick) = stream.next().await {
        // Process tick
    }
  10. How request/response flow works in rust-ibapi

    main

    When you make an API call (e.g., req_contract_details), the following sequence occurs:

    1. Client: Generates a Request ID and encodes the request.
    2. MessageBus: Receives the request, creates a dedicated response channel, and forwards the message to the Connection manager.
    3. Connection: Sends the request via a TCP socket to TWS/Gateway.
    4. TWS/Gateway: Processes the request and sends a response.
    5. Connection: Receives the response and parses it.
    6. MessageBus: Routes the parsed response back to the Client using the Request ID.
    7. Client: Decodes the response and returns the result to your application.

    Message Routing Patterns

    • Requests with IDs: The MessageBus uses dedicated channels for these responses.
    • Requests without IDs: The MessageBus uses shared channels.
    • Order-based requests: These receive special handling for tracking purposes.
  11. How the Type-State Pattern ensures valid contracts

    main

    The Contract Builder uses the Type-State Pattern to track which required fields have been set. This is implemented using phantom types in the builder's generic signature (e.g., OptionBuilder<Symbol, Strike, ExpirationDate>).

    Because of this, the .build() method is only available on the builder when all required fields for that specific contract type are present. This moves validation from runtime to compile time, making it impossible to accidentally create an incomplete contract.

  12. Follow the module structure pattern for new API modules

    main

    When creating a new API module, follow a consistent directory structure to support both synchronous and asynchronous modes. The module should be organized as follows:

    • src/<module>/mod.rs: Contains public types and module exports. This file should define types that are available regardless of feature flags.
    • src/<module>/common/: Contains shared implementation details used by both sync and async implementations.
      • mod.rs: Exports encoders/decoders.
      • encoders.rs: Message encoding functions.
      • decoders.rs: Protobuf decoding functions.
      • test_tables.rs: (Optional) Shared test cases.
    • src/<module>/sync.rs: Synchronous implementation.
    • src/<module>/async.rs: Asynchronous implementation.
    • src/<module>/sync_tests.rs: Tests for the synchronous implementation (use a flat sibling file, do not use inline mod tests).
    • src/<module>/async_tests.rs: Tests for the asynchronous implementation.

    Note: Response fixtures should not live in the module. They belong in src/testdata/builders/<domain>.rs, with one builder per domain implementing ResponseProtoEncoder.

    // src/<module>/mod.rs
    //! Module description
    
    // Common implementation modules
    mod common;
    
    // Feature-specific implementations
    #[cfg(feature = "sync")]
    mod sync;
    
    #[cfg(feature = "async")]
    mod r#async;
    
    // Public types - always available regardless of feature flags
    #[derive(Debug)]
    pub struct MyData {
        pub field: String,
    }