1use std::str::FromStr;
19
20use ibapi::contracts::SecurityType;
21use nautilus_core::{UnixNanos, time::get_atomic_clock_realtime};
22use nautilus_model::{
23 enums::{AssetClass, OptionKind},
24 identifiers::{InstrumentId, Symbol},
25 instruments::{
26 Cfd, Commodity, CryptoPerpetual, CurrencyPair, Equity, FuturesContract, FuturesSpread,
27 IndexInstrument, InstrumentAny, OptionContract, OptionSpread,
28 },
29 types::{Currency, Price, Quantity},
30};
31use rust_decimal::Decimal;
32use ustr::Ustr;
33
34use crate::common::contract_to_params;
35
36#[must_use]
46pub fn tick_size_to_precision(tick_size: f64) -> u8 {
47 if tick_size <= 0.0 {
48 return 8; }
50
51 let s = format!("{:.10}", tick_size);
53 let s = s.trim_end_matches('0');
54 let parts: Vec<&str> = s.split('.').collect();
55
56 if parts.len() == 2 {
57 parts[1].len().min(8) as u8
58 } else {
59 0
60 }
61}
62
63pub fn expiry_timestring_to_unix_nanos(
75 expiry: &str,
76 details: Option<&ibapi::contracts::ContractDetails>,
77) -> anyhow::Result<UnixNanos> {
78 if expiry.is_empty() {
79 anyhow::bail!("Empty expiry string");
80 }
81
82 let dt = if expiry.len() == 8 {
85 let year = &expiry[0..4];
87 let month = &expiry[4..6];
88 let day = &expiry[6..8];
89 let date = time::Date::from_calendar_date(
90 year.parse()?,
91 time::Month::try_from(month.parse::<u8>()?)?,
92 day.parse()?,
93 )?;
94
95 let mut expiry_time = time::Time::MIDNIGHT;
98
99 if let Some(details) = details {
100 if !details.trading_hours.is_empty()
101 && !details.trading_hours.contains(&"CLOSED".to_string())
102 {
103 let expiry_str: &str = expiry;
105 for session in &details.trading_hours {
106 if session.as_str().starts_with(expiry_str) && session.as_str().contains('-') {
107 let parts: Vec<&str> = session.as_str().split('-').collect();
108 if let Some(end_part) = parts.get(1) {
109 let inner_parts: Vec<&str> = end_part.split(':').collect();
110 if let Some(time_part) = inner_parts.get(1) {
111 if time_part.len() >= 4 {
112 let hour = time_part
113 .get(0..2)
114 .and_then(|s: &str| s.parse::<u8>().ok())
115 .unwrap_or(0);
116 let minute = time_part
117 .get(2..4)
118 .and_then(|s: &str| s.parse::<u8>().ok())
119 .unwrap_or(0);
120 expiry_time = time::Time::from_hms(hour, minute, 0)
121 .unwrap_or(time::Time::MIDNIGHT);
122 }
123 }
124 }
125 break;
126 }
127 }
128 }
129 }
130 time::PrimitiveDateTime::new(date, expiry_time)
131 } else {
132 let parts: Vec<&str> = expiry.split(' ').collect();
134 if parts.len() >= 3 {
135 let date_part = parts[0];
136 let time_part = parts[1];
137 let year = &date_part[0..4];
138 let month = &date_part[4..6];
139 let day = &date_part[6..8];
140
141 let time_parts: Vec<&str> = time_part.split(':').collect();
142 let hour = time_parts.first().unwrap_or(&"0").parse::<u8>()?;
143 let minute = time_parts.get(1).unwrap_or(&"0").parse::<u8>()?;
144 let second = time_parts.get(2).unwrap_or(&"0").parse::<u8>()?;
145
146 let date = time::Date::from_calendar_date(
147 year.parse()?,
148 time::Month::try_from(month.parse::<u8>()?)?,
149 day.parse()?,
150 )?;
151 let time_obj = time::Time::from_hms(hour, minute, second)?;
152 time::PrimitiveDateTime::new(date, time_obj)
153 } else {
154 anyhow::bail!("Invalid expiry format: {}", expiry);
155 }
156 };
157
158 let offset_dt = dt.assume_utc();
161 let nanos = offset_dt.unix_timestamp_nanos();
162 Ok(UnixNanos::new(nanos as u64))
163}
164
165pub fn parse_ib_contract_to_instrument(
176 details: &ibapi::contracts::ContractDetails,
177 instrument_id: InstrumentId,
178) -> anyhow::Result<InstrumentAny> {
179 let sec_type = &details.contract.security_type;
180
181 match sec_type {
182 SecurityType::Stock => Ok(parse_equity_contract(details, instrument_id)),
183 SecurityType::ForexPair => Ok(parse_forex_contract(details, instrument_id)),
184 SecurityType::Crypto => Ok(parse_crypto_contract(details, instrument_id)),
185 SecurityType::Future => Ok(parse_futures_contract(details, instrument_id)),
186 SecurityType::Option => parse_option_contract(details, instrument_id),
187 SecurityType::FuturesOption => parse_option_contract(details, instrument_id), SecurityType::Index => Ok(parse_index_contract(details, instrument_id)),
189 SecurityType::CFD => Ok(parse_cfd_contract(details, instrument_id)),
190 SecurityType::Commodity => Ok(parse_commodity_contract(details, instrument_id)),
191 SecurityType::Bond => Ok(parse_bond_contract(details, instrument_id)),
192 _ => anyhow::bail!("Unsupported security type: {:?}", sec_type),
193 }
194}
195
196fn ib_contract_info(details: &ibapi::contracts::ContractDetails) -> nautilus_core::Params {
197 let mut info = nautilus_core::Params::new();
198 let mut contract = serde_json::Map::new();
199
200 let contract_params = contract_to_params(&details.contract);
201 for (key, value) in &contract_params {
202 contract.insert(key.clone(), value.clone());
203 }
204
205 info.insert("contract".to_string(), serde_json::Value::Object(contract));
206 info
207}
208
209fn ib_contract_info_for_contract(contract: &ibapi::contracts::Contract) -> nautilus_core::Params {
210 let mut info = nautilus_core::Params::new();
211 let mut contract_map = serde_json::Map::new();
212 let contract_params = contract_to_params(contract);
213
214 for (key, value) in &contract_params {
215 contract_map.insert(key.clone(), value.clone());
216 }
217
218 info.insert(
219 "contract".to_string(),
220 serde_json::Value::Object(contract_map),
221 );
222 info
223}
224
225fn sec_type_to_asset_class(sec_type: &str) -> AssetClass {
226 match sec_type {
227 "STK" => AssetClass::Equity,
228 "IND" => AssetClass::Index,
229 "CASH" => AssetClass::FX,
230 "BOND" => AssetClass::Debt,
231 "CMDTY" => AssetClass::Commodity,
232 "FUT" => AssetClass::Index,
233 _ => AssetClass::Equity,
234 }
235}
236
237fn parse_equity_contract(
239 details: &ibapi::contracts::ContractDetails,
240 instrument_id: InstrumentId,
241) -> InstrumentAny {
242 let price_precision = tick_size_to_precision(details.min_tick);
243 let timestamp = get_atomic_clock_realtime().get_time_ns();
244
245 let instrument = Equity::new(
246 instrument_id,
247 Symbol::from(details.contract.local_symbol.as_str()),
248 None, Currency::from(details.contract.currency.to_string()),
250 price_precision,
251 Price::new(details.min_tick, price_precision),
252 Some(Quantity::new(100.0, 0)), None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
263 timestamp,
264 );
265
266 InstrumentAny::from(instrument)
267}
268
269fn parse_forex_contract(
271 details: &ibapi::contracts::ContractDetails,
272 instrument_id: InstrumentId,
273) -> InstrumentAny {
274 let price_precision = tick_size_to_precision(details.min_tick);
275 let size_precision = tick_size_to_precision(details.min_size);
276 let timestamp = get_atomic_clock_realtime().get_time_ns();
277
278 let instrument = CurrencyPair::new(
279 instrument_id,
280 Symbol::from(details.contract.local_symbol.as_str()),
281 Currency::from(details.contract.symbol.to_string()),
282 Currency::from(details.contract.currency.to_string()),
283 price_precision,
284 size_precision,
285 Price::new(details.min_tick, price_precision),
286 Quantity::new(details.size_increment, size_precision),
287 None, None, None, None, None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
301 timestamp,
302 );
303
304 InstrumentAny::from(instrument)
305}
306
307fn parse_crypto_contract(
309 details: &ibapi::contracts::ContractDetails,
310 instrument_id: InstrumentId,
311) -> InstrumentAny {
312 let price_precision = tick_size_to_precision(details.min_tick);
313 let size_precision = tick_size_to_precision(details.min_size);
314 let timestamp = get_atomic_clock_realtime().get_time_ns();
315
316 let instrument = CryptoPerpetual::new(
317 instrument_id,
318 Symbol::from(details.contract.local_symbol.as_str()),
319 Currency::from(details.contract.symbol.to_string()),
320 Currency::from(details.contract.currency.to_string()),
321 Currency::from(details.contract.currency.to_string()),
322 true, price_precision,
324 size_precision,
325 Price::new(details.min_tick, price_precision),
326 Quantity::new(details.size_increment, size_precision),
327 None, None, None, Some(Quantity::new(details.min_size, size_precision)),
331 None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
341 timestamp,
342 );
343
344 InstrumentAny::from(instrument)
345}
346
347fn parse_futures_contract(
349 details: &ibapi::contracts::ContractDetails,
350 instrument_id: InstrumentId,
351) -> InstrumentAny {
352 let price_precision = tick_size_to_precision(details.min_tick);
353 let timestamp = get_atomic_clock_realtime().get_time_ns();
354
355 let expiration_ns = if !details
357 .contract
358 .last_trade_date_or_contract_month
359 .is_empty()
360 {
361 expiry_timestring_to_unix_nanos(
362 &details.contract.last_trade_date_or_contract_month,
363 Some(details),
364 )
365 .unwrap_or_else(|_| UnixNanos::from(timestamp.as_u64() + 90 * 24 * 60 * 60 * 1_000_000_000))
366 } else {
368 UnixNanos::from(timestamp.as_u64() + 90 * 24 * 60 * 60 * 1_000_000_000) };
370
371 let ninety_days_ns: u64 = 90 * 24 * 60 * 60 * 1_000_000_000;
372 let activation_ns = expiration_ns
373 .checked_sub(ninety_days_ns)
374 .unwrap_or(UnixNanos::from(0)); let multiplier = details.contract.multiplier.parse::<f64>().unwrap_or(1.0);
377
378 let instrument = FuturesContract::new(
379 instrument_id,
380 Symbol::from(details.contract.local_symbol.as_str()),
381 sec_type_to_asset_class(details.under_security_type.as_str()),
382 None, Ustr::from(details.under_symbol.as_str()),
384 activation_ns,
385 expiration_ns,
386 Currency::from(details.contract.currency.to_string()),
387 price_precision,
388 Price::new(details.min_tick, price_precision),
389 Quantity::new(multiplier, 0),
390 Quantity::new(1.0, 0),
391 None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
401 timestamp,
402 );
403
404 InstrumentAny::from(instrument)
405}
406
407fn parse_option_contract(
409 details: &ibapi::contracts::ContractDetails,
410 instrument_id: InstrumentId,
411) -> anyhow::Result<InstrumentAny> {
412 let price_precision = tick_size_to_precision(details.min_tick);
413 let timestamp = get_atomic_clock_realtime().get_time_ns();
414
415 let expiration_ns = if !details
417 .contract
418 .last_trade_date_or_contract_month
419 .is_empty()
420 {
421 expiry_timestring_to_unix_nanos(
422 &details.contract.last_trade_date_or_contract_month,
423 Some(details),
424 )
425 .unwrap_or_else(|_| UnixNanos::from(timestamp.as_u64() + 90 * 24 * 60 * 60 * 1_000_000_000))
426 } else {
428 UnixNanos::from(timestamp.as_u64() + 90 * 24 * 60 * 60 * 1_000_000_000) };
430
431 let ninety_days_ns: u64 = 90 * 24 * 60 * 60 * 1_000_000_000;
432 let activation_ns = expiration_ns
433 .checked_sub(ninety_days_ns)
434 .unwrap_or(UnixNanos::from(0)); let option_kind = match details.contract.right.as_str() {
438 "C" => OptionKind::Call,
439 "P" => OptionKind::Put,
440 _ => anyhow::bail!("Unknown option kind: {}", details.contract.right),
441 };
442
443 let multiplier = details.contract.multiplier.parse::<f64>().unwrap_or(100.0);
444 let asset_class = match details.under_security_type.as_str() {
445 "IND" => AssetClass::Index,
446 _ => AssetClass::Equity,
447 };
448 let underlying =
449 if details.under_security_type == "IND" && !details.under_symbol.starts_with('^') {
450 format!("^{}", details.under_symbol)
451 } else {
452 details.under_symbol.clone()
453 };
454
455 let instrument = OptionContract::new(
456 instrument_id,
457 Symbol::from(details.contract.local_symbol.as_str()),
458 asset_class,
459 None, Ustr::from(underlying.as_str()),
461 option_kind,
462 Price::new(details.contract.strike, price_precision),
463 Currency::from(details.contract.currency.to_string()),
464 activation_ns,
465 expiration_ns,
466 price_precision,
467 Price::new(details.min_tick, price_precision),
468 Quantity::new(multiplier, 0),
469 Quantity::new(multiplier, 0),
470 None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
480 timestamp,
481 );
482
483 Ok(InstrumentAny::from(instrument))
484}
485
486#[allow(clippy::items_after_test_module)]
487#[cfg(test)]
488mod tests {
489 use ibapi::contracts::{Contract, ContractDetails, Currency, Exchange, SecurityType, Symbol};
490 use nautilus_model::{
491 enums::AssetClass,
492 identifiers::{InstrumentId, Symbol as NautilusSymbol, Venue},
493 instruments::{Instrument, InstrumentAny},
494 };
495 use rstest::rstest;
496 use ustr::Ustr;
497
498 use super::parse_ib_contract_to_instrument;
499
500 #[rstest]
501 fn test_parse_option_contract_prefixes_index_underlying() {
502 let details = ContractDetails {
503 contract: Contract {
504 symbol: Symbol::from("SPXW"),
505 security_type: SecurityType::Option,
506 exchange: Exchange::from("SMART"),
507 currency: Currency::from("USD"),
508 local_symbol: "SPXW 260313P06630000".to_string(),
509 last_trade_date_or_contract_month: "20260313".to_string(),
510 right: "P".to_string(),
511 strike: 6630.0,
512 multiplier: "100".to_string(),
513 ..Default::default()
514 },
515 min_tick: 0.05,
516 under_symbol: "SPX".to_string(),
517 under_security_type: "IND".to_string(),
518 ..Default::default()
519 };
520 let instrument_id = InstrumentId::new(
521 NautilusSymbol::from("SPXW 260313P06630000"),
522 Venue::from("SMART"),
523 );
524
525 let instrument = parse_ib_contract_to_instrument(&details, instrument_id).unwrap();
526
527 let InstrumentAny::OptionContract(option) = instrument else {
528 panic!("expected option contract");
529 };
530
531 assert_eq!(option.asset_class(), AssetClass::Index);
532 assert_eq!(option.underlying(), Some(Ustr::from("^SPX")));
533 }
534}
535
536fn parse_index_contract(
541 details: &ibapi::contracts::ContractDetails,
542 instrument_id: InstrumentId,
543) -> InstrumentAny {
544 let price_precision = tick_size_to_precision(details.min_tick);
545 let size_precision = tick_size_to_precision(details.min_size);
546 let timestamp = get_atomic_clock_realtime().get_time_ns();
547
548 let instrument = IndexInstrument::new(
549 instrument_id,
550 Symbol::from(details.contract.local_symbol.as_str()),
551 Currency::from(details.contract.currency.to_string()),
552 price_precision,
553 size_precision,
554 Price::new(details.min_tick, price_precision),
555 Quantity::new(details.size_increment, size_precision),
556 Some(ib_contract_info(details)), timestamp,
558 timestamp,
559 );
560
561 InstrumentAny::from(instrument)
562}
563
564pub fn create_spread_instrument_id(
584 leg_tuples: &[(InstrumentId, i32)],
585) -> anyhow::Result<InstrumentId> {
586 if leg_tuples.len() < 2 {
587 anyhow::bail!("instrument_ratios list needs to have at least 2 legs");
588 }
589
590 let first_venue = leg_tuples[0].0.venue;
592
593 for (instrument_id, ratio) in leg_tuples {
594 if *ratio == 0 {
595 anyhow::bail!("ratio cannot be zero");
596 }
597
598 if instrument_id.venue != first_venue {
599 anyhow::bail!(
600 "All venues must match. Expected {}, was {}",
601 first_venue,
602 instrument_id.venue
603 );
604 }
605 }
606
607 let mut sorted_ratios = leg_tuples.to_vec();
609 sorted_ratios.sort_by(|a, b| a.0.symbol.as_str().cmp(b.0.symbol.as_str()));
610
611 let mut symbol_parts = Vec::new();
613
614 for (instrument_id, ratio) in &sorted_ratios {
615 let symbol_part = if *ratio > 0 {
616 format!("({}){}", ratio, instrument_id.symbol.as_str())
617 } else {
618 format!("(({})){}", ratio.abs(), instrument_id.symbol.as_str())
619 };
620 symbol_parts.push(symbol_part);
621 }
622
623 let composite_symbol = symbol_parts.join("_");
624 let symbol = Symbol::from(composite_symbol.as_str());
625
626 Ok(InstrumentId::new(symbol, first_venue))
627}
628
629pub fn parse_spread_instrument_id(
644 instrument_id: InstrumentId,
645 leg_contract_details: &[(&ibapi::contracts::ContractDetails, i32)],
646 timestamp_ns: Option<UnixNanos>,
647) -> anyhow::Result<OptionSpread> {
648 if leg_contract_details.is_empty() {
649 anyhow::bail!("leg_contract_details must be provided");
650 }
651
652 let (first_details, _) = leg_contract_details[0];
654 let first_contract = &first_details.contract;
655
656 let currency = Currency::from(first_contract.currency.to_string());
658 let underlying = if !first_details.under_symbol.is_empty() {
659 Ustr::from(first_details.under_symbol.as_str())
660 } else {
661 Ustr::from(first_contract.symbol.as_str())
662 };
663
664 let multiplier_str = first_contract.multiplier.to_string();
666 let multiplier =
667 Quantity::from_str(&multiplier_str).unwrap_or_else(|_| Quantity::new(100.0, 0)); let asset_class = match first_contract.security_type {
671 ibapi::contracts::SecurityType::FuturesOption => AssetClass::Index, _ => AssetClass::Equity, };
674
675 let price_precision = tick_size_to_precision(first_details.min_tick);
677 let price_increment = Price::new(first_details.min_tick, price_precision);
678
679 let timestamp = timestamp_ns.unwrap_or_else(|| get_atomic_clock_realtime().get_time_ns());
681
682 let lot_size = multiplier;
684
685 let spread = OptionSpread::new_checked(
687 instrument_id,
688 Symbol::from(instrument_id.symbol.as_str()), asset_class,
690 None, underlying,
692 Ustr::from("SPREAD"), UnixNanos::new(0), UnixNanos::new(0), currency,
696 price_precision,
697 price_increment,
698 multiplier,
699 lot_size,
700 None, None, None, None, Some(Decimal::ZERO), Some(Decimal::ZERO), Some(Decimal::ZERO), Some(Decimal::ZERO), None, timestamp,
710 timestamp,
711 )?;
712
713 Ok(spread)
714}
715
716pub fn parse_option_spread_instrument_id(
717 instrument_id: InstrumentId,
718 leg_contract_details: &[(&ibapi::contracts::ContractDetails, i32)],
719 bag_contract: Option<&ibapi::contracts::Contract>,
720 timestamp_ns: Option<UnixNanos>,
721) -> anyhow::Result<OptionSpread> {
722 let mut spread = parse_spread_instrument_id(instrument_id, leg_contract_details, timestamp_ns)?;
723 spread.info = bag_contract.map(ib_contract_info_for_contract);
724 Ok(spread)
725}
726
727pub fn parse_futures_spread_instrument_id(
728 instrument_id: InstrumentId,
729 leg_contract_details: &[(&ibapi::contracts::ContractDetails, i32)],
730 bag_contract: Option<&ibapi::contracts::Contract>,
731 timestamp_ns: Option<UnixNanos>,
732) -> anyhow::Result<FuturesSpread> {
733 if leg_contract_details.is_empty() {
734 anyhow::bail!("leg_contract_details must be provided");
735 }
736
737 let (first_details, _) = leg_contract_details[0];
738 let first_contract = &first_details.contract;
739 let currency = Currency::from(first_contract.currency.to_string());
740 let underlying = if !first_details.under_symbol.is_empty() {
741 Ustr::from(first_details.under_symbol.as_str())
742 } else {
743 Ustr::from(first_contract.symbol.as_str())
744 };
745 let multiplier = Quantity::from_str(&first_contract.multiplier.to_string())
746 .unwrap_or_else(|_| Quantity::new(1.0, 0));
747 let price_precision = tick_size_to_precision(first_details.min_tick);
748 let price_increment = Price::new(first_details.min_tick, price_precision);
749 let timestamp = timestamp_ns.unwrap_or_else(|| get_atomic_clock_realtime().get_time_ns());
750
751 Ok(FuturesSpread::new_checked(
752 instrument_id,
753 Symbol::from(instrument_id.symbol.as_str()),
754 AssetClass::Index,
755 None,
756 underlying,
757 Ustr::from("SPREAD"),
758 UnixNanos::new(0),
759 UnixNanos::new(0),
760 currency,
761 price_precision,
762 price_increment,
763 multiplier,
764 Quantity::new(1.0, 0),
765 None,
766 None,
767 None,
768 None,
769 Some(Decimal::ZERO),
770 Some(Decimal::ZERO),
771 Some(Decimal::ZERO),
772 Some(Decimal::ZERO),
773 bag_contract.map(ib_contract_info_for_contract),
774 timestamp,
775 timestamp,
776 )?)
777}
778
779pub fn parse_spread_instrument_any(
780 instrument_id: InstrumentId,
781 leg_contract_details: &[(&ibapi::contracts::ContractDetails, i32)],
782 bag_contract: Option<&ibapi::contracts::Contract>,
783 timestamp_ns: Option<UnixNanos>,
784) -> anyhow::Result<InstrumentAny> {
785 let has_future = leg_contract_details.iter().any(|(details, _)| {
786 matches!(
787 details.contract.security_type,
788 SecurityType::Future | SecurityType::ContinuousFuture
789 )
790 });
791
792 if has_future {
793 Ok(InstrumentAny::from(parse_futures_spread_instrument_id(
794 instrument_id,
795 leg_contract_details,
796 bag_contract,
797 timestamp_ns,
798 )?))
799 } else {
800 Ok(InstrumentAny::from(parse_option_spread_instrument_id(
801 instrument_id,
802 leg_contract_details,
803 bag_contract,
804 timestamp_ns,
805 )?))
806 }
807}
808
809fn parse_cfd_contract(
811 details: &ibapi::contracts::ContractDetails,
812 instrument_id: InstrumentId,
813) -> InstrumentAny {
814 let price_precision = tick_size_to_precision(details.min_tick);
815 let size_precision = tick_size_to_precision(details.min_size);
816 let timestamp = get_atomic_clock_realtime().get_time_ns();
817
818 let base_currency = details
819 .contract
820 .local_symbol
821 .contains('.')
822 .then(|| Currency::from(details.contract.symbol.to_string()));
823
824 let instrument = Cfd::new(
825 instrument_id,
826 Symbol::from(details.contract.local_symbol.as_str()),
827 sec_type_to_asset_class(details.under_security_type.as_str()),
828 base_currency,
829 Currency::from(details.contract.currency.to_string()),
830 price_precision,
831 size_precision,
832 Price::new(details.min_tick, price_precision),
833 Quantity::new(details.size_increment, size_precision),
834 None,
835 None,
836 None,
837 None,
838 None,
839 None,
840 None,
841 None,
842 None,
843 None,
844 None,
845 Some(ib_contract_info(details)),
846 timestamp,
847 timestamp,
848 );
849
850 InstrumentAny::from(instrument)
851}
852
853fn parse_commodity_contract(
855 details: &ibapi::contracts::ContractDetails,
856 instrument_id: InstrumentId,
857) -> InstrumentAny {
858 let price_precision = tick_size_to_precision(details.min_tick);
859 let size_precision = tick_size_to_precision(details.min_size);
860 let timestamp = get_atomic_clock_realtime().get_time_ns();
861
862 let instrument = Commodity::new(
863 instrument_id,
864 Symbol::from(details.contract.local_symbol.as_str()),
865 AssetClass::Commodity,
866 Currency::from(details.contract.currency.to_string()),
867 price_precision,
868 size_precision,
869 Price::new(details.min_tick, price_precision),
870 Quantity::new(details.size_increment, size_precision),
871 None,
872 None,
873 None,
874 None,
875 None,
876 None,
877 None,
878 None,
879 None,
880 None,
881 None,
882 Some(ib_contract_info(details)),
883 timestamp,
884 timestamp,
885 );
886
887 InstrumentAny::from(instrument)
888}
889
890fn parse_bond_contract(
892 details: &ibapi::contracts::ContractDetails,
893 instrument_id: InstrumentId,
894) -> InstrumentAny {
895 let price_precision = tick_size_to_precision(details.min_tick);
898 let timestamp = get_atomic_clock_realtime().get_time_ns();
899
900 let instrument = Equity::new(
901 instrument_id,
902 Symbol::from(details.contract.local_symbol.as_str()),
903 None, Currency::from(details.contract.currency.to_string()),
905 price_precision,
906 Price::new(details.min_tick, price_precision),
907 Some(Quantity::new(1.0, 0)), None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
918 timestamp,
919 );
920
921 InstrumentAny::from(instrument)
922}