1use std::str::FromStr;
23
24use nautilus_common::enums::LogColor;
25use nautilus_core::{UUID4, UnixNanos};
26use nautilus_model::{
27 enums::{LiquiditySide, OrderStatus, OrderType},
28 events::{
29 OrderAccepted, OrderCanceled, OrderEventAny, OrderExpired, OrderFilled, OrderRejected,
30 OrderTriggered, OrderUpdated,
31 },
32 identifiers::{AccountId, PositionId},
33 instruments::{Instrument, InstrumentAny},
34 orders::{Order, OrderAny, TRIGGERABLE_ORDER_TYPES},
35 reports::{FillReport, OrderStatusReport},
36 types::{Money, Price, Quantity},
37};
38use rust_decimal::Decimal;
39use ustr::Ustr;
40
41use super::{
42 ids::create_inferred_reconciliation_trade_id, positions::is_within_single_unit_tolerance,
43};
44
45fn reconciliation_position_id(
46 report: &OrderStatusReport,
47 instrument: &InstrumentAny,
48) -> PositionId {
49 report
50 .venue_position_id
51 .unwrap_or_else(|| PositionId::new(format!("{}-EXTERNAL", instrument.id())))
52}
53
54pub fn generate_external_order_status_events(
55 order: &OrderAny,
56 report: &OrderStatusReport,
57 account_id: &AccountId,
58 instrument: &InstrumentAny,
59 ts_now: UnixNanos,
60) -> Vec<OrderEventAny> {
61 let accepted = OrderEventAny::Accepted(OrderAccepted::new(
62 order.trader_id(),
63 order.strategy_id(),
64 order.instrument_id(),
65 order.client_order_id(),
66 report.venue_order_id,
67 *account_id,
68 UUID4::new(),
69 report.ts_accepted,
70 ts_now,
71 true, ));
73
74 match report.order_status {
75 OrderStatus::Accepted | OrderStatus::Triggered => vec![accepted],
76 OrderStatus::PartiallyFilled | OrderStatus::Filled => {
77 let mut events = vec![accepted];
78
79 if !report.filled_qty.is_zero()
80 && let Some(filled) =
81 create_inferred_fill(order, report, account_id, instrument, ts_now, None)
82 {
83 events.push(filled);
84 }
85
86 events
87 }
88 OrderStatus::Canceled => {
89 let canceled = OrderEventAny::Canceled(OrderCanceled::new(
90 order.trader_id(),
91 order.strategy_id(),
92 order.instrument_id(),
93 order.client_order_id(),
94 UUID4::new(),
95 report.ts_last,
96 ts_now,
97 true, Some(report.venue_order_id),
99 Some(*account_id),
100 ));
101 vec![accepted, canceled]
102 }
103 OrderStatus::Expired => {
104 let expired = OrderEventAny::Expired(OrderExpired::new(
105 order.trader_id(),
106 order.strategy_id(),
107 order.instrument_id(),
108 order.client_order_id(),
109 UUID4::new(),
110 report.ts_last,
111 ts_now,
112 true, Some(report.venue_order_id),
114 Some(*account_id),
115 ));
116 vec![accepted, expired]
117 }
118 OrderStatus::Rejected => {
119 vec![OrderEventAny::Rejected(OrderRejected::new(
121 order.trader_id(),
122 order.strategy_id(),
123 order.instrument_id(),
124 order.client_order_id(),
125 *account_id,
126 Ustr::from(report.cancel_reason.as_deref().unwrap_or("UNKNOWN")),
127 UUID4::new(),
128 report.ts_last,
129 ts_now,
130 true, false,
132 ))]
133 }
134 _ => {
135 log::warn!(
136 "Unhandled order status {} for external order {}",
137 report.order_status,
138 order.client_order_id()
139 );
140 Vec::new()
141 }
142 }
143}
144
145pub fn create_inferred_fill(
147 order: &OrderAny,
148 report: &OrderStatusReport,
149 account_id: &AccountId,
150 instrument: &InstrumentAny,
151 ts_now: UnixNanos,
152 commission: Option<Money>,
153) -> Option<OrderEventAny> {
154 let liquidity_side = match order.order_type() {
155 OrderType::Market | OrderType::StopMarket | OrderType::TrailingStopMarket => {
156 LiquiditySide::Taker
157 }
158 _ if report.post_only => LiquiditySide::Maker,
159 _ => LiquiditySide::NoLiquiditySide,
160 };
161
162 let last_px = if let Some(avg_px) = report.avg_px {
163 match Price::from_decimal_dp(avg_px, instrument.price_precision()) {
164 Ok(px) => px,
165 Err(e) => {
166 log::warn!("Failed to create price from avg_px for inferred fill: {e}");
167 return None;
168 }
169 }
170 } else if let Some(price) = report.price {
171 price
172 } else {
173 log::warn!(
174 "Cannot create inferred fill for {}: no avg_px or price available",
175 order.client_order_id()
176 );
177 return None;
178 };
179
180 let position_id = reconciliation_position_id(report, instrument);
181 let trade_id = create_inferred_reconciliation_trade_id(
182 *account_id,
183 order.instrument_id(),
184 order.client_order_id(),
185 Some(report.venue_order_id),
186 report.order_side,
187 order.order_type(),
188 report.filled_qty,
189 report.filled_qty,
190 last_px,
191 position_id,
192 report.ts_last,
193 );
194
195 log::info!(
196 "Generated inferred fill for {} ({}) qty={} px={}",
197 order.client_order_id(),
198 report.venue_order_id,
199 report.filled_qty,
200 last_px,
201 );
202
203 Some(OrderEventAny::Filled(OrderFilled::new(
204 order.trader_id(),
205 order.strategy_id(),
206 order.instrument_id(),
207 order.client_order_id(),
208 report.venue_order_id,
209 *account_id,
210 trade_id,
211 report.order_side,
212 order.order_type(),
213 report.filled_qty,
214 last_px,
215 instrument.quote_currency(),
216 liquidity_side,
217 UUID4::new(),
218 report.ts_last,
219 ts_now,
220 true, report.venue_position_id,
222 commission,
223 )))
224}
225
226#[must_use]
232pub fn create_reconciliation_accepted(
233 order: &OrderAny,
234 report: &OrderStatusReport,
235 ts_now: UnixNanos,
236) -> OrderEventAny {
237 OrderEventAny::Accepted(OrderAccepted::new(
238 order.trader_id(),
239 order.strategy_id(),
240 order.instrument_id(),
241 order.client_order_id(),
242 order.venue_order_id().unwrap_or(report.venue_order_id),
243 order
244 .account_id()
245 .expect("Order should have account_id for reconciliation"),
246 UUID4::new(),
247 report.ts_accepted,
248 ts_now,
249 true, ))
251}
252
253#[must_use]
255pub fn create_reconciliation_rejected(
256 order: &OrderAny,
257 reason: Option<&str>,
258 ts_now: UnixNanos,
259) -> Option<OrderEventAny> {
260 let account_id = order.account_id()?;
261 let reason = reason.unwrap_or("UNKNOWN");
262
263 Some(OrderEventAny::Rejected(OrderRejected::new(
264 order.trader_id(),
265 order.strategy_id(),
266 order.instrument_id(),
267 order.client_order_id(),
268 account_id,
269 Ustr::from(reason),
270 UUID4::new(),
271 ts_now,
272 ts_now,
273 true, false, )))
276}
277
278#[must_use]
280pub fn create_reconciliation_triggered(
281 order: &OrderAny,
282 report: &OrderStatusReport,
283 ts_now: UnixNanos,
284) -> OrderEventAny {
285 OrderEventAny::Triggered(OrderTriggered::new(
286 order.trader_id(),
287 order.strategy_id(),
288 order.instrument_id(),
289 order.client_order_id(),
290 UUID4::new(),
291 report.ts_triggered.unwrap_or(ts_now),
292 ts_now,
293 true, order.venue_order_id(),
295 order.account_id(),
296 ))
297}
298
299#[must_use]
301pub fn create_reconciliation_canceled(
302 order: &OrderAny,
303 report: &OrderStatusReport,
304 ts_now: UnixNanos,
305) -> OrderEventAny {
306 OrderEventAny::Canceled(OrderCanceled::new(
307 order.trader_id(),
308 order.strategy_id(),
309 order.instrument_id(),
310 order.client_order_id(),
311 UUID4::new(),
312 report.ts_last,
313 ts_now,
314 true, order.venue_order_id(),
316 order.account_id(),
317 ))
318}
319
320#[must_use]
322pub fn create_reconciliation_expired(
323 order: &OrderAny,
324 report: &OrderStatusReport,
325 ts_now: UnixNanos,
326) -> OrderEventAny {
327 OrderEventAny::Expired(OrderExpired::new(
328 order.trader_id(),
329 order.strategy_id(),
330 order.instrument_id(),
331 order.client_order_id(),
332 UUID4::new(),
333 report.ts_last,
334 ts_now,
335 true, order.venue_order_id(),
337 order.account_id(),
338 ))
339}
340
341#[must_use]
343pub fn create_reconciliation_updated(
344 order: &OrderAny,
345 report: &OrderStatusReport,
346 ts_now: UnixNanos,
347) -> OrderEventAny {
348 let trigger_price = match order.order_type() {
355 OrderType::StopMarket
356 | OrderType::StopLimit
357 | OrderType::MarketIfTouched
358 | OrderType::LimitIfTouched
359 | OrderType::TrailingStopMarket
360 | OrderType::TrailingStopLimit => report.trigger_price,
361 _ => None,
362 };
363
364 OrderEventAny::Updated(OrderUpdated::new(
365 order.trader_id(),
366 order.strategy_id(),
367 order.instrument_id(),
368 order.client_order_id(),
369 report.quantity,
370 UUID4::new(),
371 report.ts_last,
372 ts_now,
373 true, order.venue_order_id(),
375 order.account_id(),
376 report.price,
377 trigger_price,
378 None, order.is_quote_quantity(),
380 ))
381}
382
383pub fn should_reconciliation_update(order: &OrderAny, report: &OrderStatusReport) -> bool {
386 if report.quantity != order.quantity() && report.quantity >= order.filled_qty() {
388 return true;
389 }
390
391 match order.order_type() {
392 OrderType::Limit => report.price != order.price(),
393 OrderType::StopMarket | OrderType::TrailingStopMarket => {
394 report.trigger_price != order.trigger_price()
395 }
396 OrderType::StopLimit | OrderType::TrailingStopLimit => {
397 report.trigger_price != order.trigger_price() || report.price != order.price()
398 }
399 _ => false,
400 }
401}
402
403#[must_use]
408pub fn reconcile_order_report(
409 order: &OrderAny,
410 report: &OrderStatusReport,
411 instrument: Option<&InstrumentAny>,
412 ts_now: UnixNanos,
413) -> Option<OrderEventAny> {
414 if order.status() == report.order_status && order.filled_qty() == report.filled_qty {
415 if should_reconciliation_update(order, report) {
416 log::info!(
417 "Order {} has been updated at venue: qty={}->{}, price={:?}->{:?}",
418 order.client_order_id(),
419 order.quantity(),
420 report.quantity,
421 order.price(),
422 report.price
423 );
424 return Some(create_reconciliation_updated(order, report, ts_now));
425 }
426 return None; }
428
429 match report.order_status {
430 OrderStatus::Accepted => {
431 if order.status() == OrderStatus::Accepted
432 && should_reconciliation_update(order, report)
433 {
434 return Some(create_reconciliation_updated(order, report, ts_now));
435 }
436 Some(create_reconciliation_accepted(order, report, ts_now))
437 }
438 OrderStatus::Rejected => {
439 create_reconciliation_rejected(order, report.cancel_reason.as_deref(), ts_now)
440 }
441 OrderStatus::Triggered => {
442 if TRIGGERABLE_ORDER_TYPES.contains(&order.order_type()) {
443 Some(create_reconciliation_triggered(order, report, ts_now))
444 } else {
445 log::debug!(
446 "Skipping OrderTriggered for {} order {}: market-style stops have no TRIGGERED state",
447 order.order_type(),
448 order.client_order_id(),
449 );
450 None
451 }
452 }
453 OrderStatus::Canceled => Some(create_reconciliation_canceled(order, report, ts_now)),
454 OrderStatus::Expired => Some(create_reconciliation_expired(order, report, ts_now)),
455
456 OrderStatus::PartiallyFilled | OrderStatus::Filled => {
457 reconcile_fill_quantity_mismatch(order, report, instrument, ts_now)
458 }
459
460 OrderStatus::PendingUpdate | OrderStatus::PendingCancel => {
462 log::debug!(
463 "Order {} in pending state: {:?}",
464 order.client_order_id(),
465 report.order_status
466 );
467 None
468 }
469
470 OrderStatus::Initialized
472 | OrderStatus::Submitted
473 | OrderStatus::Denied
474 | OrderStatus::Emulated
475 | OrderStatus::Released => {
476 log::warn!(
477 "Unexpected order status in venue report for {}: {:?}",
478 order.client_order_id(),
479 report.order_status
480 );
481 None
482 }
483 }
484}
485
486#[must_use]
492pub fn generate_reconciliation_order_events(
493 order: &OrderAny,
494 report: &OrderStatusReport,
495 instrument: Option<&InstrumentAny>,
496 ts_now: UnixNanos,
497) -> Vec<OrderEventAny> {
498 if should_accept_before_reconciliation(order, report) {
499 let accepted = create_reconciliation_accepted(order, report, ts_now);
500 let mut accepted_order = order.clone();
501
502 if let Err(e) = accepted_order.apply(accepted.clone()) {
503 log::warn!(
504 "Failed to pre-apply reconciliation acceptance for {}: {e}",
505 order.client_order_id(),
506 );
507 return reconcile_order_report(order, report, instrument, ts_now)
508 .into_iter()
509 .collect();
510 }
511
512 let mut events = vec![accepted];
513
514 if let Some(event) = reconcile_order_report(&accepted_order, report, instrument, ts_now) {
515 events.push(event);
516 }
517 return events;
518 }
519
520 reconcile_order_report(order, report, instrument, ts_now)
521 .into_iter()
522 .collect()
523}
524
525fn should_accept_before_reconciliation(order: &OrderAny, report: &OrderStatusReport) -> bool {
526 order.status() == OrderStatus::Submitted && report.order_status != OrderStatus::Rejected
527}
528
529fn reconcile_fill_quantity_mismatch(
533 order: &OrderAny,
534 report: &OrderStatusReport,
535 instrument: Option<&InstrumentAny>,
536 ts_now: UnixNanos,
537) -> Option<OrderEventAny> {
538 let order_filled_qty = order.filled_qty();
539 let report_filled_qty = report.filled_qty;
540
541 if report_filled_qty < order_filled_qty {
542 log::error!(
544 "Fill qty mismatch for {}: cached={}, venue={} (venue < cached)",
545 order.client_order_id(),
546 order_filled_qty,
547 report_filled_qty
548 );
549 return None;
550 }
551
552 if report_filled_qty > order_filled_qty {
553 if order.is_closed() {
556 let precision = order_filled_qty.precision.max(report_filled_qty.precision);
557
558 if is_within_single_unit_tolerance(
559 report_filled_qty.as_decimal(),
560 order_filled_qty.as_decimal(),
561 precision,
562 ) {
563 return None;
564 }
565
566 log::debug!(
567 "{} {} already closed but reported difference in filled_qty: \
568 report={}, cached={}, skipping inferred fill generation for closed order",
569 order.instrument_id(),
570 order.client_order_id(),
571 report_filled_qty,
572 order_filled_qty,
573 );
574 return None;
575 }
576
577 let Some(instrument) = instrument else {
579 log::warn!(
580 "Cannot generate inferred fill for {}: instrument not available",
581 order.client_order_id()
582 );
583 return None;
584 };
585
586 let account_id = order.account_id()?;
587 return create_incremental_inferred_fill(
588 order,
589 report,
590 &account_id,
591 instrument,
592 ts_now,
593 None,
594 );
595 }
596
597 if order.status() != report.order_status {
602 if should_reconciliation_update(order, report) {
603 log::info!(
604 "Status mismatch with matching fill qty for {}: local={:?}, venue={:?}, \
605 filled_qty={}, updating quantity {}->{}",
606 order.client_order_id(),
607 order.status(),
608 report.order_status,
609 report.filled_qty,
610 order.quantity(),
611 report.quantity,
612 );
613 return Some(create_reconciliation_updated(order, report, ts_now));
614 }
615
616 log::warn!(
617 "Status mismatch with matching fill qty for {}: local={:?}, venue={:?}, filled_qty={}",
618 order.client_order_id(),
619 order.status(),
620 report.order_status,
621 report.filled_qty
622 );
623 }
624
625 None
626}
627
628pub fn create_incremental_inferred_fill(
630 order: &OrderAny,
631 report: &OrderStatusReport,
632 account_id: &AccountId,
633 instrument: &InstrumentAny,
634 ts_now: UnixNanos,
635 commission: Option<Money>,
636) -> Option<OrderEventAny> {
637 let order_filled_qty = order.filled_qty();
638 debug_assert!(
639 report.filled_qty >= order_filled_qty,
640 "incremental inferred fill requires report.filled_qty ({}) >= order.filled_qty ({}) for {}",
641 report.filled_qty,
642 order_filled_qty,
643 order.client_order_id(),
644 );
645 let last_qty = report.filled_qty - order_filled_qty;
646
647 if last_qty <= Quantity::zero(instrument.size_precision()) {
648 return None;
649 }
650
651 let liquidity_side = match order.order_type() {
652 OrderType::Market
653 | OrderType::StopMarket
654 | OrderType::MarketToLimit
655 | OrderType::TrailingStopMarket => LiquiditySide::Taker,
656 _ if order.is_post_only() => LiquiditySide::Maker,
657 _ => LiquiditySide::NoLiquiditySide,
658 };
659
660 let last_px = calculate_incremental_fill_price(order, report, instrument)?;
661
662 let venue_order_id = order.venue_order_id().unwrap_or(report.venue_order_id);
663 let position_id = reconciliation_position_id(report, instrument);
664 let trade_id = create_inferred_reconciliation_trade_id(
665 *account_id,
666 order.instrument_id(),
667 order.client_order_id(),
668 Some(venue_order_id),
669 order.order_side(),
670 order.order_type(),
671 report.filled_qty,
672 last_qty,
673 last_px,
674 position_id,
675 report.ts_last,
676 );
677
678 log::info!(
679 color = LogColor::Blue as u8;
680 "Generated inferred fill for {}: qty={}, px={}",
681 order.client_order_id(),
682 last_qty,
683 last_px,
684 );
685
686 Some(OrderEventAny::Filled(OrderFilled::new(
687 order.trader_id(),
688 order.strategy_id(),
689 order.instrument_id(),
690 order.client_order_id(),
691 venue_order_id,
692 *account_id,
693 trade_id,
694 order.order_side(),
695 order.order_type(),
696 last_qty,
697 last_px,
698 instrument.quote_currency(),
699 liquidity_side,
700 UUID4::new(),
701 report.ts_last,
702 ts_now,
703 true, None, commission,
706 )))
707}
708
709pub fn create_inferred_fill_for_qty(
715 order: &OrderAny,
716 report: &OrderStatusReport,
717 account_id: &AccountId,
718 instrument: &InstrumentAny,
719 fill_qty: Quantity,
720 ts_now: UnixNanos,
721 commission: Option<Money>,
722) -> Option<OrderEventAny> {
723 if fill_qty.is_zero() {
724 return None;
725 }
726
727 let liquidity_side = match order.order_type() {
728 OrderType::Market
729 | OrderType::StopMarket
730 | OrderType::MarketToLimit
731 | OrderType::TrailingStopMarket => LiquiditySide::Taker,
732 _ if order.is_post_only() => LiquiditySide::Maker,
733 _ => LiquiditySide::NoLiquiditySide,
734 };
735
736 let last_px = if let Some(avg_px) = report.avg_px {
737 Price::from_decimal_dp(avg_px, instrument.price_precision()).ok()?
738 } else if let Some(price) = report.price {
739 price
740 } else if let Some(price) = order.price() {
741 price
742 } else {
743 log::warn!(
744 "Cannot determine fill price for {}: no avg_px or price available",
745 order.client_order_id()
746 );
747 return None;
748 };
749
750 let venue_order_id = order.venue_order_id().unwrap_or(report.venue_order_id);
751 let position_id = reconciliation_position_id(report, instrument);
752 let trade_id = create_inferred_reconciliation_trade_id(
753 *account_id,
754 order.instrument_id(),
755 order.client_order_id(),
756 Some(venue_order_id),
757 order.order_side(),
758 order.order_type(),
759 report.filled_qty,
760 fill_qty,
761 last_px,
762 position_id,
763 report.ts_last,
764 );
765
766 log::info!(
767 color = LogColor::Blue as u8;
768 "Generated inferred fill for {}: qty={}, px={}",
769 order.client_order_id(),
770 fill_qty,
771 last_px,
772 );
773
774 Some(OrderEventAny::Filled(OrderFilled::new(
775 order.trader_id(),
776 order.strategy_id(),
777 order.instrument_id(),
778 order.client_order_id(),
779 venue_order_id,
780 *account_id,
781 trade_id,
782 order.order_side(),
783 order.order_type(),
784 fill_qty,
785 last_px,
786 instrument.quote_currency(),
787 liquidity_side,
788 UUID4::new(),
789 report.ts_last,
790 ts_now,
791 true, None, commission,
794 )))
795}
796
797fn calculate_incremental_fill_price(
799 order: &OrderAny,
800 report: &OrderStatusReport,
801 instrument: &InstrumentAny,
802) -> Option<Price> {
803 let order_filled_qty = order.filled_qty();
804 debug_assert!(
805 report.filled_qty >= order_filled_qty,
806 "incremental fill price requires report.filled_qty ({}) >= order.filled_qty ({}) for {}",
807 report.filled_qty,
808 order_filled_qty,
809 order.client_order_id(),
810 );
811
812 if order_filled_qty.is_zero() {
814 if let Some(avg_px) = report.avg_px {
815 return Price::from_decimal_dp(avg_px, instrument.price_precision()).ok();
816 }
817
818 if let Some(price) = report.price {
819 return Some(price);
820 }
821
822 if let Some(price) = order.price() {
823 return Some(price);
824 }
825 log::warn!(
826 "Cannot determine fill price for {}: no avg_px, report price, or order price",
827 order.client_order_id()
828 );
829 return None;
830 }
831
832 if let Some(report_avg_px) = report.avg_px {
834 let Some(order_avg_px) = order.avg_px() else {
835 return Price::from_decimal_dp(report_avg_px, instrument.price_precision()).ok();
837 };
838 let report_filled_qty = report.filled_qty;
839 let last_qty = report_filled_qty - order_filled_qty;
840
841 let report_notional = report_avg_px * report_filled_qty.as_decimal();
842 let order_notional = Decimal::from_str(&order_avg_px.to_string()).unwrap_or_default()
843 * order_filled_qty.as_decimal();
844 let last_notional = report_notional - order_notional;
845 let last_px_decimal = last_notional / last_qty.as_decimal();
846
847 return Price::from_decimal_dp(last_px_decimal, instrument.price_precision()).ok();
848 }
849
850 if let Some(price) = report.price {
852 return Some(price);
853 }
854
855 order.price()
856}
857
858pub fn reconcile_fill_report(
863 order: &OrderAny,
864 report: &FillReport,
865 instrument: &InstrumentAny,
866 ts_now: UnixNanos,
867 allow_overfills: bool,
868) -> Option<OrderEventAny> {
869 debug_assert!(
870 !report.last_qty.is_zero(),
871 "fill report last_qty must be non-zero for {}",
872 order.client_order_id(),
873 );
874
875 if order.trade_ids().iter().any(|id| **id == report.trade_id) {
876 log::debug!(
877 "Duplicate fill detected: trade_id {} already exists for order {}",
878 report.trade_id,
879 order.client_order_id()
880 );
881 return None;
882 }
883
884 let potential_filled_qty = order.filled_qty() + report.last_qty;
885 if potential_filled_qty > order.quantity() {
886 if !allow_overfills {
887 log::warn!(
888 "Rejecting fill that would cause overfill for {}: order.quantity={}, order.filled_qty={}, fill.last_qty={}, would result in filled_qty={}",
889 order.client_order_id(),
890 order.quantity(),
891 order.filled_qty(),
892 report.last_qty,
893 potential_filled_qty
894 );
895 return None;
896 }
897 log::warn!(
898 "Allowing overfill during reconciliation for {}: order.quantity={}, order.filled_qty={}, fill.last_qty={}, will result in filled_qty={}",
899 order.client_order_id(),
900 order.quantity(),
901 order.filled_qty(),
902 report.last_qty,
903 potential_filled_qty
904 );
905 }
906
907 let account_id = order.account_id().unwrap_or(report.account_id);
909 let venue_order_id = order.venue_order_id().unwrap_or(report.venue_order_id);
910
911 log::info!(
912 color = LogColor::Blue as u8;
913 "Reconciling fill for {}: qty={}, px={}, trade_id={}",
914 order.client_order_id(),
915 report.last_qty,
916 report.last_px,
917 report.trade_id,
918 );
919
920 Some(OrderEventAny::Filled(OrderFilled::new(
921 order.trader_id(),
922 order.strategy_id(),
923 order.instrument_id(),
924 order.client_order_id(),
925 venue_order_id,
926 account_id,
927 report.trade_id,
928 order.order_side(),
929 order.order_type(),
930 report.last_qty,
931 report.last_px,
932 instrument.quote_currency(),
933 report.liquidity_side,
934 UUID4::new(),
935 report.ts_event,
936 ts_now,
937 true, report.venue_position_id,
939 Some(report.commission),
940 )))
941}