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 false, 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 #[allow(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 false, 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 #[allow(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 false, 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::{OrderAccepted, OrderCanceled, OrderDenied, OrderRejected},
1213 identifiers::{
1214 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, StrategyId, TraderId,
1215 VenueOrderId,
1216 },
1217 orders::{LimitOrder, MarketOrder, OrderAny, stubs::TestOrderStubs},
1218 types::{Price, Quantity},
1219 };
1220 use rstest::rstest;
1221
1222 use super::*;
1223
1224 #[derive(Debug)]
1225 struct TestAlgorithm {
1226 core: ExecutionAlgorithmCore,
1227 on_order_called: bool,
1228 last_order_client_id: Option<ClientOrderId>,
1229 }
1230
1231 impl TestAlgorithm {
1232 fn new(config: ExecutionAlgorithmConfig) -> Self {
1233 Self {
1234 core: ExecutionAlgorithmCore::new(config),
1235 on_order_called: false,
1236 last_order_client_id: None,
1237 }
1238 }
1239 }
1240
1241 impl DataActor for TestAlgorithm {}
1242
1243 nautilus_actor!(TestAlgorithm);
1244
1245 impl ExecutionAlgorithm for TestAlgorithm {
1246 fn core_mut(&mut self) -> &mut ExecutionAlgorithmCore {
1247 &mut self.core
1248 }
1249
1250 fn on_order(&mut self, order: OrderAny) -> anyhow::Result<()> {
1251 self.on_order_called = true;
1252 self.last_order_client_id = Some(order.client_order_id());
1253 Ok(())
1254 }
1255 }
1256
1257 fn create_test_algorithm() -> TestAlgorithm {
1258 let unique_id = format!("TEST-{}", UUID4::new());
1260 let config = ExecutionAlgorithmConfig {
1261 exec_algorithm_id: Some(ExecAlgorithmId::new(&unique_id)),
1262 ..Default::default()
1263 };
1264 TestAlgorithm::new(config)
1265 }
1266
1267 fn register_algorithm(algo: &mut TestAlgorithm) {
1268 let trader_id = TraderId::from("TRADER-001");
1269 let clock = Rc::new(RefCell::new(TestClock::new()));
1270 let cache = Rc::new(RefCell::new(Cache::default()));
1271
1272 algo.core.register(trader_id, clock, cache).unwrap();
1273
1274 algo.transition_state(ComponentTrigger::Initialize).unwrap();
1276 algo.transition_state(ComponentTrigger::Start).unwrap();
1277 algo.transition_state(ComponentTrigger::StartCompleted)
1278 .unwrap();
1279 }
1280
1281 #[rstest]
1282 fn test_algorithm_creation() {
1283 let algo = create_test_algorithm();
1284 assert!(algo.core.exec_algorithm_id.inner().starts_with("TEST-"));
1285 assert!(!algo.on_order_called);
1286 assert!(algo.last_order_client_id.is_none());
1287 }
1288
1289 #[rstest]
1290 fn test_algorithm_registration() {
1291 let mut algo = create_test_algorithm();
1292 register_algorithm(&mut algo);
1293
1294 assert!(algo.core.trader_id().is_some());
1295 assert_eq!(algo.core.trader_id(), Some(TraderId::from("TRADER-001")));
1296 }
1297
1298 #[rstest]
1299 fn test_algorithm_id() {
1300 let mut algo = create_test_algorithm();
1301 assert!(algo.id().inner().starts_with("TEST-"));
1302 }
1303
1304 #[rstest]
1305 fn test_algorithm_spawn_market_creates_valid_order() {
1306 let mut algo = create_test_algorithm();
1307 register_algorithm(&mut algo);
1308
1309 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1310 let mut primary = OrderAny::Market(MarketOrder::new(
1311 TraderId::from("TRADER-001"),
1312 StrategyId::from("STRAT-001"),
1313 instrument_id,
1314 ClientOrderId::from("O-001"),
1315 OrderSide::Buy,
1316 Quantity::from("1.0"),
1317 TimeInForce::Gtc,
1318 UUID4::new(),
1319 0.into(),
1320 false, false, None, None, None, None, None, None, None, None, ));
1331
1332 let spawned = algo.spawn_market(
1333 &mut primary,
1334 Quantity::from("0.5"),
1335 TimeInForce::Ioc,
1336 false,
1337 None, false, );
1340
1341 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1342 assert_eq!(spawned.instrument_id, instrument_id);
1343 assert_eq!(spawned.order_side(), OrderSide::Buy);
1344 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1345 assert_eq!(spawned.time_in_force, TimeInForce::Ioc);
1346 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1347 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1348 }
1349
1350 #[rstest]
1351 fn test_algorithm_spawn_increments_sequence() {
1352 let mut algo = create_test_algorithm();
1353 register_algorithm(&mut algo);
1354
1355 let mut primary = OrderAny::Market(MarketOrder::new(
1356 TraderId::from("TRADER-001"),
1357 StrategyId::from("STRAT-001"),
1358 InstrumentId::from("BTC/USDT.BINANCE"),
1359 ClientOrderId::from("O-001"),
1360 OrderSide::Buy,
1361 Quantity::from("1.0"),
1362 TimeInForce::Gtc,
1363 UUID4::new(),
1364 0.into(),
1365 false,
1366 false,
1367 None,
1368 None,
1369 None,
1370 None,
1371 None,
1372 None,
1373 None,
1374 None,
1375 ));
1376
1377 let spawned1 = algo.spawn_market(
1378 &mut primary,
1379 Quantity::from("0.25"),
1380 TimeInForce::Ioc,
1381 false,
1382 None,
1383 false,
1384 );
1385 let spawned2 = algo.spawn_market(
1386 &mut primary,
1387 Quantity::from("0.25"),
1388 TimeInForce::Ioc,
1389 false,
1390 None,
1391 false,
1392 );
1393 let spawned3 = algo.spawn_market(
1394 &mut primary,
1395 Quantity::from("0.25"),
1396 TimeInForce::Ioc,
1397 false,
1398 None,
1399 false,
1400 );
1401
1402 assert_eq!(spawned1.client_order_id.as_str(), "O-001-E1");
1403 assert_eq!(spawned2.client_order_id.as_str(), "O-001-E2");
1404 assert_eq!(spawned3.client_order_id.as_str(), "O-001-E3");
1405 }
1406
1407 #[rstest]
1408 fn test_algorithm_default_handlers_do_not_panic() {
1409 let mut algo = create_test_algorithm();
1410
1411 algo.on_order_initialized(OrderInitialized::default());
1412 algo.on_order_denied(OrderDenied::default());
1413 algo.on_order_emulated(OrderEmulated::default());
1414 algo.on_order_released(OrderReleased::default());
1415 algo.on_order_submitted(OrderSubmitted::default());
1416 algo.on_order_rejected(OrderRejected::default());
1417 algo.on_order_accepted(OrderAccepted::default());
1418 algo.on_algo_order_canceled(OrderCanceled::default());
1419 algo.on_order_expired(OrderExpired::default());
1420 algo.on_order_triggered(OrderTriggered::default());
1421 algo.on_order_pending_update(OrderPendingUpdate::default());
1422 algo.on_order_pending_cancel(OrderPendingCancel::default());
1423 algo.on_order_modify_rejected(OrderModifyRejected::default());
1424 algo.on_order_cancel_rejected(OrderCancelRejected::default());
1425 algo.on_order_updated(OrderUpdated::default());
1426 algo.on_algo_order_filled(OrderFilled::default());
1427 }
1428
1429 #[rstest]
1430 fn test_strategy_subscription_tracking() {
1431 let mut algo = create_test_algorithm();
1432 let strategy_id = StrategyId::from("STRAT-001");
1433
1434 assert!(!algo.core.is_strategy_subscribed(&strategy_id));
1435
1436 algo.subscribe_to_strategy_events(strategy_id);
1437 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1438
1439 algo.subscribe_to_strategy_events(strategy_id);
1441 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1442 }
1443
1444 #[rstest]
1445 fn test_algorithm_reset() {
1446 let mut algo = create_test_algorithm();
1447 let strategy_id = StrategyId::from("STRAT-001");
1448 let primary_id = ClientOrderId::new("O-001");
1449
1450 let _ = algo.core.spawn_client_order_id(&primary_id);
1451 algo.core.add_subscribed_strategy(strategy_id);
1452
1453 assert!(algo.core.spawn_sequence(&primary_id).is_some());
1454 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1455
1456 ExecutionAlgorithm::on_reset(&mut algo).unwrap();
1457
1458 assert!(algo.core.spawn_sequence(&primary_id).is_none());
1459 assert!(!algo.core.is_strategy_subscribed(&strategy_id));
1460 }
1461
1462 #[rstest]
1463 fn test_algorithm_spawn_limit_creates_valid_order() {
1464 let mut algo = create_test_algorithm();
1465 register_algorithm(&mut algo);
1466
1467 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1468 let mut primary = OrderAny::Market(MarketOrder::new(
1469 TraderId::from("TRADER-001"),
1470 StrategyId::from("STRAT-001"),
1471 instrument_id,
1472 ClientOrderId::from("O-001"),
1473 OrderSide::Buy,
1474 Quantity::from("1.0"),
1475 TimeInForce::Gtc,
1476 UUID4::new(),
1477 0.into(),
1478 false,
1479 false,
1480 None,
1481 None,
1482 None,
1483 None,
1484 None,
1485 None,
1486 None,
1487 None,
1488 ));
1489
1490 let price = Price::from("50000.0");
1491 let spawned = algo.spawn_limit(
1492 &mut primary,
1493 Quantity::from("0.5"),
1494 price,
1495 TimeInForce::Gtc,
1496 None, false, false, None, None, None, false, );
1504
1505 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1506 assert_eq!(spawned.instrument_id, instrument_id);
1507 assert_eq!(spawned.order_side(), OrderSide::Buy);
1508 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1509 assert_eq!(spawned.price, price);
1510 assert_eq!(spawned.time_in_force, TimeInForce::Gtc);
1511 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1512 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1513 }
1514
1515 #[rstest]
1516 fn test_algorithm_spawn_market_to_limit_creates_valid_order() {
1517 let mut algo = create_test_algorithm();
1518 register_algorithm(&mut algo);
1519
1520 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1521 let mut primary = OrderAny::Market(MarketOrder::new(
1522 TraderId::from("TRADER-001"),
1523 StrategyId::from("STRAT-001"),
1524 instrument_id,
1525 ClientOrderId::from("O-001"),
1526 OrderSide::Buy,
1527 Quantity::from("1.0"),
1528 TimeInForce::Gtc,
1529 UUID4::new(),
1530 0.into(),
1531 false,
1532 false,
1533 None,
1534 None,
1535 None,
1536 None,
1537 None,
1538 None,
1539 None,
1540 None,
1541 ));
1542
1543 let spawned = algo.spawn_market_to_limit(
1544 &mut primary,
1545 Quantity::from("0.5"),
1546 TimeInForce::Gtc,
1547 None, false, None, None, None, false, );
1554
1555 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1556 assert_eq!(spawned.instrument_id, instrument_id);
1557 assert_eq!(spawned.order_side(), OrderSide::Buy);
1558 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1559 assert_eq!(spawned.time_in_force, TimeInForce::Gtc);
1560 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1561 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1562 }
1563
1564 #[rstest]
1565 fn test_algorithm_spawn_market_with_tags() {
1566 let mut algo = create_test_algorithm();
1567 register_algorithm(&mut algo);
1568
1569 let mut primary = OrderAny::Market(MarketOrder::new(
1570 TraderId::from("TRADER-001"),
1571 StrategyId::from("STRAT-001"),
1572 InstrumentId::from("BTC/USDT.BINANCE"),
1573 ClientOrderId::from("O-001"),
1574 OrderSide::Buy,
1575 Quantity::from("1.0"),
1576 TimeInForce::Gtc,
1577 UUID4::new(),
1578 0.into(),
1579 false,
1580 false,
1581 None,
1582 None,
1583 None,
1584 None,
1585 None,
1586 None,
1587 None,
1588 None,
1589 ));
1590
1591 let tags = vec![ustr::Ustr::from("TAG1"), ustr::Ustr::from("TAG2")];
1592 let spawned = algo.spawn_market(
1593 &mut primary,
1594 Quantity::from("0.5"),
1595 TimeInForce::Ioc,
1596 false,
1597 Some(tags.clone()),
1598 false,
1599 );
1600
1601 assert_eq!(spawned.tags, Some(tags));
1602 }
1603
1604 #[rstest]
1605 fn test_algorithm_reduce_primary_order() {
1606 let mut algo = create_test_algorithm();
1607 register_algorithm(&mut algo);
1608
1609 let order = OrderAny::Market(MarketOrder::new(
1610 TraderId::from("TRADER-001"),
1611 StrategyId::from("STRAT-001"),
1612 InstrumentId::from("BTC/USDT.BINANCE"),
1613 ClientOrderId::from("O-001"),
1614 OrderSide::Buy,
1615 Quantity::from("1.0"),
1616 TimeInForce::Gtc,
1617 UUID4::new(),
1618 0.into(),
1619 false,
1620 false,
1621 None,
1622 None,
1623 None,
1624 None,
1625 None,
1626 None,
1627 None,
1628 None,
1629 ));
1630
1631 let mut primary = TestOrderStubs::make_accepted_order(&order);
1633
1634 {
1635 let cache_rc = algo.core.cache_rc();
1636 let mut cache = cache_rc.borrow_mut();
1637 cache.add_order(primary.clone(), None, None, false).unwrap();
1638 }
1639
1640 let spawn_qty = Quantity::from("0.3");
1641 algo.reduce_primary_order(&mut primary, spawn_qty);
1642
1643 assert_eq!(primary.quantity(), Quantity::from("0.7"));
1644 }
1645
1646 #[rstest]
1647 fn test_algorithm_spawn_market_with_reduce_primary() {
1648 let mut algo = create_test_algorithm();
1649 register_algorithm(&mut algo);
1650
1651 let order = OrderAny::Market(MarketOrder::new(
1652 TraderId::from("TRADER-001"),
1653 StrategyId::from("STRAT-001"),
1654 InstrumentId::from("BTC/USDT.BINANCE"),
1655 ClientOrderId::from("O-001"),
1656 OrderSide::Buy,
1657 Quantity::from("1.0"),
1658 TimeInForce::Gtc,
1659 UUID4::new(),
1660 0.into(),
1661 false,
1662 false,
1663 None,
1664 None,
1665 None,
1666 None,
1667 None,
1668 None,
1669 None,
1670 None,
1671 ));
1672
1673 let mut primary = TestOrderStubs::make_accepted_order(&order);
1675
1676 {
1677 let cache_rc = algo.core.cache_rc();
1678 let mut cache = cache_rc.borrow_mut();
1679 cache.add_order(primary.clone(), None, None, false).unwrap();
1680 }
1681
1682 let spawned = algo.spawn_market(
1683 &mut primary,
1684 Quantity::from("0.4"),
1685 TimeInForce::Ioc,
1686 false,
1687 None,
1688 true, );
1690
1691 assert_eq!(spawned.quantity, Quantity::from("0.4"));
1692 assert_eq!(primary.quantity(), Quantity::from("0.6"));
1693 }
1694
1695 #[rstest]
1696 fn test_algorithm_generate_order_canceled() {
1697 let mut algo = create_test_algorithm();
1698 register_algorithm(&mut algo);
1699
1700 let order = OrderAny::Market(MarketOrder::new(
1701 TraderId::from("TRADER-001"),
1702 StrategyId::from("STRAT-001"),
1703 InstrumentId::from("BTC/USDT.BINANCE"),
1704 ClientOrderId::from("O-001"),
1705 OrderSide::Buy,
1706 Quantity::from("1.0"),
1707 TimeInForce::Gtc,
1708 UUID4::new(),
1709 0.into(),
1710 false,
1711 false,
1712 None,
1713 None,
1714 None,
1715 None,
1716 None,
1717 None,
1718 None,
1719 None,
1720 ));
1721
1722 let event = algo.generate_order_canceled(&order);
1723
1724 assert_eq!(event.trader_id, TraderId::from("TRADER-001"));
1725 assert_eq!(event.strategy_id, StrategyId::from("STRAT-001"));
1726 assert_eq!(event.instrument_id, InstrumentId::from("BTC/USDT.BINANCE"));
1727 assert_eq!(event.client_order_id, ClientOrderId::from("O-001"));
1728 }
1729
1730 #[rstest]
1731 fn test_algorithm_modify_order_in_place_updates_quantity() {
1732 let mut algo = create_test_algorithm();
1733 register_algorithm(&mut algo);
1734
1735 let mut order = OrderAny::Limit(LimitOrder::new(
1736 TraderId::from("TRADER-001"),
1737 StrategyId::from("STRAT-001"),
1738 InstrumentId::from("BTC/USDT.BINANCE"),
1739 ClientOrderId::from("O-001"),
1740 OrderSide::Buy,
1741 Quantity::from("1.0"),
1742 Price::from("50000.0"),
1743 TimeInForce::Gtc,
1744 None, false, false, false, None, None, None, None, None, None, None, None, None, None, None, UUID4::new(),
1760 0.into(),
1761 ));
1762
1763 {
1764 let cache_rc = algo.core.cache_rc();
1765 let mut cache = cache_rc.borrow_mut();
1766 cache.add_order(order.clone(), None, None, false).unwrap();
1767 }
1768
1769 let new_qty = Quantity::from("0.5");
1770 algo.modify_order_in_place(&mut order, Some(new_qty), None, None)
1771 .unwrap();
1772
1773 assert_eq!(order.quantity(), new_qty);
1774 }
1775
1776 #[rstest]
1777 fn test_algorithm_modify_order_in_place_rejects_no_changes() {
1778 let mut algo = create_test_algorithm();
1779 register_algorithm(&mut algo);
1780
1781 let mut order = OrderAny::Limit(LimitOrder::new(
1782 TraderId::from("TRADER-001"),
1783 StrategyId::from("STRAT-001"),
1784 InstrumentId::from("BTC/USDT.BINANCE"),
1785 ClientOrderId::from("O-001"),
1786 OrderSide::Buy,
1787 Quantity::from("1.0"),
1788 Price::from("50000.0"),
1789 TimeInForce::Gtc,
1790 None,
1791 false,
1792 false,
1793 false,
1794 None,
1795 None,
1796 None,
1797 None,
1798 None,
1799 None,
1800 None,
1801 None,
1802 None,
1803 None,
1804 None,
1805 UUID4::new(),
1806 0.into(),
1807 ));
1808
1809 let result =
1811 algo.modify_order_in_place(&mut order, Some(Quantity::from("1.0")), None, None);
1812
1813 assert!(result.is_err());
1814 assert!(
1815 result
1816 .unwrap_err()
1817 .to_string()
1818 .contains("no parameters differ")
1819 );
1820 }
1821
1822 #[rstest]
1823 fn test_spawned_order_denied_restores_primary_quantity() {
1824 let mut algo = create_test_algorithm();
1825 register_algorithm(&mut algo);
1826
1827 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1828 let exec_algorithm_id = algo.id();
1829
1830 let mut primary = OrderAny::Market(MarketOrder::new(
1831 TraderId::from("TRADER-001"),
1832 StrategyId::from("STRAT-001"),
1833 instrument_id,
1834 ClientOrderId::from("O-001"),
1835 OrderSide::Buy,
1836 Quantity::from("1.0"),
1837 TimeInForce::Gtc,
1838 UUID4::new(),
1839 0.into(),
1840 false,
1841 false,
1842 None,
1843 None,
1844 None,
1845 None,
1846 Some(exec_algorithm_id),
1847 None,
1848 None,
1849 None,
1850 ));
1851
1852 {
1853 let cache_rc = algo.core.cache_rc();
1854 let mut cache = cache_rc.borrow_mut();
1855 cache.add_order(primary.clone(), None, None, false).unwrap();
1856 }
1857
1858 let spawned = algo.spawn_market(
1859 &mut primary,
1860 Quantity::from("0.5"),
1861 TimeInForce::Fok,
1862 false,
1863 None,
1864 true,
1865 );
1866
1867 {
1868 let cache_rc = algo.core.cache_rc();
1869 let mut cache = cache_rc.borrow_mut();
1870 cache.update_order(&primary).unwrap();
1871 }
1872
1873 assert_eq!(primary.quantity(), Quantity::from("0.5"));
1874
1875 let mut spawned_order = OrderAny::Market(spawned);
1876 {
1877 let cache_rc = algo.core.cache_rc();
1878 let mut cache = cache_rc.borrow_mut();
1879 cache
1880 .add_order(spawned_order.clone(), None, None, false)
1881 .unwrap();
1882 }
1883
1884 let denied = OrderDenied::new(
1885 spawned_order.trader_id(),
1886 spawned_order.strategy_id(),
1887 spawned_order.instrument_id(),
1888 spawned_order.client_order_id(),
1889 "TEST_DENIAL".into(),
1890 UUID4::new(),
1891 0.into(),
1892 0.into(),
1893 );
1894
1895 spawned_order.apply(OrderEventAny::Denied(denied)).unwrap();
1896 {
1897 let cache_rc = algo.core.cache_rc();
1898 let mut cache = cache_rc.borrow_mut();
1899 cache.update_order(&spawned_order).unwrap();
1900 }
1901
1902 algo.handle_order_event(OrderEventAny::Denied(denied));
1903
1904 let restored_primary = {
1905 let cache = algo.core.cache();
1906 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
1907 };
1908 assert_eq!(restored_primary.quantity(), Quantity::from("1.0"));
1909 }
1910
1911 #[rstest]
1912 fn test_spawned_order_rejected_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 rejected = OrderRejected::new(
1974 spawned_order.trader_id(),
1975 spawned_order.strategy_id(),
1976 spawned_order.instrument_id(),
1977 spawned_order.client_order_id(),
1978 AccountId::from("BINANCE-001"),
1979 "TEST_REJECTION".into(),
1980 UUID4::new(),
1981 0.into(),
1982 0.into(),
1983 false,
1984 false,
1985 );
1986
1987 spawned_order
1988 .apply(OrderEventAny::Rejected(rejected))
1989 .unwrap();
1990 {
1991 let cache_rc = algo.core.cache_rc();
1992 let mut cache = cache_rc.borrow_mut();
1993 cache.update_order(&spawned_order).unwrap();
1994 }
1995
1996 algo.handle_order_event(OrderEventAny::Rejected(rejected));
1997
1998 let restored_primary = {
1999 let cache = algo.core.cache();
2000 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2001 };
2002 assert_eq!(restored_primary.quantity(), Quantity::from("1.0"));
2003 }
2004
2005 #[rstest]
2006 fn test_spawned_order_with_reduce_primary_false_does_not_restore() {
2007 let mut algo = create_test_algorithm();
2008 register_algorithm(&mut algo);
2009
2010 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2011 let exec_algorithm_id = algo.id();
2012
2013 let mut primary = OrderAny::Market(MarketOrder::new(
2014 TraderId::from("TRADER-001"),
2015 StrategyId::from("STRAT-001"),
2016 instrument_id,
2017 ClientOrderId::from("O-001"),
2018 OrderSide::Buy,
2019 Quantity::from("1.0"),
2020 TimeInForce::Gtc,
2021 UUID4::new(),
2022 0.into(),
2023 false,
2024 false,
2025 None,
2026 None,
2027 None,
2028 None,
2029 Some(exec_algorithm_id),
2030 None,
2031 None,
2032 None,
2033 ));
2034
2035 {
2036 let cache_rc = algo.core.cache_rc();
2037 let mut cache = cache_rc.borrow_mut();
2038 cache.add_order(primary.clone(), None, None, false).unwrap();
2039 }
2040
2041 let spawned = algo.spawn_market(
2042 &mut primary,
2043 Quantity::from("0.5"),
2044 TimeInForce::Fok,
2045 false,
2046 None,
2047 false,
2048 );
2049
2050 assert_eq!(primary.quantity(), Quantity::from("1.0"));
2051
2052 let mut spawned_order = OrderAny::Market(spawned);
2053 {
2054 let cache_rc = algo.core.cache_rc();
2055 let mut cache = cache_rc.borrow_mut();
2056 cache
2057 .add_order(spawned_order.clone(), None, None, false)
2058 .unwrap();
2059 }
2060
2061 let denied = OrderDenied::new(
2062 spawned_order.trader_id(),
2063 spawned_order.strategy_id(),
2064 spawned_order.instrument_id(),
2065 spawned_order.client_order_id(),
2066 "TEST_DENIAL".into(),
2067 UUID4::new(),
2068 0.into(),
2069 0.into(),
2070 );
2071
2072 spawned_order.apply(OrderEventAny::Denied(denied)).unwrap();
2073 {
2074 let cache_rc = algo.core.cache_rc();
2075 let mut cache = cache_rc.borrow_mut();
2076 cache.update_order(&spawned_order).unwrap();
2077 }
2078
2079 algo.handle_order_event(OrderEventAny::Denied(denied));
2080
2081 let final_primary = {
2082 let cache = algo.core.cache();
2083 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2084 };
2085 assert_eq!(final_primary.quantity(), Quantity::from("1.0"));
2086 }
2087
2088 #[rstest]
2089 fn test_multiple_spawns_with_one_denied_restores_correctly() {
2090 let mut algo = create_test_algorithm();
2091 register_algorithm(&mut algo);
2092
2093 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2094 let exec_algorithm_id = algo.id();
2095
2096 let mut primary = OrderAny::Market(MarketOrder::new(
2097 TraderId::from("TRADER-001"),
2098 StrategyId::from("STRAT-001"),
2099 instrument_id,
2100 ClientOrderId::from("O-001"),
2101 OrderSide::Buy,
2102 Quantity::from("1.0"),
2103 TimeInForce::Gtc,
2104 UUID4::new(),
2105 0.into(),
2106 false,
2107 false,
2108 None,
2109 None,
2110 None,
2111 None,
2112 Some(exec_algorithm_id),
2113 None,
2114 None,
2115 None,
2116 ));
2117
2118 {
2119 let cache_rc = algo.core.cache_rc();
2120 let mut cache = cache_rc.borrow_mut();
2121 cache.add_order(primary.clone(), None, None, false).unwrap();
2122 }
2123
2124 let spawned1 = algo.spawn_market(
2125 &mut primary,
2126 Quantity::from("0.3"),
2127 TimeInForce::Fok,
2128 false,
2129 None,
2130 true,
2131 );
2132 {
2133 let cache_rc = algo.core.cache_rc();
2134 let mut cache = cache_rc.borrow_mut();
2135 cache.update_order(&primary).unwrap();
2136 }
2137
2138 let spawned2 = algo.spawn_market(
2139 &mut primary,
2140 Quantity::from("0.4"),
2141 TimeInForce::Fok,
2142 false,
2143 None,
2144 true,
2145 );
2146 {
2147 let cache_rc = algo.core.cache_rc();
2148 let mut cache = cache_rc.borrow_mut();
2149 cache.update_order(&primary).unwrap();
2150 }
2151
2152 assert_eq!(primary.quantity(), Quantity::from("0.3"));
2153
2154 let spawned_order1 = OrderAny::Market(spawned1);
2155 let mut spawned_order2 = OrderAny::Market(spawned2);
2156 {
2157 let cache_rc = algo.core.cache_rc();
2158 let mut cache = cache_rc.borrow_mut();
2159 cache.add_order(spawned_order1, None, None, false).unwrap();
2160 cache
2161 .add_order(spawned_order2.clone(), None, None, false)
2162 .unwrap();
2163 }
2164
2165 let denied = OrderDenied::new(
2166 spawned_order2.trader_id(),
2167 spawned_order2.strategy_id(),
2168 spawned_order2.instrument_id(),
2169 spawned_order2.client_order_id(),
2170 "TEST_DENIAL".into(),
2171 UUID4::new(),
2172 0.into(),
2173 0.into(),
2174 );
2175
2176 spawned_order2.apply(OrderEventAny::Denied(denied)).unwrap();
2177 {
2178 let cache_rc = algo.core.cache_rc();
2179 let mut cache = cache_rc.borrow_mut();
2180 cache.update_order(&spawned_order2).unwrap();
2181 }
2182
2183 algo.handle_order_event(OrderEventAny::Denied(denied));
2184
2185 let restored_primary = {
2186 let cache = algo.core.cache();
2187 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2188 };
2189 assert_eq!(restored_primary.quantity(), Quantity::from("0.7"));
2190 }
2191
2192 #[rstest]
2193 fn test_spawned_order_accepted_prevents_restoration() {
2194 let mut algo = create_test_algorithm();
2195 register_algorithm(&mut algo);
2196
2197 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2198 let exec_algorithm_id = algo.id();
2199
2200 let mut primary = OrderAny::Market(MarketOrder::new(
2201 TraderId::from("TRADER-001"),
2202 StrategyId::from("STRAT-001"),
2203 instrument_id,
2204 ClientOrderId::from("O-001"),
2205 OrderSide::Buy,
2206 Quantity::from("1.0"),
2207 TimeInForce::Gtc,
2208 UUID4::new(),
2209 0.into(),
2210 false,
2211 false,
2212 None,
2213 None,
2214 None,
2215 None,
2216 Some(exec_algorithm_id),
2217 None,
2218 None,
2219 None,
2220 ));
2221
2222 {
2223 let cache_rc = algo.core.cache_rc();
2224 let mut cache = cache_rc.borrow_mut();
2225 cache.add_order(primary.clone(), None, None, false).unwrap();
2226 }
2227
2228 let spawned = algo.spawn_market(
2229 &mut primary,
2230 Quantity::from("0.5"),
2231 TimeInForce::Fok,
2232 false,
2233 None,
2234 true,
2235 );
2236
2237 {
2238 let cache_rc = algo.core.cache_rc();
2239 let mut cache = cache_rc.borrow_mut();
2240 cache.update_order(&primary).unwrap();
2241 }
2242
2243 assert_eq!(primary.quantity(), Quantity::from("0.5"));
2244
2245 let mut spawned_order = OrderAny::Market(spawned);
2246 {
2247 let cache_rc = algo.core.cache_rc();
2248 let mut cache = cache_rc.borrow_mut();
2249 cache
2250 .add_order(spawned_order.clone(), None, None, false)
2251 .unwrap();
2252 }
2253
2254 let accepted = OrderAccepted::new(
2255 spawned_order.trader_id(),
2256 spawned_order.strategy_id(),
2257 spawned_order.instrument_id(),
2258 spawned_order.client_order_id(),
2259 VenueOrderId::from("V-123"),
2260 AccountId::from("BINANCE-001"),
2261 UUID4::new(),
2262 0.into(),
2263 0.into(),
2264 false,
2265 );
2266
2267 spawned_order
2268 .apply(OrderEventAny::Accepted(accepted))
2269 .unwrap();
2270 {
2271 let cache_rc = algo.core.cache_rc();
2272 let mut cache = cache_rc.borrow_mut();
2273 cache.update_order(&spawned_order).unwrap();
2274 }
2275
2276 algo.handle_order_event(OrderEventAny::Accepted(accepted));
2277
2278 let primary_after_accept = {
2279 let cache = algo.core.cache();
2280 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2281 };
2282 assert_eq!(primary_after_accept.quantity(), Quantity::from("0.5"));
2283
2284 let canceled = OrderCanceled::new(
2286 spawned_order.trader_id(),
2287 spawned_order.strategy_id(),
2288 spawned_order.instrument_id(),
2289 spawned_order.client_order_id(),
2290 UUID4::new(),
2291 0.into(),
2292 0.into(),
2293 false,
2294 Some(VenueOrderId::from("V-123")),
2295 Some(AccountId::from("BINANCE-001")),
2296 );
2297
2298 spawned_order
2299 .apply(OrderEventAny::Canceled(canceled))
2300 .unwrap();
2301 {
2302 let cache_rc = algo.core.cache_rc();
2303 let mut cache = cache_rc.borrow_mut();
2304 cache.update_order(&spawned_order).unwrap();
2305 }
2306
2307 algo.handle_order_event(OrderEventAny::Canceled(canceled));
2308
2309 let final_primary = {
2310 let cache = algo.core.cache();
2311 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2312 };
2313 assert_eq!(final_primary.quantity(), Quantity::from("0.5"));
2314 }
2315
2316 #[rstest]
2317 #[should_panic(expected = "exceeds primary leaves_qty")]
2318 fn test_spawn_quantity_exceeds_leaves_qty_panics() {
2319 let mut algo = create_test_algorithm();
2320 register_algorithm(&mut algo);
2321
2322 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2323 let exec_algorithm_id = algo.id();
2324
2325 let mut primary = OrderAny::Market(MarketOrder::new(
2326 TraderId::from("TRADER-001"),
2327 StrategyId::from("STRAT-001"),
2328 instrument_id,
2329 ClientOrderId::from("O-001"),
2330 OrderSide::Buy,
2331 Quantity::from("1.0"),
2332 TimeInForce::Gtc,
2333 UUID4::new(),
2334 0.into(),
2335 false,
2336 false,
2337 None,
2338 None,
2339 None,
2340 None,
2341 Some(exec_algorithm_id),
2342 None,
2343 None,
2344 None,
2345 ));
2346
2347 {
2348 let cache_rc = algo.core.cache_rc();
2349 let mut cache = cache_rc.borrow_mut();
2350 cache.add_order(primary.clone(), None, None, false).unwrap();
2351 }
2352
2353 let _ = algo.spawn_market(
2354 &mut primary,
2355 Quantity::from("0.8"),
2356 TimeInForce::Fok,
2357 false,
2358 None,
2359 true,
2360 );
2361
2362 {
2363 let cache_rc = algo.core.cache_rc();
2364 let mut cache = cache_rc.borrow_mut();
2365 cache.update_order(&primary).unwrap();
2366 }
2367
2368 assert_eq!(primary.quantity(), Quantity::from("0.2"));
2369 assert_eq!(primary.leaves_qty(), Quantity::from("0.2"));
2370
2371 let _ = algo.spawn_market(
2373 &mut primary,
2374 Quantity::from("0.5"),
2375 TimeInForce::Fok,
2376 false,
2377 None,
2378 true,
2379 );
2380 }
2381}