1use std::{fmt::Display, str::FromStr};
19
20use nautilus_model::enums::{
21 OrderSide, OrderStatus as NautilusOrderStatus, OrderType as NautilusOrderType,
22 TimeInForce as NautilusTimeInForce,
23};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(
28 feature = "python",
29 pyo3::pyclass(
30 module = "nautilus_trader.adapters.interactive_brokers",
31 from_py_object,
32 rename_all = "SCREAMING_SNAKE_CASE"
33 )
34)]
35#[cfg_attr(
36 feature = "python",
37 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
38 module = "nautilus_trader.adapters.interactive_brokers"
39 )
40)]
41pub enum IbAction {
42 Buy,
44 Bought,
46 Sell,
48 Sold,
50 SellShort,
52 SellLong,
54}
55
56impl IbAction {
57 #[must_use]
59 pub const fn order_side(self) -> OrderSide {
60 match self {
61 Self::Buy | Self::Bought => OrderSide::Buy,
62 Self::Sell | Self::Sold | Self::SellShort | Self::SellLong => OrderSide::Sell,
63 }
64 }
65
66 #[must_use]
68 pub const fn signed_multiplier(self) -> i32 {
69 match self {
70 Self::Buy | Self::Bought => 1,
71 Self::Sell | Self::Sold | Self::SellShort | Self::SellLong => -1,
72 }
73 }
74
75 #[must_use]
77 pub const fn ibapi_action(self) -> ibapi::orders::Action {
78 match self {
79 Self::Buy | Self::Bought => ibapi::orders::Action::Buy,
80 Self::Sell | Self::Sold => ibapi::orders::Action::Sell,
81 Self::SellShort => ibapi::orders::Action::SellShort,
82 Self::SellLong => ibapi::orders::Action::SellLong,
83 }
84 }
85}
86
87impl FromStr for IbAction {
88 type Err = anyhow::Error;
89
90 fn from_str(value: &str) -> Result<Self, Self::Err> {
91 match value {
92 "BUY" => Ok(Self::Buy),
93 "BOT" => Ok(Self::Bought),
94 "SELL" => Ok(Self::Sell),
95 "SLD" => Ok(Self::Sold),
96 "SSHORT" => Ok(Self::SellShort),
97 "SLONG" => Ok(Self::SellLong),
98 _ => anyhow::bail!("Unknown IB action: {value}"),
99 }
100 }
101}
102
103impl From<ibapi::orders::Action> for IbAction {
104 fn from(value: ibapi::orders::Action) -> Self {
105 match value {
106 ibapi::orders::Action::Buy => Self::Buy,
107 ibapi::orders::Action::Sell => Self::Sell,
108 ibapi::orders::Action::SellShort => Self::SellShort,
109 ibapi::orders::Action::SellLong => Self::SellLong,
110 }
111 }
112}
113
114impl Display for IbAction {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.write_str(match self {
117 Self::Buy => "BUY",
118 Self::Bought => "BOT",
119 Self::Sell => "SELL",
120 Self::Sold => "SLD",
121 Self::SellShort => "SSHORT",
122 Self::SellLong => "SLONG",
123 })
124 }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[cfg_attr(
130 feature = "python",
131 pyo3::pyclass(
132 module = "nautilus_trader.adapters.interactive_brokers",
133 from_py_object,
134 rename_all = "SCREAMING_SNAKE_CASE"
135 )
136)]
137#[cfg_attr(
138 feature = "python",
139 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
140 module = "nautilus_trader.adapters.interactive_brokers"
141 )
142)]
143pub enum IbOrderStatus {
144 ApiPending,
146 PendingSubmit,
148 PreSubmitted,
150 Submitted,
152 PendingCancel,
154 ApiCancelled,
156 Cancelled,
158 Filled,
160 Inactive,
162}
163
164impl IbOrderStatus {
165 #[must_use]
167 pub const fn nautilus_status(self) -> NautilusOrderStatus {
168 match self {
169 Self::ApiPending | Self::PendingSubmit => NautilusOrderStatus::Submitted,
170 Self::PreSubmitted | Self::Submitted => NautilusOrderStatus::Accepted,
172 Self::PendingCancel => NautilusOrderStatus::PendingCancel,
173 Self::ApiCancelled | Self::Cancelled => NautilusOrderStatus::Canceled,
174 Self::Filled => NautilusOrderStatus::Filled,
175 Self::Inactive => NautilusOrderStatus::Rejected,
176 }
177 }
178
179 #[must_use]
181 pub const fn is_accepted(self) -> bool {
182 matches!(self, Self::Submitted | Self::PreSubmitted)
183 }
184
185 #[must_use]
187 pub const fn is_terminal(self) -> bool {
188 matches!(
189 self,
190 Self::Filled | Self::ApiCancelled | Self::Cancelled | Self::Inactive
191 )
192 }
193}
194
195impl FromStr for IbOrderStatus {
196 type Err = anyhow::Error;
197
198 fn from_str(value: &str) -> Result<Self, Self::Err> {
199 match value {
200 "ApiPending" => Ok(Self::ApiPending),
201 "PendingSubmit" => Ok(Self::PendingSubmit),
202 "PreSubmitted" => Ok(Self::PreSubmitted),
203 "Submitted" => Ok(Self::Submitted),
204 "PendingCancel" => Ok(Self::PendingCancel),
205 "ApiCancelled" => Ok(Self::ApiCancelled),
206 "Cancelled" => Ok(Self::Cancelled),
207 "Filled" => Ok(Self::Filled),
208 "Inactive" => Ok(Self::Inactive),
209 _ => anyhow::bail!("Unknown IB order status: {value}"),
210 }
211 }
212}
213
214impl Display for IbOrderStatus {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 f.write_str(match self {
217 Self::ApiPending => "ApiPending",
218 Self::PendingSubmit => "PendingSubmit",
219 Self::PreSubmitted => "PreSubmitted",
220 Self::Submitted => "Submitted",
221 Self::PendingCancel => "PendingCancel",
222 Self::ApiCancelled => "ApiCancelled",
223 Self::Cancelled => "Cancelled",
224 Self::Filled => "Filled",
225 Self::Inactive => "Inactive",
226 })
227 }
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232#[cfg_attr(
233 feature = "python",
234 pyo3::pyclass(
235 module = "nautilus_trader.adapters.interactive_brokers",
236 from_py_object,
237 rename_all = "SCREAMING_SNAKE_CASE"
238 )
239)]
240#[cfg_attr(
241 feature = "python",
242 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
243 module = "nautilus_trader.adapters.interactive_brokers"
244 )
245)]
246pub enum IbOrderType {
247 Market,
249 MarketOnClose,
251 Limit,
253 LimitOnClose,
255 Stop,
257 StopLimit,
259 TrailingStop,
261 TrailingStopLimit,
263 MarketIfTouched,
265 LimitIfTouched,
267 MarketToLimit,
269 MarketWithProtection,
271 StopWithProtection,
273 Midprice,
275 PeggedToMarket,
277 PeggedToStock,
279 PeggedToMidpoint,
281 PeggedToBenchmark,
283 PegBest,
285 Relative,
287 PassiveRelative,
289 Volatility,
291 BoxTop,
293 RelativeLimitCombo,
295 RelativeMarketCombo,
297}
298
299impl IbOrderType {
300 #[must_use]
302 pub const fn as_str(self) -> &'static str {
303 match self {
304 Self::Market => "MKT",
305 Self::MarketOnClose => "MOC",
306 Self::Limit => "LMT",
307 Self::LimitOnClose => "LOC",
308 Self::Stop => "STP",
309 Self::StopLimit => "STP LMT",
310 Self::TrailingStop => "TRAIL",
311 Self::TrailingStopLimit => "TRAIL LIMIT",
312 Self::MarketIfTouched => "MIT",
313 Self::LimitIfTouched => "LIT",
314 Self::MarketToLimit => "MTL",
315 Self::MarketWithProtection => "MKT PRT",
316 Self::StopWithProtection => "STP PRT",
317 Self::Midprice => "MIDPRICE",
318 Self::PeggedToMarket => "PEG MKT",
319 Self::PeggedToStock => "PEG STK",
320 Self::PeggedToMidpoint => "PEG MID",
321 Self::PeggedToBenchmark => "PEG BENCH",
322 Self::PegBest => "PEG BEST",
323 Self::Relative => "REL",
324 Self::PassiveRelative => "PASSV REL",
325 Self::Volatility => "VOL",
326 Self::BoxTop => "BOX TOP",
327 Self::RelativeLimitCombo => "REL + LMT",
328 Self::RelativeMarketCombo => "REL + MKT",
329 }
330 }
331
332 #[must_use]
334 pub const fn nautilus_order_type(self) -> NautilusOrderType {
335 match self {
336 Self::Market | Self::MarketOnClose => NautilusOrderType::Market,
337 Self::Limit | Self::LimitOnClose => NautilusOrderType::Limit,
338 Self::Stop => NautilusOrderType::StopMarket,
339 Self::StopLimit => NautilusOrderType::StopLimit,
340 Self::TrailingStop => NautilusOrderType::TrailingStopMarket,
341 Self::TrailingStopLimit => NautilusOrderType::TrailingStopLimit,
342 Self::MarketIfTouched => NautilusOrderType::MarketIfTouched,
343 Self::LimitIfTouched => NautilusOrderType::LimitIfTouched,
344 Self::MarketToLimit => NautilusOrderType::MarketToLimit,
345 Self::MarketWithProtection
346 | Self::Midprice
347 | Self::PeggedToMarket
348 | Self::PeggedToStock
349 | Self::PeggedToMidpoint
350 | Self::PeggedToBenchmark
351 | Self::PegBest
352 | Self::Relative
353 | Self::PassiveRelative
354 | Self::Volatility
355 | Self::BoxTop
356 | Self::RelativeMarketCombo => NautilusOrderType::Market,
357 Self::RelativeLimitCombo => NautilusOrderType::Limit,
358 Self::StopWithProtection => NautilusOrderType::StopMarket,
359 }
360 }
361
362 #[must_use]
364 pub const fn from_nautilus(
365 order_type: NautilusOrderType,
366 time_in_force: NautilusTimeInForce,
367 ) -> Self {
368 match order_type {
369 NautilusOrderType::Market => {
370 if matches!(time_in_force, NautilusTimeInForce::AtTheClose) {
371 Self::MarketOnClose
372 } else {
373 Self::Market
374 }
375 }
376 NautilusOrderType::Limit => {
377 if matches!(time_in_force, NautilusTimeInForce::AtTheClose) {
378 Self::LimitOnClose
379 } else {
380 Self::Limit
381 }
382 }
383 NautilusOrderType::StopMarket => Self::Stop,
384 NautilusOrderType::StopLimit => Self::StopLimit,
385 NautilusOrderType::MarketIfTouched => Self::MarketIfTouched,
386 NautilusOrderType::LimitIfTouched => Self::LimitIfTouched,
387 NautilusOrderType::TrailingStopMarket => Self::TrailingStop,
388 NautilusOrderType::TrailingStopLimit => Self::TrailingStopLimit,
389 NautilusOrderType::MarketToLimit => Self::MarketToLimit,
390 }
391 }
392}
393
394impl FromStr for IbOrderType {
395 type Err = anyhow::Error;
396
397 fn from_str(value: &str) -> Result<Self, Self::Err> {
398 match value {
399 "MKT" => Ok(Self::Market),
400 "MOC" => Ok(Self::MarketOnClose),
401 "LMT" => Ok(Self::Limit),
402 "LOC" => Ok(Self::LimitOnClose),
403 "STP" => Ok(Self::Stop),
404 "STP LMT" => Ok(Self::StopLimit),
405 "TRAIL" => Ok(Self::TrailingStop),
406 "TRAIL LIMIT" => Ok(Self::TrailingStopLimit),
407 "MIT" => Ok(Self::MarketIfTouched),
408 "LIT" => Ok(Self::LimitIfTouched),
409 "MTL" => Ok(Self::MarketToLimit),
410 "MKT PRT" => Ok(Self::MarketWithProtection),
411 "STP PRT" => Ok(Self::StopWithProtection),
412 "MIDPRICE" => Ok(Self::Midprice),
413 "PEG MKT" => Ok(Self::PeggedToMarket),
414 "PEG STK" => Ok(Self::PeggedToStock),
415 "PEG MID" | "PEGMID" => Ok(Self::PeggedToMidpoint),
416 "PEG BENCH" | "PEGBENCH" => Ok(Self::PeggedToBenchmark),
417 "PEG BEST" | "PEGBEST" => Ok(Self::PegBest),
418 "REL" => Ok(Self::Relative),
419 "PASSV REL" => Ok(Self::PassiveRelative),
420 "VOL" => Ok(Self::Volatility),
421 "BOX TOP" => Ok(Self::BoxTop),
422 "REL + LMT" => Ok(Self::RelativeLimitCombo),
423 "REL + MKT" => Ok(Self::RelativeMarketCombo),
424 _ => anyhow::bail!("Unknown IB order type: {value}"),
425 }
426 }
427}
428
429impl Display for IbOrderType {
430 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431 f.write_str(self.as_str())
432 }
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq)]
437#[cfg_attr(
438 feature = "python",
439 pyo3::pyclass(
440 module = "nautilus_trader.adapters.interactive_brokers",
441 from_py_object,
442 rename_all = "SCREAMING_SNAKE_CASE"
443 )
444)]
445#[cfg_attr(
446 feature = "python",
447 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
448 module = "nautilus_trader.adapters.interactive_brokers"
449 )
450)]
451pub enum IbTimeInForce {
452 Day,
454 GoodTilCanceled,
456 ImmediateOrCancel,
458 GoodTilDate,
460 OnOpen,
462 FillOrKill,
464 DayTilCanceled,
466 Auction,
468}
469
470impl IbTimeInForce {
471 #[must_use]
473 pub const fn nautilus_time_in_force(self) -> NautilusTimeInForce {
474 match self {
475 Self::Day | Self::DayTilCanceled | Self::Auction => NautilusTimeInForce::Day,
476 Self::GoodTilCanceled => NautilusTimeInForce::Gtc,
477 Self::ImmediateOrCancel => NautilusTimeInForce::Ioc,
478 Self::GoodTilDate => NautilusTimeInForce::Gtd,
479 Self::OnOpen => NautilusTimeInForce::AtTheOpen,
480 Self::FillOrKill => NautilusTimeInForce::Fok,
481 }
482 }
483
484 #[must_use]
486 pub const fn from_nautilus(time_in_force: NautilusTimeInForce) -> Self {
487 match time_in_force {
488 NautilusTimeInForce::Day | NautilusTimeInForce::AtTheClose => Self::Day,
489 NautilusTimeInForce::Gtc => Self::GoodTilCanceled,
490 NautilusTimeInForce::Ioc => Self::ImmediateOrCancel,
491 NautilusTimeInForce::Fok => Self::FillOrKill,
492 NautilusTimeInForce::Gtd => Self::GoodTilDate,
493 NautilusTimeInForce::AtTheOpen => Self::OnOpen,
494 }
495 }
496
497 #[must_use]
499 pub const fn ibapi_time_in_force(self) -> ibapi::orders::TimeInForce {
500 match self {
501 Self::Day => ibapi::orders::TimeInForce::Day,
502 Self::GoodTilCanceled => ibapi::orders::TimeInForce::GoodTillCanceled,
503 Self::ImmediateOrCancel => ibapi::orders::TimeInForce::ImmediateOrCancel,
504 Self::GoodTilDate => ibapi::orders::TimeInForce::GoodTillDate,
505 Self::OnOpen => ibapi::orders::TimeInForce::OnOpen,
506 Self::FillOrKill => ibapi::orders::TimeInForce::FillOrKill,
507 Self::DayTilCanceled => ibapi::orders::TimeInForce::DayTillCanceled,
508 Self::Auction => ibapi::orders::TimeInForce::Auction,
509 }
510 }
511}
512
513impl From<ibapi::orders::TimeInForce> for IbTimeInForce {
514 fn from(value: ibapi::orders::TimeInForce) -> Self {
515 match value {
516 ibapi::orders::TimeInForce::Day => Self::Day,
517 ibapi::orders::TimeInForce::GoodTillCanceled => Self::GoodTilCanceled,
518 ibapi::orders::TimeInForce::ImmediateOrCancel => Self::ImmediateOrCancel,
519 ibapi::orders::TimeInForce::GoodTillDate => Self::GoodTilDate,
520 ibapi::orders::TimeInForce::OnOpen => Self::OnOpen,
521 ibapi::orders::TimeInForce::FillOrKill => Self::FillOrKill,
522 ibapi::orders::TimeInForce::DayTillCanceled => Self::DayTilCanceled,
523 ibapi::orders::TimeInForce::Auction => Self::Auction,
524 ibapi::orders::TimeInForce::GoodTillCrossing => {
526 tracing::warn!("Unsupported IB time in force GTX; reporting DAY");
527 Self::Day
528 }
529 ibapi::orders::TimeInForce::Unknown(raw) => {
530 tracing::warn!("Unknown IB time in force {raw}; reporting DAY");
531 Self::Day
532 }
533 }
534 }
535}
536
537impl FromStr for IbTimeInForce {
538 type Err = anyhow::Error;
539
540 fn from_str(value: &str) -> Result<Self, Self::Err> {
541 match value {
542 "DAY" => Ok(Self::Day),
543 "GTC" => Ok(Self::GoodTilCanceled),
544 "IOC" => Ok(Self::ImmediateOrCancel),
545 "GTD" => Ok(Self::GoodTilDate),
546 "OPG" => Ok(Self::OnOpen),
547 "FOK" => Ok(Self::FillOrKill),
548 "DTC" => Ok(Self::DayTilCanceled),
549 "AUC" => Ok(Self::Auction),
550 _ => anyhow::bail!("Unknown IB time in force: {value}"),
551 }
552 }
553}
554
555impl Display for IbTimeInForce {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 f.write_str(match self {
558 Self::Day => "DAY",
559 Self::GoodTilCanceled => "GTC",
560 Self::ImmediateOrCancel => "IOC",
561 Self::GoodTilDate => "GTD",
562 Self::OnOpen => "OPG",
563 Self::FillOrKill => "FOK",
564 Self::DayTilCanceled => "DTC",
565 Self::Auction => "AUC",
566 })
567 }
568}
569
570#[derive(Debug, Clone, Copy, PartialEq, Eq)]
572#[cfg_attr(
573 feature = "python",
574 pyo3::pyclass(
575 module = "nautilus_trader.adapters.interactive_brokers",
576 from_py_object,
577 rename_all = "SCREAMING_SNAKE_CASE"
578 )
579)]
580#[cfg_attr(
581 feature = "python",
582 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
583 module = "nautilus_trader.adapters.interactive_brokers"
584 )
585)]
586pub enum IbBuilderTimeInForce {
587 Day,
588 GoodTillCancel,
589 ImmediateOrCancel,
590 GoodTillDate,
591 FillOrKill,
592 GoodTillCrossing,
593 DayTillCanceled,
594 Auction,
595 OpeningAuction,
596}
597
598impl IbBuilderTimeInForce {
599 #[must_use]
600 pub const fn as_str(self) -> &'static str {
601 match self {
602 Self::Day => "DAY",
603 Self::GoodTillCancel => "GTC",
604 Self::ImmediateOrCancel => "IOC",
605 Self::GoodTillDate => "GTD",
606 Self::FillOrKill => "FOK",
607 Self::GoodTillCrossing => "GTX",
608 Self::DayTillCanceled => "DTC",
609 Self::Auction => "AUC",
610 Self::OpeningAuction => "OPG",
611 }
612 }
613}
614
615impl FromStr for IbBuilderTimeInForce {
616 type Err = anyhow::Error;
617
618 fn from_str(value: &str) -> Result<Self, Self::Err> {
619 match value {
620 "DAY" => Ok(Self::Day),
621 "GTC" => Ok(Self::GoodTillCancel),
622 "IOC" => Ok(Self::ImmediateOrCancel),
623 "GTD" => Ok(Self::GoodTillDate),
624 "FOK" => Ok(Self::FillOrKill),
625 "GTX" => Ok(Self::GoodTillCrossing),
626 "DTC" => Ok(Self::DayTillCanceled),
627 "AUC" => Ok(Self::Auction),
628 "OPG" => Ok(Self::OpeningAuction),
629 _ => anyhow::bail!("Unknown IB builder time in force: {value}"),
630 }
631 }
632}
633
634impl Display for IbBuilderTimeInForce {
635 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
636 f.write_str(self.as_str())
637 }
638}
639
640#[derive(Debug, Clone, Copy, PartialEq, Eq)]
642#[cfg_attr(
643 feature = "python",
644 pyo3::pyclass(
645 module = "nautilus_trader.adapters.interactive_brokers",
646 from_py_object,
647 rename_all = "SCREAMING_SNAKE_CASE"
648 )
649)]
650#[cfg_attr(
651 feature = "python",
652 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
653 module = "nautilus_trader.adapters.interactive_brokers"
654 )
655)]
656pub enum IbComboLegOpenClose {
657 Same,
658 Open,
659 Close,
660 Unknown,
661}
662
663impl IbComboLegOpenClose {
664 #[must_use]
666 pub const fn as_i32(self) -> i32 {
667 match self {
668 Self::Same => 0,
669 Self::Open => 1,
670 Self::Close => 2,
671 Self::Unknown => 3,
672 }
673 }
674
675 #[must_use]
677 pub const fn ibapi_combo_leg_open_close(self) -> ibapi::contracts::ComboLegOpenClose {
678 match self {
679 Self::Same => ibapi::contracts::ComboLegOpenClose::Same,
680 Self::Open => ibapi::contracts::ComboLegOpenClose::Open,
681 Self::Close => ibapi::contracts::ComboLegOpenClose::Close,
682 Self::Unknown => ibapi::contracts::ComboLegOpenClose::Unknown,
683 }
684 }
685}
686
687impl From<ibapi::contracts::ComboLegOpenClose> for IbComboLegOpenClose {
688 fn from(value: ibapi::contracts::ComboLegOpenClose) -> Self {
689 match value {
690 ibapi::contracts::ComboLegOpenClose::Same => Self::Same,
691 ibapi::contracts::ComboLegOpenClose::Open => Self::Open,
692 ibapi::contracts::ComboLegOpenClose::Close => Self::Close,
693 ibapi::contracts::ComboLegOpenClose::Unknown => Self::Unknown,
694 }
695 }
696}
697
698#[derive(Debug, Clone, Copy, PartialEq, Eq)]
700#[cfg_attr(
701 feature = "python",
702 pyo3::pyclass(
703 module = "nautilus_trader.adapters.interactive_brokers",
704 from_py_object,
705 rename_all = "SCREAMING_SNAKE_CASE"
706 )
707)]
708#[cfg_attr(
709 feature = "python",
710 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
711 module = "nautilus_trader.adapters.interactive_brokers"
712 )
713)]
714pub enum IbConditionKind {
715 Price,
717 Time,
719 Margin,
721 Execution,
723 Volume,
725 PercentChange,
727}
728
729impl IbConditionKind {
730 #[must_use]
732 pub const fn as_str(self) -> &'static str {
733 match self {
734 Self::Price => "price",
735 Self::Time => "time",
736 Self::Margin => "margin",
737 Self::Execution => "execution",
738 Self::Volume => "volume",
739 Self::PercentChange => "percent_change",
740 }
741 }
742}
743
744impl FromStr for IbConditionKind {
745 type Err = anyhow::Error;
746
747 fn from_str(value: &str) -> Result<Self, Self::Err> {
748 match value.to_ascii_lowercase().as_str() {
749 "price" => Ok(Self::Price),
750 "time" => Ok(Self::Time),
751 "margin" => Ok(Self::Margin),
752 "execution" => Ok(Self::Execution),
753 "volume" => Ok(Self::Volume),
754 "percent_change" | "percentchange" => Ok(Self::PercentChange),
755 _ => anyhow::bail!("Unknown IB condition kind: {value}"),
756 }
757 }
758}
759
760impl Display for IbConditionKind {
761 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
762 f.write_str(self.as_str())
763 }
764}
765
766#[derive(Debug, Clone, Copy, PartialEq, Eq)]
768#[cfg_attr(
769 feature = "python",
770 pyo3::pyclass(
771 module = "nautilus_trader.adapters.interactive_brokers",
772 from_py_object,
773 rename_all = "SCREAMING_SNAKE_CASE"
774 )
775)]
776#[cfg_attr(
777 feature = "python",
778 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
779 module = "nautilus_trader.adapters.interactive_brokers"
780 )
781)]
782pub enum IbConditionConjunction {
783 And,
785 Or,
787}
788
789impl IbConditionConjunction {
790 #[must_use]
792 pub const fn as_str(self) -> &'static str {
793 match self {
794 Self::And => "and",
795 Self::Or => "or",
796 }
797 }
798
799 #[must_use]
801 pub const fn is_conjunction(self) -> bool {
802 matches!(self, Self::And)
803 }
804}
805
806impl FromStr for IbConditionConjunction {
807 type Err = anyhow::Error;
808
809 fn from_str(value: &str) -> Result<Self, Self::Err> {
810 match value.to_ascii_lowercase().as_str() {
811 "and" | "a" => Ok(Self::And),
812 "or" | "o" => Ok(Self::Or),
813 _ => anyhow::bail!("Unknown IB condition conjunction: {value}"),
814 }
815 }
816}
817
818impl Display for IbConditionConjunction {
819 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
820 f.write_str(self.as_str())
821 }
822}
823
824#[derive(Debug, Clone, Copy, PartialEq, Eq)]
826#[cfg_attr(
827 feature = "python",
828 pyo3::pyclass(
829 module = "nautilus_trader.adapters.interactive_brokers",
830 from_py_object,
831 rename_all = "SCREAMING_SNAKE_CASE"
832 )
833)]
834#[cfg_attr(
835 feature = "python",
836 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
837 module = "nautilus_trader.adapters.interactive_brokers"
838 )
839)]
840pub enum IbTriggerMethod {
841 Default,
843 DoubleBidAsk,
845 Last,
847 DoubleLast,
849 BidAsk,
851 LastOrBidAsk,
853 Midpoint,
855}
856
857impl IbTriggerMethod {
858 #[must_use]
860 pub const fn as_i32(self) -> i32 {
861 match self {
862 Self::Default => 0,
863 Self::DoubleBidAsk => 1,
864 Self::Last => 2,
865 Self::DoubleLast => 3,
866 Self::BidAsk => 4,
867 Self::LastOrBidAsk => 7,
868 Self::Midpoint => 8,
869 }
870 }
871
872 #[must_use]
874 pub const fn ibapi_trigger_method(self) -> ibapi::orders::conditions::TriggerMethod {
875 match self {
876 Self::Default => ibapi::orders::conditions::TriggerMethod::Default,
877 Self::DoubleBidAsk => ibapi::orders::conditions::TriggerMethod::DoubleBidAsk,
878 Self::Last => ibapi::orders::conditions::TriggerMethod::Last,
879 Self::DoubleLast => ibapi::orders::conditions::TriggerMethod::DoubleLast,
880 Self::BidAsk => ibapi::orders::conditions::TriggerMethod::BidAsk,
881 Self::LastOrBidAsk => ibapi::orders::conditions::TriggerMethod::LastOrBidAsk,
882 Self::Midpoint => ibapi::orders::conditions::TriggerMethod::Midpoint,
883 }
884 }
885}
886
887impl TryFrom<i32> for IbTriggerMethod {
888 type Error = anyhow::Error;
889
890 fn try_from(value: i32) -> Result<Self, Self::Error> {
891 match value {
892 0 => Ok(Self::Default),
893 1 => Ok(Self::DoubleBidAsk),
894 2 => Ok(Self::Last),
895 3 => Ok(Self::DoubleLast),
896 4 => Ok(Self::BidAsk),
897 7 => Ok(Self::LastOrBidAsk),
898 8 => Ok(Self::Midpoint),
899 _ => anyhow::bail!("Unknown IB trigger method: {value}"),
900 }
901 }
902}
903
904impl From<IbTriggerMethod> for i32 {
905 fn from(value: IbTriggerMethod) -> Self {
906 value.as_i32()
907 }
908}
909
910impl From<ibapi::orders::conditions::TriggerMethod> for IbTriggerMethod {
911 fn from(value: ibapi::orders::conditions::TriggerMethod) -> Self {
912 match value {
913 ibapi::orders::conditions::TriggerMethod::Default => Self::Default,
914 ibapi::orders::conditions::TriggerMethod::DoubleBidAsk => Self::DoubleBidAsk,
915 ibapi::orders::conditions::TriggerMethod::Last => Self::Last,
916 ibapi::orders::conditions::TriggerMethod::DoubleLast => Self::DoubleLast,
917 ibapi::orders::conditions::TriggerMethod::BidAsk => Self::BidAsk,
918 ibapi::orders::conditions::TriggerMethod::LastOrBidAsk => Self::LastOrBidAsk,
919 ibapi::orders::conditions::TriggerMethod::Midpoint => Self::Midpoint,
920 ibapi::orders::conditions::TriggerMethod::Unknown(code) => {
921 tracing::warn!("Unknown IB trigger method {code}; using the default");
922 Self::Default
923 }
924 }
925 }
926}
927
928impl Display for IbTriggerMethod {
929 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
930 write!(f, "{}", self.as_i32())
931 }
932}
933
934#[derive(Debug, Clone, Copy, PartialEq, Eq)]
936#[cfg_attr(
937 feature = "python",
938 pyo3::pyclass(
939 module = "nautilus_trader.adapters.interactive_brokers",
940 from_py_object,
941 rename_all = "SCREAMING_SNAKE_CASE"
942 )
943)]
944#[cfg_attr(
945 feature = "python",
946 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
947 module = "nautilus_trader.adapters.interactive_brokers"
948 )
949)]
950pub enum IbOcaType {
951 None,
953 CancelWithBlock,
955 ReduceWithBlock,
957 ReduceWithoutBlock,
959}
960
961impl IbOcaType {
962 #[must_use]
964 pub const fn as_i32(self) -> i32 {
965 match self {
966 Self::None => 0,
967 Self::CancelWithBlock => 1,
968 Self::ReduceWithBlock => 2,
969 Self::ReduceWithoutBlock => 3,
970 }
971 }
972
973 #[must_use]
975 pub const fn ibapi_oca_type(self) -> ibapi::orders::OcaType {
976 match self {
977 Self::None => ibapi::orders::OcaType::None,
978 Self::CancelWithBlock => ibapi::orders::OcaType::CancelWithBlock,
979 Self::ReduceWithBlock => ibapi::orders::OcaType::ReduceWithBlock,
980 Self::ReduceWithoutBlock => ibapi::orders::OcaType::ReduceWithoutBlock,
981 }
982 }
983}
984
985impl TryFrom<i32> for IbOcaType {
986 type Error = anyhow::Error;
987
988 fn try_from(value: i32) -> Result<Self, Self::Error> {
989 match value {
990 0 => Ok(Self::None),
991 1 => Ok(Self::CancelWithBlock),
992 2 => Ok(Self::ReduceWithBlock),
993 3 => Ok(Self::ReduceWithoutBlock),
994 _ => anyhow::bail!("Unknown IB OCA type: {value}"),
995 }
996 }
997}
998
999impl From<IbOcaType> for i32 {
1000 fn from(value: IbOcaType) -> Self {
1001 value.as_i32()
1002 }
1003}
1004
1005impl From<ibapi::orders::OcaType> for IbOcaType {
1006 fn from(value: ibapi::orders::OcaType) -> Self {
1007 match value {
1008 ibapi::orders::OcaType::None => Self::None,
1009 ibapi::orders::OcaType::CancelWithBlock => Self::CancelWithBlock,
1010 ibapi::orders::OcaType::ReduceWithBlock => Self::ReduceWithBlock,
1011 ibapi::orders::OcaType::ReduceWithoutBlock => Self::ReduceWithoutBlock,
1012 ibapi::orders::OcaType::Unknown(code) => {
1013 tracing::warn!("Unknown IB OCA type {code}; reporting no OCA group handling");
1014 Self::None
1015 }
1016 }
1017 }
1018}
1019
1020impl Display for IbOcaType {
1021 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1022 write!(f, "{}", self.as_i32())
1023 }
1024}
1025
1026#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1028#[cfg_attr(
1029 feature = "python",
1030 pyo3::pyclass(
1031 module = "nautilus_trader.adapters.interactive_brokers",
1032 from_py_object,
1033 rename_all = "SCREAMING_SNAKE_CASE"
1034 )
1035)]
1036#[cfg_attr(
1037 feature = "python",
1038 pyo3_stub_gen::derive::gen_stub_pyclass_enum(
1039 module = "nautilus_trader.adapters.interactive_brokers"
1040 )
1041)]
1042pub enum IbLiquidity {
1043 None,
1044 AddedLiquidity,
1045 RemovedLiquidity,
1046 LiquidityRoutedOut,
1047 Unknown,
1049}
1050
1051impl IbLiquidity {
1052 #[must_use]
1054 pub const fn as_i32(self) -> i32 {
1055 match self {
1056 Self::None => 0,
1057 Self::AddedLiquidity => 1,
1058 Self::RemovedLiquidity => 2,
1059 Self::LiquidityRoutedOut => 3,
1060 Self::Unknown => -1,
1061 }
1062 }
1063
1064 #[must_use]
1066 pub fn ibapi_liquidity(self) -> ibapi::orders::Liquidity {
1067 ibapi::orders::Liquidity::from(self.as_i32())
1068 }
1069}
1070
1071impl From<i32> for IbLiquidity {
1072 fn from(value: i32) -> Self {
1073 match value {
1074 0 => Self::None,
1075 1 => Self::AddedLiquidity,
1076 2 => Self::RemovedLiquidity,
1077 3 => Self::LiquidityRoutedOut,
1078 _ => {
1079 tracing::warn!("IB execution used unknown liquidity code {value}");
1080 Self::Unknown
1081 }
1082 }
1083 }
1084}
1085
1086impl From<ibapi::orders::Liquidity> for IbLiquidity {
1087 fn from(value: ibapi::orders::Liquidity) -> Self {
1088 match value {
1089 ibapi::orders::Liquidity::None => Self::None,
1090 ibapi::orders::Liquidity::AddedLiquidity => Self::AddedLiquidity,
1091 ibapi::orders::Liquidity::RemovedLiquidity => Self::RemovedLiquidity,
1092 ibapi::orders::Liquidity::LiquidityRoutedOut => Self::LiquidityRoutedOut,
1093 ibapi::orders::Liquidity::Unknown(code) => {
1094 tracing::warn!("IB execution used unknown liquidity code {code}");
1095 Self::Unknown
1096 }
1097 }
1098 }
1099}
1100
1101impl Display for IbLiquidity {
1102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1103 write!(f, "{}", self.as_i32())
1104 }
1105}