1use indexmap::IndexMap;
19use nautilus_core::{UUID4, UnixNanos};
20use nautilus_model::identifiers::{ClientId, InstrumentId, StrategyId, TraderId};
21use nautilus_serialization::{
22 base_capnp,
23 capnp::{ToCapnp, order_side_to_capnp},
24 trading_capnp,
25};
26
27use crate::messages::execution::{
28 BatchCancelOrders, CancelAllOrders, CancelOrder, ModifyOrder, QueryAccount, QueryOrder,
29 SubmitOrder, SubmitOrderList, TradingCommand,
30};
31
32fn populate_string_map<'a>(
34 builder: base_capnp::string_map::Builder<'a>,
35 params: &IndexMap<String, String>,
36) {
37 let mut entries_builder = builder.init_entries(params.len() as u32);
38 for (i, (key, value)) in params.iter().enumerate() {
39 let mut entry_builder = entries_builder.reborrow().get(i as u32);
40 entry_builder.set_key(key.as_str());
41 entry_builder.set_value(value.as_str());
42 }
43}
44
45fn populate_trading_command_header<'a>(
47 mut builder: trading_capnp::trading_command_header::Builder<'a>,
48 trader_id: &TraderId,
49 client_id: Option<&ClientId>,
50 strategy_id: &StrategyId,
51 instrument_id: &InstrumentId,
52 command_id: &UUID4,
53 ts_init: UnixNanos,
54) {
55 let trader_id_builder = builder.reborrow().init_trader_id();
56 trader_id.to_capnp(trader_id_builder);
57
58 if let Some(client_id) = client_id {
59 let client_id_builder = builder.reborrow().init_client_id();
60 client_id.to_capnp(client_id_builder);
61 }
62
63 let strategy_id_builder = builder.reborrow().init_strategy_id();
64 strategy_id.to_capnp(strategy_id_builder);
65
66 let instrument_id_builder = builder.reborrow().init_instrument_id();
67 instrument_id.to_capnp(instrument_id_builder);
68
69 let command_id_builder = builder.reborrow().init_command_id();
70 command_id.to_capnp(command_id_builder);
71
72 let mut ts_init_builder = builder.reborrow().init_ts_init();
73 ts_init_builder.set_value(*ts_init);
74}
75
76impl<'a> ToCapnp<'a> for CancelOrder {
77 type Builder = trading_capnp::cancel_order::Builder<'a>;
78
79 fn to_capnp(&self, mut builder: Self::Builder) {
80 let header_builder = builder.reborrow().init_header();
81 populate_trading_command_header(
82 header_builder,
83 &self.trader_id,
84 self.client_id.as_ref(),
85 &self.strategy_id,
86 &self.instrument_id,
87 &self.command_id,
88 self.ts_init,
89 );
90
91 let client_order_id_builder = builder.reborrow().init_client_order_id();
92 self.client_order_id.to_capnp(client_order_id_builder);
93
94 if let Some(ref venue_order_id) = self.venue_order_id {
95 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
96 venue_order_id.to_capnp(venue_order_id_builder);
97 }
98
99 if let Some(ref params) = self.params {
100 let params_builder = builder.reborrow().init_params();
101 populate_string_map(params_builder, params);
102 }
103 }
104}
105
106impl<'a> ToCapnp<'a> for CancelAllOrders {
107 type Builder = trading_capnp::cancel_all_orders::Builder<'a>;
108
109 fn to_capnp(&self, mut builder: Self::Builder) {
110 let header_builder = builder.reborrow().init_header();
111 populate_trading_command_header(
112 header_builder,
113 &self.trader_id,
114 self.client_id.as_ref(),
115 &self.strategy_id,
116 &self.instrument_id,
117 &self.command_id,
118 self.ts_init,
119 );
120
121 builder.set_order_side(order_side_to_capnp(self.order_side));
122
123 if let Some(ref params) = self.params {
124 let params_builder = builder.reborrow().init_params();
125 populate_string_map(params_builder, params);
126 }
127 }
128}
129
130impl<'a> ToCapnp<'a> for BatchCancelOrders {
131 type Builder = trading_capnp::batch_cancel_orders::Builder<'a>;
132
133 fn to_capnp(&self, mut builder: Self::Builder) {
134 let header_builder = builder.reborrow().init_header();
135 populate_trading_command_header(
136 header_builder,
137 &self.trader_id,
138 self.client_id.as_ref(),
139 &self.strategy_id,
140 &self.instrument_id,
141 &self.command_id,
142 self.ts_init,
143 );
144
145 let mut cancellations_builder = builder
146 .reborrow()
147 .init_cancellations(self.cancels.len() as u32);
148 for (i, cancel) in self.cancels.iter().enumerate() {
149 let cancel_builder = cancellations_builder.reborrow().get(i as u32);
150 cancel.to_capnp(cancel_builder);
151 }
152
153 if let Some(ref params) = self.params {
154 let params_builder = builder.reborrow().init_params();
155 populate_string_map(params_builder, params);
156 }
157 }
158}
159
160impl<'a> ToCapnp<'a> for ModifyOrder {
161 type Builder = trading_capnp::modify_order::Builder<'a>;
162
163 fn to_capnp(&self, mut builder: Self::Builder) {
164 let header_builder = builder.reborrow().init_header();
165 populate_trading_command_header(
166 header_builder,
167 &self.trader_id,
168 self.client_id.as_ref(),
169 &self.strategy_id,
170 &self.instrument_id,
171 &self.command_id,
172 self.ts_init,
173 );
174
175 let client_order_id_builder = builder.reborrow().init_client_order_id();
176 self.client_order_id.to_capnp(client_order_id_builder);
177
178 if let Some(ref venue_order_id) = self.venue_order_id {
179 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
180 venue_order_id.to_capnp(venue_order_id_builder);
181 }
182
183 if let Some(ref quantity) = self.quantity {
184 let quantity_builder = builder.reborrow().init_quantity();
185 quantity.to_capnp(quantity_builder);
186 }
187
188 if let Some(ref price) = self.price {
189 let price_builder = builder.reborrow().init_price();
190 price.to_capnp(price_builder);
191 }
192
193 if let Some(ref trigger_price) = self.trigger_price {
194 let trigger_price_builder = builder.reborrow().init_trigger_price();
195 trigger_price.to_capnp(trigger_price_builder);
196 }
197
198 if let Some(ref params) = self.params {
199 let params_builder = builder.reborrow().init_params();
200 populate_string_map(params_builder, params);
201 }
202 }
203}
204
205impl<'a> ToCapnp<'a> for QueryOrder {
206 type Builder = trading_capnp::query_order::Builder<'a>;
207
208 fn to_capnp(&self, mut builder: Self::Builder) {
209 let header_builder = builder.reborrow().init_header();
210 populate_trading_command_header(
211 header_builder,
212 &self.trader_id,
213 self.client_id.as_ref(),
214 &self.strategy_id,
215 &self.instrument_id,
216 &self.command_id,
217 self.ts_init,
218 );
219
220 let client_order_id_builder = builder.reborrow().init_client_order_id();
221 self.client_order_id.to_capnp(client_order_id_builder);
222
223 if let Some(ref venue_order_id) = self.venue_order_id {
224 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
225 venue_order_id.to_capnp(venue_order_id_builder);
226 }
227 }
228}
229
230impl<'a> ToCapnp<'a> for QueryAccount {
231 type Builder = trading_capnp::query_account::Builder<'a>;
232
233 fn to_capnp(&self, mut builder: Self::Builder) {
234 let trader_id_builder = builder.reborrow().init_trader_id();
235 self.trader_id.to_capnp(trader_id_builder);
236
237 let account_id_builder = builder.reborrow().init_account_id();
238 self.account_id.to_capnp(account_id_builder);
239
240 let command_id_builder = builder.reborrow().init_command_id();
241 self.command_id.to_capnp(command_id_builder);
242
243 let mut ts_init_builder = builder.reborrow().init_ts_init();
244 ts_init_builder.set_value(*self.ts_init);
245 }
246}
247
248impl<'a> ToCapnp<'a> for SubmitOrder {
249 type Builder = trading_capnp::submit_order::Builder<'a>;
250
251 fn to_capnp(&self, mut builder: Self::Builder) {
252 let header_builder = builder.reborrow().init_header();
253 populate_trading_command_header(
254 header_builder,
255 &self.trader_id,
256 self.client_id.as_ref(),
257 &self.strategy_id,
258 &self.instrument_id,
259 &self.command_id,
260 self.ts_init,
261 );
262
263 let order_init = self.order.init_event();
264 let order_init_builder = builder.reborrow().init_order_init();
265 order_init.to_capnp(order_init_builder);
266
267 if let Some(ref position_id) = self.position_id {
268 let position_id_builder = builder.reborrow().init_position_id();
269 position_id.to_capnp(position_id_builder);
270 }
271
272 if let Some(ref params) = self.params {
273 let params_builder = builder.reborrow().init_params();
274 populate_string_map(params_builder, params);
275 }
276 }
277}
278
279impl<'a> ToCapnp<'a> for SubmitOrderList {
280 type Builder = trading_capnp::submit_order_list::Builder<'a>;
281
282 fn to_capnp(&self, mut builder: Self::Builder) {
283 let header_builder = builder.reborrow().init_header();
284 populate_trading_command_header(
285 header_builder,
286 &self.trader_id,
287 self.client_id.as_ref(),
288 &self.strategy_id,
289 &self.instrument_id,
290 &self.command_id,
291 self.ts_init,
292 );
293
294 let mut order_inits_builder = builder
295 .reborrow()
296 .init_order_inits(self.order_list.orders.len() as u32);
297 for (i, order) in self.order_list.orders.iter().enumerate() {
298 let order_init = order.init_event();
299 let order_init_builder = order_inits_builder.reborrow().get(i as u32);
300 order_init.to_capnp(order_init_builder);
301 }
302
303 if let Some(ref position_id) = self.position_id {
304 let position_id_builder = builder.reborrow().init_position_id();
305 position_id.to_capnp(position_id_builder);
306 }
307
308 if let Some(ref params) = self.params {
309 let params_builder = builder.reborrow().init_params();
310 populate_string_map(params_builder, params);
311 }
312 }
313}
314
315impl<'a> ToCapnp<'a> for TradingCommand {
316 type Builder = trading_capnp::trading_command::Builder<'a>;
317
318 fn to_capnp(&self, builder: Self::Builder) {
319 match self {
320 Self::SubmitOrder(command) => {
321 let submit_builder = builder.init_submit_order();
322 command.to_capnp(submit_builder);
323 }
324 Self::SubmitOrderList(command) => {
325 let submit_list_builder = builder.init_submit_order_list();
326 command.to_capnp(submit_list_builder);
327 }
328 Self::ModifyOrder(command) => {
329 let modify_builder = builder.init_modify_order();
330 command.to_capnp(modify_builder);
331 }
332 Self::CancelOrder(command) => {
333 let cancel_builder = builder.init_cancel_order();
334 command.to_capnp(cancel_builder);
335 }
336 Self::CancelAllOrders(command) => {
337 let cancel_all_builder = builder.init_cancel_all_orders();
338 command.to_capnp(cancel_all_builder);
339 }
340 Self::BatchCancelOrders(command) => {
341 let batch_cancel_builder = builder.init_batch_cancel_orders();
342 command.to_capnp(batch_cancel_builder);
343 }
344 Self::QueryOrder(command) => {
345 let query_builder = builder.init_query_order();
346 command.to_capnp(query_builder);
347 }
348 Self::QueryAccount(command) => {
349 let query_builder = builder.init_query_account();
350 command.to_capnp(query_builder);
351 }
352 }
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use capnp::message::Builder;
359 use nautilus_core::UnixNanos;
360 use nautilus_model::{
361 enums::{OrderSide, OrderType},
362 identifiers::{
363 AccountId, ClientId, ClientOrderId, InstrumentId, OrderListId, StrategyId, TraderId,
364 },
365 orders::{Order, OrderList, OrderTestBuilder},
366 stubs::TestDefault,
367 types::{Price, Quantity},
368 };
369 use rstest::*;
370
371 use super::*;
372 use crate::messages::execution::{
373 cancel::{BatchCancelOrdersBuilder, CancelAllOrdersBuilder, CancelOrderBuilder},
374 modify::ModifyOrderBuilder,
375 query::{QueryAccountBuilder, QueryOrderBuilder},
376 };
377
378 #[fixture]
379 fn trader_id() -> TraderId {
380 TraderId::test_default()
381 }
382
383 #[fixture]
384 fn strategy_id() -> StrategyId {
385 StrategyId::test_default()
386 }
387
388 #[fixture]
389 fn instrument_id() -> InstrumentId {
390 InstrumentId::test_default()
391 }
392
393 #[fixture]
394 fn client_order_id() -> ClientOrderId {
395 ClientOrderId::test_default()
396 }
397
398 #[fixture]
399 fn command_id() -> UUID4 {
400 UUID4::new()
401 }
402
403 #[fixture]
404 fn ts_init() -> UnixNanos {
405 UnixNanos::default()
406 }
407
408 #[fixture]
409 fn client_id() -> ClientId {
410 ClientId::new("TEST")
411 }
412
413 #[rstest]
414 fn test_cancel_order_serialization(
415 trader_id: TraderId,
416 client_id: ClientId,
417 strategy_id: StrategyId,
418 instrument_id: InstrumentId,
419 client_order_id: ClientOrderId,
420 command_id: UUID4,
421 ts_init: UnixNanos,
422 ) {
423 let command = CancelOrderBuilder::default()
424 .trader_id(trader_id)
425 .client_id(Some(client_id))
426 .strategy_id(strategy_id)
427 .instrument_id(instrument_id)
428 .client_order_id(client_order_id)
429 .venue_order_id(None)
430 .command_id(command_id)
431 .ts_init(ts_init)
432 .params(None)
433 .build()
434 .unwrap();
435
436 let mut message = Builder::new_default();
437 {
438 let builder = message.init_root::<trading_capnp::cancel_order::Builder>();
439 command.to_capnp(builder);
440 }
441
442 let reader = message
443 .get_root_as_reader::<trading_capnp::cancel_order::Reader>()
444 .expect("Valid capnp message");
445
446 assert!(reader.has_header());
448 let header = reader.get_header().unwrap();
449 assert!(header.has_trader_id());
450 assert!(header.has_client_id());
451 assert!(header.has_strategy_id());
452 assert!(header.has_instrument_id());
453 assert!(header.has_command_id());
454 assert!(header.has_ts_init());
455 }
456
457 #[rstest]
458 fn test_cancel_all_orders_serialization(
459 trader_id: TraderId,
460 strategy_id: StrategyId,
461 instrument_id: InstrumentId,
462 command_id: UUID4,
463 ts_init: UnixNanos,
464 ) {
465 let command = CancelAllOrdersBuilder::default()
466 .trader_id(trader_id)
467 .client_id(None)
468 .strategy_id(strategy_id)
469 .instrument_id(instrument_id)
470 .order_side(OrderSide::Buy)
471 .command_id(command_id)
472 .ts_init(ts_init)
473 .params(None)
474 .build()
475 .unwrap();
476
477 let mut message = Builder::new_default();
478 {
479 let builder = message.init_root::<trading_capnp::cancel_all_orders::Builder>();
480 command.to_capnp(builder);
481 }
482
483 let reader = message
484 .get_root_as_reader::<trading_capnp::cancel_all_orders::Reader>()
485 .expect("Valid capnp message");
486
487 assert!(reader.has_header());
488 }
489
490 #[rstest]
491 fn test_batch_cancel_orders_serialization(
492 trader_id: TraderId,
493 strategy_id: StrategyId,
494 instrument_id: InstrumentId,
495 command_id: UUID4,
496 ts_init: UnixNanos,
497 ) {
498 let cancel1 = CancelOrderBuilder::default()
499 .trader_id(trader_id)
500 .client_id(None)
501 .strategy_id(strategy_id)
502 .instrument_id(instrument_id)
503 .client_order_id(ClientOrderId::new("O-001"))
504 .venue_order_id(None)
505 .command_id(UUID4::new())
506 .ts_init(ts_init)
507 .params(None)
508 .build()
509 .unwrap();
510
511 let cancel2 = CancelOrderBuilder::default()
512 .trader_id(trader_id)
513 .client_id(None)
514 .strategy_id(strategy_id)
515 .instrument_id(instrument_id)
516 .client_order_id(ClientOrderId::new("O-002"))
517 .venue_order_id(None)
518 .command_id(UUID4::new())
519 .ts_init(ts_init)
520 .params(None)
521 .build()
522 .unwrap();
523
524 let command = BatchCancelOrdersBuilder::default()
525 .trader_id(trader_id)
526 .client_id(None)
527 .strategy_id(strategy_id)
528 .instrument_id(instrument_id)
529 .cancels(vec![cancel1, cancel2])
530 .command_id(command_id)
531 .ts_init(ts_init)
532 .params(None)
533 .build()
534 .unwrap();
535
536 let mut message = Builder::new_default();
537 {
538 let builder = message.init_root::<trading_capnp::batch_cancel_orders::Builder>();
539 command.to_capnp(builder);
540 }
541
542 let reader = message
543 .get_root_as_reader::<trading_capnp::batch_cancel_orders::Reader>()
544 .expect("Valid capnp message");
545
546 assert!(reader.has_header());
547 assert!(reader.has_cancellations());
548 assert_eq!(reader.get_cancellations().unwrap().len(), 2);
549 }
550
551 #[rstest]
552 fn test_modify_order_serialization(
553 trader_id: TraderId,
554 strategy_id: StrategyId,
555 instrument_id: InstrumentId,
556 client_order_id: ClientOrderId,
557 command_id: UUID4,
558 ts_init: UnixNanos,
559 ) {
560 let command = ModifyOrderBuilder::default()
561 .trader_id(trader_id)
562 .client_id(None)
563 .strategy_id(strategy_id)
564 .instrument_id(instrument_id)
565 .client_order_id(client_order_id)
566 .venue_order_id(None)
567 .quantity(Some(Quantity::new(100.0, 0)))
568 .price(Some(Price::new(50_000.0, 2)))
569 .trigger_price(Some(Price::new(49_000.0, 2)))
570 .command_id(command_id)
571 .ts_init(ts_init)
572 .params(None)
573 .build()
574 .unwrap();
575
576 let mut message = Builder::new_default();
577 {
578 let builder = message.init_root::<trading_capnp::modify_order::Builder>();
579 command.to_capnp(builder);
580 }
581
582 let reader = message
583 .get_root_as_reader::<trading_capnp::modify_order::Reader>()
584 .expect("Valid capnp message");
585
586 assert!(reader.has_header());
587 assert!(reader.has_quantity());
588 assert!(reader.has_price());
589 assert!(reader.has_trigger_price());
590 }
591
592 #[rstest]
593 fn test_query_order_serialization(
594 trader_id: TraderId,
595 strategy_id: StrategyId,
596 instrument_id: InstrumentId,
597 client_order_id: ClientOrderId,
598 command_id: UUID4,
599 ts_init: UnixNanos,
600 ) {
601 let command = QueryOrderBuilder::default()
602 .trader_id(trader_id)
603 .client_id(None)
604 .strategy_id(strategy_id)
605 .instrument_id(instrument_id)
606 .client_order_id(client_order_id)
607 .venue_order_id(None)
608 .command_id(command_id)
609 .ts_init(ts_init)
610 .build()
611 .unwrap();
612
613 let mut message = Builder::new_default();
614 {
615 let builder = message.init_root::<trading_capnp::query_order::Builder>();
616 command.to_capnp(builder);
617 }
618
619 let reader = message
620 .get_root_as_reader::<trading_capnp::query_order::Reader>()
621 .expect("Valid capnp message");
622
623 assert!(reader.has_header());
624 }
625
626 #[rstest]
627 fn test_query_account_serialization(
628 trader_id: TraderId,
629 command_id: UUID4,
630 ts_init: UnixNanos,
631 ) {
632 let command = QueryAccountBuilder::default()
633 .trader_id(trader_id)
634 .client_id(None)
635 .account_id(AccountId::new("ACC-001"))
636 .command_id(command_id)
637 .ts_init(ts_init)
638 .build()
639 .unwrap();
640
641 let mut message = Builder::new_default();
642 {
643 let builder = message.init_root::<trading_capnp::query_account::Builder>();
644 command.to_capnp(builder);
645 }
646
647 let reader = message
648 .get_root_as_reader::<trading_capnp::query_account::Reader>()
649 .expect("Valid capnp message");
650
651 assert!(reader.has_trader_id());
652 assert!(reader.has_account_id());
653 }
654
655 #[rstest]
656 fn test_submit_order_serialization(command_id: UUID4, ts_init: UnixNanos, client_id: ClientId) {
657 let order = OrderTestBuilder::new(OrderType::Limit)
658 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
659 .side(OrderSide::Buy)
660 .quantity(Quantity::new(1.0, 8))
661 .price(Price::new(50_000.0, 2))
662 .build();
663
664 let command = SubmitOrder::new(
665 order.trader_id(),
666 Some(client_id),
667 order.strategy_id(),
668 order.instrument_id(),
669 order,
670 None,
671 None,
672 None,
673 command_id,
674 ts_init,
675 );
676
677 let mut message = Builder::new_default();
678 {
679 let builder = message.init_root::<trading_capnp::submit_order::Builder>();
680 command.to_capnp(builder);
681 }
682
683 let reader = message
684 .get_root_as_reader::<trading_capnp::submit_order::Reader>()
685 .expect("Valid capnp message");
686
687 assert!(reader.has_header());
688 assert!(reader.has_order_init());
689 }
690
691 #[rstest]
692 fn test_submit_order_list_serialization(
693 command_id: UUID4,
694 ts_init: UnixNanos,
695 client_id: ClientId,
696 ) {
697 let order1 = OrderTestBuilder::new(OrderType::Limit)
698 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
699 .side(OrderSide::Buy)
700 .quantity(Quantity::new(1.0, 8))
701 .price(Price::new(50_000.0, 2))
702 .build();
703
704 let order2 = OrderTestBuilder::new(OrderType::Limit)
705 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
706 .side(OrderSide::Sell)
707 .quantity(Quantity::new(1.0, 8))
708 .price(Price::new(51_000.0, 2))
709 .build();
710
711 let order_list = OrderList::new(
712 OrderListId::new("OL-001"),
713 InstrumentId::from("BTCUSDT.BINANCE"),
714 order1.strategy_id(),
715 vec![order1.clone(), order2],
716 ts_init,
717 );
718
719 let command = SubmitOrderList::new(
720 order1.trader_id(),
721 Some(client_id),
722 order1.strategy_id(),
723 order1.instrument_id(),
724 order_list,
725 None,
726 None,
727 None,
728 command_id,
729 ts_init,
730 );
731
732 let mut message = Builder::new_default();
733 {
734 let builder = message.init_root::<trading_capnp::submit_order_list::Builder>();
735 command.to_capnp(builder);
736 }
737
738 let reader = message
739 .get_root_as_reader::<trading_capnp::submit_order_list::Reader>()
740 .expect("Valid capnp message");
741
742 assert!(reader.has_header());
743 assert!(reader.has_order_inits());
744 assert_eq!(reader.get_order_inits().unwrap().len(), 2);
745 }
746
747 #[rstest]
748 fn test_trading_command_enum_serialization(
749 trader_id: TraderId,
750 strategy_id: StrategyId,
751 instrument_id: InstrumentId,
752 client_order_id: ClientOrderId,
753 command_id: UUID4,
754 ts_init: UnixNanos,
755 ) {
756 let cancel = CancelOrderBuilder::default()
757 .trader_id(trader_id)
758 .client_id(None)
759 .strategy_id(strategy_id)
760 .instrument_id(instrument_id)
761 .client_order_id(client_order_id)
762 .venue_order_id(None)
763 .command_id(command_id)
764 .ts_init(ts_init)
765 .params(None)
766 .build()
767 .unwrap();
768
769 let command = TradingCommand::CancelOrder(cancel);
770
771 let mut message = Builder::new_default();
772 {
773 let builder = message.init_root::<trading_capnp::trading_command::Builder>();
774 command.to_capnp(builder);
775 }
776
777 let reader = message
778 .get_root_as_reader::<trading_capnp::trading_command::Reader>()
779 .expect("Valid capnp message");
780
781 assert!(reader.has_cancel_order());
783 }
784}