Skip to main content

truefix_twsapi_client/
requests.rs

1use prost::Message;
2
3use crate::comm;
4use crate::constants::{UNSET_DOUBLE, UNSET_INTEGER};
5use crate::error::{TwsApiError, TwsApiResult};
6use crate::message::Outgoing;
7use crate::protobuf;
8use crate::server_versions::{
9    MAX_CLIENT_VER, MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
10    MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2, MIN_SERVER_VER_ADVANCED_ORDER_REJECT,
11    MIN_SERVER_VER_ALGO_ID, MIN_SERVER_VER_ALGO_ORDERS, MIN_SERVER_VER_ATTACHED_ORDERS,
12    MIN_SERVER_VER_AUTO_CANCEL_PARENT, MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE,
13    MIN_SERVER_VER_BOND_ISSUERID, MIN_SERVER_VER_CASH_QTY, MIN_SERVER_VER_CME_TAGGING_FIELDS,
14    MIN_SERVER_VER_CUSTOMER_ACCOUNT, MIN_SERVER_VER_D_PEG_ORDERS, MIN_SERVER_VER_DECISION_MAKER,
15    MIN_SERVER_VER_DELTA_NEUTRAL, MIN_SERVER_VER_DELTA_NEUTRAL_CONID,
16    MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE, MIN_SERVER_VER_DURATION,
17    MIN_SERVER_VER_EXECUTION_DATA_CHAIN, MIN_SERVER_VER_EXT_OPERATOR,
18    MIN_SERVER_VER_FA_PROFILE_DESUPPORT, MIN_SERVER_VER_HEDGE_MAX_SIZE,
19    MIN_SERVER_VER_HEDGE_ORDERS, MIN_SERVER_VER_HISTORICAL_TICKS, MIN_SERVER_VER_IMBALANCE_ONLY,
20    MIN_SERVER_VER_INCLUDE_OVERNIGHT, MIN_SERVER_VER_LINKING, MIN_SERVER_VER_MANUAL_ORDER_TIME,
21    MIN_SERVER_VER_MANUAL_ORDER_TIME_EXERCISE_OPTIONS, MIN_SERVER_VER_MIFID_EXECUTION,
22    MIN_SERVER_VER_MKT_DEPTH_PRIM_EXCHANGE, MIN_SERVER_VER_MODELS_SUPPORT,
23    MIN_SERVER_VER_NEWS_QUERY_ORIGINS, MIN_SERVER_VER_NOT_HELD,
24    MIN_SERVER_VER_OPT_OUT_SMART_ROUTING, MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE,
25    MIN_SERVER_VER_ORDER_CONTAINER, MIN_SERVER_VER_ORDER_SOLICITED,
26    MIN_SERVER_VER_PARAMETRIZED_DAYS_OF_EXECUTIONS, MIN_SERVER_VER_PEGBEST_PEGMID_OFFSETS,
27    MIN_SERVER_VER_PEGGED_TO_BENCHMARK, MIN_SERVER_VER_PLACE_ORDER_CONID,
28    MIN_SERVER_VER_POST_TO_ATS, MIN_SERVER_VER_PRICE_MGMT_ALGO, MIN_SERVER_VER_PRIMARYEXCH,
29    MIN_SERVER_VER_PROFESSIONAL_CUSTOMER, MIN_SERVER_VER_PROTOBUF,
30    MIN_SERVER_VER_PROTOBUF_ACCOUNTS_POSITIONS, MIN_SERVER_VER_PROTOBUF_COMPLETED_ORDER,
31    MIN_SERVER_VER_PROTOBUF_CONTRACT_DATA, MIN_SERVER_VER_PROTOBUF_HISTORICAL_DATA,
32    MIN_SERVER_VER_PROTOBUF_MARKET_DATA, MIN_SERVER_VER_PROTOBUF_NEWS_DATA,
33    MIN_SERVER_VER_PROTOBUF_PLACE_ORDER, MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_1,
34    MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_2, MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_3,
35    MIN_SERVER_VER_PROTOBUF_SCAN_DATA, MIN_SERVER_VER_PTA_ORDERS,
36    MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE, MIN_SERVER_VER_REPLACE_FA_END,
37    MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT, MIN_SERVER_VER_REQ_MKT_DATA_CONID,
38    MIN_SERVER_VER_REQ_SMART_COMPONENTS, MIN_SERVER_VER_RFQ_FIELDS, MIN_SERVER_VER_SCALE_ORDERS2,
39    MIN_SERVER_VER_SCALE_ORDERS3, MIN_SERVER_VER_SCALE_TABLE, MIN_SERVER_VER_SCANNER_GENERIC_OPTS,
40    MIN_SERVER_VER_SEC_ID_TYPE, MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS,
41    MIN_SERVER_VER_SMART_DEPTH, MIN_SERVER_VER_SOFT_DOLLAR_TIER, MIN_SERVER_VER_SSHORTX_OLD,
42    MIN_SERVER_VER_SYNT_REALTIME_BARS, MIN_SERVER_VER_TICK_BY_TICK,
43    MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE, MIN_SERVER_VER_TRADING_CLASS,
44    MIN_SERVER_VER_TRAILING_PERCENT, MIN_SERVER_VER_UNDO_RFQ_FIELDS,
45};
46use crate::types::{
47    Contract, ExecutionFilter, Order, OrderCancel, ScannerSubscription, TagValue, TickerId,
48};
49
50/// A request that can be encoded as TWS NUL-separated fields.
51pub trait EncodableRequest {
52    /// Outgoing message id.
53    fn message(&self) -> Outgoing;
54
55    /// Appends fields after the message id.
56    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()>;
57
58    /// Appends fields for a negotiated server version.
59    fn encode_fields_for_server_version(
60        &self,
61        fields: &mut FieldSink,
62        _server_version: i32,
63    ) -> TwsApiResult<()> {
64        self.encode_fields(fields)
65    }
66
67    /// Encodes this request as protobuf when implemented for the request type.
68    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
69        Ok(None)
70    }
71}
72
73/// Encodes a request frame, preferring protobuf when supported by server and request type.
74pub fn encode_request_frame<R>(request: &R, server_version: i32) -> TwsApiResult<Vec<u8>>
75where
76    R: EncodableRequest,
77{
78    encode_request_frame_with_protobuf(request, server_version, true)
79}
80
81/// Encodes a request frame, optionally allowing protobuf when supported.
82pub fn encode_request_frame_with_protobuf<R>(
83    request: &R,
84    server_version: i32,
85    prefer_protobuf: bool,
86) -> TwsApiResult<Vec<u8>>
87where
88    R: EncodableRequest,
89{
90    if prefer_protobuf
91        && protobuf_min_server_version(request.message())
92            .is_some_and(|min_version| server_version >= min_version)
93        && let Some(payload) = request.encode_protobuf()?
94    {
95        return Ok(comm::make_msg_proto(
96            request.message().protobuf_id(),
97            &payload,
98        ));
99    }
100
101    let mut fields = FieldSink::default();
102    request.encode_fields_for_server_version(&mut fields, server_version)?;
103    comm::make_msg(
104        request.message().id(),
105        // Since protocol version 151, the official SDK frames *all* outgoing
106        // request ids as raw integers. `prefer_protobuf` chooses only the
107        // request body encoding; a field-encoded body still keeps this frame.
108        server_version >= MIN_SERVER_VER_PROTOBUF,
109        &fields.into_string(),
110    )
111}
112
113/// Returns the minimum server version for protobuf encoding of an outgoing message.
114pub const fn protobuf_min_server_version(message: Outgoing) -> Option<i32> {
115    Some(match message {
116        Outgoing::ReqExecutions => MIN_SERVER_VER_PROTOBUF,
117        Outgoing::PlaceOrder | Outgoing::CancelOrder | Outgoing::ReqGlobalCancel => {
118            MIN_SERVER_VER_PROTOBUF_PLACE_ORDER
119        }
120        Outgoing::ReqAllOpenOrders
121        | Outgoing::ReqAutoOpenOrders
122        | Outgoing::ReqOpenOrders
123        | Outgoing::ReqCompletedOrders => MIN_SERVER_VER_PROTOBUF_COMPLETED_ORDER,
124        Outgoing::ReqContractData => MIN_SERVER_VER_PROTOBUF_CONTRACT_DATA,
125        Outgoing::ReqMktData
126        | Outgoing::CancelMktData
127        | Outgoing::ReqMktDepth
128        | Outgoing::CancelMktDepth
129        | Outgoing::ReqMarketDataType => MIN_SERVER_VER_PROTOBUF_MARKET_DATA,
130        Outgoing::ReqAcctData
131        | Outgoing::ReqManagedAccounts
132        | Outgoing::ReqPositions
133        | Outgoing::CancelPositions
134        | Outgoing::ReqAccountSummary
135        | Outgoing::CancelAccountSummary
136        | Outgoing::ReqPositionsMulti
137        | Outgoing::CancelPositionsMulti
138        | Outgoing::ReqAccountUpdatesMulti
139        | Outgoing::CancelAccountUpdatesMulti => MIN_SERVER_VER_PROTOBUF_ACCOUNTS_POSITIONS,
140        Outgoing::ReqHistoricalData
141        | Outgoing::CancelHistoricalData
142        | Outgoing::ReqRealTimeBars
143        | Outgoing::CancelRealTimeBars
144        | Outgoing::ReqHeadTimestamp
145        | Outgoing::CancelHeadTimestamp
146        | Outgoing::ReqHistogramData
147        | Outgoing::CancelHistogramData
148        | Outgoing::ReqHistoricalTicks
149        | Outgoing::ReqTickByTickData
150        | Outgoing::CancelTickByTickData => MIN_SERVER_VER_PROTOBUF_HISTORICAL_DATA,
151        Outgoing::ReqNewsBulletins
152        | Outgoing::CancelNewsBulletins
153        | Outgoing::ReqNewsArticle
154        | Outgoing::ReqNewsProviders
155        | Outgoing::ReqHistoricalNews
156        | Outgoing::ReqWshMetaData
157        | Outgoing::CancelWshMetaData
158        | Outgoing::ReqWshEventData
159        | Outgoing::CancelWshEventData => MIN_SERVER_VER_PROTOBUF_NEWS_DATA,
160        Outgoing::ReqScannerParameters
161        | Outgoing::ReqScannerSubscription
162        | Outgoing::CancelScannerSubscription
163        | Outgoing::ReqPnl
164        | Outgoing::CancelPnl
165        | Outgoing::ReqPnlSingle
166        | Outgoing::CancelPnlSingle => MIN_SERVER_VER_PROTOBUF_SCAN_DATA,
167        Outgoing::ReqFa
168        | Outgoing::ReplaceFa
169        | Outgoing::ExerciseOptions
170        | Outgoing::ReqCalcImpliedVolat
171        | Outgoing::CancelCalcImpliedVolat
172        | Outgoing::ReqCalcOptionPrice
173        | Outgoing::CancelCalcOptionPrice => MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_1,
174        Outgoing::ReqSecDefOptParams
175        | Outgoing::ReqSoftDollarTiers
176        | Outgoing::ReqFamilyCodes
177        | Outgoing::ReqMatchingSymbols
178        | Outgoing::ReqSmartComponents
179        | Outgoing::ReqMarketRule
180        | Outgoing::ReqUserInfo => MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_2,
181        Outgoing::ReqIds
182        | Outgoing::ReqCurrentTime
183        | Outgoing::ReqCurrentTimeInMillis
184        | Outgoing::SetServerLogLevel
185        | Outgoing::VerifyRequest
186        | Outgoing::VerifyMessage
187        | Outgoing::QueryDisplayGroups
188        | Outgoing::SubscribeToGroupEvents
189        | Outgoing::UpdateDisplayGroup
190        | Outgoing::UnsubscribeFromGroupEvents
191        | Outgoing::ReqMktDepthExchanges => MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_3,
192        Outgoing::VerifyAndAuthRequest
193        | Outgoing::VerifyAndAuthMessage
194        | Outgoing::StartApi
195        | Outgoing::CancelContractData
196        | Outgoing::CancelHistoricalTicks
197        | Outgoing::ReqConfig
198        | Outgoing::UpdateConfig => return None,
199    })
200}
201
202/// Field encoder with TWS sentinel handling.
203#[derive(Debug, Clone, Default)]
204pub struct FieldSink {
205    fields: String,
206}
207
208impl FieldSink {
209    /// Appends a regular field.
210    pub fn push<T>(&mut self, value: T) -> TwsApiResult<&mut Self>
211    where
212        T: comm::TwsField,
213    {
214        self.fields.push_str(&comm::make_field(value)?);
215        Ok(self)
216    }
217
218    /// Appends a field where TWS unset sentinels are encoded as empty values.
219    pub fn push_empty<T>(&mut self, value: T) -> TwsApiResult<&mut Self>
220    where
221        T: comm::TwsNullableField,
222    {
223        self.fields.push_str(&comm::make_field_handle_empty(value)?);
224        Ok(self)
225    }
226
227    /// Appends pre-encoded raw fields.
228    pub fn push_raw(&mut self, raw: &str) -> &mut Self {
229        self.fields.push_str(raw);
230        self
231    }
232
233    /// Returns encoded fields.
234    pub fn into_string(self) -> String {
235        self.fields
236    }
237}
238
239/// A raw request for migration and protocol coverage.
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct RawRequest {
242    /// Message id.
243    pub message: Outgoing,
244    /// Already encoded NUL-separated fields after the message id.
245    pub fields: String,
246}
247
248/// Result of validating order fields against a negotiated server version.
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub(crate) struct OrderFieldValidation {
251    /// Unsupported parameter name.
252    pub parameter: &'static str,
253    /// Minimum supported server version.
254    pub min_version: i32,
255}
256
257impl EncodableRequest for RawRequest {
258    fn message(&self) -> Outgoing {
259        self.message
260    }
261
262    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
263        fields.push_raw(&self.fields);
264        Ok(())
265    }
266}
267
268/// Request with no field payload.
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub struct EmptyRequest {
271    /// Message id.
272    pub message: Outgoing,
273}
274
275impl EncodableRequest for EmptyRequest {
276    fn message(&self) -> Outgoing {
277        self.message
278    }
279
280    fn encode_fields(&self, _fields: &mut FieldSink) -> TwsApiResult<()> {
281        Ok(())
282    }
283
284    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
285        let payload = match self.message {
286            Outgoing::ReqMktDepthExchanges => {
287                protobuf::MarketDepthExchangesRequest::default().encode_to_vec()
288            }
289            Outgoing::ReqNewsProviders => protobuf::NewsProvidersRequest::default().encode_to_vec(),
290            Outgoing::ReqFamilyCodes => protobuf::FamilyCodesRequest::default().encode_to_vec(),
291            Outgoing::ReqCurrentTimeInMillis => {
292                protobuf::CurrentTimeInMillisRequest::default().encode_to_vec()
293            }
294            _ => return Ok(None),
295        };
296        Ok(Some(payload))
297    }
298}
299
300/// Request for `startApi`.
301#[derive(Debug, Clone, Default, PartialEq, Eq)]
302pub struct StartApiRequest {
303    /// Client id.
304    pub client_id: i32,
305    /// Optional capabilities.
306    pub optional_capabilities: Option<String>,
307    /// Whether to encode optional capabilities.
308    pub include_optional_capabilities: bool,
309}
310
311impl EncodableRequest for StartApiRequest {
312    fn message(&self) -> Outgoing {
313        Outgoing::StartApi
314    }
315
316    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
317        fields.push(2)?.push(self.client_id)?;
318        if self.include_optional_capabilities {
319            fields.push(self.optional_capabilities.as_deref().unwrap_or(""))?;
320        }
321        Ok(())
322    }
323}
324
325/// Request with only a protocol version field.
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub struct VersionedRequest {
328    /// Message id.
329    pub message: Outgoing,
330    /// Protocol version.
331    pub version: i32,
332}
333
334impl EncodableRequest for VersionedRequest {
335    fn message(&self) -> Outgoing {
336        self.message
337    }
338
339    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
340        fields.push(self.version)?;
341        Ok(())
342    }
343
344    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
345        let payload = match self.message {
346            Outgoing::ReqCurrentTime => protobuf::CurrentTimeRequest::default().encode_to_vec(),
347            Outgoing::ReqCurrentTimeInMillis => {
348                protobuf::CurrentTimeInMillisRequest::default().encode_to_vec()
349            }
350            Outgoing::ReqPositions => protobuf::PositionsRequest::default().encode_to_vec(),
351            Outgoing::CancelPositions => protobuf::CancelPositions::default().encode_to_vec(),
352            Outgoing::ReqOpenOrders => protobuf::OpenOrdersRequest::default().encode_to_vec(),
353            Outgoing::ReqAllOpenOrders => protobuf::AllOpenOrdersRequest::default().encode_to_vec(),
354            Outgoing::ReqManagedAccounts => {
355                protobuf::ManagedAccountsRequest::default().encode_to_vec()
356            }
357            Outgoing::ReqScannerParameters => {
358                protobuf::ScannerParametersRequest::default().encode_to_vec()
359            }
360            Outgoing::CancelNewsBulletins => {
361                protobuf::CancelNewsBulletins::default().encode_to_vec()
362            }
363            _ => return Ok(None),
364        };
365        Ok(Some(payload))
366    }
367}
368
369/// Request with only an id field.
370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371pub struct IdRequest {
372    /// Message id.
373    pub message: Outgoing,
374    /// Protocol version.
375    pub version: Option<i32>,
376    /// Request id.
377    pub req_id: i32,
378}
379
380/// Server log-level request.
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382pub struct SetServerLogLevelRequest {
383    /// Log level.
384    pub log_level: i32,
385}
386
387impl EncodableRequest for SetServerLogLevelRequest {
388    fn message(&self) -> Outgoing {
389        Outgoing::SetServerLogLevel
390    }
391
392    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
393        fields.push(1)?.push(self.log_level)?;
394        Ok(())
395    }
396
397    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
398        Ok(Some(
399            protobuf::SetServerLogLevelRequest {
400                log_level: Some(self.log_level),
401            }
402            .encode_to_vec(),
403        ))
404    }
405}
406
407/// Request a market data type switch.
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
409pub struct MarketDataTypeRequest {
410    /// Market data type.
411    pub market_data_type: i32,
412}
413
414impl EncodableRequest for MarketDataTypeRequest {
415    fn message(&self) -> Outgoing {
416        Outgoing::ReqMarketDataType
417    }
418
419    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
420        fields.push(1)?.push(self.market_data_type)?;
421        Ok(())
422    }
423
424    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
425        Ok(Some(
426            protobuf::MarketDataTypeRequest {
427                market_data_type: Some(self.market_data_type),
428            }
429            .encode_to_vec(),
430        ))
431    }
432}
433
434/// Smart components request.
435#[derive(Debug, Clone, PartialEq, Eq)]
436pub struct SmartComponentsRequest {
437    /// Request id.
438    pub req_id: i32,
439    /// BBO exchange.
440    pub bbo_exchange: String,
441}
442
443impl EncodableRequest for SmartComponentsRequest {
444    fn message(&self) -> Outgoing {
445        Outgoing::ReqSmartComponents
446    }
447
448    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
449        fields.push(self.req_id)?.push(&self.bbo_exchange)?;
450        Ok(())
451    }
452
453    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
454        Ok(Some(
455            protobuf::SmartComponentsRequest {
456                req_id: Some(self.req_id),
457                bbo_exchange: non_empty(self.bbo_exchange.clone()),
458            }
459            .encode_to_vec(),
460        ))
461    }
462}
463
464/// Cancel market depth request.
465#[derive(Debug, Clone, Copy, PartialEq, Eq)]
466pub struct CancelMarketDepthRequest {
467    /// Request id.
468    pub req_id: i32,
469    /// Smart depth.
470    pub is_smart_depth: bool,
471}
472
473impl EncodableRequest for CancelMarketDepthRequest {
474    fn message(&self) -> Outgoing {
475        Outgoing::CancelMktDepth
476    }
477
478    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
479        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
480    }
481
482    fn encode_fields_for_server_version(
483        &self,
484        fields: &mut FieldSink,
485        server_version: i32,
486    ) -> TwsApiResult<()> {
487        fields.push(1)?.push(self.req_id)?;
488        if server_version >= MIN_SERVER_VER_SMART_DEPTH {
489            fields.push(self.is_smart_depth)?;
490        }
491        Ok(())
492    }
493
494    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
495        Ok(Some(
496            protobuf::CancelMarketDepth {
497                req_id: Some(self.req_id),
498                is_smart_depth: self.is_smart_depth.then_some(true),
499            }
500            .encode_to_vec(),
501        ))
502    }
503}
504
505/// Auto-open-orders request.
506#[derive(Debug, Clone, Copy, PartialEq, Eq)]
507pub struct AutoOpenOrdersRequest {
508    /// Auto-bind flag.
509    pub auto_bind: bool,
510}
511
512impl EncodableRequest for AutoOpenOrdersRequest {
513    fn message(&self) -> Outgoing {
514        Outgoing::ReqAutoOpenOrders
515    }
516
517    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
518        fields.push(1)?.push(self.auto_bind)?;
519        Ok(())
520    }
521
522    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
523        Ok(Some(
524            protobuf::AutoOpenOrdersRequest {
525                auto_bind: Some(self.auto_bind),
526            }
527            .encode_to_vec(),
528        ))
529    }
530}
531
532/// Global cancel request.
533#[derive(Debug, Clone, Default, PartialEq, Eq)]
534pub struct GlobalCancelRequest {
535    /// Cancel metadata.
536    pub order_cancel: OrderCancel,
537}
538
539impl EncodableRequest for GlobalCancelRequest {
540    fn message(&self) -> Outgoing {
541        Outgoing::ReqGlobalCancel
542    }
543
544    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
545        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
546    }
547
548    fn encode_fields_for_server_version(
549        &self,
550        fields: &mut FieldSink,
551        server_version: i32,
552    ) -> TwsApiResult<()> {
553        if server_version < MIN_SERVER_VER_CME_TAGGING_FIELDS {
554            fields.push(1)?;
555        } else {
556            fields
557                .push(&self.order_cancel.ext_operator)?
558                .push_empty(self.order_cancel.manual_order_indicator)?;
559        }
560        Ok(())
561    }
562
563    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
564        Ok(Some(
565            protobuf::GlobalCancelRequest {
566                order_cancel: Some(order_cancel_to_proto(&self.order_cancel)),
567            }
568            .encode_to_vec(),
569        ))
570    }
571}
572
573/// Account data subscription request.
574#[derive(Debug, Clone, PartialEq, Eq)]
575pub struct AccountDataRequest {
576    /// Subscribe or unsubscribe.
577    pub subscribe: bool,
578    /// Account code.
579    pub account_code: String,
580}
581
582impl EncodableRequest for AccountDataRequest {
583    fn message(&self) -> Outgoing {
584        Outgoing::ReqAcctData
585    }
586
587    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
588        fields
589            .push(2)?
590            .push(self.subscribe)?
591            .push(&self.account_code)?;
592        Ok(())
593    }
594
595    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
596        Ok(Some(
597            protobuf::AccountDataRequest {
598                subscribe: Some(self.subscribe),
599                acct_code: non_empty(self.account_code.clone()),
600            }
601            .encode_to_vec(),
602        ))
603    }
604}
605
606/// Account summary request.
607#[derive(Debug, Clone, PartialEq, Eq)]
608pub struct AccountSummaryRequest {
609    /// Request id.
610    pub req_id: i32,
611    /// Group name.
612    pub group_name: String,
613    /// Tags.
614    pub tags: String,
615}
616
617impl EncodableRequest for AccountSummaryRequest {
618    fn message(&self) -> Outgoing {
619        Outgoing::ReqAccountSummary
620    }
621
622    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
623        fields
624            .push(1)?
625            .push(self.req_id)?
626            .push(&self.group_name)?
627            .push(&self.tags)?;
628        Ok(())
629    }
630
631    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
632        Ok(Some(
633            protobuf::AccountSummaryRequest {
634                req_id: Some(self.req_id),
635                group: non_empty(self.group_name.clone()),
636                tags: non_empty(self.tags.clone()),
637            }
638            .encode_to_vec(),
639        ))
640    }
641}
642
643/// Positions multi request.
644#[derive(Debug, Clone, PartialEq, Eq)]
645pub struct PositionsMultiRequest {
646    /// Request id.
647    pub req_id: i32,
648    /// Account.
649    pub account: String,
650    /// Model code.
651    pub model_code: String,
652}
653
654impl EncodableRequest for PositionsMultiRequest {
655    fn message(&self) -> Outgoing {
656        Outgoing::ReqPositionsMulti
657    }
658
659    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
660        fields
661            .push(1)?
662            .push(self.req_id)?
663            .push(&self.account)?
664            .push(&self.model_code)?;
665        Ok(())
666    }
667
668    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
669        Ok(Some(
670            protobuf::PositionsMultiRequest {
671                req_id: Some(self.req_id),
672                account: non_empty(self.account.clone()),
673                model_code: non_empty(self.model_code.clone()),
674            }
675            .encode_to_vec(),
676        ))
677    }
678}
679
680/// Account updates multi request.
681#[derive(Debug, Clone, PartialEq, Eq)]
682pub struct AccountUpdatesMultiRequest {
683    /// Request id.
684    pub req_id: i32,
685    /// Account.
686    pub account: String,
687    /// Model code.
688    pub model_code: String,
689    /// Whether to include ledger and NLV.
690    pub ledger_and_nlv: bool,
691}
692
693impl EncodableRequest for AccountUpdatesMultiRequest {
694    fn message(&self) -> Outgoing {
695        Outgoing::ReqAccountUpdatesMulti
696    }
697
698    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
699        fields
700            .push(1)?
701            .push(self.req_id)?
702            .push(&self.account)?
703            .push(&self.model_code)?
704            .push(self.ledger_and_nlv)?;
705        Ok(())
706    }
707
708    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
709        Ok(Some(
710            protobuf::AccountUpdatesMultiRequest {
711                req_id: Some(self.req_id),
712                account: non_empty(self.account.clone()),
713                model_code: non_empty(self.model_code.clone()),
714                ledger_and_nlv: Some(self.ledger_and_nlv),
715            }
716            .encode_to_vec(),
717        ))
718    }
719}
720
721/// PnL request.
722#[derive(Debug, Clone, PartialEq, Eq)]
723pub struct PnlRequest {
724    /// Request id.
725    pub req_id: i32,
726    /// Account.
727    pub account: String,
728    /// Model code.
729    pub model_code: String,
730}
731
732impl EncodableRequest for PnlRequest {
733    fn message(&self) -> Outgoing {
734        Outgoing::ReqPnl
735    }
736
737    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
738        fields
739            .push(self.req_id)?
740            .push(&self.account)?
741            .push(&self.model_code)?;
742        Ok(())
743    }
744
745    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
746        Ok(Some(
747            protobuf::PnLRequest {
748                req_id: Some(self.req_id),
749                account: non_empty(self.account.clone()),
750                model_code: non_empty(self.model_code.clone()),
751            }
752            .encode_to_vec(),
753        ))
754    }
755}
756
757/// Single-position PnL request.
758#[derive(Debug, Clone, PartialEq, Eq)]
759pub struct PnlSingleRequest {
760    /// Request id.
761    pub req_id: i32,
762    /// Account.
763    pub account: String,
764    /// Model code.
765    pub model_code: String,
766    /// Contract id.
767    pub con_id: i32,
768}
769
770/// News bulletin subscription request.
771#[derive(Debug, Clone, Copy, PartialEq, Eq)]
772pub struct NewsBulletinsRequest {
773    /// Include all messages.
774    pub all_messages: bool,
775}
776
777impl EncodableRequest for NewsBulletinsRequest {
778    fn message(&self) -> Outgoing {
779        Outgoing::ReqNewsBulletins
780    }
781
782    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
783        fields.push(1)?.push(self.all_messages)?;
784        Ok(())
785    }
786
787    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
788        Ok(Some(
789            protobuf::NewsBulletinsRequest {
790                all_messages: Some(self.all_messages),
791            }
792            .encode_to_vec(),
793        ))
794    }
795}
796
797/// News article request.
798#[derive(Debug, Clone, Default, PartialEq, Eq)]
799pub struct NewsArticleRequest {
800    /// Request id.
801    pub req_id: i32,
802    /// Provider code.
803    pub provider_code: String,
804    /// Article id.
805    pub article_id: String,
806    /// Request options.
807    pub options: Vec<TagValue>,
808}
809
810impl EncodableRequest for NewsArticleRequest {
811    fn message(&self) -> Outgoing {
812        Outgoing::ReqNewsArticle
813    }
814
815    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
816        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
817    }
818
819    fn encode_fields_for_server_version(
820        &self,
821        fields: &mut FieldSink,
822        server_version: i32,
823    ) -> TwsApiResult<()> {
824        fields
825            .push(self.req_id)?
826            .push(&self.provider_code)?
827            .push(&self.article_id)?;
828        if server_version >= MIN_SERVER_VER_NEWS_QUERY_ORIGINS {
829            fields.push(tag_values_to_tws_options(&self.options))?;
830        }
831        Ok(())
832    }
833
834    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
835        Ok(Some(
836            protobuf::NewsArticleRequest {
837                req_id: Some(self.req_id),
838                provider_code: non_empty(self.provider_code.clone()),
839                article_id: non_empty(self.article_id.clone()),
840                news_article_options: tag_values_to_map(&self.options),
841            }
842            .encode_to_vec(),
843        ))
844    }
845}
846
847/// Historical news request.
848#[derive(Debug, Clone, Default, PartialEq, Eq)]
849pub struct HistoricalNewsRequest {
850    /// Request id.
851    pub req_id: i32,
852    /// Contract id.
853    pub con_id: i32,
854    /// Provider codes.
855    pub provider_codes: String,
856    /// Start date/time.
857    pub start_date_time: String,
858    /// End date/time.
859    pub end_date_time: String,
860    /// Total results.
861    pub total_results: i32,
862    /// Request options.
863    pub options: Vec<TagValue>,
864}
865
866impl EncodableRequest for HistoricalNewsRequest {
867    fn message(&self) -> Outgoing {
868        Outgoing::ReqHistoricalNews
869    }
870
871    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
872        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
873    }
874
875    fn encode_fields_for_server_version(
876        &self,
877        fields: &mut FieldSink,
878        server_version: i32,
879    ) -> TwsApiResult<()> {
880        fields
881            .push(self.req_id)?
882            .push(self.con_id)?
883            .push(&self.provider_codes)?
884            .push(&self.start_date_time)?
885            .push(&self.end_date_time)?
886            .push(self.total_results)?;
887        if server_version >= MIN_SERVER_VER_NEWS_QUERY_ORIGINS {
888            fields.push(tag_values_to_tws_options(&self.options))?;
889        }
890        Ok(())
891    }
892
893    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
894        Ok(Some(
895            protobuf::HistoricalNewsRequest {
896                req_id: Some(self.req_id),
897                con_id: Some(self.con_id),
898                provider_codes: non_empty(self.provider_codes.clone()),
899                start_date_time: non_empty(self.start_date_time.clone()),
900                end_date_time: non_empty(self.end_date_time.clone()),
901                total_results: Some(self.total_results),
902                historical_news_options: tag_values_to_map(&self.options),
903            }
904            .encode_to_vec(),
905        ))
906    }
907}
908
909/// Security-definition option parameters request.
910#[derive(Debug, Clone, PartialEq, Eq)]
911pub struct SecDefOptParamsRequest {
912    /// Request id.
913    pub req_id: i32,
914    /// Underlying symbol.
915    pub underlying_symbol: String,
916    /// FUT/FOP exchange.
917    pub fut_fop_exchange: String,
918    /// Underlying security type.
919    pub underlying_sec_type: String,
920    /// Underlying contract id.
921    pub underlying_con_id: i32,
922}
923
924impl EncodableRequest for SecDefOptParamsRequest {
925    fn message(&self) -> Outgoing {
926        Outgoing::ReqSecDefOptParams
927    }
928
929    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
930        fields
931            .push(self.req_id)?
932            .push(&self.underlying_symbol)?
933            .push(&self.fut_fop_exchange)?
934            .push(&self.underlying_sec_type)?
935            .push(self.underlying_con_id)?;
936        Ok(())
937    }
938
939    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
940        Ok(Some(
941            protobuf::SecDefOptParamsRequest {
942                req_id: Some(self.req_id),
943                underlying_symbol: non_empty(self.underlying_symbol.clone()),
944                fut_fop_exchange: non_empty(self.fut_fop_exchange.clone()),
945                underlying_sec_type: non_empty(self.underlying_sec_type.clone()),
946                underlying_con_id: Some(self.underlying_con_id),
947            }
948            .encode_to_vec(),
949        ))
950    }
951}
952
953/// Matching-symbols request.
954#[derive(Debug, Clone, PartialEq, Eq)]
955pub struct MatchingSymbolsRequest {
956    /// Request id.
957    pub req_id: i32,
958    /// Search pattern.
959    pub pattern: String,
960}
961
962impl EncodableRequest for MatchingSymbolsRequest {
963    fn message(&self) -> Outgoing {
964        Outgoing::ReqMatchingSymbols
965    }
966
967    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
968        fields.push(self.req_id)?.push(&self.pattern)?;
969        Ok(())
970    }
971
972    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
973        Ok(Some(
974            protobuf::MatchingSymbolsRequest {
975                req_id: Some(self.req_id),
976                pattern: non_empty(self.pattern.clone()),
977            }
978            .encode_to_vec(),
979        ))
980    }
981}
982
983/// Completed-orders request.
984#[derive(Debug, Clone, Copy, PartialEq, Eq)]
985pub struct CompletedOrdersRequest {
986    /// Include API-only orders.
987    pub api_only: bool,
988}
989
990impl EncodableRequest for CompletedOrdersRequest {
991    fn message(&self) -> Outgoing {
992        Outgoing::ReqCompletedOrders
993    }
994
995    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
996        fields.push(self.api_only)?;
997        Ok(())
998    }
999
1000    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1001        Ok(Some(
1002            protobuf::CompletedOrdersRequest {
1003                api_only: Some(self.api_only),
1004            }
1005            .encode_to_vec(),
1006        ))
1007    }
1008}
1009
1010/// Financial-advisor data request.
1011#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1012pub struct FinancialAdvisorRequest {
1013    /// FA data type.
1014    pub fa_data_type: i32,
1015}
1016
1017impl EncodableRequest for FinancialAdvisorRequest {
1018    fn message(&self) -> Outgoing {
1019        Outgoing::ReqFa
1020    }
1021
1022    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1023        fields.push(1)?.push(self.fa_data_type)?;
1024        Ok(())
1025    }
1026
1027    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1028        Ok(Some(
1029            protobuf::FaRequest {
1030                fa_data_type: Some(self.fa_data_type),
1031            }
1032            .encode_to_vec(),
1033        ))
1034    }
1035}
1036
1037/// Financial-advisor replace request.
1038#[derive(Debug, Clone, PartialEq, Eq)]
1039pub struct ReplaceFinancialAdvisorRequest {
1040    /// Request id.
1041    pub req_id: i32,
1042    /// FA data type.
1043    pub fa_data_type: i32,
1044    /// XML payload.
1045    pub xml: String,
1046}
1047
1048impl EncodableRequest for ReplaceFinancialAdvisorRequest {
1049    fn message(&self) -> Outgoing {
1050        Outgoing::ReplaceFa
1051    }
1052
1053    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1054        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
1055    }
1056
1057    fn encode_fields_for_server_version(
1058        &self,
1059        fields: &mut FieldSink,
1060        server_version: i32,
1061    ) -> TwsApiResult<()> {
1062        fields.push(1)?.push(self.fa_data_type)?.push(&self.xml)?;
1063        if server_version >= MIN_SERVER_VER_REPLACE_FA_END {
1064            fields.push(self.req_id)?;
1065        }
1066        Ok(())
1067    }
1068
1069    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1070        Ok(Some(
1071            protobuf::FaReplace {
1072                req_id: Some(self.req_id),
1073                fa_data_type: Some(self.fa_data_type),
1074                xml: non_empty(self.xml.clone()),
1075            }
1076            .encode_to_vec(),
1077        ))
1078    }
1079}
1080
1081/// Head timestamp request.
1082#[derive(Debug, Clone, Default, PartialEq)]
1083pub struct HeadTimestampRequest {
1084    /// Request id.
1085    pub req_id: i32,
1086    /// Contract.
1087    pub contract: Contract,
1088    /// Use regular trading hours.
1089    pub use_rth: bool,
1090    /// Data type.
1091    pub what_to_show: String,
1092    /// Format date.
1093    pub format_date: i32,
1094}
1095
1096impl EncodableRequest for HeadTimestampRequest {
1097    fn message(&self) -> Outgoing {
1098        Outgoing::ReqHeadTimestamp
1099    }
1100
1101    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1102        fields.push(self.req_id)?;
1103        encode_contract_head_or_histogram(fields, &self.contract)?;
1104        fields
1105            .push(self.use_rth)?
1106            .push(&self.what_to_show)?
1107            .push(self.format_date)?;
1108        Ok(())
1109    }
1110
1111    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1112        Ok(Some(
1113            protobuf::HeadTimestampRequest {
1114                req_id: Some(self.req_id),
1115                contract: Some(contract_to_proto(&self.contract, None)),
1116                use_rth: Some(self.use_rth),
1117                what_to_show: non_empty(self.what_to_show.clone()),
1118                format_date: Some(self.format_date),
1119            }
1120            .encode_to_vec(),
1121        ))
1122    }
1123}
1124
1125/// Histogram data request.
1126#[derive(Debug, Clone, Default, PartialEq)]
1127pub struct HistogramDataRequest {
1128    /// Request id.
1129    pub req_id: i32,
1130    /// Contract.
1131    pub contract: Contract,
1132    /// Use regular trading hours.
1133    pub use_rth: bool,
1134    /// Time period.
1135    pub time_period: String,
1136}
1137
1138impl EncodableRequest for HistogramDataRequest {
1139    fn message(&self) -> Outgoing {
1140        Outgoing::ReqHistogramData
1141    }
1142
1143    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1144        fields.push(self.req_id)?;
1145        encode_contract_head_or_histogram(fields, &self.contract)?;
1146        fields.push(self.use_rth)?.push(&self.time_period)?;
1147        Ok(())
1148    }
1149
1150    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1151        Ok(Some(
1152            protobuf::HistogramDataRequest {
1153                req_id: Some(self.req_id),
1154                contract: Some(contract_to_proto(&self.contract, None)),
1155                use_rth: Some(self.use_rth),
1156                time_period: non_empty(self.time_period.clone()),
1157            }
1158            .encode_to_vec(),
1159        ))
1160    }
1161}
1162
1163/// Real-time bars request.
1164#[derive(Debug, Clone, Default, PartialEq)]
1165pub struct RealTimeBarsRequest {
1166    /// Request id.
1167    pub req_id: i32,
1168    /// Contract.
1169    pub contract: Contract,
1170    /// Bar size.
1171    pub bar_size: i32,
1172    /// Data type.
1173    pub what_to_show: String,
1174    /// Use regular trading hours.
1175    pub use_rth: bool,
1176    /// Request options.
1177    pub options: Vec<TagValue>,
1178}
1179
1180impl EncodableRequest for RealTimeBarsRequest {
1181    fn message(&self) -> Outgoing {
1182        Outgoing::ReqRealTimeBars
1183    }
1184
1185    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1186        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
1187    }
1188
1189    fn encode_fields_for_server_version(
1190        &self,
1191        fields: &mut FieldSink,
1192        server_version: i32,
1193    ) -> TwsApiResult<()> {
1194        fields.push(3)?.push(self.req_id)?;
1195        encode_contract_real_time_bars(fields, &self.contract, server_version)?;
1196        fields
1197            .push(self.bar_size)?
1198            .push(&self.what_to_show)?
1199            .push(self.use_rth)?;
1200        if server_version >= MIN_SERVER_VER_LINKING {
1201            fields.push(tag_values_to_tws_options(&self.options))?;
1202        }
1203        Ok(())
1204    }
1205
1206    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1207        Ok(Some(
1208            protobuf::RealTimeBarsRequest {
1209                req_id: Some(self.req_id),
1210                contract: Some(contract_to_proto(&self.contract, None)),
1211                bar_size: Some(self.bar_size),
1212                what_to_show: non_empty(self.what_to_show.clone()),
1213                use_rth: Some(self.use_rth),
1214                real_time_bars_options: tag_values_to_map(&self.options),
1215            }
1216            .encode_to_vec(),
1217        ))
1218    }
1219}
1220
1221/// Subscribe to display-group events.
1222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1223pub struct SubscribeToGroupEventsRequest {
1224    /// Request id.
1225    pub req_id: i32,
1226    /// Group id.
1227    pub group_id: i32,
1228}
1229
1230impl EncodableRequest for SubscribeToGroupEventsRequest {
1231    fn message(&self) -> Outgoing {
1232        Outgoing::SubscribeToGroupEvents
1233    }
1234
1235    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1236        fields.push(1)?.push(self.req_id)?.push(self.group_id)?;
1237        Ok(())
1238    }
1239
1240    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1241        Ok(Some(
1242            protobuf::SubscribeToGroupEventsRequest {
1243                req_id: Some(self.req_id),
1244                group_id: Some(self.group_id),
1245            }
1246            .encode_to_vec(),
1247        ))
1248    }
1249}
1250
1251/// Update display group.
1252#[derive(Debug, Clone, PartialEq, Eq)]
1253pub struct UpdateDisplayGroupRequest {
1254    /// Request id.
1255    pub req_id: i32,
1256    /// Contract info.
1257    pub contract_info: String,
1258}
1259
1260impl EncodableRequest for UpdateDisplayGroupRequest {
1261    fn message(&self) -> Outgoing {
1262        Outgoing::UpdateDisplayGroup
1263    }
1264
1265    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1266        fields
1267            .push(1)?
1268            .push(self.req_id)?
1269            .push(&self.contract_info)?;
1270        Ok(())
1271    }
1272
1273    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1274        Ok(Some(
1275            protobuf::UpdateDisplayGroupRequest {
1276                req_id: Some(self.req_id),
1277                contract_info: non_empty(self.contract_info.clone()),
1278            }
1279            .encode_to_vec(),
1280        ))
1281    }
1282}
1283
1284/// Verify request.
1285#[derive(Debug, Clone, PartialEq, Eq)]
1286pub struct VerifyRequest {
1287    /// API name.
1288    pub api_name: String,
1289    /// API version.
1290    pub api_version: String,
1291}
1292
1293impl EncodableRequest for VerifyRequest {
1294    fn message(&self) -> Outgoing {
1295        Outgoing::VerifyRequest
1296    }
1297
1298    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1299        fields
1300            .push(1)?
1301            .push(&self.api_name)?
1302            .push(&self.api_version)?;
1303        Ok(())
1304    }
1305
1306    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1307        Ok(Some(
1308            protobuf::VerifyRequest {
1309                api_name: non_empty(self.api_name.clone()),
1310                api_version: non_empty(self.api_version.clone()),
1311            }
1312            .encode_to_vec(),
1313        ))
1314    }
1315}
1316
1317/// Verify message request.
1318#[derive(Debug, Clone, PartialEq, Eq)]
1319pub struct VerifyMessageRequest {
1320    /// API data.
1321    pub api_data: String,
1322}
1323
1324impl EncodableRequest for VerifyMessageRequest {
1325    fn message(&self) -> Outgoing {
1326        Outgoing::VerifyMessage
1327    }
1328
1329    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1330        fields.push(1)?.push(&self.api_data)?;
1331        Ok(())
1332    }
1333
1334    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1335        Ok(Some(
1336            protobuf::VerifyMessageRequest {
1337                api_data: non_empty(self.api_data.clone()),
1338            }
1339            .encode_to_vec(),
1340        ))
1341    }
1342}
1343
1344/// Verify-and-auth request.
1345#[derive(Debug, Clone, PartialEq, Eq)]
1346pub struct VerifyAndAuthRequest {
1347    /// API name.
1348    pub api_name: String,
1349    /// API version.
1350    pub api_version: String,
1351    /// Opaque ISV key.
1352    pub opaque_isv_key: String,
1353}
1354
1355impl EncodableRequest for VerifyAndAuthRequest {
1356    fn message(&self) -> Outgoing {
1357        Outgoing::VerifyAndAuthRequest
1358    }
1359
1360    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1361        fields
1362            .push(1)?
1363            .push(&self.api_name)?
1364            .push(&self.api_version)?
1365            .push(&self.opaque_isv_key)?;
1366        Ok(())
1367    }
1368}
1369
1370/// Verify-and-auth message request.
1371#[derive(Debug, Clone, PartialEq, Eq)]
1372pub struct VerifyAndAuthMessageRequest {
1373    /// API data.
1374    pub api_data: String,
1375    /// Challenge response.
1376    pub xyz_response: String,
1377}
1378
1379impl EncodableRequest for VerifyAndAuthMessageRequest {
1380    fn message(&self) -> Outgoing {
1381        Outgoing::VerifyAndAuthMessage
1382    }
1383
1384    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1385        fields
1386            .push(1)?
1387            .push(&self.api_data)?
1388            .push(&self.xyz_response)?;
1389        Ok(())
1390    }
1391}
1392
1393/// WSH event data request.
1394#[derive(Debug, Clone, Default, PartialEq, Eq)]
1395pub struct WshEventDataRequest {
1396    /// Request id.
1397    pub req_id: i32,
1398    /// Contract id.
1399    pub con_id: i32,
1400    /// Filter JSON.
1401    pub filter: String,
1402    /// Fill watchlist flag.
1403    pub fill_watchlist: bool,
1404    /// Fill portfolio flag.
1405    pub fill_portfolio: bool,
1406    /// Fill competitors flag.
1407    pub fill_competitors: bool,
1408    /// Start date.
1409    pub start_date: String,
1410    /// End date.
1411    pub end_date: String,
1412    /// Total limit.
1413    pub total_limit: i32,
1414}
1415
1416impl EncodableRequest for WshEventDataRequest {
1417    fn message(&self) -> Outgoing {
1418        Outgoing::ReqWshEventData
1419    }
1420
1421    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1422        fields
1423            .push(self.req_id)?
1424            .push(self.con_id)?
1425            .push(&self.filter)?
1426            .push(self.fill_watchlist)?
1427            .push(self.fill_portfolio)?
1428            .push(self.fill_competitors)?
1429            .push(&self.start_date)?
1430            .push(&self.end_date)?
1431            .push(self.total_limit)?;
1432        Ok(())
1433    }
1434
1435    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1436        Ok(Some(
1437            protobuf::WshEventDataRequest {
1438                req_id: Some(self.req_id),
1439                con_id: valid_i32(self.con_id),
1440                filter: non_empty(self.filter.clone()),
1441                fill_watchlist: self.fill_watchlist.then_some(true),
1442                fill_portfolio: self.fill_portfolio.then_some(true),
1443                fill_competitors: self.fill_competitors.then_some(true),
1444                start_date: non_empty(self.start_date.clone()),
1445                end_date: non_empty(self.end_date.clone()),
1446                total_limit: valid_i32(self.total_limit),
1447            }
1448            .encode_to_vec(),
1449        ))
1450    }
1451}
1452
1453/// Tick-by-tick data request.
1454#[derive(Debug, Clone, Default, PartialEq)]
1455pub struct TickByTickRequest {
1456    /// Request id.
1457    pub req_id: i32,
1458    /// Contract.
1459    pub contract: Contract,
1460    /// Tick type.
1461    pub tick_type: String,
1462    /// Number of ticks.
1463    pub number_of_ticks: i32,
1464    /// Ignore size flag.
1465    pub ignore_size: bool,
1466}
1467
1468impl EncodableRequest for TickByTickRequest {
1469    fn message(&self) -> Outgoing {
1470        Outgoing::ReqTickByTickData
1471    }
1472
1473    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1474        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
1475    }
1476
1477    fn encode_fields_for_server_version(
1478        &self,
1479        fields: &mut FieldSink,
1480        server_version: i32,
1481    ) -> TwsApiResult<()> {
1482        ensure_server_version(server_version, MIN_SERVER_VER_TICK_BY_TICK)?;
1483        fields
1484            .push(self.req_id)?
1485            .push(self.contract.con_id)?
1486            .push(&self.contract.symbol)?
1487            .push(&self.contract.sec_type)?
1488            .push(&self.contract.last_trade_date_or_contract_month)?
1489            .push_empty(self.contract.strike)?
1490            .push(&self.contract.right)?
1491            .push(&self.contract.multiplier)?
1492            .push(&self.contract.exchange)?
1493            .push(&self.contract.primary_exchange)?
1494            .push(&self.contract.currency)?
1495            .push(&self.contract.local_symbol)?
1496            .push(&self.contract.trading_class)?
1497            .push(&self.tick_type)?;
1498        if server_version >= MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE {
1499            fields.push(self.number_of_ticks)?.push(self.ignore_size)?;
1500        }
1501        Ok(())
1502    }
1503
1504    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1505        Ok(Some(
1506            protobuf::TickByTickRequest {
1507                req_id: Some(self.req_id),
1508                contract: Some(contract_to_proto(&self.contract, None)),
1509                tick_type: non_empty(self.tick_type.clone()),
1510                number_of_ticks: valid_i32(self.number_of_ticks),
1511                ignore_size: self.ignore_size.then_some(true),
1512            }
1513            .encode_to_vec(),
1514        ))
1515    }
1516}
1517
1518/// Calculate implied volatility request.
1519#[derive(Debug, Clone, Default, PartialEq)]
1520pub struct CalculateImpliedVolatilityRequest {
1521    /// Request id.
1522    pub req_id: i32,
1523    /// Contract.
1524    pub contract: Contract,
1525    /// Option price.
1526    pub option_price: f64,
1527    /// Underlying price.
1528    pub under_price: f64,
1529    /// Request options.
1530    pub options: Vec<TagValue>,
1531}
1532
1533impl EncodableRequest for CalculateImpliedVolatilityRequest {
1534    fn message(&self) -> Outgoing {
1535        Outgoing::ReqCalcImpliedVolat
1536    }
1537
1538    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1539        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
1540    }
1541
1542    fn encode_fields_for_server_version(
1543        &self,
1544        fields: &mut FieldSink,
1545        server_version: i32,
1546    ) -> TwsApiResult<()> {
1547        ensure_server_version(server_version, MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT)?;
1548        fields.push(3)?.push(self.req_id)?;
1549        encode_contract_for_calculation(fields, &self.contract, server_version)?;
1550        fields.push(self.option_price)?.push(self.under_price)?;
1551        if server_version >= MIN_SERVER_VER_LINKING {
1552            fields.push(tag_values_to_tws_options(&self.options))?;
1553        }
1554        Ok(())
1555    }
1556
1557    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1558        Ok(Some(
1559            protobuf::CalculateImpliedVolatilityRequest {
1560                req_id: Some(self.req_id),
1561                contract: Some(contract_to_proto(&self.contract, None)),
1562                option_price: valid_f64(self.option_price),
1563                under_price: valid_f64(self.under_price),
1564                implied_volatility_options: tag_values_to_map(&self.options),
1565            }
1566            .encode_to_vec(),
1567        ))
1568    }
1569}
1570
1571/// Calculate option price request.
1572#[derive(Debug, Clone, Default, PartialEq)]
1573pub struct CalculateOptionPriceRequest {
1574    /// Request id.
1575    pub req_id: i32,
1576    /// Contract.
1577    pub contract: Contract,
1578    /// Volatility.
1579    pub volatility: f64,
1580    /// Underlying price.
1581    pub under_price: f64,
1582    /// Request options.
1583    pub options: Vec<TagValue>,
1584}
1585
1586impl EncodableRequest for CalculateOptionPriceRequest {
1587    fn message(&self) -> Outgoing {
1588        Outgoing::ReqCalcOptionPrice
1589    }
1590
1591    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1592        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
1593    }
1594
1595    fn encode_fields_for_server_version(
1596        &self,
1597        fields: &mut FieldSink,
1598        server_version: i32,
1599    ) -> TwsApiResult<()> {
1600        ensure_server_version(server_version, MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT)?;
1601        fields.push(3)?.push(self.req_id)?;
1602        encode_contract_for_calculation(fields, &self.contract, server_version)?;
1603        fields.push(self.volatility)?.push(self.under_price)?;
1604        if server_version >= MIN_SERVER_VER_LINKING {
1605            fields.push(tag_values_to_tws_options(&self.options))?;
1606        }
1607        Ok(())
1608    }
1609
1610    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1611        Ok(Some(
1612            protobuf::CalculateOptionPriceRequest {
1613                req_id: Some(self.req_id),
1614                contract: Some(contract_to_proto(&self.contract, None)),
1615                volatility: valid_f64(self.volatility),
1616                under_price: valid_f64(self.under_price),
1617                option_price_options: tag_values_to_map(&self.options),
1618            }
1619            .encode_to_vec(),
1620        ))
1621    }
1622}
1623
1624/// Exercise options request.
1625#[derive(Debug, Clone, Default, PartialEq)]
1626pub struct ExerciseOptionsRequest {
1627    /// Order/request id.
1628    pub order_id: i32,
1629    /// Contract.
1630    pub contract: Contract,
1631    /// Exercise action.
1632    pub exercise_action: i32,
1633    /// Exercise quantity.
1634    pub exercise_quantity: i32,
1635    /// Account.
1636    pub account: String,
1637    /// Override natural action.
1638    pub override_system_action: bool,
1639    /// Manual order time.
1640    pub manual_order_time: String,
1641    /// Customer account.
1642    pub customer_account: String,
1643    /// Professional customer flag.
1644    pub professional_customer: bool,
1645}
1646
1647impl EncodableRequest for ExerciseOptionsRequest {
1648    fn message(&self) -> Outgoing {
1649        Outgoing::ExerciseOptions
1650    }
1651
1652    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1653        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
1654    }
1655
1656    fn encode_fields_for_server_version(
1657        &self,
1658        fields: &mut FieldSink,
1659        server_version: i32,
1660    ) -> TwsApiResult<()> {
1661        fields.push(2)?.push(self.order_id)?;
1662        if server_version >= MIN_SERVER_VER_TRADING_CLASS {
1663            fields.push(self.contract.con_id)?;
1664        }
1665        fields
1666            .push(&self.contract.symbol)?
1667            .push(&self.contract.sec_type)?
1668            .push(&self.contract.last_trade_date_or_contract_month)?
1669            .push_empty(self.contract.strike)?
1670            .push(&self.contract.right)?
1671            .push(&self.contract.multiplier)?
1672            .push(&self.contract.exchange)?
1673            .push(&self.contract.currency)?
1674            .push(&self.contract.local_symbol)?;
1675        if server_version >= MIN_SERVER_VER_TRADING_CLASS {
1676            fields.push(&self.contract.trading_class)?;
1677        }
1678        fields
1679            .push(self.exercise_action)?
1680            .push(self.exercise_quantity)?
1681            .push(&self.account)?
1682            .push(self.override_system_action)?;
1683        if server_version >= MIN_SERVER_VER_MANUAL_ORDER_TIME_EXERCISE_OPTIONS {
1684            fields.push(&self.manual_order_time)?;
1685        }
1686        if server_version >= MIN_SERVER_VER_CUSTOMER_ACCOUNT {
1687            fields.push(&self.customer_account)?;
1688        }
1689        if server_version >= MIN_SERVER_VER_PROFESSIONAL_CUSTOMER {
1690            fields.push(self.professional_customer)?;
1691        }
1692        Ok(())
1693    }
1694
1695    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1696        Ok(Some(
1697            protobuf::ExerciseOptionsRequest {
1698                order_id: Some(self.order_id),
1699                contract: Some(contract_to_proto(&self.contract, None)),
1700                exercise_action: Some(self.exercise_action),
1701                exercise_quantity: Some(self.exercise_quantity),
1702                account: non_empty(self.account.clone()),
1703                r#override: Some(self.override_system_action),
1704                manual_order_time: non_empty(self.manual_order_time.clone()),
1705                customer_account: non_empty(self.customer_account.clone()),
1706                professional_customer: self.professional_customer.then_some(true),
1707            }
1708            .encode_to_vec(),
1709        ))
1710    }
1711}
1712
1713/// Historical ticks request.
1714#[derive(Debug, Clone, Default, PartialEq)]
1715pub struct HistoricalTicksRequest {
1716    /// Request id.
1717    pub req_id: i32,
1718    /// Contract.
1719    pub contract: Contract,
1720    /// Start date/time.
1721    pub start_date_time: String,
1722    /// End date/time.
1723    pub end_date_time: String,
1724    /// Number of ticks.
1725    pub number_of_ticks: i32,
1726    /// Data type.
1727    pub what_to_show: String,
1728    /// Use regular trading hours.
1729    pub use_rth: bool,
1730    /// Ignore size flag.
1731    pub ignore_size: bool,
1732    /// Request options.
1733    pub misc_options: Vec<TagValue>,
1734}
1735
1736impl EncodableRequest for HistoricalTicksRequest {
1737    fn message(&self) -> Outgoing {
1738        Outgoing::ReqHistoricalTicks
1739    }
1740
1741    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1742        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
1743    }
1744
1745    fn encode_fields_for_server_version(
1746        &self,
1747        fields: &mut FieldSink,
1748        server_version: i32,
1749    ) -> TwsApiResult<()> {
1750        ensure_server_version(server_version, MIN_SERVER_VER_HISTORICAL_TICKS)?;
1751        fields
1752            .push(self.req_id)?
1753            .push(self.contract.con_id)?
1754            .push(&self.contract.symbol)?
1755            .push(&self.contract.sec_type)?
1756            .push(&self.contract.last_trade_date_or_contract_month)?
1757            .push_empty(self.contract.strike)?
1758            .push(&self.contract.right)?
1759            .push(&self.contract.multiplier)?
1760            .push(&self.contract.exchange)?
1761            .push(&self.contract.primary_exchange)?
1762            .push(&self.contract.currency)?
1763            .push(&self.contract.local_symbol)?
1764            .push(&self.contract.trading_class)?
1765            .push(self.contract.include_expired)?
1766            .push(&self.start_date_time)?
1767            .push(&self.end_date_time)?
1768            .push(self.number_of_ticks)?
1769            .push(&self.what_to_show)?
1770            .push(self.use_rth)?
1771            .push(self.ignore_size)?
1772            .push(tag_values_to_tws_options(&self.misc_options))?;
1773        Ok(())
1774    }
1775
1776    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1777        Ok(Some(
1778            protobuf::HistoricalTicksRequest {
1779                req_id: Some(self.req_id),
1780                contract: Some(contract_to_proto(&self.contract, None)),
1781                start_date_time: non_empty(self.start_date_time.clone()),
1782                end_date_time: non_empty(self.end_date_time.clone()),
1783                number_of_ticks: Some(self.number_of_ticks),
1784                what_to_show: non_empty(self.what_to_show.clone()),
1785                use_rth: Some(self.use_rth),
1786                ignore_size: self.ignore_size.then_some(true),
1787                misc_options: tag_values_to_map(&self.misc_options),
1788            }
1789            .encode_to_vec(),
1790        ))
1791    }
1792}
1793
1794impl EncodableRequest for PnlSingleRequest {
1795    fn message(&self) -> Outgoing {
1796        Outgoing::ReqPnlSingle
1797    }
1798
1799    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1800        fields
1801            .push(self.req_id)?
1802            .push(&self.account)?
1803            .push(&self.model_code)?
1804            .push(self.con_id)?;
1805        Ok(())
1806    }
1807
1808    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1809        Ok(Some(
1810            protobuf::PnLSingleRequest {
1811                req_id: Some(self.req_id),
1812                account: non_empty(self.account.clone()),
1813                model_code: non_empty(self.model_code.clone()),
1814                con_id: Some(self.con_id),
1815            }
1816            .encode_to_vec(),
1817        ))
1818    }
1819}
1820
1821impl EncodableRequest for IdRequest {
1822    fn message(&self) -> Outgoing {
1823        self.message
1824    }
1825
1826    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1827        if let Some(version) = self.version {
1828            fields.push(version)?;
1829        }
1830        fields.push(self.req_id)?;
1831        Ok(())
1832    }
1833
1834    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
1835        let payload = match self.message {
1836            Outgoing::ReqIds => protobuf::IdsRequest {
1837                num_ids: Some(self.req_id),
1838            }
1839            .encode_to_vec(),
1840            Outgoing::CancelMktData => protobuf::CancelMarketData {
1841                req_id: Some(self.req_id),
1842            }
1843            .encode_to_vec(),
1844            Outgoing::CancelMktDepth => protobuf::CancelMarketDepth {
1845                req_id: Some(self.req_id),
1846                is_smart_depth: None,
1847            }
1848            .encode_to_vec(),
1849            Outgoing::CancelAccountSummary => protobuf::CancelAccountSummary {
1850                req_id: Some(self.req_id),
1851            }
1852            .encode_to_vec(),
1853            Outgoing::CancelPositionsMulti => protobuf::CancelPositionsMulti {
1854                req_id: Some(self.req_id),
1855            }
1856            .encode_to_vec(),
1857            Outgoing::CancelAccountUpdatesMulti => protobuf::CancelAccountUpdatesMulti {
1858                req_id: Some(self.req_id),
1859            }
1860            .encode_to_vec(),
1861            Outgoing::CancelPnl => protobuf::CancelPnL {
1862                req_id: Some(self.req_id),
1863            }
1864            .encode_to_vec(),
1865            Outgoing::CancelPnlSingle => protobuf::CancelPnLSingle {
1866                req_id: Some(self.req_id),
1867            }
1868            .encode_to_vec(),
1869            Outgoing::CancelCalcImpliedVolat => protobuf::CancelCalculateImpliedVolatility {
1870                req_id: Some(self.req_id),
1871            }
1872            .encode_to_vec(),
1873            Outgoing::CancelCalcOptionPrice => protobuf::CancelCalculateOptionPrice {
1874                req_id: Some(self.req_id),
1875            }
1876            .encode_to_vec(),
1877            Outgoing::CancelContractData => protobuf::CancelContractData {
1878                req_id: Some(self.req_id),
1879            }
1880            .encode_to_vec(),
1881            Outgoing::CancelHistoricalData => protobuf::CancelHistoricalData {
1882                req_id: Some(self.req_id),
1883            }
1884            .encode_to_vec(),
1885            Outgoing::CancelScannerSubscription => protobuf::CancelScannerSubscription {
1886                req_id: Some(self.req_id),
1887            }
1888            .encode_to_vec(),
1889            Outgoing::CancelTickByTickData => protobuf::CancelTickByTick {
1890                req_id: Some(self.req_id),
1891            }
1892            .encode_to_vec(),
1893            Outgoing::CancelWshMetaData => protobuf::CancelWshMetaData {
1894                req_id: Some(self.req_id),
1895            }
1896            .encode_to_vec(),
1897            Outgoing::CancelWshEventData => protobuf::CancelWshEventData {
1898                req_id: Some(self.req_id),
1899            }
1900            .encode_to_vec(),
1901            Outgoing::CancelHeadTimestamp => protobuf::CancelHeadTimestamp {
1902                req_id: Some(self.req_id),
1903            }
1904            .encode_to_vec(),
1905            Outgoing::CancelHistogramData => protobuf::CancelHistogramData {
1906                req_id: Some(self.req_id),
1907            }
1908            .encode_to_vec(),
1909            Outgoing::CancelRealTimeBars => protobuf::CancelRealTimeBars {
1910                req_id: Some(self.req_id),
1911            }
1912            .encode_to_vec(),
1913            Outgoing::QueryDisplayGroups => protobuf::QueryDisplayGroupsRequest {
1914                req_id: Some(self.req_id),
1915            }
1916            .encode_to_vec(),
1917            Outgoing::UnsubscribeFromGroupEvents => protobuf::UnsubscribeFromGroupEventsRequest {
1918                req_id: Some(self.req_id),
1919            }
1920            .encode_to_vec(),
1921            Outgoing::ReqMarketRule => protobuf::MarketRuleRequest {
1922                market_rule_id: Some(self.req_id),
1923            }
1924            .encode_to_vec(),
1925            Outgoing::ReqSoftDollarTiers => protobuf::SoftDollarTiersRequest {
1926                req_id: Some(self.req_id),
1927            }
1928            .encode_to_vec(),
1929            Outgoing::ReqWshMetaData => protobuf::WshMetaDataRequest {
1930                req_id: Some(self.req_id),
1931            }
1932            .encode_to_vec(),
1933            Outgoing::ReqUserInfo => protobuf::UserInfoRequest {
1934                req_id: Some(self.req_id),
1935            }
1936            .encode_to_vec(),
1937            _ => return Ok(None),
1938        };
1939        Ok(Some(payload))
1940    }
1941}
1942
1943/// Market data request.
1944#[derive(Debug, Clone, Default, PartialEq)]
1945pub struct MarketDataRequest {
1946    /// Request id.
1947    pub req_id: TickerId,
1948    /// Contract.
1949    pub contract: Contract,
1950    /// Generic tick list.
1951    pub generic_tick_list: String,
1952    /// Snapshot flag.
1953    pub snapshot: bool,
1954    /// Regulatory snapshot flag.
1955    pub regulatory_snapshot: bool,
1956    /// Market data options.
1957    pub market_data_options: Vec<TagValue>,
1958}
1959
1960impl EncodableRequest for MarketDataRequest {
1961    fn message(&self) -> Outgoing {
1962        Outgoing::ReqMktData
1963    }
1964
1965    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
1966        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
1967    }
1968
1969    fn encode_fields_for_server_version(
1970        &self,
1971        fields: &mut FieldSink,
1972        server_version: i32,
1973    ) -> TwsApiResult<()> {
1974        fields.push(11)?.push(self.req_id)?;
1975        if server_version >= MIN_SERVER_VER_REQ_MKT_DATA_CONID {
1976            fields.push(self.contract.con_id)?;
1977        }
1978        encode_contract_market_data(fields, &self.contract, server_version)?;
1979
1980        if self.contract.sec_type == "BAG" {
1981            fields.push(self.contract.combo_legs.len())?;
1982            for leg in &self.contract.combo_legs {
1983                fields
1984                    .push(leg.con_id)?
1985                    .push(leg.ratio)?
1986                    .push(&leg.action)?
1987                    .push(&leg.exchange)?;
1988            }
1989        }
1990
1991        if server_version >= MIN_SERVER_VER_DELTA_NEUTRAL {
1992            if let Some(delta_neutral) = &self.contract.delta_neutral_contract {
1993                fields
1994                    .push(true)?
1995                    .push(delta_neutral.con_id)?
1996                    .push(delta_neutral.delta)?
1997                    .push(delta_neutral.price)?;
1998            } else {
1999                fields.push(false)?;
2000            }
2001        }
2002
2003        fields.push(&self.generic_tick_list)?.push(self.snapshot)?;
2004        if server_version >= MIN_SERVER_VER_REQ_SMART_COMPONENTS {
2005            fields.push(self.regulatory_snapshot)?;
2006        }
2007        if server_version >= MIN_SERVER_VER_LINKING {
2008            fields.push(tag_values_to_tws_options(&self.market_data_options))?;
2009        }
2010        Ok(())
2011    }
2012
2013    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2014        let mut msg = protobuf::MarketDataRequest {
2015            req_id: Some(self.req_id),
2016            contract: Some(contract_to_proto(&self.contract, None)),
2017            generic_tick_list: non_empty(self.generic_tick_list.clone()),
2018            snapshot: self.snapshot.then_some(true),
2019            regulatory_snapshot: self.regulatory_snapshot.then_some(true),
2020            market_data_options: tag_values_to_map(&self.market_data_options),
2021        };
2022        if msg.market_data_options.is_empty() {
2023            msg.market_data_options = Default::default();
2024        }
2025        Ok(Some(msg.encode_to_vec()))
2026    }
2027}
2028
2029/// Market depth request.
2030#[derive(Debug, Clone, Default, PartialEq)]
2031pub struct MarketDepthRequest {
2032    /// Request id.
2033    pub req_id: TickerId,
2034    /// Contract.
2035    pub contract: Contract,
2036    /// Number of rows.
2037    pub num_rows: i32,
2038    /// Smart depth.
2039    pub is_smart_depth: bool,
2040    /// Market depth options.
2041    pub market_depth_options: Vec<TagValue>,
2042}
2043
2044impl EncodableRequest for MarketDepthRequest {
2045    fn message(&self) -> Outgoing {
2046        Outgoing::ReqMktDepth
2047    }
2048
2049    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2050        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2051    }
2052
2053    fn encode_fields_for_server_version(
2054        &self,
2055        fields: &mut FieldSink,
2056        server_version: i32,
2057    ) -> TwsApiResult<()> {
2058        fields.push(5)?.push(self.req_id)?;
2059        if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2060            fields.push(self.contract.con_id)?;
2061        }
2062        fields
2063            .push(&self.contract.symbol)?
2064            .push(&self.contract.sec_type)?
2065            .push(&self.contract.last_trade_date_or_contract_month)?
2066            .push_empty(self.contract.strike)?
2067            .push(&self.contract.right)?
2068            .push(&self.contract.multiplier)?
2069            .push(&self.contract.exchange)?;
2070        if server_version >= MIN_SERVER_VER_MKT_DEPTH_PRIM_EXCHANGE {
2071            fields.push(&self.contract.primary_exchange)?;
2072        }
2073        fields
2074            .push(&self.contract.currency)?
2075            .push(&self.contract.local_symbol)?;
2076        if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2077            fields.push(&self.contract.trading_class)?;
2078        }
2079        fields.push(self.num_rows)?;
2080        if server_version >= MIN_SERVER_VER_SMART_DEPTH {
2081            fields.push(self.is_smart_depth)?;
2082        }
2083        if server_version >= MIN_SERVER_VER_LINKING {
2084            fields.push(tag_values_to_tws_options(&self.market_depth_options))?;
2085        }
2086        Ok(())
2087    }
2088
2089    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2090        let msg = protobuf::MarketDepthRequest {
2091            req_id: Some(self.req_id),
2092            contract: Some(contract_to_proto(&self.contract, None)),
2093            num_rows: Some(self.num_rows),
2094            is_smart_depth: self.is_smart_depth.then_some(true),
2095            market_depth_options: tag_values_to_map(&self.market_depth_options),
2096        };
2097        Ok(Some(msg.encode_to_vec()))
2098    }
2099}
2100
2101/// Place order request.
2102#[derive(Debug, Clone, Default, PartialEq)]
2103pub struct PlaceOrderRequest {
2104    /// Order id.
2105    pub order_id: i32,
2106    /// Contract.
2107    pub contract: Contract,
2108    /// Order.
2109    pub order: Order,
2110    /// Extra raw fields for TWS server-version-specific order tail fields.
2111    pub extra_fields: String,
2112}
2113
2114impl EncodableRequest for PlaceOrderRequest {
2115    fn message(&self) -> Outgoing {
2116        Outgoing::PlaceOrder
2117    }
2118
2119    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2120        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2121    }
2122
2123    fn encode_fields_for_server_version(
2124        &self,
2125        fields: &mut FieldSink,
2126        server_version: i32,
2127    ) -> TwsApiResult<()> {
2128        encode_place_order_fields(
2129            fields,
2130            server_version,
2131            self.order_id,
2132            &self.contract,
2133            &self.order,
2134            &self.extra_fields,
2135        )
2136    }
2137
2138    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2139        let msg = protobuf::PlaceOrderRequest {
2140            order_id: Some(self.order_id),
2141            contract: Some(contract_to_proto(&self.contract, Some(&self.order))),
2142            order: Some(order_to_proto(&self.order)),
2143            attached_orders: attached_orders_to_proto(&self.order),
2144        };
2145        Ok(Some(msg.encode_to_vec()))
2146    }
2147}
2148
2149/// Cancel order request.
2150#[derive(Debug, Clone, Default, PartialEq, Eq)]
2151pub struct CancelOrderRequest {
2152    /// Order id.
2153    pub order_id: i32,
2154    /// Cancel metadata.
2155    pub order_cancel: OrderCancel,
2156}
2157
2158impl EncodableRequest for CancelOrderRequest {
2159    fn message(&self) -> Outgoing {
2160        Outgoing::CancelOrder
2161    }
2162
2163    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2164        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2165    }
2166
2167    fn encode_fields_for_server_version(
2168        &self,
2169        fields: &mut FieldSink,
2170        server_version: i32,
2171    ) -> TwsApiResult<()> {
2172        if server_version < MIN_SERVER_VER_CME_TAGGING_FIELDS {
2173            fields.push(1)?;
2174        }
2175        fields.push(self.order_id)?;
2176        if server_version >= MIN_SERVER_VER_MANUAL_ORDER_TIME {
2177            fields.push(&self.order_cancel.manual_order_cancel_time)?;
2178        }
2179        if (MIN_SERVER_VER_RFQ_FIELDS..MIN_SERVER_VER_UNDO_RFQ_FIELDS).contains(&server_version) {
2180            fields.push("")?.push("")?.push_empty(UNSET_INTEGER)?;
2181        }
2182        if server_version >= MIN_SERVER_VER_CME_TAGGING_FIELDS {
2183            fields
2184                .push(&self.order_cancel.ext_operator)?
2185                .push_empty(self.order_cancel.manual_order_indicator)?;
2186        }
2187        Ok(())
2188    }
2189
2190    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2191        let msg = protobuf::CancelOrderRequest {
2192            order_id: Some(self.order_id),
2193            order_cancel: Some(order_cancel_to_proto(&self.order_cancel)),
2194        };
2195        Ok(Some(msg.encode_to_vec()))
2196    }
2197}
2198
2199/// Execution request.
2200#[derive(Debug, Clone, Default, PartialEq, Eq)]
2201pub struct ExecutionRequest {
2202    /// Request id.
2203    pub req_id: i32,
2204    /// Execution filter.
2205    pub filter: ExecutionFilter,
2206}
2207
2208impl EncodableRequest for ExecutionRequest {
2209    fn message(&self) -> Outgoing {
2210        Outgoing::ReqExecutions
2211    }
2212
2213    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2214        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2215    }
2216
2217    fn encode_fields_for_server_version(
2218        &self,
2219        fields: &mut FieldSink,
2220        server_version: i32,
2221    ) -> TwsApiResult<()> {
2222        fields.push(3)?;
2223        if server_version >= MIN_SERVER_VER_EXECUTION_DATA_CHAIN {
2224            fields.push(self.req_id)?;
2225        }
2226        fields
2227            .push(self.filter.client_id)?
2228            .push(&self.filter.acct_code)?
2229            .push(&self.filter.time)?
2230            .push(&self.filter.symbol)?
2231            .push(&self.filter.sec_type)?
2232            .push(&self.filter.exchange)?
2233            .push(&self.filter.side)?;
2234        if server_version >= MIN_SERVER_VER_PARAMETRIZED_DAYS_OF_EXECUTIONS {
2235            fields
2236                .push(self.filter.last_n_days)?
2237                .push(self.filter.specific_dates.len())?;
2238            for date in &self.filter.specific_dates {
2239                fields.push(*date)?;
2240            }
2241        }
2242        Ok(())
2243    }
2244
2245    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2246        let msg = protobuf::ExecutionRequest {
2247            req_id: Some(self.req_id),
2248            execution_filter: Some(execution_filter_to_proto(&self.filter)),
2249        };
2250        Ok(Some(msg.encode_to_vec()))
2251    }
2252}
2253
2254/// Contract details request.
2255#[derive(Debug, Clone, Default, PartialEq)]
2256pub struct ContractDetailsRequest {
2257    /// Request id.
2258    pub req_id: i32,
2259    /// Contract.
2260    pub contract: Contract,
2261}
2262
2263impl EncodableRequest for ContractDetailsRequest {
2264    fn message(&self) -> Outgoing {
2265        Outgoing::ReqContractData
2266    }
2267
2268    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2269        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2270    }
2271
2272    fn encode_fields_for_server_version(
2273        &self,
2274        fields: &mut FieldSink,
2275        server_version: i32,
2276    ) -> TwsApiResult<()> {
2277        // reqContractDetails remains a versioned legacy request even on
2278        // current TWS/Gateway servers. Omitting version 8 shifts req_id and
2279        // makes the symbol get decoded as Con Id.
2280        fields.push(8)?;
2281        fields.push(self.req_id)?;
2282
2283        fields
2284            .push(self.contract.con_id)?
2285            .push(&self.contract.symbol)?
2286            .push(&self.contract.sec_type)?
2287            .push(&self.contract.last_trade_date_or_contract_month)?
2288            .push_empty(self.contract.strike)?
2289            .push(&self.contract.right)?
2290            .push(&self.contract.multiplier)?;
2291
2292        if server_version >= MIN_SERVER_VER_PRIMARYEXCH {
2293            fields
2294                .push(&self.contract.exchange)?
2295                .push(&self.contract.primary_exchange)?;
2296        } else if server_version >= MIN_SERVER_VER_LINKING {
2297            let exchange = if !self.contract.primary_exchange.is_empty()
2298                && (self.contract.exchange == "BEST" || self.contract.exchange == "SMART")
2299            {
2300                format!(
2301                    "{}:{}",
2302                    self.contract.exchange, self.contract.primary_exchange
2303                )
2304            } else {
2305                self.contract.exchange.clone()
2306            };
2307            fields.push(exchange)?;
2308        }
2309
2310        fields
2311            .push(&self.contract.currency)?
2312            .push(&self.contract.local_symbol)?;
2313        if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2314            fields.push(&self.contract.trading_class)?;
2315        }
2316        fields.push(self.contract.include_expired)?;
2317        if server_version >= MIN_SERVER_VER_SEC_ID_TYPE {
2318            fields
2319                .push(&self.contract.sec_id_type)?
2320                .push(&self.contract.sec_id)?;
2321        }
2322        if server_version >= MIN_SERVER_VER_BOND_ISSUERID {
2323            fields.push(&self.contract.issuer_id)?;
2324        }
2325        Ok(())
2326    }
2327
2328    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2329        let msg = protobuf::ContractDataRequest {
2330            req_id: Some(self.req_id),
2331            contract: Some(contract_to_proto(&self.contract, None)),
2332        };
2333        Ok(Some(msg.encode_to_vec()))
2334    }
2335}
2336
2337/// Historical data request.
2338#[derive(Debug, Clone, Default, PartialEq)]
2339pub struct HistoricalDataRequest {
2340    /// Request id.
2341    pub req_id: i32,
2342    /// Contract.
2343    pub contract: Contract,
2344    /// End date/time.
2345    pub end_date_time: String,
2346    /// Duration string.
2347    pub duration_str: String,
2348    /// Bar size setting.
2349    pub bar_size_setting: String,
2350    /// What to show.
2351    pub what_to_show: String,
2352    /// Use RTH.
2353    pub use_rth: i32,
2354    /// Format date.
2355    pub format_date: i32,
2356    /// Keep up to date.
2357    pub keep_up_to_date: bool,
2358    /// Chart options.
2359    pub chart_options: Vec<TagValue>,
2360}
2361
2362impl EncodableRequest for HistoricalDataRequest {
2363    fn message(&self) -> Outgoing {
2364        Outgoing::ReqHistoricalData
2365    }
2366
2367    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2368        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2369    }
2370
2371    fn encode_fields_for_server_version(
2372        &self,
2373        fields: &mut FieldSink,
2374        server_version: i32,
2375    ) -> TwsApiResult<()> {
2376        if server_version < MIN_SERVER_VER_SYNT_REALTIME_BARS {
2377            fields.push(6)?;
2378        }
2379        fields.push(self.req_id)?;
2380        if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2381            fields.push(self.contract.con_id)?;
2382        }
2383        encode_contract_historical_data(fields, &self.contract, server_version)?;
2384        fields
2385            .push(&self.end_date_time)?
2386            .push(&self.bar_size_setting)?
2387            .push(&self.duration_str)?
2388            .push(self.use_rth)?
2389            .push(&self.what_to_show)?
2390            .push(self.format_date)?;
2391
2392        if self.contract.sec_type == "BAG" {
2393            fields.push(self.contract.combo_legs.len())?;
2394            for leg in &self.contract.combo_legs {
2395                fields
2396                    .push(leg.con_id)?
2397                    .push(leg.ratio)?
2398                    .push(&leg.action)?
2399                    .push(&leg.exchange)?;
2400            }
2401        }
2402
2403        if server_version >= MIN_SERVER_VER_SYNT_REALTIME_BARS {
2404            fields.push(self.keep_up_to_date)?;
2405        }
2406        if server_version >= MIN_SERVER_VER_LINKING {
2407            fields.push(tag_values_to_tws_options(&self.chart_options))?;
2408        }
2409        Ok(())
2410    }
2411
2412    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2413        let msg = protobuf::HistoricalDataRequest {
2414            req_id: Some(self.req_id),
2415            contract: Some(contract_to_proto(&self.contract, None)),
2416            end_date_time: non_empty(self.end_date_time.clone()),
2417            bar_size_setting: non_empty(self.bar_size_setting.clone()),
2418            duration: non_empty(self.duration_str.clone()),
2419            use_rth: (self.use_rth != 0).then_some(true),
2420            what_to_show: non_empty(self.what_to_show.clone()),
2421            format_date: Some(self.format_date),
2422            keep_up_to_date: self.keep_up_to_date.then_some(true),
2423            chart_options: tag_values_to_map(&self.chart_options),
2424        };
2425        Ok(Some(msg.encode_to_vec()))
2426    }
2427}
2428
2429/// Scanner subscription request.
2430#[derive(Debug, Clone, Default, PartialEq)]
2431pub struct ScannerSubscriptionRequest {
2432    /// Request id.
2433    pub req_id: i32,
2434    /// Subscription.
2435    pub subscription: ScannerSubscription,
2436    /// Scanner subscription options.
2437    pub scanner_subscription_options: Vec<TagValue>,
2438    /// Scanner subscription filter options.
2439    pub scanner_subscription_filter_options: Vec<TagValue>,
2440}
2441
2442impl EncodableRequest for ScannerSubscriptionRequest {
2443    fn message(&self) -> Outgoing {
2444        Outgoing::ReqScannerSubscription
2445    }
2446
2447    fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2448        self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2449    }
2450
2451    fn encode_fields_for_server_version(
2452        &self,
2453        fields: &mut FieldSink,
2454        server_version: i32,
2455    ) -> TwsApiResult<()> {
2456        let sub = &self.subscription;
2457        if server_version < MIN_SERVER_VER_SCANNER_GENERIC_OPTS {
2458            fields.push(4)?;
2459        }
2460        fields
2461            .push(self.req_id)?
2462            .push(sub.number_of_rows)?
2463            .push(&sub.instrument)?
2464            .push(&sub.location_code)?
2465            .push(&sub.scan_code)?
2466            .push_empty(sub.above_price)?
2467            .push_empty(sub.below_price)?
2468            .push_empty(sub.above_volume)?
2469            .push_empty(sub.market_cap_above)?
2470            .push_empty(sub.market_cap_below)?
2471            .push(&sub.moody_rating_above)?
2472            .push(&sub.moody_rating_below)?
2473            .push(&sub.sp_rating_above)?
2474            .push(&sub.sp_rating_below)?
2475            .push(&sub.maturity_date_above)?
2476            .push(&sub.maturity_date_below)?
2477            .push_empty(sub.coupon_rate_above)?
2478            .push_empty(sub.coupon_rate_below)?
2479            .push(sub.exclude_convertible)?
2480            .push_empty(sub.average_option_volume_above)?
2481            .push(&sub.scanner_setting_pairs)?
2482            .push(&sub.stock_type_filter)?;
2483        if server_version >= MIN_SERVER_VER_SCANNER_GENERIC_OPTS {
2484            fields.push(tag_values_to_tws_options(
2485                &self.scanner_subscription_filter_options,
2486            ))?;
2487        }
2488        if server_version >= MIN_SERVER_VER_LINKING {
2489            fields.push(tag_values_to_tws_options(
2490                &self.scanner_subscription_options,
2491            ))?;
2492        }
2493        Ok(())
2494    }
2495
2496    fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2497        let mut subscription = scanner_subscription_to_proto(&self.subscription);
2498        subscription.scanner_subscription_options =
2499            tag_values_to_map(&self.scanner_subscription_options);
2500        subscription.scanner_subscription_filter_options =
2501            tag_values_to_map(&self.scanner_subscription_filter_options);
2502
2503        let msg = protobuf::ScannerSubscriptionRequest {
2504            req_id: Some(self.req_id),
2505            scanner_subscription: Some(subscription),
2506        };
2507        Ok(Some(msg.encode_to_vec()))
2508    }
2509}
2510
2511fn encode_contract_head_or_histogram(
2512    fields: &mut FieldSink,
2513    contract: &Contract,
2514) -> TwsApiResult<()> {
2515    fields
2516        .push(contract.con_id)?
2517        .push(&contract.symbol)?
2518        .push(&contract.sec_type)?
2519        .push(&contract.last_trade_date_or_contract_month)?
2520        .push_empty(contract.strike)?
2521        .push(&contract.right)?
2522        .push(&contract.multiplier)?
2523        .push(&contract.exchange)?
2524        .push(&contract.primary_exchange)?
2525        .push(&contract.currency)?
2526        .push(&contract.local_symbol)?
2527        .push(&contract.trading_class)?
2528        .push(contract.include_expired)?;
2529    Ok(())
2530}
2531
2532fn encode_contract_real_time_bars(
2533    fields: &mut FieldSink,
2534    contract: &Contract,
2535    server_version: i32,
2536) -> TwsApiResult<()> {
2537    if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2538        fields.push(contract.con_id)?;
2539    }
2540    fields
2541        .push(&contract.symbol)?
2542        .push(&contract.sec_type)?
2543        .push(&contract.last_trade_date_or_contract_month)?
2544        .push_empty(contract.strike)?
2545        .push(&contract.right)?
2546        .push(&contract.multiplier)?
2547        .push(&contract.exchange)?
2548        .push(&contract.primary_exchange)?
2549        .push(&contract.currency)?
2550        .push(&contract.local_symbol)?;
2551    if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2552        fields.push(&contract.trading_class)?;
2553    }
2554    Ok(())
2555}
2556
2557fn encode_contract_market_data(
2558    fields: &mut FieldSink,
2559    contract: &Contract,
2560    server_version: i32,
2561) -> TwsApiResult<()> {
2562    fields
2563        .push(&contract.symbol)?
2564        .push(&contract.sec_type)?
2565        .push(&contract.last_trade_date_or_contract_month)?
2566        .push_empty(contract.strike)?
2567        .push(&contract.right)?
2568        .push(&contract.multiplier)?
2569        .push(&contract.exchange)?
2570        .push(&contract.primary_exchange)?
2571        .push(&contract.currency)?
2572        .push(&contract.local_symbol)?;
2573    if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2574        fields.push(&contract.trading_class)?;
2575    }
2576    Ok(())
2577}
2578
2579fn encode_contract_historical_data(
2580    fields: &mut FieldSink,
2581    contract: &Contract,
2582    server_version: i32,
2583) -> TwsApiResult<()> {
2584    fields
2585        .push(&contract.symbol)?
2586        .push(&contract.sec_type)?
2587        .push(&contract.last_trade_date_or_contract_month)?
2588        .push_empty(contract.strike)?
2589        .push(&contract.right)?
2590        .push(&contract.multiplier)?
2591        .push(&contract.exchange)?
2592        .push(&contract.primary_exchange)?
2593        .push(&contract.currency)?
2594        .push(&contract.local_symbol)?;
2595    if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2596        fields.push(&contract.trading_class)?;
2597    }
2598    fields.push(contract.include_expired)?;
2599    Ok(())
2600}
2601
2602fn encode_contract_for_calculation(
2603    fields: &mut FieldSink,
2604    contract: &Contract,
2605    server_version: i32,
2606) -> TwsApiResult<()> {
2607    fields
2608        .push(contract.con_id)?
2609        .push(&contract.symbol)?
2610        .push(&contract.sec_type)?
2611        .push(&contract.last_trade_date_or_contract_month)?
2612        .push_empty(contract.strike)?
2613        .push(&contract.right)?
2614        .push(&contract.multiplier)?
2615        .push(&contract.exchange)?
2616        .push(&contract.primary_exchange)?
2617        .push(&contract.currency)?
2618        .push(&contract.local_symbol)?;
2619    if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2620        fields.push(&contract.trading_class)?;
2621    }
2622    Ok(())
2623}
2624
2625fn encode_place_order_fields(
2626    fields: &mut FieldSink,
2627    server_version: i32,
2628    order_id: i32,
2629    contract: &Contract,
2630    order: &Order,
2631    extra_fields: &str,
2632) -> TwsApiResult<()> {
2633    if server_version < MIN_SERVER_VER_ORDER_CONTAINER {
2634        fields.push(45)?;
2635    }
2636
2637    fields.push(order_id)?;
2638    if server_version >= MIN_SERVER_VER_PLACE_ORDER_CONID {
2639        fields.push(contract.con_id)?;
2640    }
2641    fields
2642        .push(&contract.symbol)?
2643        .push(&contract.sec_type)?
2644        .push(&contract.last_trade_date_or_contract_month)?
2645        .push_empty(contract.strike)?
2646        .push(&contract.right)?
2647        .push(&contract.multiplier)?
2648        .push(&contract.exchange)?
2649        .push(&contract.primary_exchange)?
2650        .push(&contract.currency)?
2651        .push(&contract.local_symbol)?;
2652    if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2653        fields.push(&contract.trading_class)?;
2654    }
2655    if server_version >= MIN_SERVER_VER_SEC_ID_TYPE {
2656        fields.push(&contract.sec_id_type)?.push(&contract.sec_id)?;
2657    }
2658
2659    fields.push(&order.action)?;
2660    fields.push(order.total_quantity.to_string())?;
2661    fields
2662        .push(&order.order_type)?
2663        .push_empty(order.limit_price)?
2664        .push_empty(order.aux_price)?
2665        .push(&order.tif)?
2666        .push(&order.oca_group)?
2667        .push(&order.account)?
2668        .push(&order.open_close)?
2669        .push(order.origin as i32)?
2670        .push(&order.order_ref)?
2671        .push(order.transmit)?
2672        .push(order.parent_id)?
2673        .push(order.block_order)?
2674        .push(order.sweep_to_fill)?
2675        .push(order.display_size)?
2676        .push(order.trigger_method)?
2677        .push(order.outside_rth)?
2678        .push(order.hidden)?;
2679
2680    if contract.sec_type == "BAG" {
2681        fields.push(contract.combo_legs.len())?;
2682        for leg in &contract.combo_legs {
2683            fields
2684                .push(leg.con_id)?
2685                .push(leg.ratio)?
2686                .push(&leg.action)?
2687                .push(&leg.exchange)?
2688                .push(leg.open_close as i32)?
2689                .push(leg.short_sale_slot)?
2690                .push(&leg.designated_location)?;
2691            if server_version >= MIN_SERVER_VER_SSHORTX_OLD {
2692                fields.push(leg.exempt_code)?;
2693            }
2694        }
2695    }
2696
2697    if server_version >= MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE && contract.sec_type == "BAG" {
2698        fields.push(order.order_combo_legs.len())?;
2699        for leg in &order.order_combo_legs {
2700            fields.push_empty(leg.price)?;
2701        }
2702    }
2703
2704    if server_version >= MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS && contract.sec_type == "BAG" {
2705        encode_tag_values(fields, &order.smart_combo_routing_params)?;
2706    }
2707
2708    fields
2709        .push("")?
2710        .push(order.discretionary_amount)?
2711        .push(&order.good_after_time)?
2712        .push(&order.good_till_date)?
2713        .push(&order.fa_group)?
2714        .push(&order.fa_method)?
2715        .push(&order.fa_percentage)?;
2716    if server_version < MIN_SERVER_VER_FA_PROFILE_DESUPPORT {
2717        fields.push("")?;
2718    }
2719    if server_version >= MIN_SERVER_VER_MODELS_SUPPORT {
2720        fields.push(&order.model_code)?;
2721    }
2722
2723    fields
2724        .push(order.short_sale_slot)?
2725        .push(&order.designated_location)?;
2726    if server_version >= MIN_SERVER_VER_SSHORTX_OLD {
2727        fields.push(order.exempt_code)?;
2728    }
2729
2730    fields
2731        .push(order.oca_type)?
2732        .push(&order.rule80a)?
2733        .push(&order.settling_firm)?
2734        .push(order.all_or_none)?
2735        .push_empty(order.min_qty)?
2736        .push_empty(order.percent_offset)?
2737        .push(false)?
2738        .push(false)?
2739        .push_empty(UNSET_DOUBLE)?
2740        .push(order.auction_strategy as i32)?
2741        .push_empty(order.starting_price)?
2742        .push_empty(order.stock_ref_price)?
2743        .push_empty(order.delta)?
2744        .push_empty(order.stock_range_lower)?
2745        .push_empty(order.stock_range_upper)?
2746        .push(order.override_percentage_constraints)?
2747        .push_empty(order.volatility)?
2748        .push_empty(order.volatility_type)?
2749        .push(&order.delta_neutral_order_type)?
2750        .push_empty(order.delta_neutral_aux_price)?;
2751
2752    if server_version >= MIN_SERVER_VER_DELTA_NEUTRAL_CONID
2753        && !order.delta_neutral_order_type.is_empty()
2754    {
2755        fields
2756            .push(order.delta_neutral_con_id)?
2757            .push(&order.delta_neutral_settling_firm)?
2758            .push(&order.delta_neutral_clearing_account)?
2759            .push(&order.delta_neutral_clearing_intent)?;
2760    }
2761    if server_version >= MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE
2762        && !order.delta_neutral_order_type.is_empty()
2763    {
2764        fields
2765            .push(&order.delta_neutral_open_close)?
2766            .push(order.delta_neutral_short_sale)?
2767            .push(order.delta_neutral_short_sale_slot)?
2768            .push(&order.delta_neutral_designated_location)?;
2769    }
2770
2771    fields
2772        .push(order.continuous_update)?
2773        .push_empty(order.reference_price_type)?
2774        .push_empty(order.trail_stop_price)?;
2775    if server_version >= MIN_SERVER_VER_TRAILING_PERCENT {
2776        fields.push_empty(order.trailing_percent)?;
2777    }
2778
2779    if server_version >= MIN_SERVER_VER_SCALE_ORDERS2 {
2780        fields
2781            .push_empty(order.scale_init_level_size)?
2782            .push_empty(order.scale_subs_level_size)?;
2783    } else {
2784        fields.push("")?.push_empty(order.scale_init_level_size)?;
2785    }
2786    fields.push_empty(order.scale_price_increment)?;
2787    if server_version >= MIN_SERVER_VER_SCALE_ORDERS3
2788        && order.scale_price_increment != UNSET_DOUBLE
2789        && order.scale_price_increment > 0.0
2790    {
2791        fields
2792            .push_empty(order.scale_price_adjust_value)?
2793            .push_empty(order.scale_price_adjust_interval)?
2794            .push_empty(order.scale_profit_offset)?
2795            .push(order.scale_auto_reset)?
2796            .push_empty(order.scale_init_position)?
2797            .push_empty(order.scale_init_fill_qty)?
2798            .push(order.scale_random_percent)?;
2799    }
2800    if server_version >= MIN_SERVER_VER_SCALE_TABLE {
2801        fields
2802            .push(&order.scale_table)?
2803            .push(&order.active_start_time)?
2804            .push(&order.active_stop_time)?;
2805    }
2806
2807    if server_version >= MIN_SERVER_VER_HEDGE_ORDERS {
2808        fields.push(&order.hedge_type)?;
2809        if !order.hedge_type.is_empty() {
2810            fields.push(&order.hedge_param)?;
2811        }
2812    }
2813    if server_version >= MIN_SERVER_VER_OPT_OUT_SMART_ROUTING {
2814        fields.push(order.opt_out_smart_routing)?;
2815    }
2816    if server_version >= MIN_SERVER_VER_PTA_ORDERS {
2817        fields
2818            .push(&order.clearing_account)?
2819            .push(&order.clearing_intent)?;
2820    }
2821    if server_version >= MIN_SERVER_VER_NOT_HELD {
2822        fields.push(order.not_held)?;
2823    }
2824    if server_version >= MIN_SERVER_VER_DELTA_NEUTRAL {
2825        if let Some(delta) = &contract.delta_neutral_contract {
2826            fields
2827                .push(true)?
2828                .push(delta.con_id)?
2829                .push(delta.delta)?
2830                .push(delta.price)?;
2831        } else {
2832            fields.push(false)?;
2833        }
2834    }
2835    if server_version >= MIN_SERVER_VER_ALGO_ORDERS {
2836        fields.push(&order.algo_strategy)?;
2837        if !order.algo_strategy.is_empty() {
2838            encode_tag_values(fields, &order.algo_params)?;
2839        }
2840    }
2841    if server_version >= MIN_SERVER_VER_ALGO_ID {
2842        fields.push(&order.algo_id)?;
2843    }
2844    fields.push(order.what_if)?;
2845    if server_version >= MIN_SERVER_VER_LINKING {
2846        fields.push(tag_values_to_tws_options(&order.order_misc_options))?;
2847    }
2848    if server_version >= MIN_SERVER_VER_ORDER_SOLICITED {
2849        fields.push(order.solicited)?;
2850    }
2851    if server_version >= MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE {
2852        fields
2853            .push(order.randomize_size)?
2854            .push(order.randomize_price)?;
2855    }
2856    if server_version >= MIN_SERVER_VER_PEGGED_TO_BENCHMARK {
2857        if is_peg_benchmark_order(&order.order_type) {
2858            fields
2859                .push(order.reference_contract_id)?
2860                .push(order.is_pegged_change_amount_decrease)?
2861                .push(order.pegged_change_amount)?
2862                .push(order.reference_change_amount)?
2863                .push(&order.reference_exchange_id)?;
2864        }
2865        fields.push(order.conditions.len())?;
2866        if !order.conditions.is_empty() {
2867            for condition in &order.conditions {
2868                encode_order_condition(fields, condition)?;
2869            }
2870            fields
2871                .push(order.conditions_ignore_rth)?
2872                .push(order.conditions_cancel_order)?;
2873        }
2874        fields
2875            .push(&order.adjusted_order_type)?
2876            .push(order.trigger_price)?
2877            .push(order.limit_price_offset)?
2878            .push(order.adjusted_stop_price)?
2879            .push(order.adjusted_stop_limit_price)?
2880            .push(order.adjusted_trailing_amount)?
2881            .push(order.adjustable_trailing_unit)?;
2882    }
2883    if server_version >= MIN_SERVER_VER_EXT_OPERATOR {
2884        fields.push(&order.ext_operator)?;
2885    }
2886    if server_version >= MIN_SERVER_VER_SOFT_DOLLAR_TIER {
2887        fields
2888            .push(&order.soft_dollar_tier.name)?
2889            .push(&order.soft_dollar_tier.value)?;
2890    }
2891    if server_version >= MIN_SERVER_VER_CASH_QTY {
2892        fields.push(order.cash_qty)?;
2893    }
2894    if server_version >= MIN_SERVER_VER_DECISION_MAKER {
2895        fields
2896            .push(&order.mifid2_decision_maker)?
2897            .push(&order.mifid2_decision_algo)?;
2898    }
2899    if server_version >= MIN_SERVER_VER_MIFID_EXECUTION {
2900        fields
2901            .push(&order.mifid2_execution_trader)?
2902            .push(&order.mifid2_execution_algo)?;
2903    }
2904    if server_version >= MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE {
2905        fields.push(order.dont_use_auto_price_for_hedge)?;
2906    }
2907    if server_version >= MIN_SERVER_VER_ORDER_CONTAINER {
2908        fields.push(order.is_oms_container)?;
2909    }
2910    if server_version >= MIN_SERVER_VER_D_PEG_ORDERS {
2911        fields.push(order.discretionary_up_to_limit_price)?;
2912    }
2913    if server_version >= MIN_SERVER_VER_PRICE_MGMT_ALGO {
2914        if order.use_price_mgmt_algo == UNSET_INTEGER {
2915            fields.push_empty(UNSET_INTEGER)?;
2916        } else {
2917            fields.push(order.use_price_mgmt_algo != 0)?;
2918        }
2919    }
2920    if server_version >= MIN_SERVER_VER_DURATION {
2921        fields.push(order.duration)?;
2922    }
2923    if server_version >= MIN_SERVER_VER_POST_TO_ATS {
2924        fields.push(order.post_to_ats)?;
2925    }
2926    if server_version >= MIN_SERVER_VER_AUTO_CANCEL_PARENT {
2927        fields.push(order.auto_cancel_parent)?;
2928    }
2929    if server_version >= MIN_SERVER_VER_ADVANCED_ORDER_REJECT {
2930        fields.push(&order.advanced_error_override)?;
2931    }
2932    if server_version >= MIN_SERVER_VER_MANUAL_ORDER_TIME {
2933        fields.push(&order.manual_order_time)?;
2934    }
2935    if server_version >= MIN_SERVER_VER_PEGBEST_PEGMID_OFFSETS {
2936        let mut send_mid_offsets = false;
2937        if contract.exchange == "IBKRATS" {
2938            fields.push_empty(order.min_trade_qty)?;
2939        }
2940        if is_peg_best_order(&order.order_type) {
2941            fields
2942                .push_empty(order.min_compete_size)?
2943                .push_empty(order.compete_against_best_offset)?;
2944            send_mid_offsets = order.compete_against_best_offset
2945                == crate::constants::COMPETE_AGAINST_BEST_OFFSET_UP_TO_MID;
2946        } else if is_peg_mid_order(&order.order_type) {
2947            send_mid_offsets = true;
2948        }
2949        if send_mid_offsets {
2950            fields
2951                .push_empty(order.mid_offset_at_whole)?
2952                .push_empty(order.mid_offset_at_half)?;
2953        }
2954    }
2955    if server_version >= MIN_SERVER_VER_CUSTOMER_ACCOUNT {
2956        fields.push(&order.customer_account)?;
2957    }
2958    if server_version >= MIN_SERVER_VER_PROFESSIONAL_CUSTOMER {
2959        fields.push(order.professional_customer)?;
2960    }
2961    if (MIN_SERVER_VER_RFQ_FIELDS..MIN_SERVER_VER_UNDO_RFQ_FIELDS).contains(&server_version) {
2962        fields.push("")?.push_empty(UNSET_INTEGER)?;
2963    }
2964    if server_version >= MIN_SERVER_VER_INCLUDE_OVERNIGHT {
2965        fields.push(order.include_overnight)?;
2966    }
2967    if server_version >= MIN_SERVER_VER_CME_TAGGING_FIELDS {
2968        fields.push(order.manual_order_indicator)?;
2969    }
2970    if server_version >= MIN_SERVER_VER_IMBALANCE_ONLY {
2971        fields.push(order.imbalance_only)?;
2972    }
2973
2974    fields.push_raw(extra_fields);
2975    Ok(())
2976}
2977
2978fn encode_order_condition(
2979    fields: &mut FieldSink,
2980    condition: &crate::types::OrderCondition,
2981) -> TwsApiResult<()> {
2982    use crate::types::OrderCondition;
2983
2984    match condition {
2985        OrderCondition::Price {
2986            is_conjunction_connection,
2987            trigger_method,
2988            con_id,
2989            exchange,
2990            is_more,
2991            price,
2992        } => {
2993            fields
2994                .push(1)?
2995                .push(if *is_conjunction_connection { "a" } else { "o" })?
2996                .push(*is_more)?
2997                .push(*price)?
2998                .push(*con_id)?
2999                .push(&exchange[..])?
3000                .push(*trigger_method)?;
3001        }
3002        OrderCondition::Time {
3003            is_conjunction_connection,
3004            is_more,
3005            time,
3006        } => {
3007            fields
3008                .push(3)?
3009                .push(if *is_conjunction_connection { "a" } else { "o" })?
3010                .push(*is_more)?
3011                .push(&time[..])?;
3012        }
3013        OrderCondition::Margin {
3014            is_conjunction_connection,
3015            is_more,
3016            percent,
3017        } => {
3018            fields
3019                .push(4)?
3020                .push(if *is_conjunction_connection { "a" } else { "o" })?
3021                .push(*is_more)?
3022                .push(*percent)?;
3023        }
3024        OrderCondition::Execution {
3025            is_conjunction_connection,
3026            sec_type,
3027            exchange,
3028            symbol,
3029        } => {
3030            fields
3031                .push(5)?
3032                .push(if *is_conjunction_connection { "a" } else { "o" })?
3033                .push(&sec_type[..])?
3034                .push(&exchange[..])?
3035                .push(&symbol[..])?;
3036        }
3037        OrderCondition::Volume {
3038            is_conjunction_connection,
3039            con_id,
3040            exchange,
3041            is_more,
3042            volume,
3043        } => {
3044            fields
3045                .push(6)?
3046                .push(if *is_conjunction_connection { "a" } else { "o" })?
3047                .push(*is_more)?
3048                .push(*volume)?
3049                .push(*con_id)?
3050                .push(&exchange[..])?;
3051        }
3052        OrderCondition::PercentChange {
3053            is_conjunction_connection,
3054            con_id,
3055            exchange,
3056            is_more,
3057            change_percent,
3058        } => {
3059            fields
3060                .push(7)?
3061                .push(if *is_conjunction_connection { "a" } else { "o" })?
3062                .push(*is_more)?
3063                .push(*change_percent)?
3064                .push(*con_id)?
3065                .push(&exchange[..])?;
3066        }
3067    }
3068    Ok(())
3069}
3070
3071fn is_peg_benchmark_order(order_type: &str) -> bool {
3072    matches!(order_type, "PEG BENCH" | "PEGBENCH")
3073}
3074
3075fn is_peg_mid_order(order_type: &str) -> bool {
3076    matches!(order_type, "PEG MID" | "PEGMID")
3077}
3078
3079fn is_peg_best_order(order_type: &str) -> bool {
3080    matches!(order_type, "PEG BEST" | "PEGBEST")
3081}
3082
3083fn encode_tag_values(fields: &mut FieldSink, values: &[TagValue]) -> TwsApiResult<()> {
3084    fields.push(values.len())?;
3085    for value in values {
3086        fields.push(&value.tag)?.push(&value.value)?;
3087    }
3088    Ok(())
3089}
3090
3091fn tag_values_to_tws_options(values: &[TagValue]) -> String {
3092    values
3093        .iter()
3094        .map(|value| format!("{}={};", value.tag, value.value))
3095        .collect::<String>()
3096}
3097
3098fn tag_values_to_map(values: &[TagValue]) -> std::collections::HashMap<String, String> {
3099    values
3100        .iter()
3101        .map(|value| (value.tag.clone(), value.value.clone()))
3102        .collect()
3103}
3104
3105fn contract_to_proto(contract: &Contract, order: Option<&Order>) -> protobuf::Contract {
3106    protobuf::Contract {
3107        con_id: valid_i32(contract.con_id),
3108        symbol: non_empty(contract.symbol.clone()),
3109        sec_type: non_empty(contract.sec_type.clone()),
3110        last_trade_date_or_contract_month: non_empty(
3111            contract.last_trade_date_or_contract_month.clone(),
3112        ),
3113        strike: valid_f64(contract.strike),
3114        right: non_empty(contract.right.clone()),
3115        multiplier: contract.multiplier.parse::<f64>().ok(),
3116        exchange: non_empty(contract.exchange.clone()),
3117        primary_exch: non_empty(contract.primary_exchange.clone()),
3118        currency: non_empty(contract.currency.clone()),
3119        local_symbol: non_empty(contract.local_symbol.clone()),
3120        trading_class: non_empty(contract.trading_class.clone()),
3121        sec_id_type: non_empty(contract.sec_id_type.clone()),
3122        sec_id: non_empty(contract.sec_id.clone()),
3123        description: non_empty(contract.description.clone()),
3124        issuer_id: non_empty(contract.issuer_id.clone()),
3125        delta_neutral_contract: contract.delta_neutral_contract.as_ref().map(|delta| {
3126            protobuf::DeltaNeutralContract {
3127                con_id: valid_i32(delta.con_id),
3128                delta: valid_f64(delta.delta),
3129                price: valid_f64(delta.price),
3130            }
3131        }),
3132        include_expired: contract.include_expired.then_some(true),
3133        combo_legs_descrip: non_empty(contract.combo_legs_description.clone()),
3134        combo_legs: contract
3135            .combo_legs
3136            .iter()
3137            .enumerate()
3138            .map(|(idx, leg)| {
3139                let per_leg_price = order
3140                    .and_then(|order| order.order_combo_legs.get(idx))
3141                    .and_then(|leg| valid_f64(leg.price));
3142                protobuf::ComboLeg {
3143                    con_id: valid_i32(leg.con_id),
3144                    ratio: valid_i32(leg.ratio),
3145                    action: non_empty(leg.action.clone()),
3146                    exchange: non_empty(leg.exchange.clone()),
3147                    open_close: valid_i32(leg.open_close as i32),
3148                    short_sales_slot: valid_i32(leg.short_sale_slot),
3149                    designated_location: non_empty(leg.designated_location.clone()),
3150                    exempt_code: valid_i32(leg.exempt_code),
3151                    per_leg_price,
3152                }
3153            })
3154            .collect(),
3155        last_trade_date: non_empty(contract.last_trade_date.clone()),
3156    }
3157}
3158
3159fn order_to_proto(order: &Order) -> protobuf::Order {
3160    let mut msg = protobuf::Order {
3161        client_id: valid_i32(order.client_id),
3162        order_id: valid_i32(order.order_id),
3163        perm_id: Some(order.perm_id),
3164        parent_id: valid_i32(order.parent_id),
3165        action: non_empty(order.action.clone()),
3166        total_quantity: Some(order.total_quantity.to_string()),
3167        display_size: valid_i32(order.display_size),
3168        order_type: non_empty(order.order_type.clone()),
3169        lmt_price: valid_f64(order.limit_price),
3170        aux_price: valid_f64(order.aux_price),
3171        tif: non_empty(order.tif.clone()),
3172        account: non_empty(order.account.clone()),
3173        settling_firm: non_empty(order.settling_firm.clone()),
3174        clearing_account: non_empty(order.clearing_account.clone()),
3175        clearing_intent: non_empty(order.clearing_intent.clone()),
3176        all_or_none: order.all_or_none.then_some(true),
3177        block_order: order.block_order.then_some(true),
3178        hidden: order.hidden.then_some(true),
3179        outside_rth: order.outside_rth.then_some(true),
3180        sweep_to_fill: order.sweep_to_fill.then_some(true),
3181        percent_offset: valid_f64(order.percent_offset),
3182        trailing_percent: valid_f64(order.trailing_percent),
3183        trail_stop_price: valid_f64(order.trail_stop_price),
3184        min_qty: valid_i32(order.min_qty),
3185        good_after_time: non_empty(order.good_after_time.clone()),
3186        good_till_date: non_empty(order.good_till_date.clone()),
3187        oca_group: non_empty(order.oca_group.clone()),
3188        order_ref: non_empty(order.order_ref.clone()),
3189        rule80_a: non_empty(order.rule80a.clone()),
3190        oca_type: valid_i32(order.oca_type),
3191        trigger_method: valid_i32(order.trigger_method),
3192        active_start_time: non_empty(order.active_start_time.clone()),
3193        active_stop_time: non_empty(order.active_stop_time.clone()),
3194        fa_group: non_empty(order.fa_group.clone()),
3195        fa_method: non_empty(order.fa_method.clone()),
3196        fa_percentage: non_empty(order.fa_percentage.clone()),
3197        volatility: valid_f64(order.volatility),
3198        volatility_type: valid_i32(order.volatility_type),
3199        continuous_update: order.continuous_update.then_some(true),
3200        reference_price_type: valid_i32(order.reference_price_type),
3201        delta_neutral_order_type: non_empty(order.delta_neutral_order_type.clone()),
3202        delta_neutral_aux_price: valid_f64(order.delta_neutral_aux_price),
3203        delta_neutral_con_id: valid_i32(order.delta_neutral_con_id),
3204        delta_neutral_open_close: non_empty(order.delta_neutral_open_close.clone()),
3205        delta_neutral_short_sale: order.delta_neutral_short_sale.then_some(true),
3206        delta_neutral_short_sale_slot: valid_i32(order.delta_neutral_short_sale_slot),
3207        delta_neutral_designated_location: non_empty(
3208            order.delta_neutral_designated_location.clone(),
3209        ),
3210        scale_init_level_size: valid_i32(order.scale_init_level_size),
3211        scale_subs_level_size: valid_i32(order.scale_subs_level_size),
3212        scale_price_increment: valid_f64(order.scale_price_increment),
3213        scale_price_adjust_value: valid_f64(order.scale_price_adjust_value),
3214        scale_price_adjust_interval: valid_i32(order.scale_price_adjust_interval),
3215        scale_profit_offset: valid_f64(order.scale_profit_offset),
3216        scale_auto_reset: order.scale_auto_reset.then_some(true),
3217        scale_init_position: valid_i32(order.scale_init_position),
3218        scale_init_fill_qty: valid_i32(order.scale_init_fill_qty),
3219        scale_random_percent: order.scale_random_percent.then_some(true),
3220        scale_table: non_empty(order.scale_table.clone()),
3221        hedge_type: non_empty(order.hedge_type.clone()),
3222        hedge_param: non_empty(order.hedge_param.clone()),
3223        algo_strategy: non_empty(order.algo_strategy.clone()),
3224        algo_id: non_empty(order.algo_id.clone()),
3225        what_if: order.what_if.then_some(true),
3226        transmit: order.transmit.then_some(true),
3227        override_percentage_constraints: order.override_percentage_constraints.then_some(true),
3228        open_close: non_empty(order.open_close.clone()),
3229        origin: valid_i32(order.origin as i32),
3230        short_sale_slot: valid_i32(order.short_sale_slot),
3231        designated_location: non_empty(order.designated_location.clone()),
3232        exempt_code: valid_i32(order.exempt_code),
3233        delta_neutral_settling_firm: non_empty(order.delta_neutral_settling_firm.clone()),
3234        delta_neutral_clearing_account: non_empty(order.delta_neutral_clearing_account.clone()),
3235        delta_neutral_clearing_intent: non_empty(order.delta_neutral_clearing_intent.clone()),
3236        discretionary_amt: valid_f64(order.discretionary_amount),
3237        opt_out_smart_routing: order.opt_out_smart_routing.then_some(true),
3238        starting_price: valid_f64(order.starting_price),
3239        stock_ref_price: valid_f64(order.stock_ref_price),
3240        delta: valid_f64(order.delta),
3241        stock_range_lower: valid_f64(order.stock_range_lower),
3242        stock_range_upper: valid_f64(order.stock_range_upper),
3243        not_held: order.not_held.then_some(true),
3244        solicited: order.solicited.then_some(true),
3245        randomize_size: order.randomize_size.then_some(true),
3246        randomize_price: order.randomize_price.then_some(true),
3247        reference_contract_id: valid_i32(order.reference_contract_id),
3248        pegged_change_amount: valid_f64(order.pegged_change_amount),
3249        is_pegged_change_amount_decrease: order.is_pegged_change_amount_decrease.then_some(true),
3250        reference_change_amount: valid_f64(order.reference_change_amount),
3251        reference_exchange_id: non_empty(order.reference_exchange_id.clone()),
3252        adjusted_order_type: non_empty(order.adjusted_order_type.clone()),
3253        trigger_price: valid_f64(order.trigger_price),
3254        adjusted_stop_price: valid_f64(order.adjusted_stop_price),
3255        adjusted_stop_limit_price: valid_f64(order.adjusted_stop_limit_price),
3256        adjusted_trailing_amount: valid_f64(order.adjusted_trailing_amount),
3257        adjustable_trailing_unit: valid_i32(order.adjustable_trailing_unit),
3258        lmt_price_offset: valid_f64(order.limit_price_offset),
3259        model_code: non_empty(order.model_code.clone()),
3260        ext_operator: non_empty(order.ext_operator.clone()),
3261        soft_dollar_tier: Some(protobuf::SoftDollarTier {
3262            name: non_empty(order.soft_dollar_tier.name.clone()),
3263            value: non_empty(order.soft_dollar_tier.value.clone()),
3264            display_name: non_empty(order.soft_dollar_tier.display_name.clone()),
3265        }),
3266        cash_qty: valid_f64(order.cash_qty),
3267        mifid2_decision_maker: non_empty(order.mifid2_decision_maker.clone()),
3268        mifid2_decision_algo: non_empty(order.mifid2_decision_algo.clone()),
3269        mifid2_execution_trader: non_empty(order.mifid2_execution_trader.clone()),
3270        mifid2_execution_algo: non_empty(order.mifid2_execution_algo.clone()),
3271        dont_use_auto_price_for_hedge: order.dont_use_auto_price_for_hedge.then_some(true),
3272        is_oms_container: order.is_oms_container.then_some(true),
3273        discretionary_up_to_limit_price: order.discretionary_up_to_limit_price.then_some(true),
3274        auto_cancel_date: non_empty(order.auto_cancel_date.clone()),
3275        filled_quantity: non_empty(order.filled_quantity.to_string()),
3276        ref_futures_con_id: valid_i32(order.ref_futures_con_id),
3277        auto_cancel_parent: order.auto_cancel_parent.then_some(true),
3278        shareholder: non_empty(order.shareholder.clone()),
3279        imbalance_only: order.imbalance_only.then_some(true),
3280        route_marketable_to_bbo: valid_i32(order.route_marketable_to_bbo),
3281        parent_perm_id: Some(order.parent_perm_id),
3282        use_price_mgmt_algo: valid_i32(order.use_price_mgmt_algo),
3283        duration: valid_i32(order.duration),
3284        post_to_ats: valid_i32(order.post_to_ats),
3285        advanced_error_override: non_empty(order.advanced_error_override.clone()),
3286        manual_order_time: non_empty(order.manual_order_time.clone()),
3287        min_trade_qty: valid_i32(order.min_trade_qty),
3288        min_compete_size: valid_i32(order.min_compete_size),
3289        compete_against_best_offset: valid_f64(order.compete_against_best_offset),
3290        mid_offset_at_whole: valid_f64(order.mid_offset_at_whole),
3291        mid_offset_at_half: valid_f64(order.mid_offset_at_half),
3292        customer_account: non_empty(order.customer_account.clone()),
3293        professional_customer: order.professional_customer.then_some(true),
3294        bond_accrued_interest: non_empty(order.bond_accrued_interest.clone()),
3295        include_overnight: order.include_overnight.then_some(true),
3296        manual_order_indicator: valid_i32(order.manual_order_indicator),
3297        submitter: non_empty(order.submitter.clone()),
3298        deactivate: order.deactivate.then_some(true),
3299        post_only: order.post_only.then_some(true),
3300        allow_pre_open: order.allow_pre_open.then_some(true),
3301        ignore_open_auction: order.ignore_open_auction.then_some(true),
3302        seek_price_improvement: valid_i32(order.seek_price_improvement),
3303        what_if_type: valid_i32(order.what_if_type),
3304        hedge_max_size: valid_i32(order.hedge_max_size),
3305        ..protobuf::Order::default()
3306    };
3307    msg.algo_params = tag_values_to_map(&order.algo_params);
3308    msg.smart_combo_routing_params = tag_values_to_map(&order.smart_combo_routing_params);
3309    msg.order_misc_options = tag_values_to_map(&order.order_misc_options);
3310    msg
3311}
3312
3313fn attached_orders_to_proto(order: &Order) -> Option<protobuf::AttachedOrders> {
3314    let attached = protobuf::AttachedOrders {
3315        sl_order_id: valid_i32(order.stop_loss_order_id),
3316        sl_order_type: non_empty(order.stop_loss_order_type.clone()),
3317        pt_order_id: valid_i32(order.profit_taker_order_id),
3318        pt_order_type: non_empty(order.profit_taker_order_type.clone()),
3319    };
3320    (attached.sl_order_id.is_some()
3321        || attached.sl_order_type.is_some()
3322        || attached.pt_order_id.is_some()
3323        || attached.pt_order_type.is_some())
3324    .then_some(attached)
3325}
3326
3327pub(crate) fn validate_order_parameters(
3328    order: &Order,
3329    server_version: i32,
3330) -> Option<OrderFieldValidation> {
3331    if server_version < MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1 {
3332        if order.deactivate {
3333            return Some(OrderFieldValidation {
3334                parameter: "deactivate",
3335                min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
3336            });
3337        }
3338        if order.post_only {
3339            return Some(OrderFieldValidation {
3340                parameter: "postOnly",
3341                min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
3342            });
3343        }
3344        if order.allow_pre_open {
3345            return Some(OrderFieldValidation {
3346                parameter: "allowPreOpen",
3347                min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
3348            });
3349        }
3350        if order.ignore_open_auction {
3351            return Some(OrderFieldValidation {
3352                parameter: "ignoreOpenAuction",
3353                min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
3354            });
3355        }
3356    }
3357
3358    if server_version < MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2 {
3359        if order.route_marketable_to_bbo != UNSET_INTEGER {
3360            return Some(OrderFieldValidation {
3361                parameter: "routeMarketableToBbo",
3362                min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2,
3363            });
3364        }
3365        if order.seek_price_improvement != UNSET_INTEGER {
3366            return Some(OrderFieldValidation {
3367                parameter: "seekPriceImprovement",
3368                min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2,
3369            });
3370        }
3371        if order.what_if_type != UNSET_INTEGER {
3372            return Some(OrderFieldValidation {
3373                parameter: "whatIfType",
3374                min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2,
3375            });
3376        }
3377    }
3378
3379    if server_version < MIN_SERVER_VER_HEDGE_MAX_SIZE && order.hedge_max_size != UNSET_INTEGER {
3380        return Some(OrderFieldValidation {
3381            parameter: "hedgeMaxSize",
3382            min_version: MIN_SERVER_VER_HEDGE_MAX_SIZE,
3383        });
3384    }
3385
3386    None
3387}
3388
3389pub(crate) fn validate_attached_orders_parameters(
3390    order: &Order,
3391    server_version: i32,
3392) -> Option<OrderFieldValidation> {
3393    if server_version < MIN_SERVER_VER_ATTACHED_ORDERS {
3394        if order.stop_loss_order_id != UNSET_INTEGER {
3395            return Some(OrderFieldValidation {
3396                parameter: "slOrderId",
3397                min_version: MIN_SERVER_VER_ATTACHED_ORDERS,
3398            });
3399        }
3400        if !order.stop_loss_order_type.is_empty() {
3401            return Some(OrderFieldValidation {
3402                parameter: "slOrderType",
3403                min_version: MIN_SERVER_VER_ATTACHED_ORDERS,
3404            });
3405        }
3406        if order.profit_taker_order_id != UNSET_INTEGER {
3407            return Some(OrderFieldValidation {
3408                parameter: "ptOrderId",
3409                min_version: MIN_SERVER_VER_ATTACHED_ORDERS,
3410            });
3411        }
3412        if !order.profit_taker_order_type.is_empty() {
3413            return Some(OrderFieldValidation {
3414                parameter: "ptOrderType",
3415                min_version: MIN_SERVER_VER_ATTACHED_ORDERS,
3416            });
3417        }
3418    }
3419
3420    None
3421}
3422
3423#[cfg(test)]
3424#[allow(clippy::items_after_test_module, clippy::field_reassign_with_default)]
3425mod tests {
3426    use super::*;
3427    use crate::server_versions::{
3428        MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1, MIN_SERVER_VER_ATTACHED_ORDERS,
3429        MIN_SERVER_VER_HEDGE_MAX_SIZE,
3430    };
3431
3432    #[test]
3433    fn validate_order_parameters_rejects_new_fields_on_old_server_versions() {
3434        let mut order = Order::default();
3435        order.deactivate = true;
3436        let validation =
3437            validate_order_parameters(&order, MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1 - 1)
3438                .expect("expected deactivate to be rejected");
3439        assert_eq!(validation.parameter, "deactivate");
3440        assert_eq!(
3441            validation.min_version,
3442            MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1
3443        );
3444
3445        let mut order = Order::default();
3446        order.hedge_max_size = 1;
3447        let validation = validate_order_parameters(&order, MIN_SERVER_VER_HEDGE_MAX_SIZE - 1)
3448            .expect("expected hedgeMaxSize to be rejected");
3449        assert_eq!(validation.parameter, "hedgeMaxSize");
3450        assert_eq!(validation.min_version, MIN_SERVER_VER_HEDGE_MAX_SIZE);
3451    }
3452
3453    #[test]
3454    fn validate_attached_orders_parameters_rejects_low_versions() {
3455        let mut order = Order::default();
3456        order.stop_loss_order_id = 1;
3457        let validation =
3458            validate_attached_orders_parameters(&order, MIN_SERVER_VER_ATTACHED_ORDERS - 1)
3459                .expect("expected attached orders to be rejected");
3460        assert_eq!(validation.parameter, "slOrderId");
3461        assert_eq!(validation.min_version, MIN_SERVER_VER_ATTACHED_ORDERS);
3462    }
3463
3464    #[test]
3465    fn validate_order_parameters_allows_supported_defaults() {
3466        let order = Order::default();
3467        assert!(
3468            validate_order_parameters(&order, MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1 - 1)
3469                .is_none()
3470        );
3471        assert!(
3472            validate_attached_orders_parameters(&order, MIN_SERVER_VER_ATTACHED_ORDERS - 1)
3473                .is_none()
3474        );
3475    }
3476}
3477
3478fn order_cancel_to_proto(order_cancel: &OrderCancel) -> protobuf::OrderCancel {
3479    protobuf::OrderCancel {
3480        manual_order_cancel_time: non_empty(order_cancel.manual_order_cancel_time.clone()),
3481        ext_operator: non_empty(order_cancel.ext_operator.clone()),
3482        manual_order_indicator: valid_i32(order_cancel.manual_order_indicator),
3483    }
3484}
3485
3486fn execution_filter_to_proto(filter: &ExecutionFilter) -> protobuf::ExecutionFilter {
3487    protobuf::ExecutionFilter {
3488        client_id: valid_i32(filter.client_id),
3489        acct_code: non_empty(filter.acct_code.clone()),
3490        time: non_empty(filter.time.clone()),
3491        symbol: non_empty(filter.symbol.clone()),
3492        sec_type: non_empty(filter.sec_type.clone()),
3493        exchange: non_empty(filter.exchange.clone()),
3494        side: non_empty(filter.side.clone()),
3495        last_n_days: valid_i32(filter.last_n_days),
3496        specific_dates: filter.specific_dates.clone(),
3497    }
3498}
3499
3500fn scanner_subscription_to_proto(
3501    subscription: &ScannerSubscription,
3502) -> protobuf::ScannerSubscription {
3503    protobuf::ScannerSubscription {
3504        number_of_rows: valid_i32(subscription.number_of_rows),
3505        instrument: non_empty(subscription.instrument.clone()),
3506        location_code: non_empty(subscription.location_code.clone()),
3507        scan_code: non_empty(subscription.scan_code.clone()),
3508        above_price: valid_f64(subscription.above_price),
3509        below_price: valid_f64(subscription.below_price),
3510        above_volume: (subscription.above_volume != UNSET_INTEGER)
3511            .then_some(i64::from(subscription.above_volume)),
3512        market_cap_above: valid_f64(subscription.market_cap_above),
3513        market_cap_below: valid_f64(subscription.market_cap_below),
3514        moody_rating_above: non_empty(subscription.moody_rating_above.clone()),
3515        moody_rating_below: non_empty(subscription.moody_rating_below.clone()),
3516        sp_rating_above: non_empty(subscription.sp_rating_above.clone()),
3517        sp_rating_below: non_empty(subscription.sp_rating_below.clone()),
3518        maturity_date_above: non_empty(subscription.maturity_date_above.clone()),
3519        maturity_date_below: non_empty(subscription.maturity_date_below.clone()),
3520        coupon_rate_above: valid_f64(subscription.coupon_rate_above),
3521        coupon_rate_below: valid_f64(subscription.coupon_rate_below),
3522        exclude_convertible: subscription.exclude_convertible.then_some(true),
3523        average_option_volume_above: (subscription.average_option_volume_above != UNSET_INTEGER)
3524            .then_some(i64::from(subscription.average_option_volume_above)),
3525        scanner_setting_pairs: non_empty(subscription.scanner_setting_pairs.clone()),
3526        stock_type_filter: non_empty(subscription.stock_type_filter.clone()),
3527        scanner_subscription_filter_options: Default::default(),
3528        scanner_subscription_options: Default::default(),
3529    }
3530}
3531
3532fn non_empty(value: String) -> Option<String> {
3533    (!value.is_empty()).then_some(value)
3534}
3535
3536fn valid_i32(value: i32) -> Option<i32> {
3537    (value != UNSET_INTEGER).then_some(value)
3538}
3539
3540fn valid_f64(value: f64) -> Option<f64> {
3541    (value != UNSET_DOUBLE && value.is_finite()).then_some(value)
3542}
3543
3544fn ensure_server_version(server_version: i32, min_version: i32) -> TwsApiResult<()> {
3545    if server_version < min_version {
3546        return Err(TwsApiError::UnsupportedServerVersion {
3547            server_version,
3548            min_version,
3549        });
3550    }
3551    Ok(())
3552}