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