1mod 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}