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