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 {
88 obj.get(key)
89 .and_then(|v| v.as_str())
90 .unwrap_or_default()
91 .to_string()
92 };
93
94 let get_i32 = |key: &str| -> i32 {
95 obj.get(key)
96 .and_then(|v| v.as_i64())
97 .map_or(0, |n| n as i32)
98 };
99
100 let get_f64 = |key: &str| -> f64 { obj.get(key).and_then(|v| v.as_f64()).unwrap_or(0.0) };
101
102 let get_bool = |key: &str| -> bool { obj.get(key).and_then(|v| v.as_bool()).unwrap_or(false) };
103
104 let parse_option_right = |key: &str| -> Option<OptionRight> {
105 match IbOptionRight::from_str(&get_str(key)).ok()? {
106 IbOptionRight::Call => Some(OptionRight::Call),
107 IbOptionRight::Put => Some(OptionRight::Put),
108 }
109 };
110
111 let parse_security_id_type = |key: &str| -> Option<SecurityIdType> {
112 match get_str(key).to_ascii_uppercase().as_str() {
113 "CUSIP" => Some(SecurityIdType::Cusip),
114 "ISIN" => Some(SecurityIdType::Isin),
115 "SEDOL" => Some(SecurityIdType::Sedol),
116 "RIC" => Some(SecurityIdType::Ric),
117 "FIGI" => Some(SecurityIdType::Figi),
118 _ => None,
119 }
120 };
121
122 let sec_type_str = get_str("secType");
124 let security_type = if sec_type_str.is_empty() {
125 SecurityType::Stock
126 } else {
127 IbSecurityType::from_str(&sec_type_str).map_or_else(
128 |_| SecurityType::Other(sec_type_str.clone()),
129 IbSecurityType::ibapi_security_type,
130 )
131 };
132
133 Ok(Contract {
134 contract_id: get_i32("conId"),
135 symbol: Symbol::from(get_str("symbol")),
136 security_type,
137 last_trade_date_or_contract_month: get_str("lastTradeDateOrContractMonth"),
138 strike: get_f64("strike"),
139 right: parse_option_right("right"),
140 multiplier: get_str("multiplier"),
141 exchange: IBExchange::from(get_str("exchange")),
142 currency: IBCurrency::from(get_str("currency")),
143 local_symbol: get_str("localSymbol"),
144 primary_exchange: IBExchange::from(get_str("primaryExchange")),
145 trading_class: get_str("tradingClass"),
146 include_expired: get_bool("includeExpired"),
147 security_id_type: parse_security_id_type("secIdType"),
148 security_id: get_str("secId"),
149 last_trade_date: None,
150 combo_legs_description: get_str("comboLegsDescrip"),
151 combo_legs: Vec::new(), delta_neutral_contract: None, issuer_id: get_str("issuerId"),
154 description: get_str("description"),
155 })
156}
157
158pub fn parse_contracts_from_json_array(json_str: &str) -> anyhow::Result<Vec<Contract>> {
164 let value: Value = serde_json::from_str(json_str).context("Failed to parse JSON string")?;
165
166 let array = value
167 .as_array()
168 .ok_or_else(|| anyhow::anyhow!("Expected JSON array for contracts"))?;
169
170 let mut contracts = Vec::new();
171
172 for (idx, item) in array.iter().enumerate() {
173 match parse_contract_from_json(item) {
174 Ok(contract) => contracts.push(contract),
175 Err(e) => {
176 tracing::warn!("Failed to parse contract at index {}: {}", idx, e);
177 }
178 }
179 }
180
181 Ok(contracts)
182}
183
184use anyhow::Context;