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