use std::str::FromStr;
use anyhow::Context;
use ibapi::contracts::SecurityType;
use nautilus_core::{UnixNanos, time::get_atomic_clock_realtime};
use nautilus_model::{
enums::AssetClass,
identifiers::{InstrumentId, Symbol},
instruments::{
Cfd, Commodity, CryptoPerpetual, CurrencyPair, Equity, FuturesContract, FuturesSpread,
IndexInstrument, InstrumentAny, OptionContract, OptionSpread,
},
types::{Currency, Price, Quantity},
};
use rust_decimal::Decimal;
use ustr::Ustr;
use crate::common::{
contract_to_params,
enums::{IbOptionRight, IbSecurityType},
};
#[must_use]
pub fn tick_size_to_precision(tick_size: f64) -> u8 {
if tick_size <= 0.0 {
return 8; }
let s = format!("{:.10}", tick_size);
let s = s.trim_end_matches('0');
let parts: Vec<&str> = s.split('.').collect();
if parts.len() == 2 {
parts[1].len().min(8) as u8
} else {
0
}
}
pub fn expiry_timestring_to_unix_nanos(
expiry: &str,
details: Option<&ibapi::contracts::ContractDetails>,
) -> anyhow::Result<UnixNanos> {
if expiry.is_empty() {
anyhow::bail!("Empty expiry string");
}
let dt = if expiry.len() == 8 {
let year = &expiry[0..4];
let month = &expiry[4..6];
let day = &expiry[6..8];
let date = time::Date::from_calendar_date(
year.parse()?,
time::Month::try_from(month.parse::<u8>()?)?,
day.parse()?,
)?;
let mut expiry_time = time::Time::MIDNIGHT;
if let Some(details) = details {
if !details.trading_hours.is_empty()
&& !details.trading_hours.contains(&"CLOSED".to_string())
{
let expiry_str: &str = expiry;
for session in &details.trading_hours {
if session.as_str().starts_with(expiry_str) && session.as_str().contains('-') {
let parts: Vec<&str> = session.as_str().split('-').collect();
if let Some(end_part) = parts.get(1) {
let inner_parts: Vec<&str> = end_part.split(':').collect();
if let Some(time_part) = inner_parts.get(1) {
if time_part.len() >= 4 {
let hour = time_part
.get(0..2)
.and_then(|s: &str| s.parse::<u8>().ok())
.unwrap_or(0);
let minute = time_part
.get(2..4)
.and_then(|s: &str| s.parse::<u8>().ok())
.unwrap_or(0);
expiry_time = time::Time::from_hms(hour, minute, 0)
.unwrap_or(time::Time::MIDNIGHT);
}
}
}
break;
}
}
}
}
time::PrimitiveDateTime::new(date, expiry_time)
} else {
let parts: Vec<&str> = expiry.split(' ').collect();
if parts.len() >= 3 {
let date_part = parts[0];
let time_part = parts[1];
let year = &date_part[0..4];
let month = &date_part[4..6];
let day = &date_part[6..8];
let time_parts: Vec<&str> = time_part.split(':').collect();
let hour = time_parts.first().unwrap_or(&"0").parse::<u8>()?;
let minute = time_parts.get(1).unwrap_or(&"0").parse::<u8>()?;
let second = time_parts.get(2).unwrap_or(&"0").parse::<u8>()?;
let date = time::Date::from_calendar_date(
year.parse()?,
time::Month::try_from(month.parse::<u8>()?)?,
day.parse()?,
)?;
let time_obj = time::Time::from_hms(hour, minute, second)?;
time::PrimitiveDateTime::new(date, time_obj)
} else {
anyhow::bail!("Invalid expiry format: {}", expiry);
}
};
let offset_dt = dt.assume_utc();
let nanos = offset_dt.unix_timestamp_nanos();
Ok(UnixNanos::new(nanos as u64))
}
pub fn parse_ib_contract_to_instrument(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> anyhow::Result<InstrumentAny> {
let sec_type = &details.contract.security_type;
match sec_type {
SecurityType::Stock => Ok(parse_equity_contract(details, instrument_id)),
SecurityType::ForexPair => Ok(parse_forex_contract(details, instrument_id)),
SecurityType::Crypto => Ok(parse_crypto_contract(details, instrument_id)),
SecurityType::Future | SecurityType::ContinuousFuture => {
Ok(parse_futures_contract(details, instrument_id))
}
SecurityType::Option => parse_option_contract(details, instrument_id),
SecurityType::FuturesOption => parse_option_contract(details, instrument_id), SecurityType::Index => Ok(parse_index_contract(details, instrument_id)),
SecurityType::CFD => Ok(parse_cfd_contract(details, instrument_id)),
SecurityType::Commodity => Ok(parse_commodity_contract(details, instrument_id)),
SecurityType::Bond => Ok(parse_bond_contract(details, instrument_id)),
_ => anyhow::bail!("Unsupported security type: {:?}", sec_type),
}
}
fn ib_contract_info(details: &ibapi::contracts::ContractDetails) -> nautilus_core::Params {
let mut info = nautilus_core::Params::new();
let mut contract = serde_json::Map::new();
let contract_params = contract_to_params(&details.contract);
for (key, value) in &contract_params {
contract.insert(key.clone(), value.clone());
}
info.insert("contract".to_string(), serde_json::Value::Object(contract));
info.insert(
"priceMagnifier".to_string(),
serde_json::Value::from(details.price_magnifier),
);
info
}
fn ib_contract_info_for_contract(contract: &ibapi::contracts::Contract) -> nautilus_core::Params {
let mut info = nautilus_core::Params::new();
let mut contract_map = serde_json::Map::new();
let contract_params = contract_to_params(contract);
for (key, value) in &contract_params {
contract_map.insert(key.clone(), value.clone());
}
info.insert(
"contract".to_string(),
serde_json::Value::Object(contract_map),
);
info
}
fn sec_type_to_asset_class(sec_type: &str) -> AssetClass {
match IbSecurityType::from_str(sec_type).ok() {
Some(IbSecurityType::Stock) => AssetClass::Equity,
Some(IbSecurityType::Index) => AssetClass::Index,
Some(IbSecurityType::ForexPair) => AssetClass::FX,
Some(IbSecurityType::Bond) => AssetClass::Debt,
Some(IbSecurityType::Commodity) => AssetClass::Commodity,
Some(IbSecurityType::Future) => AssetClass::Index,
_ => AssetClass::Equity,
}
}
fn parse_equity_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> InstrumentAny {
let price_precision = tick_size_to_precision(details.min_tick);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let instrument = Equity::new(
instrument_id,
Symbol::from(details.contract.local_symbol.as_str()),
None, Currency::from(details.contract.currency.to_string()),
price_precision,
Price::new(details.min_tick, price_precision),
Some(Quantity::new(100.0, 0)), None, None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
timestamp,
);
InstrumentAny::from(instrument)
}
fn parse_forex_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> InstrumentAny {
let price_precision = tick_size_to_precision(details.min_tick);
let size_precision = tick_size_to_precision(details.min_size);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let instrument = CurrencyPair::new(
instrument_id,
Symbol::from(details.contract.local_symbol.as_str()),
Currency::from(details.contract.symbol.to_string()),
Currency::from(details.contract.currency.to_string()),
price_precision,
size_precision,
Price::new(details.min_tick, price_precision),
Quantity::new(details.size_increment, size_precision),
None, None, None, None, None, None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
timestamp,
);
InstrumentAny::from(instrument)
}
fn parse_crypto_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> InstrumentAny {
let price_precision = tick_size_to_precision(details.min_tick);
let size_precision = tick_size_to_precision(details.min_size);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let instrument = CryptoPerpetual::new(
instrument_id,
Symbol::from(details.contract.local_symbol.as_str()),
Currency::from(details.contract.symbol.to_string()),
Currency::from(details.contract.currency.to_string()),
Currency::from(details.contract.currency.to_string()),
true, price_precision,
size_precision,
Price::new(details.min_tick, price_precision),
Quantity::new(details.size_increment, size_precision),
None, None, None, Some(Quantity::new(details.min_size, size_precision)),
None, None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
timestamp,
);
InstrumentAny::from(instrument)
}
fn parse_contract_multiplier(multiplier: &str, default: f64) -> Quantity {
if multiplier.is_empty() {
return Quantity::new(default, 0);
}
Quantity::from_str(multiplier).unwrap_or_else(|e| {
tracing::warn!(
"Failed to parse IB contract multiplier '{multiplier}', using default {default}: {e}"
);
Quantity::new(default, 0)
})
}
fn parse_futures_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> InstrumentAny {
let price_precision = tick_size_to_precision(details.min_tick);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let expiration_ns = if !details
.contract
.last_trade_date_or_contract_month
.is_empty()
{
expiry_timestring_to_unix_nanos(
&details.contract.last_trade_date_or_contract_month,
Some(details),
)
.unwrap_or_else(|_| UnixNanos::from(timestamp.as_u64() + 90 * 24 * 60 * 60 * 1_000_000_000))
} else {
UnixNanos::from(timestamp.as_u64() + 90 * 24 * 60 * 60 * 1_000_000_000) };
let ninety_days_ns: u64 = 90 * 24 * 60 * 60 * 1_000_000_000;
let activation_ns = expiration_ns
.checked_sub(ninety_days_ns)
.unwrap_or(UnixNanos::from(0));
let multiplier = parse_contract_multiplier(&details.contract.multiplier, 1.0);
let raw_symbol = if matches!(
details.contract.security_type,
SecurityType::ContinuousFuture
) && !details.contract.symbol.as_str().is_empty()
{
details.contract.symbol.as_str()
} else {
details.contract.local_symbol.as_str()
};
let instrument = FuturesContract::new(
instrument_id,
Symbol::from(raw_symbol),
sec_type_to_asset_class(details.under_security_type.as_str()),
None, Ustr::from(details.under_symbol.as_str()),
activation_ns,
expiration_ns,
Currency::from(details.contract.currency.to_string()),
price_precision,
Price::new(details.min_tick, price_precision),
multiplier,
Quantity::new(1.0, 0),
None, None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
timestamp,
);
InstrumentAny::from(instrument)
}
fn parse_option_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> anyhow::Result<InstrumentAny> {
let price_precision = tick_size_to_precision(details.min_tick);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let expiration_ns = if !details
.contract
.last_trade_date_or_contract_month
.is_empty()
{
expiry_timestring_to_unix_nanos(
&details.contract.last_trade_date_or_contract_month,
Some(details),
)
.unwrap_or_else(|_| UnixNanos::from(timestamp.as_u64() + 90 * 24 * 60 * 60 * 1_000_000_000))
} else {
UnixNanos::from(timestamp.as_u64() + 90 * 24 * 60 * 60 * 1_000_000_000) };
let ninety_days_ns: u64 = 90 * 24 * 60 * 60 * 1_000_000_000;
let activation_ns = expiration_ns
.checked_sub(ninety_days_ns)
.unwrap_or(UnixNanos::from(0));
let option_kind = details
.contract
.right
.map(|right| IbOptionRight::from_str(right.as_str()))
.transpose()?
.context("Option contract missing right")?
.option_kind();
let multiplier = parse_contract_multiplier(&details.contract.multiplier, 100.0);
let asset_class = sec_type_to_asset_class(details.under_security_type.as_str());
let underlying =
if details.under_security_type == "IND" && !details.under_symbol.starts_with('^') {
format!("^{}", details.under_symbol)
} else {
details.under_symbol.clone()
};
let instrument = OptionContract::new(
instrument_id,
Symbol::from(details.contract.local_symbol.as_str()),
asset_class,
None, Ustr::from(underlying.as_str()),
option_kind,
Price::new(details.contract.strike, price_precision),
Currency::from(details.contract.currency.to_string()),
activation_ns,
expiration_ns,
price_precision,
Price::new(details.min_tick, price_precision),
multiplier,
multiplier,
None, None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
timestamp,
);
Ok(InstrumentAny::from(instrument))
}
#[allow(clippy::items_after_test_module)]
#[cfg(test)]
mod tests {
use ibapi::contracts::{
Contract, ContractDetails, Currency, Exchange, OptionRight, SecurityType, Symbol,
};
use nautilus_model::{
enums::AssetClass,
identifiers::{InstrumentId, Symbol as NautilusSymbol, Venue},
instruments::{Instrument, InstrumentAny},
types::{Price, Quantity},
};
use rstest::rstest;
use ustr::Ustr;
use super::{
parse_contract_multiplier, parse_ib_contract_to_instrument,
parse_option_spread_instrument_id,
};
#[rstest]
fn test_parse_option_contract_prefixes_index_underlying() {
let details = ContractDetails {
contract: Contract {
symbol: Symbol::from("SPXW"),
security_type: SecurityType::Option,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
local_symbol: "SPXW 260313P06630000".to_string(),
last_trade_date_or_contract_month: "20260313".to_string(),
right: Some(OptionRight::Put),
strike: 6630.0,
multiplier: "100".to_string(),
..Default::default()
},
min_tick: 0.05,
under_symbol: "SPX".to_string(),
under_security_type: "IND".to_string(),
..Default::default()
};
let instrument_id = InstrumentId::new(
NautilusSymbol::from("SPXW 260313P06630000"),
Venue::from("SMART"),
);
let instrument = parse_ib_contract_to_instrument(&details, instrument_id).unwrap();
let InstrumentAny::OptionContract(option) = instrument else {
panic!("expected option contract");
};
assert_eq!(option.asset_class(), AssetClass::Index);
assert_eq!(option.underlying(), Some(Ustr::from("^SPX")));
}
#[rstest]
fn test_parse_contract_preserves_price_magnifier_in_info() {
let details = ContractDetails {
contract: Contract {
symbol: Symbol::from("AAPL"),
security_type: SecurityType::Stock,
exchange: Exchange::from("SMART"),
primary_exchange: Exchange::from("NASDAQ"),
currency: Currency::from("USD"),
local_symbol: String::from("AAPL"),
..Default::default()
},
min_tick: 0.01,
price_magnifier: 100,
..Default::default()
};
let instrument_id = InstrumentId::new(NautilusSymbol::from("AAPL"), Venue::from("XNAS"));
let instrument = parse_ib_contract_to_instrument(&details, instrument_id).unwrap();
let InstrumentAny::Equity(equity) = instrument else {
panic!("expected equity");
};
assert_eq!(
equity.info.unwrap().get("priceMagnifier"),
Some(&serde_json::Value::from(100))
);
}
#[rstest]
#[case("100", 100.0)]
#[case("", 1.0)]
#[case("not-a-number", 1.0)]
fn test_parse_contract_multiplier_uses_quantity_parser(
#[case] multiplier: &str,
#[case] expected: f64,
) {
assert_eq!(
parse_contract_multiplier(multiplier, 1.0),
Quantity::new(expected, 0)
);
}
#[rstest]
fn test_parse_continuous_future_contract_uses_symbol_as_raw_symbol() {
let details = ContractDetails {
contract: Contract {
symbol: Symbol::from("ES"),
security_type: SecurityType::ContinuousFuture,
exchange: Exchange::from("CME"),
currency: Currency::from("USD"),
local_symbol: String::new(),
multiplier: "50".to_string(),
..Default::default()
},
min_tick: 0.25,
under_symbol: "ES".to_string(),
under_security_type: "IND".to_string(),
..Default::default()
};
let instrument_id = InstrumentId::new(NautilusSymbol::from("ES"), Venue::from("CME"));
let instrument = parse_ib_contract_to_instrument(&details, instrument_id).unwrap();
let InstrumentAny::FuturesContract(future) = instrument else {
panic!("expected futures contract");
};
assert_eq!(future.raw_symbol().as_str(), "ES");
}
#[rstest]
fn test_parse_option_spread_uses_minimum_leg_tick() {
let leg1 = ContractDetails {
contract: Contract {
symbol: Symbol::from("SPY"),
security_type: SecurityType::Option,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
local_symbol: "SPY 260120C00400000".to_string(),
multiplier: "100".to_string(),
..Default::default()
},
min_tick: 0.05,
under_symbol: "SPY".to_string(),
..Default::default()
};
let leg2 = ContractDetails {
contract: Contract {
symbol: Symbol::from("SPY"),
security_type: SecurityType::Option,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
local_symbol: "SPY 260120C00410000".to_string(),
multiplier: "100".to_string(),
..Default::default()
},
min_tick: 0.01,
under_symbol: "SPY".to_string(),
..Default::default()
};
let instrument_id =
InstrumentId::from("(1)SPY 260120C00400000_((-1))SPY 260120C00410000.SMART");
let spread = parse_option_spread_instrument_id(
instrument_id,
&[(&leg1, 1), (&leg2, -1)],
None,
None,
)
.unwrap();
assert_eq!(spread.price_precision(), 2);
assert_eq!(spread.price_increment(), Price::from("0.01"));
}
}
fn parse_index_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> InstrumentAny {
let price_precision = tick_size_to_precision(details.min_tick);
let size_precision = tick_size_to_precision(details.min_size);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let instrument = IndexInstrument::new(
instrument_id,
Symbol::from(details.contract.local_symbol.as_str()),
Currency::from(details.contract.currency.to_string()),
price_precision,
size_precision,
Price::new(details.min_tick, price_precision),
Quantity::new(details.size_increment, size_precision),
None,
Some(ib_contract_info(details)), timestamp,
timestamp,
);
InstrumentAny::from(instrument)
}
pub fn parse_spread_instrument_id(
instrument_id: InstrumentId,
leg_contract_details: &[(&ibapi::contracts::ContractDetails, i32)],
timestamp_ns: Option<UnixNanos>,
) -> anyhow::Result<OptionSpread> {
if leg_contract_details.is_empty() {
anyhow::bail!("leg_contract_details must be provided");
}
let (first_details, _) = leg_contract_details[0];
let first_contract = &first_details.contract;
let currency = Currency::from(first_contract.currency.to_string());
let underlying = if !first_details.under_symbol.is_empty() {
Ustr::from(first_details.under_symbol.as_str())
} else {
Ustr::from(first_contract.symbol.as_str())
};
let multiplier_str = first_contract.multiplier.to_string();
let multiplier =
Quantity::from_str(&multiplier_str).unwrap_or_else(|_| Quantity::new(100.0, 0));
let asset_class = match first_contract.security_type {
ibapi::contracts::SecurityType::FuturesOption => AssetClass::Index, _ => AssetClass::Equity, };
let min_tick = leg_contract_details
.iter()
.map(|(details, _)| details.min_tick)
.fold(first_details.min_tick, f64::min);
let price_precision = tick_size_to_precision(min_tick);
let price_increment = Price::new(min_tick, price_precision);
let timestamp = timestamp_ns.unwrap_or_else(|| get_atomic_clock_realtime().get_time_ns());
let lot_size = multiplier;
let spread = OptionSpread::new_checked(
instrument_id,
Symbol::from(instrument_id.symbol.as_str()), asset_class,
None, underlying,
Ustr::from("SPREAD"), UnixNanos::new(0), UnixNanos::new(0), currency,
price_precision,
price_increment,
multiplier,
lot_size,
None, None, None, None, Some(Decimal::ZERO), Some(Decimal::ZERO), Some(Decimal::ZERO), Some(Decimal::ZERO), None, None, timestamp,
timestamp,
)?;
Ok(spread)
}
pub fn parse_option_spread_instrument_id(
instrument_id: InstrumentId,
leg_contract_details: &[(&ibapi::contracts::ContractDetails, i32)],
bag_contract: Option<&ibapi::contracts::Contract>,
timestamp_ns: Option<UnixNanos>,
) -> anyhow::Result<OptionSpread> {
let mut spread = parse_spread_instrument_id(instrument_id, leg_contract_details, timestamp_ns)?;
spread.info = bag_contract.map(ib_contract_info_for_contract);
Ok(spread)
}
pub fn parse_futures_spread_instrument_id(
instrument_id: InstrumentId,
leg_contract_details: &[(&ibapi::contracts::ContractDetails, i32)],
bag_contract: Option<&ibapi::contracts::Contract>,
timestamp_ns: Option<UnixNanos>,
) -> anyhow::Result<FuturesSpread> {
if leg_contract_details.is_empty() {
anyhow::bail!("leg_contract_details must be provided");
}
let (first_details, _) = leg_contract_details[0];
let first_contract = &first_details.contract;
let currency = Currency::from(first_contract.currency.to_string());
let underlying = if !first_details.under_symbol.is_empty() {
Ustr::from(first_details.under_symbol.as_str())
} else {
Ustr::from(first_contract.symbol.as_str())
};
let multiplier = Quantity::from_str(&first_contract.multiplier.to_string())
.unwrap_or_else(|_| Quantity::new(1.0, 0));
let min_tick = leg_contract_details
.iter()
.map(|(details, _)| details.min_tick)
.fold(first_details.min_tick, f64::min);
let price_precision = tick_size_to_precision(min_tick);
let price_increment = Price::new(min_tick, price_precision);
let timestamp = timestamp_ns.unwrap_or_else(|| get_atomic_clock_realtime().get_time_ns());
Ok(FuturesSpread::new_checked(
instrument_id,
Symbol::from(instrument_id.symbol.as_str()),
AssetClass::Index,
None,
underlying,
Ustr::from("SPREAD"),
UnixNanos::new(0),
UnixNanos::new(0),
currency,
price_precision,
price_increment,
multiplier,
Quantity::new(1.0, 0),
None,
None,
None,
None,
Some(Decimal::ZERO),
Some(Decimal::ZERO),
Some(Decimal::ZERO),
Some(Decimal::ZERO),
None,
bag_contract.map(ib_contract_info_for_contract),
timestamp,
timestamp,
)?)
}
pub fn parse_spread_instrument_any(
instrument_id: InstrumentId,
leg_contract_details: &[(&ibapi::contracts::ContractDetails, i32)],
bag_contract: Option<&ibapi::contracts::Contract>,
timestamp_ns: Option<UnixNanos>,
) -> anyhow::Result<InstrumentAny> {
let has_future = leg_contract_details.iter().any(|(details, _)| {
matches!(
details.contract.security_type,
SecurityType::Future | SecurityType::ContinuousFuture
)
});
if has_future {
Ok(InstrumentAny::from(parse_futures_spread_instrument_id(
instrument_id,
leg_contract_details,
bag_contract,
timestamp_ns,
)?))
} else {
Ok(InstrumentAny::from(parse_option_spread_instrument_id(
instrument_id,
leg_contract_details,
bag_contract,
timestamp_ns,
)?))
}
}
fn parse_cfd_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> InstrumentAny {
let price_precision = tick_size_to_precision(details.min_tick);
let size_precision = tick_size_to_precision(details.min_size);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let base_currency = details
.contract
.local_symbol
.contains('.')
.then(|| Currency::from(details.contract.symbol.to_string()));
let instrument = Cfd::new(
instrument_id,
Symbol::from(details.contract.local_symbol.as_str()),
sec_type_to_asset_class(details.under_security_type.as_str()),
base_currency,
Currency::from(details.contract.currency.to_string()),
price_precision,
size_precision,
Price::new(details.min_tick, price_precision),
Quantity::new(details.size_increment, size_precision),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
Some(ib_contract_info(details)),
timestamp,
timestamp,
);
InstrumentAny::from(instrument)
}
fn parse_commodity_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> InstrumentAny {
let price_precision = tick_size_to_precision(details.min_tick);
let size_precision = tick_size_to_precision(details.min_size);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let instrument = Commodity::new(
instrument_id,
Symbol::from(details.contract.local_symbol.as_str()),
AssetClass::Commodity,
Currency::from(details.contract.currency.to_string()),
price_precision,
size_precision,
Price::new(details.min_tick, price_precision),
Quantity::new(details.size_increment, size_precision),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
Some(ib_contract_info(details)),
timestamp,
timestamp,
);
InstrumentAny::from(instrument)
}
fn parse_bond_contract(
details: &ibapi::contracts::ContractDetails,
instrument_id: InstrumentId,
) -> InstrumentAny {
let price_precision = tick_size_to_precision(details.min_tick);
let timestamp = get_atomic_clock_realtime().get_time_ns();
let instrument = Equity::new(
instrument_id,
Symbol::from(details.contract.local_symbol.as_str()),
None, Currency::from(details.contract.currency.to_string()),
price_precision,
Price::new(details.min_tick, price_precision),
Some(Quantity::new(1.0, 0)), None, None, None, None, None, None, None, None, None, Some(ib_contract_info(details)), timestamp,
timestamp,
);
InstrumentAny::from(instrument)
}