use indexmap::IndexMap;
use nautilus_core::{UUID4, UnixNanos};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use ustr::Ustr;
use crate::{
enums::{
ContingencyType, LiquiditySide, OrderSide, OrderStatus, OrderType, TimeInForce,
TrailingOffsetType, TriggerType,
},
identifiers::{
AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
StrategyId, TradeId, TraderId, VenueOrderId,
},
orders::{Order, OrderAny},
types::{Money, Price, Quantity},
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[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 OrderSnapshot {
pub trader_id: TraderId,
pub strategy_id: StrategyId,
pub instrument_id: InstrumentId,
pub client_order_id: ClientOrderId,
pub venue_order_id: Option<VenueOrderId>,
pub position_id: Option<PositionId>,
pub account_id: Option<AccountId>,
pub last_trade_id: Option<TradeId>,
pub order_type: OrderType,
pub order_side: OrderSide,
pub quantity: Quantity,
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: Option<TrailingOffsetType>,
pub time_in_force: TimeInForce,
pub expire_time: Option<UnixNanos>,
pub filled_qty: Quantity,
pub liquidity_side: Option<LiquiditySide>,
pub avg_px: Option<f64>,
pub slippage: Option<f64>,
pub commissions: Vec<Money>,
pub status: OrderStatus,
pub is_post_only: bool,
pub is_reduce_only: bool,
pub is_quote_quantity: bool,
pub display_qty: Option<Quantity>,
pub emulation_trigger: Option<TriggerType>,
pub trigger_instrument_id: Option<InstrumentId>,
pub contingency_type: Option<ContingencyType>,
pub order_list_id: Option<OrderListId>,
pub linked_order_ids: Option<Vec<ClientOrderId>>,
pub parent_order_id: Option<ClientOrderId>,
pub exec_algorithm_id: Option<ExecAlgorithmId>,
pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
pub exec_spawn_id: Option<ClientOrderId>,
pub tags: Option<Vec<Ustr>>,
pub init_id: UUID4,
pub ts_init: UnixNanos,
pub ts_last: UnixNanos,
}
impl From<OrderAny> for OrderSnapshot {
fn from(order: OrderAny) -> Self {
Self {
trader_id: order.trader_id(),
strategy_id: order.strategy_id(),
instrument_id: order.instrument_id(),
client_order_id: order.client_order_id(),
venue_order_id: order.venue_order_id(),
position_id: order.position_id(),
account_id: order.account_id(),
last_trade_id: order.last_trade_id(),
order_type: order.order_type(),
order_side: order.order_side(),
quantity: order.quantity(),
price: order.price(),
trigger_price: order.trigger_price(),
trigger_type: order.trigger_type(),
limit_offset: order.limit_offset(),
trailing_offset: order.trailing_offset(),
trailing_offset_type: order.trailing_offset_type(),
time_in_force: order.time_in_force(),
expire_time: order.expire_time(),
filled_qty: order.filled_qty(),
liquidity_side: order.liquidity_side(),
avg_px: order.avg_px(),
slippage: order.slippage(),
commissions: order.commissions().values().copied().collect(),
status: order.status(),
is_post_only: order.is_post_only(),
is_reduce_only: order.is_reduce_only(),
is_quote_quantity: order.is_quote_quantity(),
display_qty: order.display_qty(),
emulation_trigger: order.emulation_trigger(),
trigger_instrument_id: order.trigger_instrument_id(),
contingency_type: order.contingency_type(),
order_list_id: order.order_list_id(),
linked_order_ids: order.linked_order_ids().map(Vec::from),
parent_order_id: order.parent_order_id(),
exec_algorithm_id: order.exec_algorithm_id(),
exec_algorithm_params: order.exec_algorithm_params().cloned(),
exec_spawn_id: order.exec_spawn_id(),
tags: order.tags().map(Vec::from),
init_id: order.init_id(),
ts_init: order.ts_init(),
ts_last: order.ts_last(),
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::orders::OrderTestBuilder;
#[rstest]
fn test_snapshot_from_market_order() {
let order = OrderTestBuilder::new(OrderType::Market)
.instrument_id(InstrumentId::from("EURUSD.SIM"))
.side(OrderSide::Buy)
.quantity(Quantity::from(100))
.build();
let snapshot = OrderSnapshot::from(order.clone());
assert_eq!(snapshot.trader_id, order.trader_id());
assert_eq!(snapshot.strategy_id, order.strategy_id());
assert_eq!(snapshot.instrument_id, order.instrument_id());
assert_eq!(snapshot.client_order_id, order.client_order_id());
assert_eq!(snapshot.venue_order_id, order.venue_order_id());
assert_eq!(snapshot.order_side, order.order_side());
assert_eq!(snapshot.order_type, order.order_type());
assert_eq!(snapshot.quantity, order.quantity());
assert_eq!(snapshot.status, order.status());
assert_eq!(snapshot.ts_init, order.ts_init());
assert_eq!(snapshot.ts_last, order.ts_last());
assert_eq!(snapshot.filled_qty, order.filled_qty());
assert!(!snapshot.is_post_only);
assert!(!snapshot.is_quote_quantity);
}
#[rstest]
fn test_snapshot_from_limit_order() {
let order = OrderTestBuilder::new(OrderType::Limit)
.instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
.side(OrderSide::Sell)
.quantity(Quantity::from("0.5"))
.price(Price::from("50000"))
.build();
let snapshot = OrderSnapshot::from(order);
assert_eq!(snapshot.order_type, OrderType::Limit);
assert_eq!(snapshot.order_side, OrderSide::Sell);
assert_eq!(snapshot.price, Some(Price::from("50000")));
assert_eq!(
snapshot.instrument_id,
InstrumentId::from("BTCUSDT.BINANCE")
);
assert_eq!(snapshot.quantity, Quantity::from("0.5"));
}
}