use ibapi::orders::{Order as IBOrder, TimeInForce};
use nautilus_model::{
enums::{OrderSide, OrderType as NautilusOrderType, TrailingOffsetType},
instruments::Instrument,
orders::{Order as NautilusOrder, any::OrderAny},
};
use super::{convert_price, format_ib_datetime, trigger_type_to_ib_trigger_method};
use crate::providers::instruments::InteractiveBrokersInstrumentProvider;
pub(super) fn apply_expire_time_policy(ib_order: &mut IBOrder, order: &OrderAny) {
if matches!(ib_order.tif, TimeInForce::GoodTilDate)
&& let Some(expire) = order.expire_time()
{
ib_order.good_till_date = format_ib_datetime(expire);
}
}
pub(super) fn apply_account_policy(ib_order: &mut IBOrder, order: &OrderAny) {
if let Some(account_id) = order.account_id() {
ib_order.account = account_id.to_string();
}
}
pub(super) fn apply_quantity_policy(
ib_order: &mut IBOrder,
order: &OrderAny,
instrument_provider: &InteractiveBrokersInstrumentProvider,
) -> anyhow::Result<()> {
if let Some(instrument) = instrument_provider.find(&order.instrument_id())
&& instrument.is_inverse()
&& order.is_quote_quantity()
{
if order.order_side() != OrderSide::Buy {
anyhow::bail!(
"Interactive Brokers only accepts a quote quantity (`cash_qty`) for BUY orders; \
a SELL must use the base quantity"
);
}
ib_order.cash_qty = Some(order.quantity().as_f64());
ib_order.total_quantity = 0.0;
}
Ok(())
}
pub(super) fn apply_trailing_order_policy(
ib_order: &mut IBOrder,
order: &OrderAny,
price_magnifier: f64,
) -> anyhow::Result<()> {
if !matches!(
order.order_type(),
NautilusOrderType::TrailingStopMarket | NautilusOrderType::TrailingStopLimit
) {
return Ok(());
}
if let Some(trailing_offset) = order.trailing_offset() {
let trailing_offset_f64 = trailing_offset.to_string().parse::<f64>().map_err(|e| {
anyhow::anyhow!("Failed to convert trailing offset {trailing_offset} to f64: {e}")
})?;
match order.trailing_offset_type() {
Some(TrailingOffsetType::BasisPoints) => {
ib_order.trailing_percent = Some(trailing_offset_f64 / 100.0);
}
Some(TrailingOffsetType::Price | TrailingOffsetType::NoTrailingOffset) | None => {
ib_order.aux_price = Some(trailing_offset_f64);
}
Some(other) => anyhow::bail!("`TrailingOffsetType` {:?} is not supported", other),
}
}
if let Some(trigger_price) = order.trigger_price() {
let converted_trigger = convert_price(trigger_price, price_magnifier);
ib_order.trail_stop_price = Some(converted_trigger);
ib_order.trigger_method = order
.trigger_type()
.map(trigger_type_to_ib_trigger_method)
.unwrap_or_default();
}
Ok(())
}
pub(super) fn apply_display_quantity_policy(ib_order: &mut IBOrder, order: &OrderAny) {
if let Some(display_qty) = order.display_qty() {
ib_order.display_size = Some(display_qty.as_f64() as i32);
}
}
pub(super) fn apply_order_list_policy(_ib_order: &mut IBOrder, _order: &OrderAny) {
}