1use prost::Message;
2
3use crate::comm;
4use crate::constants::{UNSET_DOUBLE, UNSET_INTEGER};
5use crate::error::{TwsApiError, TwsApiResult};
6use crate::message::Outgoing;
7use crate::protobuf;
8use crate::server_versions::{
9 MAX_CLIENT_VER, MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
10 MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2, MIN_SERVER_VER_ADVANCED_ORDER_REJECT,
11 MIN_SERVER_VER_ALGO_ID, MIN_SERVER_VER_ALGO_ORDERS, MIN_SERVER_VER_ATTACHED_ORDERS,
12 MIN_SERVER_VER_AUTO_CANCEL_PARENT, MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE,
13 MIN_SERVER_VER_BOND_ISSUERID, MIN_SERVER_VER_CASH_QTY, MIN_SERVER_VER_CME_TAGGING_FIELDS,
14 MIN_SERVER_VER_CONTRACT_DATA_CHAIN, MIN_SERVER_VER_CUSTOMER_ACCOUNT,
15 MIN_SERVER_VER_D_PEG_ORDERS, MIN_SERVER_VER_DECISION_MAKER, MIN_SERVER_VER_DELTA_NEUTRAL,
16 MIN_SERVER_VER_DELTA_NEUTRAL_CONID, MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE,
17 MIN_SERVER_VER_DURATION, MIN_SERVER_VER_EXECUTION_DATA_CHAIN, MIN_SERVER_VER_EXT_OPERATOR,
18 MIN_SERVER_VER_FA_PROFILE_DESUPPORT, MIN_SERVER_VER_HEDGE_MAX_SIZE,
19 MIN_SERVER_VER_HEDGE_ORDERS, MIN_SERVER_VER_HISTORICAL_TICKS, MIN_SERVER_VER_IMBALANCE_ONLY,
20 MIN_SERVER_VER_INCLUDE_OVERNIGHT, MIN_SERVER_VER_LINKING, MIN_SERVER_VER_MANUAL_ORDER_TIME,
21 MIN_SERVER_VER_MANUAL_ORDER_TIME_EXERCISE_OPTIONS, MIN_SERVER_VER_MIFID_EXECUTION,
22 MIN_SERVER_VER_MKT_DEPTH_PRIM_EXCHANGE, MIN_SERVER_VER_MODELS_SUPPORT,
23 MIN_SERVER_VER_NEWS_QUERY_ORIGINS, MIN_SERVER_VER_NOT_HELD,
24 MIN_SERVER_VER_OPT_OUT_SMART_ROUTING, MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE,
25 MIN_SERVER_VER_ORDER_CONTAINER, MIN_SERVER_VER_ORDER_SOLICITED,
26 MIN_SERVER_VER_PARAMETRIZED_DAYS_OF_EXECUTIONS, MIN_SERVER_VER_PEGBEST_PEGMID_OFFSETS,
27 MIN_SERVER_VER_PEGGED_TO_BENCHMARK, MIN_SERVER_VER_PLACE_ORDER_CONID,
28 MIN_SERVER_VER_POST_TO_ATS, MIN_SERVER_VER_PRICE_MGMT_ALGO, MIN_SERVER_VER_PRIMARYEXCH,
29 MIN_SERVER_VER_PROFESSIONAL_CUSTOMER, MIN_SERVER_VER_PROTOBUF,
30 MIN_SERVER_VER_PROTOBUF_ACCOUNTS_POSITIONS, MIN_SERVER_VER_PROTOBUF_COMPLETED_ORDER,
31 MIN_SERVER_VER_PROTOBUF_CONTRACT_DATA, MIN_SERVER_VER_PROTOBUF_HISTORICAL_DATA,
32 MIN_SERVER_VER_PROTOBUF_MARKET_DATA, MIN_SERVER_VER_PROTOBUF_NEWS_DATA,
33 MIN_SERVER_VER_PROTOBUF_PLACE_ORDER, MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_1,
34 MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_2, MIN_SERVER_VER_PROTOBUF_REST_MESSAGES_3,
35 MIN_SERVER_VER_PROTOBUF_SCAN_DATA, MIN_SERVER_VER_PTA_ORDERS,
36 MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE, MIN_SERVER_VER_REPLACE_FA_END,
37 MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT, MIN_SERVER_VER_REQ_MKT_DATA_CONID,
38 MIN_SERVER_VER_REQ_SMART_COMPONENTS, MIN_SERVER_VER_RFQ_FIELDS, MIN_SERVER_VER_SCALE_ORDERS2,
39 MIN_SERVER_VER_SCALE_ORDERS3, MIN_SERVER_VER_SCALE_TABLE, MIN_SERVER_VER_SCANNER_GENERIC_OPTS,
40 MIN_SERVER_VER_SEC_ID_TYPE, MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS,
41 MIN_SERVER_VER_SMART_DEPTH, MIN_SERVER_VER_SOFT_DOLLAR_TIER, MIN_SERVER_VER_SSHORTX_OLD,
42 MIN_SERVER_VER_SYNT_REALTIME_BARS, MIN_SERVER_VER_TICK_BY_TICK,
43 MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE, MIN_SERVER_VER_TRADING_CLASS,
44 MIN_SERVER_VER_TRAILING_PERCENT, MIN_SERVER_VER_UNDO_RFQ_FIELDS,
45};
46use crate::types::{
47 Contract, ExecutionFilter, Order, OrderCancel, ScannerSubscription, TagValue, TickerId,
48};
49
50pub trait EncodableRequest {
52 fn message(&self) -> Outgoing;
54
55 fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()>;
57
58 fn encode_fields_for_server_version(
60 &self,
61 fields: &mut FieldSink,
62 _server_version: i32,
63 ) -> TwsApiResult<()> {
64 self.encode_fields(fields)
65 }
66
67 fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
69 Ok(None)
70 }
71}
72
73pub fn encode_request_frame<R>(request: &R, server_version: i32) -> TwsApiResult<Vec<u8>>
75where
76 R: EncodableRequest,
77{
78 encode_request_frame_with_protobuf(request, server_version, true)
79}
80
81pub fn encode_request_frame_with_protobuf<R>(
83 request: &R,
84 server_version: i32,
85 prefer_protobuf: bool,
86) -> TwsApiResult<Vec<u8>>
87where
88 R: EncodableRequest,
89{
90 if prefer_protobuf
91 && protobuf_min_server_version(request.message())
92 .is_some_and(|min_version| server_version >= min_version)
93 && let Some(payload) = request.encode_protobuf()?
94 {
95 return Ok(comm::make_msg_proto(
96 request.message().protobuf_id(),
97 &payload,
98 ));
99 }
100
101 let mut fields = FieldSink::default();
102 request.encode_fields_for_server_version(&mut fields, server_version)?;
103 comm::make_msg(
104 request.message().id(),
105 server_version >= MIN_SERVER_VER_PROTOBUF,
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)?;
2278 if server_version >= MIN_SERVER_VER_CONTRACT_DATA_CHAIN {
2279 fields.push(self.req_id)?;
2280 }
2281
2282 fields
2283 .push(self.contract.con_id)?
2284 .push(&self.contract.symbol)?
2285 .push(&self.contract.sec_type)?
2286 .push(&self.contract.last_trade_date_or_contract_month)?
2287 .push_empty(self.contract.strike)?
2288 .push(&self.contract.right)?
2289 .push(&self.contract.multiplier)?;
2290
2291 if server_version >= MIN_SERVER_VER_PRIMARYEXCH {
2292 fields
2293 .push(&self.contract.exchange)?
2294 .push(&self.contract.primary_exchange)?;
2295 } else if server_version >= MIN_SERVER_VER_LINKING {
2296 let exchange = if !self.contract.primary_exchange.is_empty()
2297 && (self.contract.exchange == "BEST" || self.contract.exchange == "SMART")
2298 {
2299 format!(
2300 "{}:{}",
2301 self.contract.exchange, self.contract.primary_exchange
2302 )
2303 } else {
2304 self.contract.exchange.clone()
2305 };
2306 fields.push(exchange)?;
2307 }
2308
2309 fields
2310 .push(&self.contract.currency)?
2311 .push(&self.contract.local_symbol)?;
2312 if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2313 fields.push(&self.contract.trading_class)?;
2314 }
2315 fields.push(self.contract.include_expired)?;
2316 if server_version >= MIN_SERVER_VER_SEC_ID_TYPE {
2317 fields
2318 .push(&self.contract.sec_id_type)?
2319 .push(&self.contract.sec_id)?;
2320 }
2321 if server_version >= MIN_SERVER_VER_BOND_ISSUERID {
2322 fields.push(&self.contract.issuer_id)?;
2323 }
2324 Ok(())
2325 }
2326
2327 fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2328 let msg = protobuf::ContractDataRequest {
2329 req_id: Some(self.req_id),
2330 contract: Some(contract_to_proto(&self.contract, None)),
2331 };
2332 Ok(Some(msg.encode_to_vec()))
2333 }
2334}
2335
2336#[derive(Debug, Clone, Default, PartialEq)]
2338pub struct HistoricalDataRequest {
2339 pub req_id: i32,
2341 pub contract: Contract,
2343 pub end_date_time: String,
2345 pub duration_str: String,
2347 pub bar_size_setting: String,
2349 pub what_to_show: String,
2351 pub use_rth: i32,
2353 pub format_date: i32,
2355 pub keep_up_to_date: bool,
2357 pub chart_options: Vec<TagValue>,
2359}
2360
2361impl EncodableRequest for HistoricalDataRequest {
2362 fn message(&self) -> Outgoing {
2363 Outgoing::ReqHistoricalData
2364 }
2365
2366 fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2367 self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2368 }
2369
2370 fn encode_fields_for_server_version(
2371 &self,
2372 fields: &mut FieldSink,
2373 server_version: i32,
2374 ) -> TwsApiResult<()> {
2375 if server_version < MIN_SERVER_VER_SYNT_REALTIME_BARS {
2376 fields.push(6)?;
2377 }
2378 fields.push(self.req_id)?;
2379 if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2380 fields.push(self.contract.con_id)?;
2381 }
2382 encode_contract_historical_data(fields, &self.contract, server_version)?;
2383 fields
2384 .push(&self.end_date_time)?
2385 .push(&self.bar_size_setting)?
2386 .push(&self.duration_str)?
2387 .push(self.use_rth)?
2388 .push(&self.what_to_show)?
2389 .push(self.format_date)?;
2390
2391 if self.contract.sec_type == "BAG" {
2392 fields.push(self.contract.combo_legs.len())?;
2393 for leg in &self.contract.combo_legs {
2394 fields
2395 .push(leg.con_id)?
2396 .push(leg.ratio)?
2397 .push(&leg.action)?
2398 .push(&leg.exchange)?;
2399 }
2400 }
2401
2402 if server_version >= MIN_SERVER_VER_SYNT_REALTIME_BARS {
2403 fields.push(self.keep_up_to_date)?;
2404 }
2405 if server_version >= MIN_SERVER_VER_LINKING {
2406 fields.push(tag_values_to_tws_options(&self.chart_options))?;
2407 }
2408 Ok(())
2409 }
2410
2411 fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2412 let msg = protobuf::HistoricalDataRequest {
2413 req_id: Some(self.req_id),
2414 contract: Some(contract_to_proto(&self.contract, None)),
2415 end_date_time: non_empty(self.end_date_time.clone()),
2416 bar_size_setting: non_empty(self.bar_size_setting.clone()),
2417 duration: non_empty(self.duration_str.clone()),
2418 use_rth: (self.use_rth != 0).then_some(true),
2419 what_to_show: non_empty(self.what_to_show.clone()),
2420 format_date: Some(self.format_date),
2421 keep_up_to_date: self.keep_up_to_date.then_some(true),
2422 chart_options: tag_values_to_map(&self.chart_options),
2423 };
2424 Ok(Some(msg.encode_to_vec()))
2425 }
2426}
2427
2428#[derive(Debug, Clone, Default, PartialEq)]
2430pub struct ScannerSubscriptionRequest {
2431 pub req_id: i32,
2433 pub subscription: ScannerSubscription,
2435 pub scanner_subscription_options: Vec<TagValue>,
2437 pub scanner_subscription_filter_options: Vec<TagValue>,
2439}
2440
2441impl EncodableRequest for ScannerSubscriptionRequest {
2442 fn message(&self) -> Outgoing {
2443 Outgoing::ReqScannerSubscription
2444 }
2445
2446 fn encode_fields(&self, fields: &mut FieldSink) -> TwsApiResult<()> {
2447 self.encode_fields_for_server_version(fields, MAX_CLIENT_VER)
2448 }
2449
2450 fn encode_fields_for_server_version(
2451 &self,
2452 fields: &mut FieldSink,
2453 server_version: i32,
2454 ) -> TwsApiResult<()> {
2455 let sub = &self.subscription;
2456 if server_version < MIN_SERVER_VER_SCANNER_GENERIC_OPTS {
2457 fields.push(4)?;
2458 }
2459 fields
2460 .push(self.req_id)?
2461 .push(sub.number_of_rows)?
2462 .push(&sub.instrument)?
2463 .push(&sub.location_code)?
2464 .push(&sub.scan_code)?
2465 .push_empty(sub.above_price)?
2466 .push_empty(sub.below_price)?
2467 .push_empty(sub.above_volume)?
2468 .push_empty(sub.market_cap_above)?
2469 .push_empty(sub.market_cap_below)?
2470 .push(&sub.moody_rating_above)?
2471 .push(&sub.moody_rating_below)?
2472 .push(&sub.sp_rating_above)?
2473 .push(&sub.sp_rating_below)?
2474 .push(&sub.maturity_date_above)?
2475 .push(&sub.maturity_date_below)?
2476 .push_empty(sub.coupon_rate_above)?
2477 .push_empty(sub.coupon_rate_below)?
2478 .push(sub.exclude_convertible)?
2479 .push_empty(sub.average_option_volume_above)?
2480 .push(&sub.scanner_setting_pairs)?
2481 .push(&sub.stock_type_filter)?;
2482 if server_version >= MIN_SERVER_VER_SCANNER_GENERIC_OPTS {
2483 fields.push(tag_values_to_tws_options(
2484 &self.scanner_subscription_filter_options,
2485 ))?;
2486 }
2487 if server_version >= MIN_SERVER_VER_LINKING {
2488 fields.push(tag_values_to_tws_options(
2489 &self.scanner_subscription_options,
2490 ))?;
2491 }
2492 Ok(())
2493 }
2494
2495 fn encode_protobuf(&self) -> TwsApiResult<Option<Vec<u8>>> {
2496 let mut subscription = scanner_subscription_to_proto(&self.subscription);
2497 subscription.scanner_subscription_options =
2498 tag_values_to_map(&self.scanner_subscription_options);
2499 subscription.scanner_subscription_filter_options =
2500 tag_values_to_map(&self.scanner_subscription_filter_options);
2501
2502 let msg = protobuf::ScannerSubscriptionRequest {
2503 req_id: Some(self.req_id),
2504 scanner_subscription: Some(subscription),
2505 };
2506 Ok(Some(msg.encode_to_vec()))
2507 }
2508}
2509
2510fn encode_contract_head_or_histogram(
2511 fields: &mut FieldSink,
2512 contract: &Contract,
2513) -> TwsApiResult<()> {
2514 fields
2515 .push(contract.con_id)?
2516 .push(&contract.symbol)?
2517 .push(&contract.sec_type)?
2518 .push(&contract.last_trade_date_or_contract_month)?
2519 .push_empty(contract.strike)?
2520 .push(&contract.right)?
2521 .push(&contract.multiplier)?
2522 .push(&contract.exchange)?
2523 .push(&contract.primary_exchange)?
2524 .push(&contract.currency)?
2525 .push(&contract.local_symbol)?
2526 .push(&contract.trading_class)?
2527 .push(contract.include_expired)?;
2528 Ok(())
2529}
2530
2531fn encode_contract_real_time_bars(
2532 fields: &mut FieldSink,
2533 contract: &Contract,
2534 server_version: i32,
2535) -> TwsApiResult<()> {
2536 if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2537 fields.push(contract.con_id)?;
2538 }
2539 fields
2540 .push(&contract.symbol)?
2541 .push(&contract.sec_type)?
2542 .push(&contract.last_trade_date_or_contract_month)?
2543 .push_empty(contract.strike)?
2544 .push(&contract.right)?
2545 .push(&contract.multiplier)?
2546 .push(&contract.exchange)?
2547 .push(&contract.primary_exchange)?
2548 .push(&contract.currency)?
2549 .push(&contract.local_symbol)?;
2550 if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2551 fields.push(&contract.trading_class)?;
2552 }
2553 Ok(())
2554}
2555
2556fn encode_contract_market_data(
2557 fields: &mut FieldSink,
2558 contract: &Contract,
2559 server_version: i32,
2560) -> TwsApiResult<()> {
2561 fields
2562 .push(&contract.symbol)?
2563 .push(&contract.sec_type)?
2564 .push(&contract.last_trade_date_or_contract_month)?
2565 .push_empty(contract.strike)?
2566 .push(&contract.right)?
2567 .push(&contract.multiplier)?
2568 .push(&contract.exchange)?
2569 .push(&contract.primary_exchange)?
2570 .push(&contract.currency)?
2571 .push(&contract.local_symbol)?;
2572 if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2573 fields.push(&contract.trading_class)?;
2574 }
2575 Ok(())
2576}
2577
2578fn encode_contract_historical_data(
2579 fields: &mut FieldSink,
2580 contract: &Contract,
2581 server_version: i32,
2582) -> TwsApiResult<()> {
2583 fields
2584 .push(&contract.symbol)?
2585 .push(&contract.sec_type)?
2586 .push(&contract.last_trade_date_or_contract_month)?
2587 .push_empty(contract.strike)?
2588 .push(&contract.right)?
2589 .push(&contract.multiplier)?
2590 .push(&contract.exchange)?
2591 .push(&contract.primary_exchange)?
2592 .push(&contract.currency)?
2593 .push(&contract.local_symbol)?;
2594 if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2595 fields.push(&contract.trading_class)?;
2596 }
2597 fields.push(contract.include_expired)?;
2598 Ok(())
2599}
2600
2601fn encode_contract_for_calculation(
2602 fields: &mut FieldSink,
2603 contract: &Contract,
2604 server_version: i32,
2605) -> TwsApiResult<()> {
2606 fields
2607 .push(contract.con_id)?
2608 .push(&contract.symbol)?
2609 .push(&contract.sec_type)?
2610 .push(&contract.last_trade_date_or_contract_month)?
2611 .push_empty(contract.strike)?
2612 .push(&contract.right)?
2613 .push(&contract.multiplier)?
2614 .push(&contract.exchange)?
2615 .push(&contract.primary_exchange)?
2616 .push(&contract.currency)?
2617 .push(&contract.local_symbol)?;
2618 if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2619 fields.push(&contract.trading_class)?;
2620 }
2621 Ok(())
2622}
2623
2624fn encode_place_order_fields(
2625 fields: &mut FieldSink,
2626 server_version: i32,
2627 order_id: i32,
2628 contract: &Contract,
2629 order: &Order,
2630 extra_fields: &str,
2631) -> TwsApiResult<()> {
2632 if server_version < MIN_SERVER_VER_ORDER_CONTAINER {
2633 fields.push(45)?;
2634 }
2635
2636 fields.push(order_id)?;
2637 if server_version >= MIN_SERVER_VER_PLACE_ORDER_CONID {
2638 fields.push(contract.con_id)?;
2639 }
2640 fields
2641 .push(&contract.symbol)?
2642 .push(&contract.sec_type)?
2643 .push(&contract.last_trade_date_or_contract_month)?
2644 .push_empty(contract.strike)?
2645 .push(&contract.right)?
2646 .push(&contract.multiplier)?
2647 .push(&contract.exchange)?
2648 .push(&contract.primary_exchange)?
2649 .push(&contract.currency)?
2650 .push(&contract.local_symbol)?;
2651 if server_version >= MIN_SERVER_VER_TRADING_CLASS {
2652 fields.push(&contract.trading_class)?;
2653 }
2654 if server_version >= MIN_SERVER_VER_SEC_ID_TYPE {
2655 fields.push(&contract.sec_id_type)?.push(&contract.sec_id)?;
2656 }
2657
2658 fields.push(&order.action)?;
2659 fields.push(order.total_quantity.to_string())?;
2660 fields
2661 .push(&order.order_type)?
2662 .push_empty(order.limit_price)?
2663 .push_empty(order.aux_price)?
2664 .push(&order.tif)?
2665 .push(&order.oca_group)?
2666 .push(&order.account)?
2667 .push(&order.open_close)?
2668 .push(order.origin as i32)?
2669 .push(&order.order_ref)?
2670 .push(order.transmit)?
2671 .push(order.parent_id)?
2672 .push(order.block_order)?
2673 .push(order.sweep_to_fill)?
2674 .push(order.display_size)?
2675 .push(order.trigger_method)?
2676 .push(order.outside_rth)?
2677 .push(order.hidden)?;
2678
2679 if contract.sec_type == "BAG" {
2680 fields.push(contract.combo_legs.len())?;
2681 for leg in &contract.combo_legs {
2682 fields
2683 .push(leg.con_id)?
2684 .push(leg.ratio)?
2685 .push(&leg.action)?
2686 .push(&leg.exchange)?
2687 .push(leg.open_close as i32)?
2688 .push(leg.short_sale_slot)?
2689 .push(&leg.designated_location)?;
2690 if server_version >= MIN_SERVER_VER_SSHORTX_OLD {
2691 fields.push(leg.exempt_code)?;
2692 }
2693 }
2694 }
2695
2696 if server_version >= MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE && contract.sec_type == "BAG" {
2697 fields.push(order.order_combo_legs.len())?;
2698 for leg in &order.order_combo_legs {
2699 fields.push_empty(leg.price)?;
2700 }
2701 }
2702
2703 if server_version >= MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS && contract.sec_type == "BAG" {
2704 encode_tag_values(fields, &order.smart_combo_routing_params)?;
2705 }
2706
2707 fields
2708 .push("")?
2709 .push(order.discretionary_amount)?
2710 .push(&order.good_after_time)?
2711 .push(&order.good_till_date)?
2712 .push(&order.fa_group)?
2713 .push(&order.fa_method)?
2714 .push(&order.fa_percentage)?;
2715 if server_version < MIN_SERVER_VER_FA_PROFILE_DESUPPORT {
2716 fields.push("")?;
2717 }
2718 if server_version >= MIN_SERVER_VER_MODELS_SUPPORT {
2719 fields.push(&order.model_code)?;
2720 }
2721
2722 fields
2723 .push(order.short_sale_slot)?
2724 .push(&order.designated_location)?;
2725 if server_version >= MIN_SERVER_VER_SSHORTX_OLD {
2726 fields.push(order.exempt_code)?;
2727 }
2728
2729 fields
2730 .push(order.oca_type)?
2731 .push(&order.rule80a)?
2732 .push(&order.settling_firm)?
2733 .push(order.all_or_none)?
2734 .push_empty(order.min_qty)?
2735 .push_empty(order.percent_offset)?
2736 .push(false)?
2737 .push(false)?
2738 .push_empty(UNSET_DOUBLE)?
2739 .push(order.auction_strategy as i32)?
2740 .push_empty(order.starting_price)?
2741 .push_empty(order.stock_ref_price)?
2742 .push_empty(order.delta)?
2743 .push_empty(order.stock_range_lower)?
2744 .push_empty(order.stock_range_upper)?
2745 .push(order.override_percentage_constraints)?
2746 .push_empty(order.volatility)?
2747 .push_empty(order.volatility_type)?
2748 .push(&order.delta_neutral_order_type)?
2749 .push_empty(order.delta_neutral_aux_price)?;
2750
2751 if server_version >= MIN_SERVER_VER_DELTA_NEUTRAL_CONID
2752 && !order.delta_neutral_order_type.is_empty()
2753 {
2754 fields
2755 .push(order.delta_neutral_con_id)?
2756 .push(&order.delta_neutral_settling_firm)?
2757 .push(&order.delta_neutral_clearing_account)?
2758 .push(&order.delta_neutral_clearing_intent)?;
2759 }
2760 if server_version >= MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE
2761 && !order.delta_neutral_order_type.is_empty()
2762 {
2763 fields
2764 .push(&order.delta_neutral_open_close)?
2765 .push(order.delta_neutral_short_sale)?
2766 .push(order.delta_neutral_short_sale_slot)?
2767 .push(&order.delta_neutral_designated_location)?;
2768 }
2769
2770 fields
2771 .push(order.continuous_update)?
2772 .push_empty(order.reference_price_type)?
2773 .push_empty(order.trail_stop_price)?;
2774 if server_version >= MIN_SERVER_VER_TRAILING_PERCENT {
2775 fields.push_empty(order.trailing_percent)?;
2776 }
2777
2778 if server_version >= MIN_SERVER_VER_SCALE_ORDERS2 {
2779 fields
2780 .push_empty(order.scale_init_level_size)?
2781 .push_empty(order.scale_subs_level_size)?;
2782 } else {
2783 fields.push("")?.push_empty(order.scale_init_level_size)?;
2784 }
2785 fields.push_empty(order.scale_price_increment)?;
2786 if server_version >= MIN_SERVER_VER_SCALE_ORDERS3
2787 && order.scale_price_increment != UNSET_DOUBLE
2788 && order.scale_price_increment > 0.0
2789 {
2790 fields
2791 .push_empty(order.scale_price_adjust_value)?
2792 .push_empty(order.scale_price_adjust_interval)?
2793 .push_empty(order.scale_profit_offset)?
2794 .push(order.scale_auto_reset)?
2795 .push_empty(order.scale_init_position)?
2796 .push_empty(order.scale_init_fill_qty)?
2797 .push(order.scale_random_percent)?;
2798 }
2799 if server_version >= MIN_SERVER_VER_SCALE_TABLE {
2800 fields
2801 .push(&order.scale_table)?
2802 .push(&order.active_start_time)?
2803 .push(&order.active_stop_time)?;
2804 }
2805
2806 if server_version >= MIN_SERVER_VER_HEDGE_ORDERS {
2807 fields.push(&order.hedge_type)?;
2808 if !order.hedge_type.is_empty() {
2809 fields.push(&order.hedge_param)?;
2810 }
2811 }
2812 if server_version >= MIN_SERVER_VER_OPT_OUT_SMART_ROUTING {
2813 fields.push(order.opt_out_smart_routing)?;
2814 }
2815 if server_version >= MIN_SERVER_VER_PTA_ORDERS {
2816 fields
2817 .push(&order.clearing_account)?
2818 .push(&order.clearing_intent)?;
2819 }
2820 if server_version >= MIN_SERVER_VER_NOT_HELD {
2821 fields.push(order.not_held)?;
2822 }
2823 if server_version >= MIN_SERVER_VER_DELTA_NEUTRAL {
2824 if let Some(delta) = &contract.delta_neutral_contract {
2825 fields
2826 .push(true)?
2827 .push(delta.con_id)?
2828 .push(delta.delta)?
2829 .push(delta.price)?;
2830 } else {
2831 fields.push(false)?;
2832 }
2833 }
2834 if server_version >= MIN_SERVER_VER_ALGO_ORDERS {
2835 fields.push(&order.algo_strategy)?;
2836 if !order.algo_strategy.is_empty() {
2837 encode_tag_values(fields, &order.algo_params)?;
2838 }
2839 }
2840 if server_version >= MIN_SERVER_VER_ALGO_ID {
2841 fields.push(&order.algo_id)?;
2842 }
2843 fields.push(order.what_if)?;
2844 if server_version >= MIN_SERVER_VER_LINKING {
2845 fields.push(tag_values_to_tws_options(&order.order_misc_options))?;
2846 }
2847 if server_version >= MIN_SERVER_VER_ORDER_SOLICITED {
2848 fields.push(order.solicited)?;
2849 }
2850 if server_version >= MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE {
2851 fields
2852 .push(order.randomize_size)?
2853 .push(order.randomize_price)?;
2854 }
2855 if server_version >= MIN_SERVER_VER_PEGGED_TO_BENCHMARK {
2856 if is_peg_benchmark_order(&order.order_type) {
2857 fields
2858 .push(order.reference_contract_id)?
2859 .push(order.is_pegged_change_amount_decrease)?
2860 .push(order.pegged_change_amount)?
2861 .push(order.reference_change_amount)?
2862 .push(&order.reference_exchange_id)?;
2863 }
2864 fields.push(order.conditions.len())?;
2865 if !order.conditions.is_empty() {
2866 for condition in &order.conditions {
2867 encode_order_condition(fields, condition)?;
2868 }
2869 fields
2870 .push(order.conditions_ignore_rth)?
2871 .push(order.conditions_cancel_order)?;
2872 }
2873 fields
2874 .push(&order.adjusted_order_type)?
2875 .push(order.trigger_price)?
2876 .push(order.limit_price_offset)?
2877 .push(order.adjusted_stop_price)?
2878 .push(order.adjusted_stop_limit_price)?
2879 .push(order.adjusted_trailing_amount)?
2880 .push(order.adjustable_trailing_unit)?;
2881 }
2882 if server_version >= MIN_SERVER_VER_EXT_OPERATOR {
2883 fields.push(&order.ext_operator)?;
2884 }
2885 if server_version >= MIN_SERVER_VER_SOFT_DOLLAR_TIER {
2886 fields
2887 .push(&order.soft_dollar_tier.name)?
2888 .push(&order.soft_dollar_tier.value)?;
2889 }
2890 if server_version >= MIN_SERVER_VER_CASH_QTY {
2891 fields.push(order.cash_qty)?;
2892 }
2893 if server_version >= MIN_SERVER_VER_DECISION_MAKER {
2894 fields
2895 .push(&order.mifid2_decision_maker)?
2896 .push(&order.mifid2_decision_algo)?;
2897 }
2898 if server_version >= MIN_SERVER_VER_MIFID_EXECUTION {
2899 fields
2900 .push(&order.mifid2_execution_trader)?
2901 .push(&order.mifid2_execution_algo)?;
2902 }
2903 if server_version >= MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE {
2904 fields.push(order.dont_use_auto_price_for_hedge)?;
2905 }
2906 if server_version >= MIN_SERVER_VER_ORDER_CONTAINER {
2907 fields.push(order.is_oms_container)?;
2908 }
2909 if server_version >= MIN_SERVER_VER_D_PEG_ORDERS {
2910 fields.push(order.discretionary_up_to_limit_price)?;
2911 }
2912 if server_version >= MIN_SERVER_VER_PRICE_MGMT_ALGO {
2913 if order.use_price_mgmt_algo == UNSET_INTEGER {
2914 fields.push_empty(UNSET_INTEGER)?;
2915 } else {
2916 fields.push(order.use_price_mgmt_algo != 0)?;
2917 }
2918 }
2919 if server_version >= MIN_SERVER_VER_DURATION {
2920 fields.push(order.duration)?;
2921 }
2922 if server_version >= MIN_SERVER_VER_POST_TO_ATS {
2923 fields.push(order.post_to_ats)?;
2924 }
2925 if server_version >= MIN_SERVER_VER_AUTO_CANCEL_PARENT {
2926 fields.push(order.auto_cancel_parent)?;
2927 }
2928 if server_version >= MIN_SERVER_VER_ADVANCED_ORDER_REJECT {
2929 fields.push(&order.advanced_error_override)?;
2930 }
2931 if server_version >= MIN_SERVER_VER_MANUAL_ORDER_TIME {
2932 fields.push(&order.manual_order_time)?;
2933 }
2934 if server_version >= MIN_SERVER_VER_PEGBEST_PEGMID_OFFSETS {
2935 let mut send_mid_offsets = false;
2936 if contract.exchange == "IBKRATS" {
2937 fields.push_empty(order.min_trade_qty)?;
2938 }
2939 if is_peg_best_order(&order.order_type) {
2940 fields
2941 .push_empty(order.min_compete_size)?
2942 .push_empty(order.compete_against_best_offset)?;
2943 send_mid_offsets = order.compete_against_best_offset
2944 == crate::constants::COMPETE_AGAINST_BEST_OFFSET_UP_TO_MID;
2945 } else if is_peg_mid_order(&order.order_type) {
2946 send_mid_offsets = true;
2947 }
2948 if send_mid_offsets {
2949 fields
2950 .push_empty(order.mid_offset_at_whole)?
2951 .push_empty(order.mid_offset_at_half)?;
2952 }
2953 }
2954 if server_version >= MIN_SERVER_VER_CUSTOMER_ACCOUNT {
2955 fields.push(&order.customer_account)?;
2956 }
2957 if server_version >= MIN_SERVER_VER_PROFESSIONAL_CUSTOMER {
2958 fields.push(order.professional_customer)?;
2959 }
2960 if (MIN_SERVER_VER_RFQ_FIELDS..MIN_SERVER_VER_UNDO_RFQ_FIELDS).contains(&server_version) {
2961 fields.push("")?.push_empty(UNSET_INTEGER)?;
2962 }
2963 if server_version >= MIN_SERVER_VER_INCLUDE_OVERNIGHT {
2964 fields.push(order.include_overnight)?;
2965 }
2966 if server_version >= MIN_SERVER_VER_CME_TAGGING_FIELDS {
2967 fields.push(order.manual_order_indicator)?;
2968 }
2969 if server_version >= MIN_SERVER_VER_IMBALANCE_ONLY {
2970 fields.push(order.imbalance_only)?;
2971 }
2972
2973 fields.push_raw(extra_fields);
2974 Ok(())
2975}
2976
2977fn encode_order_condition(
2978 fields: &mut FieldSink,
2979 condition: &crate::types::OrderCondition,
2980) -> TwsApiResult<()> {
2981 use crate::types::OrderCondition;
2982
2983 match condition {
2984 OrderCondition::Price {
2985 is_conjunction_connection,
2986 trigger_method,
2987 con_id,
2988 exchange,
2989 is_more,
2990 price,
2991 } => {
2992 fields
2993 .push(1)?
2994 .push(if *is_conjunction_connection { "a" } else { "o" })?
2995 .push(*is_more)?
2996 .push(*price)?
2997 .push(*con_id)?
2998 .push(&exchange[..])?
2999 .push(*trigger_method)?;
3000 }
3001 OrderCondition::Time {
3002 is_conjunction_connection,
3003 is_more,
3004 time,
3005 } => {
3006 fields
3007 .push(3)?
3008 .push(if *is_conjunction_connection { "a" } else { "o" })?
3009 .push(*is_more)?
3010 .push(&time[..])?;
3011 }
3012 OrderCondition::Margin {
3013 is_conjunction_connection,
3014 is_more,
3015 percent,
3016 } => {
3017 fields
3018 .push(4)?
3019 .push(if *is_conjunction_connection { "a" } else { "o" })?
3020 .push(*is_more)?
3021 .push(*percent)?;
3022 }
3023 OrderCondition::Execution {
3024 is_conjunction_connection,
3025 sec_type,
3026 exchange,
3027 symbol,
3028 } => {
3029 fields
3030 .push(5)?
3031 .push(if *is_conjunction_connection { "a" } else { "o" })?
3032 .push(&sec_type[..])?
3033 .push(&exchange[..])?
3034 .push(&symbol[..])?;
3035 }
3036 OrderCondition::Volume {
3037 is_conjunction_connection,
3038 con_id,
3039 exchange,
3040 is_more,
3041 volume,
3042 } => {
3043 fields
3044 .push(6)?
3045 .push(if *is_conjunction_connection { "a" } else { "o" })?
3046 .push(*is_more)?
3047 .push(*volume)?
3048 .push(*con_id)?
3049 .push(&exchange[..])?;
3050 }
3051 OrderCondition::PercentChange {
3052 is_conjunction_connection,
3053 con_id,
3054 exchange,
3055 is_more,
3056 change_percent,
3057 } => {
3058 fields
3059 .push(7)?
3060 .push(if *is_conjunction_connection { "a" } else { "o" })?
3061 .push(*is_more)?
3062 .push(*change_percent)?
3063 .push(*con_id)?
3064 .push(&exchange[..])?;
3065 }
3066 }
3067 Ok(())
3068}
3069
3070fn is_peg_benchmark_order(order_type: &str) -> bool {
3071 matches!(order_type, "PEG BENCH" | "PEGBENCH")
3072}
3073
3074fn is_peg_mid_order(order_type: &str) -> bool {
3075 matches!(order_type, "PEG MID" | "PEGMID")
3076}
3077
3078fn is_peg_best_order(order_type: &str) -> bool {
3079 matches!(order_type, "PEG BEST" | "PEGBEST")
3080}
3081
3082fn encode_tag_values(fields: &mut FieldSink, values: &[TagValue]) -> TwsApiResult<()> {
3083 fields.push(values.len())?;
3084 for value in values {
3085 fields.push(&value.tag)?.push(&value.value)?;
3086 }
3087 Ok(())
3088}
3089
3090fn tag_values_to_tws_options(values: &[TagValue]) -> String {
3091 values
3092 .iter()
3093 .map(|value| format!("{}={};", value.tag, value.value))
3094 .collect::<String>()
3095}
3096
3097fn tag_values_to_map(values: &[TagValue]) -> std::collections::HashMap<String, String> {
3098 values
3099 .iter()
3100 .map(|value| (value.tag.clone(), value.value.clone()))
3101 .collect()
3102}
3103
3104fn contract_to_proto(contract: &Contract, order: Option<&Order>) -> protobuf::Contract {
3105 protobuf::Contract {
3106 con_id: valid_i32(contract.con_id),
3107 symbol: non_empty(contract.symbol.clone()),
3108 sec_type: non_empty(contract.sec_type.clone()),
3109 last_trade_date_or_contract_month: non_empty(
3110 contract.last_trade_date_or_contract_month.clone(),
3111 ),
3112 strike: valid_f64(contract.strike),
3113 right: non_empty(contract.right.clone()),
3114 multiplier: contract.multiplier.parse::<f64>().ok(),
3115 exchange: non_empty(contract.exchange.clone()),
3116 primary_exch: non_empty(contract.primary_exchange.clone()),
3117 currency: non_empty(contract.currency.clone()),
3118 local_symbol: non_empty(contract.local_symbol.clone()),
3119 trading_class: non_empty(contract.trading_class.clone()),
3120 sec_id_type: non_empty(contract.sec_id_type.clone()),
3121 sec_id: non_empty(contract.sec_id.clone()),
3122 description: non_empty(contract.description.clone()),
3123 issuer_id: non_empty(contract.issuer_id.clone()),
3124 delta_neutral_contract: contract.delta_neutral_contract.as_ref().map(|delta| {
3125 protobuf::DeltaNeutralContract {
3126 con_id: valid_i32(delta.con_id),
3127 delta: valid_f64(delta.delta),
3128 price: valid_f64(delta.price),
3129 }
3130 }),
3131 include_expired: contract.include_expired.then_some(true),
3132 combo_legs_descrip: non_empty(contract.combo_legs_description.clone()),
3133 combo_legs: contract
3134 .combo_legs
3135 .iter()
3136 .enumerate()
3137 .map(|(idx, leg)| {
3138 let per_leg_price = order
3139 .and_then(|order| order.order_combo_legs.get(idx))
3140 .and_then(|leg| valid_f64(leg.price));
3141 protobuf::ComboLeg {
3142 con_id: valid_i32(leg.con_id),
3143 ratio: valid_i32(leg.ratio),
3144 action: non_empty(leg.action.clone()),
3145 exchange: non_empty(leg.exchange.clone()),
3146 open_close: valid_i32(leg.open_close as i32),
3147 short_sales_slot: valid_i32(leg.short_sale_slot),
3148 designated_location: non_empty(leg.designated_location.clone()),
3149 exempt_code: valid_i32(leg.exempt_code),
3150 per_leg_price,
3151 }
3152 })
3153 .collect(),
3154 last_trade_date: non_empty(contract.last_trade_date.clone()),
3155 }
3156}
3157
3158fn order_to_proto(order: &Order) -> protobuf::Order {
3159 let mut msg = protobuf::Order {
3160 client_id: valid_i32(order.client_id),
3161 order_id: valid_i32(order.order_id),
3162 perm_id: Some(order.perm_id),
3163 parent_id: valid_i32(order.parent_id),
3164 action: non_empty(order.action.clone()),
3165 total_quantity: Some(order.total_quantity.to_string()),
3166 display_size: valid_i32(order.display_size),
3167 order_type: non_empty(order.order_type.clone()),
3168 lmt_price: valid_f64(order.limit_price),
3169 aux_price: valid_f64(order.aux_price),
3170 tif: non_empty(order.tif.clone()),
3171 account: non_empty(order.account.clone()),
3172 settling_firm: non_empty(order.settling_firm.clone()),
3173 clearing_account: non_empty(order.clearing_account.clone()),
3174 clearing_intent: non_empty(order.clearing_intent.clone()),
3175 all_or_none: order.all_or_none.then_some(true),
3176 block_order: order.block_order.then_some(true),
3177 hidden: order.hidden.then_some(true),
3178 outside_rth: order.outside_rth.then_some(true),
3179 sweep_to_fill: order.sweep_to_fill.then_some(true),
3180 percent_offset: valid_f64(order.percent_offset),
3181 trailing_percent: valid_f64(order.trailing_percent),
3182 trail_stop_price: valid_f64(order.trail_stop_price),
3183 min_qty: valid_i32(order.min_qty),
3184 good_after_time: non_empty(order.good_after_time.clone()),
3185 good_till_date: non_empty(order.good_till_date.clone()),
3186 oca_group: non_empty(order.oca_group.clone()),
3187 order_ref: non_empty(order.order_ref.clone()),
3188 rule80_a: non_empty(order.rule80a.clone()),
3189 oca_type: valid_i32(order.oca_type),
3190 trigger_method: valid_i32(order.trigger_method),
3191 active_start_time: non_empty(order.active_start_time.clone()),
3192 active_stop_time: non_empty(order.active_stop_time.clone()),
3193 fa_group: non_empty(order.fa_group.clone()),
3194 fa_method: non_empty(order.fa_method.clone()),
3195 fa_percentage: non_empty(order.fa_percentage.clone()),
3196 volatility: valid_f64(order.volatility),
3197 volatility_type: valid_i32(order.volatility_type),
3198 continuous_update: order.continuous_update.then_some(true),
3199 reference_price_type: valid_i32(order.reference_price_type),
3200 delta_neutral_order_type: non_empty(order.delta_neutral_order_type.clone()),
3201 delta_neutral_aux_price: valid_f64(order.delta_neutral_aux_price),
3202 delta_neutral_con_id: valid_i32(order.delta_neutral_con_id),
3203 delta_neutral_open_close: non_empty(order.delta_neutral_open_close.clone()),
3204 delta_neutral_short_sale: order.delta_neutral_short_sale.then_some(true),
3205 delta_neutral_short_sale_slot: valid_i32(order.delta_neutral_short_sale_slot),
3206 delta_neutral_designated_location: non_empty(
3207 order.delta_neutral_designated_location.clone(),
3208 ),
3209 scale_init_level_size: valid_i32(order.scale_init_level_size),
3210 scale_subs_level_size: valid_i32(order.scale_subs_level_size),
3211 scale_price_increment: valid_f64(order.scale_price_increment),
3212 scale_price_adjust_value: valid_f64(order.scale_price_adjust_value),
3213 scale_price_adjust_interval: valid_i32(order.scale_price_adjust_interval),
3214 scale_profit_offset: valid_f64(order.scale_profit_offset),
3215 scale_auto_reset: order.scale_auto_reset.then_some(true),
3216 scale_init_position: valid_i32(order.scale_init_position),
3217 scale_init_fill_qty: valid_i32(order.scale_init_fill_qty),
3218 scale_random_percent: order.scale_random_percent.then_some(true),
3219 scale_table: non_empty(order.scale_table.clone()),
3220 hedge_type: non_empty(order.hedge_type.clone()),
3221 hedge_param: non_empty(order.hedge_param.clone()),
3222 algo_strategy: non_empty(order.algo_strategy.clone()),
3223 algo_id: non_empty(order.algo_id.clone()),
3224 what_if: order.what_if.then_some(true),
3225 transmit: order.transmit.then_some(true),
3226 override_percentage_constraints: order.override_percentage_constraints.then_some(true),
3227 open_close: non_empty(order.open_close.clone()),
3228 origin: valid_i32(order.origin as i32),
3229 short_sale_slot: valid_i32(order.short_sale_slot),
3230 designated_location: non_empty(order.designated_location.clone()),
3231 exempt_code: valid_i32(order.exempt_code),
3232 delta_neutral_settling_firm: non_empty(order.delta_neutral_settling_firm.clone()),
3233 delta_neutral_clearing_account: non_empty(order.delta_neutral_clearing_account.clone()),
3234 delta_neutral_clearing_intent: non_empty(order.delta_neutral_clearing_intent.clone()),
3235 discretionary_amt: valid_f64(order.discretionary_amount),
3236 opt_out_smart_routing: order.opt_out_smart_routing.then_some(true),
3237 starting_price: valid_f64(order.starting_price),
3238 stock_ref_price: valid_f64(order.stock_ref_price),
3239 delta: valid_f64(order.delta),
3240 stock_range_lower: valid_f64(order.stock_range_lower),
3241 stock_range_upper: valid_f64(order.stock_range_upper),
3242 not_held: order.not_held.then_some(true),
3243 solicited: order.solicited.then_some(true),
3244 randomize_size: order.randomize_size.then_some(true),
3245 randomize_price: order.randomize_price.then_some(true),
3246 reference_contract_id: valid_i32(order.reference_contract_id),
3247 pegged_change_amount: valid_f64(order.pegged_change_amount),
3248 is_pegged_change_amount_decrease: order.is_pegged_change_amount_decrease.then_some(true),
3249 reference_change_amount: valid_f64(order.reference_change_amount),
3250 reference_exchange_id: non_empty(order.reference_exchange_id.clone()),
3251 adjusted_order_type: non_empty(order.adjusted_order_type.clone()),
3252 trigger_price: valid_f64(order.trigger_price),
3253 adjusted_stop_price: valid_f64(order.adjusted_stop_price),
3254 adjusted_stop_limit_price: valid_f64(order.adjusted_stop_limit_price),
3255 adjusted_trailing_amount: valid_f64(order.adjusted_trailing_amount),
3256 adjustable_trailing_unit: valid_i32(order.adjustable_trailing_unit),
3257 lmt_price_offset: valid_f64(order.limit_price_offset),
3258 model_code: non_empty(order.model_code.clone()),
3259 ext_operator: non_empty(order.ext_operator.clone()),
3260 soft_dollar_tier: Some(protobuf::SoftDollarTier {
3261 name: non_empty(order.soft_dollar_tier.name.clone()),
3262 value: non_empty(order.soft_dollar_tier.value.clone()),
3263 display_name: non_empty(order.soft_dollar_tier.display_name.clone()),
3264 }),
3265 cash_qty: valid_f64(order.cash_qty),
3266 mifid2_decision_maker: non_empty(order.mifid2_decision_maker.clone()),
3267 mifid2_decision_algo: non_empty(order.mifid2_decision_algo.clone()),
3268 mifid2_execution_trader: non_empty(order.mifid2_execution_trader.clone()),
3269 mifid2_execution_algo: non_empty(order.mifid2_execution_algo.clone()),
3270 dont_use_auto_price_for_hedge: order.dont_use_auto_price_for_hedge.then_some(true),
3271 is_oms_container: order.is_oms_container.then_some(true),
3272 discretionary_up_to_limit_price: order.discretionary_up_to_limit_price.then_some(true),
3273 auto_cancel_date: non_empty(order.auto_cancel_date.clone()),
3274 filled_quantity: non_empty(order.filled_quantity.to_string()),
3275 ref_futures_con_id: valid_i32(order.ref_futures_con_id),
3276 auto_cancel_parent: order.auto_cancel_parent.then_some(true),
3277 shareholder: non_empty(order.shareholder.clone()),
3278 imbalance_only: order.imbalance_only.then_some(true),
3279 route_marketable_to_bbo: valid_i32(order.route_marketable_to_bbo),
3280 parent_perm_id: Some(order.parent_perm_id),
3281 use_price_mgmt_algo: valid_i32(order.use_price_mgmt_algo),
3282 duration: valid_i32(order.duration),
3283 post_to_ats: valid_i32(order.post_to_ats),
3284 advanced_error_override: non_empty(order.advanced_error_override.clone()),
3285 manual_order_time: non_empty(order.manual_order_time.clone()),
3286 min_trade_qty: valid_i32(order.min_trade_qty),
3287 min_compete_size: valid_i32(order.min_compete_size),
3288 compete_against_best_offset: valid_f64(order.compete_against_best_offset),
3289 mid_offset_at_whole: valid_f64(order.mid_offset_at_whole),
3290 mid_offset_at_half: valid_f64(order.mid_offset_at_half),
3291 customer_account: non_empty(order.customer_account.clone()),
3292 professional_customer: order.professional_customer.then_some(true),
3293 bond_accrued_interest: non_empty(order.bond_accrued_interest.clone()),
3294 include_overnight: order.include_overnight.then_some(true),
3295 manual_order_indicator: valid_i32(order.manual_order_indicator),
3296 submitter: non_empty(order.submitter.clone()),
3297 deactivate: order.deactivate.then_some(true),
3298 post_only: order.post_only.then_some(true),
3299 allow_pre_open: order.allow_pre_open.then_some(true),
3300 ignore_open_auction: order.ignore_open_auction.then_some(true),
3301 seek_price_improvement: valid_i32(order.seek_price_improvement),
3302 what_if_type: valid_i32(order.what_if_type),
3303 hedge_max_size: valid_i32(order.hedge_max_size),
3304 ..protobuf::Order::default()
3305 };
3306 msg.algo_params = tag_values_to_map(&order.algo_params);
3307 msg.smart_combo_routing_params = tag_values_to_map(&order.smart_combo_routing_params);
3308 msg.order_misc_options = tag_values_to_map(&order.order_misc_options);
3309 msg
3310}
3311
3312fn attached_orders_to_proto(order: &Order) -> Option<protobuf::AttachedOrders> {
3313 let attached = protobuf::AttachedOrders {
3314 sl_order_id: valid_i32(order.stop_loss_order_id),
3315 sl_order_type: non_empty(order.stop_loss_order_type.clone()),
3316 pt_order_id: valid_i32(order.profit_taker_order_id),
3317 pt_order_type: non_empty(order.profit_taker_order_type.clone()),
3318 };
3319 (attached.sl_order_id.is_some()
3320 || attached.sl_order_type.is_some()
3321 || attached.pt_order_id.is_some()
3322 || attached.pt_order_type.is_some())
3323 .then_some(attached)
3324}
3325
3326pub(crate) fn validate_order_parameters(
3327 order: &Order,
3328 server_version: i32,
3329) -> Option<OrderFieldValidation> {
3330 if server_version < MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1 {
3331 if order.deactivate {
3332 return Some(OrderFieldValidation {
3333 parameter: "deactivate",
3334 min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
3335 });
3336 }
3337 if order.post_only {
3338 return Some(OrderFieldValidation {
3339 parameter: "postOnly",
3340 min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
3341 });
3342 }
3343 if order.allow_pre_open {
3344 return Some(OrderFieldValidation {
3345 parameter: "allowPreOpen",
3346 min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
3347 });
3348 }
3349 if order.ignore_open_auction {
3350 return Some(OrderFieldValidation {
3351 parameter: "ignoreOpenAuction",
3352 min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1,
3353 });
3354 }
3355 }
3356
3357 if server_version < MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2 {
3358 if order.route_marketable_to_bbo != UNSET_INTEGER {
3359 return Some(OrderFieldValidation {
3360 parameter: "routeMarketableToBbo",
3361 min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2,
3362 });
3363 }
3364 if order.seek_price_improvement != UNSET_INTEGER {
3365 return Some(OrderFieldValidation {
3366 parameter: "seekPriceImprovement",
3367 min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2,
3368 });
3369 }
3370 if order.what_if_type != UNSET_INTEGER {
3371 return Some(OrderFieldValidation {
3372 parameter: "whatIfType",
3373 min_version: MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_2,
3374 });
3375 }
3376 }
3377
3378 if server_version < MIN_SERVER_VER_HEDGE_MAX_SIZE && order.hedge_max_size != UNSET_INTEGER {
3379 return Some(OrderFieldValidation {
3380 parameter: "hedgeMaxSize",
3381 min_version: MIN_SERVER_VER_HEDGE_MAX_SIZE,
3382 });
3383 }
3384
3385 None
3386}
3387
3388pub(crate) fn validate_attached_orders_parameters(
3389 order: &Order,
3390 server_version: i32,
3391) -> Option<OrderFieldValidation> {
3392 if server_version < MIN_SERVER_VER_ATTACHED_ORDERS {
3393 if order.stop_loss_order_id != UNSET_INTEGER {
3394 return Some(OrderFieldValidation {
3395 parameter: "slOrderId",
3396 min_version: MIN_SERVER_VER_ATTACHED_ORDERS,
3397 });
3398 }
3399 if !order.stop_loss_order_type.is_empty() {
3400 return Some(OrderFieldValidation {
3401 parameter: "slOrderType",
3402 min_version: MIN_SERVER_VER_ATTACHED_ORDERS,
3403 });
3404 }
3405 if order.profit_taker_order_id != UNSET_INTEGER {
3406 return Some(OrderFieldValidation {
3407 parameter: "ptOrderId",
3408 min_version: MIN_SERVER_VER_ATTACHED_ORDERS,
3409 });
3410 }
3411 if !order.profit_taker_order_type.is_empty() {
3412 return Some(OrderFieldValidation {
3413 parameter: "ptOrderType",
3414 min_version: MIN_SERVER_VER_ATTACHED_ORDERS,
3415 });
3416 }
3417 }
3418
3419 None
3420}
3421
3422#[cfg(test)]
3423#[allow(clippy::items_after_test_module, clippy::field_reassign_with_default)]
3424mod tests {
3425 use super::*;
3426 use crate::server_versions::{
3427 MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1, MIN_SERVER_VER_ATTACHED_ORDERS,
3428 MIN_SERVER_VER_HEDGE_MAX_SIZE,
3429 };
3430
3431 #[test]
3432 fn validate_order_parameters_rejects_new_fields_on_old_server_versions() {
3433 let mut order = Order::default();
3434 order.deactivate = true;
3435 let validation =
3436 validate_order_parameters(&order, MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1 - 1)
3437 .expect("expected deactivate to be rejected");
3438 assert_eq!(validation.parameter, "deactivate");
3439 assert_eq!(
3440 validation.min_version,
3441 MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1
3442 );
3443
3444 let mut order = Order::default();
3445 order.hedge_max_size = 1;
3446 let validation = validate_order_parameters(&order, MIN_SERVER_VER_HEDGE_MAX_SIZE - 1)
3447 .expect("expected hedgeMaxSize to be rejected");
3448 assert_eq!(validation.parameter, "hedgeMaxSize");
3449 assert_eq!(validation.min_version, MIN_SERVER_VER_HEDGE_MAX_SIZE);
3450 }
3451
3452 #[test]
3453 fn validate_attached_orders_parameters_rejects_low_versions() {
3454 let mut order = Order::default();
3455 order.stop_loss_order_id = 1;
3456 let validation =
3457 validate_attached_orders_parameters(&order, MIN_SERVER_VER_ATTACHED_ORDERS - 1)
3458 .expect("expected attached orders to be rejected");
3459 assert_eq!(validation.parameter, "slOrderId");
3460 assert_eq!(validation.min_version, MIN_SERVER_VER_ATTACHED_ORDERS);
3461 }
3462
3463 #[test]
3464 fn validate_order_parameters_allows_supported_defaults() {
3465 let order = Order::default();
3466 assert!(
3467 validate_order_parameters(&order, MIN_SERVER_VER_ADDITIONAL_ORDER_PARAMS_1 - 1)
3468 .is_none()
3469 );
3470 assert!(
3471 validate_attached_orders_parameters(&order, MIN_SERVER_VER_ATTACHED_ORDERS - 1)
3472 .is_none()
3473 );
3474 }
3475}
3476
3477fn order_cancel_to_proto(order_cancel: &OrderCancel) -> protobuf::OrderCancel {
3478 protobuf::OrderCancel {
3479 manual_order_cancel_time: non_empty(order_cancel.manual_order_cancel_time.clone()),
3480 ext_operator: non_empty(order_cancel.ext_operator.clone()),
3481 manual_order_indicator: valid_i32(order_cancel.manual_order_indicator),
3482 }
3483}
3484
3485fn execution_filter_to_proto(filter: &ExecutionFilter) -> protobuf::ExecutionFilter {
3486 protobuf::ExecutionFilter {
3487 client_id: valid_i32(filter.client_id),
3488 acct_code: non_empty(filter.acct_code.clone()),
3489 time: non_empty(filter.time.clone()),
3490 symbol: non_empty(filter.symbol.clone()),
3491 sec_type: non_empty(filter.sec_type.clone()),
3492 exchange: non_empty(filter.exchange.clone()),
3493 side: non_empty(filter.side.clone()),
3494 last_n_days: valid_i32(filter.last_n_days),
3495 specific_dates: filter.specific_dates.clone(),
3496 }
3497}
3498
3499fn scanner_subscription_to_proto(
3500 subscription: &ScannerSubscription,
3501) -> protobuf::ScannerSubscription {
3502 protobuf::ScannerSubscription {
3503 number_of_rows: valid_i32(subscription.number_of_rows),
3504 instrument: non_empty(subscription.instrument.clone()),
3505 location_code: non_empty(subscription.location_code.clone()),
3506 scan_code: non_empty(subscription.scan_code.clone()),
3507 above_price: valid_f64(subscription.above_price),
3508 below_price: valid_f64(subscription.below_price),
3509 above_volume: (subscription.above_volume != UNSET_INTEGER)
3510 .then_some(i64::from(subscription.above_volume)),
3511 market_cap_above: valid_f64(subscription.market_cap_above),
3512 market_cap_below: valid_f64(subscription.market_cap_below),
3513 moody_rating_above: non_empty(subscription.moody_rating_above.clone()),
3514 moody_rating_below: non_empty(subscription.moody_rating_below.clone()),
3515 sp_rating_above: non_empty(subscription.sp_rating_above.clone()),
3516 sp_rating_below: non_empty(subscription.sp_rating_below.clone()),
3517 maturity_date_above: non_empty(subscription.maturity_date_above.clone()),
3518 maturity_date_below: non_empty(subscription.maturity_date_below.clone()),
3519 coupon_rate_above: valid_f64(subscription.coupon_rate_above),
3520 coupon_rate_below: valid_f64(subscription.coupon_rate_below),
3521 exclude_convertible: subscription.exclude_convertible.then_some(true),
3522 average_option_volume_above: (subscription.average_option_volume_above != UNSET_INTEGER)
3523 .then_some(i64::from(subscription.average_option_volume_above)),
3524 scanner_setting_pairs: non_empty(subscription.scanner_setting_pairs.clone()),
3525 stock_type_filter: non_empty(subscription.stock_type_filter.clone()),
3526 scanner_subscription_filter_options: Default::default(),
3527 scanner_subscription_options: Default::default(),
3528 }
3529}
3530
3531fn non_empty(value: String) -> Option<String> {
3532 (!value.is_empty()).then_some(value)
3533}
3534
3535fn valid_i32(value: i32) -> Option<i32> {
3536 (value != UNSET_INTEGER).then_some(value)
3537}
3538
3539fn valid_f64(value: f64) -> Option<f64> {
3540 (value != UNSET_DOUBLE && value.is_finite()).then_some(value)
3541}
3542
3543fn ensure_server_version(server_version: i32, min_version: i32) -> TwsApiResult<()> {
3544 if server_version < min_version {
3545 return Err(TwsApiError::UnsupportedServerVersion {
3546 server_version,
3547 min_version,
3548 });
3549 }
3550 Ok(())
3551}