nautilus_interactive_brokers/common/
contracts.rs1use std::str::FromStr;
19
20use ibapi::contracts::{
21 Contract, Currency as IBCurrency, Exchange as IBExchange, OptionRight, SecurityIdType,
22 SecurityType, Symbol,
23};
24use nautilus_core::Params;
25use serde_json::Value;
26
27use crate::common::enums::{IbOptionRight, IbSecurityType};
28
29#[must_use]
31pub fn contract_to_json_value(contract: &Contract) -> Value {
32 serde_json::json!({
33 "secType": security_type_to_code(&contract.security_type),
34 "conId": contract.contract_id,
35 "exchange": contract.exchange.to_string(),
36 "primaryExchange": contract.primary_exchange.to_string(),
37 "symbol": contract.symbol.to_string(),
38 "localSymbol": contract.local_symbol,
39 "currency": contract.currency.to_string(),
40 "tradingClass": contract.trading_class,
41 "lastTradeDateOrContractMonth": contract.last_trade_date_or_contract_month,
42 "multiplier": contract.multiplier,
43 "strike": contract.strike,
44 "right": contract.right,
45 "includeExpired": contract.include_expired,
46 "secIdType": contract.security_id_type,
47 "secId": contract.security_id,
48 "description": contract.description,
49 "issuerId": contract.issuer_id,
50 "comboLegsDescrip": contract.combo_legs_description,
51 })
52}
53
54#[must_use]
55pub fn contract_to_params(contract: &Contract) -> Params {
56 let mut params = Params::new();
57
58 if let Value::Object(map) = contract_to_json_value(contract) {
59 for (key, value) in map {
60 params.insert(key, value);
61 }
62 }
63
64 params
65}
66
67fn security_type_to_code(security_type: &SecurityType) -> String {
68 IbSecurityType::try_from(security_type).map_or_else(
69 |_| security_type.to_string(),
70 |security_type| security_type.to_string(),
71 )
72}
73
74pub fn parse_contract_from_json(json: &Value) -> anyhow::Result<Contract> {
83 let obj = json
84 .as_object()
85 .ok_or_else(|| anyhow::anyhow!("Expected JSON object for contract"))?;
86
87 let get_str = |key: &str| -> String {
89 obj.get(key)
90 .and_then(|v| v.as_str())
91 .unwrap_or_default()
92 .to_string()
93 };
94
95 let get_i32 = |key: &str| -> i32 {
97 obj.get(key)
98 .and_then(|v| v.as_i64())
99 .map_or(0, |n| n as i32)
100 };
101
102 let get_f64 = |key: &str| -> f64 { obj.get(key).and_then(|v| v.as_f64()).unwrap_or(0.0) };
104
105 let get_bool = |key: &str| -> bool { obj.get(key).and_then(|v| v.as_bool()).unwrap_or(false) };
107
108 let parse_option_right = |key: &str| -> Option<OptionRight> {
109 match IbOptionRight::from_str(&get_str(key)).ok()? {
110 IbOptionRight::Call => Some(OptionRight::Call),
111 IbOptionRight::Put => Some(OptionRight::Put),
112 }
113 };
114
115 let parse_security_id_type = |key: &str| -> Option<SecurityIdType> {
116 match get_str(key).to_ascii_uppercase().as_str() {
117 "CUSIP" => Some(SecurityIdType::Cusip),
118 "ISIN" => Some(SecurityIdType::Isin),
119 "SEDOL" => Some(SecurityIdType::Sedol),
120 "RIC" => Some(SecurityIdType::Ric),
121 "FIGI" => Some(SecurityIdType::Figi),
122 _ => None,
123 }
124 };
125
126 let sec_type_str = get_str("secType");
128 let security_type = if sec_type_str.is_empty() {
129 SecurityType::Stock
130 } else {
131 IbSecurityType::from_str(&sec_type_str).map_or_else(
132 |_| SecurityType::Other(sec_type_str.clone()),
133 IbSecurityType::ibapi_security_type,
134 )
135 };
136
137 Ok(Contract {
138 contract_id: get_i32("conId"),
139 symbol: Symbol::from(get_str("symbol")),
140 security_type,
141 last_trade_date_or_contract_month: get_str("lastTradeDateOrContractMonth"),
142 strike: get_f64("strike"),
143 right: parse_option_right("right"),
144 multiplier: get_str("multiplier"),
145 exchange: IBExchange::from(get_str("exchange")),
146 currency: IBCurrency::from(get_str("currency")),
147 local_symbol: get_str("localSymbol"),
148 primary_exchange: IBExchange::from(get_str("primaryExchange")),
149 trading_class: get_str("tradingClass"),
150 include_expired: get_bool("includeExpired"),
151 security_id_type: parse_security_id_type("secIdType"),
152 security_id: get_str("secId"),
153 last_trade_date: None,
154 combo_legs_description: get_str("comboLegsDescrip"),
155 combo_legs: Vec::new(), delta_neutral_contract: None, issuer_id: get_str("issuerId"),
158 description: get_str("description"),
159 })
160}
161
162pub fn parse_contracts_from_json_array(json_str: &str) -> anyhow::Result<Vec<Contract>> {
168 let value: Value = serde_json::from_str(json_str).context("Failed to parse JSON string")?;
169
170 let array = value
171 .as_array()
172 .ok_or_else(|| anyhow::anyhow!("Expected JSON array for contracts"))?;
173
174 let mut contracts = Vec::new();
175
176 for (idx, item) in array.iter().enumerate() {
177 match parse_contract_from_json(item) {
178 Ok(contract) => contracts.push(contract),
179 Err(e) => {
180 tracing::warn!("Failed to parse contract at index {}: {}", idx, e);
181 }
182 }
183 }
184
185 Ok(contracts)
186}
187
188use anyhow::Context;