1use nautilus_core::{Params, UUID4, UnixNanos};
19use nautilus_model::identifiers::{ClientId, InstrumentId, StrategyId, TraderId};
20use nautilus_serialization::{
21 base_capnp,
22 capnp::{ToCapnp, order_side_to_capnp},
23 trading_capnp,
24};
25
26use crate::messages::execution::{
27 BatchCancelOrders, CancelAllOrders, CancelOrder, ModifyOrder, QueryAccount, QueryOrder,
28 SubmitOrder, SubmitOrderList, TradingCommand,
29};
30
31fn populate_string_map(builder: base_capnp::string_map::Builder<'_>, params: &Params) {
33 let mut entries_builder = builder.init_entries(params.len() as u32);
34 for (i, (key, value)) in params.iter().enumerate() {
35 let mut entry_builder = entries_builder.reborrow().get(i as u32);
36 entry_builder.set_key(key.as_str());
37 let value_str = serde_json::to_string(value).unwrap_or_else(|_| value.to_string());
38 entry_builder.set_value(value_str.as_str());
39 }
40}
41
42#[allow(clippy::too_many_arguments)]
44fn populate_trading_command_header(
45 mut builder: trading_capnp::trading_command_header::Builder<'_>,
46 trader_id: &TraderId,
47 client_id: Option<&ClientId>,
48 strategy_id: &StrategyId,
49 instrument_id: &InstrumentId,
50 command_id: &UUID4,
51 ts_init: UnixNanos,
52 correlation_id: Option<&UUID4>,
53 causation_id: Option<&UUID4>,
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 if let Some(correlation_id) = correlation_id {
76 let correlation_id_builder = builder.reborrow().init_correlation_id();
77 correlation_id.to_capnp(correlation_id_builder);
78 }
79
80 if let Some(causation_id) = causation_id {
81 let causation_id_builder = builder.reborrow().init_causation_id();
82 causation_id.to_capnp(causation_id_builder);
83 }
84}
85
86impl<'a> ToCapnp<'a> for CancelOrder {
87 type Builder = trading_capnp::cancel_order::Builder<'a>;
88
89 fn to_capnp(&self, mut builder: Self::Builder) {
90 let header_builder = builder.reborrow().init_header();
91 populate_trading_command_header(
92 header_builder,
93 &self.trader_id,
94 self.client_id.as_ref(),
95 &self.strategy_id,
96 &self.instrument_id,
97 &self.command_id,
98 self.ts_init,
99 self.correlation_id.as_ref(),
100 self.causation_id.as_ref(),
101 );
102
103 let client_order_id_builder = builder.reborrow().init_client_order_id();
104 self.client_order_id.to_capnp(client_order_id_builder);
105
106 if let Some(ref venue_order_id) = self.venue_order_id {
107 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
108 venue_order_id.to_capnp(venue_order_id_builder);
109 }
110
111 if let Some(ref params) = self.params {
112 let params_builder = builder.reborrow().init_params();
113 populate_string_map(params_builder, params);
114 }
115 }
116}
117
118impl<'a> ToCapnp<'a> for CancelAllOrders {
119 type Builder = trading_capnp::cancel_all_orders::Builder<'a>;
120
121 fn to_capnp(&self, mut builder: Self::Builder) {
122 let header_builder = builder.reborrow().init_header();
123 populate_trading_command_header(
124 header_builder,
125 &self.trader_id,
126 self.client_id.as_ref(),
127 &self.strategy_id,
128 &self.instrument_id,
129 &self.command_id,
130 self.ts_init,
131 self.correlation_id.as_ref(),
132 self.causation_id.as_ref(),
133 );
134
135 builder.set_order_side(order_side_to_capnp(self.order_side));
136
137 if let Some(ref params) = self.params {
138 let params_builder = builder.reborrow().init_params();
139 populate_string_map(params_builder, params);
140 }
141 }
142}
143
144impl<'a> ToCapnp<'a> for BatchCancelOrders {
145 type Builder = trading_capnp::batch_cancel_orders::Builder<'a>;
146
147 fn to_capnp(&self, mut builder: Self::Builder) {
148 let header_builder = builder.reborrow().init_header();
149 populate_trading_command_header(
150 header_builder,
151 &self.trader_id,
152 self.client_id.as_ref(),
153 &self.strategy_id,
154 &self.instrument_id,
155 &self.command_id,
156 self.ts_init,
157 self.correlation_id.as_ref(),
158 self.causation_id.as_ref(),
159 );
160
161 let mut cancellations_builder = builder
162 .reborrow()
163 .init_cancellations(self.cancels.len() as u32);
164 for (i, cancel) in self.cancels.iter().enumerate() {
165 let cancel_builder = cancellations_builder.reborrow().get(i as u32);
166 cancel.to_capnp(cancel_builder);
167 }
168
169 if let Some(ref params) = self.params {
170 let params_builder = builder.reborrow().init_params();
171 populate_string_map(params_builder, params);
172 }
173 }
174}
175
176impl<'a> ToCapnp<'a> for ModifyOrder {
177 type Builder = trading_capnp::modify_order::Builder<'a>;
178
179 fn to_capnp(&self, mut builder: Self::Builder) {
180 let header_builder = builder.reborrow().init_header();
181 populate_trading_command_header(
182 header_builder,
183 &self.trader_id,
184 self.client_id.as_ref(),
185 &self.strategy_id,
186 &self.instrument_id,
187 &self.command_id,
188 self.ts_init,
189 self.correlation_id.as_ref(),
190 self.causation_id.as_ref(),
191 );
192
193 let client_order_id_builder = builder.reborrow().init_client_order_id();
194 self.client_order_id.to_capnp(client_order_id_builder);
195
196 if let Some(ref venue_order_id) = self.venue_order_id {
197 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
198 venue_order_id.to_capnp(venue_order_id_builder);
199 }
200
201 if let Some(ref quantity) = self.quantity {
202 let quantity_builder = builder.reborrow().init_quantity();
203 quantity.to_capnp(quantity_builder);
204 }
205
206 if let Some(ref price) = self.price {
207 let price_builder = builder.reborrow().init_price();
208 price.to_capnp(price_builder);
209 }
210
211 if let Some(ref trigger_price) = self.trigger_price {
212 let trigger_price_builder = builder.reborrow().init_trigger_price();
213 trigger_price.to_capnp(trigger_price_builder);
214 }
215
216 if let Some(ref params) = self.params {
217 let params_builder = builder.reborrow().init_params();
218 populate_string_map(params_builder, params);
219 }
220 }
221}
222
223impl<'a> ToCapnp<'a> for QueryOrder {
224 type Builder = trading_capnp::query_order::Builder<'a>;
225
226 fn to_capnp(&self, mut builder: Self::Builder) {
227 let header_builder = builder.reborrow().init_header();
228 populate_trading_command_header(
229 header_builder,
230 &self.trader_id,
231 self.client_id.as_ref(),
232 &self.strategy_id,
233 &self.instrument_id,
234 &self.command_id,
235 self.ts_init,
236 self.correlation_id.as_ref(),
237 self.causation_id.as_ref(),
238 );
239
240 let client_order_id_builder = builder.reborrow().init_client_order_id();
241 self.client_order_id.to_capnp(client_order_id_builder);
242
243 if let Some(ref venue_order_id) = self.venue_order_id {
244 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
245 venue_order_id.to_capnp(venue_order_id_builder);
246 }
247 }
248}
249
250impl<'a> ToCapnp<'a> for QueryAccount {
251 type Builder = trading_capnp::query_account::Builder<'a>;
252
253 fn to_capnp(&self, mut builder: Self::Builder) {
254 let trader_id_builder = builder.reborrow().init_trader_id();
255 self.trader_id.to_capnp(trader_id_builder);
256
257 let account_id_builder = builder.reborrow().init_account_id();
258 self.account_id.to_capnp(account_id_builder);
259
260 let command_id_builder = builder.reborrow().init_command_id();
261 self.command_id.to_capnp(command_id_builder);
262
263 let mut ts_init_builder = builder.reborrow().init_ts_init();
264 ts_init_builder.set_value(*self.ts_init);
265
266 if let Some(ref correlation_id) = self.correlation_id {
267 let correlation_id_builder = builder.reborrow().init_correlation_id();
268 correlation_id.to_capnp(correlation_id_builder);
269 }
270
271 if let Some(ref causation_id) = self.causation_id {
272 let causation_id_builder = builder.reborrow().init_causation_id();
273 causation_id.to_capnp(causation_id_builder);
274 }
275 }
276}
277
278impl<'a> ToCapnp<'a> for SubmitOrder {
279 type Builder = trading_capnp::submit_order::Builder<'a>;
280
281 fn to_capnp(&self, mut builder: Self::Builder) {
282 let header_builder = builder.reborrow().init_header();
283 populate_trading_command_header(
284 header_builder,
285 &self.trader_id,
286 self.client_id.as_ref(),
287 &self.strategy_id,
288 &self.instrument_id,
289 &self.command_id,
290 self.ts_init,
291 self.correlation_id.as_ref(),
292 self.causation_id.as_ref(),
293 );
294
295 let order_init_builder = builder.reborrow().init_order_init();
296 self.order_init.to_capnp(order_init_builder);
297
298 if let Some(ref position_id) = self.position_id {
299 let position_id_builder = builder.reborrow().init_position_id();
300 position_id.to_capnp(position_id_builder);
301 }
302
303 if let Some(ref params) = self.params {
304 let params_builder = builder.reborrow().init_params();
305 populate_string_map(params_builder, params);
306 }
307 }
308}
309
310impl<'a> ToCapnp<'a> for SubmitOrderList {
311 type Builder = trading_capnp::submit_order_list::Builder<'a>;
312
313 fn to_capnp(&self, mut builder: Self::Builder) {
314 let header_builder = builder.reborrow().init_header();
315 populate_trading_command_header(
316 header_builder,
317 &self.trader_id,
318 self.client_id.as_ref(),
319 &self.strategy_id,
320 &self.instrument_id,
321 &self.command_id,
322 self.ts_init,
323 self.correlation_id.as_ref(),
324 self.causation_id.as_ref(),
325 );
326
327 let mut order_inits_builder = builder
328 .reborrow()
329 .init_order_inits(self.order_inits.len() as u32);
330 for (i, order_init) in self.order_inits.iter().enumerate() {
331 let order_init_builder = order_inits_builder.reborrow().get(i as u32);
332 order_init.to_capnp(order_init_builder);
333 }
334
335 if let Some(ref position_id) = self.position_id {
336 let position_id_builder = builder.reborrow().init_position_id();
337 position_id.to_capnp(position_id_builder);
338 }
339
340 if let Some(ref params) = self.params {
341 let params_builder = builder.reborrow().init_params();
342 populate_string_map(params_builder, params);
343 }
344 }
345}
346
347impl<'a> ToCapnp<'a> for TradingCommand {
348 type Builder = trading_capnp::trading_command::Builder<'a>;
349
350 fn to_capnp(&self, builder: Self::Builder) {
351 match self {
352 Self::SubmitOrder(command) => {
353 let submit_builder = builder.init_submit_order();
354 command.to_capnp(submit_builder);
355 }
356 Self::SubmitOrderList(command) => {
357 let submit_list_builder = builder.init_submit_order_list();
358 command.to_capnp(submit_list_builder);
359 }
360 Self::ModifyOrder(command) => {
361 let modify_builder = builder.init_modify_order();
362 command.to_capnp(modify_builder);
363 }
364 Self::CancelOrder(command) => {
365 let cancel_builder = builder.init_cancel_order();
366 command.to_capnp(cancel_builder);
367 }
368 Self::CancelAllOrders(command) => {
369 let cancel_all_builder = builder.init_cancel_all_orders();
370 command.to_capnp(cancel_all_builder);
371 }
372 Self::BatchCancelOrders(command) => {
373 let batch_cancel_builder = builder.init_batch_cancel_orders();
374 command.to_capnp(batch_cancel_builder);
375 }
376 Self::QueryOrder(command) => {
377 let query_builder = builder.init_query_order();
378 command.to_capnp(query_builder);
379 }
380 Self::QueryAccount(command) => {
381 let query_builder = builder.init_query_account();
382 command.to_capnp(query_builder);
383 }
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use capnp::message::Builder;
391 use nautilus_core::UnixNanos;
392 use nautilus_model::{
393 enums::{OrderSide, OrderType},
394 identifiers::{
395 AccountId, ClientId, ClientOrderId, InstrumentId, OrderListId, StrategyId, TraderId,
396 },
397 orders::{Order, OrderList, OrderTestBuilder},
398 stubs::TestDefault,
399 types::{Price, Quantity},
400 };
401 use nautilus_serialization::capnp::FromCapnp;
402 use rstest::*;
403
404 use super::*;
405 use crate::messages::execution::{
406 cancel::{BatchCancelOrdersBuilder, CancelAllOrdersBuilder, CancelOrderBuilder},
407 modify::ModifyOrderBuilder,
408 query::{QueryAccountBuilder, QueryOrderBuilder},
409 };
410
411 #[fixture]
412 fn trader_id() -> TraderId {
413 TraderId::test_default()
414 }
415
416 #[fixture]
417 fn strategy_id() -> StrategyId {
418 StrategyId::test_default()
419 }
420
421 #[fixture]
422 fn instrument_id() -> InstrumentId {
423 InstrumentId::test_default()
424 }
425
426 #[fixture]
427 fn client_order_id() -> ClientOrderId {
428 ClientOrderId::test_default()
429 }
430
431 #[fixture]
432 fn command_id() -> UUID4 {
433 UUID4::new()
434 }
435
436 #[fixture]
437 fn ts_init() -> UnixNanos {
438 UnixNanos::default()
439 }
440
441 #[fixture]
442 fn client_id() -> ClientId {
443 ClientId::new("TEST")
444 }
445
446 #[rstest]
447 fn test_cancel_order_serialization(
448 trader_id: TraderId,
449 client_id: ClientId,
450 strategy_id: StrategyId,
451 instrument_id: InstrumentId,
452 client_order_id: ClientOrderId,
453 command_id: UUID4,
454 ts_init: UnixNanos,
455 ) {
456 let command = CancelOrderBuilder::default()
457 .trader_id(trader_id)
458 .client_id(Some(client_id))
459 .strategy_id(strategy_id)
460 .instrument_id(instrument_id)
461 .client_order_id(client_order_id)
462 .venue_order_id(None)
463 .command_id(command_id)
464 .ts_init(ts_init)
465 .params(None)
466 .build()
467 .unwrap();
468
469 let mut message = Builder::new_default();
470 {
471 let builder = message.init_root::<trading_capnp::cancel_order::Builder>();
472 command.to_capnp(builder);
473 }
474
475 let reader = message
476 .get_root_as_reader::<trading_capnp::cancel_order::Reader>()
477 .expect("Valid capnp message");
478
479 assert!(reader.has_header());
481 let header = reader.get_header().unwrap();
482 assert!(header.has_trader_id());
483 assert!(header.has_client_id());
484 assert!(header.has_strategy_id());
485 assert!(header.has_instrument_id());
486 assert!(header.has_command_id());
487 assert!(header.has_ts_init());
488 }
489
490 #[rstest]
491 fn test_cancel_all_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 command = CancelAllOrdersBuilder::default()
499 .trader_id(trader_id)
500 .client_id(None)
501 .strategy_id(strategy_id)
502 .instrument_id(instrument_id)
503 .order_side(OrderSide::Buy)
504 .command_id(command_id)
505 .ts_init(ts_init)
506 .params(None)
507 .build()
508 .unwrap();
509
510 let mut message = Builder::new_default();
511 {
512 let builder = message.init_root::<trading_capnp::cancel_all_orders::Builder>();
513 command.to_capnp(builder);
514 }
515
516 let reader = message
517 .get_root_as_reader::<trading_capnp::cancel_all_orders::Reader>()
518 .expect("Valid capnp message");
519
520 assert!(reader.has_header());
521 }
522
523 #[rstest]
524 fn test_batch_cancel_orders_serialization(
525 trader_id: TraderId,
526 strategy_id: StrategyId,
527 instrument_id: InstrumentId,
528 command_id: UUID4,
529 ts_init: UnixNanos,
530 ) {
531 let cancel1 = CancelOrderBuilder::default()
532 .trader_id(trader_id)
533 .client_id(None)
534 .strategy_id(strategy_id)
535 .instrument_id(instrument_id)
536 .client_order_id(ClientOrderId::new("O-001"))
537 .venue_order_id(None)
538 .command_id(UUID4::new())
539 .ts_init(ts_init)
540 .params(None)
541 .build()
542 .unwrap();
543
544 let cancel2 = CancelOrderBuilder::default()
545 .trader_id(trader_id)
546 .client_id(None)
547 .strategy_id(strategy_id)
548 .instrument_id(instrument_id)
549 .client_order_id(ClientOrderId::new("O-002"))
550 .venue_order_id(None)
551 .command_id(UUID4::new())
552 .ts_init(ts_init)
553 .params(None)
554 .build()
555 .unwrap();
556
557 let command = BatchCancelOrdersBuilder::default()
558 .trader_id(trader_id)
559 .client_id(None)
560 .strategy_id(strategy_id)
561 .instrument_id(instrument_id)
562 .cancels(vec![cancel1, cancel2])
563 .command_id(command_id)
564 .ts_init(ts_init)
565 .params(None)
566 .build()
567 .unwrap();
568
569 let mut message = Builder::new_default();
570 {
571 let builder = message.init_root::<trading_capnp::batch_cancel_orders::Builder>();
572 command.to_capnp(builder);
573 }
574
575 let reader = message
576 .get_root_as_reader::<trading_capnp::batch_cancel_orders::Reader>()
577 .expect("Valid capnp message");
578
579 assert!(reader.has_header());
580 assert!(reader.has_cancellations());
581 assert_eq!(reader.get_cancellations().unwrap().len(), 2);
582 }
583
584 #[rstest]
585 fn test_modify_order_serialization(
586 trader_id: TraderId,
587 strategy_id: StrategyId,
588 instrument_id: InstrumentId,
589 client_order_id: ClientOrderId,
590 command_id: UUID4,
591 ts_init: UnixNanos,
592 ) {
593 let command = ModifyOrderBuilder::default()
594 .trader_id(trader_id)
595 .client_id(None)
596 .strategy_id(strategy_id)
597 .instrument_id(instrument_id)
598 .client_order_id(client_order_id)
599 .venue_order_id(None)
600 .quantity(Some(Quantity::new(100.0, 0)))
601 .price(Some(Price::new(50_000.0, 2)))
602 .trigger_price(Some(Price::new(49_000.0, 2)))
603 .command_id(command_id)
604 .ts_init(ts_init)
605 .params(None)
606 .build()
607 .unwrap();
608
609 let mut message = Builder::new_default();
610 {
611 let builder = message.init_root::<trading_capnp::modify_order::Builder>();
612 command.to_capnp(builder);
613 }
614
615 let reader = message
616 .get_root_as_reader::<trading_capnp::modify_order::Reader>()
617 .expect("Valid capnp message");
618
619 assert!(reader.has_header());
620 assert!(reader.has_quantity());
621 assert!(reader.has_price());
622 assert!(reader.has_trigger_price());
623 }
624
625 #[rstest]
626 fn test_query_order_serialization(
627 trader_id: TraderId,
628 strategy_id: StrategyId,
629 instrument_id: InstrumentId,
630 client_order_id: ClientOrderId,
631 command_id: UUID4,
632 ts_init: UnixNanos,
633 ) {
634 let command = QueryOrderBuilder::default()
635 .trader_id(trader_id)
636 .client_id(None)
637 .strategy_id(strategy_id)
638 .instrument_id(instrument_id)
639 .client_order_id(client_order_id)
640 .venue_order_id(None)
641 .command_id(command_id)
642 .ts_init(ts_init)
643 .params(None)
644 .build()
645 .unwrap();
646
647 let mut message = Builder::new_default();
648 {
649 let builder = message.init_root::<trading_capnp::query_order::Builder>();
650 command.to_capnp(builder);
651 }
652
653 let reader = message
654 .get_root_as_reader::<trading_capnp::query_order::Reader>()
655 .expect("Valid capnp message");
656
657 assert!(reader.has_header());
658 }
659
660 #[rstest]
661 fn test_query_account_serialization(
662 trader_id: TraderId,
663 command_id: UUID4,
664 ts_init: UnixNanos,
665 ) {
666 let command = QueryAccountBuilder::default()
667 .trader_id(trader_id)
668 .client_id(None)
669 .account_id(AccountId::new("ACC-001"))
670 .command_id(command_id)
671 .ts_init(ts_init)
672 .params(None)
673 .build()
674 .unwrap();
675
676 let mut message = Builder::new_default();
677 {
678 let builder = message.init_root::<trading_capnp::query_account::Builder>();
679 command.to_capnp(builder);
680 }
681
682 let reader = message
683 .get_root_as_reader::<trading_capnp::query_account::Reader>()
684 .expect("Valid capnp message");
685
686 assert!(reader.has_trader_id());
687 assert!(reader.has_account_id());
688 }
689
690 #[rstest]
691 #[case(None)]
692 #[case(Some(UUID4::new()))]
693 fn test_cancel_order_correlation_id_roundtrips_through_capnp_header(
694 trader_id: TraderId,
695 client_id: ClientId,
696 strategy_id: StrategyId,
697 instrument_id: InstrumentId,
698 client_order_id: ClientOrderId,
699 #[case] correlation_id: Option<UUID4>,
700 ) {
701 let command = CancelOrderBuilder::default()
702 .trader_id(trader_id)
703 .client_id(Some(client_id))
704 .strategy_id(strategy_id)
705 .instrument_id(instrument_id)
706 .client_order_id(client_order_id)
707 .venue_order_id(None)
708 .command_id(UUID4::new())
709 .ts_init(UnixNanos::default())
710 .params(None)
711 .correlation_id(correlation_id)
712 .build()
713 .unwrap();
714
715 let mut message = Builder::new_default();
716 {
717 let builder = message.init_root::<trading_capnp::cancel_order::Builder>();
718 command.to_capnp(builder);
719 }
720
721 let reader = message
722 .get_root_as_reader::<trading_capnp::cancel_order::Reader>()
723 .expect("Valid capnp message");
724 let header = reader.get_header().unwrap();
725
726 assert_eq!(header.has_correlation_id(), correlation_id.is_some());
727 if let Some(expected) = correlation_id {
728 let decoded = UUID4::from_capnp(header.get_correlation_id().unwrap())
729 .expect("correlation_id decodes");
730 assert_eq!(decoded, expected);
731 }
732 }
733
734 #[rstest]
735 #[case(None)]
736 #[case(Some(UUID4::new()))]
737 fn test_cancel_order_causation_id_roundtrips_through_capnp_header(
738 trader_id: TraderId,
739 client_id: ClientId,
740 strategy_id: StrategyId,
741 instrument_id: InstrumentId,
742 client_order_id: ClientOrderId,
743 #[case] causation_id: Option<UUID4>,
744 ) {
745 let command = CancelOrderBuilder::default()
746 .trader_id(trader_id)
747 .client_id(Some(client_id))
748 .strategy_id(strategy_id)
749 .instrument_id(instrument_id)
750 .client_order_id(client_order_id)
751 .venue_order_id(None)
752 .command_id(UUID4::new())
753 .ts_init(UnixNanos::default())
754 .params(None)
755 .causation_id(causation_id)
756 .build()
757 .unwrap();
758
759 let mut message = Builder::new_default();
760 {
761 let builder = message.init_root::<trading_capnp::cancel_order::Builder>();
762 command.to_capnp(builder);
763 }
764
765 let reader = message
766 .get_root_as_reader::<trading_capnp::cancel_order::Reader>()
767 .expect("Valid capnp message");
768 let header = reader.get_header().unwrap();
769
770 assert_eq!(header.has_causation_id(), causation_id.is_some());
771 if let Some(expected) = causation_id {
772 let decoded = UUID4::from_capnp(header.get_causation_id().unwrap())
773 .expect("causation_id decodes");
774 assert_eq!(decoded, expected);
775 }
776 }
777
778 #[rstest]
779 #[case(None)]
780 #[case(Some(UUID4::new()))]
781 fn test_query_account_correlation_id_roundtrips_through_capnp(
782 trader_id: TraderId,
783 command_id: UUID4,
784 ts_init: UnixNanos,
785 #[case] correlation_id: Option<UUID4>,
786 ) {
787 let command = QueryAccountBuilder::default()
788 .trader_id(trader_id)
789 .client_id(None)
790 .account_id(AccountId::new("ACC-001"))
791 .command_id(command_id)
792 .ts_init(ts_init)
793 .params(None)
794 .correlation_id(correlation_id)
795 .build()
796 .unwrap();
797
798 let mut message = Builder::new_default();
799 {
800 let builder = message.init_root::<trading_capnp::query_account::Builder>();
801 command.to_capnp(builder);
802 }
803
804 let reader = message
805 .get_root_as_reader::<trading_capnp::query_account::Reader>()
806 .expect("Valid capnp message");
807
808 assert_eq!(reader.has_correlation_id(), correlation_id.is_some());
809 if let Some(expected) = correlation_id {
810 let decoded = UUID4::from_capnp(reader.get_correlation_id().unwrap())
811 .expect("correlation_id decodes");
812 assert_eq!(decoded, expected);
813 }
814 }
815
816 #[rstest]
817 #[case(None)]
818 #[case(Some(UUID4::new()))]
819 fn test_query_account_causation_id_roundtrips_through_capnp(
820 trader_id: TraderId,
821 command_id: UUID4,
822 ts_init: UnixNanos,
823 #[case] causation_id: Option<UUID4>,
824 ) {
825 let command = QueryAccountBuilder::default()
829 .trader_id(trader_id)
830 .client_id(None)
831 .account_id(AccountId::new("ACC-001"))
832 .command_id(command_id)
833 .ts_init(ts_init)
834 .params(None)
835 .causation_id(causation_id)
836 .build()
837 .unwrap();
838
839 let mut message = Builder::new_default();
840 {
841 let builder = message.init_root::<trading_capnp::query_account::Builder>();
842 command.to_capnp(builder);
843 }
844
845 let reader = message
846 .get_root_as_reader::<trading_capnp::query_account::Reader>()
847 .expect("Valid capnp message");
848
849 assert_eq!(reader.has_causation_id(), causation_id.is_some());
850 if let Some(expected) = causation_id {
851 let decoded = UUID4::from_capnp(reader.get_causation_id().unwrap())
852 .expect("causation_id decodes");
853 assert_eq!(decoded, expected);
854 }
855 }
856
857 #[rstest]
858 fn test_submit_order_serialization(command_id: UUID4, ts_init: UnixNanos, client_id: ClientId) {
859 let order = OrderTestBuilder::new(OrderType::Limit)
860 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
861 .side(OrderSide::Buy)
862 .quantity(Quantity::new(1.0, 8))
863 .price(Price::new(50_000.0, 2))
864 .build();
865
866 let command = SubmitOrder::new(
867 order.trader_id(),
868 Some(client_id),
869 order.strategy_id(),
870 order.instrument_id(),
871 order.client_order_id(),
872 order.init_event().clone(),
873 None,
874 None,
875 None,
876 command_id,
877 ts_init,
878 None, );
880
881 let mut message = Builder::new_default();
882 {
883 let builder = message.init_root::<trading_capnp::submit_order::Builder>();
884 command.to_capnp(builder);
885 }
886
887 let reader = message
888 .get_root_as_reader::<trading_capnp::submit_order::Reader>()
889 .expect("Valid capnp message");
890
891 assert!(reader.has_header());
892 assert!(reader.has_order_init());
893 }
894
895 #[rstest]
896 fn test_submit_order_list_serialization(
897 command_id: UUID4,
898 ts_init: UnixNanos,
899 client_id: ClientId,
900 ) {
901 let order1 = OrderTestBuilder::new(OrderType::Limit)
902 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
903 .client_order_id(ClientOrderId::from("O-001"))
904 .side(OrderSide::Buy)
905 .quantity(Quantity::new(1.0, 8))
906 .price(Price::new(50_000.0, 2))
907 .build();
908
909 let order2 = OrderTestBuilder::new(OrderType::Limit)
910 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
911 .client_order_id(ClientOrderId::from("O-002"))
912 .side(OrderSide::Sell)
913 .quantity(Quantity::new(1.0, 8))
914 .price(Price::new(51_000.0, 2))
915 .build();
916
917 let orders = [order1.clone(), order2];
918 let order_inits: Vec<_> = orders.iter().map(|o| o.init_event().clone()).collect();
919 let order_list = OrderList::new(
920 OrderListId::new("OL-001"),
921 InstrumentId::from("BTCUSDT.BINANCE"),
922 order1.strategy_id(),
923 vec![order1.client_order_id(), orders[1].client_order_id()],
924 ts_init,
925 );
926
927 let command = SubmitOrderList::new(
928 order1.trader_id(),
929 Some(client_id),
930 order1.strategy_id(),
931 order_list,
932 order_inits,
933 None,
934 None,
935 None,
936 command_id,
937 ts_init,
938 None, );
940
941 let mut message = Builder::new_default();
942 {
943 let builder = message.init_root::<trading_capnp::submit_order_list::Builder>();
944 command.to_capnp(builder);
945 }
946
947 let reader = message
948 .get_root_as_reader::<trading_capnp::submit_order_list::Reader>()
949 .expect("Valid capnp message");
950
951 assert!(reader.has_header());
952 assert!(reader.has_order_inits());
953 assert_eq!(reader.get_order_inits().unwrap().len(), 2);
954 }
955
956 #[rstest]
957 fn test_trading_command_enum_serialization(
958 trader_id: TraderId,
959 strategy_id: StrategyId,
960 instrument_id: InstrumentId,
961 client_order_id: ClientOrderId,
962 command_id: UUID4,
963 ts_init: UnixNanos,
964 ) {
965 let cancel = CancelOrderBuilder::default()
966 .trader_id(trader_id)
967 .client_id(None)
968 .strategy_id(strategy_id)
969 .instrument_id(instrument_id)
970 .client_order_id(client_order_id)
971 .venue_order_id(None)
972 .command_id(command_id)
973 .ts_init(ts_init)
974 .params(None)
975 .build()
976 .unwrap();
977
978 let command = TradingCommand::CancelOrder(cancel);
979
980 let mut message = Builder::new_default();
981 {
982 let builder = message.init_root::<trading_capnp::trading_command::Builder>();
983 command.to_capnp(builder);
984 }
985
986 let reader = message
987 .get_root_as_reader::<trading_capnp::trading_command::Reader>()
988 .expect("Valid capnp message");
989
990 assert!(reader.has_cancel_order());
992 }
993}