Skip to main content

nautilus_interactive_brokers/common/
enums.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//! Enumerations for the Interactive Brokers adapter.
17
18mod contracts;
19mod market_data;
20mod misc;
21mod order;
22
23pub use contracts::*;
24pub use market_data::*;
25pub use misc::*;
26pub use order::*;
27
28#[cfg(test)]
29mod tests {
30    use std::str::FromStr;
31
32    use nautilus_model::enums::{
33        OptionKind, OrderSide, OrderStatus as NautilusOrderStatus, OrderType as NautilusOrderType,
34        TimeInForce as NautilusTimeInForce,
35    };
36    use rstest::rstest;
37
38    use super::*;
39
40    #[rstest]
41    #[case("BUY", IbAction::Buy, OrderSide::Buy, 1)]
42    #[case("BOT", IbAction::Bought, OrderSide::Buy, 1)]
43    #[case("SELL", IbAction::Sell, OrderSide::Sell, -1)]
44    #[case("SLD", IbAction::Sold, OrderSide::Sell, -1)]
45    #[case("SSHORT", IbAction::SellShort, OrderSide::Sell, -1)]
46    #[case("SLONG", IbAction::SellLong, OrderSide::Sell, -1)]
47    fn test_ib_action_parse(
48        #[case] value: &str,
49        #[case] expected_action: IbAction,
50        #[case] expected_side: OrderSide,
51        #[case] expected_multiplier: i32,
52    ) {
53        let action = IbAction::from_str(value).unwrap();
54        assert_eq!(action, expected_action);
55        assert_eq!(action.order_side(), expected_side);
56        assert_eq!(action.signed_multiplier(), expected_multiplier);
57        assert_eq!(action.to_string(), value);
58        assert_eq!(
59            IbAction::from(action.ibapi_action()).order_side(),
60            expected_side
61        );
62    }
63
64    #[rstest]
65    #[case(
66        "ApiPending",
67        IbOrderStatus::ApiPending,
68        NautilusOrderStatus::Submitted
69    )]
70    #[case(
71        "PendingSubmit",
72        IbOrderStatus::PendingSubmit,
73        NautilusOrderStatus::Submitted
74    )]
75    #[case(
76        "PreSubmitted",
77        IbOrderStatus::PreSubmitted,
78        NautilusOrderStatus::Submitted
79    )]
80    #[case("Submitted", IbOrderStatus::Submitted, NautilusOrderStatus::Accepted)]
81    #[case(
82        "PendingCancel",
83        IbOrderStatus::PendingCancel,
84        NautilusOrderStatus::PendingCancel
85    )]
86    #[case(
87        "ApiCancelled",
88        IbOrderStatus::ApiCancelled,
89        NautilusOrderStatus::Canceled
90    )]
91    #[case("Cancelled", IbOrderStatus::Cancelled, NautilusOrderStatus::Canceled)]
92    #[case("Filled", IbOrderStatus::Filled, NautilusOrderStatus::Filled)]
93    #[case("Inactive", IbOrderStatus::Inactive, NautilusOrderStatus::Rejected)]
94    fn test_ib_order_status_parse(
95        #[case] value: &str,
96        #[case] expected_status: IbOrderStatus,
97        #[case] expected_nautilus_status: NautilusOrderStatus,
98    ) {
99        let status = IbOrderStatus::from_str(value).unwrap();
100        assert_eq!(status, expected_status);
101        assert_eq!(status.nautilus_status(), expected_nautilus_status);
102        assert_eq!(status.to_string(), value);
103    }
104
105    #[rstest]
106    #[case("MKT", IbOrderType::Market, NautilusOrderType::Market)]
107    #[case("MOC", IbOrderType::MarketOnClose, NautilusOrderType::Market)]
108    #[case("LMT", IbOrderType::Limit, NautilusOrderType::Limit)]
109    #[case("LOC", IbOrderType::LimitOnClose, NautilusOrderType::Limit)]
110    #[case("STP", IbOrderType::Stop, NautilusOrderType::StopMarket)]
111    #[case("STP LMT", IbOrderType::StopLimit, NautilusOrderType::StopLimit)]
112    #[case(
113        "TRAIL",
114        IbOrderType::TrailingStop,
115        NautilusOrderType::TrailingStopMarket
116    )]
117    #[case(
118        "TRAIL LIMIT",
119        IbOrderType::TrailingStopLimit,
120        NautilusOrderType::TrailingStopLimit
121    )]
122    #[case(
123        "MIT",
124        IbOrderType::MarketIfTouched,
125        NautilusOrderType::MarketIfTouched
126    )]
127    #[case("LIT", IbOrderType::LimitIfTouched, NautilusOrderType::LimitIfTouched)]
128    #[case("MTL", IbOrderType::MarketToLimit, NautilusOrderType::MarketToLimit)]
129    fn test_ib_order_type_parse(
130        #[case] value: &str,
131        #[case] expected_order_type: IbOrderType,
132        #[case] expected_nautilus_order_type: NautilusOrderType,
133    ) {
134        let order_type = IbOrderType::from_str(value).unwrap();
135        assert_eq!(order_type, expected_order_type);
136        assert_eq!(
137            order_type.nautilus_order_type(),
138            expected_nautilus_order_type
139        );
140        assert_eq!(order_type.to_string(), value);
141    }
142
143    #[rstest]
144    #[case("PEGMID", IbOrderType::PeggedToMidpoint, "PEG MID")]
145    #[case("PEGBENCH", IbOrderType::PeggedToBenchmark, "PEG BENCH")]
146    #[case("PEGBEST", IbOrderType::PegBest, "PEG BEST")]
147    fn test_ib_order_type_parse_accepts_ibapi_python_aliases(
148        #[case] value: &str,
149        #[case] expected_order_type: IbOrderType,
150        #[case] expected_display: &str,
151    ) {
152        let order_type = IbOrderType::from_str(value).unwrap();
153        assert_eq!(order_type, expected_order_type);
154        assert_eq!(order_type.to_string(), expected_display);
155    }
156
157    #[rstest]
158    fn test_ib_order_type_wire_roundtrip_preserves_nautilus_order_type() {
159        let order_types = [
160            IbOrderType::Market,
161            IbOrderType::MarketOnClose,
162            IbOrderType::Limit,
163            IbOrderType::LimitOnClose,
164            IbOrderType::Stop,
165            IbOrderType::StopLimit,
166            IbOrderType::TrailingStop,
167            IbOrderType::TrailingStopLimit,
168            IbOrderType::MarketIfTouched,
169            IbOrderType::LimitIfTouched,
170            IbOrderType::MarketToLimit,
171            IbOrderType::MarketWithProtection,
172            IbOrderType::StopWithProtection,
173            IbOrderType::Midprice,
174            IbOrderType::PeggedToMarket,
175            IbOrderType::PeggedToStock,
176            IbOrderType::PeggedToMidpoint,
177            IbOrderType::PeggedToBenchmark,
178            IbOrderType::PegBest,
179            IbOrderType::Relative,
180            IbOrderType::PassiveRelative,
181            IbOrderType::Volatility,
182            IbOrderType::BoxTop,
183            IbOrderType::RelativeLimitCombo,
184            IbOrderType::RelativeMarketCombo,
185        ];
186
187        for order_type in order_types {
188            let parsed = IbOrderType::from_str(order_type.as_str()).unwrap();
189
190            assert_eq!(
191                parsed.nautilus_order_type(),
192                order_type.nautilus_order_type(),
193                "{} round-tripped through {:?}",
194                order_type.as_str(),
195                parsed,
196            );
197        }
198    }
199
200    #[rstest]
201    #[case("DAY", IbTimeInForce::Day, NautilusTimeInForce::Day)]
202    #[case("GTC", IbTimeInForce::GoodTilCanceled, NautilusTimeInForce::Gtc)]
203    #[case("IOC", IbTimeInForce::ImmediateOrCancel, NautilusTimeInForce::Ioc)]
204    #[case("GTD", IbTimeInForce::GoodTilDate, NautilusTimeInForce::Gtd)]
205    #[case("OPG", IbTimeInForce::OnOpen, NautilusTimeInForce::AtTheOpen)]
206    #[case("FOK", IbTimeInForce::FillOrKill, NautilusTimeInForce::Fok)]
207    #[case("DTC", IbTimeInForce::DayTilCanceled, NautilusTimeInForce::Day)]
208    #[case("AUC", IbTimeInForce::Auction, NautilusTimeInForce::Day)]
209    fn test_ib_time_in_force_parse(
210        #[case] value: &str,
211        #[case] expected_time_in_force: IbTimeInForce,
212        #[case] expected_nautilus_time_in_force: NautilusTimeInForce,
213    ) {
214        let time_in_force = IbTimeInForce::from_str(value).unwrap();
215        assert_eq!(time_in_force, expected_time_in_force);
216        assert_eq!(
217            time_in_force.nautilus_time_in_force(),
218            expected_nautilus_time_in_force
219        );
220        assert_eq!(time_in_force.to_string(), value);
221        assert_eq!(
222            IbTimeInForce::from(time_in_force.ibapi_time_in_force()),
223            expected_time_in_force
224        );
225    }
226
227    #[rstest]
228    #[case("STK", IbSecurityType::Stock)]
229    #[case("OPT", IbSecurityType::Option)]
230    #[case("FUT", IbSecurityType::Future)]
231    #[case("CONTFUT", IbSecurityType::ContinuousFuture)]
232    #[case("IND", IbSecurityType::Index)]
233    #[case("FOP", IbSecurityType::FuturesOption)]
234    #[case("CASH", IbSecurityType::ForexPair)]
235    #[case("BAG", IbSecurityType::Spread)]
236    #[case("WAR", IbSecurityType::Warrant)]
237    #[case("BOND", IbSecurityType::Bond)]
238    #[case("CMDTY", IbSecurityType::Commodity)]
239    #[case("NEWS", IbSecurityType::News)]
240    #[case("FUND", IbSecurityType::MutualFund)]
241    #[case("CRYPTO", IbSecurityType::Crypto)]
242    #[case("CFD", IbSecurityType::Cfd)]
243    fn test_ib_security_type_parse(
244        #[case] value: &str,
245        #[case] expected_security_type: IbSecurityType,
246    ) {
247        let security_type = IbSecurityType::from_str(value).unwrap();
248        assert_eq!(security_type, expected_security_type);
249        assert_eq!(security_type.to_string(), value);
250        assert_eq!(
251            IbSecurityType::try_from(&security_type.ibapi_security_type()).unwrap(),
252            expected_security_type
253        );
254    }
255
256    #[rstest]
257    #[case("C", IbOptionRight::Call, OptionKind::Call)]
258    #[case("P", IbOptionRight::Put, OptionKind::Put)]
259    #[case("CALL", IbOptionRight::Call, OptionKind::Call)]
260    #[case("PUT", IbOptionRight::Put, OptionKind::Put)]
261    fn test_ib_option_right_parse(
262        #[case] value: &str,
263        #[case] expected_right: IbOptionRight,
264        #[case] expected_option_kind: OptionKind,
265    ) {
266        let right = IbOptionRight::from_str(value).unwrap();
267        assert_eq!(right, expected_right);
268        assert_eq!(right.option_kind(), expected_option_kind);
269    }
270
271    #[rstest]
272    #[case("TRADES", IbHistoricalTickType::Trades)]
273    #[case("BID_ASK", IbHistoricalTickType::BidAsk)]
274    fn test_ib_historical_tick_type_parse(
275        #[case] value: &str,
276        #[case] expected_tick_type: IbHistoricalTickType,
277    ) {
278        let tick_type = IbHistoricalTickType::from_str(value).unwrap();
279        assert_eq!(tick_type, expected_tick_type);
280        assert_eq!(tick_type.to_string(), value);
281    }
282
283    #[rstest]
284    #[case(true, IbTradingHours::Regular)]
285    #[case(false, IbTradingHours::Extended)]
286    fn test_ib_trading_hours_parse(
287        #[case] use_rth: bool,
288        #[case] expected_trading_hours: IbTradingHours,
289    ) {
290        let trading_hours = IbTradingHours::from(use_rth);
291        assert_eq!(trading_hours, expected_trading_hours);
292        assert_eq!(trading_hours.use_rth(), use_rth);
293        assert_eq!(
294            trading_hours.ibapi_trading_hours().use_rth(),
295            expected_trading_hours.use_rth()
296        );
297    }
298
299    #[rstest]
300    #[case(IbHistoricalBarSize::Sec, "1 secs")]
301    #[case(IbHistoricalBarSize::Min5, "5 mins")]
302    #[case(IbHistoricalBarSize::Hour2, "2 hours")]
303    #[case(IbHistoricalBarSize::Day, "1 day")]
304    fn test_ib_historical_bar_size_display(
305        #[case] bar_size: IbHistoricalBarSize,
306        #[case] expected_display: &str,
307    ) {
308        assert_eq!(bar_size.to_string(), expected_display);
309    }
310
311    #[rstest]
312    #[case(IbHistoricalWhatToShow::Trades, "TRADES")]
313    #[case(IbHistoricalWhatToShow::Midpoint, "MIDPOINT")]
314    #[case(IbHistoricalWhatToShow::BidAsk, "BID_ASK")]
315    #[case(IbHistoricalWhatToShow::AdjustedLast, "ADJUSTED_LAST")]
316    fn test_ib_historical_what_to_show_display(
317        #[case] what_to_show: IbHistoricalWhatToShow,
318        #[case] expected_display: &str,
319    ) {
320        assert_eq!(what_to_show.as_str(), expected_display);
321        assert_eq!(what_to_show.to_string(), expected_display);
322    }
323
324    #[rstest]
325    #[case(IbRealtimeBarSize::Sec5, "5 secs")]
326    fn test_ib_realtime_bar_size_display(
327        #[case] bar_size: IbRealtimeBarSize,
328        #[case] expected_display: &str,
329    ) {
330        assert_eq!(bar_size.to_string(), expected_display);
331    }
332
333    #[rstest]
334    #[case(IbRealtimeWhatToShow::Trades, "TRADES")]
335    #[case(IbRealtimeWhatToShow::Midpoint, "MIDPOINT")]
336    #[case(IbRealtimeWhatToShow::Bid, "BID")]
337    #[case(IbRealtimeWhatToShow::Ask, "ASK")]
338    fn test_ib_realtime_what_to_show_display(
339        #[case] what_to_show: IbRealtimeWhatToShow,
340        #[case] expected_display: &str,
341    ) {
342        assert_eq!(what_to_show.as_str(), expected_display);
343        assert_eq!(what_to_show.to_string(), expected_display);
344    }
345
346    #[rstest]
347    #[case("price", IbConditionKind::Price)]
348    #[case("time", IbConditionKind::Time)]
349    #[case("margin", IbConditionKind::Margin)]
350    #[case("execution", IbConditionKind::Execution)]
351    #[case("volume", IbConditionKind::Volume)]
352    #[case("percent_change", IbConditionKind::PercentChange)]
353    fn test_ib_condition_kind_parse(#[case] value: &str, #[case] expected_kind: IbConditionKind) {
354        let kind = IbConditionKind::from_str(value).unwrap();
355        assert_eq!(kind, expected_kind);
356        assert_eq!(kind.to_string(), value);
357    }
358
359    #[rstest]
360    #[case("and", IbConditionConjunction::And, true)]
361    #[case("or", IbConditionConjunction::Or, false)]
362    #[case("a", IbConditionConjunction::And, true)]
363    #[case("o", IbConditionConjunction::Or, false)]
364    fn test_ib_condition_conjunction_parse(
365        #[case] value: &str,
366        #[case] expected_conjunction: IbConditionConjunction,
367        #[case] expected_is_conjunction: bool,
368    ) {
369        let conjunction = IbConditionConjunction::from_str(value).unwrap();
370        assert_eq!(conjunction, expected_conjunction);
371        assert_eq!(conjunction.is_conjunction(), expected_is_conjunction);
372    }
373
374    #[rstest]
375    #[case(0, IbTriggerMethod::Default)]
376    #[case(1, IbTriggerMethod::DoubleBidAsk)]
377    #[case(2, IbTriggerMethod::Last)]
378    #[case(3, IbTriggerMethod::DoubleLast)]
379    #[case(4, IbTriggerMethod::BidAsk)]
380    #[case(7, IbTriggerMethod::LastOrBidAsk)]
381    #[case(8, IbTriggerMethod::Midpoint)]
382    fn test_ib_trigger_method_parse(
383        #[case] value: i32,
384        #[case] expected_trigger_method: IbTriggerMethod,
385    ) {
386        let trigger_method = IbTriggerMethod::from(value);
387        assert_eq!(trigger_method, expected_trigger_method);
388        assert_eq!(trigger_method.as_i32(), value);
389        assert_eq!(
390            IbTriggerMethod::from(trigger_method.ibapi_trigger_method()),
391            expected_trigger_method
392        );
393    }
394
395    #[rstest]
396    #[case(0, IbOcaType::None)]
397    #[case(1, IbOcaType::CancelWithBlock)]
398    #[case(2, IbOcaType::ReduceWithBlock)]
399    #[case(3, IbOcaType::ReduceWithoutBlock)]
400    fn test_ib_oca_type_parse(#[case] value: i32, #[case] expected_oca_type: IbOcaType) {
401        let oca_type = IbOcaType::from(value);
402        assert_eq!(oca_type, expected_oca_type);
403        assert_eq!(oca_type.as_i32(), value);
404        assert_eq!(
405            IbOcaType::from(oca_type.ibapi_oca_type()),
406            expected_oca_type
407        );
408    }
409
410    #[rstest]
411    #[case(0, IbComboLegOpenClose::Same)]
412    #[case(1, IbComboLegOpenClose::Open)]
413    #[case(2, IbComboLegOpenClose::Close)]
414    #[case(3, IbComboLegOpenClose::Unknown)]
415    fn test_ib_combo_leg_open_close_parse(
416        #[case] value: i32,
417        #[case] expected_open_close: IbComboLegOpenClose,
418    ) {
419        assert_eq!(expected_open_close.as_i32(), value);
420        assert_eq!(
421            IbComboLegOpenClose::from(expected_open_close.ibapi_combo_leg_open_close()),
422            expected_open_close
423        );
424    }
425
426    #[rstest]
427    #[case(0, IbLiquidity::None)]
428    #[case(1, IbLiquidity::AddedLiquidity)]
429    #[case(2, IbLiquidity::RemovedLiquidity)]
430    #[case(3, IbLiquidity::LiquidityRoutedOut)]
431    fn test_ib_liquidity_parse(#[case] value: i32, #[case] expected_liquidity: IbLiquidity) {
432        let liquidity = IbLiquidity::from(value);
433        assert_eq!(liquidity, expected_liquidity);
434        assert_eq!(liquidity.as_i32(), value);
435        assert_eq!(
436            IbLiquidity::from(liquidity.ibapi_liquidity()),
437            expected_liquidity
438        );
439    }
440}