use crate::error::ConnectivityError;
use ibapi::contracts::{Contract, Currency, Exchange, OptionRight, SecurityType, Symbol};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum ContractConfigError {
#[error("OPT contract requires a `right` field (expected one of C/CALL/P/PUT)")]
MissingOptionRight,
#[error("unrecognized option right {right:?} (expected one of C/CALL/P/PUT)")]
UnrecognizedOptionRight { right: String },
#[error("OPT contract requires a `strike` field")]
MissingStrike,
#[error("FUT/OPT contract requires a `last_trade_date` field")]
MissingLastTradeDate,
#[error("unrecognized security_type {security_type:?} (expected one of STK/FUT/OPT/CASH)")]
UnrecognizedSecurityType { security_type: String },
}
#[derive(Debug, Clone, PartialEq, Error)]
#[non_exhaustive]
pub enum ResolveContractError {
#[error("no IB contract matches the description")]
NoMatch,
#[error(
"{} IB contracts match the description (contract ids {:?}); narrow it",
.matches.len(),
contract_ids(.matches)
)]
Ambiguous {
matches: Vec<Contract>,
},
#[error("IB refused the contract details request (error {code}): {message}")]
Refused {
code: i32,
message: String,
},
#[error("contract details request failed: {0}")]
Connectivity(#[from] ConnectivityError),
#[error("contract details request failed: {0}")]
Failed(String),
}
impl ResolveContractError {
pub fn is_transient(&self) -> bool {
matches!(self, Self::Connectivity(e) if e.is_transient())
}
}
fn contract_ids(contracts: &[Contract]) -> Vec<i32> {
contracts
.iter()
.map(|contract| contract.contract_id)
.collect()
}
fn parse_option_right(right: &str) -> Option<OptionRight> {
let right = right.trim();
if right.eq_ignore_ascii_case("C") || right.eq_ignore_ascii_case("CALL") {
Some(OptionRight::Call)
} else if right.eq_ignore_ascii_case("P") || right.eq_ignore_ascii_case("PUT") {
Some(OptionRight::Put)
} else {
None
}
}
pub fn stock_contract(symbol: &str, exchange: &str, currency: &str) -> Contract {
Contract::stock(symbol)
.on_exchange(exchange)
.in_currency(currency)
.build()
}
pub fn futures_contract(
symbol: &str,
last_trade_date: &str,
exchange: &str,
currency: &str,
) -> Contract {
Contract {
symbol: Symbol::new(symbol),
security_type: SecurityType::Future,
last_trade_date_or_contract_month: last_trade_date.to_string(),
exchange: Exchange::new(exchange),
currency: Currency::new(currency),
..Default::default()
}
}
pub fn option_contract(
symbol: &str,
last_trade_date: &str,
strike: f64,
right: &str,
exchange: &str,
currency: &str,
) -> Result<Contract, ContractConfigError> {
let right =
parse_option_right(right).ok_or_else(|| ContractConfigError::UnrecognizedOptionRight {
right: right.to_string(),
})?;
Ok(Contract {
symbol: Symbol::new(symbol),
security_type: SecurityType::Option,
last_trade_date_or_contract_month: last_trade_date.to_string(),
strike,
right: Some(right),
exchange: Exchange::new(exchange),
currency: Currency::new(currency),
..Default::default()
})
}
pub fn forex_contract(symbol: &str, currency: &str) -> Contract {
Contract {
symbol: Symbol::new(symbol),
security_type: SecurityType::ForexPair,
exchange: Exchange::new("IDEALPRO"),
currency: Currency::new(currency),
..Default::default()
}
}