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