use crate::{contracts::SecurityType, messages::ResponseMessage, orders::TagValue, server_versions, Error};
use super::{Contract, ContractDescription, ContractDetails, MarketRule, PriceIncrement};
pub(crate) fn contract_details(server_version: i32, message: &mut ResponseMessage) -> Result<ContractDetails, Error> {
message.skip();
let mut message_version = 8;
if server_version < server_versions::SIZE_RULES {
message_version = message.next_int()?;
}
let mut request_id = -1;
if message_version >= 3 {
request_id = message.next_int()?;
}
let mut contract = ContractDetails::default();
contract.contract.symbol = message.next_string()?;
contract.contract.security_type = SecurityType::from(&message.next_string()?);
read_last_trade_date(&mut contract, &message.next_string()?, false)?;
contract.contract.strike = message.next_double()?;
contract.contract.right = message.next_string()?;
contract.contract.exchange = message.next_string()?;
contract.contract.currency = message.next_string()?;
contract.contract.local_symbol = message.next_string()?;
contract.market_name = message.next_string()?;
contract.contract.trading_class = message.next_string()?;
contract.contract.contract_id = message.next_int()?;
contract.min_tick = message.next_double()?;
if (server_versions::MD_SIZE_MULTIPLIER..server_versions::SIZE_RULES).contains(&server_version) {
message.next_int()?; }
contract.contract.multiplier = message.next_string()?;
contract.order_types = message.next_string()?;
contract.valid_exchanges = message.next_string()?;
if message_version >= 2 {
contract.price_magnifier = message.next_int()?;
}
if message_version >= 4 {
contract.under_contract_id = message.next_int()?;
}
if message_version >= 5 {
contract.long_name = message.next_string()?;
contract.contract.primary_exchange = message.next_string()?;
}
if message_version >= 6 {
contract.contract_month = message.next_string()?;
contract.industry = message.next_string()?;
contract.category = message.next_string()?;
contract.subcategory = message.next_string()?;
contract.time_zone_id = message.next_string()?;
contract.trading_hours = message.next_string()?;
contract.liquid_hours = message.next_string()?;
}
if message_version >= 8 {
contract.ev_rule = message.next_string()?;
contract.ev_multiplier = message.next_double()?;
}
if message_version >= 7 {
let sec_id_list_count = message.next_int()?;
for _ in 0..sec_id_list_count {
let tag = message.next_string()?;
let value = message.next_string()?;
contract.sec_id_list.push(TagValue { tag, value });
}
}
if server_version > server_versions::AGG_GROUP {
contract.agg_group = message.next_int()?;
}
if server_version > server_versions::UNDERLYING_INFO {
contract.under_symbol = message.next_string()?;
contract.under_security_type = message.next_string()?;
}
if server_version > server_versions::MARKET_RULES {
contract.market_rule_ids = message.next_string()?;
}
if server_version > server_versions::REAL_EXPIRATION_DATE {
contract.real_expiration_date = message.next_string()?;
}
if server_version > server_versions::STOCK_TYPE {
contract.stock_type = message.next_string()?;
}
if (server_versions::FRACTIONAL_SIZE_SUPPORT..server_versions::SIZE_RULES).contains(&server_version) {
message.next_double()?; }
if server_version >= server_versions::SIZE_RULES {
contract.min_size = message.next_double()?;
contract.size_increment = message.next_double()?;
contract.suggested_size_increment = message.next_double()?;
}
Ok(contract)
}
fn read_last_trade_date(contract: &mut ContractDetails, last_trade_date_or_contract_month: &str, is_bond: bool) -> Result<(), Error> {
if last_trade_date_or_contract_month.is_empty() {
return Ok(());
}
let splitted: Vec<&str> = if last_trade_date_or_contract_month.contains('-') {
last_trade_date_or_contract_month.split('-').collect()
} else {
last_trade_date_or_contract_month.split(' ').collect()
};
if !splitted.is_empty() {
if is_bond {
contract.maturity = splitted[0].to_string();
} else {
contract.contract.last_trade_date_or_contract_month = splitted[0].to_string();
}
}
if splitted.len() > 1 {
contract.last_trade_time = splitted[1].to_string();
}
if is_bond && splitted.len() > 2 {
contract.time_zone_id = splitted[2].to_string();
}
Ok(())
}
pub(crate) fn contract_descriptions(server_version: i32, message: &mut ResponseMessage) -> Result<Vec<ContractDescription>, Error> {
message.skip();
let _request_id = message.next_int()?;
let contract_descriptions_count = message.next_int()?;
if contract_descriptions_count < 1 {
return Ok(Vec::default());
}
let mut contract_descriptions: Vec<ContractDescription> = Vec::with_capacity(contract_descriptions_count as usize);
for _ in 0..contract_descriptions_count {
let mut contract = Contract {
contract_id: message.next_int()?,
symbol: message.next_string()?,
security_type: SecurityType::from(&message.next_string()?),
primary_exchange: message.next_string()?,
currency: message.next_string()?,
..Default::default()
};
let derivative_security_types_count = message.next_int()?;
let mut derivative_security_types: Vec<String> = Vec::with_capacity(derivative_security_types_count as usize);
for _ in 0..derivative_security_types_count {
derivative_security_types.push(message.next_string()?);
}
if server_version >= server_versions::BOND_ISSUERID {
contract.description = message.next_string()?;
contract.issuer_id = message.next_string()?;
}
contract_descriptions.push(ContractDescription {
contract,
derivative_security_types,
});
}
Ok(contract_descriptions)
}
pub(crate) fn market_rule(message: &mut ResponseMessage) -> Result<MarketRule, Error> {
message.skip();
let mut market_rule = MarketRule {
market_rule_id: message.next_int()?,
..Default::default()
};
let price_increments_count = message.next_int()?;
for _ in 0..price_increments_count {
market_rule.price_increments.push(PriceIncrement {
low_edge: message.next_double()?,
increment: message.next_double()?,
});
}
Ok(market_rule)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_market_rule() {
let mut message = ResponseMessage::from("93\026\01\00\00.01\0");
let results = market_rule(&mut message);
if let Ok(market_rule) = results {
assert_eq!(market_rule.market_rule_id, 26, "market_rule.market_rule_id");
assert_eq!(market_rule.price_increments.len(), 1, "market_rule.price_increments.len()");
assert_eq!(market_rule.price_increments[0].low_edge, 0.0, "market_rule.price_increments[0].low_edge");
assert_eq!(
market_rule.price_increments[0].increment, 0.01,
"market_rule.price_increments[0].increment"
);
} else if let Err(err) = results {
assert!(false, "error decoding market rule: {err}");
}
}
}