ibapi 4.0.0

A Rust implementation of the Interactive Brokers TWS API, providing a reliable and user friendly interface for TWS and IB Gateway. Designed with a focus on simplicity and performance.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
//! Test utilities shared across all modules for testing

#[cfg(test)]
#[allow(dead_code)] // These utilities will be used by other modules
pub mod helpers {
    use crate::stubs::MessageBusStub;
    use crate::{server_versions, Client};
    use std::sync::Arc;

    /// Creates a test client with an empty message bus
    pub fn create_test_client() -> (Client, Arc<MessageBusStub>) {
        create_test_client_with_version(server_versions::SIZE_RULES)
    }

    /// Creates a test client with a specific server version
    pub fn create_test_client_with_version(server_version: i32) -> (Client, Arc<MessageBusStub>) {
        let message_bus = Arc::new(MessageBusStub::with_responses(vec![]));
        let client = Client::stubbed(message_bus.clone(), server_version);
        (client, message_bus)
    }

    /// Creates a test client with specified response messages
    pub fn create_test_client_with_responses(responses: Vec<String>) -> (Client, Arc<MessageBusStub>) {
        let message_bus = Arc::new(MessageBusStub::with_responses(responses));
        let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
        (client, message_bus)
    }

    /// Creates a test client with specified response messages and server version
    pub fn create_test_client_with_responses_and_version(responses: Vec<String>, server_version: i32) -> (Client, Arc<MessageBusStub>) {
        let message_bus = Arc::new(MessageBusStub::with_responses(responses));
        let client = Client::stubbed(message_bus.clone(), server_version);
        (client, message_bus)
    }

    /// Creates a test client backed by [`MessageBusStub::with_ordered_responses`].
    /// Pairs with [`proto_response`] for proto-framed fixtures.
    pub fn create_test_client_with_ordered_proto_responses(responses: Vec<crate::messages::ResponseMessage>) -> (Client, Arc<MessageBusStub>) {
        let message_bus = Arc::new(MessageBusStub::with_ordered_responses(responses));
        let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
        (client, message_bus)
    }

    #[cfg(feature = "sync")]
    pub fn create_blocking_test_client() -> (crate::client::blocking::Client, Arc<MessageBusStub>) {
        create_blocking_test_client_with_version(server_versions::SIZE_RULES)
    }

    #[cfg(feature = "sync")]
    pub fn create_blocking_test_client_with_version(server_version: i32) -> (crate::client::blocking::Client, Arc<MessageBusStub>) {
        create_blocking_test_client_with_responses_and_version(vec![], server_version)
    }

    #[cfg(feature = "sync")]
    pub fn create_blocking_test_client_with_responses(responses: Vec<String>) -> (crate::client::blocking::Client, Arc<MessageBusStub>) {
        create_blocking_test_client_with_responses_and_version(responses, server_versions::SIZE_RULES)
    }

    #[cfg(feature = "sync")]
    pub fn create_blocking_test_client_with_responses_and_version(
        responses: Vec<String>,
        server_version: i32,
    ) -> (crate::client::blocking::Client, Arc<MessageBusStub>) {
        let message_bus = Arc::new(MessageBusStub::with_responses(responses));
        let client = crate::client::blocking::Client::stubbed(message_bus.clone(), server_version);
        (client, message_bus)
    }

    /// Sync sibling of [`create_test_client_with_ordered_proto_responses`].
    #[cfg(feature = "sync")]
    pub fn create_blocking_test_client_with_ordered_proto_responses(
        responses: Vec<crate::messages::ResponseMessage>,
    ) -> (crate::client::blocking::Client, Arc<MessageBusStub>) {
        let message_bus = Arc::new(MessageBusStub::with_ordered_responses(responses));
        let client = crate::client::blocking::Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
        (client, message_bus)
    }

    /// Asserts that the nth request message has the expected protobuf message ID
    pub fn assert_request_msg_id(message_bus: &MessageBusStub, index: usize, expected: crate::messages::OutgoingMessages) {
        let request_messages = message_bus.request_messages.read().unwrap();
        assert!(
            request_messages.len() > index,
            "Expected at least {} request messages, got {}",
            index + 1,
            request_messages.len()
        );
        assert_proto_msg_id(&request_messages[index], expected);
    }

    /// Gets request message count from the message bus
    pub fn request_message_count(message_bus: &MessageBusStub) -> usize {
        message_bus.request_messages.read().unwrap().len()
    }

    /// Decodes a protobuf request message (skips 4-byte msg_id header)
    pub fn decode_request_proto<T: prost::Message + Default>(message_bus: &MessageBusStub, index: usize) -> T {
        let request_messages = message_bus.request_messages.read().unwrap();
        T::decode(&request_messages[index][4..]).unwrap()
    }

    /// Asserts that the nth request matches the expected message id AND decodes to `expected`.
    /// Strict counterpart to `assert_request_msg_id`, which only checks the 4-byte header.
    pub fn assert_request_proto<T>(message_bus: &MessageBusStub, index: usize, expected_msg_id: crate::messages::OutgoingMessages, expected: &T)
    where
        T: prost::Message + Default + PartialEq + std::fmt::Debug,
    {
        assert_request_msg_id(message_bus, index, expected_msg_id);
        let actual: T = decode_request_proto(message_bus, index);
        assert_eq!(&actual, expected, "request {index} body mismatch");
    }

    /// Builder-aware variant of [`assert_request_proto`]: pulls the expected message id and
    /// proto body from the builder's `RequestEncoder` impl, so tests don't repeat the msg id.
    pub fn assert_request<B: crate::testdata::builders::RequestEncoder>(message_bus: &MessageBusStub, index: usize, expected: &B) {
        assert_request_proto(message_bus, index, B::MSG_ID, &expected.to_proto());
    }

    /// Build a text-format `ResponseMessage` for use with
    /// [`MessageBusStub::with_ordered_responses`]. Accepts pipe-delimited
    /// builder output (`encode_pipe()`) or raw NUL-delimited literals.
    pub fn text_response(s: impl Into<String>) -> crate::messages::ResponseMessage {
        crate::messages::ResponseMessage::from(&s.into().replace('|', "\0"))
    }

    /// Build a proto-framed `ResponseMessage` for use with
    /// [`MessageBusStub::with_ordered_responses`]. Pairs with
    /// `Builder::encode_proto()`.
    pub fn proto_response(msg_type: crate::messages::IncomingMessages, bytes: Vec<u8>) -> crate::messages::ResponseMessage {
        crate::messages::ResponseMessage::from_protobuf(msg_type as i32, bytes)
    }

    /// Build a proto-framed wire payload (4-byte BE `msg_id + PROTOBUF_MSG_ID`
    /// followed by `proto.encode_to_vec()`). For `MemoryStream::push_inbound`
    /// and `spawn_handshake_listener` fixtures that need raw bytes, not a
    /// parsed `ResponseMessage`.
    pub fn binary_proto<M: prost::Message>(msg_id: i32, proto: &M) -> Vec<u8> {
        crate::messages::encode_protobuf_message(msg_id, &proto.encode_to_vec())
    }

    /// A message id that maps to no [`IncomingMessages`](crate::messages::IncomingMessages)
    /// variant — what a mis-framed read produces once the length prefix has
    /// slipped. Assertions on the resulting diagnostic name this value, so it
    /// lives here rather than being spelled at each site.
    pub const UNKNOWN_MESSAGE_ID: i32 = 9799;

    /// Proto-framed wire payload whose message id maps to no known kind, for
    /// driving the unroutable-frame reporting. Payload is filler — nothing
    /// decodes it, because nothing can route it.
    pub fn unknown_message_frame() -> Vec<u8> {
        crate::messages::encode_protobuf_message(UNKNOWN_MESSAGE_ID, &[0x08, 0x64])
    }

    /// `NextValidId` proto-framed handshake frame.
    pub fn next_valid_id_frame(order_id: i32) -> Vec<u8> {
        binary_proto(
            crate::messages::IncomingMessages::NextValidId as i32,
            &crate::proto::NextValidId { order_id: Some(order_id) },
        )
    }

    /// `ManagedAccounts` proto-framed handshake frame.
    pub fn managed_accounts_frame(accounts: &str) -> Vec<u8> {
        binary_proto(
            crate::messages::IncomingMessages::ManagedAccounts as i32,
            &crate::proto::ManagedAccounts {
                accounts_list: Some(accounts.to_string()),
            },
        )
    }

    /// Build a `proto::ErrorMessage` envelope with `error_time` and
    /// `advanced_order_reject_json` defaulted (set those fields on the returned
    /// struct when a test needs them). `None` for `request_id` / `code`
    /// expresses a request-less / code-less frame — the fields are optional on
    /// the wire and IB Gateway omits both on informational notices.
    pub fn error_envelope(request_id: Option<i32>, code: Option<i32>, msg: impl Into<String>) -> crate::proto::ErrorMessage {
        crate::proto::ErrorMessage {
            id: request_id,
            error_time: None,
            error_code: code,
            error_msg: Some(msg.into()),
            advanced_order_reject_json: None,
        }
    }

    /// Proto-framed `Error` [`ResponseMessage`](crate::messages::ResponseMessage)
    /// for `MessageBusStub::with_ordered_responses` fixtures.
    pub fn proto_error_response(request_id: i32, code: i32, msg: impl Into<String>) -> crate::messages::ResponseMessage {
        proto_response(
            crate::messages::IncomingMessages::Error,
            prost::Message::encode_to_vec(&error_envelope(Some(request_id), Some(code), msg)),
        )
    }

    /// Proto-framed `Error` wire payload (`[4-byte BE msg_id][proto bytes]`)
    /// for `MemoryStream::push_inbound` / `spawn_handshake_listener` fixtures.
    pub fn error_frame(request_id: i32, code: i32, msg: impl Into<String>) -> Vec<u8> {
        binary_proto(
            crate::messages::IncomingMessages::Error as i32,
            &error_envelope(Some(request_id), Some(code), msg),
        )
    }

    /// Common test constants that can be used across modules
    pub mod constants {
        /// Test account identifiers
        pub const TEST_ACCOUNT: &str = "DU1234567";
        pub const TEST_ACCOUNT_2: &str = "DU7654321";
        pub const TEST_ACCOUNT_3: &str = "DU9876543";

        /// Test model codes
        pub const TEST_MODEL_CODE: &str = "TARGET2024";
        pub const TEST_MODEL_CODE_2: &str = "GROWTH2024";

        /// Test contract IDs
        pub const TEST_CONTRACT_ID: i32 = 1001;
        pub const TEST_CONTRACT_ID_2: i32 = 2002;

        /// Test order IDs
        pub const TEST_ORDER_ID: i32 = 5001;
        pub const TEST_ORDER_ID_2: i32 = 5002;

        /// Test ticker IDs
        pub const TEST_TICKER_ID: i32 = 100;
        pub const TEST_TICKER_ID_2: i32 = 200;

        /// First request_id assigned by `Client::next_request_id()`. Mirrors
        /// `client::id_generator::INITIAL_REQUEST_ID` for assertions in tests
        /// that don't have direct access to that private constant.
        pub const TEST_REQ_ID_FIRST: i32 = 9000;
    }

    /// Re-export constants at module level for easier access
    pub use constants::*;

    /// Asserts the first 4 bytes of a protobuf-encoded message match the expected OutgoingMessages variant + 200 offset.
    pub fn assert_proto_msg_id(bytes: &[u8], expected: crate::messages::OutgoingMessages) {
        let msg_id = i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
        assert_eq!(msg_id, expected as i32 + 200);
    }

    /// Counts how many messages in `messages` carry the given protobuf message id (variant + 200 offset).
    pub fn count_proto_msgs(messages: &[Vec<u8>], expected: crate::messages::OutgoingMessages) -> usize {
        let target = expected as i32 + 200;
        messages
            .iter()
            .filter(|m| m.len() >= 4 && i32::from_be_bytes([m[0], m[1], m[2], m[3]]) == target)
            .count()
    }

    /// Builds an `Error::Notice` carrying a synthesized [`Notice`](crate::messages::Notice)
    /// — no wire timestamp, no advanced-order-reject JSON. Test-only sugar for the
    /// `Error::Notice(Notice::synthesized(code, msg))` shape used by Result-path tests
    /// (production code never builds these; the wire path goes through
    /// `From<ResponseMessage> for Error`).
    pub fn tws_error_notice(code: i32, message: impl Into<String>) -> crate::Error {
        crate::Error::Notice(crate::messages::Notice::synthesized(code, message.into()))
    }

    /// Asserts that `err` is `Error::Notice(notice)` where `notice.code == expected_code`
    /// and `notice.message` contains `expected_substring`.
    pub fn assert_tws_error_message(err: crate::Error, expected_code: i32, expected_substring: &str) {
        match err {
            crate::Error::Notice(notice) => {
                assert_eq!(notice.code, expected_code, "wrong error code");
                assert!(
                    notice.message.contains(expected_substring),
                    "error message {:?} does not contain {expected_substring:?}",
                    notice.message
                );
            }
            other => panic!("expected Error::Notice(code={expected_code}), got {other:?}"),
        }
    }

    /// Asserts that a decoder rejected a malformed decimal wire field, naming the
    /// offending value in the error.
    ///
    /// Used by the per-decoder "is this field wired to `parse_optional_decimal`"
    /// tests. The helper's own semantics are covered exhaustively in
    /// `src/proto/decoders_tests.rs`; these call sites only prove the wiring, so
    /// they all want this one assertion rather than their own `matches!`.
    pub fn assert_decimal_parse_error<T: std::fmt::Debug>(result: Result<T, crate::Error>, offending_value: &str) {
        match result {
            Err(crate::Error::Parse(_, value, msg)) => {
                assert_eq!(value, offending_value, "error should carry the offending wire value");
                assert!(msg.contains("invalid decimal wire value"), "unexpected message: {msg}");
            }
            other => panic!("expected Error::Parse for {offending_value:?}, got {other:?}"),
        }
    }

    /// Asserts that a proto-only decoder rejects a text-framed frame of the type
    /// it handles, with [`Error::UnexpectedWireFormat`](crate::Error::UnexpectedWireFormat).
    ///
    /// One assertion for every `*_rejects_text_framing` test. The 22 of them
    /// spelled it four ways (`expect_err` + `matches!`, `unwrap_err` + `matches!`,
    /// a `match` with `panic!`, and three different panic messages), which is why
    /// #731's rename of a single variant produced ~600 test-side lines.
    ///
    /// `expected` is checked against the frame's own leading discriminant before
    /// the decoder runs. That is not ceremony: `require_proto` fails on framing
    /// without reading the type, so a fixture naming the wrong message id passes
    /// the assertion anyway — #738 found exactly that in four fixtures (`87` for
    /// `MarketRule`, which is 93; a literal `"newsProviders"` that parses as no
    /// discriminant at all). A fixture field no assertion depends on will be
    /// wrong eventually.
    pub fn assert_rejects_text_framing<T: std::fmt::Debug>(
        expected: crate::messages::IncomingMessages,
        text_frame: &str,
        decode: impl FnOnce(&crate::messages::ResponseMessage) -> Result<T, crate::Error>,
    ) {
        let message = crate::messages::ResponseMessage::from(text_frame);
        assert_eq!(
            message.message_type(),
            expected,
            "fixture is framed as the wrong message type; the decoder never reads it"
        );

        match decode(&message) {
            Err(crate::Error::UnexpectedWireFormat(_)) => {}
            other => panic!("expected Error::UnexpectedWireFormat for a text-framed {expected:?}, got {other:?}"),
        }
    }
}

/// Generic round-trip / reject-unknown helpers for typed wire enums built with `impl_wire_enum!`.
#[cfg(test)]
#[allow(dead_code)] // Consumers grow as the typed-status sweep lands.
pub mod wire_enum {
    /// Assert `Display`, `FromStr`, and `ToField` agree on a hand-written
    /// `(variant, wire)` table. One helper covers every trait impl generated
    /// by `impl_wire_enum!` — independent verification (the table is not
    /// derived from `as_str()`, so a typo in either direction surfaces).
    pub fn check_wire_enum_round_trip<T>(table: &[(T, &'static str)])
    where
        T: std::fmt::Display + std::fmt::Debug + PartialEq + std::str::FromStr<Err = crate::Error> + crate::ToField,
    {
        for (variant, wire) in table {
            assert_eq!(variant.to_string(), *wire, "Display for {variant:?}");
            assert_eq!(&T::from_str(wire).unwrap(), variant, "FromStr({wire})");
            assert_eq!(variant.to_field(), *wire, "ToField for {variant:?}");
        }
    }

    /// Assert every input string in `unknowns` produces `Err(Error::Parse(..))`.
    pub fn check_wire_enum_rejects_unknown<T>(unknowns: &[&str])
    where
        T: std::str::FromStr<Err = crate::Error> + std::fmt::Debug,
    {
        for &s in unknowns {
            let err = T::from_str(s);
            assert!(
                matches!(err, Err(crate::Error::Parse(_, _, _))),
                "expected Parse error for {s:?}, got {err:?}",
            );
        }
    }
}

/// Walking the crate's own source, for the gates that check a hand-listed roster
/// against the tree.
#[cfg(test)]
pub mod source_scan {
    use std::path::Path;

    /// Hand every production `.rs` file under `src/` to `visit` as
    /// `(path, contents)`.
    ///
    /// **What counts as production source is defined here and nowhere else.**
    /// `tests.rs` and `*_tests.rs` are skipped: they hold test-only decoders and
    /// fixtures that exist to exercise the drivers, not to decode a wire
    /// message, and a roster gate that counted them would report failures no
    /// production change could fix. `response_message_ids_tests` is the one gate
    /// that depends on this definition, since #749 replaced the other — the
    /// one-shot pairing roster — with a trait the compiler enumerates.
    ///
    /// Takes a visitor rather than returning a `Vec` so a caller looking for
    /// several things reads each file once.
    pub fn visit_production_sources(visit: &mut impl FnMut(&Path, &str)) {
        visit_dir(&Path::new(env!("CARGO_MANIFEST_DIR")).join("src"), visit);
    }

    fn visit_dir(dir: &Path, visit: &mut impl FnMut(&Path, &str)) {
        let entries = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read_dir {}: {e}", dir.display()));

        for entry in entries.flatten() {
            let path = entry.path();
            // `file_type()` reuses what `readdir` already returned; `is_dir()`
            // would re-`stat` every entry.
            if entry.file_type().is_ok_and(|t| t.is_dir()) {
                visit_dir(&path, visit);
                continue;
            }

            let name = path.file_name().and_then(|n| n.to_str()).unwrap_or_default();
            if !name.ends_with(".rs") || name == "tests.rs" || name.ends_with("_tests.rs") {
                continue;
            }

            let contents = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
            visit(&path, &contents);
        }
    }
}

#[cfg(test)]
#[path = "test_utils_tests.rs"]
mod tests;