nautilus_interactive_brokers/common/
contracts.rs1use ibapi::contracts::{
19 Contract, Currency as IBCurrency, Exchange as IBExchange, SecurityType, Symbol,
20};
21use nautilus_core::Params;
22use serde_json::Value;
23
24#[must_use]
26pub fn contract_to_json_value(contract: &Contract) -> Value {
27 serde_json::json!({
28 "secType": security_type_to_code(&contract.security_type),
29 "conId": contract.contract_id,
30 "exchange": contract.exchange.to_string(),
31 "primaryExchange": contract.primary_exchange.to_string(),
32 "symbol": contract.symbol.to_string(),
33 "localSymbol": contract.local_symbol,
34 "currency": contract.currency.to_string(),
35 "tradingClass": contract.trading_class,
36 "lastTradeDateOrContractMonth": contract.last_trade_date_or_contract_month,
37 "multiplier": contract.multiplier,
38 "strike": contract.strike,
39 "right": contract.right,
40 "includeExpired": contract.include_expired,
41 "secIdType": contract.security_id_type,
42 "secId": contract.security_id,
43 "description": contract.description,
44 "issuerId": contract.issuer_id,
45 "comboLegsDescrip": contract.combo_legs_description,
46 })
47}
48
49#[must_use]
50pub fn contract_to_params(contract: &Contract) -> Params {
51 let mut params = Params::new();
52
53 if let Value::Object(map) = contract_to_json_value(contract) {
54 for (key, value) in map {
55 params.insert(key, value);
56 }
57 }
58
59 params
60}
61
62fn security_type_to_code(security_type: &SecurityType) -> &str {
63 match security_type {
64 SecurityType::Stock => "STK",
65 SecurityType::Option => "OPT",
66 SecurityType::Future => "FUT",
67 SecurityType::FuturesOption => "FOP",
68 SecurityType::ForexPair => "CASH",
69 SecurityType::Crypto => "CRYPTO",
70 SecurityType::ContinuousFuture => "CONTFUT",
71 SecurityType::Index => "IND",
72 SecurityType::CFD => "CFD",
73 SecurityType::Commodity => "CMDTY",
74 SecurityType::Bond => "BOND",
75 SecurityType::Warrant => "WAR",
76 SecurityType::News => "NEWS",
77 SecurityType::MutualFund => "FUND",
78 SecurityType::Spread => "BAG",
79 SecurityType::Other(other) => other.as_str(),
80 }
81}
82
83pub fn parse_contract_from_json(json: &Value) -> anyhow::Result<Contract> {
100 let obj = json
101 .as_object()
102 .ok_or_else(|| anyhow::anyhow!("Expected JSON object for contract"))?;
103
104 let get_str = |key: &str| -> String {
106 obj.get(key)
107 .and_then(|v| v.as_str())
108 .unwrap_or_default()
109 .to_string()
110 };
111
112 let get_i32 = |key: &str| -> i32 {
114 obj.get(key)
115 .and_then(|v| v.as_i64())
116 .map_or(0, |n| n as i32)
117 };
118
119 let get_f64 = |key: &str| -> f64 { obj.get(key).and_then(|v| v.as_f64()).unwrap_or(0.0) };
121
122 let get_bool = |key: &str| -> bool { obj.get(key).and_then(|v| v.as_bool()).unwrap_or(false) };
124
125 let sec_type_str = get_str("secType");
127 let security_type = match sec_type_str.as_str() {
128 "STK" | "stk" => SecurityType::Stock,
129 "OPT" | "opt" => SecurityType::Option,
130 "FUT" | "fut" => SecurityType::Future,
131 "FOP" | "fop" => SecurityType::FuturesOption,
132 "CASH" | "cash" => SecurityType::ForexPair,
133 "CRYPTO" | "crypto" => SecurityType::Crypto,
134 "IND" | "ind" => SecurityType::Index,
135 "CFD" | "cfd" => SecurityType::CFD,
136 "CMDTY" | "cmdty" => SecurityType::Commodity,
137 "BOND" | "bond" => SecurityType::Bond,
138 "BAG" | "bag" => SecurityType::Spread,
139 "" => SecurityType::Stock, other => SecurityType::Other(other.to_string()),
141 };
142
143 Ok(Contract {
144 contract_id: get_i32("conId"),
145 symbol: Symbol::from(get_str("symbol")),
146 security_type,
147 last_trade_date_or_contract_month: get_str("lastTradeDateOrContractMonth"),
148 strike: get_f64("strike"),
149 right: get_str("right"),
150 multiplier: get_str("multiplier"),
151 exchange: IBExchange::from(get_str("exchange")),
152 currency: IBCurrency::from(get_str("currency")),
153 local_symbol: get_str("localSymbol"),
154 primary_exchange: IBExchange::from(get_str("primaryExchange")),
155 trading_class: get_str("tradingClass"),
156 include_expired: get_bool("includeExpired"),
157 security_id_type: get_str("secIdType"),
158 security_id: get_str("secId"),
159 last_trade_date: None,
160 combo_legs_description: get_str("comboLegsDescrip"),
161 combo_legs: Vec::new(), delta_neutral_contract: None, issuer_id: get_str("issuerId"),
164 description: get_str("description"),
165 })
166}
167
168pub fn parse_contracts_from_json_array(json_str: &str) -> anyhow::Result<Vec<Contract>> {
182 let value: Value = serde_json::from_str(json_str).context("Failed to parse JSON string")?;
183
184 let array = value
185 .as_array()
186 .ok_or_else(|| anyhow::anyhow!("Expected JSON array for contracts"))?;
187
188 let mut contracts = Vec::new();
189
190 for (idx, item) in array.iter().enumerate() {
191 match parse_contract_from_json(item) {
192 Ok(contract) => contracts.push(contract),
193 Err(e) => {
194 tracing::warn!("Failed to parse contract at index {}: {}", idx, e);
195 }
196 }
197 }
198
199 Ok(contracts)
200}
201
202use anyhow::Context;