Releases: wboayue/rust-ibapi
v2.11.3
Bug Fixes
TWS wire encoding for conditional orders (#469)
Conditional orders now serialize with the correct conjunction connector ("a"/"o" instead of bool) and the field ordering that matches the official Python IB API. Previously live TWS rejected conditional placements with parser errors like Unable to parse field: 'Trigger Price' for input string: 'CME'. Thanks to @faysou for the fix.
use ibapi::orders::builder::{price, time};
let order_id = client.order(&contract)
.buy(100)
.market()
.condition(price(265598, "SMART").greater_than(150.0))
.and_condition(time().greater_than("20271231 23:59:59 US/Eastern"))
.submit().await?;Unset-double sentinel encoding (#469)
f64::MAX now serializes as the canonical IB wire token 1.7976931348623157E308 rather than Rust's default decimal expansion. This is required for broad contract-discovery requests (e.g. futures-options chains) where fields like strike must be left unset rather than sent as a real 0.0; finite f64 values are unchanged. Thanks to @faysou for the fix.
let mut contract = Contract::default();
contract.symbol = "ES".to_string();
contract.security_type = SecurityType::Future;
contract.exchange = "CME".to_string();
contract.last_trade_date_or_contract_month = "202506".to_string();
// strike left at f64::MAX (unset) — now emitted as the IB sentinel token
let details = client.contract_details(&contract).await?;v2.11.2
What's New
Runtime-registerable timezone aliases (#464)
register_timezone_alias(name, iana) and the IBAPI_TIMEZONE_ALIASES env var let you map gateway-supplied timezone strings without rebuilding the crate. User-registered aliases take precedence over the built-in TIMEZONE_ALIASES table, so they work for both new entries and overrides.
use ibapi::register_timezone_alias;
register_timezone_alias("China Standard Time", "Asia/Shanghai");
let client = Client::connect("127.0.0.1:4002", 100).await?;Or via env var:
IBAPI_TIMEZONE_ALIASES="China Standard Time=Asia/Shanghai;Romance Standard Time=Europe/Paris" \
cargo run --example connectBug Fixes
Historical decoder no longer panics on unknown timezone (#466)
parse_time_zone in the historical-data path now returns Error::Simple instead of panicking when it encounters an unmapped timezone name. The error message points at register_timezone_alias and IBAPI_TIMEZONE_ALIASES so users can extend the registry without a new release. Previously an unmapped name in a historical schedule or bar response would crash the process, defeating the runtime registry added in #464.
v2.11.1
What's New
Public Client::disconnect() for clean shutdown (#462)
Both async and sync clients now expose disconnect(), which closes channels and joins/awaits the dispatch task so the tokio runtime (or worker threads) can exit cleanly. Drop cannot run the full shutdown path on async because it cannot .await, so callers holding Arc<Client> across spawned tasks should call disconnect().await explicitly before drop.
let client = Client::connect("127.0.0.1:4002", 100).await?;
// ... use client ...
client.disconnect().await;Bug Fixes
European continental Windows timezone names (#458)
Adds nine TIMEZONE_ALIASES entries (E. Europe, Eastern European, FLE, GTB, Central European, W. Europe, Romance, etc.) covering the Windows TZ strings IB Gateway sends from non-English continental European installs. Without these the handshake hit "Time zone not found" and Client::connect aborted with early eof.
Thanks to @iKiok.
Clear error for unmappable IB Gateway timezone (#460)
parse_connection_time now returns a descriptive error naming the offending timezone string and pointing at TIMEZONE_ALIASES when IB sends a name that is neither a known alias nor a valid IANA zone. Previously the failure was a silent log and the handshake returned Ok, leaving downstream tz-dependent code to fail mysteriously.
SGT timezone alias for Singapore (#461)
Maps SGT → Asia/Singapore so Singapore-based IB Gateway installations populate client.time_zone instead of leaving it None.
Thanks to @Niqnil.
v2.11.0
Bug Fixes
Bracket order time-in-force propagation (#444)
Child orders (take profit, stop loss) now inherit the parent order's time-in-force setting. Previously they always defaulted to Day, leaving positions exposed after the maintenance window.
client.order(&contract)
.buy(100)
.good_till_cancel() // now applies to all three orders
.bracket()
.entry_limit(50.0)
.take_profit(55.0)
.stop_loss(45.0)
.submit_all()Thanks to @rickykresslein for reporting.
Improved error context for parsing failures (#440)
Error messages now include field context when parsing TWS responses fails, making it easier to diagnose protocol issues.
Thanks to @vjsingh1984.
Timezone-aware IB timestamp parsing (#443)
Fixed timestamp parsing to correctly handle IB timezone suffixes and deduplicate StreamDecoder responses.
Thanks to @faysou.
British Summer Time zone mapping (#445)
Added BST timezone mapping so timestamps during British Summer Time are parsed correctly.
Thanks to @kinz-dev.
v2.10.0
What's New
Continuous futures contract type for historical data (#435)
Adds ContinuousFuture security type and a builder for constructing continuous futures contracts, useful for fetching uninterrupted historical data across contract rollovers.
use ibapi::contracts::Contract;
let es = Contract::continuous_futures("ES")
.on_exchange("CME")
.build();
let bars = client.historical_data(&es, 30.days(), HistoricalBarSize::Day, None).await?;— contributed by @BensonYanger
Bug Fixes
Fix async market_depth cancel not propagating is_smart_depth flag (#437)
The async market_depth() was not propagating the is_smart_depth flag into the subscription's decoder context. This caused cancel messages to be sent with is_smart_depth = false, mismatching the original subscribe. TWS silently ignored the mismatched cancel, eventually hitting the 3-subscription limit (error 309).
// Async smart depth subscriptions now cancel correctly,
// matching the sync client behavior
let mut sub = client.market_depth(&contract, 5, true).await?;
drop(sub); // cancel now sends correct is_smart_depth = true— contributed by @lodibrahim
v2.9.2
What's New
utoipa feature flag with ToSchema on public types (#428)
Optional utoipa feature that derives ToSchema on all public data model types, enabling direct use in OpenAPI schema generation.
// Cargo.toml
// [dependencies]
// ibapi = { version = "2.9", features = ["utoipa"] }
// Public data model types (Contract, Order, Execution, etc.) now impl
// utoipa::ToSchema so they can be used directly in utoipa's
// #[utoipa::path] and OpenApi derivesBug Fixes
Send CancelHistoricalData when streaming subscription is dropped (#430)
historical_data_streaming subscriptions now properly send a cancel message to the server when dropped or explicitly cancelled, preventing orphaned server-side subscriptions.
let mut subscription = client
.historical_data_streaming(&contract, 1.days(), HistoricalBarSize::Min, None)
.await?;
// Cancel is now automatically sent when subscription is dropped
drop(subscription);
// Explicit cancel is also supported and idempotent
// subscription.cancel();Correct cancel_order and global_cancel encoding for server >= 192 (#429)
Fixed message encoding for cancel_order and global_cancel on servers supporting CME tagging fields (v192+). The version field is now correctly omitted, and ext_operator and manual_order_indicator fields are appended to match the reference client.
// cancel_order and global_cancel now work correctly
// against IB Gateway / TWS server versions >= 192
client.cancel_order(order_id, "").await?;
client.global_cancel().await?;v2.9.1
Bug Fixes
Decode FULL_ORDER_PREVIEW_FIELDS in open order message (#424)
v2.9.0 bumped max_version to 200, causing TWS to send fields the decoder did not consume — shifting all subsequent reads and corrupting open order messages.
Adds version-gated decoding for:
- FULL_ORDER_PREVIEW_FIELDS (v195):
margin_currency, 9 outside-RTH margin fields,suggested_size,reject_reason, andorder_allocationsonOrderState - FUND_DATA_FIELDS (v179): 17 mutual fund fields on
ContractDetailswithFundDistributionPolicyIndicatorandFundAssetTypeenums - INELIGIBILITY_REASONS (v186):
IneligibilityReasonwith id/description pairs onContractDetails
// New fields on OrderState
let order_state = open_order.order_state;
println!("margin currency: {}", order_state.margin_currency);
println!("suggested size: {:?}", order_state.suggested_size);
for alloc in &order_state.order_allocations {
println!("allocation: {} @ {:?}", alloc.account, alloc.position);
}
// New fund fields on ContractDetails
let details = contract_details;
println!("fund: {} ({})", details.fund_name, details.fund_family);
println!("asset type: {:?}", details.fund_asset_type);
// Ineligibility reasons
for reason in &details.ineligibility_reasons {
println!("{}: {}", reason.id, reason.description);
}Fix sync skip handling, remove dead retry infrastructure (#423)
Sync next() never actually skipped unexpected messages — it fell through to the terminal arm, silently ending the subscription on the first stray message. Rewrote as a loop (matching the async pattern) and removed dead Retry variant and all retry infrastructure.
Skip unexpected messages on shared channels without retry limit (#418)
Subscriptions on shared broadcast channels (e.g., RequestOpenOrders, RequestPositions) receive all messages on the channel. Stray messages were counted toward MAX_DECODE_RETRIES (10), killing healthy subscriptions after just 10 wrong-channel messages. Now maps UnexpectedResponse to a Skip that continues without incrementing the retry counter.
Release order update stream ownership on subscription drop (#416)
order_update_stream ownership was never released when the subscription was dropped, causing all subsequent calls to fail with AlreadySubscribed. Now participates in the cleanup signal mechanism matching other subscription types.
Remove deadlock risk from async server_version (#420, #422)
server_version() used futures::executor::block_on() to acquire a tokio Mutex on every message read, blocking worker threads and risking deadlock during reconnection. Replaced with a lock-free AtomicI32 cache using Release/Acquire ordering.
Update toml dependency from 0.9 to 1.0 (#414)
Contributors
- @vjsingh1984 (#418, #416)
- @tsbernar (#420)
v2.9.0
What's New
Millisecond-precision server time (#406)
New server_time_millis() returns the TWS/Gateway server time with millisecond precision via the CurrentTimeInMillis message.
let client = Client::connect("127.0.0.1:4002", 100).await?;
let server_time = client.server_time_millis().await?;
println!("Server time: {server_time}");Contract.last_trade_date field (#406)
Derivatives now include a parsed last_trade_date: Option<time::Date> field populated by the server, separate from the input-only last_trade_date_or_contract_month string.
let details = client.contract_details(&contract).await?;
for d in &details {
println!("Last trade date: {:?}", d.contract.last_trade_date);
}HistoricalBarUpdate::End variant (#406)
Streaming historical data subscriptions now emit an End { start, end } variant when the server signals end-of-stream, providing the date range of the returned data.
match update {
HistoricalBarUpdate::Update(bar) => { /* process bar */ }
HistoricalBarUpdate::End { start, end } => {
println!("Historical data range: {start} to {end}");
}
}Server version bump to 200 (#411)
Max supported server version raised from 173 to 200 (PARAMETRIZED_DAYS_OF_EXECUTIONS). This unlocks fields added in server versions 174–200:
Order:customer_account,professional_customer,bond_accrued_interest,include_overnight,manual_order_indicator,submitterExecution:pending_price_revision,submitter
Protobuf schema definitions (#404)
Added prost-based protobuf compilation pipeline behind the proto feature flag. Proto files are fetched from the IB tws-api repo at build time. Groundwork for protobuf message framing required by server v201+.
New message codes (#405)
Added incoming messages (HistoricalDataEnd, CurrentTimeInMillis, ConfigResponse, UpdateConfigResponse) and outgoing messages (RequestCurrentTimeInMillis, CancelContractData, CancelHistoricalTicks, ReqConfig, UpdateConfig). Updated server version constants through v221.
Bug Fixes
Fix spurious retry warnings on subscription end (#407)
Subscription iterators now track stream-ended state, preventing stray messages from triggering misleading "retry" warnings after a stream completes. Retry log level reduced from warn to debug.
Fix error message parsing for server v194+ (#406)
Server versions >= 194 drop the version field from error messages and append a timestamp. Error parsing now correctly handles both old and new formats. Notice struct now includes an error_time: Option<OffsetDateTime> field.
Fix historical data end dates for server v196+ (#406)
Server v196+ moves start/end dates to a separate HistoricalDataEnd message. Non-streaming historical_data() now consumes this message to return correct date ranges instead of falling back to now_utc().
Fix Greenwich Mean Time timezone mapping (#402)
TWS may return "Greenwich Mean Time" or "GMT Standard Time" as timezone strings during connection handshake. These Windows-style names are now mapped to Europe/London, fixing "Time zone not found" connection errors. (Thanks @hoiung)
Fix missing encoder/decoder fields for v183–v200 (#411)
Fixed decode_open_order missing reads and encode_place_order missing fields for server versions 183–200, including customer_account, professional_customer, CME tagging fields, RFQ compatibility, and imbalance_only.
Note: cancel_contract_details() and cancel_historical_ticks() are implemented but require server version 215 (CANCEL_CONTRACT_DATA). The current max supported version is 200, so these methods will return a version error until protobuf framing support (v201+) is added. They are excluded from documentation until then.
v2.8.2
What's New
Integration test suite (#400)
Added ~80 integration tests covering the full public API (sync + async) against a live IB Gateway.
- Tests organized in
integration/workspace with sharedibapi-testhelper crate - Token-bucket rate limiter and RAII client ID pool for parallel-safe execution
- Serial test groups for APIs that don't support concurrent requests
Bug Fixes
Fix async client deadlock (#400)
Split async TcpStream into separate read/write halves to prevent deadlocks when concurrent tasks contend for the single socket mutex. Replaced polling-based shutdown loop with tokio::Notify to avoid cancelling read_exact mid-read, which could corrupt the TCP stream.
// Before: single mutex could deadlock when reading and writing concurrently
pub(crate) socket: Mutex<TcpStream>,
// After: independent locks for read and write
pub(crate) reader: Mutex<OwnedReadHalf>,
pub(crate) writer: Mutex<OwnedWriteHalf>,v2.8.1
Bug Fixes
Revert server max_version to 173 (#399)
The v2.8.0 bump of max_version to 200 (PARAMETRIZED_DAYS_OF_EXECUTIONS) caused decoder regressions — TWS sends additional fields for server versions 174-200 that decoders don't yet handle, leading to misaligned message parsing. Reverted to 173 (WSH_EVENT_DATA_FILTERS_DATE) until proper decoder coverage is added.