ibapi/
messages.rs

1//! Message encoding, decoding, and routing for TWS API communication.
2//!
3//! This module handles the low-level message protocol between the client and TWS,
4//! including request/response message formatting, field encoding/decoding,
5//! and message type definitions.
6
7use std::fmt::Display;
8use std::io::Write;
9use std::ops::Index;
10use std::str::{self, FromStr};
11
12use byteorder::{BigEndian, WriteBytesExt};
13
14use log::debug;
15use serde::{Deserialize, Serialize};
16use time::OffsetDateTime;
17
18use crate::{Error, ToField};
19
20pub mod parser_registry;
21pub(crate) mod shared_channel_configuration;
22#[cfg(test)]
23mod tests;
24
25#[cfg(test)]
26mod from_str_tests {
27    use super::*;
28    use std::str::FromStr;
29
30    #[test]
31    fn test_outgoing_messages_from_str() {
32        // Test some common message types
33        assert_eq!(OutgoingMessages::from_str("1").unwrap(), OutgoingMessages::RequestMarketData);
34        assert_eq!(OutgoingMessages::from_str("17").unwrap(), OutgoingMessages::RequestManagedAccounts);
35        assert_eq!(OutgoingMessages::from_str("49").unwrap(), OutgoingMessages::RequestCurrentTime);
36        assert_eq!(OutgoingMessages::from_str("61").unwrap(), OutgoingMessages::RequestPositions);
37
38        // Test error cases
39        assert!(OutgoingMessages::from_str("999").is_err());
40        assert!(OutgoingMessages::from_str("abc").is_err());
41        assert!(OutgoingMessages::from_str("").is_err());
42    }
43
44    #[test]
45    fn test_outgoing_messages_roundtrip() {
46        // Test that we can convert to string and back
47        let msg = OutgoingMessages::RequestCurrentTime;
48        let as_string = msg.to_string();
49        let parsed = OutgoingMessages::from_str(&as_string).unwrap();
50        assert_eq!(parsed, OutgoingMessages::RequestCurrentTime);
51
52        // Test with another message type
53        let msg = OutgoingMessages::RequestManagedAccounts;
54        let as_string = msg.to_string();
55        let parsed = OutgoingMessages::from_str(&as_string).unwrap();
56        assert_eq!(parsed, OutgoingMessages::RequestManagedAccounts);
57    }
58
59    #[test]
60    fn test_incoming_messages_from_str() {
61        // Test some common message types
62        assert_eq!(IncomingMessages::from_str("4").unwrap(), IncomingMessages::Error);
63        assert_eq!(IncomingMessages::from_str("15").unwrap(), IncomingMessages::ManagedAccounts);
64        assert_eq!(IncomingMessages::from_str("49").unwrap(), IncomingMessages::CurrentTime);
65        assert_eq!(IncomingMessages::from_str("61").unwrap(), IncomingMessages::Position);
66
67        // Test NotValid for unknown values
68        assert_eq!(IncomingMessages::from_str("999").unwrap(), IncomingMessages::NotValid);
69        assert_eq!(IncomingMessages::from_str("0").unwrap(), IncomingMessages::NotValid);
70        assert_eq!(IncomingMessages::from_str("-1").unwrap(), IncomingMessages::NotValid);
71
72        // Test error cases for non-numeric strings
73        assert!(IncomingMessages::from_str("abc").is_err());
74        assert!(IncomingMessages::from_str("").is_err());
75        assert!(IncomingMessages::from_str("1.5").is_err());
76    }
77
78    #[test]
79    fn test_incoming_messages_roundtrip() {
80        // Test with CurrentTime message
81        let n = 49;
82        let msg = IncomingMessages::from(n);
83        let as_string = n.to_string();
84        let parsed = IncomingMessages::from_str(&as_string).unwrap();
85        assert_eq!(parsed, msg);
86
87        // Test with ManagedAccounts message
88        let n = 15;
89        let msg = IncomingMessages::from(n);
90        let as_string = n.to_string();
91        let parsed = IncomingMessages::from_str(&as_string).unwrap();
92        assert_eq!(parsed, msg);
93
94        // Test with NotValid (unknown value)
95        let n = 999;
96        let msg = IncomingMessages::from(n);
97        let as_string = n.to_string();
98        let parsed = IncomingMessages::from_str(&as_string).unwrap();
99        assert_eq!(parsed, msg);
100        assert_eq!(parsed, IncomingMessages::NotValid);
101    }
102}
103
104const INFINITY_STR: &str = "Infinity";
105const UNSET_DOUBLE: &str = "1.7976931348623157E308";
106const UNSET_INTEGER: &str = "2147483647";
107const UNSET_LONG: &str = "9223372036854775807";
108
109// Index of message text in the response message
110pub(crate) const MESSAGE_INDEX: usize = 4;
111// Index of message code in the response message
112pub(crate) const CODE_INDEX: usize = 3;
113
114/// Messages emitted by TWS/Gateway over the market data socket.
115#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
116pub enum IncomingMessages {
117    /// Gateway initiated shutdown.
118    Shutdown = -2,
119    /// Unknown or unsupported message id.
120    NotValid = -1,
121    /// Tick price update.
122    TickPrice = 1,
123    /// Tick size update.
124    TickSize = 2,
125    /// Order status update.
126    OrderStatus = 3,
127    /// Error (includes request id and code).
128    Error = 4,
129    /// Open order description.
130    OpenOrder = 5,
131    /// Account value key/value pair.
132    AccountValue = 6,
133    /// Portfolio value line.
134    PortfolioValue = 7,
135    /// Account update timestamp.
136    AccountUpdateTime = 8,
137    /// Next valid order id notification.
138    NextValidId = 9,
139    /// Contract details payload.
140    ContractData = 10,
141    /// Execution data update.
142    ExecutionData = 11,
143    /// Level 1 market depth row update.
144    MarketDepth = 12,
145    /// Level 2 market depth row update.
146    MarketDepthL2 = 13,
147    /// News bulletin broadcast.
148    NewsBulletins = 14,
149    /// List of managed accounts.
150    ManagedAccounts = 15,
151    /// Financial advisor configuration data.
152    ReceiveFA = 16,
153    /// Historical bar data payload.
154    HistoricalData = 17,
155    /// Bond contract details payload.
156    BondContractData = 18,
157    /// Scanner parameter definitions.
158    ScannerParameters = 19,
159    /// Scanner subscription results.
160    ScannerData = 20,
161    /// Option computation tick.
162    TickOptionComputation = 21,
163    /// Generic numeric tick (e.g. implied volatility).
164    TickGeneric = 45,
165    /// String-valued tick (exchange names, etc.).
166    TickString = 46,
167    /// Exchange for Physical tick update.
168    TickEFP = 47, //TICK EFP 47
169    /// Current world clock time.
170    CurrentTime = 49,
171    /// Real-time bars update.
172    RealTimeBars = 50,
173    /// Fundamental data response.
174    FundamentalData = 51,
175    /// End marker for contract details batches.
176    ContractDataEnd = 52,
177    /// End marker for open order batches.
178    OpenOrderEnd = 53,
179    /// End marker for account download.
180    AccountDownloadEnd = 54,
181    /// End marker for execution data.
182    ExecutionDataEnd = 55,
183    /// Delta-neutral validation response.
184    DeltaNeutralValidation = 56,
185    /// End of tick snapshot.
186    TickSnapshotEnd = 57,
187    /// Market data type acknowledgment.
188    MarketDataType = 58,
189    /// Commissions report payload.
190    CommissionsReport = 59,
191    /// Position update.
192    Position = 61,
193    /// End marker for position updates.
194    PositionEnd = 62,
195    /// Account summary update.
196    AccountSummary = 63,
197    /// End marker for account summary stream.
198    AccountSummaryEnd = 64,
199    /// API verification challenge.
200    VerifyMessageApi = 65,
201    /// API verification completion.
202    VerifyCompleted = 66,
203    /// Display group list response.
204    DisplayGroupList = 67,
205    /// Display group update.
206    DisplayGroupUpdated = 68,
207    /// Auth + verification challenge.
208    VerifyAndAuthMessageApi = 69,
209    /// Auth + verification completion.
210    VerifyAndAuthCompleted = 70,
211    /// Multi-account position update.
212    PositionMulti = 71,
213    /// End marker for multi-account position stream.
214    PositionMultiEnd = 72,
215    /// Multi-account account update.
216    AccountUpdateMulti = 73,
217    /// End marker for multi-account account stream.
218    AccountUpdateMultiEnd = 74,
219    /// Option security definition parameters.
220    SecurityDefinitionOptionParameter = 75,
221    /// End marker for option security definition stream.
222    SecurityDefinitionOptionParameterEnd = 76,
223    /// Soft dollar tier information.
224    SoftDollarTier = 77,
225    /// Family code response.
226    FamilyCodes = 78,
227    /// Matching symbol samples.
228    SymbolSamples = 79,
229    /// Exchanges offering market depth.
230    MktDepthExchanges = 80,
231    /// Tick request parameter info.
232    TickReqParams = 81,
233    /// Smart component routing map.
234    SmartComponents = 82,
235    /// News article content.
236    NewsArticle = 83,
237    /// News headline tick.
238    TickNews = 84,
239    /// Available news providers.
240    NewsProviders = 85,
241    /// Historical news headlines.
242    HistoricalNews = 86,
243    /// End marker for historical news.
244    HistoricalNewsEnd = 87,
245    /// Head timestamp for historical data.
246    HeadTimestamp = 88,
247    /// Histogram data response.
248    HistogramData = 89,
249    /// Streaming historical data update.
250    HistoricalDataUpdate = 90,
251    /// Market data request reroute notice.
252    RerouteMktDataReq = 91,
253    /// Market depth request reroute notice.
254    RerouteMktDepthReq = 92,
255    /// Market rule response.
256    MarketRule = 93,
257    /// Account PnL update.
258    PnL = 94,
259    /// Single position PnL update.
260    PnLSingle = 95,
261    /// Historical tick data (midpoint).
262    HistoricalTick = 96,
263    /// Historical tick data (bid/ask).
264    HistoricalTickBidAsk = 97,
265    /// Historical tick data (trades).
266    HistoricalTickLast = 98,
267    /// Tick-by-tick streaming data.
268    TickByTick = 99,
269    /// Order bound notification for API multiple endpoints.
270    OrderBound = 100,
271    /// Completed order information.
272    CompletedOrder = 101,
273    /// End marker for completed orders.
274    CompletedOrdersEnd = 102,
275    /// End marker for FA profile replacement.
276    ReplaceFAEnd = 103,
277    /// Wall Street Horizon metadata update.
278    WshMetaData = 104,
279    /// Wall Street Horizon event payload.
280    WshEventData = 105,
281    /// Historical schedule response.
282    HistoricalSchedule = 106,
283    /// User information response.
284    UserInfo = 107,
285}
286
287impl From<i32> for IncomingMessages {
288    fn from(value: i32) -> IncomingMessages {
289        match value {
290            -2 => IncomingMessages::Shutdown,
291            1 => IncomingMessages::TickPrice,
292            2 => IncomingMessages::TickSize,
293            3 => IncomingMessages::OrderStatus,
294            4 => IncomingMessages::Error,
295            5 => IncomingMessages::OpenOrder,
296            6 => IncomingMessages::AccountValue,
297            7 => IncomingMessages::PortfolioValue,
298            8 => IncomingMessages::AccountUpdateTime,
299            9 => IncomingMessages::NextValidId,
300            10 => IncomingMessages::ContractData,
301            11 => IncomingMessages::ExecutionData,
302            12 => IncomingMessages::MarketDepth,
303            13 => IncomingMessages::MarketDepthL2,
304            14 => IncomingMessages::NewsBulletins,
305            15 => IncomingMessages::ManagedAccounts,
306            16 => IncomingMessages::ReceiveFA,
307            17 => IncomingMessages::HistoricalData,
308            18 => IncomingMessages::BondContractData,
309            19 => IncomingMessages::ScannerParameters,
310            20 => IncomingMessages::ScannerData,
311            21 => IncomingMessages::TickOptionComputation,
312            45 => IncomingMessages::TickGeneric,
313            46 => IncomingMessages::TickString,
314            47 => IncomingMessages::TickEFP, //TICK EFP 47
315            49 => IncomingMessages::CurrentTime,
316            50 => IncomingMessages::RealTimeBars,
317            51 => IncomingMessages::FundamentalData,
318            52 => IncomingMessages::ContractDataEnd,
319            53 => IncomingMessages::OpenOrderEnd,
320            54 => IncomingMessages::AccountDownloadEnd,
321            55 => IncomingMessages::ExecutionDataEnd,
322            56 => IncomingMessages::DeltaNeutralValidation,
323            57 => IncomingMessages::TickSnapshotEnd,
324            58 => IncomingMessages::MarketDataType,
325            59 => IncomingMessages::CommissionsReport,
326            61 => IncomingMessages::Position,
327            62 => IncomingMessages::PositionEnd,
328            63 => IncomingMessages::AccountSummary,
329            64 => IncomingMessages::AccountSummaryEnd,
330            65 => IncomingMessages::VerifyMessageApi,
331            66 => IncomingMessages::VerifyCompleted,
332            67 => IncomingMessages::DisplayGroupList,
333            68 => IncomingMessages::DisplayGroupUpdated,
334            69 => IncomingMessages::VerifyAndAuthMessageApi,
335            70 => IncomingMessages::VerifyAndAuthCompleted,
336            71 => IncomingMessages::PositionMulti,
337            72 => IncomingMessages::PositionMultiEnd,
338            73 => IncomingMessages::AccountUpdateMulti,
339            74 => IncomingMessages::AccountUpdateMultiEnd,
340            75 => IncomingMessages::SecurityDefinitionOptionParameter,
341            76 => IncomingMessages::SecurityDefinitionOptionParameterEnd,
342            77 => IncomingMessages::SoftDollarTier,
343            78 => IncomingMessages::FamilyCodes,
344            79 => IncomingMessages::SymbolSamples,
345            80 => IncomingMessages::MktDepthExchanges,
346            81 => IncomingMessages::TickReqParams,
347            82 => IncomingMessages::SmartComponents,
348            83 => IncomingMessages::NewsArticle,
349            84 => IncomingMessages::TickNews,
350            85 => IncomingMessages::NewsProviders,
351            86 => IncomingMessages::HistoricalNews,
352            87 => IncomingMessages::HistoricalNewsEnd,
353            88 => IncomingMessages::HeadTimestamp,
354            89 => IncomingMessages::HistogramData,
355            90 => IncomingMessages::HistoricalDataUpdate,
356            91 => IncomingMessages::RerouteMktDataReq,
357            92 => IncomingMessages::RerouteMktDepthReq,
358            93 => IncomingMessages::MarketRule,
359            94 => IncomingMessages::PnL,
360            95 => IncomingMessages::PnLSingle,
361            96 => IncomingMessages::HistoricalTick,
362            97 => IncomingMessages::HistoricalTickBidAsk,
363            98 => IncomingMessages::HistoricalTickLast,
364            99 => IncomingMessages::TickByTick,
365            100 => IncomingMessages::OrderBound,
366            101 => IncomingMessages::CompletedOrder,
367            102 => IncomingMessages::CompletedOrdersEnd,
368            103 => IncomingMessages::ReplaceFAEnd,
369            104 => IncomingMessages::WshMetaData,
370            105 => IncomingMessages::WshEventData,
371            106 => IncomingMessages::HistoricalSchedule,
372            107 => IncomingMessages::UserInfo,
373            _ => IncomingMessages::NotValid,
374        }
375    }
376}
377
378impl FromStr for IncomingMessages {
379    type Err = Error;
380
381    fn from_str(s: &str) -> Result<Self, Self::Err> {
382        match s.parse::<i32>() {
383            Ok(n) => Ok(IncomingMessages::from(n)),
384            Err(_) => Err(Error::Simple(format!("Invalid incoming message type: {}", s))),
385        }
386    }
387}
388
389/// Return the message field index containing the order id, if present.
390pub fn order_id_index(kind: IncomingMessages) -> Option<usize> {
391    match kind {
392        IncomingMessages::OpenOrder | IncomingMessages::OrderStatus => Some(1),
393        IncomingMessages::ExecutionData | IncomingMessages::ExecutionDataEnd => Some(2),
394        _ => None,
395    }
396}
397
398/// Return the message field index containing the request id, if present.
399pub fn request_id_index(kind: IncomingMessages) -> Option<usize> {
400    match kind {
401        IncomingMessages::AccountSummary => Some(2),
402        IncomingMessages::AccountSummaryEnd => Some(2),
403        IncomingMessages::AccountUpdateMulti => Some(2),
404        IncomingMessages::AccountUpdateMultiEnd => Some(2),
405        IncomingMessages::ContractData => Some(1),
406        IncomingMessages::ContractDataEnd => Some(2),
407        IncomingMessages::Error => Some(2),
408        IncomingMessages::ExecutionData => Some(1),
409        IncomingMessages::ExecutionDataEnd => Some(2),
410        IncomingMessages::HeadTimestamp => Some(1),
411        IncomingMessages::HistogramData => Some(1),
412        IncomingMessages::HistoricalData => Some(1),
413        IncomingMessages::HistoricalNews => Some(1),
414        IncomingMessages::HistoricalNewsEnd => Some(1),
415        IncomingMessages::HistoricalSchedule => Some(1),
416        IncomingMessages::HistoricalTick => Some(1),
417        IncomingMessages::HistoricalTickBidAsk => Some(1),
418        IncomingMessages::HistoricalTickLast => Some(1),
419        IncomingMessages::MarketDepth => Some(2),
420        IncomingMessages::MarketDepthL2 => Some(2),
421        IncomingMessages::NewsArticle => Some(1),
422        IncomingMessages::OpenOrder => Some(1),
423        IncomingMessages::PnL => Some(1),
424        IncomingMessages::PnLSingle => Some(1),
425        IncomingMessages::PositionMulti => Some(2),
426        IncomingMessages::PositionMultiEnd => Some(2),
427        IncomingMessages::RealTimeBars => Some(2),
428        IncomingMessages::ScannerData => Some(2),
429        IncomingMessages::SecurityDefinitionOptionParameter => Some(1),
430        IncomingMessages::SecurityDefinitionOptionParameterEnd => Some(1),
431        IncomingMessages::SymbolSamples => Some(1),
432        IncomingMessages::TickByTick => Some(1),
433        IncomingMessages::TickEFP => Some(2),
434        IncomingMessages::TickGeneric => Some(2),
435        IncomingMessages::TickNews => Some(1),
436        IncomingMessages::TickOptionComputation => Some(1),
437        IncomingMessages::TickPrice => Some(2),
438        IncomingMessages::TickReqParams => Some(1),
439        IncomingMessages::TickSize => Some(2),
440        IncomingMessages::TickSnapshotEnd => Some(2),
441        IncomingMessages::TickString => Some(2),
442        IncomingMessages::WshEventData => Some(1),
443        IncomingMessages::WshMetaData => Some(1),
444
445        _ => {
446            debug!("could not determine request id index for {kind:?} (this message type may not have a request id).");
447            None
448        }
449    }
450}
451
452/// Outgoing message opcodes understood by TWS/Gateway.
453#[allow(dead_code)]
454#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
455pub enum OutgoingMessages {
456    /// Request streaming market data (`reqMktData`).
457    RequestMarketData = 1,
458    /// Cancel streaming market data (`cancelMktData`).
459    CancelMarketData = 2,
460    /// Submit a new order (`placeOrder`).
461    PlaceOrder = 3,
462    /// Cancel an existing order (`cancelOrder`).
463    CancelOrder = 4,
464    /// Request the current open orders (`reqOpenOrders`).
465    RequestOpenOrders = 5,
466    /// Request account value updates (`reqAccountUpdates`).
467    RequestAccountData = 6,
468    /// Request execution reports (`reqExecutions`).
469    RequestExecutions = 7,
470    /// Request a block of valid order ids (`reqIds`).
471    RequestIds = 8,
472    /// Request contract details (`reqContractDetails`).
473    RequestContractData = 9,
474    /// Request level-two market depth (`reqMktDepth`).
475    RequestMarketDepth = 10,
476    /// Cancel level-two market depth (`cancelMktDepth`).
477    CancelMarketDepth = 11,
478    /// Subscribe to news bulletins (`reqNewsBulletins`).
479    RequestNewsBulletins = 12,
480    /// Cancel news bulletin subscription (`cancelNewsBulletins`).
481    CancelNewsBulletin = 13,
482    /// Change the server log level (`setServerLogLevel`).
483    ChangeServerLog = 14,
484    /// Request auto-open orders (`reqAutoOpenOrders`).
485    RequestAutoOpenOrders = 15,
486    /// Request all open orders (`reqAllOpenOrders`).
487    RequestAllOpenOrders = 16,
488    /// Request managed accounts list (`reqManagedAccts`).
489    RequestManagedAccounts = 17,
490    /// Request financial advisor configuration (`requestFA`).
491    RequestFA = 18,
492    /// Replace financial advisor configuration (`replaceFA`).
493    ReplaceFA = 19,
494    /// Request historical bar data (`reqHistoricalData`).
495    RequestHistoricalData = 20,
496    /// Exercise an option contract (`exerciseOptions`).
497    ExerciseOptions = 21,
498    /// Subscribe to a market scanner (`reqScannerSubscription`).
499    RequestScannerSubscription = 22,
500    /// Cancel a market scanner subscription (`cancelScannerSubscription`).
501    CancelScannerSubscription = 23,
502    /// Request scanner parameter definitions (`reqScannerParameters`).
503    RequestScannerParameters = 24,
504    /// Cancel an in-flight historical data request (`cancelHistoricalData`).
505    CancelHistoricalData = 25,
506    /// Request the current TWS/Gateway time (`reqCurrentTime`).
507    RequestCurrentTime = 49,
508    /// Request real-time bars (`reqRealTimeBars`).
509    RequestRealTimeBars = 50,
510    /// Cancel real-time bars (`cancelRealTimeBars`).
511    CancelRealTimeBars = 51,
512    /// Request fundamental data (`reqFundamentalData`).
513    RequestFundamentalData = 52,
514    /// Cancel fundamental data (`cancelFundamentalData`).
515    CancelFundamentalData = 53,
516    /// Request implied volatility calculation (`calculateImpliedVolatility`).
517    ReqCalcImpliedVolat = 54,
518    /// Request option price calculation (`calculateOptionPrice`).
519    ReqCalcOptionPrice = 55,
520    /// Cancel implied volatility calculation (`cancelImpliedVolatility`).
521    CancelImpliedVolatility = 56,
522    /// Cancel option price calculation (`cancelCalculateOptionPrice`).
523    CancelOptionPrice = 57,
524    /// Issue a global cancel request (`reqGlobalCancel`).
525    RequestGlobalCancel = 58,
526    /// Change the active market data type (`reqMarketDataType`).
527    RequestMarketDataType = 59,
528    /// Subscribe to position updates (`reqPositions`).
529    RequestPositions = 61,
530    /// Subscribe to account summary (`reqAccountSummary`).
531    RequestAccountSummary = 62,
532    /// Cancel account summary subscription (`cancelAccountSummary`).
533    CancelAccountSummary = 63,
534    /// Cancel position subscription (`cancelPositions`).
535    CancelPositions = 64,
536    /// Begin API verification handshake (`verifyRequest`).
537    VerifyRequest = 65,
538    /// Respond to verification handshake (`verifyMessage`).
539    VerifyMessage = 66,
540    /// Query display groups (`queryDisplayGroups`).
541    QueryDisplayGroups = 67,
542    /// Subscribe to display group events (`subscribeToGroupEvents`).
543    SubscribeToGroupEvents = 68,
544    /// Update a display group subscription (`updateDisplayGroup`).
545    UpdateDisplayGroup = 69,
546    /// Unsubscribe from display group events (`unsubscribeFromGroupEvents`).
547    UnsubscribeFromGroupEvents = 70,
548    /// Start the API session (`startApi`).
549    StartApi = 71,
550    /// Verification handshake with auth (`verifyAndAuthRequest`).
551    VerifyAndAuthRequest = 72,
552    /// Verification message with auth (`verifyAndAuthMessage`).
553    VerifyAndAuthMessage = 73,
554    /// Request multi-account/model positions (`reqPositionsMulti`).
555    RequestPositionsMulti = 74,
556    /// Cancel multi-account/model positions (`cancelPositionsMulti`).
557    CancelPositionsMulti = 75,
558    /// Request multi-account/model updates (`reqAccountUpdatesMulti`).
559    RequestAccountUpdatesMulti = 76,
560    /// Cancel multi-account/model updates (`cancelAccountUpdatesMulti`).
561    CancelAccountUpdatesMulti = 77,
562    /// Request optional option security parameters (`reqSecDefOptParams`).
563    RequestSecurityDefinitionOptionalParameters = 78,
564    /// Request soft-dollar tier definitions (`reqSoftDollarTiers`).
565    RequestSoftDollarTiers = 79,
566    /// Request family codes (`reqFamilyCodes`).
567    RequestFamilyCodes = 80,
568    /// Request matching symbols (`reqMatchingSymbols`).
569    RequestMatchingSymbols = 81,
570    /// Request exchanges that support depth (`reqMktDepthExchanges`).
571    RequestMktDepthExchanges = 82,
572    /// Request smart routing component map (`reqSmartComponents`).
573    RequestSmartComponents = 83,
574    /// Request detailed news article (`reqNewsArticle`).
575    RequestNewsArticle = 84,
576    /// Request available news providers (`reqNewsProviders`).
577    RequestNewsProviders = 85,
578    /// Request historical news headlines (`reqHistoricalNews`).
579    RequestHistoricalNews = 86,
580    /// Request earliest timestamp for historical data (`reqHeadTimestamp`).
581    RequestHeadTimestamp = 87,
582    /// Request histogram snapshot (`reqHistogramData`).
583    RequestHistogramData = 88,
584    /// Cancel histogram snapshot (`cancelHistogramData`).
585    CancelHistogramData = 89,
586    /// Cancel head timestamp request (`cancelHeadTimestamp`).
587    CancelHeadTimestamp = 90,
588    /// Request market rule definition (`reqMarketRule`).
589    RequestMarketRule = 91,
590    /// Request account-wide PnL stream (`reqPnL`).
591    RequestPnL = 92,
592    /// Cancel account-wide PnL stream (`cancelPnL`).
593    CancelPnL = 93,
594    /// Request single-position PnL stream (`reqPnLSingle`).
595    RequestPnLSingle = 94,
596    /// Cancel single-position PnL stream (`cancelPnLSingle`).
597    CancelPnLSingle = 95,
598    /// Request historical tick data (`reqHistoricalTicks`).
599    RequestHistoricalTicks = 96,
600    /// Request tick-by-tick data (`reqTickByTickData`).
601    RequestTickByTickData = 97,
602    /// Cancel tick-by-tick data (`cancelTickByTickData`).
603    CancelTickByTickData = 98,
604    /// Request completed order history (`reqCompletedOrders`).
605    RequestCompletedOrders = 99,
606    /// Request Wall Street Horizon metadata (`reqWshMetaData`).
607    RequestWshMetaData = 100,
608    /// Cancel Wall Street Horizon metadata (`cancelWshMetaData`).
609    CancelWshMetaData = 101,
610    /// Request Wall Street Horizon event data (`reqWshEventData`).
611    RequestWshEventData = 102,
612    /// Cancel Wall Street Horizon event data (`cancelWshEventData`).
613    CancelWshEventData = 103,
614    /// Request user information (`reqUserInfo`).
615    RequestUserInfo = 104,
616}
617
618impl ToField for OutgoingMessages {
619    fn to_field(&self) -> String {
620        (*self as i32).to_string()
621    }
622}
623
624impl std::fmt::Display for OutgoingMessages {
625    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
626        write!(f, "{}", *self as i32)
627    }
628}
629
630impl FromStr for OutgoingMessages {
631    type Err = Error;
632
633    fn from_str(s: &str) -> Result<Self, Self::Err> {
634        match s.parse::<i32>() {
635            Ok(1) => Ok(OutgoingMessages::RequestMarketData),
636            Ok(2) => Ok(OutgoingMessages::CancelMarketData),
637            Ok(3) => Ok(OutgoingMessages::PlaceOrder),
638            Ok(4) => Ok(OutgoingMessages::CancelOrder),
639            Ok(5) => Ok(OutgoingMessages::RequestOpenOrders),
640            Ok(6) => Ok(OutgoingMessages::RequestAccountData),
641            Ok(7) => Ok(OutgoingMessages::RequestExecutions),
642            Ok(8) => Ok(OutgoingMessages::RequestIds),
643            Ok(9) => Ok(OutgoingMessages::RequestContractData),
644            Ok(10) => Ok(OutgoingMessages::RequestMarketDepth),
645            Ok(11) => Ok(OutgoingMessages::CancelMarketDepth),
646            Ok(12) => Ok(OutgoingMessages::RequestNewsBulletins),
647            Ok(13) => Ok(OutgoingMessages::CancelNewsBulletin),
648            Ok(14) => Ok(OutgoingMessages::ChangeServerLog),
649            Ok(15) => Ok(OutgoingMessages::RequestAutoOpenOrders),
650            Ok(16) => Ok(OutgoingMessages::RequestAllOpenOrders),
651            Ok(17) => Ok(OutgoingMessages::RequestManagedAccounts),
652            Ok(18) => Ok(OutgoingMessages::RequestFA),
653            Ok(19) => Ok(OutgoingMessages::ReplaceFA),
654            Ok(20) => Ok(OutgoingMessages::RequestHistoricalData),
655            Ok(21) => Ok(OutgoingMessages::ExerciseOptions),
656            Ok(22) => Ok(OutgoingMessages::RequestScannerSubscription),
657            Ok(23) => Ok(OutgoingMessages::CancelScannerSubscription),
658            Ok(24) => Ok(OutgoingMessages::RequestScannerParameters),
659            Ok(25) => Ok(OutgoingMessages::CancelHistoricalData),
660            Ok(49) => Ok(OutgoingMessages::RequestCurrentTime),
661            Ok(50) => Ok(OutgoingMessages::RequestRealTimeBars),
662            Ok(51) => Ok(OutgoingMessages::CancelRealTimeBars),
663            Ok(52) => Ok(OutgoingMessages::RequestFundamentalData),
664            Ok(53) => Ok(OutgoingMessages::CancelFundamentalData),
665            Ok(54) => Ok(OutgoingMessages::ReqCalcImpliedVolat),
666            Ok(55) => Ok(OutgoingMessages::ReqCalcOptionPrice),
667            Ok(56) => Ok(OutgoingMessages::CancelImpliedVolatility),
668            Ok(57) => Ok(OutgoingMessages::CancelOptionPrice),
669            Ok(58) => Ok(OutgoingMessages::RequestGlobalCancel),
670            Ok(59) => Ok(OutgoingMessages::RequestMarketDataType),
671            Ok(61) => Ok(OutgoingMessages::RequestPositions),
672            Ok(62) => Ok(OutgoingMessages::RequestAccountSummary),
673            Ok(63) => Ok(OutgoingMessages::CancelAccountSummary),
674            Ok(64) => Ok(OutgoingMessages::CancelPositions),
675            Ok(65) => Ok(OutgoingMessages::VerifyRequest),
676            Ok(66) => Ok(OutgoingMessages::VerifyMessage),
677            Ok(67) => Ok(OutgoingMessages::QueryDisplayGroups),
678            Ok(68) => Ok(OutgoingMessages::SubscribeToGroupEvents),
679            Ok(69) => Ok(OutgoingMessages::UpdateDisplayGroup),
680            Ok(70) => Ok(OutgoingMessages::UnsubscribeFromGroupEvents),
681            Ok(71) => Ok(OutgoingMessages::StartApi),
682            Ok(72) => Ok(OutgoingMessages::VerifyAndAuthRequest),
683            Ok(73) => Ok(OutgoingMessages::VerifyAndAuthMessage),
684            Ok(74) => Ok(OutgoingMessages::RequestPositionsMulti),
685            Ok(75) => Ok(OutgoingMessages::CancelPositionsMulti),
686            Ok(76) => Ok(OutgoingMessages::RequestAccountUpdatesMulti),
687            Ok(77) => Ok(OutgoingMessages::CancelAccountUpdatesMulti),
688            Ok(78) => Ok(OutgoingMessages::RequestSecurityDefinitionOptionalParameters),
689            Ok(79) => Ok(OutgoingMessages::RequestSoftDollarTiers),
690            Ok(80) => Ok(OutgoingMessages::RequestFamilyCodes),
691            Ok(81) => Ok(OutgoingMessages::RequestMatchingSymbols),
692            Ok(82) => Ok(OutgoingMessages::RequestMktDepthExchanges),
693            Ok(83) => Ok(OutgoingMessages::RequestSmartComponents),
694            Ok(84) => Ok(OutgoingMessages::RequestNewsArticle),
695            Ok(85) => Ok(OutgoingMessages::RequestNewsProviders),
696            Ok(86) => Ok(OutgoingMessages::RequestHistoricalNews),
697            Ok(87) => Ok(OutgoingMessages::RequestHeadTimestamp),
698            Ok(88) => Ok(OutgoingMessages::RequestHistogramData),
699            Ok(89) => Ok(OutgoingMessages::CancelHistogramData),
700            Ok(90) => Ok(OutgoingMessages::CancelHeadTimestamp),
701            Ok(91) => Ok(OutgoingMessages::RequestMarketRule),
702            Ok(92) => Ok(OutgoingMessages::RequestPnL),
703            Ok(93) => Ok(OutgoingMessages::CancelPnL),
704            Ok(94) => Ok(OutgoingMessages::RequestPnLSingle),
705            Ok(95) => Ok(OutgoingMessages::CancelPnLSingle),
706            Ok(96) => Ok(OutgoingMessages::RequestHistoricalTicks),
707            Ok(97) => Ok(OutgoingMessages::RequestTickByTickData),
708            Ok(98) => Ok(OutgoingMessages::CancelTickByTickData),
709            Ok(99) => Ok(OutgoingMessages::RequestCompletedOrders),
710            Ok(100) => Ok(OutgoingMessages::RequestWshMetaData),
711            Ok(101) => Ok(OutgoingMessages::CancelWshMetaData),
712            Ok(102) => Ok(OutgoingMessages::RequestWshEventData),
713            Ok(103) => Ok(OutgoingMessages::CancelWshEventData),
714            Ok(104) => Ok(OutgoingMessages::RequestUserInfo),
715            Ok(n) => Err(Error::Simple(format!("Unknown outgoing message type: {}", n))),
716            Err(_) => Err(Error::Simple(format!("Invalid outgoing message type: {}", s))),
717        }
718    }
719}
720
721/// Encode the outbound message length prefix using the IB wire format.
722pub fn encode_length(message: &str) -> Vec<u8> {
723    let data = message.as_bytes();
724
725    let mut packet: Vec<u8> = Vec::with_capacity(data.len() + 4);
726
727    packet.write_u32::<BigEndian>(data.len() as u32).unwrap();
728    packet.write_all(data).unwrap();
729    packet
730}
731
732/// Builder for outbound TWS/Gateway request messages.
733#[derive(Default, Debug, Clone)]
734pub struct RequestMessage {
735    pub(crate) fields: Vec<String>,
736}
737
738impl RequestMessage {
739    /// Create a new empty request message.
740    pub fn new() -> Self {
741        Self::default()
742    }
743
744    pub(crate) fn push_field<T: ToField>(&mut self, val: &T) -> &RequestMessage {
745        let field = val.to_field();
746        self.fields.push(field);
747        self
748    }
749
750    /// Serialize all fields into the NUL-delimited wire format.
751    pub fn encode(&self) -> String {
752        let mut data = self.fields.join("\0");
753        data.push('\0');
754        data
755    }
756
757    #[cfg(test)]
758    pub(crate) fn len(&self) -> usize {
759        self.fields.len()
760    }
761
762    #[cfg(test)]
763    /// Serialize the message as a pipe-delimited string (test helper).
764    pub(crate) fn encode_simple(&self) -> String {
765        let mut data = self.fields.join("|");
766        data.push('|');
767        data
768    }
769    #[cfg(test)]
770    /// Construct a request message from a NUL-delimited string (test helper).
771    pub fn from(fields: &str) -> RequestMessage {
772        RequestMessage {
773            fields: fields.split_terminator('\x00').map(|x| x.to_string()).collect(),
774        }
775    }
776    #[cfg(test)]
777    /// Construct a request message from a pipe-delimited string (test helper).
778    pub fn from_simple(fields: &str) -> RequestMessage {
779        RequestMessage {
780            fields: fields.split_terminator('|').map(|x| x.to_string()).collect(),
781        }
782    }
783}
784
785impl Index<usize> for RequestMessage {
786    type Output = String;
787
788    fn index(&self, i: usize) -> &Self::Output {
789        &self.fields[i]
790    }
791}
792
793/// Parsed inbound message from TWS/Gateway.
794#[derive(Clone, Default, Debug)]
795pub struct ResponseMessage {
796    /// Cursor index for incremental decoding.
797    pub i: usize,
798    /// Raw field buffer backing this message.
799    pub fields: Vec<String>,
800}
801
802impl ResponseMessage {
803    /// Number of fields present in the message.
804    pub fn len(&self) -> usize {
805        self.fields.len()
806    }
807
808    /// Returns `true` if the message contains no fields.
809    pub fn is_empty(&self) -> bool {
810        self.fields.is_empty()
811    }
812
813    /// Returns `true` if the message informs about API shutdown.
814    pub fn is_shutdown(&self) -> bool {
815        self.message_type() == IncomingMessages::Shutdown
816    }
817
818    /// Return the discriminator identifying the message payload.
819    pub fn message_type(&self) -> IncomingMessages {
820        if self.fields.is_empty() {
821            IncomingMessages::NotValid
822        } else {
823            let message_id = i32::from_str(&self.fields[0]).unwrap_or(-1);
824            IncomingMessages::from(message_id)
825        }
826    }
827
828    /// Try to extract the request id from the message.
829    pub fn request_id(&self) -> Option<i32> {
830        if let Some(i) = request_id_index(self.message_type()) {
831            if let Ok(request_id) = self.peek_int(i) {
832                return Some(request_id);
833            }
834        }
835        None
836    }
837
838    /// Try to extract the order id from the message.
839    pub fn order_id(&self) -> Option<i32> {
840        if let Some(i) = order_id_index(self.message_type()) {
841            if let Ok(order_id) = self.peek_int(i) {
842                return Some(order_id);
843            }
844        }
845        None
846    }
847
848    /// Try to extract the execution id from the message.
849    pub fn execution_id(&self) -> Option<String> {
850        match self.message_type() {
851            IncomingMessages::ExecutionData => Some(self.peek_string(14)),
852            IncomingMessages::CommissionsReport => Some(self.peek_string(2)),
853            _ => None,
854        }
855    }
856
857    /// Peek an integer field without advancing the cursor.
858    pub fn peek_int(&self, i: usize) -> Result<i32, Error> {
859        if i >= self.fields.len() {
860            return Err(Error::Simple("expected int and found end of message".into()));
861        }
862
863        let field = &self.fields[i];
864        match field.parse() {
865            Ok(val) => Ok(val),
866            Err(err) => Err(Error::Parse(i, field.into(), err.to_string())),
867        }
868    }
869
870    /// Peek a string field without advancing the cursor.
871    pub fn peek_string(&self, i: usize) -> String {
872        self.fields[i].to_owned()
873    }
874
875    /// Consume and parse the next integer field.
876    pub fn next_int(&mut self) -> Result<i32, Error> {
877        if self.i >= self.fields.len() {
878            return Err(Error::Simple("expected int and found end of message".into()));
879        }
880
881        let field = &self.fields[self.i];
882        self.i += 1;
883
884        match field.parse() {
885            Ok(val) => Ok(val),
886            Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
887        }
888    }
889
890    /// Consume the next field returning `None` when unset.
891    pub fn next_optional_int(&mut self) -> Result<Option<i32>, Error> {
892        if self.i >= self.fields.len() {
893            return Err(Error::Simple("expected optional int and found end of message".into()));
894        }
895
896        let field = &self.fields[self.i];
897        self.i += 1;
898
899        if field.is_empty() || field == UNSET_INTEGER {
900            return Ok(None);
901        }
902
903        match field.parse::<i32>() {
904            Ok(val) => Ok(Some(val)),
905            Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
906        }
907    }
908
909    /// Consume the next field as a boolean (`"0"` or `"1"`).
910    pub fn next_bool(&mut self) -> Result<bool, Error> {
911        if self.i >= self.fields.len() {
912            return Err(Error::Simple("expected bool and found end of message".into()));
913        }
914
915        let field = &self.fields[self.i];
916        self.i += 1;
917
918        Ok(field == "1")
919    }
920
921    /// Consume and parse the next i64 field.
922    pub fn next_long(&mut self) -> Result<i64, Error> {
923        if self.i >= self.fields.len() {
924            return Err(Error::Simple("expected long and found end of message".into()));
925        }
926
927        let field = &self.fields[self.i];
928        self.i += 1;
929
930        match field.parse() {
931            Ok(val) => Ok(val),
932            Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
933        }
934    }
935
936    /// Consume the next field as an optional i64.
937    pub fn next_optional_long(&mut self) -> Result<Option<i64>, Error> {
938        if self.i >= self.fields.len() {
939            return Err(Error::Simple("expected optional long and found end of message".into()));
940        }
941
942        let field = &self.fields[self.i];
943        self.i += 1;
944
945        if field.is_empty() || field == UNSET_LONG {
946            return Ok(None);
947        }
948
949        match field.parse::<i64>() {
950            Ok(val) => Ok(Some(val)),
951            Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
952        }
953    }
954
955    /// Consume the next field and parse it as a UTC timestamp.
956    pub fn next_date_time(&mut self) -> Result<OffsetDateTime, Error> {
957        if self.i >= self.fields.len() {
958            return Err(Error::Simple("expected datetime and found end of message".into()));
959        }
960
961        let field = &self.fields[self.i];
962        self.i += 1;
963
964        if field.is_empty() {
965            return Err(Error::Simple("expected timestamp and found empty string".into()));
966        }
967
968        // from_unix_timestamp
969        let timestamp: i64 = field.parse()?;
970        match OffsetDateTime::from_unix_timestamp(timestamp) {
971            Ok(val) => Ok(val),
972            Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
973        }
974    }
975
976    /// Consume the next field as a string.
977    pub fn next_string(&mut self) -> Result<String, Error> {
978        if self.i >= self.fields.len() {
979            return Err(Error::Simple("expected string and found end of message".into()));
980        }
981
982        let field = &self.fields[self.i];
983        self.i += 1;
984        Ok(String::from(field))
985    }
986
987    /// Consume and parse the next floating-point field.
988    pub fn next_double(&mut self) -> Result<f64, Error> {
989        if self.i >= self.fields.len() {
990            return Err(Error::Simple("expected double and found end of message".into()));
991        }
992
993        let field = &self.fields[self.i];
994        self.i += 1;
995
996        if field.is_empty() || field == "0" || field == "0.0" {
997            return Ok(0.0);
998        }
999
1000        match field.parse() {
1001            Ok(val) => Ok(val),
1002            Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
1003        }
1004    }
1005
1006    /// Consume the next field as an optional floating-point value.
1007    pub fn next_optional_double(&mut self) -> Result<Option<f64>, Error> {
1008        if self.i >= self.fields.len() {
1009            return Err(Error::Simple("expected optional double and found end of message".into()));
1010        }
1011
1012        let field = &self.fields[self.i];
1013        self.i += 1;
1014
1015        if field.is_empty() || field == UNSET_DOUBLE {
1016            return Ok(None);
1017        }
1018
1019        if field == INFINITY_STR {
1020            return Ok(Some(f64::INFINITY));
1021        }
1022
1023        match field.parse() {
1024            Ok(val) => Ok(Some(val)),
1025            Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
1026        }
1027    }
1028
1029    /// Build a response message from a NUL-delimited payload.
1030    pub fn from(fields: &str) -> ResponseMessage {
1031        ResponseMessage {
1032            i: 0,
1033            fields: fields.split_terminator('\x00').map(|x| x.to_string()).collect(),
1034        }
1035    }
1036    #[cfg(test)]
1037    /// Build a response message from a pipe-delimited payload (test helper).
1038    pub fn from_simple(fields: &str) -> ResponseMessage {
1039        ResponseMessage {
1040            i: 0,
1041            fields: fields.split_terminator('|').map(|x| x.to_string()).collect(),
1042        }
1043    }
1044
1045    /// Advance the cursor past the next field.
1046    pub fn skip(&mut self) {
1047        self.i += 1;
1048    }
1049
1050    /// Encode the message back into a NUL-delimited string.
1051    pub fn encode(&self) -> String {
1052        let mut data = self.fields.join("\0");
1053        data.push('\0');
1054        data
1055    }
1056
1057    #[cfg(test)]
1058    /// Serialize the message into a pipe-delimited format (test helper).
1059    pub fn encode_simple(&self) -> String {
1060        let mut data = self.fields.join("|");
1061        data.push('|');
1062        data
1063    }
1064}
1065
1066/// An error message from the TWS API.
1067#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1068pub struct Notice {
1069    /// Error code reported by TWS.
1070    pub code: i32,
1071    /// Human-readable error message text.
1072    pub message: String,
1073}
1074
1075impl Notice {
1076    #[allow(private_interfaces)]
1077    /// Construct a notice from a response message.
1078    pub fn from(message: &ResponseMessage) -> Notice {
1079        let code = message.peek_int(CODE_INDEX).unwrap_or(-1);
1080        let message = message.peek_string(MESSAGE_INDEX);
1081        Notice { code, message }
1082    }
1083}
1084
1085impl Display for Notice {
1086    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1087        write!(f, "[{}] {}", self.code, self.message)
1088    }
1089}