1use std::fmt::{Debug, Display};
17
18use derive_builder::Builder;
19use nautilus_core::{UUID4, UnixNanos};
20use rust_decimal::Decimal;
21use serde::{Deserialize, Serialize};
22use ustr::Ustr;
23
24use crate::{
25 enums::{
26 ContingencyType, LiquiditySide, OrderSide, OrderSideSpecified, OrderType, TimeInForce,
27 TrailingOffsetType, TriggerType,
28 },
29 events::OrderEvent,
30 identifiers::{
31 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
32 StrategyId, TradeId, TraderId, VenueOrderId,
33 },
34 types::{Currency, Money, Price, Quantity},
35};
36
37#[repr(C)]
38#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Builder)]
39#[serde(tag = "type")]
40#[cfg_attr(any(test, feature = "stubs"), builder(default))]
41#[cfg_attr(
42 feature = "python",
43 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
44)]
45pub struct OrderFilled {
46 pub trader_id: TraderId,
48 pub strategy_id: StrategyId,
50 pub instrument_id: InstrumentId,
52 pub client_order_id: ClientOrderId,
54 pub venue_order_id: VenueOrderId,
55 pub account_id: AccountId,
57 pub trade_id: TradeId,
59 pub order_side: OrderSide,
61 pub order_type: OrderType,
63 pub last_qty: Quantity,
65 pub last_px: Price,
67 pub currency: Currency,
69 pub liquidity_side: LiquiditySide,
71 pub event_id: UUID4,
73 pub ts_event: UnixNanos,
75 pub ts_init: UnixNanos,
77 pub reconciliation: bool,
79 pub position_id: Option<PositionId>,
81 pub commission: Option<Money>,
83}
84
85impl OrderFilled {
86 #[allow(clippy::too_many_arguments)]
88 pub fn new(
89 trader_id: TraderId,
90 strategy_id: StrategyId,
91 instrument_id: InstrumentId,
92 client_order_id: ClientOrderId,
93 venue_order_id: VenueOrderId,
94 account_id: AccountId,
95 trade_id: TradeId,
96 order_side: OrderSide,
97 order_type: OrderType,
98 last_qty: Quantity,
99 last_px: Price,
100 currency: Currency,
101 liquidity_side: LiquiditySide,
102 event_id: UUID4,
103 ts_event: UnixNanos,
104 ts_init: UnixNanos,
105 reconciliation: bool,
106 position_id: Option<PositionId>,
107 commission: Option<Money>,
108 ) -> Self {
109 Self {
110 trader_id,
111 strategy_id,
112 instrument_id,
113 client_order_id,
114 venue_order_id,
115 account_id,
116 trade_id,
117 order_side,
118 order_type,
119 last_qty,
120 last_px,
121 currency,
122 liquidity_side,
123 event_id,
124 ts_event,
125 ts_init,
126 reconciliation,
127 position_id,
128 commission,
129 }
130 }
131
132 #[must_use]
133 pub fn specified_side(&self) -> OrderSideSpecified {
134 self.order_side.as_specified()
135 }
136
137 #[must_use]
138 pub fn is_buy(&self) -> bool {
139 self.order_side == OrderSide::Buy
140 }
141
142 #[must_use]
143 pub fn is_sell(&self) -> bool {
144 self.order_side == OrderSide::Sell
145 }
146}
147
148impl Debug for OrderFilled {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 let position_id_str = match self.position_id {
151 Some(position_id) => position_id.to_string(),
152 None => "None".to_string(),
153 };
154 let commission_str = match self.commission {
155 Some(commission) => commission.to_string(),
156 None => "None".to_string(),
157 };
158 write!(
159 f,
160 "{}(\
161 trader_id={}, \
162 strategy_id={}, \
163 instrument_id={}, \
164 client_order_id={}, \
165 venue_order_id={}, \
166 account_id={}, \
167 trade_id={}, \
168 position_id={}, \
169 order_side={}, \
170 order_type={}, \
171 last_qty={}, \
172 last_px={} {}, \
173 commission={}, \
174 liquidity_side={}, \
175 event_id={}, \
176 ts_event={}, \
177 ts_init={})",
178 stringify!(OrderFilled),
179 self.trader_id,
180 self.strategy_id,
181 self.instrument_id,
182 self.client_order_id,
183 self.venue_order_id,
184 self.account_id,
185 self.trade_id,
186 position_id_str,
187 self.order_side,
188 self.order_type,
189 self.last_qty.to_formatted_string(),
190 self.last_px.to_formatted_string(),
191 self.currency,
192 commission_str,
193 self.liquidity_side,
194 self.event_id,
195 self.ts_event,
196 self.ts_init
197 )
198 }
199}
200
201impl Display for OrderFilled {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(
204 f,
205 "{}(\
206 instrument_id={}, \
207 client_order_id={}, \
208 venue_order_id={}, \
209 account_id={}, \
210 trade_id={}, \
211 position_id={}, \
212 order_side={}, \
213 order_type={}, \
214 last_qty={}, \
215 last_px={} {}, \
216 commission={}, \
217 liquidity_side={}, \
218 ts_event={})",
219 stringify!(OrderFilled),
220 self.instrument_id,
221 self.client_order_id,
222 self.venue_order_id,
223 self.account_id,
224 self.trade_id,
225 self.position_id
226 .map_or("None".to_string(), |id| id.to_string()),
227 self.order_side,
228 self.order_type,
229 self.last_qty.to_formatted_string(),
230 self.last_px.to_formatted_string(),
231 self.currency,
232 self.commission.unwrap_or(Money::from("0.0 USD")),
233 self.liquidity_side,
234 self.ts_event
235 )
236 }
237}
238
239impl OrderEvent for OrderFilled {
240 fn id(&self) -> UUID4 {
241 self.event_id
242 }
243
244 fn kind(&self) -> &str {
245 stringify!(OrderFilled)
246 }
247
248 fn order_type(&self) -> Option<OrderType> {
249 Some(self.order_type)
250 }
251
252 fn order_side(&self) -> Option<OrderSide> {
253 Some(self.order_side)
254 }
255
256 fn trader_id(&self) -> TraderId {
257 self.trader_id
258 }
259
260 fn strategy_id(&self) -> StrategyId {
261 self.strategy_id
262 }
263
264 fn instrument_id(&self) -> InstrumentId {
265 self.instrument_id
266 }
267
268 fn trade_id(&self) -> Option<TradeId> {
269 Some(self.trade_id)
270 }
271
272 fn currency(&self) -> Option<Currency> {
273 Some(self.currency)
274 }
275
276 fn client_order_id(&self) -> ClientOrderId {
277 self.client_order_id
278 }
279
280 fn reason(&self) -> Option<Ustr> {
281 None
282 }
283
284 fn quantity(&self) -> Option<Quantity> {
285 Some(self.last_qty)
286 }
287
288 fn time_in_force(&self) -> Option<TimeInForce> {
289 None
290 }
291
292 fn liquidity_side(&self) -> Option<LiquiditySide> {
293 Some(self.liquidity_side)
294 }
295
296 fn post_only(&self) -> Option<bool> {
297 None
298 }
299
300 fn reduce_only(&self) -> Option<bool> {
301 None
302 }
303
304 fn quote_quantity(&self) -> Option<bool> {
305 None
306 }
307
308 fn reconciliation(&self) -> bool {
309 self.reconciliation
310 }
311
312 fn price(&self) -> Option<Price> {
313 None
314 }
315
316 fn last_px(&self) -> Option<Price> {
317 Some(self.last_px)
318 }
319
320 fn last_qty(&self) -> Option<Quantity> {
321 Some(self.last_qty)
322 }
323
324 fn trigger_price(&self) -> Option<Price> {
325 None
326 }
327
328 fn trigger_type(&self) -> Option<TriggerType> {
329 None
330 }
331
332 fn limit_offset(&self) -> Option<Decimal> {
333 None
334 }
335
336 fn trailing_offset(&self) -> Option<Decimal> {
337 None
338 }
339
340 fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
341 None
342 }
343
344 fn expire_time(&self) -> Option<UnixNanos> {
345 None
346 }
347
348 fn display_qty(&self) -> Option<Quantity> {
349 None
350 }
351
352 fn emulation_trigger(&self) -> Option<TriggerType> {
353 None
354 }
355
356 fn trigger_instrument_id(&self) -> Option<InstrumentId> {
357 None
358 }
359
360 fn contingency_type(&self) -> Option<ContingencyType> {
361 None
362 }
363
364 fn order_list_id(&self) -> Option<OrderListId> {
365 None
366 }
367
368 fn linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
369 None
370 }
371
372 fn parent_order_id(&self) -> Option<ClientOrderId> {
373 None
374 }
375
376 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
377 None
378 }
379
380 fn exec_spawn_id(&self) -> Option<ClientOrderId> {
381 None
382 }
383
384 fn venue_order_id(&self) -> Option<VenueOrderId> {
385 Some(self.venue_order_id)
386 }
387
388 fn account_id(&self) -> Option<AccountId> {
389 Some(self.account_id)
390 }
391
392 fn position_id(&self) -> Option<PositionId> {
393 self.position_id
394 }
395
396 fn commission(&self) -> Option<Money> {
397 self.commission
398 }
399
400 fn ts_event(&self) -> UnixNanos {
401 self.ts_event
402 }
403
404 fn ts_init(&self) -> UnixNanos {
405 self.ts_init
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use nautilus_core::UnixNanos;
412 use rstest::rstest;
413
414 use super::*;
415 use crate::{
416 enums::{LiquiditySide, OrderSide, OrderSideSpecified, OrderType},
417 events::order::stubs::*,
418 identifiers::{
419 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId,
420 VenueOrderId,
421 },
422 stubs::TestDefault,
423 types::{Currency, Money, Price, Quantity},
424 };
425
426 fn create_test_order_filled() -> OrderFilled {
427 OrderFilled::new(
428 TraderId::from("TRADER-001"),
429 StrategyId::from("EMA-CROSS"),
430 InstrumentId::from("EURUSD.SIM"),
431 ClientOrderId::from("O-19700101-000000-001-001-1"),
432 VenueOrderId::from("V-001"),
433 AccountId::from("SIM-001"),
434 TradeId::from("T-001"),
435 OrderSide::Buy,
436 OrderType::Market,
437 Quantity::from("100"),
438 Price::from("1.0500"),
439 Currency::USD(),
440 LiquiditySide::Taker,
441 Default::default(),
442 UnixNanos::from(1_000_000_000),
443 UnixNanos::from(2_000_000_000),
444 false,
445 Some(PositionId::from("P-001")),
446 Some(Money::new(2.5, Currency::USD())),
447 )
448 }
449
450 #[rstest]
451 fn test_order_filled_new() {
452 let order_filled = create_test_order_filled();
453
454 assert_eq!(order_filled.trader_id, TraderId::from("TRADER-001"));
455 assert_eq!(order_filled.strategy_id, StrategyId::from("EMA-CROSS"));
456 assert_eq!(order_filled.instrument_id, InstrumentId::from("EURUSD.SIM"));
457 assert_eq!(
458 order_filled.client_order_id,
459 ClientOrderId::from("O-19700101-000000-001-001-1")
460 );
461 assert_eq!(order_filled.venue_order_id, VenueOrderId::from("V-001"));
462 assert_eq!(order_filled.account_id, AccountId::from("SIM-001"));
463 assert_eq!(order_filled.trade_id, TradeId::from("T-001"));
464 assert_eq!(order_filled.order_side, OrderSide::Buy);
465 assert_eq!(order_filled.order_type, OrderType::Market);
466 assert_eq!(order_filled.last_qty, Quantity::from("100"));
467 assert_eq!(order_filled.last_px, Price::from("1.0500"));
468 assert_eq!(order_filled.currency, Currency::USD());
469 assert_eq!(order_filled.liquidity_side, LiquiditySide::Taker);
470 assert_eq!(order_filled.position_id, Some(PositionId::from("P-001")));
471 assert_eq!(
472 order_filled.commission,
473 Some(Money::new(2.5, Currency::USD()))
474 );
475 assert!(!order_filled.reconciliation);
476 }
477
478 #[rstest]
479 fn test_order_filled_display(order_filled: OrderFilled) {
480 let display = format!("{order_filled}");
481 assert_eq!(
482 display,
483 "OrderFilled(instrument_id=BTCUSDT.COINBASE, client_order_id=O-19700101-000000-001-001-1, \
484 venue_order_id=123456, account_id=SIM-001, trade_id=1, position_id=None, \
485 order_side=BUY, order_type=LIMIT, last_qty=0.561, last_px=22_000 USDT, \
486 commission=12.20000000 USDT, liquidity_side=TAKER, ts_event=0)"
487 );
488 }
489
490 #[rstest]
491 fn test_order_filled_is_buy(order_filled: OrderFilled) {
492 assert!(order_filled.is_buy());
493 assert!(!order_filled.is_sell());
494 }
495
496 #[rstest]
497 fn test_order_filled_is_sell() {
498 let mut order_filled = create_test_order_filled();
499 order_filled.order_side = OrderSide::Sell;
500
501 assert!(order_filled.is_sell());
502 assert!(!order_filled.is_buy());
503 }
504
505 #[rstest]
506 fn test_order_filled_specified_side() {
507 let buy_order = create_test_order_filled();
508 assert_eq!(buy_order.specified_side(), OrderSideSpecified::Buy);
509
510 let mut sell_order = create_test_order_filled();
511 sell_order.order_side = OrderSide::Sell;
512 assert_eq!(sell_order.specified_side(), OrderSideSpecified::Sell);
513 }
514
515 #[rstest]
516 fn test_order_filled_default() {
517 let order_filled = OrderFilled::default();
518
519 assert_eq!(order_filled.trader_id, TraderId::test_default());
520 assert_eq!(order_filled.strategy_id, StrategyId::test_default());
521 assert_eq!(order_filled.instrument_id, InstrumentId::test_default());
522 assert_eq!(order_filled.client_order_id, ClientOrderId::test_default());
523 assert_eq!(order_filled.venue_order_id, VenueOrderId::test_default());
524 assert_eq!(order_filled.account_id, AccountId::test_default());
525 assert_eq!(order_filled.trade_id, TradeId::test_default());
526 assert_eq!(order_filled.order_side, OrderSide::Buy);
527 assert_eq!(order_filled.order_type, OrderType::Market);
528 assert_eq!(order_filled.currency, Currency::USD());
529 assert_eq!(order_filled.liquidity_side, LiquiditySide::Taker);
530 assert_eq!(order_filled.position_id, None);
531 assert_eq!(order_filled.commission, None);
532 assert!(!order_filled.reconciliation);
533 }
534
535 #[rstest]
536 fn test_order_filled_order_event_trait() {
537 let order_filled = create_test_order_filled();
538
539 assert_eq!(order_filled.id(), order_filled.event_id);
540 assert_eq!(order_filled.kind(), "OrderFilled");
541 assert_eq!(order_filled.order_type(), Some(OrderType::Market));
542 assert_eq!(order_filled.order_side(), Some(OrderSide::Buy));
543 assert_eq!(order_filled.trader_id(), TraderId::from("TRADER-001"));
544 assert_eq!(order_filled.strategy_id(), StrategyId::from("EMA-CROSS"));
545 assert_eq!(
546 order_filled.instrument_id(),
547 InstrumentId::from("EURUSD.SIM")
548 );
549 assert_eq!(order_filled.trade_id(), Some(TradeId::from("T-001")));
550 assert_eq!(order_filled.currency(), Some(Currency::USD()));
551 assert_eq!(
552 order_filled.client_order_id(),
553 ClientOrderId::from("O-19700101-000000-001-001-1")
554 );
555 assert_eq!(order_filled.reason(), None);
556 assert_eq!(order_filled.quantity(), Some(Quantity::from("100")));
557 assert_eq!(order_filled.liquidity_side(), Some(LiquiditySide::Taker));
558 assert!(!order_filled.reconciliation());
559 assert_eq!(
560 order_filled.venue_order_id(),
561 Some(VenueOrderId::from("V-001"))
562 );
563 assert_eq!(order_filled.account_id(), Some(AccountId::from("SIM-001")));
564 assert_eq!(order_filled.position_id(), Some(PositionId::from("P-001")));
565 assert_eq!(
566 order_filled.commission(),
567 Some(Money::new(2.5, Currency::USD()))
568 );
569 assert_eq!(order_filled.last_px(), Some(Price::from("1.0500")));
570 assert_eq!(order_filled.last_qty(), Some(Quantity::from("100")));
571 }
572
573 #[rstest]
574 fn test_order_filled_different_order_types() {
575 let mut market_order = create_test_order_filled();
576 market_order.order_type = OrderType::Market;
577
578 let mut limit_order = create_test_order_filled();
579 limit_order.order_type = OrderType::Limit;
580
581 let mut stop_order = create_test_order_filled();
582 stop_order.order_type = OrderType::StopMarket;
583
584 assert_ne!(market_order, limit_order);
585 assert_ne!(limit_order, stop_order);
586 assert_eq!(market_order.order_type, OrderType::Market);
587 assert_eq!(limit_order.order_type, OrderType::Limit);
588 assert_eq!(stop_order.order_type, OrderType::StopMarket);
589 }
590
591 #[rstest]
592 fn test_order_filled_different_liquidity_sides() {
593 let mut taker = create_test_order_filled();
594 taker.liquidity_side = LiquiditySide::Taker;
595
596 let mut maker = create_test_order_filled();
597 maker.liquidity_side = LiquiditySide::Maker;
598
599 assert_ne!(taker, maker);
600 assert_eq!(taker.liquidity_side, LiquiditySide::Taker);
601 assert_eq!(maker.liquidity_side, LiquiditySide::Maker);
602 }
603
604 #[rstest]
605 fn test_order_filled_without_position_id() {
606 let mut order_filled = create_test_order_filled();
607 order_filled.position_id = None;
608
609 assert!(order_filled.position_id.is_none());
610 }
611
612 #[rstest]
613 fn test_order_filled_without_commission() {
614 let mut order_filled = create_test_order_filled();
615 order_filled.commission = None;
616
617 assert!(order_filled.commission.is_none());
618 }
619
620 #[rstest]
621 fn test_order_filled_with_reconciliation() {
622 let mut order_filled = create_test_order_filled();
623 order_filled.reconciliation = true;
624
625 assert!(order_filled.reconciliation);
626 }
627
628 #[rstest]
629 fn test_order_filled_clone() {
630 let order_filled1 = create_test_order_filled();
631 let order_filled2 = order_filled1;
632
633 assert_eq!(order_filled1, order_filled2);
634 }
635
636 #[rstest]
637 fn test_order_filled_debug() {
638 let order_filled = create_test_order_filled();
639 let debug_str = format!("{order_filled:?}");
640
641 assert!(debug_str.contains("OrderFilled"));
642 assert!(debug_str.contains("TRADER-001"));
643 assert!(debug_str.contains("EMA-CROSS"));
644 assert!(debug_str.contains("EURUSD.SIM"));
645 assert!(debug_str.contains("P-001"));
646 }
647
648 #[rstest]
649 fn test_order_filled_partial_eq() {
650 let order_filled1 = create_test_order_filled();
651 let mut order_filled2 = create_test_order_filled();
652 order_filled2.event_id = order_filled1.event_id; let mut order_filled3 = create_test_order_filled();
654 order_filled3.trade_id = TradeId::from("T-002");
655
656 assert_eq!(order_filled1, order_filled2);
657 assert_ne!(order_filled1, order_filled3);
658 }
659
660 #[rstest]
661 fn test_order_filled_timestamps() {
662 let order_filled = create_test_order_filled();
663
664 assert_eq!(order_filled.ts_event, UnixNanos::from(1_000_000_000));
665 assert_eq!(order_filled.ts_init, UnixNanos::from(2_000_000_000));
666 assert!(order_filled.ts_event < order_filled.ts_init);
667 }
668
669 #[rstest]
670 fn test_order_filled_different_currencies() {
671 let mut usd_fill = create_test_order_filled();
672 usd_fill.currency = Currency::USD();
673
674 let mut eur_fill = create_test_order_filled();
675 eur_fill.currency = Currency::EUR();
676
677 assert_ne!(usd_fill, eur_fill);
678 assert_eq!(usd_fill.currency, Currency::USD());
679 assert_eq!(eur_fill.currency, Currency::EUR());
680 }
681
682 #[rstest]
683 fn test_order_filled_different_prices_and_quantities() {
684 let mut large_fill = create_test_order_filled();
685 large_fill.last_qty = Quantity::from("1000");
686 large_fill.last_px = Price::from("1.1000");
687
688 let mut small_fill = create_test_order_filled();
689 small_fill.last_qty = Quantity::from("100");
690 small_fill.last_px = Price::from("1.0500");
691
692 assert_ne!(large_fill, small_fill);
693 assert_eq!(large_fill.last_qty, Quantity::from("1000"));
694 assert_eq!(large_fill.last_px, Price::from("1.1000"));
695 assert_eq!(small_fill.last_qty, Quantity::from("100"));
696 assert_eq!(small_fill.last_px, Price::from("1.0500"));
697 }
698
699 #[rstest]
700 fn test_order_filled_serialization() {
701 let original = create_test_order_filled();
702
703 let json = serde_json::to_string(&original).unwrap();
704 let deserialized: OrderFilled = serde_json::from_str(&json).unwrap();
705
706 assert_eq!(original, deserialized);
707 }
708}