From 4908814517e1596f8ec1fd96af69410878cfe0ba Mon Sep 17 00:00:00 2001 From: Vijaykumar Singh Date: Sat, 7 Feb 2026 19:23:16 -0600 Subject: [PATCH 1/4] feat: add opt-in TCP_NODELAY via IBAPI_TCP_NODELAY env var Optionally disables Nagle's algorithm on TcpStream when the environment variable IBAPI_TCP_NODELAY=1 is set. Default behavior is unchanged (Nagle enabled, matching upstream). When enabled, small writes (order submissions ~100-200 bytes) are sent immediately instead of being buffered up to 40ms. For trading systems this eliminates latency on order routing with zero practical downside. Usage: IBAPI_TCP_NODELAY=1 ./my-trading-app Affected paths: - sync: TcpSocket::new() and TcpSocket::reconnect() - async: AsyncConnection::connect_with_callback() and reconnect() --- src/connection/async.rs | 9 +++++++++ src/transport/sync.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/connection/async.rs b/src/connection/async.rs index 67dd9ab8..6101ddcb 100644 --- a/src/connection/async.rs +++ b/src/connection/async.rs @@ -40,6 +40,12 @@ impl AsyncConnection { /// setup that are not part of the normal handshake (e.g., OpenOrder, OrderStatus). pub async fn connect_with_callback(address: &str, client_id: i32, startup_callback: Option) -> Result { let socket = TcpStream::connect(address).await?; + // Optionally disable Nagle's algorithm for low-latency order submission. + // Set IBAPI_TCP_NODELAY=1 to send small writes (order messages) immediately + // instead of buffering up to 40ms. + if std::env::var("IBAPI_TCP_NODELAY").unwrap_or_default() == "1" { + socket.set_nodelay(true)?; + } let connection = Self { client_id, @@ -90,6 +96,9 @@ impl AsyncConnection { match TcpStream::connect(&self.connection_url).await { Ok(new_socket) => { + if std::env::var("IBAPI_TCP_NODELAY").unwrap_or_default() == "1" { + new_socket.set_nodelay(true)?; + } info!("reconnected !!!"); { diff --git a/src/transport/sync.rs b/src/transport/sync.rs index d0f26ec8..32cee8c8 100644 --- a/src/transport/sync.rs +++ b/src/transport/sync.rs @@ -702,6 +702,12 @@ pub(crate) struct TcpSocket { } impl TcpSocket { pub fn new(stream: TcpStream, connection_url: &str) -> Result { + // Optionally disable Nagle's algorithm for low-latency order submission. + // Set IBAPI_TCP_NODELAY=1 to send small writes immediately. + if std::env::var("IBAPI_TCP_NODELAY").unwrap_or_default() == "1" { + stream.set_nodelay(true)?; + } + let writer = stream.try_clone()?; stream.set_read_timeout(Some(TWS_READ_TIMEOUT))?; @@ -718,6 +724,9 @@ impl Reconnect for TcpSocket { fn reconnect(&self) -> Result<(), Error> { match TcpStream::connect(&self.connection_url) { Ok(stream) => { + if std::env::var("IBAPI_TCP_NODELAY").unwrap_or_default() == "1" { + stream.set_nodelay(true)?; + } stream.set_read_timeout(Some(TWS_READ_TIMEOUT))?; let mut reader = self.reader.lock()?; From 644ed16d813a6c71ec92d3c360e84f074b67982c Mon Sep 17 00:00:00 2001 From: Vijaykumar Singh Date: Sat, 7 Feb 2026 21:48:56 -0600 Subject: [PATCH 2/4] feat: add Min10 (10-minute) bar size support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 10-minute bar size to the BarSize enum for historical data fetching, matching IBKR TWS/Gateway API support. Changes: - Add Min10 variant to BarSize enum (between Min5 and Min15) - Display format: "10 mins" - FromStr parsing: "MIN10" → BarSize::Min10 - Update tests to include Min10 coverage This aligns with the official IBKR API specification which supports 10-minute bars for historical data requests. --- src/market_data/historical/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/market_data/historical/mod.rs b/src/market_data/historical/mod.rs index 2bcfb769..75cafc41 100644 --- a/src/market_data/historical/mod.rs +++ b/src/market_data/historical/mod.rs @@ -79,6 +79,8 @@ pub enum BarSize { Min3, /// Five-minute bars. Min5, + /// Ten-minute bars. + Min10, /// Fifteen-minute bars. Min15, /// Twenty-minute bars. @@ -115,6 +117,7 @@ impl Display for BarSize { Self::Min2 => write!(f, "2 mins"), Self::Min3 => write!(f, "3 mins"), Self::Min5 => write!(f, "5 mins"), + Self::Min10 => write!(f, "10 mins"), Self::Min15 => write!(f, "15 mins"), Self::Min20 => write!(f, "20 mins"), Self::Min30 => write!(f, "30 mins"), @@ -144,6 +147,7 @@ impl FromStr for BarSize { "MIN2" => Ok(Self::Min2), "MIN3" => Ok(Self::Min3), "MIN5" => Ok(Self::Min5), + "MIN10" => Ok(Self::Min10), "MIN15" => Ok(Self::Min15), "MIN20" => Ok(Self::Min20), "MIN30" => Ok(Self::Min30), @@ -599,6 +603,7 @@ mod tests { assert_eq!("2 mins", BarSize::Min2.to_string()); assert_eq!("3 mins", BarSize::Min3.to_string()); assert_eq!("5 mins", BarSize::Min5.to_string()); + assert_eq!("10 mins", BarSize::Min10.to_string()); assert_eq!("15 mins", BarSize::Min15.to_string()); assert_eq!("20 mins", BarSize::Min20.to_string()); assert_eq!("30 mins", BarSize::Min30.to_string()); @@ -623,6 +628,7 @@ mod tests { assert_eq!(BarSize::Min2, BarSize::from("MIN2")); assert_eq!(BarSize::Min3, BarSize::from("MIN3")); assert_eq!(BarSize::Min5, BarSize::from("MIN5")); + assert_eq!(BarSize::Min10, BarSize::from("MIN10")); assert_eq!(BarSize::Min15, BarSize::from("MIN15")); assert_eq!(BarSize::Min20, BarSize::from("MIN20")); assert_eq!(BarSize::Min30, BarSize::from("MIN30")); From 7907784ef79e4d896c646849a107081ad060fa6d Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Sun, 8 Feb 2026 22:59:15 -0800 Subject: [PATCH 3/4] Update async.rs --- src/connection/async.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/connection/async.rs b/src/connection/async.rs index 6101ddcb..67dd9ab8 100644 --- a/src/connection/async.rs +++ b/src/connection/async.rs @@ -40,12 +40,6 @@ impl AsyncConnection { /// setup that are not part of the normal handshake (e.g., OpenOrder, OrderStatus). pub async fn connect_with_callback(address: &str, client_id: i32, startup_callback: Option) -> Result { let socket = TcpStream::connect(address).await?; - // Optionally disable Nagle's algorithm for low-latency order submission. - // Set IBAPI_TCP_NODELAY=1 to send small writes (order messages) immediately - // instead of buffering up to 40ms. - if std::env::var("IBAPI_TCP_NODELAY").unwrap_or_default() == "1" { - socket.set_nodelay(true)?; - } let connection = Self { client_id, @@ -96,9 +90,6 @@ impl AsyncConnection { match TcpStream::connect(&self.connection_url).await { Ok(new_socket) => { - if std::env::var("IBAPI_TCP_NODELAY").unwrap_or_default() == "1" { - new_socket.set_nodelay(true)?; - } info!("reconnected !!!"); { From 3bdd47184c167d94d6ffc12ba8f54232b6c2a2e4 Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Sun, 8 Feb 2026 23:00:57 -0800 Subject: [PATCH 4/4] Update sync.rs --- src/transport/sync.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/transport/sync.rs b/src/transport/sync.rs index 32cee8c8..d0f26ec8 100644 --- a/src/transport/sync.rs +++ b/src/transport/sync.rs @@ -702,12 +702,6 @@ pub(crate) struct TcpSocket { } impl TcpSocket { pub fn new(stream: TcpStream, connection_url: &str) -> Result { - // Optionally disable Nagle's algorithm for low-latency order submission. - // Set IBAPI_TCP_NODELAY=1 to send small writes immediately. - if std::env::var("IBAPI_TCP_NODELAY").unwrap_or_default() == "1" { - stream.set_nodelay(true)?; - } - let writer = stream.try_clone()?; stream.set_read_timeout(Some(TWS_READ_TIMEOUT))?; @@ -724,9 +718,6 @@ impl Reconnect for TcpSocket { fn reconnect(&self) -> Result<(), Error> { match TcpStream::connect(&self.connection_url) { Ok(stream) => { - if std::env::var("IBAPI_TCP_NODELAY").unwrap_or_default() == "1" { - stream.set_nodelay(true)?; - } stream.set_read_timeout(Some(TWS_READ_TIMEOUT))?; let mut reader = self.reader.lock()?;