1pub mod config;
39pub mod core;
40pub mod twap;
41
42pub use core::{ExecutionAlgorithmCore, StrategyEventHandlers};
43
44pub use config::ExecutionAlgorithmConfig;
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, );
508
509 primary
510 .apply(OrderEventAny::Updated(updated))
511 .expect("Failed to apply OrderUpdated");
512
513 let cache_rc = core.cache_rc();
514 let mut cache = cache_rc.borrow_mut();
515 cache
516 .update_order(primary)
517 .expect("Failed to update order in cache");
518 }
519
520 fn restore_primary_order_quantity(&mut self, order: &OrderAny) {
526 let Some(exec_spawn_id) = order.exec_spawn_id() else {
527 return;
528 };
529
530 let reduction_qty = {
531 let core = self.core_mut();
532 core.take_pending_spawn_reduction(&order.client_order_id())
533 };
534
535 let Some(reduction_qty) = reduction_qty else {
536 return;
537 };
538
539 let primary = {
540 let cache = self.core_mut().cache();
541 cache.order(&exec_spawn_id).cloned()
542 };
543
544 let Some(mut primary) = primary else {
545 log::warn!(
546 "Cannot restore primary order quantity: primary order {exec_spawn_id} not found",
547 );
548 return;
549 };
550
551 let restore_raw = std::cmp::min(reduction_qty.raw, order.leaves_qty().raw);
553 if restore_raw == 0 {
554 return;
555 }
556
557 let restored_qty = Quantity::from_raw(
558 primary.quantity().raw + restore_raw,
559 primary.quantity().precision,
560 );
561
562 let core = self.core_mut();
563 let ts_now = core.clock().timestamp_ns();
564
565 let updated = OrderUpdated::new(
566 primary.trader_id(),
567 primary.strategy_id(),
568 primary.instrument_id(),
569 primary.client_order_id(),
570 restored_qty,
571 UUID4::new(),
572 ts_now,
573 ts_now,
574 false, primary.venue_order_id(),
576 primary.account_id(),
577 None, None, None, );
581
582 if let Err(e) = primary.apply(OrderEventAny::Updated(updated)) {
583 log::warn!("Failed to apply OrderUpdated for quantity restoration: {e}");
584 return;
585 }
586
587 {
588 let cache_rc = core.cache_rc();
589 let mut cache = cache_rc.borrow_mut();
590 if let Err(e) = cache.update_order(&primary) {
591 log::warn!("Failed to update primary order in cache: {e}");
592 return;
593 }
594 }
595
596 log::info!(
597 "Restored primary order {} quantity to {} after spawned order {} was denied/rejected",
598 primary.client_order_id(),
599 restored_qty,
600 order.client_order_id()
601 );
602 }
603
604 fn submit_order(
610 &mut self,
611 order: OrderAny,
612 position_id: Option<PositionId>,
613 client_id: Option<ClientId>,
614 ) -> anyhow::Result<()> {
615 let core = self.core_mut();
616
617 let trader_id = core.trader_id().expect("Trader ID not set");
618 let ts_init = core.clock().timestamp_ns();
619
620 let strategy_id = order.strategy_id();
622
623 {
624 let cache_rc = core.cache_rc();
625 let mut cache = cache_rc.borrow_mut();
626 cache.add_order(order.clone(), position_id, client_id, true)?;
627 }
628
629 let command = SubmitOrder::new(
630 trader_id,
631 client_id,
632 strategy_id,
633 order.instrument_id(),
634 order.client_order_id(),
635 order.init_event().clone(),
636 order.exec_algorithm_id(),
637 position_id,
638 None, UUID4::new(),
640 ts_init,
641 );
642
643 if core.config.log_commands {
644 let id = &core.actor.actor_id;
645 log::info!("{id} {SEND}{CMD} {command:?}");
646 }
647
648 msgbus::send_trading_command(
649 MessagingSwitchboard::risk_engine_execute(),
650 TradingCommand::SubmitOrder(command),
651 );
652
653 Ok(())
654 }
655
656 fn modify_order(
662 &mut self,
663 order: &mut OrderAny,
664 quantity: Option<Quantity>,
665 price: Option<Price>,
666 trigger_price: Option<Price>,
667 client_id: Option<ClientId>,
668 ) -> anyhow::Result<()> {
669 let qty_changing = quantity.is_some_and(|q| q != order.quantity());
670 let price_changing = price.is_some() && price != order.price();
671 let trigger_changing = trigger_price.is_some() && trigger_price != order.trigger_price();
672
673 if !qty_changing && !price_changing && !trigger_changing {
674 log::error!(
675 "Cannot create command ModifyOrder: \
676 quantity, price and trigger were either None \
677 or the same as existing values"
678 );
679 return Ok(());
680 }
681
682 if order.is_closed() || order.is_pending_cancel() {
683 log::warn!(
684 "Cannot create command ModifyOrder: state is {:?}, {order:?}",
685 order.status()
686 );
687 return Ok(());
688 }
689
690 let core = self.core_mut();
691 let trader_id = core.trader_id().expect("Trader ID not set");
692 let strategy_id = order.strategy_id();
693
694 if !order.is_active_local() {
695 let event = self.generate_order_pending_update(order);
696 if let Err(e) = order.apply(OrderEventAny::PendingUpdate(event)) {
697 log::warn!("InvalidStateTrigger: {e}, did not apply pending update event");
698 return Ok(());
699 }
700
701 {
702 let cache_rc = self.core_mut().cache_rc();
703 let mut cache = cache_rc.borrow_mut();
704 cache.update_order(order).ok();
705 }
706
707 let topic = format!("events.order.{strategy_id}");
708 msgbus::publish_order_event(topic.into(), &OrderEventAny::PendingUpdate(event));
709 }
710
711 let ts_init = self.core_mut().clock().timestamp_ns();
712 let command = ModifyOrder::new(
713 trader_id,
714 client_id,
715 strategy_id,
716 order.instrument_id(),
717 order.client_order_id(),
718 order.venue_order_id(),
719 quantity,
720 price,
721 trigger_price,
722 UUID4::new(),
723 ts_init,
724 None, );
726
727 if self.core_mut().config.log_commands {
728 let id = &self.core_mut().actor.actor_id;
729 log::info!("{id} {SEND}{CMD} {command:?}");
730 }
731
732 let has_emulation_trigger = order
733 .emulation_trigger()
734 .is_some_and(|t| t != TriggerType::NoTrigger);
735
736 if order.is_emulated() || has_emulation_trigger {
737 msgbus::send_trading_command(
738 MessagingSwitchboard::order_emulator_execute(),
739 TradingCommand::ModifyOrder(command),
740 );
741 } else {
742 msgbus::send_trading_command(
743 MessagingSwitchboard::risk_engine_execute(),
744 TradingCommand::ModifyOrder(command),
745 );
746 }
747
748 Ok(())
749 }
750
751 fn modify_order_in_place(
763 &mut self,
764 order: &mut OrderAny,
765 quantity: Option<Quantity>,
766 price: Option<Price>,
767 trigger_price: Option<Price>,
768 ) -> anyhow::Result<()> {
769 let status = order.status();
771 if status != OrderStatus::Initialized && status != OrderStatus::Released {
772 anyhow::bail!(
773 "Cannot modify order in place: status is {status:?}, expected INITIALIZED or RELEASED"
774 );
775 }
776
777 if price.is_some() && order.price().is_none() {
779 anyhow::bail!(
780 "Cannot modify order in place: {} orders do not have a LIMIT price",
781 order.order_type()
782 );
783 }
784
785 if trigger_price.is_some() && order.trigger_price().is_none() {
786 anyhow::bail!(
787 "Cannot modify order in place: {} orders do not have a STOP trigger price",
788 order.order_type()
789 );
790 }
791
792 let qty_changing = quantity.is_some_and(|q| q != order.quantity());
794 let price_changing = price.is_some() && price != order.price();
795 let trigger_changing = trigger_price.is_some() && trigger_price != order.trigger_price();
796
797 if !qty_changing && !price_changing && !trigger_changing {
798 anyhow::bail!("Cannot modify order in place: no parameters differ from current values");
799 }
800
801 let core = self.core_mut();
802 let ts_now = core.clock().timestamp_ns();
803
804 let updated = OrderUpdated::new(
805 order.trader_id(),
806 order.strategy_id(),
807 order.instrument_id(),
808 order.client_order_id(),
809 quantity.unwrap_or_else(|| order.quantity()),
810 UUID4::new(),
811 ts_now,
812 ts_now,
813 false, order.venue_order_id(),
815 order.account_id(),
816 price,
817 trigger_price,
818 None, );
820
821 order
822 .apply(OrderEventAny::Updated(updated))
823 .map_err(|e| anyhow::anyhow!("Failed to apply OrderUpdated: {e}"))?;
824
825 let cache_rc = core.cache_rc();
826 let mut cache = cache_rc.borrow_mut();
827 cache.update_order(order)?;
828
829 Ok(())
830 }
831
832 fn cancel_order(
838 &mut self,
839 order: &mut OrderAny,
840 client_id: Option<ClientId>,
841 ) -> anyhow::Result<()> {
842 if order.is_closed() || order.is_pending_cancel() {
843 log::warn!(
844 "Cannot cancel order: state is {:?}, {order:?}",
845 order.status()
846 );
847 return Ok(());
848 }
849
850 let core = self.core_mut();
851 let trader_id = core.trader_id().expect("Trader ID not set");
852 let strategy_id = order.strategy_id();
853
854 if !order.is_active_local() {
855 let event = self.generate_order_pending_cancel(order);
856 if let Err(e) = order.apply(OrderEventAny::PendingCancel(event)) {
857 log::warn!("InvalidStateTrigger: {e}, did not apply pending cancel event");
858 return Ok(());
859 }
860
861 {
862 let cache_rc = self.core_mut().cache_rc();
863 let mut cache = cache_rc.borrow_mut();
864 cache.update_order(order).ok();
865 }
866
867 let topic = format!("events.order.{strategy_id}");
868 msgbus::publish_order_event(topic.into(), &OrderEventAny::PendingCancel(event));
869 }
870
871 let ts_init = self.core_mut().clock().timestamp_ns();
872 let command = CancelOrder::new(
873 trader_id,
874 client_id,
875 strategy_id,
876 order.instrument_id(),
877 order.client_order_id(),
878 order.venue_order_id(),
879 UUID4::new(),
880 ts_init,
881 None, );
883
884 if self.core_mut().config.log_commands {
885 let id = &self.core_mut().actor.actor_id;
886 log::info!("{id} {SEND}{CMD} {command:?}");
887 }
888
889 let has_emulation_trigger = order
890 .emulation_trigger()
891 .is_some_and(|t| t != TriggerType::NoTrigger);
892
893 if order.is_emulated() || order.status() == OrderStatus::Released || has_emulation_trigger {
894 msgbus::send_trading_command(
895 MessagingSwitchboard::order_emulator_execute(),
896 TradingCommand::CancelOrder(command),
897 );
898 } else {
899 msgbus::send_trading_command(
900 MessagingSwitchboard::exec_engine_execute(),
901 TradingCommand::CancelOrder(command),
902 );
903 }
904
905 Ok(())
906 }
907
908 fn subscribe_to_strategy_events(&mut self, strategy_id: StrategyId)
912 where
913 Self: 'static + std::fmt::Debug + Sized,
914 {
915 let core = self.core_mut();
916 if core.is_strategy_subscribed(&strategy_id) {
917 return;
918 }
919
920 let actor_id = core.actor.actor_id.inner();
921
922 let order_topic = format!("events.order.{strategy_id}");
923 let order_actor_id = actor_id;
924 let order_handler = TypedHandler::from(move |event: &OrderEventAny| {
925 if let Some(mut algo) = try_get_actor_unchecked::<Self>(&order_actor_id) {
926 algo.handle_order_event(event.clone());
927 } else {
928 log::error!(
929 "ExecutionAlgorithm {order_actor_id} not found for order event handling"
930 );
931 }
932 });
933 msgbus::subscribe_order_events(order_topic.clone().into(), order_handler.clone(), None);
934
935 let position_topic = format!("events.position.{strategy_id}");
936 let position_handler = TypedHandler::from(move |event: &PositionEvent| {
937 if let Some(mut algo) = try_get_actor_unchecked::<Self>(&actor_id) {
938 algo.handle_position_event(event.clone());
939 } else {
940 log::error!("ExecutionAlgorithm {actor_id} not found for position event handling");
941 }
942 });
943 msgbus::subscribe_position_events(
944 position_topic.clone().into(),
945 position_handler.clone(),
946 None,
947 );
948
949 let handlers = StrategyEventHandlers {
950 order_topic,
951 order_handler,
952 position_topic,
953 position_handler,
954 };
955 core.store_strategy_event_handlers(strategy_id, handlers);
956
957 core.add_subscribed_strategy(strategy_id);
958 log::info!("Subscribed to events for strategy {strategy_id}");
959 }
960
961 fn unsubscribe_all_strategy_events(&mut self) {
965 let handlers = self.core_mut().take_strategy_event_handlers();
966 for (strategy_id, h) in handlers {
967 msgbus::unsubscribe_order_events(h.order_topic.into(), &h.order_handler);
968 msgbus::unsubscribe_position_events(h.position_topic.into(), &h.position_handler);
969 log::info!("Unsubscribed from events for strategy {strategy_id}");
970 }
971 self.core_mut().clear_subscribed_strategies();
972 }
973
974 fn handle_order_event(&mut self, event: OrderEventAny) {
976 if self.core_mut().state() != ComponentState::Running {
977 return;
978 }
979
980 let order = {
981 let cache = self.core_mut().cache();
982 cache.order(&event.client_order_id()).cloned()
983 };
984
985 let Some(order) = order else {
986 return;
987 };
988
989 let Some(order_algo_id) = order.exec_algorithm_id() else {
990 return;
991 };
992
993 if order_algo_id != self.id() {
994 return;
995 }
996
997 {
998 let core = self.core_mut();
999 if core.config.log_events {
1000 let id = &core.actor.actor_id;
1001 log::info!("{id} {RECV}{EVT} {event}");
1002 }
1003 }
1004
1005 match &event {
1006 OrderEventAny::Initialized(e) => self.on_order_initialized(e.clone()),
1007 OrderEventAny::Denied(e) => {
1008 self.restore_primary_order_quantity(&order);
1009 self.on_order_denied(*e);
1010 }
1011 OrderEventAny::Emulated(e) => self.on_order_emulated(*e),
1012 OrderEventAny::Released(e) => self.on_order_released(*e),
1013 OrderEventAny::Submitted(e) => self.on_order_submitted(*e),
1014 OrderEventAny::Rejected(e) => {
1015 self.restore_primary_order_quantity(&order);
1016 self.on_order_rejected(*e);
1017 }
1018 OrderEventAny::Accepted(e) => {
1019 self.core_mut()
1021 .take_pending_spawn_reduction(&order.client_order_id());
1022 self.on_order_accepted(*e);
1023 }
1024 OrderEventAny::Canceled(e) => {
1025 self.core_mut()
1026 .take_pending_spawn_reduction(&order.client_order_id());
1027 self.on_algo_order_canceled(*e);
1028 }
1029 OrderEventAny::Expired(e) => {
1030 self.core_mut()
1031 .take_pending_spawn_reduction(&order.client_order_id());
1032 self.on_order_expired(*e);
1033 }
1034 OrderEventAny::Triggered(e) => self.on_order_triggered(*e),
1035 OrderEventAny::PendingUpdate(e) => self.on_order_pending_update(*e),
1036 OrderEventAny::PendingCancel(e) => self.on_order_pending_cancel(*e),
1037 OrderEventAny::ModifyRejected(e) => self.on_order_modify_rejected(*e),
1038 OrderEventAny::CancelRejected(e) => self.on_order_cancel_rejected(*e),
1039 OrderEventAny::Updated(e) => self.on_order_updated(*e),
1040 OrderEventAny::Filled(e) => self.on_algo_order_filled(*e),
1041 }
1042
1043 self.on_order_event(event);
1044 }
1045
1046 fn handle_position_event(&mut self, event: PositionEvent) {
1048 if self.core_mut().state() != ComponentState::Running {
1049 return;
1050 }
1051
1052 {
1053 let core = self.core_mut();
1054 if core.config.log_events {
1055 let id = &core.actor.actor_id;
1056 log::info!("{id} {RECV}{EVT} {event:?}");
1057 }
1058 }
1059
1060 match &event {
1061 PositionEvent::PositionOpened(e) => self.on_position_opened(e.clone()),
1062 PositionEvent::PositionChanged(e) => self.on_position_changed(e.clone()),
1063 PositionEvent::PositionClosed(e) => self.on_position_closed(e.clone()),
1064 PositionEvent::PositionAdjusted(_) => {}
1065 }
1066
1067 self.on_position_event(event);
1068 }
1069
1070 fn on_start(&mut self) -> anyhow::Result<()> {
1078 let id = self.id();
1079 log::info!("Starting {id}");
1080 Ok(())
1081 }
1082
1083 fn on_stop(&mut self) -> anyhow::Result<()> {
1089 Ok(())
1090 }
1091
1092 fn on_reset(&mut self) -> anyhow::Result<()> {
1098 self.unsubscribe_all_strategy_events();
1099 self.core_mut().reset();
1100 Ok(())
1101 }
1102
1103 fn on_time_event(&mut self, _event: &TimeEvent) -> anyhow::Result<()> {
1111 Ok(())
1112 }
1113
1114 #[allow(unused_variables)]
1116 fn on_order_initialized(&mut self, event: OrderInitialized) {}
1117
1118 #[allow(unused_variables)]
1120 fn on_order_denied(&mut self, event: OrderDenied) {}
1121
1122 #[allow(unused_variables)]
1124 fn on_order_emulated(&mut self, event: OrderEmulated) {}
1125
1126 #[allow(unused_variables)]
1128 fn on_order_released(&mut self, event: OrderReleased) {}
1129
1130 #[allow(unused_variables)]
1132 fn on_order_submitted(&mut self, event: OrderSubmitted) {}
1133
1134 #[allow(unused_variables)]
1136 fn on_order_rejected(&mut self, event: OrderRejected) {}
1137
1138 #[allow(unused_variables)]
1140 fn on_order_accepted(&mut self, event: OrderAccepted) {}
1141
1142 #[allow(unused_variables)]
1144 fn on_algo_order_canceled(&mut self, event: OrderCanceled) {}
1145
1146 #[allow(unused_variables)]
1148 fn on_order_expired(&mut self, event: OrderExpired) {}
1149
1150 #[allow(unused_variables)]
1152 fn on_order_triggered(&mut self, event: OrderTriggered) {}
1153
1154 #[allow(unused_variables)]
1156 fn on_order_pending_update(&mut self, event: OrderPendingUpdate) {}
1157
1158 #[allow(unused_variables)]
1160 fn on_order_pending_cancel(&mut self, event: OrderPendingCancel) {}
1161
1162 #[allow(unused_variables)]
1164 fn on_order_modify_rejected(&mut self, event: OrderModifyRejected) {}
1165
1166 #[allow(unused_variables)]
1168 fn on_order_cancel_rejected(&mut self, event: OrderCancelRejected) {}
1169
1170 #[allow(unused_variables)]
1172 fn on_order_updated(&mut self, event: OrderUpdated) {}
1173
1174 #[allow(unused_variables)]
1176 fn on_algo_order_filled(&mut self, event: OrderFilled) {}
1177
1178 #[allow(unused_variables)]
1180 fn on_order_event(&mut self, event: OrderEventAny) {}
1181
1182 #[allow(unused_variables)]
1184 fn on_position_opened(&mut self, event: PositionOpened) {}
1185
1186 #[allow(unused_variables)]
1188 fn on_position_changed(&mut self, event: PositionChanged) {}
1189
1190 #[allow(unused_variables)]
1192 fn on_position_closed(&mut self, event: PositionClosed) {}
1193
1194 #[allow(unused_variables)]
1196 fn on_position_event(&mut self, event: PositionEvent) {}
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201 use std::{
1202 cell::RefCell,
1203 ops::{Deref, DerefMut},
1204 rc::Rc,
1205 };
1206
1207 use nautilus_common::{
1208 actor::{DataActor, DataActorCore},
1209 cache::Cache,
1210 clock::TestClock,
1211 component::Component,
1212 enums::ComponentTrigger,
1213 };
1214 use nautilus_model::{
1215 enums::OrderSide,
1216 events::{OrderAccepted, OrderCanceled, OrderDenied, OrderRejected},
1217 identifiers::{
1218 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, StrategyId, TraderId,
1219 VenueOrderId,
1220 },
1221 orders::{LimitOrder, MarketOrder, OrderAny, stubs::TestOrderStubs},
1222 types::{Price, Quantity},
1223 };
1224 use rstest::rstest;
1225
1226 use super::*;
1227
1228 #[derive(Debug)]
1229 struct TestAlgorithm {
1230 core: ExecutionAlgorithmCore,
1231 on_order_called: bool,
1232 last_order_client_id: Option<ClientOrderId>,
1233 }
1234
1235 impl TestAlgorithm {
1236 fn new(config: ExecutionAlgorithmConfig) -> Self {
1237 Self {
1238 core: ExecutionAlgorithmCore::new(config),
1239 on_order_called: false,
1240 last_order_client_id: None,
1241 }
1242 }
1243 }
1244
1245 impl Deref for TestAlgorithm {
1246 type Target = DataActorCore;
1247 fn deref(&self) -> &Self::Target {
1248 &self.core.actor
1249 }
1250 }
1251
1252 impl DerefMut for TestAlgorithm {
1253 fn deref_mut(&mut self) -> &mut Self::Target {
1254 &mut self.core.actor
1255 }
1256 }
1257
1258 impl DataActor for TestAlgorithm {}
1259
1260 impl ExecutionAlgorithm for TestAlgorithm {
1261 fn core_mut(&mut self) -> &mut ExecutionAlgorithmCore {
1262 &mut self.core
1263 }
1264
1265 fn on_order(&mut self, order: OrderAny) -> anyhow::Result<()> {
1266 self.on_order_called = true;
1267 self.last_order_client_id = Some(order.client_order_id());
1268 Ok(())
1269 }
1270 }
1271
1272 fn create_test_algorithm() -> TestAlgorithm {
1273 let unique_id = format!("TEST-{}", UUID4::new());
1275 let config = ExecutionAlgorithmConfig {
1276 exec_algorithm_id: Some(ExecAlgorithmId::new(&unique_id)),
1277 ..Default::default()
1278 };
1279 TestAlgorithm::new(config)
1280 }
1281
1282 fn register_algorithm(algo: &mut TestAlgorithm) {
1283 let trader_id = TraderId::from("TRADER-001");
1284 let clock = Rc::new(RefCell::new(TestClock::new()));
1285 let cache = Rc::new(RefCell::new(Cache::default()));
1286
1287 algo.core.register(trader_id, clock, cache).unwrap();
1288
1289 algo.transition_state(ComponentTrigger::Initialize).unwrap();
1291 algo.transition_state(ComponentTrigger::Start).unwrap();
1292 algo.transition_state(ComponentTrigger::StartCompleted)
1293 .unwrap();
1294 }
1295
1296 #[rstest]
1297 fn test_algorithm_creation() {
1298 let algo = create_test_algorithm();
1299 assert!(algo.core.exec_algorithm_id.inner().starts_with("TEST-"));
1300 assert!(!algo.on_order_called);
1301 assert!(algo.last_order_client_id.is_none());
1302 }
1303
1304 #[rstest]
1305 fn test_algorithm_registration() {
1306 let mut algo = create_test_algorithm();
1307 register_algorithm(&mut algo);
1308
1309 assert!(algo.core.trader_id().is_some());
1310 assert_eq!(algo.core.trader_id(), Some(TraderId::from("TRADER-001")));
1311 }
1312
1313 #[rstest]
1314 fn test_algorithm_id() {
1315 let mut algo = create_test_algorithm();
1316 assert!(algo.id().inner().starts_with("TEST-"));
1317 }
1318
1319 #[rstest]
1320 fn test_algorithm_spawn_market_creates_valid_order() {
1321 let mut algo = create_test_algorithm();
1322 register_algorithm(&mut algo);
1323
1324 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1325 let mut primary = OrderAny::Market(MarketOrder::new(
1326 TraderId::from("TRADER-001"),
1327 StrategyId::from("STRAT-001"),
1328 instrument_id,
1329 ClientOrderId::from("O-001"),
1330 OrderSide::Buy,
1331 Quantity::from("1.0"),
1332 TimeInForce::Gtc,
1333 UUID4::new(),
1334 0.into(),
1335 false, false, None, None, None, None, None, None, None, None, ));
1346
1347 let spawned = algo.spawn_market(
1348 &mut primary,
1349 Quantity::from("0.5"),
1350 TimeInForce::Ioc,
1351 false,
1352 None, false, );
1355
1356 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1357 assert_eq!(spawned.instrument_id, instrument_id);
1358 assert_eq!(spawned.order_side(), OrderSide::Buy);
1359 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1360 assert_eq!(spawned.time_in_force, TimeInForce::Ioc);
1361 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1362 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1363 }
1364
1365 #[rstest]
1366 fn test_algorithm_spawn_increments_sequence() {
1367 let mut algo = create_test_algorithm();
1368 register_algorithm(&mut algo);
1369
1370 let mut primary = OrderAny::Market(MarketOrder::new(
1371 TraderId::from("TRADER-001"),
1372 StrategyId::from("STRAT-001"),
1373 InstrumentId::from("BTC/USDT.BINANCE"),
1374 ClientOrderId::from("O-001"),
1375 OrderSide::Buy,
1376 Quantity::from("1.0"),
1377 TimeInForce::Gtc,
1378 UUID4::new(),
1379 0.into(),
1380 false,
1381 false,
1382 None,
1383 None,
1384 None,
1385 None,
1386 None,
1387 None,
1388 None,
1389 None,
1390 ));
1391
1392 let spawned1 = algo.spawn_market(
1393 &mut primary,
1394 Quantity::from("0.25"),
1395 TimeInForce::Ioc,
1396 false,
1397 None,
1398 false,
1399 );
1400 let spawned2 = algo.spawn_market(
1401 &mut primary,
1402 Quantity::from("0.25"),
1403 TimeInForce::Ioc,
1404 false,
1405 None,
1406 false,
1407 );
1408 let spawned3 = algo.spawn_market(
1409 &mut primary,
1410 Quantity::from("0.25"),
1411 TimeInForce::Ioc,
1412 false,
1413 None,
1414 false,
1415 );
1416
1417 assert_eq!(spawned1.client_order_id.as_str(), "O-001-E1");
1418 assert_eq!(spawned2.client_order_id.as_str(), "O-001-E2");
1419 assert_eq!(spawned3.client_order_id.as_str(), "O-001-E3");
1420 }
1421
1422 #[rstest]
1423 fn test_algorithm_default_handlers_do_not_panic() {
1424 let mut algo = create_test_algorithm();
1425
1426 algo.on_order_initialized(Default::default());
1427 algo.on_order_denied(Default::default());
1428 algo.on_order_emulated(Default::default());
1429 algo.on_order_released(Default::default());
1430 algo.on_order_submitted(Default::default());
1431 algo.on_order_rejected(Default::default());
1432 algo.on_order_accepted(Default::default());
1433 algo.on_algo_order_canceled(Default::default());
1434 algo.on_order_expired(Default::default());
1435 algo.on_order_triggered(Default::default());
1436 algo.on_order_pending_update(Default::default());
1437 algo.on_order_pending_cancel(Default::default());
1438 algo.on_order_modify_rejected(Default::default());
1439 algo.on_order_cancel_rejected(Default::default());
1440 algo.on_order_updated(Default::default());
1441 algo.on_algo_order_filled(Default::default());
1442 }
1443
1444 #[rstest]
1445 fn test_strategy_subscription_tracking() {
1446 let mut algo = create_test_algorithm();
1447 let strategy_id = StrategyId::from("STRAT-001");
1448
1449 assert!(!algo.core.is_strategy_subscribed(&strategy_id));
1450
1451 algo.subscribe_to_strategy_events(strategy_id);
1452 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1453
1454 algo.subscribe_to_strategy_events(strategy_id);
1456 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1457 }
1458
1459 #[rstest]
1460 fn test_algorithm_reset() {
1461 let mut algo = create_test_algorithm();
1462 let strategy_id = StrategyId::from("STRAT-001");
1463 let primary_id = ClientOrderId::new("O-001");
1464
1465 let _ = algo.core.spawn_client_order_id(&primary_id);
1466 algo.core.add_subscribed_strategy(strategy_id);
1467
1468 assert!(algo.core.spawn_sequence(&primary_id).is_some());
1469 assert!(algo.core.is_strategy_subscribed(&strategy_id));
1470
1471 ExecutionAlgorithm::on_reset(&mut algo).unwrap();
1472
1473 assert!(algo.core.spawn_sequence(&primary_id).is_none());
1474 assert!(!algo.core.is_strategy_subscribed(&strategy_id));
1475 }
1476
1477 #[rstest]
1478 fn test_algorithm_spawn_limit_creates_valid_order() {
1479 let mut algo = create_test_algorithm();
1480 register_algorithm(&mut algo);
1481
1482 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1483 let mut primary = OrderAny::Market(MarketOrder::new(
1484 TraderId::from("TRADER-001"),
1485 StrategyId::from("STRAT-001"),
1486 instrument_id,
1487 ClientOrderId::from("O-001"),
1488 OrderSide::Buy,
1489 Quantity::from("1.0"),
1490 TimeInForce::Gtc,
1491 UUID4::new(),
1492 0.into(),
1493 false,
1494 false,
1495 None,
1496 None,
1497 None,
1498 None,
1499 None,
1500 None,
1501 None,
1502 None,
1503 ));
1504
1505 let price = Price::from("50000.0");
1506 let spawned = algo.spawn_limit(
1507 &mut primary,
1508 Quantity::from("0.5"),
1509 price,
1510 TimeInForce::Gtc,
1511 None, false, false, None, None, None, false, );
1519
1520 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1521 assert_eq!(spawned.instrument_id, instrument_id);
1522 assert_eq!(spawned.order_side(), OrderSide::Buy);
1523 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1524 assert_eq!(spawned.price, price);
1525 assert_eq!(spawned.time_in_force, TimeInForce::Gtc);
1526 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1527 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1528 }
1529
1530 #[rstest]
1531 fn test_algorithm_spawn_market_to_limit_creates_valid_order() {
1532 let mut algo = create_test_algorithm();
1533 register_algorithm(&mut algo);
1534
1535 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1536 let mut primary = OrderAny::Market(MarketOrder::new(
1537 TraderId::from("TRADER-001"),
1538 StrategyId::from("STRAT-001"),
1539 instrument_id,
1540 ClientOrderId::from("O-001"),
1541 OrderSide::Buy,
1542 Quantity::from("1.0"),
1543 TimeInForce::Gtc,
1544 UUID4::new(),
1545 0.into(),
1546 false,
1547 false,
1548 None,
1549 None,
1550 None,
1551 None,
1552 None,
1553 None,
1554 None,
1555 None,
1556 ));
1557
1558 let spawned = algo.spawn_market_to_limit(
1559 &mut primary,
1560 Quantity::from("0.5"),
1561 TimeInForce::Gtc,
1562 None, false, None, None, None, false, );
1569
1570 assert_eq!(spawned.client_order_id.as_str(), "O-001-E1");
1571 assert_eq!(spawned.instrument_id, instrument_id);
1572 assert_eq!(spawned.order_side(), OrderSide::Buy);
1573 assert_eq!(spawned.quantity, Quantity::from("0.5"));
1574 assert_eq!(spawned.time_in_force, TimeInForce::Gtc);
1575 assert_eq!(spawned.exec_algorithm_id, Some(algo.id()));
1576 assert_eq!(spawned.exec_spawn_id, Some(ClientOrderId::from("O-001")));
1577 }
1578
1579 #[rstest]
1580 fn test_algorithm_spawn_market_with_tags() {
1581 let mut algo = create_test_algorithm();
1582 register_algorithm(&mut algo);
1583
1584 let mut primary = OrderAny::Market(MarketOrder::new(
1585 TraderId::from("TRADER-001"),
1586 StrategyId::from("STRAT-001"),
1587 InstrumentId::from("BTC/USDT.BINANCE"),
1588 ClientOrderId::from("O-001"),
1589 OrderSide::Buy,
1590 Quantity::from("1.0"),
1591 TimeInForce::Gtc,
1592 UUID4::new(),
1593 0.into(),
1594 false,
1595 false,
1596 None,
1597 None,
1598 None,
1599 None,
1600 None,
1601 None,
1602 None,
1603 None,
1604 ));
1605
1606 let tags = vec![ustr::Ustr::from("TAG1"), ustr::Ustr::from("TAG2")];
1607 let spawned = algo.spawn_market(
1608 &mut primary,
1609 Quantity::from("0.5"),
1610 TimeInForce::Ioc,
1611 false,
1612 Some(tags.clone()),
1613 false,
1614 );
1615
1616 assert_eq!(spawned.tags, Some(tags));
1617 }
1618
1619 #[rstest]
1620 fn test_algorithm_reduce_primary_order() {
1621 let mut algo = create_test_algorithm();
1622 register_algorithm(&mut algo);
1623
1624 let order = OrderAny::Market(MarketOrder::new(
1625 TraderId::from("TRADER-001"),
1626 StrategyId::from("STRAT-001"),
1627 InstrumentId::from("BTC/USDT.BINANCE"),
1628 ClientOrderId::from("O-001"),
1629 OrderSide::Buy,
1630 Quantity::from("1.0"),
1631 TimeInForce::Gtc,
1632 UUID4::new(),
1633 0.into(),
1634 false,
1635 false,
1636 None,
1637 None,
1638 None,
1639 None,
1640 None,
1641 None,
1642 None,
1643 None,
1644 ));
1645
1646 let mut primary = TestOrderStubs::make_accepted_order(&order);
1648
1649 {
1650 let cache_rc = algo.core.cache_rc();
1651 let mut cache = cache_rc.borrow_mut();
1652 cache.add_order(primary.clone(), None, None, false).unwrap();
1653 }
1654
1655 let spawn_qty = Quantity::from("0.3");
1656 algo.reduce_primary_order(&mut primary, spawn_qty);
1657
1658 assert_eq!(primary.quantity(), Quantity::from("0.7"));
1659 }
1660
1661 #[rstest]
1662 fn test_algorithm_spawn_market_with_reduce_primary() {
1663 let mut algo = create_test_algorithm();
1664 register_algorithm(&mut algo);
1665
1666 let order = OrderAny::Market(MarketOrder::new(
1667 TraderId::from("TRADER-001"),
1668 StrategyId::from("STRAT-001"),
1669 InstrumentId::from("BTC/USDT.BINANCE"),
1670 ClientOrderId::from("O-001"),
1671 OrderSide::Buy,
1672 Quantity::from("1.0"),
1673 TimeInForce::Gtc,
1674 UUID4::new(),
1675 0.into(),
1676 false,
1677 false,
1678 None,
1679 None,
1680 None,
1681 None,
1682 None,
1683 None,
1684 None,
1685 None,
1686 ));
1687
1688 let mut primary = TestOrderStubs::make_accepted_order(&order);
1690
1691 {
1692 let cache_rc = algo.core.cache_rc();
1693 let mut cache = cache_rc.borrow_mut();
1694 cache.add_order(primary.clone(), None, None, false).unwrap();
1695 }
1696
1697 let spawned = algo.spawn_market(
1698 &mut primary,
1699 Quantity::from("0.4"),
1700 TimeInForce::Ioc,
1701 false,
1702 None,
1703 true, );
1705
1706 assert_eq!(spawned.quantity, Quantity::from("0.4"));
1707 assert_eq!(primary.quantity(), Quantity::from("0.6"));
1708 }
1709
1710 #[rstest]
1711 fn test_algorithm_generate_order_canceled() {
1712 let mut algo = create_test_algorithm();
1713 register_algorithm(&mut algo);
1714
1715 let order = OrderAny::Market(MarketOrder::new(
1716 TraderId::from("TRADER-001"),
1717 StrategyId::from("STRAT-001"),
1718 InstrumentId::from("BTC/USDT.BINANCE"),
1719 ClientOrderId::from("O-001"),
1720 OrderSide::Buy,
1721 Quantity::from("1.0"),
1722 TimeInForce::Gtc,
1723 UUID4::new(),
1724 0.into(),
1725 false,
1726 false,
1727 None,
1728 None,
1729 None,
1730 None,
1731 None,
1732 None,
1733 None,
1734 None,
1735 ));
1736
1737 let event = algo.generate_order_canceled(&order);
1738
1739 assert_eq!(event.trader_id, TraderId::from("TRADER-001"));
1740 assert_eq!(event.strategy_id, StrategyId::from("STRAT-001"));
1741 assert_eq!(event.instrument_id, InstrumentId::from("BTC/USDT.BINANCE"));
1742 assert_eq!(event.client_order_id, ClientOrderId::from("O-001"));
1743 }
1744
1745 #[rstest]
1746 fn test_algorithm_modify_order_in_place_updates_quantity() {
1747 let mut algo = create_test_algorithm();
1748 register_algorithm(&mut algo);
1749
1750 let mut order = OrderAny::Limit(LimitOrder::new(
1751 TraderId::from("TRADER-001"),
1752 StrategyId::from("STRAT-001"),
1753 InstrumentId::from("BTC/USDT.BINANCE"),
1754 ClientOrderId::from("O-001"),
1755 OrderSide::Buy,
1756 Quantity::from("1.0"),
1757 Price::from("50000.0"),
1758 TimeInForce::Gtc,
1759 None, false, false, false, None, None, None, None, None, None, None, None, None, None, None, UUID4::new(),
1775 0.into(),
1776 ));
1777
1778 {
1779 let cache_rc = algo.core.cache_rc();
1780 let mut cache = cache_rc.borrow_mut();
1781 cache.add_order(order.clone(), None, None, false).unwrap();
1782 }
1783
1784 let new_qty = Quantity::from("0.5");
1785 algo.modify_order_in_place(&mut order, Some(new_qty), None, None)
1786 .unwrap();
1787
1788 assert_eq!(order.quantity(), new_qty);
1789 }
1790
1791 #[rstest]
1792 fn test_algorithm_modify_order_in_place_rejects_no_changes() {
1793 let mut algo = create_test_algorithm();
1794 register_algorithm(&mut algo);
1795
1796 let mut order = OrderAny::Limit(LimitOrder::new(
1797 TraderId::from("TRADER-001"),
1798 StrategyId::from("STRAT-001"),
1799 InstrumentId::from("BTC/USDT.BINANCE"),
1800 ClientOrderId::from("O-001"),
1801 OrderSide::Buy,
1802 Quantity::from("1.0"),
1803 Price::from("50000.0"),
1804 TimeInForce::Gtc,
1805 None,
1806 false,
1807 false,
1808 false,
1809 None,
1810 None,
1811 None,
1812 None,
1813 None,
1814 None,
1815 None,
1816 None,
1817 None,
1818 None,
1819 None,
1820 UUID4::new(),
1821 0.into(),
1822 ));
1823
1824 let result =
1826 algo.modify_order_in_place(&mut order, Some(Quantity::from("1.0")), None, None);
1827
1828 assert!(result.is_err());
1829 assert!(
1830 result
1831 .unwrap_err()
1832 .to_string()
1833 .contains("no parameters differ")
1834 );
1835 }
1836
1837 #[rstest]
1838 fn test_spawned_order_denied_restores_primary_quantity() {
1839 let mut algo = create_test_algorithm();
1840 register_algorithm(&mut algo);
1841
1842 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1843 let exec_algorithm_id = algo.id();
1844
1845 let mut primary = OrderAny::Market(MarketOrder::new(
1846 TraderId::from("TRADER-001"),
1847 StrategyId::from("STRAT-001"),
1848 instrument_id,
1849 ClientOrderId::from("O-001"),
1850 OrderSide::Buy,
1851 Quantity::from("1.0"),
1852 TimeInForce::Gtc,
1853 UUID4::new(),
1854 0.into(),
1855 false,
1856 false,
1857 None,
1858 None,
1859 None,
1860 None,
1861 Some(exec_algorithm_id),
1862 None,
1863 None,
1864 None,
1865 ));
1866
1867 {
1868 let cache_rc = algo.core.cache_rc();
1869 let mut cache = cache_rc.borrow_mut();
1870 cache.add_order(primary.clone(), None, None, false).unwrap();
1871 }
1872
1873 let spawned = algo.spawn_market(
1874 &mut primary,
1875 Quantity::from("0.5"),
1876 TimeInForce::Fok,
1877 false,
1878 None,
1879 true,
1880 );
1881
1882 {
1883 let cache_rc = algo.core.cache_rc();
1884 let mut cache = cache_rc.borrow_mut();
1885 cache.update_order(&primary).unwrap();
1886 }
1887
1888 assert_eq!(primary.quantity(), Quantity::from("0.5"));
1889
1890 let mut spawned_order = OrderAny::Market(spawned);
1891 {
1892 let cache_rc = algo.core.cache_rc();
1893 let mut cache = cache_rc.borrow_mut();
1894 cache
1895 .add_order(spawned_order.clone(), None, None, false)
1896 .unwrap();
1897 }
1898
1899 let denied = OrderDenied::new(
1900 spawned_order.trader_id(),
1901 spawned_order.strategy_id(),
1902 spawned_order.instrument_id(),
1903 spawned_order.client_order_id(),
1904 "TEST_DENIAL".into(),
1905 UUID4::new(),
1906 0.into(),
1907 0.into(),
1908 );
1909
1910 spawned_order.apply(OrderEventAny::Denied(denied)).unwrap();
1911 {
1912 let cache_rc = algo.core.cache_rc();
1913 let mut cache = cache_rc.borrow_mut();
1914 cache.update_order(&spawned_order).unwrap();
1915 }
1916
1917 algo.handle_order_event(OrderEventAny::Denied(denied));
1918
1919 let restored_primary = {
1920 let cache = algo.core.cache();
1921 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
1922 };
1923 assert_eq!(restored_primary.quantity(), Quantity::from("1.0"));
1924 }
1925
1926 #[rstest]
1927 fn test_spawned_order_rejected_restores_primary_quantity() {
1928 let mut algo = create_test_algorithm();
1929 register_algorithm(&mut algo);
1930
1931 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
1932 let exec_algorithm_id = algo.id();
1933
1934 let mut primary = OrderAny::Market(MarketOrder::new(
1935 TraderId::from("TRADER-001"),
1936 StrategyId::from("STRAT-001"),
1937 instrument_id,
1938 ClientOrderId::from("O-001"),
1939 OrderSide::Buy,
1940 Quantity::from("1.0"),
1941 TimeInForce::Gtc,
1942 UUID4::new(),
1943 0.into(),
1944 false,
1945 false,
1946 None,
1947 None,
1948 None,
1949 None,
1950 Some(exec_algorithm_id),
1951 None,
1952 None,
1953 None,
1954 ));
1955
1956 {
1957 let cache_rc = algo.core.cache_rc();
1958 let mut cache = cache_rc.borrow_mut();
1959 cache.add_order(primary.clone(), None, None, false).unwrap();
1960 }
1961
1962 let spawned = algo.spawn_market(
1963 &mut primary,
1964 Quantity::from("0.5"),
1965 TimeInForce::Fok,
1966 false,
1967 None,
1968 true,
1969 );
1970
1971 {
1972 let cache_rc = algo.core.cache_rc();
1973 let mut cache = cache_rc.borrow_mut();
1974 cache.update_order(&primary).unwrap();
1975 }
1976
1977 assert_eq!(primary.quantity(), Quantity::from("0.5"));
1978
1979 let mut spawned_order = OrderAny::Market(spawned);
1980 {
1981 let cache_rc = algo.core.cache_rc();
1982 let mut cache = cache_rc.borrow_mut();
1983 cache
1984 .add_order(spawned_order.clone(), None, None, false)
1985 .unwrap();
1986 }
1987
1988 let rejected = OrderRejected::new(
1989 spawned_order.trader_id(),
1990 spawned_order.strategy_id(),
1991 spawned_order.instrument_id(),
1992 spawned_order.client_order_id(),
1993 AccountId::from("BINANCE-001"),
1994 "TEST_REJECTION".into(),
1995 UUID4::new(),
1996 0.into(),
1997 0.into(),
1998 false,
1999 false,
2000 );
2001
2002 spawned_order
2003 .apply(OrderEventAny::Rejected(rejected))
2004 .unwrap();
2005 {
2006 let cache_rc = algo.core.cache_rc();
2007 let mut cache = cache_rc.borrow_mut();
2008 cache.update_order(&spawned_order).unwrap();
2009 }
2010
2011 algo.handle_order_event(OrderEventAny::Rejected(rejected));
2012
2013 let restored_primary = {
2014 let cache = algo.core.cache();
2015 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2016 };
2017 assert_eq!(restored_primary.quantity(), Quantity::from("1.0"));
2018 }
2019
2020 #[rstest]
2021 fn test_spawned_order_with_reduce_primary_false_does_not_restore() {
2022 let mut algo = create_test_algorithm();
2023 register_algorithm(&mut algo);
2024
2025 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2026 let exec_algorithm_id = algo.id();
2027
2028 let mut primary = OrderAny::Market(MarketOrder::new(
2029 TraderId::from("TRADER-001"),
2030 StrategyId::from("STRAT-001"),
2031 instrument_id,
2032 ClientOrderId::from("O-001"),
2033 OrderSide::Buy,
2034 Quantity::from("1.0"),
2035 TimeInForce::Gtc,
2036 UUID4::new(),
2037 0.into(),
2038 false,
2039 false,
2040 None,
2041 None,
2042 None,
2043 None,
2044 Some(exec_algorithm_id),
2045 None,
2046 None,
2047 None,
2048 ));
2049
2050 {
2051 let cache_rc = algo.core.cache_rc();
2052 let mut cache = cache_rc.borrow_mut();
2053 cache.add_order(primary.clone(), None, None, false).unwrap();
2054 }
2055
2056 let spawned = algo.spawn_market(
2057 &mut primary,
2058 Quantity::from("0.5"),
2059 TimeInForce::Fok,
2060 false,
2061 None,
2062 false,
2063 );
2064
2065 assert_eq!(primary.quantity(), Quantity::from("1.0"));
2066
2067 let mut spawned_order = OrderAny::Market(spawned);
2068 {
2069 let cache_rc = algo.core.cache_rc();
2070 let mut cache = cache_rc.borrow_mut();
2071 cache
2072 .add_order(spawned_order.clone(), None, None, false)
2073 .unwrap();
2074 }
2075
2076 let denied = OrderDenied::new(
2077 spawned_order.trader_id(),
2078 spawned_order.strategy_id(),
2079 spawned_order.instrument_id(),
2080 spawned_order.client_order_id(),
2081 "TEST_DENIAL".into(),
2082 UUID4::new(),
2083 0.into(),
2084 0.into(),
2085 );
2086
2087 spawned_order.apply(OrderEventAny::Denied(denied)).unwrap();
2088 {
2089 let cache_rc = algo.core.cache_rc();
2090 let mut cache = cache_rc.borrow_mut();
2091 cache.update_order(&spawned_order).unwrap();
2092 }
2093
2094 algo.handle_order_event(OrderEventAny::Denied(denied));
2095
2096 let final_primary = {
2097 let cache = algo.core.cache();
2098 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2099 };
2100 assert_eq!(final_primary.quantity(), Quantity::from("1.0"));
2101 }
2102
2103 #[rstest]
2104 fn test_multiple_spawns_with_one_denied_restores_correctly() {
2105 let mut algo = create_test_algorithm();
2106 register_algorithm(&mut algo);
2107
2108 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2109 let exec_algorithm_id = algo.id();
2110
2111 let mut primary = OrderAny::Market(MarketOrder::new(
2112 TraderId::from("TRADER-001"),
2113 StrategyId::from("STRAT-001"),
2114 instrument_id,
2115 ClientOrderId::from("O-001"),
2116 OrderSide::Buy,
2117 Quantity::from("1.0"),
2118 TimeInForce::Gtc,
2119 UUID4::new(),
2120 0.into(),
2121 false,
2122 false,
2123 None,
2124 None,
2125 None,
2126 None,
2127 Some(exec_algorithm_id),
2128 None,
2129 None,
2130 None,
2131 ));
2132
2133 {
2134 let cache_rc = algo.core.cache_rc();
2135 let mut cache = cache_rc.borrow_mut();
2136 cache.add_order(primary.clone(), None, None, false).unwrap();
2137 }
2138
2139 let spawned1 = algo.spawn_market(
2140 &mut primary,
2141 Quantity::from("0.3"),
2142 TimeInForce::Fok,
2143 false,
2144 None,
2145 true,
2146 );
2147 {
2148 let cache_rc = algo.core.cache_rc();
2149 let mut cache = cache_rc.borrow_mut();
2150 cache.update_order(&primary).unwrap();
2151 }
2152
2153 let spawned2 = algo.spawn_market(
2154 &mut primary,
2155 Quantity::from("0.4"),
2156 TimeInForce::Fok,
2157 false,
2158 None,
2159 true,
2160 );
2161 {
2162 let cache_rc = algo.core.cache_rc();
2163 let mut cache = cache_rc.borrow_mut();
2164 cache.update_order(&primary).unwrap();
2165 }
2166
2167 assert_eq!(primary.quantity(), Quantity::from("0.3"));
2168
2169 let spawned_order1 = OrderAny::Market(spawned1);
2170 let mut spawned_order2 = OrderAny::Market(spawned2);
2171 {
2172 let cache_rc = algo.core.cache_rc();
2173 let mut cache = cache_rc.borrow_mut();
2174 cache.add_order(spawned_order1, None, None, false).unwrap();
2175 cache
2176 .add_order(spawned_order2.clone(), None, None, false)
2177 .unwrap();
2178 }
2179
2180 let denied = OrderDenied::new(
2181 spawned_order2.trader_id(),
2182 spawned_order2.strategy_id(),
2183 spawned_order2.instrument_id(),
2184 spawned_order2.client_order_id(),
2185 "TEST_DENIAL".into(),
2186 UUID4::new(),
2187 0.into(),
2188 0.into(),
2189 );
2190
2191 spawned_order2.apply(OrderEventAny::Denied(denied)).unwrap();
2192 {
2193 let cache_rc = algo.core.cache_rc();
2194 let mut cache = cache_rc.borrow_mut();
2195 cache.update_order(&spawned_order2).unwrap();
2196 }
2197
2198 algo.handle_order_event(OrderEventAny::Denied(denied));
2199
2200 let restored_primary = {
2201 let cache = algo.core.cache();
2202 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2203 };
2204 assert_eq!(restored_primary.quantity(), Quantity::from("0.7"));
2205 }
2206
2207 #[rstest]
2208 fn test_spawned_order_accepted_prevents_restoration() {
2209 let mut algo = create_test_algorithm();
2210 register_algorithm(&mut algo);
2211
2212 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2213 let exec_algorithm_id = algo.id();
2214
2215 let mut primary = OrderAny::Market(MarketOrder::new(
2216 TraderId::from("TRADER-001"),
2217 StrategyId::from("STRAT-001"),
2218 instrument_id,
2219 ClientOrderId::from("O-001"),
2220 OrderSide::Buy,
2221 Quantity::from("1.0"),
2222 TimeInForce::Gtc,
2223 UUID4::new(),
2224 0.into(),
2225 false,
2226 false,
2227 None,
2228 None,
2229 None,
2230 None,
2231 Some(exec_algorithm_id),
2232 None,
2233 None,
2234 None,
2235 ));
2236
2237 {
2238 let cache_rc = algo.core.cache_rc();
2239 let mut cache = cache_rc.borrow_mut();
2240 cache.add_order(primary.clone(), None, None, false).unwrap();
2241 }
2242
2243 let spawned = algo.spawn_market(
2244 &mut primary,
2245 Quantity::from("0.5"),
2246 TimeInForce::Fok,
2247 false,
2248 None,
2249 true,
2250 );
2251
2252 {
2253 let cache_rc = algo.core.cache_rc();
2254 let mut cache = cache_rc.borrow_mut();
2255 cache.update_order(&primary).unwrap();
2256 }
2257
2258 assert_eq!(primary.quantity(), Quantity::from("0.5"));
2259
2260 let mut spawned_order = OrderAny::Market(spawned);
2261 {
2262 let cache_rc = algo.core.cache_rc();
2263 let mut cache = cache_rc.borrow_mut();
2264 cache
2265 .add_order(spawned_order.clone(), None, None, false)
2266 .unwrap();
2267 }
2268
2269 let accepted = OrderAccepted::new(
2270 spawned_order.trader_id(),
2271 spawned_order.strategy_id(),
2272 spawned_order.instrument_id(),
2273 spawned_order.client_order_id(),
2274 VenueOrderId::from("V-123"),
2275 AccountId::from("BINANCE-001"),
2276 UUID4::new(),
2277 0.into(),
2278 0.into(),
2279 false,
2280 );
2281
2282 spawned_order
2283 .apply(OrderEventAny::Accepted(accepted))
2284 .unwrap();
2285 {
2286 let cache_rc = algo.core.cache_rc();
2287 let mut cache = cache_rc.borrow_mut();
2288 cache.update_order(&spawned_order).unwrap();
2289 }
2290
2291 algo.handle_order_event(OrderEventAny::Accepted(accepted));
2292
2293 let primary_after_accept = {
2294 let cache = algo.core.cache();
2295 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2296 };
2297 assert_eq!(primary_after_accept.quantity(), Quantity::from("0.5"));
2298
2299 let canceled = OrderCanceled::new(
2301 spawned_order.trader_id(),
2302 spawned_order.strategy_id(),
2303 spawned_order.instrument_id(),
2304 spawned_order.client_order_id(),
2305 UUID4::new(),
2306 0.into(),
2307 0.into(),
2308 false,
2309 Some(VenueOrderId::from("V-123")),
2310 Some(AccountId::from("BINANCE-001")),
2311 );
2312
2313 spawned_order
2314 .apply(OrderEventAny::Canceled(canceled))
2315 .unwrap();
2316 {
2317 let cache_rc = algo.core.cache_rc();
2318 let mut cache = cache_rc.borrow_mut();
2319 cache.update_order(&spawned_order).unwrap();
2320 }
2321
2322 algo.handle_order_event(OrderEventAny::Canceled(canceled));
2323
2324 let final_primary = {
2325 let cache = algo.core.cache();
2326 cache.order(&ClientOrderId::from("O-001")).cloned().unwrap()
2327 };
2328 assert_eq!(final_primary.quantity(), Quantity::from("0.5"));
2329 }
2330
2331 #[rstest]
2332 #[should_panic(expected = "exceeds primary leaves_qty")]
2333 fn test_spawn_quantity_exceeds_leaves_qty_panics() {
2334 let mut algo = create_test_algorithm();
2335 register_algorithm(&mut algo);
2336
2337 let instrument_id = InstrumentId::from("BTC/USDT.BINANCE");
2338 let exec_algorithm_id = algo.id();
2339
2340 let mut primary = OrderAny::Market(MarketOrder::new(
2341 TraderId::from("TRADER-001"),
2342 StrategyId::from("STRAT-001"),
2343 instrument_id,
2344 ClientOrderId::from("O-001"),
2345 OrderSide::Buy,
2346 Quantity::from("1.0"),
2347 TimeInForce::Gtc,
2348 UUID4::new(),
2349 0.into(),
2350 false,
2351 false,
2352 None,
2353 None,
2354 None,
2355 None,
2356 Some(exec_algorithm_id),
2357 None,
2358 None,
2359 None,
2360 ));
2361
2362 {
2363 let cache_rc = algo.core.cache_rc();
2364 let mut cache = cache_rc.borrow_mut();
2365 cache.add_order(primary.clone(), None, None, false).unwrap();
2366 }
2367
2368 let _ = algo.spawn_market(
2369 &mut primary,
2370 Quantity::from("0.8"),
2371 TimeInForce::Fok,
2372 false,
2373 None,
2374 true,
2375 );
2376
2377 {
2378 let cache_rc = algo.core.cache_rc();
2379 let mut cache = cache_rc.borrow_mut();
2380 cache.update_order(&primary).unwrap();
2381 }
2382
2383 assert_eq!(primary.quantity(), Quantity::from("0.2"));
2384 assert_eq!(primary.leaves_qty(), Quantity::from("0.2"));
2385
2386 let _ = algo.spawn_market(
2388 &mut primary,
2389 Quantity::from("0.5"),
2390 TimeInForce::Fok,
2391 false,
2392 None,
2393 true,
2394 );
2395 }
2396}