1pub mod config;
39pub mod core;
40pub mod twap;
41
42pub use core::{ExecutionAlgorithmCore, StrategyEventHandlers};
43
44pub use config::{ExecutionAlgorithmConfig, ImportableExecAlgorithmConfig};
45use nautilus_common::{
46 actor::{DataActor, registry::try_get_actor_unchecked},
47 enums::ComponentState,
48 logging::{CMD, EVT, RECV, SEND},
49 messages::execution::{CancelOrder, ModifyOrder, SubmitOrder, TradingCommand},
50 msgbus::{self, MessagingSwitchboard, TypedHandler},
51 timer::TimeEvent,
52};
53use nautilus_core::{UUID4, UnixNanos};
54use nautilus_model::{
55 enums::{OrderStatus, TimeInForce, TriggerType},
56 events::{
57 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated,
58 OrderEventAny, OrderExpired, OrderFilled, OrderInitialized, OrderModifyRejected,
59 OrderPendingCancel, OrderPendingUpdate, OrderRejected, OrderReleased, OrderSubmitted,
60 OrderTriggered, OrderUpdated, PositionChanged, PositionClosed, PositionEvent,
61 PositionOpened,
62 },
63 identifiers::{ClientId, ExecAlgorithmId, PositionId, StrategyId},
64 orders::{LimitOrder, MarketOrder, MarketToLimitOrder, Order, OrderAny, OrderList},
65 types::{Price, Quantity},
66};
67pub use twap::{TwapAlgorithm, TwapAlgorithmConfig};
68use ustr::Ustr;
69
70pub trait ExecutionAlgorithm: DataActor {
89 fn core_mut(&mut self) -> &mut ExecutionAlgorithmCore;
91
92 fn id(&mut self) -> ExecAlgorithmId {
94 self.core_mut().exec_algorithm_id
95 }
96
97 fn execute(&mut self, command: TradingCommand) -> anyhow::Result<()>
108 where
109 Self: 'static + std::fmt::Debug + Sized,
110 {
111 let core = self.core_mut();
112 if core.config.log_commands {
113 let id = &core.actor.actor_id;
114 log::info!("{id} {RECV}{CMD} {command:?}");
115 }
116
117 if core.state() != ComponentState::Running {
118 return Ok(());
119 }
120
121 match command {
122 TradingCommand::SubmitOrder(cmd) => {
123 self.subscribe_to_strategy_events(cmd.strategy_id);
124 let order = self.core_mut().get_order(&cmd.client_order_id)?;
125 self.on_order(order)
126 }
127 TradingCommand::SubmitOrderList(cmd) => {
128 self.subscribe_to_strategy_events(cmd.strategy_id);
129 let orders = self.core_mut().get_orders_for_list(&cmd.order_list)?;
130 self.on_order_list(cmd.order_list, orders)
131 }
132 TradingCommand::CancelOrder(cmd) => self.handle_cancel_order(cmd),
133 _ => {
134 log::warn!("Unhandled command type: {command:?}");
135 Ok(())
136 }
137 }
138 }
139
140 fn on_order(&mut self, order: OrderAny) -> anyhow::Result<()>;
148
149 fn on_order_list(
158 &mut self,
159 _order_list: OrderList,
160 orders: Vec<OrderAny>,
161 ) -> anyhow::Result<()> {
162 for order in orders {
163 self.on_order(order)?;
164 }
165 Ok(())
166 }
167
168 fn handle_cancel_order(&mut self, command: CancelOrder) -> anyhow::Result<()> {
177 let (mut order, is_pending_cancel) = {
178 let cache = self.core_mut().cache();
179
180 let Some(order) = cache.order(&command.client_order_id) else {
181 log::warn!(
182 "Cannot cancel order: {} not found in cache",
183 command.client_order_id
184 );
185 return Ok(());
186 };
187
188 let is_pending = cache.is_order_pending_cancel_local(&command.client_order_id);
189 (order.clone(), is_pending)
190 };
191
192 if is_pending_cancel {
193 return Ok(());
194 }
195
196 if order.is_closed() {
197 log::warn!("Order already closed for {command:?}");
198 return Ok(());
199 }
200
201 let event = self.generate_order_canceled(&order);
202
203 if let Err(e) = order.apply(OrderEventAny::Canceled(event)) {
204 log::warn!("InvalidStateTrigger: {e}, did not apply cancel event");
205 return Ok(());
206 }
207
208 {
209 let cache_rc = self.core_mut().cache_rc();
210 let mut cache = cache_rc.borrow_mut();
211 cache.update_order(&order)?;
212 }
213
214 let topic = format!("events.order.{}", order.strategy_id());
215 msgbus::publish_order_event(topic.into(), &OrderEventAny::Canceled(event));
216
217 Ok(())
218 }
219
220 fn generate_order_canceled(&mut self, order: &OrderAny) -> OrderCanceled {
222 let ts_now = self.core_mut().clock().timestamp_ns();
223
224 OrderCanceled::new(
225 order.trader_id(),
226 order.strategy_id(),
227 order.instrument_id(),
228 order.client_order_id(),
229 UUID4::new(),
230 ts_now,
231 ts_now,
232 false, order.venue_order_id(),
234 order.account_id(),
235 )
236 }
237
238 fn generate_order_pending_update(&mut self, order: &OrderAny) -> OrderPendingUpdate {
240 let ts_now = self.core_mut().clock().timestamp_ns();
241
242 OrderPendingUpdate::new(
243 order.trader_id(),
244 order.strategy_id(),
245 order.instrument_id(),
246 order.client_order_id(),
247 order
248 .account_id()
249 .expect("Order must have account_id for pending update"),
250 UUID4::new(),
251 ts_now,
252 ts_now,
253 false, order.venue_order_id(),
255 )
256 }
257
258 fn generate_order_pending_cancel(&mut self, order: &OrderAny) -> OrderPendingCancel {
260 let ts_now = self.core_mut().clock().timestamp_ns();
261
262 OrderPendingCancel::new(
263 order.trader_id(),
264 order.strategy_id(),
265 order.instrument_id(),
266 order.client_order_id(),
267 order
268 .account_id()
269 .expect("Order must have account_id for pending cancel"),
270 UUID4::new(),
271 ts_now,
272 ts_now,
273 false, order.venue_order_id(),
275 )
276 }
277
278 fn spawn_market(
291 &mut self,
292 primary: &mut OrderAny,
293 quantity: Quantity,
294 time_in_force: TimeInForce,
295 reduce_only: bool,
296 tags: Option<Vec<Ustr>>,
297 reduce_primary: bool,
298 ) -> MarketOrder {
299 let core = self.core_mut();
301 let client_order_id = core.spawn_client_order_id(&primary.client_order_id());
302 let ts_init = core.clock().timestamp_ns();
303 let exec_algorithm_id = core.exec_algorithm_id;
304
305 if reduce_primary {
306 self.reduce_primary_order(primary, quantity);
307 self.core_mut()
308 .track_pending_spawn_reduction(client_order_id, quantity);
309 }
310
311 MarketOrder::new(
312 primary.trader_id(),
313 primary.strategy_id(),
314 primary.instrument_id(),
315 client_order_id,
316 primary.order_side(),
317 quantity,
318 time_in_force,
319 UUID4::new(),
320 ts_init,
321 reduce_only,
322 primary.is_quote_quantity(),
323 primary.contingency_type(),
324 primary.order_list_id(),
325 primary.linked_order_ids().map(|ids| ids.to_vec()),
326 primary.parent_order_id(),
327 Some(exec_algorithm_id),
328 primary.exec_algorithm_params().cloned(),
329 Some(primary.client_order_id()),
330 tags.or_else(|| primary.tags().map(|t| t.to_vec())),
331 )
332 }
333
334 #[expect(clippy::too_many_arguments)]
347 fn spawn_limit(
348 &mut self,
349 primary: &mut OrderAny,
350 quantity: Quantity,
351 price: Price,
352 time_in_force: TimeInForce,
353 expire_time: Option<UnixNanos>,
354 post_only: bool,
355 reduce_only: bool,
356 display_qty: Option<Quantity>,
357 emulation_trigger: Option<TriggerType>,
358 tags: Option<Vec<Ustr>>,
359 reduce_primary: bool,
360 ) -> LimitOrder {
361 let core = self.core_mut();
363 let client_order_id = core.spawn_client_order_id(&primary.client_order_id());
364 let ts_init = core.clock().timestamp_ns();
365 let exec_algorithm_id = core.exec_algorithm_id;
366
367 if reduce_primary {
368 self.reduce_primary_order(primary, quantity);
369 self.core_mut()
370 .track_pending_spawn_reduction(client_order_id, quantity);
371 }
372
373 LimitOrder::new(
374 primary.trader_id(),
375 primary.strategy_id(),
376 primary.instrument_id(),
377 client_order_id,
378 primary.order_side(),
379 quantity,
380 price,
381 time_in_force,
382 expire_time,
383 post_only,
384 reduce_only,
385 primary.is_quote_quantity(),
386 display_qty,
387 emulation_trigger,
388 None, primary.contingency_type(),
390 primary.order_list_id(),
391 primary.linked_order_ids().map(|ids| ids.to_vec()),
392 primary.parent_order_id(),
393 Some(exec_algorithm_id),
394 primary.exec_algorithm_params().cloned(),
395 Some(primary.client_order_id()),
396 tags.or_else(|| primary.tags().map(|t| t.to_vec())),
397 UUID4::new(),
398 ts_init,
399 )
400 }
401
402 #[expect(clippy::too_many_arguments)]
415 fn spawn_market_to_limit(
416 &mut self,
417 primary: &mut OrderAny,
418 quantity: Quantity,
419 time_in_force: TimeInForce,
420 expire_time: Option<UnixNanos>,
421 reduce_only: bool,
422 display_qty: Option<Quantity>,
423 emulation_trigger: Option<TriggerType>,
424 tags: Option<Vec<Ustr>>,
425 reduce_primary: bool,
426 ) -> MarketToLimitOrder {
427 let core = self.core_mut();
429 let client_order_id = core.spawn_client_order_id(&primary.client_order_id());
430 let ts_init = core.clock().timestamp_ns();
431 let exec_algorithm_id = core.exec_algorithm_id;
432
433 if reduce_primary {
434 self.reduce_primary_order(primary, quantity);
435 self.core_mut()
436 .track_pending_spawn_reduction(client_order_id, quantity);
437 }
438
439 let mut order = MarketToLimitOrder::new(
440 primary.trader_id(),
441 primary.strategy_id(),
442 primary.instrument_id(),
443 client_order_id,
444 primary.order_side(),
445 quantity,
446 time_in_force,
447 expire_time,
448 false, reduce_only,
450 primary.is_quote_quantity(),
451 display_qty,
452 primary.contingency_type(),
453 primary.order_list_id(),
454 primary.linked_order_ids().map(|ids| ids.to_vec()),
455 primary.parent_order_id(),
456 Some(exec_algorithm_id),
457 primary.exec_algorithm_params().cloned(),
458 Some(primary.client_order_id()),
459 tags.or_else(|| primary.tags().map(|t| t.to_vec())),
460 UUID4::new(),
461 ts_init,
462 );
463
464 if emulation_trigger.is_some() {
465 order.set_emulation_trigger(emulation_trigger);
466 }
467
468 order
469 }
470
471 fn reduce_primary_order(&mut self, primary: &mut OrderAny, spawn_qty: Quantity) {
480 let leaves_qty = primary.leaves_qty();
481 assert!(
482 leaves_qty >= spawn_qty,
483 "Spawn quantity {spawn_qty} exceeds primary leaves_qty {leaves_qty}"
484 );
485
486 let primary_qty = primary.quantity();
487 let new_qty = Quantity::from_raw(primary_qty.raw - spawn_qty.raw, primary_qty.precision);
488
489 let core = self.core_mut();
490 let ts_now = core.clock().timestamp_ns();
491
492 let updated = OrderUpdated::new(
493 primary.trader_id(),
494 primary.strategy_id(),
495 primary.instrument_id(),
496 primary.client_order_id(),
497 new_qty,
498 UUID4::new(),
499 ts_now,
500 ts_now,
501 false, primary.venue_order_id(),
503 primary.account_id(),
504 None, None, None, primary.is_quote_quantity(),
508 );
509
510 primary
511 .apply(OrderEventAny::Updated(updated))
512 .expect("Failed to apply OrderUpdated");
513
514 let cache_rc = core.cache_rc();
515 let mut cache = cache_rc.borrow_mut();
516 cache
517 .update_order(primary)
518 .expect("Failed to update order in cache");
519 }
520
521 fn restore_primary_order_quantity(&mut self, order: &OrderAny) {
527 let Some(exec_spawn_id) = order.exec_spawn_id() else {
528 return;
529 };
530
531 let reduction_qty = {
532 let core = self.core_mut();
533 core.take_pending_spawn_reduction(&order.client_order_id())
534 };
535
536 let Some(reduction_qty) = reduction_qty else {
537 return;
538 };
539
540 let primary = {
541 let cache = self.core_mut().cache();
542 cache.order(&exec_spawn_id).cloned()
543 };
544
545 let Some(mut primary) = primary else {
546 log::warn!(
547 "Cannot restore primary order quantity: primary order {exec_spawn_id} not found",
548 );
549 return;
550 };
551
552 let restore_raw = std::cmp::min(reduction_qty.raw, order.leaves_qty().raw);
554 if restore_raw == 0 {
555 return;
556 }
557
558 let restored_qty = Quantity::from_raw(
559 primary.quantity().raw + restore_raw,
560 primary.quantity().precision,
561 );
562
563 let core = self.core_mut();
564 let ts_now = core.clock().timestamp_ns();
565
566 let updated = OrderUpdated::new(
567 primary.trader_id(),
568 primary.strategy_id(),
569 primary.instrument_id(),
570 primary.client_order_id(),
571 restored_qty,
572 UUID4::new(),
573 ts_now,
574 ts_now,
575 false, primary.venue_order_id(),
577 primary.account_id(),
578 None, None, None, primary.is_quote_quantity(),
582 );
583
584 if let Err(e) = primary.apply(OrderEventAny::Updated(updated)) {
585 log::warn!("Failed to apply OrderUpdated for quantity restoration: {e}");
586 return;
587 }
588
589 {
590 let cache_rc = core.cache_rc();
591 let mut cache = cache_rc.borrow_mut();
592 if let Err(e) = cache.update_order(&primary) {
593 log::warn!("Failed to update primary order in cache: {e}");
594 return;
595 }
596 }
597
598 log::info!(
599 "Restored primary order {} quantity to {} after spawned order {} was denied/rejected",
600 primary.client_order_id(),
601 restored_qty,
602 order.client_order_id()
603 );
604 }
605
606 fn submit_order(
612 &mut self,
613 order: OrderAny,
614 position_id: Option<PositionId>,
615 client_id: Option<ClientId>,
616 ) -> anyhow::Result<()> {
617 let core = self.core_mut();
618
619 let trader_id = core.trader_id().expect("Trader ID not set");
620 let ts_init = core.clock().timestamp_ns();
621
622 let strategy_id = order.strategy_id();
624
625 {
626 let cache_rc = core.cache_rc();
627 let mut cache = cache_rc.borrow_mut();
628 cache.add_order(order.clone(), position_id, client_id, true)?;
629 }
630
631 let command = SubmitOrder::new(
632 trader_id,
633 client_id,
634 strategy_id,
635 order.instrument_id(),
636 order.client_order_id(),
637 order.init_event().clone(),
638 order.exec_algorithm_id(),
639 position_id,
640 None, UUID4::new(),
642 ts_init,
643 );
644
645 if core.config.log_commands {
646 let id = &core.actor.actor_id;
647 log::info!("{id} {SEND}{CMD} {command:?}");
648 }
649
650 msgbus::send_trading_command(
651 MessagingSwitchboard::risk_engine_execute(),
652 TradingCommand::SubmitOrder(command),
653 );
654
655 Ok(())
656 }
657
658 fn modify_order(
664 &mut self,
665 order: &mut OrderAny,
666 quantity: Option<Quantity>,
667 price: Option<Price>,
668 trigger_price: Option<Price>,
669 client_id: Option<ClientId>,
670 ) -> anyhow::Result<()> {
671 let qty_changing = quantity.is_some_and(|q| q != order.quantity());
672 let price_changing = price.is_some() && price != order.price();
673 let trigger_changing = trigger_price.is_some() && trigger_price != order.trigger_price();
674
675 if !qty_changing && !price_changing && !trigger_changing {
676 log::error!(
677 "Cannot create command ModifyOrder: \
678 quantity, price and trigger were either None \
679 or the same as existing values"
680 );
681 return Ok(());
682 }
683
684 if order.is_closed() || order.is_pending_cancel() {
685 log::warn!(
686 "Cannot create command ModifyOrder: state is {:?}, {order:?}",
687 order.status()
688 );
689 return Ok(());
690 }
691
692 let core = self.core_mut();
693 let trader_id = core.trader_id().expect("Trader ID not set");
694 let strategy_id = order.strategy_id();
695
696 if !order.is_active_local() {
697 let event = self.generate_order_pending_update(order);
698 if let Err(e) = order.apply(OrderEventAny::PendingUpdate(event)) {
699 log::warn!("InvalidStateTrigger: {e}, did not apply pending update event");
700 return Ok(());
701 }
702
703 {
704 let cache_rc = self.core_mut().cache_rc();
705 let mut cache = cache_rc.borrow_mut();
706 cache.update_order(order).ok();
707 }
708
709 let topic = format!("events.order.{strategy_id}");
710 msgbus::publish_order_event(topic.into(), &OrderEventAny::PendingUpdate(event));
711 }
712
713 let ts_init = self.core_mut().clock().timestamp_ns();
714 let command = ModifyOrder::new(
715 trader_id,
716 client_id,
717 strategy_id,
718 order.instrument_id(),
719 order.client_order_id(),
720 order.venue_order_id(),
721 quantity,
722 price,
723 trigger_price,
724 UUID4::new(),
725 ts_init,
726 None, );
728
729 if self.core_mut().config.log_commands {
730 let id = &self.core_mut().actor.actor_id;
731 log::info!("{id} {SEND}{CMD} {command:?}");
732 }
733
734 let has_emulation_trigger = order
735 .emulation_trigger()
736 .is_some_and(|t| t != TriggerType::NoTrigger);
737
738 if order.is_emulated() || has_emulation_trigger {
739 msgbus::send_trading_command(
740 MessagingSwitchboard::order_emulator_execute(),
741 TradingCommand::ModifyOrder(command),
742 );
743 } else {
744 msgbus::send_trading_command(
745 MessagingSwitchboard::risk_engine_execute(),
746 TradingCommand::ModifyOrder(command),
747 );
748 }
749
750 Ok(())
751 }
752
753 fn modify_order_in_place(
765 &mut self,
766 order: &mut OrderAny,
767 quantity: Option<Quantity>,
768 price: Option<Price>,
769 trigger_price: Option<Price>,
770 ) -> anyhow::Result<()> {
771 let status = order.status();
773 if status != OrderStatus::Initialized && status != OrderStatus::Released {
774 anyhow::bail!(
775 "Cannot modify order in place: status is {status:?}, expected INITIALIZED or RELEASED"
776 );
777 }
778
779 if price.is_some() && order.price().is_none() {
781 anyhow::bail!(
782 "Cannot modify order in place: {} orders do not have a LIMIT price",
783 order.order_type()
784 );
785 }
786
787 if trigger_price.is_some() && order.trigger_price().is_none() {
788 anyhow::bail!(
789 "Cannot modify order in place: {} orders do not have a STOP trigger price",
790 order.order_type()
791 );
792 }
793
794 let qty_changing = quantity.is_some_and(|q| q != order.quantity());
796 let price_changing = price.is_some() && price != order.price();
797 let trigger_changing = trigger_price.is_some() && trigger_price != order.trigger_price();
798
799 if !qty_changing && !price_changing && !trigger_changing {
800 anyhow::bail!("Cannot modify order in place: no parameters differ from current values");
801 }
802
803 let core = self.core_mut();
804 let ts_now = core.clock().timestamp_ns();
805
806 let updated = OrderUpdated::new(
807 order.trader_id(),
808 order.strategy_id(),
809 order.instrument_id(),
810 order.client_order_id(),
811 quantity.unwrap_or_else(|| order.quantity()),
812 UUID4::new(),
813 ts_now,
814 ts_now,
815 false, order.venue_order_id(),
817 order.account_id(),
818 price,
819 trigger_price,
820 None, order.is_quote_quantity(),
822 );
823
824 order
825 .apply(OrderEventAny::Updated(updated))
826 .map_err(|e| anyhow::anyhow!("Failed to apply OrderUpdated: {e}"))?;
827
828 let cache_rc = core.cache_rc();
829 let mut cache = cache_rc.borrow_mut();
830 cache.update_order(order)?;
831
832 Ok(())
833 }
834
835 fn cancel_order(
841 &mut self,
842 order: &mut OrderAny,
843 client_id: Option<ClientId>,
844 ) -> anyhow::Result<()> {
845 if order.is_closed() || order.is_pending_cancel() {
846 log::warn!(
847 "Cannot cancel order: state is {:?}, {order:?}",
848 order.status()
849 );
850 return Ok(());
851 }
852
853 let core = self.core_mut();
854 let trader_id = core.trader_id().expect("Trader ID not set");
855 let strategy_id = order.strategy_id();
856
857 if !order.is_active_local() {
858 let event = self.generate_order_pending_cancel(order);
859 if let Err(e) = order.apply(OrderEventAny::PendingCancel(event)) {
860 log::warn!("InvalidStateTrigger: {e}, did not apply pending cancel event");
861 return Ok(());
862 }
863
864 {
865 let cache_rc = self.core_mut().cache_rc();
866 let mut cache = cache_rc.borrow_mut();
867 cache.update_order(order).ok();
868 }
869
870 let topic = format!("events.order.{strategy_id}");
871 msgbus::publish_order_event(topic.into(), &OrderEventAny::PendingCancel(event));
872 }
873
874 let ts_init = self.core_mut().clock().timestamp_ns();
875 let command = CancelOrder::new(
876 trader_id,
877 client_id,
878 strategy_id,
879 order.instrument_id(),
880 order.client_order_id(),
881 order.venue_order_id(),
882 UUID4::new(),
883 ts_init,
884 None, );
886
887 if self.core_mut().config.log_commands {
888 let id = &self.core_mut().actor.actor_id;
889 log::info!("{id} {SEND}{CMD} {command:?}");
890 }
891
892 let has_emulation_trigger = order
893 .emulation_trigger()
894 .is_some_and(|t| t != TriggerType::NoTrigger);
895
896 if order.is_emulated() || order.status() == OrderStatus::Released || has_emulation_trigger {
897 msgbus::send_trading_command(
898 MessagingSwitchboard::order_emulator_execute(),
899 TradingCommand::CancelOrder(command),
900 );
901 } else {
902 msgbus::send_trading_command(
903 MessagingSwitchboard::exec_engine_execute(),
904 TradingCommand::CancelOrder(command),
905 );
906 }
907
908 Ok(())
909 }
910
911 fn subscribe_to_strategy_events(&mut self, strategy_id: StrategyId)
915 where
916 Self: 'static + std::fmt::Debug + Sized,
917 {
918 let core = self.core_mut();
919 if core.is_strategy_subscribed(&strategy_id) {
920 return;
921 }
922
923 let actor_id = core.actor.actor_id.inner();
924
925 let order_topic = format!("events.order.{strategy_id}");
926 let order_actor_id = actor_id;
927 let order_handler = TypedHandler::from(move |event: &OrderEventAny| {
928 if let Some(mut algo) = try_get_actor_unchecked::<Self>(&order_actor_id) {
929 algo.handle_order_event(event.clone());
930 } else {
931 log::error!(
932 "ExecutionAlgorithm {order_actor_id} not found for order event handling"
933 );
934 }
935 });
936 msgbus::subscribe_order_events(order_topic.clone().into(), order_handler.clone(), None);
937
938 let position_topic = format!("events.position.{strategy_id}");
939 let position_handler = TypedHandler::from(move |event: &PositionEvent| {
940 if let Some(mut algo) = try_get_actor_unchecked::<Self>(&actor_id) {
941 algo.handle_position_event(event.clone());
942 } else {
943 log::error!("ExecutionAlgorithm {actor_id} not found for position event handling");
944 }
945 });
946 msgbus::subscribe_position_events(
947 position_topic.clone().into(),
948 position_handler.clone(),
949 None,
950 );
951
952 let handlers = StrategyEventHandlers {
953 order_topic,
954 order_handler,
955 position_topic,
956 position_handler,
957 };
958 core.store_strategy_event_handlers(strategy_id, handlers);
959
960 core.add_subscribed_strategy(strategy_id);
961 log::info!("Subscribed to events for strategy {strategy_id}");
962 }
963
964 fn unsubscribe_all_strategy_events(&mut self) {
968 let handlers = self.core_mut().take_strategy_event_handlers();
969 for (strategy_id, h) in handlers {
970 msgbus::unsubscribe_order_events(h.order_topic.into(), &h.order_handler);
971 msgbus::unsubscribe_position_events(h.position_topic.into(), &h.position_handler);
972 log::info!("Unsubscribed from events for strategy {strategy_id}");
973 }
974 self.core_mut().clear_subscribed_strategies();
975 }
976
977 fn handle_order_event(&mut self, event: OrderEventAny) {
979 if self.core_mut().state() != ComponentState::Running {
980 return;
981 }
982
983 let order = {
984 let cache = self.core_mut().cache();
985 cache.order(&event.client_order_id()).cloned()
986 };
987
988 let Some(order) = order else {
989 return;
990 };
991
992 let Some(order_algo_id) = order.exec_algorithm_id() else {
993 return;
994 };
995
996 if order_algo_id != self.id() {
997 return;
998 }
999
1000 {
1001 let core = self.core_mut();
1002 if core.config.log_events {
1003 let id = &core.actor.actor_id;
1004 log::info!("{id} {RECV}{EVT} {event}");
1005 }
1006 }
1007
1008 match &event {
1009 OrderEventAny::Initialized(e) => self.on_order_initialized(e.clone()),
1010 OrderEventAny::Denied(e) => {
1011 self.restore_primary_order_quantity(&order);
1012 self.on_order_denied(*e);
1013 }
1014 OrderEventAny::Emulated(e) => self.on_order_emulated(*e),
1015 OrderEventAny::Released(e) => self.on_order_released(*e),
1016 OrderEventAny::Submitted(e) => self.on_order_submitted(*e),
1017 OrderEventAny::Rejected(e) => {
1018 self.restore_primary_order_quantity(&order);
1019 self.on_order_rejected(*e);
1020 }
1021 OrderEventAny::Accepted(e) => {
1022 self.core_mut()
1024 .take_pending_spawn_reduction(&order.client_order_id());
1025 self.on_order_accepted(*e);
1026 }
1027 OrderEventAny::Canceled(e) => {
1028 self.core_mut()
1029 .take_pending_spawn_reduction(&order.client_order_id());
1030 self.on_algo_order_canceled(*e);
1031 }
1032 OrderEventAny::Expired(e) => {
1033 self.core_mut()
1034 .take_pending_spawn_reduction(&order.client_order_id());
1035 self.on_order_expired(*e);
1036 }
1037 OrderEventAny::Triggered(e) => self.on_order_triggered(*e),
1038 OrderEventAny::PendingUpdate(e) => self.on_order_pending_update(*e),
1039 OrderEventAny::PendingCancel(e) => self.on_order_pending_cancel(*e),
1040 OrderEventAny::ModifyRejected(e) => self.on_order_modify_rejected(*e),
1041 OrderEventAny::CancelRejected(e) => self.on_order_cancel_rejected(*e),
1042 OrderEventAny::Updated(e) => self.on_order_updated(*e),
1043 OrderEventAny::Filled(e) => self.on_algo_order_filled(*e),
1044 }
1045
1046 self.on_order_event(event);
1047 }
1048
1049 fn handle_position_event(&mut self, event: PositionEvent) {
1051 if self.core_mut().state() != ComponentState::Running {
1052 return;
1053 }
1054
1055 {
1056 let core = self.core_mut();
1057 if core.config.log_events {
1058 let id = &core.actor.actor_id;
1059 log::info!("{id} {RECV}{EVT} {event:?}");
1060 }
1061 }
1062
1063 match &event {
1064 PositionEvent::PositionOpened(e) => self.on_position_opened(e.clone()),
1065 PositionEvent::PositionChanged(e) => self.on_position_changed(e.clone()),
1066 PositionEvent::PositionClosed(e) => self.on_position_closed(e.clone()),
1067 PositionEvent::PositionAdjusted(_) => {}
1068 }
1069
1070 self.on_position_event(event);
1071 }
1072
1073 fn on_start(&mut self) -> anyhow::Result<()> {
1081 let id = self.id();
1082 log::info!("Starting {id}");
1083 Ok(())
1084 }
1085
1086 fn on_stop(&mut self) -> anyhow::Result<()> {
1092 Ok(())
1093 }
1094
1095 fn on_reset(&mut self) -> anyhow::Result<()> {
1101 self.unsubscribe_all_strategy_events();
1102 self.core_mut().reset();
1103 Ok(())
1104 }
1105
1106 fn on_time_event(&mut self, _event: &TimeEvent) -> anyhow::Result<()> {
1114 Ok(())
1115 }
1116
1117 #[allow(unused_variables)]
1119 fn on_order_initialized(&mut self, event: OrderInitialized) {}
1120
1121 #[allow(unused_variables)]
1123 fn on_order_denied(&mut self, event: OrderDenied) {}
1124
1125 #[allow(unused_variables)]
1127 fn on_order_emulated(&mut self, event: OrderEmulated) {}
1128
1129 #[allow(unused_variables)]
1131 fn on_order_released(&mut self, event: OrderReleased) {}
1132
1133 #[allow(unused_variables)]
1135 fn on_order_submitted(&mut self, event: OrderSubmitted) {}
1136
1137 #[allow(unused_variables)]
1139 fn on_order_rejected(&mut self, event: OrderRejected) {}
1140
1141 #[allow(unused_variables)]
1143 fn on_order_accepted(&mut self, event: OrderAccepted) {}
1144
1145 #[allow(unused_variables)]
1147 fn on_algo_order_canceled(&mut self, event: OrderCanceled) {}
1148
1149 #[allow(unused_variables)]
1151 fn on_order_expired(&mut self, event: OrderExpired) {}
1152
1153 #[allow(unused_variables)]
1155 fn on_order_triggered(&mut self, event: OrderTriggered) {}
1156
1157 #[allow(unused_variables)]
1159 fn on_order_pending_update(&mut self, event: OrderPendingUpdate) {}
1160
1161 #[allow(unused_variables)]
1163 fn on_order_pending_cancel(&mut self, event: OrderPendingCancel) {}
1164
1165 #[allow(unused_variables)]
1167 fn on_order_modify_rejected(&mut self, event: OrderModifyRejected) {}
1168
1169 #[allow(unused_variables)]
1171 fn on_order_cancel_rejected(&mut self, event: OrderCancelRejected) {}
1172
1173 #[allow(unused_variables)]
1175 fn on_order_updated(&mut self, event: OrderUpdated) {}
1176
1177 #[allow(unused_variables)]
1179 fn on_algo_order_filled(&mut self, event: OrderFilled) {}
1180
1181 #[allow(unused_variables)]
1183 fn on_order_event(&mut self, event: OrderEventAny) {}
1184
1185 #[allow(unused_variables)]
1187 fn on_position_opened(&mut self, event: PositionOpened) {}
1188
1189 #[allow(unused_variables)]
1191 fn on_position_changed(&mut self, event: PositionChanged) {}
1192
1193 #[allow(unused_variables)]
1195 fn on_position_closed(&mut self, event: PositionClosed) {}
1196
1197 #[allow(unused_variables)]
1199 fn on_position_event(&mut self, event: PositionEvent) {}
1200}
1201
1202#[cfg(test)]
1203mod tests {
1204 use std::{cell::RefCell, rc::Rc};
1205
1206 use nautilus_common::{
1207 actor::DataActor, cache::Cache, clock::TestClock, component::Component,
1208 enums::ComponentTrigger, nautilus_actor,
1209 };
1210 use nautilus_model::{
1211 enums::OrderSide,
1212 events::{
1213 OrderAccepted, OrderCanceled, OrderDenied, OrderRejected, order::spec::OrderFilledSpec,
1214 },
1215 identifiers::{
1216 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, StrategyId, TraderId,
1217 VenueOrderId,
1218 },
1219 orders::{LimitOrder, MarketOrder, OrderAny, stubs::TestOrderStubs},
1220 types::{Price, Quantity},
1221 };
1222 use rstest::rstest;
1223
1224 use super::*;
1225
1226 #[derive(Debug)]
1227 struct TestAlgorithm {
1228 core: ExecutionAlgorithmCore,
1229 on_order_called: bool,
1230 last_order_client_id: Option<ClientOrderId>,
1231 }
1232
1233 impl TestAlgorithm {
1234 fn new(config: ExecutionAlgorithmConfig) -> Self {
1235 Self {
1236 core: ExecutionAlgorithmCore::new(config),
1237 on_order_called: false,
1238 last_order_client_id: None,
1239 }
1240 }
1241 }
1242
1243 impl DataActor for TestAlgorithm {}
1244
1245 nautilus_actor!(TestAlgorithm);
1246
1247 impl ExecutionAlgorithm for TestAlgorithm {
1248 fn core_mut(&mut self) -> &mut ExecutionAlgorithmCore {
1249 &mut self.core
1250 }
1251
1252 fn on_order(&mut self, order: OrderAny) -> anyhow::Result<()> {
1253 self.on_order_called = true;
1254 self.last_order_client_id = Some(order.client_order_id());
1255 Ok(())
1256 }
1257 }
1258
1259 fn create_test_algorithm() -> TestAlgorithm {
1260 let unique_id = format!("TEST-{}", UUID4::new());
1262 let config = ExecutionAlgorithmConfig {
1263 exec_algorithm_id: Some(ExecAlgorithmId::new(&unique_id)),
1264 ..Default::default()
1265 };
1266 TestAlgorithm::new(config)
1267 }
1268
1269 fn register_algorithm(algo: &mut TestAlgorithm) {
1270 let trader_id = TraderId::from("TRADER-001");
1271 let clock = Rc::new(RefCell::new(TestClock::new()));
1272 let cache = Rc::new(RefCell::new(Cache::default()));
1273
1274 algo.core.register(trader_id, clock, cache).unwrap();
1275
1276 algo.transition_state(ComponentTrigger::Initialize).unwrap();
1278 algo.transition_state(ComponentTrigger::Start).unwrap();
1279 algo.transition_state(ComponentTrigger::StartCompleted)
1280 .unwrap();
1281 }
1282
1283 #[rstest]
1284 fn test_algorithm_creation() {
1285 let algo = create_test_algorithm();
1286 assert!(algo.core.exec_algorithm_id.inner().starts_with("TEST-"));
1287 assert!(!algo.on_order_called);
1288 assert!(algo.last_order_client_id.is_none());
1289 }
1290
1291 #[rstest]
1292 fn test_algorithm_registration() {
1293 let mut algo = create_test_algorithm();
1294 register_algorithm(&mut algo);
1295
1296 assert!(algo.core.trader_id().is_some());
1297 assert_eq!(algo.core.trader_id(), Some(TraderId::from("TRADER-001")));
1298 }
1299
1300 #[rstest]
1301 fn test_algorithm_id() {
1302 let mut algo = create_test_algorithm();
1303 assert!(algo.id().inner().starts_with("TEST-"));
1304 }
1305
1306 #[rstest]
1307 fn test_algorithm_spawn_market_creates_valid_order() {
1308 let mut algo = create_test_algorithm();
1309 register_algorithm(&mut algo);
1310
1311 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1312 let mut primary = OrderAny::Market(MarketOrder::new(
1313 TraderId::from("TRADER-001"),
1314 StrategyId::from("STRAT-001"),
1315 instrument_id,
1316 ClientOrderId::from("O-001"),
1317 OrderSide::Buy,
1318 Quantity::from("1.0"),
1319 TimeInForce::Gtc,
1320 UUID4::new(),
1321 0.into(),
1322 false, false, None, None, None, None, None, None, None, None, ));
1333
1334 let spawned = algo.spawn_market(
1335 &mut primary,
1336 Quantity::from("0.5"),
1337 TimeInForce::Ioc,
1338 false,
1339 None, false, );
1342
1343 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1344 assert_eq!(spawned.instrument_id, instrument_id);
1345 assert_eq!(spawned.order_side(), OrderSide::Buy);
1346 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1347 assert_eq!(spawned.time_in_force, TimeInForce::Ioc);
1348 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1349 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1350 }
1351
1352 #[rstest]
1353 fn test_algorithm_spawn_increments_sequence() {
1354 let mut algo = create_test_algorithm();
1355 register_algorithm(&mut algo);
1356
1357 let mut primary = OrderAny::Market(MarketOrder::new(
1358 TraderId::from("TRADER-001"),
1359 StrategyId::from("STRAT-001"),
1360 InstrumentId::from("BTC/USDT.BINANCE"),
1361 ClientOrderId::from("O-001"),
1362 OrderSide::Buy,
1363 Quantity::from("1.0"),
1364 TimeInForce::Gtc,
1365 UUID4::new(),
1366 0.into(),
1367 false,
1368 false,
1369 None,
1370 None,
1371 None,
1372 None,
1373 None,
1374 None,
1375 None,
1376 None,
1377 ));
1378
1379 let spawned1 = algo.spawn_market(
1380 &mut primary,
1381 Quantity::from("0.25"),
1382 TimeInForce::Ioc,
1383 false,
1384 None,
1385 false,
1386 );
1387 let spawned2 = algo.spawn_market(
1388 &mut primary,
1389 Quantity::from("0.25"),
1390 TimeInForce::Ioc,
1391 false,
1392 None,
1393 false,
1394 );
1395 let spawned3 = algo.spawn_market(
1396 &mut primary,
1397 Quantity::from("0.25"),
1398 TimeInForce::Ioc,
1399 false,
1400 None,
1401 false,
1402 );
1403
1404 assert_eq!(spawned1.client_order_id.as_str(), "O-001-E1");
1405 assert_eq!(spawned2.client_order_id.as_str(), "O-001-E2");
1406 assert_eq!(spawned3.client_order_id.as_str(), "O-001-E3");
1407 }
1408
1409 #[rstest]
1410 fn test_algorithm_default_handlers_do_not_panic() {
1411 let mut algo = create_test_algorithm();
1412
1413 algo.on_order_initialized(OrderInitialized::default());
1414 algo.on_order_denied(OrderDenied::default());
1415 algo.on_order_emulated(OrderEmulated::default());
1416 algo.on_order_released(OrderReleased::default());
1417 algo.on_order_submitted(OrderSubmitted::default());
1418 algo.on_order_rejected(OrderRejected::default());
1419 algo.on_order_accepted(OrderAccepted::default());
1420 algo.on_algo_order_canceled(OrderCanceled::default());
1421 algo.on_order_expired(OrderExpired::default());
1422 algo.on_order_triggered(OrderTriggered::default());
1423 algo.on_order_pending_update(OrderPendingUpdate::default());
1424 algo.on_order_pending_cancel(OrderPendingCancel::default());
1425 algo.on_order_modify_rejected(OrderModifyRejected::default());
1426 algo.on_order_cancel_rejected(OrderCancelRejected::default());
1427 algo.on_order_updated(OrderUpdated::default());
1428 algo.on_algo_order_filled(OrderFilledSpec::builder().build());
1429 }
1430
1431 #[rstest]
1432 fn test_strategy_subscription_tracking() {
1433 let mut algo = create_test_algorithm();
1434 let strategy_id = StrategyId::from("STRAT-001");
1435
1436 assert!(!algo.core.is_strategy_subscribed(&strategy_id));
1437
1438 algo.subscribe_to_strategy_events(strategy_id);
1439 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1440
1441 algo.subscribe_to_strategy_events(strategy_id);
1443 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1444 }
1445
1446 #[rstest]
1447 fn test_algorithm_reset() {
1448 let mut algo = create_test_algorithm();
1449 let strategy_id = StrategyId::from("STRAT-001");
1450 let primary_id = ClientOrderId::new("O-001");
1451
1452 let _ = algo.core.spawn_client_order_id(&primary_id);
1453 algo.core.add_subscribed_strategy(strategy_id);
1454
1455 assert!(algo.core.spawn_sequence(&primary_id).is_some());
1456 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1457
1458 ExecutionAlgorithm::on_reset(&mut algo).unwrap();
1459
1460 assert!(algo.core.spawn_sequence(&primary_id).is_none());
1461 assert!(!algo.core.is_strategy_subscribed(&strategy_id));
1462 }
1463
1464 #[rstest]
1465 fn test_algorithm_spawn_limit_creates_valid_order() {
1466 let mut algo = create_test_algorithm();
1467 register_algorithm(&mut algo);
1468
1469 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1470 let mut primary = OrderAny::Market(MarketOrder::new(
1471 TraderId::from("TRADER-001"),
1472 StrategyId::from("STRAT-001"),
1473 instrument_id,
1474 ClientOrderId::from("O-001"),
1475 OrderSide::Buy,
1476 Quantity::from("1.0"),
1477 TimeInForce::Gtc,
1478 UUID4::new(),
1479 0.into(),
1480 false,
1481 false,
1482 None,
1483 None,
1484 None,
1485 None,
1486 None,
1487 None,
1488 None,
1489 None,
1490 ));
1491
1492 let price = Price::from("50000.0");
1493 let spawned = algo.spawn_limit(
1494 &mut primary,
1495 Quantity::from("0.5"),
1496 price,
1497 TimeInForce::Gtc,
1498 None, false, false, None, None, None, false, );
1506
1507 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1508 assert_eq!(spawned.instrument_id, instrument_id);
1509 assert_eq!(spawned.order_side(), OrderSide::Buy);
1510 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1511 assert_eq!(spawned.price, price);
1512 assert_eq!(spawned.time_in_force, TimeInForce::Gtc);
1513 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1514 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1515 }
1516
1517 #[rstest]
1518 fn test_algorithm_spawn_market_to_limit_creates_valid_order() {
1519 let mut algo = create_test_algorithm();
1520 register_algorithm(&mut algo);
1521
1522 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1523 let mut primary = OrderAny::Market(MarketOrder::new(
1524 TraderId::from("TRADER-001"),
1525 StrategyId::from("STRAT-001"),
1526 instrument_id,
1527 ClientOrderId::from("O-001"),
1528 OrderSide::Buy,
1529 Quantity::from("1.0"),
1530 TimeInForce::Gtc,
1531 UUID4::new(),
1532 0.into(),
1533 false,
1534 false,
1535 None,
1536 None,
1537 None,
1538 None,
1539 None,
1540 None,
1541 None,
1542 None,
1543 ));
1544
1545 let spawned = algo.spawn_market_to_limit(
1546 &mut primary,
1547 Quantity::from("0.5"),
1548 TimeInForce::Gtc,
1549 None, false, None, None, None, false, );
1556
1557 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1558 assert_eq!(spawned.instrument_id, instrument_id);
1559 assert_eq!(spawned.order_side(), OrderSide::Buy);
1560 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1561 assert_eq!(spawned.time_in_force, TimeInForce::Gtc);
1562 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1563 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1564 }
1565
1566 #[rstest]
1567 fn test_algorithm_spawn_market_with_tags() {
1568 let mut algo = create_test_algorithm();
1569 register_algorithm(&mut algo);
1570
1571 let mut primary = OrderAny::Market(MarketOrder::new(
1572 TraderId::from("TRADER-001"),
1573 StrategyId::from("STRAT-001"),
1574 InstrumentId::from("BTC/USDT.BINANCE"),
1575 ClientOrderId::from("O-001"),
1576 OrderSide::Buy,
1577 Quantity::from("1.0"),
1578 TimeInForce::Gtc,
1579 UUID4::new(),
1580 0.into(),
1581 false,
1582 false,
1583 None,
1584 None,
1585 None,
1586 None,
1587 None,
1588 None,
1589 None,
1590 None,
1591 ));
1592
1593 let tags = vec![ustr::Ustr::from("TAG1"), ustr::Ustr::from("TAG2")];
1594 let spawned = algo.spawn_market(
1595 &mut primary,
1596 Quantity::from("0.5"),
1597 TimeInForce::Ioc,
1598 false,
1599 Some(tags.clone()),
1600 false,
1601 );
1602
1603 assert_eq!(spawned.tags, Some(tags));
1604 }
1605
1606 #[rstest]
1607 fn test_algorithm_spawn_propagates_primary_fields() {
1608 let mut algo = create_test_algorithm();
1609 register_algorithm(&mut algo);
1610
1611 let mut params = indexmap::IndexMap::new();
1612 params.insert(ustr::Ustr::from("horizon_secs"), ustr::Ustr::from("30"));
1613 params.insert(ustr::Ustr::from("interval_secs"), ustr::Ustr::from("10"));
1614 let primary_tags = vec![ustr::Ustr::from("PRIMARY_TAG")];
1615 let linked_order_ids = vec![ClientOrderId::from("LINK-1")];
1616
1617 let mut primary = OrderAny::Market(MarketOrder::new(
1618 TraderId::from("TRADER-001"),
1619 StrategyId::from("STRAT-001"),
1620 InstrumentId::from("BTC/USDT.BINANCE"),
1621 ClientOrderId::from("O-001"),
1622 OrderSide::Buy,
1623 Quantity::from("1.0"),
1624 TimeInForce::Gtc,
1625 UUID4::new(),
1626 0.into(),
1627 false, true, None, None, Some(linked_order_ids.clone()),
1632 None, Some(algo.id()),
1634 Some(params.clone()),
1635 None, Some(primary_tags.clone()),
1637 ));
1638
1639 let spawned_market = algo.spawn_market(
1640 &mut primary,
1641 Quantity::from("0.25"),
1642 TimeInForce::Ioc,
1643 false,
1644 None, false,
1646 );
1647 assert!(spawned_market.is_quote_quantity);
1648 assert_eq!(spawned_market.exec_algorithm_params, Some(params.clone()));
1649 assert_eq!(spawned_market.tags, Some(primary_tags.clone()));
1650 assert_eq!(
1651 spawned_market.linked_order_ids,
1652 Some(linked_order_ids.clone())
1653 );
1654
1655 let spawned_limit = algo.spawn_limit(
1656 &mut primary,
1657 Quantity::from("0.25"),
1658 Price::from("50000.0"),
1659 TimeInForce::Gtc,
1660 None, false, false, None, None, None, false,
1667 );
1668 assert!(spawned_limit.is_quote_quantity);
1669 assert_eq!(spawned_limit.exec_algorithm_params, Some(params.clone()));
1670 assert_eq!(spawned_limit.tags, Some(primary_tags.clone()));
1671 assert_eq!(
1672 spawned_limit.linked_order_ids,
1673 Some(linked_order_ids.clone())
1674 );
1675
1676 let spawned_mtl = algo.spawn_market_to_limit(
1677 &mut primary,
1678 Quantity::from("0.25"),
1679 TimeInForce::Gtc,
1680 None, false, None, None, None, false,
1686 );
1687 assert!(spawned_mtl.is_quote_quantity);
1688 assert_eq!(spawned_mtl.exec_algorithm_params, Some(params));
1689 assert_eq!(spawned_mtl.tags, Some(primary_tags));
1690 assert_eq!(spawned_mtl.linked_order_ids, Some(linked_order_ids));
1691 }
1692
1693 #[rstest]
1694 fn test_algorithm_reduce_primary_order() {
1695 let mut algo = create_test_algorithm();
1696 register_algorithm(&mut algo);
1697
1698 let order = OrderAny::Market(MarketOrder::new(
1699 TraderId::from("TRADER-001"),
1700 StrategyId::from("STRAT-001"),
1701 InstrumentId::from("BTC/USDT.BINANCE"),
1702 ClientOrderId::from("O-001"),
1703 OrderSide::Buy,
1704 Quantity::from("1.0"),
1705 TimeInForce::Gtc,
1706 UUID4::new(),
1707 0.into(),
1708 false,
1709 false,
1710 None,
1711 None,
1712 None,
1713 None,
1714 None,
1715 None,
1716 None,
1717 None,
1718 ));
1719
1720 let mut primary = TestOrderStubs::make_accepted_order(&order);
1722
1723 {
1724 let cache_rc = algo.core.cache_rc();
1725 let mut cache = cache_rc.borrow_mut();
1726 cache.add_order(primary.clone(), None, None, false).unwrap();
1727 }
1728
1729 let spawn_qty = Quantity::from("0.3");
1730 algo.reduce_primary_order(&mut primary, spawn_qty);
1731
1732 assert_eq!(primary.quantity(), Quantity::from("0.7"));
1733 }
1734
1735 #[rstest]
1736 fn test_algorithm_spawn_market_with_reduce_primary() {
1737 let mut algo = create_test_algorithm();
1738 register_algorithm(&mut algo);
1739
1740 let order = OrderAny::Market(MarketOrder::new(
1741 TraderId::from("TRADER-001"),
1742 StrategyId::from("STRAT-001"),
1743 InstrumentId::from("BTC/USDT.BINANCE"),
1744 ClientOrderId::from("O-001"),
1745 OrderSide::Buy,
1746 Quantity::from("1.0"),
1747 TimeInForce::Gtc,
1748 UUID4::new(),
1749 0.into(),
1750 false,
1751 false,
1752 None,
1753 None,
1754 None,
1755 None,
1756 None,
1757 None,
1758 None,
1759 None,
1760 ));
1761
1762 let mut primary = TestOrderStubs::make_accepted_order(&order);
1764
1765 {
1766 let cache_rc = algo.core.cache_rc();
1767 let mut cache = cache_rc.borrow_mut();
1768 cache.add_order(primary.clone(), None, None, false).unwrap();
1769 }
1770
1771 let spawned = algo.spawn_market(
1772 &mut primary,
1773 Quantity::from("0.4"),
1774 TimeInForce::Ioc,
1775 false,
1776 None,
1777 true, );
1779
1780 assert_eq!(spawned.quantity, Quantity::from("0.4"));
1781 assert_eq!(primary.quantity(), Quantity::from("0.6"));
1782 }
1783
1784 #[rstest]
1785 fn test_algorithm_generate_order_canceled() {
1786 let mut algo = create_test_algorithm();
1787 register_algorithm(&mut algo);
1788
1789 let order = OrderAny::Market(MarketOrder::new(
1790 TraderId::from("TRADER-001"),
1791 StrategyId::from("STRAT-001"),
1792 InstrumentId::from("BTC/USDT.BINANCE"),
1793 ClientOrderId::from("O-001"),
1794 OrderSide::Buy,
1795 Quantity::from("1.0"),
1796 TimeInForce::Gtc,
1797 UUID4::new(),
1798 0.into(),
1799 false,
1800 false,
1801 None,
1802 None,
1803 None,
1804 None,
1805 None,
1806 None,
1807 None,
1808 None,
1809 ));
1810
1811 let event = algo.generate_order_canceled(&order);
1812
1813 assert_eq!(event.trader_id, TraderId::from("TRADER-001"));
1814 assert_eq!(event.strategy_id, StrategyId::from("STRAT-001"));
1815 assert_eq!(event.instrument_id, InstrumentId::from("BTC/USDT.BINANCE"));
1816 assert_eq!(event.client_order_id, ClientOrderId::from("O-001"));
1817 }
1818
1819 #[rstest]
1820 fn test_algorithm_modify_order_in_place_updates_quantity() {
1821 let mut algo = create_test_algorithm();
1822 register_algorithm(&mut algo);
1823
1824 let mut order = OrderAny::Limit(LimitOrder::new(
1825 TraderId::from("TRADER-001"),
1826 StrategyId::from("STRAT-001"),
1827 InstrumentId::from("BTC/USDT.BINANCE"),
1828 ClientOrderId::from("O-001"),
1829 OrderSide::Buy,
1830 Quantity::from("1.0"),
1831 Price::from("50000.0"),
1832 TimeInForce::Gtc,
1833 None, false, false, false, None, None, None, None, None, None, None, None, None, None, None, UUID4::new(),
1849 0.into(),
1850 ));
1851
1852 {
1853 let cache_rc = algo.core.cache_rc();
1854 let mut cache = cache_rc.borrow_mut();
1855 cache.add_order(order.clone(), None, None, false).unwrap();
1856 }
1857
1858 let new_qty = Quantity::from("0.5");
1859 algo.modify_order_in_place(&mut order, Some(new_qty), None, None)
1860 .unwrap();
1861
1862 assert_eq!(order.quantity(), new_qty);
1863 }
1864
1865 #[rstest]
1866 fn test_algorithm_modify_order_in_place_rejects_no_changes() {
1867 let mut algo = create_test_algorithm();
1868 register_algorithm(&mut algo);
1869
1870 let mut order = OrderAny::Limit(LimitOrder::new(
1871 TraderId::from("TRADER-001"),
1872 StrategyId::from("STRAT-001"),
1873 InstrumentId::from("BTC/USDT.BINANCE"),
1874 ClientOrderId::from("O-001"),
1875 OrderSide::Buy,
1876 Quantity::from("1.0"),
1877 Price::from("50000.0"),
1878 TimeInForce::Gtc,
1879 None,
1880 false,
1881 false,
1882 false,
1883 None,
1884 None,
1885 None,
1886 None,
1887 None,
1888 None,
1889 None,
1890 None,
1891 None,
1892 None,
1893 None,
1894 UUID4::new(),
1895 0.into(),
1896 ));
1897
1898 let result =
1900 algo.modify_order_in_place(&mut order, Some(Quantity::from("1.0")), None, None);
1901
1902 assert!(result.is_err());
1903 assert!(
1904 result
1905 .unwrap_err()
1906 .to_string()
1907 .contains("no parameters differ")
1908 );
1909 }
1910
1911 #[rstest]
1912 fn test_spawned_order_denied_restores_primary_quantity() {
1913 let mut algo = create_test_algorithm();
1914 register_algorithm(&mut algo);
1915
1916 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1917 let exec_algorithm_id = algo.id();
1918
1919 let mut primary = OrderAny::Market(MarketOrder::new(
1920 TraderId::from("TRADER-001"),
1921 StrategyId::from("STRAT-001"),
1922 instrument_id,
1923 ClientOrderId::from("O-001"),
1924 OrderSide::Buy,
1925 Quantity::from("1.0"),
1926 TimeInForce::Gtc,
1927 UUID4::new(),
1928 0.into(),
1929 false,
1930 false,
1931 None,
1932 None,
1933 None,
1934 None,
1935 Some(exec_algorithm_id),
1936 None,
1937 None,
1938 None,
1939 ));
1940
1941 {
1942 let cache_rc = algo.core.cache_rc();
1943 let mut cache = cache_rc.borrow_mut();
1944 cache.add_order(primary.clone(), None, None, false).unwrap();
1945 }
1946
1947 let spawned = algo.spawn_market(
1948 &mut primary,
1949 Quantity::from("0.5"),
1950 TimeInForce::Fok,
1951 false,
1952 None,
1953 true,
1954 );
1955
1956 {
1957 let cache_rc = algo.core.cache_rc();
1958 let mut cache = cache_rc.borrow_mut();
1959 cache.update_order(&primary).unwrap();
1960 }
1961
1962 assert_eq!(primary.quantity(), Quantity::from("0.5"));
1963
1964 let mut spawned_order = OrderAny::Market(spawned);
1965 {
1966 let cache_rc = algo.core.cache_rc();
1967 let mut cache = cache_rc.borrow_mut();
1968 cache
1969 .add_order(spawned_order.clone(), None, None, false)
1970 .unwrap();
1971 }
1972
1973 let denied = OrderDenied::new(
1974 spawned_order.trader_id(),
1975 spawned_order.strategy_id(),
1976 spawned_order.instrument_id(),
1977 spawned_order.client_order_id(),
1978 "TEST_DENIAL".into(),
1979 UUID4::new(),
1980 0.into(),
1981 0.into(),
1982 );
1983
1984 spawned_order.apply(OrderEventAny::Denied(denied)).unwrap();
1985 {
1986 let cache_rc = algo.core.cache_rc();
1987 let mut cache = cache_rc.borrow_mut();
1988 cache.update_order(&spawned_order).unwrap();
1989 }
1990
1991 algo.handle_order_event(OrderEventAny::Denied(denied));
1992
1993 let restored_primary = {
1994 let cache = algo.core.cache();
1995 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
1996 };
1997 assert_eq!(restored_primary.quantity(), Quantity::from("1.0"));
1998 }
1999
2000 #[rstest]
2001 fn test_spawned_order_rejected_restores_primary_quantity() {
2002 let mut algo = create_test_algorithm();
2003 register_algorithm(&mut algo);
2004
2005 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2006 let exec_algorithm_id = algo.id();
2007
2008 let mut primary = OrderAny::Market(MarketOrder::new(
2009 TraderId::from("TRADER-001"),
2010 StrategyId::from("STRAT-001"),
2011 instrument_id,
2012 ClientOrderId::from("O-001"),
2013 OrderSide::Buy,
2014 Quantity::from("1.0"),
2015 TimeInForce::Gtc,
2016 UUID4::new(),
2017 0.into(),
2018 false,
2019 false,
2020 None,
2021 None,
2022 None,
2023 None,
2024 Some(exec_algorithm_id),
2025 None,
2026 None,
2027 None,
2028 ));
2029
2030 {
2031 let cache_rc = algo.core.cache_rc();
2032 let mut cache = cache_rc.borrow_mut();
2033 cache.add_order(primary.clone(), None, None, false).unwrap();
2034 }
2035
2036 let spawned = algo.spawn_market(
2037 &mut primary,
2038 Quantity::from("0.5"),
2039 TimeInForce::Fok,
2040 false,
2041 None,
2042 true,
2043 );
2044
2045 {
2046 let cache_rc = algo.core.cache_rc();
2047 let mut cache = cache_rc.borrow_mut();
2048 cache.update_order(&primary).unwrap();
2049 }
2050
2051 assert_eq!(primary.quantity(), Quantity::from("0.5"));
2052
2053 let mut spawned_order = OrderAny::Market(spawned);
2054 {
2055 let cache_rc = algo.core.cache_rc();
2056 let mut cache = cache_rc.borrow_mut();
2057 cache
2058 .add_order(spawned_order.clone(), None, None, false)
2059 .unwrap();
2060 }
2061
2062 let rejected = OrderRejected::new(
2063 spawned_order.trader_id(),
2064 spawned_order.strategy_id(),
2065 spawned_order.instrument_id(),
2066 spawned_order.client_order_id(),
2067 AccountId::from("BINANCE-001"),
2068 "TEST_REJECTION".into(),
2069 UUID4::new(),
2070 0.into(),
2071 0.into(),
2072 false,
2073 false,
2074 );
2075
2076 spawned_order
2077 .apply(OrderEventAny::Rejected(rejected))
2078 .unwrap();
2079 {
2080 let cache_rc = algo.core.cache_rc();
2081 let mut cache = cache_rc.borrow_mut();
2082 cache.update_order(&spawned_order).unwrap();
2083 }
2084
2085 algo.handle_order_event(OrderEventAny::Rejected(rejected));
2086
2087 let restored_primary = {
2088 let cache = algo.core.cache();
2089 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2090 };
2091 assert_eq!(restored_primary.quantity(), Quantity::from("1.0"));
2092 }
2093
2094 #[rstest]
2095 fn test_spawned_order_with_reduce_primary_false_does_not_restore() {
2096 let mut algo = create_test_algorithm();
2097 register_algorithm(&mut algo);
2098
2099 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2100 let exec_algorithm_id = algo.id();
2101
2102 let mut primary = OrderAny::Market(MarketOrder::new(
2103 TraderId::from("TRADER-001"),
2104 StrategyId::from("STRAT-001"),
2105 instrument_id,
2106 ClientOrderId::from("O-001"),
2107 OrderSide::Buy,
2108 Quantity::from("1.0"),
2109 TimeInForce::Gtc,
2110 UUID4::new(),
2111 0.into(),
2112 false,
2113 false,
2114 None,
2115 None,
2116 None,
2117 None,
2118 Some(exec_algorithm_id),
2119 None,
2120 None,
2121 None,
2122 ));
2123
2124 {
2125 let cache_rc = algo.core.cache_rc();
2126 let mut cache = cache_rc.borrow_mut();
2127 cache.add_order(primary.clone(), None, None, false).unwrap();
2128 }
2129
2130 let spawned = algo.spawn_market(
2131 &mut primary,
2132 Quantity::from("0.5"),
2133 TimeInForce::Fok,
2134 false,
2135 None,
2136 false,
2137 );
2138
2139 assert_eq!(primary.quantity(), Quantity::from("1.0"));
2140
2141 let mut spawned_order = OrderAny::Market(spawned);
2142 {
2143 let cache_rc = algo.core.cache_rc();
2144 let mut cache = cache_rc.borrow_mut();
2145 cache
2146 .add_order(spawned_order.clone(), None, None, false)
2147 .unwrap();
2148 }
2149
2150 let denied = OrderDenied::new(
2151 spawned_order.trader_id(),
2152 spawned_order.strategy_id(),
2153 spawned_order.instrument_id(),
2154 spawned_order.client_order_id(),
2155 "TEST_DENIAL".into(),
2156 UUID4::new(),
2157 0.into(),
2158 0.into(),
2159 );
2160
2161 spawned_order.apply(OrderEventAny::Denied(denied)).unwrap();
2162 {
2163 let cache_rc = algo.core.cache_rc();
2164 let mut cache = cache_rc.borrow_mut();
2165 cache.update_order(&spawned_order).unwrap();
2166 }
2167
2168 algo.handle_order_event(OrderEventAny::Denied(denied));
2169
2170 let final_primary = {
2171 let cache = algo.core.cache();
2172 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2173 };
2174 assert_eq!(final_primary.quantity(), Quantity::from("1.0"));
2175 }
2176
2177 #[rstest]
2178 fn test_multiple_spawns_with_one_denied_restores_correctly() {
2179 let mut algo = create_test_algorithm();
2180 register_algorithm(&mut algo);
2181
2182 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2183 let exec_algorithm_id = algo.id();
2184
2185 let mut primary = OrderAny::Market(MarketOrder::new(
2186 TraderId::from("TRADER-001"),
2187 StrategyId::from("STRAT-001"),
2188 instrument_id,
2189 ClientOrderId::from("O-001"),
2190 OrderSide::Buy,
2191 Quantity::from("1.0"),
2192 TimeInForce::Gtc,
2193 UUID4::new(),
2194 0.into(),
2195 false,
2196 false,
2197 None,
2198 None,
2199 None,
2200 None,
2201 Some(exec_algorithm_id),
2202 None,
2203 None,
2204 None,
2205 ));
2206
2207 {
2208 let cache_rc = algo.core.cache_rc();
2209 let mut cache = cache_rc.borrow_mut();
2210 cache.add_order(primary.clone(), None, None, false).unwrap();
2211 }
2212
2213 let spawned1 = algo.spawn_market(
2214 &mut primary,
2215 Quantity::from("0.3"),
2216 TimeInForce::Fok,
2217 false,
2218 None,
2219 true,
2220 );
2221 {
2222 let cache_rc = algo.core.cache_rc();
2223 let mut cache = cache_rc.borrow_mut();
2224 cache.update_order(&primary).unwrap();
2225 }
2226
2227 let spawned2 = algo.spawn_market(
2228 &mut primary,
2229 Quantity::from("0.4"),
2230 TimeInForce::Fok,
2231 false,
2232 None,
2233 true,
2234 );
2235 {
2236 let cache_rc = algo.core.cache_rc();
2237 let mut cache = cache_rc.borrow_mut();
2238 cache.update_order(&primary).unwrap();
2239 }
2240
2241 assert_eq!(primary.quantity(), Quantity::from("0.3"));
2242
2243 let spawned_order1 = OrderAny::Market(spawned1);
2244 let mut spawned_order2 = OrderAny::Market(spawned2);
2245 {
2246 let cache_rc = algo.core.cache_rc();
2247 let mut cache = cache_rc.borrow_mut();
2248 cache.add_order(spawned_order1, None, None, false).unwrap();
2249 cache
2250 .add_order(spawned_order2.clone(), None, None, false)
2251 .unwrap();
2252 }
2253
2254 let denied = OrderDenied::new(
2255 spawned_order2.trader_id(),
2256 spawned_order2.strategy_id(),
2257 spawned_order2.instrument_id(),
2258 spawned_order2.client_order_id(),
2259 "TEST_DENIAL".into(),
2260 UUID4::new(),
2261 0.into(),
2262 0.into(),
2263 );
2264
2265 spawned_order2.apply(OrderEventAny::Denied(denied)).unwrap();
2266 {
2267 let cache_rc = algo.core.cache_rc();
2268 let mut cache = cache_rc.borrow_mut();
2269 cache.update_order(&spawned_order2).unwrap();
2270 }
2271
2272 algo.handle_order_event(OrderEventAny::Denied(denied));
2273
2274 let restored_primary = {
2275 let cache = algo.core.cache();
2276 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2277 };
2278 assert_eq!(restored_primary.quantity(), Quantity::from("0.7"));
2279 }
2280
2281 #[rstest]
2282 fn test_spawned_order_accepted_prevents_restoration() {
2283 let mut algo = create_test_algorithm();
2284 register_algorithm(&mut algo);
2285
2286 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2287 let exec_algorithm_id = algo.id();
2288
2289 let mut primary = OrderAny::Market(MarketOrder::new(
2290 TraderId::from("TRADER-001"),
2291 StrategyId::from("STRAT-001"),
2292 instrument_id,
2293 ClientOrderId::from("O-001"),
2294 OrderSide::Buy,
2295 Quantity::from("1.0"),
2296 TimeInForce::Gtc,
2297 UUID4::new(),
2298 0.into(),
2299 false,
2300 false,
2301 None,
2302 None,
2303 None,
2304 None,
2305 Some(exec_algorithm_id),
2306 None,
2307 None,
2308 None,
2309 ));
2310
2311 {
2312 let cache_rc = algo.core.cache_rc();
2313 let mut cache = cache_rc.borrow_mut();
2314 cache.add_order(primary.clone(), None, None, false).unwrap();
2315 }
2316
2317 let spawned = algo.spawn_market(
2318 &mut primary,
2319 Quantity::from("0.5"),
2320 TimeInForce::Fok,
2321 false,
2322 None,
2323 true,
2324 );
2325
2326 {
2327 let cache_rc = algo.core.cache_rc();
2328 let mut cache = cache_rc.borrow_mut();
2329 cache.update_order(&primary).unwrap();
2330 }
2331
2332 assert_eq!(primary.quantity(), Quantity::from("0.5"));
2333
2334 let mut spawned_order = OrderAny::Market(spawned);
2335 {
2336 let cache_rc = algo.core.cache_rc();
2337 let mut cache = cache_rc.borrow_mut();
2338 cache
2339 .add_order(spawned_order.clone(), None, None, false)
2340 .unwrap();
2341 }
2342
2343 let accepted = OrderAccepted::new(
2344 spawned_order.trader_id(),
2345 spawned_order.strategy_id(),
2346 spawned_order.instrument_id(),
2347 spawned_order.client_order_id(),
2348 VenueOrderId::from("V-123"),
2349 AccountId::from("BINANCE-001"),
2350 UUID4::new(),
2351 0.into(),
2352 0.into(),
2353 false,
2354 );
2355
2356 spawned_order
2357 .apply(OrderEventAny::Accepted(accepted))
2358 .unwrap();
2359 {
2360 let cache_rc = algo.core.cache_rc();
2361 let mut cache = cache_rc.borrow_mut();
2362 cache.update_order(&spawned_order).unwrap();
2363 }
2364
2365 algo.handle_order_event(OrderEventAny::Accepted(accepted));
2366
2367 let primary_after_accept = {
2368 let cache = algo.core.cache();
2369 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2370 };
2371 assert_eq!(primary_after_accept.quantity(), Quantity::from("0.5"));
2372
2373 let canceled = OrderCanceled::new(
2375 spawned_order.trader_id(),
2376 spawned_order.strategy_id(),
2377 spawned_order.instrument_id(),
2378 spawned_order.client_order_id(),
2379 UUID4::new(),
2380 0.into(),
2381 0.into(),
2382 false,
2383 Some(VenueOrderId::from("V-123")),
2384 Some(AccountId::from("BINANCE-001")),
2385 );
2386
2387 spawned_order
2388 .apply(OrderEventAny::Canceled(canceled))
2389 .unwrap();
2390 {
2391 let cache_rc = algo.core.cache_rc();
2392 let mut cache = cache_rc.borrow_mut();
2393 cache.update_order(&spawned_order).unwrap();
2394 }
2395
2396 algo.handle_order_event(OrderEventAny::Canceled(canceled));
2397
2398 let final_primary = {
2399 let cache = algo.core.cache();
2400 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2401 };
2402 assert_eq!(final_primary.quantity(), Quantity::from("0.5"));
2403 }
2404
2405 #[rstest]
2406 #[should_panic(expected = "exceeds primary leaves_qty")]
2407 fn test_spawn_quantity_exceeds_leaves_qty_panics() {
2408 let mut algo = create_test_algorithm();
2409 register_algorithm(&mut algo);
2410
2411 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2412 let exec_algorithm_id = algo.id();
2413
2414 let mut primary = OrderAny::Market(MarketOrder::new(
2415 TraderId::from("TRADER-001"),
2416 StrategyId::from("STRAT-001"),
2417 instrument_id,
2418 ClientOrderId::from("O-001"),
2419 OrderSide::Buy,
2420 Quantity::from("1.0"),
2421 TimeInForce::Gtc,
2422 UUID4::new(),
2423 0.into(),
2424 false,
2425 false,
2426 None,
2427 None,
2428 None,
2429 None,
2430 Some(exec_algorithm_id),
2431 None,
2432 None,
2433 None,
2434 ));
2435
2436 {
2437 let cache_rc = algo.core.cache_rc();
2438 let mut cache = cache_rc.borrow_mut();
2439 cache.add_order(primary.clone(), None, None, false).unwrap();
2440 }
2441
2442 let _ = algo.spawn_market(
2443 &mut primary,
2444 Quantity::from("0.8"),
2445 TimeInForce::Fok,
2446 false,
2447 None,
2448 true,
2449 );
2450
2451 {
2452 let cache_rc = algo.core.cache_rc();
2453 let mut cache = cache_rc.borrow_mut();
2454 cache.update_order(&primary).unwrap();
2455 }
2456
2457 assert_eq!(primary.quantity(), Quantity::from("0.2"));
2458 assert_eq!(primary.leaves_qty(), Quantity::from("0.2"));
2459
2460 let _ = algo.spawn_market(
2462 &mut primary,
2463 Quantity::from("0.5"),
2464 TimeInForce::Fok,
2465 false,
2466 None,
2467 true,
2468 );
2469 }
2470}