Skip to main content

nautilus_interactive_brokers/common/enums/
order.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Interactive Brokers order enumerations.
17
18use 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/// Interactive Brokers execution/action side values.
26#[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 action from order data.
43    Buy,
44    /// Bought side from execution data.
45    Bought,
46    /// Sell action from order data.
47    Sell,
48    /// Sold side from execution data.
49    Sold,
50    /// Sell short action from order data.
51    SellShort,
52    /// Sell long action from order data.
53    SellLong,
54}
55
56impl IbAction {
57    /// Converts the IB action to a Nautilus order side.
58    #[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    /// Returns `1` for buy/bought and `-1` for sell/sold.
67    #[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    /// Converts to the rust-ibapi order action enum.
76    #[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/// Interactive Brokers order status values.
128#[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    /// Order is pending API processing.
145    ApiPending,
146    /// Order is pending submission.
147    PendingSubmit,
148    /// Order has been pre-submitted.
149    PreSubmitted,
150    /// Order has been submitted.
151    Submitted,
152    /// Order is pending cancellation.
153    PendingCancel,
154    /// Order was cancelled by the API.
155    ApiCancelled,
156    /// Order was cancelled.
157    Cancelled,
158    /// Order has been fully filled.
159    Filled,
160    /// Order is inactive.
161    Inactive,
162}
163
164impl IbOrderStatus {
165    /// Converts the IB order status to a Nautilus order status.
166    #[must_use]
167    pub const fn nautilus_status(self) -> NautilusOrderStatus {
168        match self {
169            Self::ApiPending | Self::PendingSubmit => NautilusOrderStatus::Submitted,
170            // IB accepted the order but holds it, for example until `goodAfterTime`
171            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    /// Returns whether this status means an order should be treated as accepted.
180    #[must_use]
181    pub const fn is_accepted(self) -> bool {
182        matches!(self, Self::Submitted | Self::PreSubmitted)
183    }
184
185    /// Returns whether this status is terminal for pending spread fill handling.
186    #[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/// Interactive Brokers order type values.
231#[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 order.
248    Market,
249    /// Market-on-close order.
250    MarketOnClose,
251    /// Limit order.
252    Limit,
253    /// Limit-on-close order.
254    LimitOnClose,
255    /// Stop market order.
256    Stop,
257    /// Stop limit order.
258    StopLimit,
259    /// Trailing stop market order.
260    TrailingStop,
261    /// Trailing stop limit order.
262    TrailingStopLimit,
263    /// Market-if-touched order.
264    MarketIfTouched,
265    /// Limit-if-touched order.
266    LimitIfTouched,
267    /// Market-to-limit order.
268    MarketToLimit,
269    /// Market order with price protection.
270    MarketWithProtection,
271    /// Stop order with price protection.
272    StopWithProtection,
273    /// Midprice order targeting the NBBO midpoint.
274    Midprice,
275    /// Pegged-to-market order.
276    PeggedToMarket,
277    /// Pegged-to-stock order.
278    PeggedToStock,
279    /// Pegged-to-midpoint order.
280    PeggedToMidpoint,
281    /// Pegged-to-benchmark order.
282    PeggedToBenchmark,
283    /// Peg-to-best order.
284    PegBest,
285    /// Relative order.
286    Relative,
287    /// Passive relative order.
288    PassiveRelative,
289    /// Volatility order.
290    Volatility,
291    /// Box-top order.
292    BoxTop,
293    /// Relative plus limit combo order.
294    RelativeLimitCombo,
295    /// Relative plus market combo order.
296    RelativeMarketCombo,
297}
298
299impl IbOrderType {
300    /// Returns the IB wire string.
301    #[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    /// Converts the IB order type to a Nautilus order type.
333    #[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    /// Converts a Nautilus order type and time-in-force pair to the IB order type.
363    #[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/// Interactive Brokers time-in-force values.
436#[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 order.
453    Day,
454    /// Good-till-cancelled order.
455    GoodTilCanceled,
456    /// Immediate-or-cancel order.
457    ImmediateOrCancel,
458    /// Good-till-date order.
459    GoodTilDate,
460    /// Opening auction order.
461    OnOpen,
462    /// Fill-or-kill order.
463    FillOrKill,
464    /// Day-till-cancelled order.
465    DayTilCanceled,
466    /// Auction order.
467    Auction,
468}
469
470impl IbTimeInForce {
471    /// Converts this IB time-in-force to a Nautilus time-in-force.
472    #[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    /// Converts a Nautilus time-in-force to the corresponding IB time-in-force.
485    #[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    /// Converts this adapter enum to the rust-ibapi enum.
498    #[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            // Decoded as DAY before ibapi 4.2 modeled them
525            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/// Interactive Brokers builder time-in-force values.
571#[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/// Interactive Brokers combo-leg open/close values.
641#[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    /// Returns the IB integer code.
665    #[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    /// Converts to the rust-ibapi combo-leg open/close enum.
676    #[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/// Interactive Brokers conditional order condition types.
699#[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 condition.
716    Price,
717    /// Time condition.
718    Time,
719    /// Margin condition.
720    Margin,
721    /// Execution condition.
722    Execution,
723    /// Volume condition.
724    Volume,
725    /// Percent-change condition.
726    PercentChange,
727}
728
729impl IbConditionKind {
730    /// Returns the JSON/tag string accepted by the adapter.
731    #[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/// Interactive Brokers conditional order conjunction values.
767#[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    /// Logical AND.
784    And,
785    /// Logical OR.
786    Or,
787}
788
789impl IbConditionConjunction {
790    /// Returns the JSON/tag string accepted by the adapter.
791    #[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    /// Returns whether this conjunction is the rust-ibapi `is_conjunction` flag.
800    #[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/// Interactive Brokers price-condition trigger methods.
825#[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 method.
842    Default,
843    /// Two consecutive bid or ask prices.
844    DoubleBidAsk,
845    /// Last traded price.
846    Last,
847    /// Two consecutive last prices.
848    DoubleLast,
849    /// Current bid or ask price.
850    BidAsk,
851    /// Last price or bid/ask if no last price is available.
852    LastOrBidAsk,
853    /// Midpoint between bid and ask.
854    Midpoint,
855}
856
857impl IbTriggerMethod {
858    /// Returns the IB integer code.
859    #[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    /// Converts to the rust-ibapi trigger method.
873    #[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/// Interactive Brokers one-cancels-all behavior.
935#[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    /// Not part of an OCA group.
952    None,
953    /// Cancel remaining orders with block.
954    CancelWithBlock,
955    /// Proportionally reduce remaining orders with block.
956    ReduceWithBlock,
957    /// Proportionally reduce remaining orders without block.
958    ReduceWithoutBlock,
959}
960
961impl IbOcaType {
962    /// Returns the IB integer code.
963    #[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    /// Converts to the rust-ibapi OCA type.
974    #[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/// Interactive Brokers execution liquidity values.
1027#[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    /// An unrecognized value received from IB.
1048    Unknown,
1049}
1050
1051impl IbLiquidity {
1052    /// Returns the IB integer code.
1053    #[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    /// Converts to the rust-ibapi execution liquidity enum.
1065    #[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}