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