use std::str::FromStr;
use ibapi::contracts::{
Contract, Currency as IBCurrency, Exchange as IBExchange, OptionRight, SecurityIdType,
SecurityType, Symbol,
};
use nautilus_core::Params;
use serde_json::Value;
use crate::common::enums::{IbOptionRight, IbSecurityType};
#[must_use]
pub fn contract_to_json_value(contract: &Contract) -> Value {
serde_json::json!({
"secType": security_type_to_code(&contract.security_type),
"conId": contract.contract_id,
"exchange": contract.exchange.to_string(),
"primaryExchange": contract.primary_exchange.to_string(),
"symbol": contract.symbol.to_string(),
"localSymbol": contract.local_symbol,
"currency": contract.currency.to_string(),
"tradingClass": contract.trading_class,
"lastTradeDateOrContractMonth": contract.last_trade_date_or_contract_month,
"multiplier": contract.multiplier,
"strike": contract.strike,
"right": contract.right,
"includeExpired": contract.include_expired,
"secIdType": contract.security_id_type,
"secId": contract.security_id,
"description": contract.description,
"issuerId": contract.issuer_id,
"comboLegsDescrip": contract.combo_legs_description,
})
}
#[must_use]
pub fn contract_to_params(contract: &Contract) -> Params {
let mut params = Params::new();
if let Value::Object(map) = contract_to_json_value(contract) {
for (key, value) in map {
params.insert(key, value);
}
}
params
}
fn security_type_to_code(security_type: &SecurityType) -> String {
IbSecurityType::try_from(security_type).map_or_else(
|_| security_type.to_string(),
|security_type| security_type.to_string(),
)
}
pub fn parse_contract_from_json(json: &Value) -> anyhow::Result<Contract> {
let obj = json
.as_object()
.ok_or_else(|| anyhow::anyhow!("Expected JSON object for contract"))?;
let get_str = |key: &str| -> String {
obj.get(key)
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string()
};
let get_i32 = |key: &str| -> i32 {
obj.get(key)
.and_then(|v| v.as_i64())
.map_or(0, |n| n as i32)
};
let get_f64 = |key: &str| -> f64 { obj.get(key).and_then(|v| v.as_f64()).unwrap_or(0.0) };
let get_bool = |key: &str| -> bool { obj.get(key).and_then(|v| v.as_bool()).unwrap_or(false) };
let parse_option_right = |key: &str| -> Option<OptionRight> {
match IbOptionRight::from_str(&get_str(key)).ok()? {
IbOptionRight::Call => Some(OptionRight::Call),
IbOptionRight::Put => Some(OptionRight::Put),
}
};
let parse_security_id_type = |key: &str| -> Option<SecurityIdType> {
match get_str(key).to_ascii_uppercase().as_str() {
"CUSIP" => Some(SecurityIdType::Cusip),
"ISIN" => Some(SecurityIdType::Isin),
"SEDOL" => Some(SecurityIdType::Sedol),
"RIC" => Some(SecurityIdType::Ric),
"FIGI" => Some(SecurityIdType::Figi),
_ => None,
}
};
let sec_type_str = get_str("secType");
let security_type = if sec_type_str.is_empty() {
SecurityType::Stock
} else {
IbSecurityType::from_str(&sec_type_str).map_or_else(
|_| SecurityType::Other(sec_type_str.clone()),
IbSecurityType::ibapi_security_type,
)
};
Ok(Contract {
contract_id: get_i32("conId"),
symbol: Symbol::from(get_str("symbol")),
security_type,
last_trade_date_or_contract_month: get_str("lastTradeDateOrContractMonth"),
strike: get_f64("strike"),
right: parse_option_right("right"),
multiplier: get_str("multiplier"),
exchange: IBExchange::from(get_str("exchange")),
currency: IBCurrency::from(get_str("currency")),
local_symbol: get_str("localSymbol"),
primary_exchange: IBExchange::from(get_str("primaryExchange")),
trading_class: get_str("tradingClass"),
include_expired: get_bool("includeExpired"),
security_id_type: parse_security_id_type("secIdType"),
security_id: get_str("secId"),
last_trade_date: None,
combo_legs_description: get_str("comboLegsDescrip"),
combo_legs: Vec::new(), delta_neutral_contract: None, issuer_id: get_str("issuerId"),
description: get_str("description"),
})
}
pub fn parse_contracts_from_json_array(json_str: &str) -> anyhow::Result<Vec<Contract>> {
let value: Value = serde_json::from_str(json_str).context("Failed to parse JSON string")?;
let array = value
.as_array()
.ok_or_else(|| anyhow::anyhow!("Expected JSON array for contracts"))?;
let mut contracts = Vec::new();
for (idx, item) in array.iter().enumerate() {
match parse_contract_from_json(item) {
Ok(contract) => contracts.push(contract),
Err(e) => {
tracing::warn!("Failed to parse contract at index {}: {}", idx, e);
}
}
}
Ok(contracts)
}
use anyhow::Context;