use std::{fmt::Display, str::FromStr};
use nautilus_core::{UUID4, UnixNanos};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::{
enums::{
ContingencyType, OrderSide, OrderStatus, OrderType, TimeInForce, TrailingOffsetType,
TriggerType,
},
identifiers::{AccountId, ClientOrderId, InstrumentId, OrderListId, PositionId, VenueOrderId},
orders::Order,
types::{Price, Quantity},
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
)]
pub struct OrderStatusReport {
pub account_id: AccountId,
pub instrument_id: InstrumentId,
pub client_order_id: Option<ClientOrderId>,
pub venue_order_id: VenueOrderId,
pub order_side: OrderSide,
pub order_type: OrderType,
pub time_in_force: TimeInForce,
pub order_status: OrderStatus,
pub quantity: Quantity,
pub filled_qty: Quantity,
pub report_id: UUID4,
pub ts_accepted: UnixNanos,
pub ts_last: UnixNanos,
pub ts_init: UnixNanos,
pub order_list_id: Option<OrderListId>,
pub venue_position_id: Option<PositionId>,
pub linked_order_ids: Option<Vec<ClientOrderId>>,
pub parent_order_id: Option<ClientOrderId>,
pub contingency_type: ContingencyType,
pub expire_time: Option<UnixNanos>,
pub price: Option<Price>,
pub trigger_price: Option<Price>,
pub trigger_type: Option<TriggerType>,
pub limit_offset: Option<Decimal>,
pub trailing_offset: Option<Decimal>,
pub trailing_offset_type: TrailingOffsetType,
pub avg_px: Option<Decimal>,
pub display_qty: Option<Quantity>,
pub post_only: bool,
pub reduce_only: bool,
pub cancel_reason: Option<String>,
pub ts_triggered: Option<UnixNanos>,
}
impl OrderStatusReport {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn new(
account_id: AccountId,
instrument_id: InstrumentId,
client_order_id: Option<ClientOrderId>,
venue_order_id: VenueOrderId,
order_side: OrderSide,
order_type: OrderType,
time_in_force: TimeInForce,
order_status: OrderStatus,
quantity: Quantity,
filled_qty: Quantity,
ts_accepted: UnixNanos,
ts_last: UnixNanos,
ts_init: UnixNanos,
report_id: Option<UUID4>,
) -> Self {
Self {
account_id,
instrument_id,
client_order_id,
venue_order_id,
order_side,
order_type,
time_in_force,
order_status,
quantity,
filled_qty,
report_id: report_id.unwrap_or_default(),
ts_accepted,
ts_last,
ts_init,
order_list_id: None,
venue_position_id: None,
linked_order_ids: None,
parent_order_id: None,
contingency_type: ContingencyType::default(),
expire_time: None,
price: None,
trigger_price: None,
trigger_type: None,
limit_offset: None,
trailing_offset: None,
trailing_offset_type: TrailingOffsetType::default(),
avg_px: None,
display_qty: None,
post_only: false,
reduce_only: false,
cancel_reason: None,
ts_triggered: None,
}
}
#[must_use]
pub const fn with_client_order_id(mut self, client_order_id: ClientOrderId) -> Self {
self.client_order_id = Some(client_order_id);
self
}
#[must_use]
pub const fn with_order_list_id(mut self, order_list_id: OrderListId) -> Self {
self.order_list_id = Some(order_list_id);
self
}
#[must_use]
pub fn with_linked_order_ids(
mut self,
linked_order_ids: impl IntoIterator<Item = ClientOrderId>,
) -> Self {
self.linked_order_ids = Some(linked_order_ids.into_iter().collect());
self
}
#[must_use]
pub const fn with_parent_order_id(mut self, parent_order_id: ClientOrderId) -> Self {
self.parent_order_id = Some(parent_order_id);
self
}
#[must_use]
pub const fn with_venue_position_id(mut self, venue_position_id: PositionId) -> Self {
self.venue_position_id = Some(venue_position_id);
self
}
#[must_use]
pub const fn with_price(mut self, price: Price) -> Self {
self.price = Some(price);
self
}
pub fn with_avg_px(mut self, avg_px: f64) -> anyhow::Result<Self> {
if !avg_px.is_finite() {
anyhow::bail!(
"avg_px must be finite, was: {} (is_nan: {}, is_infinite: {})",
avg_px,
avg_px.is_nan(),
avg_px.is_infinite()
);
}
self.avg_px =
Some(Decimal::from_str(&avg_px.to_string()).map_err(|e| {
anyhow::anyhow!("Failed to convert avg_px to Decimal: {avg_px} ({e})")
})?);
Ok(self)
}
#[must_use]
pub const fn with_trigger_price(mut self, trigger_price: Price) -> Self {
self.trigger_price = Some(trigger_price);
self
}
#[must_use]
pub const fn with_trigger_type(mut self, trigger_type: TriggerType) -> Self {
self.trigger_type = Some(trigger_type);
self
}
#[must_use]
pub const fn with_limit_offset(mut self, limit_offset: Decimal) -> Self {
self.limit_offset = Some(limit_offset);
self
}
#[must_use]
pub const fn with_trailing_offset(mut self, trailing_offset: Decimal) -> Self {
self.trailing_offset = Some(trailing_offset);
self
}
#[must_use]
pub const fn with_trailing_offset_type(
mut self,
trailing_offset_type: TrailingOffsetType,
) -> Self {
self.trailing_offset_type = trailing_offset_type;
self
}
#[must_use]
pub const fn with_display_qty(mut self, display_qty: Quantity) -> Self {
self.display_qty = Some(display_qty);
self
}
#[must_use]
pub const fn with_expire_time(mut self, expire_time: UnixNanos) -> Self {
self.expire_time = Some(expire_time);
self
}
#[must_use]
pub const fn with_post_only(mut self, post_only: bool) -> Self {
self.post_only = post_only;
self
}
#[must_use]
pub const fn with_reduce_only(mut self, reduce_only: bool) -> Self {
self.reduce_only = reduce_only;
self
}
#[must_use]
pub fn with_cancel_reason(mut self, cancel_reason: String) -> Self {
self.cancel_reason = Some(cancel_reason);
self
}
#[must_use]
pub const fn with_ts_triggered(mut self, ts_triggered: UnixNanos) -> Self {
self.ts_triggered = Some(ts_triggered);
self
}
#[must_use]
pub const fn with_contingency_type(mut self, contingency_type: ContingencyType) -> Self {
self.contingency_type = contingency_type;
self
}
#[must_use]
pub fn is_order_updated(&self, order: &impl Order) -> bool {
if order.has_price()
&& let Some(report_price) = self.price
&& let Some(order_price) = order.price()
&& order_price != report_price
{
return true;
}
if let Some(order_trigger_price) = order.trigger_price()
&& let Some(report_trigger_price) = self.trigger_price
&& order_trigger_price != report_trigger_price
{
return true;
}
order.quantity() != self.quantity
}
}
impl Display for OrderStatusReport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"OrderStatusReport(\
account_id={}, \
instrument_id={}, \
venue_order_id={}, \
order_side={}, \
order_type={}, \
time_in_force={}, \
order_status={}, \
quantity={}, \
filled_qty={}, \
report_id={}, \
ts_accepted={}, \
ts_last={}, \
ts_init={}, \
client_order_id={:?}, \
order_list_id={:?}, \
venue_position_id={:?}, \
linked_order_ids={:?}, \
parent_order_id={:?}, \
contingency_type={}, \
expire_time={:?}, \
price={:?}, \
trigger_price={:?}, \
trigger_type={:?}, \
limit_offset={:?}, \
trailing_offset={:?}, \
trailing_offset_type={}, \
avg_px={:?}, \
display_qty={:?}, \
post_only={}, \
reduce_only={}, \
cancel_reason={:?}, \
ts_triggered={:?}\
)",
self.account_id,
self.instrument_id,
self.venue_order_id,
self.order_side,
self.order_type,
self.time_in_force,
self.order_status,
self.quantity,
self.filled_qty,
self.report_id,
self.ts_accepted,
self.ts_last,
self.ts_init,
self.client_order_id,
self.order_list_id,
self.venue_position_id,
self.linked_order_ids,
self.parent_order_id,
self.contingency_type,
self.expire_time,
self.price,
self.trigger_price,
self.trigger_type,
self.limit_offset,
self.trailing_offset,
self.trailing_offset_type,
self.avg_px,
self.display_qty,
self.post_only,
self.reduce_only,
self.cancel_reason,
self.ts_triggered,
)
}
}
#[cfg(test)]
mod tests {
use nautilus_core::UnixNanos;
use rstest::*;
use rust_decimal_macros::dec;
use super::*;
use crate::{
enums::{
ContingencyType, OrderSide, OrderStatus, OrderType, TimeInForce, TrailingOffsetType,
TriggerType,
},
identifiers::{
AccountId, ClientOrderId, InstrumentId, OrderListId, PositionId, VenueOrderId,
},
orders::builder::OrderTestBuilder,
types::{Price, Quantity},
};
fn test_order_status_report() -> OrderStatusReport {
OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
Some(ClientOrderId::from("O-19700101-000000-001-001-1")),
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Limit,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("100"),
Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
}
#[rstest]
fn test_order_status_report_new() {
let report = test_order_status_report();
assert_eq!(report.account_id, AccountId::from("SIM-001"));
assert_eq!(report.instrument_id, InstrumentId::from("AUDUSD.SIM"));
assert_eq!(
report.client_order_id,
Some(ClientOrderId::from("O-19700101-000000-001-001-1"))
);
assert_eq!(report.venue_order_id, VenueOrderId::from("1"));
assert_eq!(report.order_side, OrderSide::Buy);
assert_eq!(report.order_type, OrderType::Limit);
assert_eq!(report.time_in_force, TimeInForce::Gtc);
assert_eq!(report.order_status, OrderStatus::Accepted);
assert_eq!(report.quantity, Quantity::from("100"));
assert_eq!(report.filled_qty, Quantity::from("0"));
assert_eq!(report.ts_accepted, UnixNanos::from(1_000_000_000));
assert_eq!(report.ts_last, UnixNanos::from(2_000_000_000));
assert_eq!(report.ts_init, UnixNanos::from(3_000_000_000));
assert_eq!(report.order_list_id, None);
assert_eq!(report.venue_position_id, None);
assert_eq!(report.linked_order_ids, None);
assert_eq!(report.parent_order_id, None);
assert_eq!(report.contingency_type, ContingencyType::default());
assert_eq!(report.expire_time, None);
assert_eq!(report.price, None);
assert_eq!(report.trigger_price, None);
assert_eq!(report.trigger_type, None);
assert_eq!(report.limit_offset, None);
assert_eq!(report.trailing_offset, None);
assert_eq!(report.trailing_offset_type, TrailingOffsetType::default());
assert_eq!(report.avg_px, None);
assert_eq!(report.display_qty, None);
assert!(!report.post_only);
assert!(!report.reduce_only);
assert_eq!(report.cancel_reason, None);
assert_eq!(report.ts_triggered, None);
}
#[rstest]
fn test_order_status_report_with_generated_report_id() {
let report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Market,
TimeInForce::Ioc,
OrderStatus::Filled,
Quantity::from("100"),
Quantity::from("100"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None, );
assert_ne!(
report.report_id.to_string(),
"00000000-0000-0000-0000-000000000000"
);
}
#[rstest]
#[allow(clippy::panic_in_result_fn)]
fn test_order_status_report_builder_methods() -> anyhow::Result<()> {
let report = test_order_status_report()
.with_client_order_id(ClientOrderId::from("O-19700101-000000-001-001-2"))
.with_order_list_id(OrderListId::from("OL-001"))
.with_venue_position_id(PositionId::from("P-001"))
.with_parent_order_id(ClientOrderId::from("O-PARENT"))
.with_price(Price::from("1.00000"))
.with_avg_px(1.00001)?
.with_trigger_price(Price::from("0.99000"))
.with_trigger_type(TriggerType::Default)
.with_limit_offset(dec!(0.0001))
.with_trailing_offset(dec!(0.0002))
.with_trailing_offset_type(TrailingOffsetType::BasisPoints)
.with_display_qty(Quantity::from("50"))
.with_expire_time(UnixNanos::from(4_000_000_000))
.with_post_only(true)
.with_reduce_only(true)
.with_cancel_reason("User requested".to_string())
.with_ts_triggered(UnixNanos::from(1_500_000_000))
.with_contingency_type(ContingencyType::Oco);
assert_eq!(
report.client_order_id,
Some(ClientOrderId::from("O-19700101-000000-001-001-2"))
);
assert_eq!(report.order_list_id, Some(OrderListId::from("OL-001")));
assert_eq!(report.venue_position_id, Some(PositionId::from("P-001")));
assert_eq!(
report.parent_order_id,
Some(ClientOrderId::from("O-PARENT"))
);
assert_eq!(report.price, Some(Price::from("1.00000")));
assert_eq!(report.avg_px, Some(dec!(1.00001)));
assert_eq!(report.trigger_price, Some(Price::from("0.99000")));
assert_eq!(report.trigger_type, Some(TriggerType::Default));
assert_eq!(report.limit_offset, Some(dec!(0.0001)));
assert_eq!(report.trailing_offset, Some(dec!(0.0002)));
assert_eq!(report.trailing_offset_type, TrailingOffsetType::BasisPoints);
assert_eq!(report.display_qty, Some(Quantity::from("50")));
assert_eq!(report.expire_time, Some(UnixNanos::from(4_000_000_000)));
assert!(report.post_only);
assert!(report.reduce_only);
assert_eq!(report.cancel_reason, Some("User requested".to_string()));
assert_eq!(report.ts_triggered, Some(UnixNanos::from(1_500_000_000)));
assert_eq!(report.contingency_type, ContingencyType::Oco);
Ok(())
}
#[rstest]
fn test_display() {
let report = test_order_status_report();
let display_str = format!("{report}");
assert!(display_str.contains("OrderStatusReport"));
assert!(display_str.contains("SIM-001"));
assert!(display_str.contains("AUDUSD.SIM"));
assert!(display_str.contains("BUY"));
assert!(display_str.contains("LIMIT"));
assert!(display_str.contains("GTC"));
assert!(display_str.contains("ACCEPTED"));
assert!(display_str.contains("100"));
}
#[rstest]
fn test_clone_and_equality() {
let report1 = test_order_status_report();
let report2 = report1.clone();
assert_eq!(report1, report2);
}
#[rstest]
fn test_serialization_roundtrip() {
let original = test_order_status_report();
let json = serde_json::to_string(&original).unwrap();
let deserialized: OrderStatusReport = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
}
#[rstest]
fn test_order_status_report_different_order_types() {
let market_report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Market,
TimeInForce::Ioc,
OrderStatus::Filled,
Quantity::from("100"),
Quantity::from("100"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
);
let stop_report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("2"),
OrderSide::Sell,
OrderType::StopMarket,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("50"),
Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
);
assert_eq!(market_report.order_type, OrderType::Market);
assert_eq!(stop_report.order_type, OrderType::StopMarket);
assert_ne!(market_report, stop_report);
}
#[rstest]
fn test_order_status_report_different_statuses() {
let accepted_report = test_order_status_report();
let filled_report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
Some(ClientOrderId::from("O-19700101-000000-001-001-1")),
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Limit,
TimeInForce::Gtc,
OrderStatus::Filled,
Quantity::from("100"),
Quantity::from("100"), UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
);
assert_eq!(accepted_report.order_status, OrderStatus::Accepted);
assert_eq!(filled_report.order_status, OrderStatus::Filled);
assert_ne!(accepted_report, filled_report);
}
#[rstest]
#[allow(clippy::panic_in_result_fn)]
fn test_order_status_report_with_optional_fields() -> anyhow::Result<()> {
let mut report = test_order_status_report();
assert_eq!(report.price, None);
assert_eq!(report.avg_px, None);
assert!(!report.post_only);
assert!(!report.reduce_only);
report = report
.with_price(Price::from("1.00000"))
.with_avg_px(1.00001)?
.with_post_only(true)
.with_reduce_only(true);
assert_eq!(report.price, Some(Price::from("1.00000")));
assert_eq!(report.avg_px, Some(dec!(1.00001)));
assert!(report.post_only);
assert!(report.reduce_only);
Ok(())
}
#[rstest]
fn test_order_status_report_partial_fill() {
let partial_fill_report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
Some(ClientOrderId::from("O-19700101-000000-001-001-1")),
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Limit,
TimeInForce::Gtc,
OrderStatus::PartiallyFilled,
Quantity::from("100"),
Quantity::from("30"), UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
);
assert_eq!(partial_fill_report.quantity, Quantity::from("100"));
assert_eq!(partial_fill_report.filled_qty, Quantity::from("30"));
assert_eq!(
partial_fill_report.order_status,
OrderStatus::PartiallyFilled
);
}
#[rstest]
fn test_order_status_report_with_all_timestamp_fields() {
let report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::StopLimit,
TimeInForce::Gtc,
OrderStatus::Triggered,
Quantity::from("100"),
Quantity::from("0"),
UnixNanos::from(1_000_000_000), UnixNanos::from(2_000_000_000), UnixNanos::from(3_000_000_000), None,
)
.with_ts_triggered(UnixNanos::from(1_500_000_000));
assert_eq!(report.ts_accepted, UnixNanos::from(1_000_000_000));
assert_eq!(report.ts_last, UnixNanos::from(2_000_000_000));
assert_eq!(report.ts_init, UnixNanos::from(3_000_000_000));
assert_eq!(report.ts_triggered, Some(UnixNanos::from(1_500_000_000)));
}
#[rstest]
fn test_is_order_updated_returns_true_when_price_differs() {
let order = OrderTestBuilder::new(OrderType::Limit)
.instrument_id(InstrumentId::from("AUDUSD.SIM"))
.quantity(Quantity::from(100))
.price(Price::from("1.00000"))
.build();
let report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Limit,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("100"),
Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
.with_price(Price::from("1.00100"));
assert!(report.is_order_updated(&order));
}
#[rstest]
fn test_is_order_updated_returns_true_when_trigger_price_differs() {
let order = OrderTestBuilder::new(OrderType::StopMarket)
.instrument_id(InstrumentId::from("AUDUSD.SIM"))
.quantity(Quantity::from(100))
.trigger_price(Price::from("0.99000"))
.build();
let report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::StopMarket,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("100"),
Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
.with_trigger_price(Price::from("0.99100"));
assert!(report.is_order_updated(&order));
}
#[rstest]
fn test_is_order_updated_returns_true_when_quantity_differs() {
let order = OrderTestBuilder::new(OrderType::Limit)
.instrument_id(InstrumentId::from("AUDUSD.SIM"))
.quantity(Quantity::from(100))
.price(Price::from("1.00000"))
.build();
let report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Limit,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("200"), Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
.with_price(Price::from("1.00000"));
assert!(report.is_order_updated(&order));
}
#[rstest]
fn test_is_order_updated_returns_false_when_all_match() {
let order = OrderTestBuilder::new(OrderType::Limit)
.instrument_id(InstrumentId::from("AUDUSD.SIM"))
.quantity(Quantity::from(100))
.price(Price::from("1.00000"))
.build();
let report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Limit,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("100"), Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
.with_price(Price::from("1.00000"));
assert!(!report.is_order_updated(&order));
}
#[rstest]
fn test_is_order_updated_returns_false_when_order_has_no_price() {
let order = OrderTestBuilder::new(OrderType::Market)
.instrument_id(InstrumentId::from("AUDUSD.SIM"))
.quantity(Quantity::from(100))
.build();
let report = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::Market,
TimeInForce::Ioc,
OrderStatus::Accepted,
Quantity::from("100"), Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
.with_price(Price::from("1.00000"));
assert!(!report.is_order_updated(&order));
}
#[rstest]
fn test_is_order_updated_stop_limit_order_with_both_prices() {
let order = OrderTestBuilder::new(OrderType::StopLimit)
.instrument_id(InstrumentId::from("AUDUSD.SIM"))
.quantity(Quantity::from(100))
.price(Price::from("1.00000"))
.trigger_price(Price::from("0.99000"))
.build();
let report_same = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::StopLimit,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("100"),
Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
.with_price(Price::from("1.00000"))
.with_trigger_price(Price::from("0.99000"));
assert!(!report_same.is_order_updated(&order));
let report_diff_price = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::StopLimit,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("100"),
Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
.with_price(Price::from("1.00100")) .with_trigger_price(Price::from("0.99000"));
assert!(report_diff_price.is_order_updated(&order));
let report_diff_trigger = OrderStatusReport::new(
AccountId::from("SIM-001"),
InstrumentId::from("AUDUSD.SIM"),
None,
VenueOrderId::from("1"),
OrderSide::Buy,
OrderType::StopLimit,
TimeInForce::Gtc,
OrderStatus::Accepted,
Quantity::from("100"),
Quantity::from("0"),
UnixNanos::from(1_000_000_000),
UnixNanos::from(2_000_000_000),
UnixNanos::from(3_000_000_000),
None,
)
.with_price(Price::from("1.00000"))
.with_trigger_price(Price::from("0.99100"));
assert!(report_diff_trigger.is_order_updated(&order));
}
}