1use std::sync::Arc;
33
34use arc_swap::ArcSwapOption;
35use nautilus_common::{
36 factories::OrderEventFactory,
37 live::sender::EventSender,
38 messages::{ExecutionEvent, ExecutionReport},
39};
40use nautilus_core::{Params, UUID4, UnixNanos, time::AtomicTime};
41use nautilus_model::{
42 enums::{AccountType, LiquiditySide},
43 events::{
44 AccountState, OrderAcceptedBatch, OrderCancelRejected, OrderCanceledBatch, OrderEventAny,
45 OrderModifyRejected, OrderRejected, OrderSubmittedBatch,
46 },
47 identifiers::{
48 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId,
49 VenueOrderId,
50 },
51 orders::OrderAny,
52 reports::{FillReport, OrderStatusReport, PositionStatusReport},
53 types::{AccountBalance, Currency, MarginBalance, Money, Price, Quantity},
54};
55
56#[derive(Debug, Clone)]
66pub struct ExecutionEventEmitter {
67 clock: &'static AtomicTime,
68 factory: OrderEventFactory,
69 sender: Arc<ArcSwapOption<EventSender<ExecutionEvent>>>,
70}
71
72impl ExecutionEventEmitter {
73 #[must_use]
78 pub fn new(
79 clock: &'static AtomicTime,
80 trader_id: TraderId,
81 account_id: AccountId,
82 account_type: AccountType,
83 base_currency: Option<Currency>,
84 ) -> Self {
85 Self {
86 clock,
87 factory: OrderEventFactory::new(trader_id, account_id, account_type, base_currency),
88 sender: Arc::new(ArcSwapOption::empty()),
89 }
90 }
91
92 fn ts_init(&self) -> UnixNanos {
93 self.clock.get_time_ns()
94 }
95
96 pub fn set_sender(&mut self, sender: impl Into<EventSender<ExecutionEvent>>) {
108 self.sender.store(Some(Arc::new(sender.into())));
109 }
110
111 #[must_use]
113 pub fn is_initialized(&self) -> bool {
114 self.sender.load().is_some()
115 }
116
117 #[must_use]
119 pub fn trader_id(&self) -> TraderId {
120 self.factory.trader_id()
121 }
122
123 #[must_use]
125 pub fn account_id(&self) -> AccountId {
126 self.factory.account_id()
127 }
128
129 pub fn set_account_id(&mut self, account_id: AccountId) {
131 self.factory.set_account_id(account_id);
132 }
133
134 pub fn emit_account_state(
136 &self,
137 balances: Vec<AccountBalance>,
138 margins: Vec<MarginBalance>,
139 reported: bool,
140 ts_event: UnixNanos,
141 info: Option<Params>,
142 ) {
143 if let Err(e) = self.try_emit_account_state(balances, margins, reported, ts_event, info) {
144 log::warn!("{e}");
145 }
146 }
147
148 pub fn try_emit_account_state(
154 &self,
155 balances: Vec<AccountBalance>,
156 margins: Vec<MarginBalance>,
157 reported: bool,
158 ts_event: UnixNanos,
159 info: Option<Params>,
160 ) -> anyhow::Result<()> {
161 let state = self.factory.generate_account_state(
162 balances,
163 margins,
164 reported,
165 ts_event,
166 self.ts_init(),
167 info,
168 );
169 self.try_send_account_state(state)
170 }
171
172 pub fn emit_order_denied(&self, order: &OrderAny, reason: &str) {
174 let event = self
175 .factory
176 .generate_order_denied(order, reason, self.ts_init());
177 self.send_order_event(event);
178 }
179
180 pub fn emit_order_submitted(&self, order: &OrderAny) {
182 if let Err(e) = self.try_emit_order_submitted(order) {
183 log::warn!("{e}");
184 }
185 }
186
187 pub fn try_emit_order_submitted(&self, order: &OrderAny) -> anyhow::Result<()> {
193 let event = self.factory.generate_order_submitted(order, self.ts_init());
194 self.try_send_order_event(event)
195 }
196
197 pub fn emit_order_rejected(
199 &self,
200 order: &OrderAny,
201 reason: &str,
202 ts_event: UnixNanos,
203 due_post_only: bool,
204 ) {
205 let event = self.factory.generate_order_rejected(
206 order,
207 reason,
208 ts_event,
209 self.ts_init(),
210 due_post_only,
211 );
212 self.send_order_event(event);
213 }
214
215 pub fn emit_order_accepted(
217 &self,
218 order: &OrderAny,
219 venue_order_id: VenueOrderId,
220 ts_event: UnixNanos,
221 ) {
222 let event =
223 self.factory
224 .generate_order_accepted(order, venue_order_id, ts_event, self.ts_init());
225 self.send_order_event(event);
226 }
227
228 pub fn emit_order_modify_rejected(
230 &self,
231 order: &OrderAny,
232 venue_order_id: Option<VenueOrderId>,
233 reason: &str,
234 ts_event: UnixNanos,
235 ) {
236 let event = self.factory.generate_order_modify_rejected(
237 order,
238 venue_order_id,
239 reason,
240 ts_event,
241 self.ts_init(),
242 );
243 self.send_order_event(event);
244 }
245
246 pub fn emit_order_cancel_rejected(
248 &self,
249 order: &OrderAny,
250 venue_order_id: Option<VenueOrderId>,
251 reason: &str,
252 ts_event: UnixNanos,
253 ) {
254 let event = self.factory.generate_order_cancel_rejected(
255 order,
256 venue_order_id,
257 reason,
258 ts_event,
259 self.ts_init(),
260 );
261 self.send_order_event(event);
262 }
263
264 #[expect(clippy::too_many_arguments)]
266 pub fn emit_order_updated(
267 &self,
268 order: &OrderAny,
269 venue_order_id: VenueOrderId,
270 quantity: Quantity,
271 price: Option<Price>,
272 trigger_price: Option<Price>,
273 protection_price: Option<Price>,
274 ts_event: UnixNanos,
275 ) {
276 let event = self.factory.generate_order_updated(
277 order,
278 venue_order_id,
279 quantity,
280 price,
281 trigger_price,
282 protection_price,
283 ts_event,
284 self.ts_init(),
285 );
286 self.send_order_event(event);
287 }
288
289 pub fn emit_order_canceled(
291 &self,
292 order: &OrderAny,
293 venue_order_id: Option<VenueOrderId>,
294 ts_event: UnixNanos,
295 ) {
296 let event =
297 self.factory
298 .generate_order_canceled(order, venue_order_id, ts_event, self.ts_init());
299 self.send_order_event(event);
300 }
301
302 pub fn emit_order_triggered(
304 &self,
305 order: &OrderAny,
306 venue_order_id: Option<VenueOrderId>,
307 ts_event: UnixNanos,
308 ) {
309 let event =
310 self.factory
311 .generate_order_triggered(order, venue_order_id, ts_event, self.ts_init());
312 self.send_order_event(event);
313 }
314
315 pub fn emit_order_expired(
317 &self,
318 order: &OrderAny,
319 venue_order_id: Option<VenueOrderId>,
320 ts_event: UnixNanos,
321 ) {
322 let event =
323 self.factory
324 .generate_order_expired(order, venue_order_id, ts_event, self.ts_init());
325 self.send_order_event(event);
326 }
327
328 #[expect(clippy::too_many_arguments)]
330 pub fn emit_order_filled(
331 &self,
332 order: &OrderAny,
333 venue_order_id: VenueOrderId,
334 venue_position_id: Option<PositionId>,
335 trade_id: TradeId,
336 last_qty: Quantity,
337 last_px: Price,
338 quote_currency: Currency,
339 commission: Option<Money>,
340 liquidity_side: LiquiditySide,
341 ts_event: UnixNanos,
342 ) {
343 let event = self.factory.generate_order_filled(
344 order,
345 venue_order_id,
346 venue_position_id,
347 trade_id,
348 last_qty,
349 last_px,
350 quote_currency,
351 commission,
352 liquidity_side,
353 ts_event,
354 self.ts_init(),
355 );
356 self.send_order_event(event);
357 }
358
359 pub fn emit_order_rejected_event(
361 &self,
362 strategy_id: StrategyId,
363 instrument_id: InstrumentId,
364 client_order_id: ClientOrderId,
365 reason: &str,
366 ts_event: UnixNanos,
367 due_post_only: bool,
368 ) {
369 let event = OrderRejected::new(
370 self.factory.trader_id(),
371 strategy_id,
372 instrument_id,
373 client_order_id,
374 self.factory.account_id(),
375 reason.into(),
376 UUID4::new(),
377 ts_event,
378 self.ts_init(),
379 false,
380 due_post_only,
381 );
382 self.send_order_event(OrderEventAny::Rejected(event));
383 }
384
385 pub fn emit_order_modify_rejected_event(
387 &self,
388 strategy_id: StrategyId,
389 instrument_id: InstrumentId,
390 client_order_id: ClientOrderId,
391 venue_order_id: Option<VenueOrderId>,
392 reason: &str,
393 ts_event: UnixNanos,
394 ) {
395 let event = OrderModifyRejected::new(
396 self.factory.trader_id(),
397 strategy_id,
398 instrument_id,
399 client_order_id,
400 reason.into(),
401 UUID4::new(),
402 ts_event,
403 self.ts_init(),
404 false,
405 venue_order_id,
406 Some(self.factory.account_id()),
407 );
408 self.send_order_event(OrderEventAny::ModifyRejected(event));
409 }
410
411 pub fn emit_order_cancel_rejected_event(
413 &self,
414 strategy_id: StrategyId,
415 instrument_id: InstrumentId,
416 client_order_id: ClientOrderId,
417 venue_order_id: Option<VenueOrderId>,
418 reason: &str,
419 ts_event: UnixNanos,
420 ) {
421 let event = OrderCancelRejected::new(
422 self.factory.trader_id(),
423 strategy_id,
424 instrument_id,
425 client_order_id,
426 reason.into(),
427 UUID4::new(),
428 ts_event,
429 self.ts_init(),
430 false,
431 venue_order_id,
432 Some(self.factory.account_id()),
433 );
434 self.send_order_event(OrderEventAny::CancelRejected(event));
435 }
436
437 pub fn send_order_event(&self, event: OrderEventAny) {
439 if let Err(e) = self.try_send_order_event(event) {
440 log::warn!("{e}");
441 }
442 }
443
444 pub fn try_send_order_event(&self, event: OrderEventAny) -> anyhow::Result<()> {
450 self.try_send(ExecutionEvent::Order(event), "order event")
451 }
452
453 pub fn send_order_submitted_batch(&self, batch: OrderSubmittedBatch) {
455 if let Err(e) = self.try_send(
456 ExecutionEvent::OrderSubmittedBatch(batch),
457 "order submitted batch",
458 ) {
459 log::warn!("{e}");
460 }
461 }
462
463 pub fn send_order_accepted_batch(&self, batch: OrderAcceptedBatch) {
465 if let Err(e) = self.try_send(
466 ExecutionEvent::OrderAcceptedBatch(batch),
467 "order accepted batch",
468 ) {
469 log::warn!("{e}");
470 }
471 }
472
473 pub fn send_order_canceled_batch(&self, batch: OrderCanceledBatch) {
475 if let Err(e) = self.try_send(
476 ExecutionEvent::OrderCanceledBatch(batch),
477 "order canceled batch",
478 ) {
479 log::warn!("{e}");
480 }
481 }
482
483 pub fn send_account_state(&self, state: AccountState) {
485 if let Err(e) = self.try_send_account_state(state) {
486 log::warn!("{e}");
487 }
488 }
489
490 pub fn try_send_account_state(&self, state: AccountState) -> anyhow::Result<()> {
496 self.try_send(ExecutionEvent::Account(state), "account state")
497 }
498
499 pub fn send_execution_report(&self, report: ExecutionReport) {
501 if let Err(e) = self.try_send_execution_report(report) {
502 log::warn!("{e}");
503 }
504 }
505
506 pub fn try_send_execution_report(&self, report: ExecutionReport) -> anyhow::Result<()> {
512 self.try_send(ExecutionEvent::Report(report), "execution report")
513 }
514
515 pub fn send_order_status_report(&self, report: OrderStatusReport) {
517 self.send_execution_report(ExecutionReport::Order(Box::new(report)));
518 }
519
520 pub fn send_fill_report(&self, report: FillReport) {
522 self.send_execution_report(ExecutionReport::Fill(Box::new(report)));
523 }
524
525 pub fn send_order_with_fills(&self, report: OrderStatusReport, fills: Vec<FillReport>) {
527 self.send_execution_report(ExecutionReport::OrderWithFills(Box::new(report), fills));
528 }
529
530 pub fn send_position_report(&self, report: PositionStatusReport) {
532 self.send_execution_report(ExecutionReport::Position(Box::new(report)));
533 }
534
535 fn try_send(&self, event: ExecutionEvent, kind: &str) -> anyhow::Result<()> {
536 let sender = self.sender.load();
537 let sender = sender
538 .as_ref()
539 .ok_or_else(|| anyhow::anyhow!("Cannot send {kind}: sender not initialized"))?;
540 sender
541 .send(event)
542 .map_err(|e| anyhow::anyhow!("Failed to send {kind}: {e}"))
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use nautilus_core::time::get_atomic_clock_static;
549 use nautilus_model::events::order::spec::OrderSubmittedSpec;
550 use rstest::rstest;
551
552 use super::*;
553
554 fn create_emitter() -> ExecutionEventEmitter {
555 ExecutionEventEmitter::new(
556 get_atomic_clock_static(),
557 TraderId::from("TRADER-001"),
558 AccountId::from("SIM-001"),
559 AccountType::Cash,
560 None,
561 )
562 }
563
564 fn create_order_event() -> OrderEventAny {
565 OrderEventAny::Submitted(
566 OrderSubmittedSpec::builder()
567 .client_order_id(ClientOrderId::from("O-001"))
568 .build(),
569 )
570 }
571
572 #[rstest]
573 fn test_clone_before_set_sender_observes_sender() {
574 let mut emitter = create_emitter();
575 let cloned = emitter.clone();
576 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
577
578 emitter.set_sender(tx);
579
580 assert!(cloned.is_initialized());
581 cloned.send_order_event(create_order_event());
582 assert!(matches!(
583 rx.try_recv(),
584 Ok(ExecutionEvent::Order(OrderEventAny::Submitted(_)))
585 ));
586 }
587
588 #[rstest]
589 fn test_set_sender_replaces_existing_sender() {
590 let mut emitter = create_emitter();
591 let (tx_a, mut rx_a) = tokio::sync::mpsc::unbounded_channel();
592 let (tx_b, mut rx_b) = tokio::sync::mpsc::unbounded_channel();
593
594 emitter.set_sender(tx_a);
595 emitter.set_sender(tx_b);
596 emitter.send_order_event(create_order_event());
597
598 assert!(matches!(
599 rx_a.try_recv(),
600 Err(tokio::sync::mpsc::error::TryRecvError::Disconnected)
601 ));
602 assert!(matches!(
603 rx_b.try_recv(),
604 Ok(ExecutionEvent::Order(OrderEventAny::Submitted(_)))
605 ));
606 }
607
608 #[rstest]
609 fn test_never_initialized_sender_drops_or_errors() {
610 let emitter = create_emitter();
611
612 emitter.send_order_event(create_order_event());
613 let error = emitter
614 .try_send_order_event(create_order_event())
615 .unwrap_err();
616
617 assert_eq!(
618 error.to_string(),
619 "Cannot send order event: sender not initialized"
620 );
621 }
622}