nautilus_common/serialization/capnp/
trading.rs1use nautilus_core::{UUID4, UnixNanos};
19use nautilus_model::identifiers::{ClientId, InstrumentId, StrategyId, TraderId};
20use nautilus_serialization::{
21 capnp::{ToCapnp, order_side_to_capnp},
22 trading_capnp,
23};
24
25use crate::messages::execution::{
26 BatchCancelOrders, CancelAllOrders, CancelOrder, ModifyOrder, QueryAccount, QueryOrder,
27 SubmitOrder, SubmitOrderList, TradingCommand,
28};
29
30fn populate_trading_command_header<'a>(
32 mut builder: trading_capnp::trading_command_header::Builder<'a>,
33 trader_id: &TraderId,
34 client_id: &ClientId,
35 strategy_id: &StrategyId,
36 instrument_id: &InstrumentId,
37 command_id: &UUID4,
38 ts_init: UnixNanos,
39) {
40 let trader_id_builder = builder.reborrow().init_trader_id();
41 trader_id.to_capnp(trader_id_builder);
42
43 let client_id_builder = builder.reborrow().init_client_id();
44 client_id.to_capnp(client_id_builder);
45
46 let strategy_id_builder = builder.reborrow().init_strategy_id();
47 strategy_id.to_capnp(strategy_id_builder);
48
49 let instrument_id_builder = builder.reborrow().init_instrument_id();
50 instrument_id.to_capnp(instrument_id_builder);
51
52 let command_id_builder = builder.reborrow().init_command_id();
53 command_id.to_capnp(command_id_builder);
54
55 let mut ts_init_builder = builder.reborrow().init_ts_init();
56 ts_init_builder.set_value(*ts_init);
57}
58
59impl<'a> ToCapnp<'a> for CancelOrder {
60 type Builder = trading_capnp::cancel_order::Builder<'a>;
61
62 fn to_capnp(&self, mut builder: Self::Builder) {
63 let header_builder = builder.reborrow().init_header();
64 populate_trading_command_header(
65 header_builder,
66 &self.trader_id,
67 &self.client_id,
68 &self.strategy_id,
69 &self.instrument_id,
70 &self.command_id,
71 self.ts_init,
72 );
73
74 let client_order_id_builder = builder.reborrow().init_client_order_id();
75 self.client_order_id.to_capnp(client_order_id_builder);
76
77 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
78 self.venue_order_id.to_capnp(venue_order_id_builder);
79 }
80}
81
82impl<'a> ToCapnp<'a> for CancelAllOrders {
83 type Builder = trading_capnp::cancel_all_orders::Builder<'a>;
84
85 fn to_capnp(&self, mut builder: Self::Builder) {
86 let header_builder = builder.reborrow().init_header();
87 populate_trading_command_header(
88 header_builder,
89 &self.trader_id,
90 &self.client_id,
91 &self.strategy_id,
92 &self.instrument_id,
93 &self.command_id,
94 self.ts_init,
95 );
96
97 builder.set_order_side(order_side_to_capnp(self.order_side));
98 }
99}
100
101impl<'a> ToCapnp<'a> for BatchCancelOrders {
102 type Builder = trading_capnp::batch_cancel_orders::Builder<'a>;
103
104 fn to_capnp(&self, mut builder: Self::Builder) {
105 let header_builder = builder.reborrow().init_header();
106 populate_trading_command_header(
107 header_builder,
108 &self.trader_id,
109 &self.client_id,
110 &self.strategy_id,
111 &self.instrument_id,
112 &self.command_id,
113 self.ts_init,
114 );
115
116 let mut cancellations_builder = builder
117 .reborrow()
118 .init_cancellations(self.cancels.len() as u32);
119 for (i, cancel) in self.cancels.iter().enumerate() {
120 let cancel_builder = cancellations_builder.reborrow().get(i as u32);
121 cancel.to_capnp(cancel_builder);
122 }
123 }
124}
125
126impl<'a> ToCapnp<'a> for ModifyOrder {
127 type Builder = trading_capnp::modify_order::Builder<'a>;
128
129 fn to_capnp(&self, mut builder: Self::Builder) {
130 let header_builder = builder.reborrow().init_header();
131 populate_trading_command_header(
132 header_builder,
133 &self.trader_id,
134 &self.client_id,
135 &self.strategy_id,
136 &self.instrument_id,
137 &self.command_id,
138 self.ts_init,
139 );
140
141 let client_order_id_builder = builder.reborrow().init_client_order_id();
142 self.client_order_id.to_capnp(client_order_id_builder);
143
144 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
145 self.venue_order_id.to_capnp(venue_order_id_builder);
146
147 if let Some(ref quantity) = self.quantity {
148 let quantity_builder = builder.reborrow().init_quantity();
149 quantity.to_capnp(quantity_builder);
150 }
151
152 if let Some(ref price) = self.price {
153 let price_builder = builder.reborrow().init_price();
154 price.to_capnp(price_builder);
155 }
156
157 if let Some(ref trigger_price) = self.trigger_price {
158 let trigger_price_builder = builder.reborrow().init_trigger_price();
159 trigger_price.to_capnp(trigger_price_builder);
160 }
161 }
162}
163
164impl<'a> ToCapnp<'a> for QueryOrder {
165 type Builder = trading_capnp::query_order::Builder<'a>;
166
167 fn to_capnp(&self, mut builder: Self::Builder) {
168 let header_builder = builder.reborrow().init_header();
169 populate_trading_command_header(
170 header_builder,
171 &self.trader_id,
172 &self.client_id,
173 &self.strategy_id,
174 &self.instrument_id,
175 &self.command_id,
176 self.ts_init,
177 );
178
179 let client_order_id_builder = builder.reborrow().init_client_order_id();
180 self.client_order_id.to_capnp(client_order_id_builder);
181
182 let venue_order_id_builder = builder.reborrow().init_venue_order_id();
183 self.venue_order_id.to_capnp(venue_order_id_builder);
184 }
185}
186
187impl<'a> ToCapnp<'a> for QueryAccount {
188 type Builder = trading_capnp::query_account::Builder<'a>;
189
190 fn to_capnp(&self, mut builder: Self::Builder) {
191 let trader_id_builder = builder.reborrow().init_trader_id();
192 self.trader_id.to_capnp(trader_id_builder);
193
194 let account_id_builder = builder.reborrow().init_account_id();
195 self.account_id.to_capnp(account_id_builder);
196
197 let command_id_builder = builder.reborrow().init_command_id();
198 self.command_id.to_capnp(command_id_builder);
199
200 let mut ts_init_builder = builder.reborrow().init_ts_init();
201 ts_init_builder.set_value(*self.ts_init);
202 }
203}
204
205impl<'a> ToCapnp<'a> for SubmitOrder {
206 type Builder = trading_capnp::submit_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,
214 &self.strategy_id,
215 &self.instrument_id,
216 &self.command_id,
217 self.ts_init,
218 );
219
220 let order_init = self.order.init_event();
221 let order_init_builder = builder.reborrow().init_order_init();
222 order_init.to_capnp(order_init_builder);
223
224 if let Some(ref position_id) = self.position_id {
225 let position_id_builder = builder.reborrow().init_position_id();
226 position_id.to_capnp(position_id_builder);
227 }
228 }
229}
230
231impl<'a> ToCapnp<'a> for SubmitOrderList {
232 type Builder = trading_capnp::submit_order_list::Builder<'a>;
233
234 fn to_capnp(&self, mut builder: Self::Builder) {
235 let header_builder = builder.reborrow().init_header();
236 populate_trading_command_header(
237 header_builder,
238 &self.trader_id,
239 &self.client_id,
240 &self.strategy_id,
241 &self.instrument_id,
242 &self.command_id,
243 self.ts_init,
244 );
245
246 let mut order_inits_builder = builder
247 .reborrow()
248 .init_order_inits(self.order_list.orders.len() as u32);
249 for (i, order) in self.order_list.orders.iter().enumerate() {
250 let order_init = order.init_event();
251 let order_init_builder = order_inits_builder.reborrow().get(i as u32);
252 order_init.to_capnp(order_init_builder);
253 }
254
255 if let Some(ref position_id) = self.position_id {
256 let position_id_builder = builder.reborrow().init_position_id();
257 position_id.to_capnp(position_id_builder);
258 }
259 }
260}
261
262impl<'a> ToCapnp<'a> for TradingCommand {
263 type Builder = trading_capnp::trading_command::Builder<'a>;
264
265 fn to_capnp(&self, builder: Self::Builder) {
266 match self {
267 Self::SubmitOrder(command) => {
268 let submit_builder = builder.init_submit_order();
269 command.to_capnp(submit_builder);
270 }
271 Self::SubmitOrderList(command) => {
272 let submit_list_builder = builder.init_submit_order_list();
273 command.to_capnp(submit_list_builder);
274 }
275 Self::ModifyOrder(command) => {
276 let modify_builder = builder.init_modify_order();
277 command.to_capnp(modify_builder);
278 }
279 Self::CancelOrder(command) => {
280 let cancel_builder = builder.init_cancel_order();
281 command.to_capnp(cancel_builder);
282 }
283 Self::CancelAllOrders(command) => {
284 let cancel_all_builder = builder.init_cancel_all_orders();
285 command.to_capnp(cancel_all_builder);
286 }
287 Self::BatchCancelOrders(command) => {
288 let batch_cancel_builder = builder.init_batch_cancel_orders();
289 command.to_capnp(batch_cancel_builder);
290 }
291 Self::QueryOrder(command) => {
292 let query_builder = builder.init_query_order();
293 command.to_capnp(query_builder);
294 }
295 Self::QueryAccount(command) => {
296 let query_builder = builder.init_query_account();
297 command.to_capnp(query_builder);
298 }
299 }
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use capnp::message::Builder;
306 use nautilus_core::UnixNanos;
307 use nautilus_model::{
308 enums::{OrderSide, OrderType},
309 identifiers::{AccountId, ClientId, ClientOrderId, InstrumentId, OrderListId},
310 orders::{Order, OrderList, OrderTestBuilder},
311 types::{Price, Quantity},
312 };
313 use rstest::*;
314
315 use super::*;
316 use crate::messages::execution::{
317 cancel::{BatchCancelOrdersBuilder, CancelAllOrdersBuilder, CancelOrderBuilder},
318 modify::ModifyOrderBuilder,
319 query::{QueryAccountBuilder, QueryOrderBuilder},
320 };
321
322 #[fixture]
323 fn command_id() -> UUID4 {
324 UUID4::new()
325 }
326
327 #[fixture]
328 fn ts_init() -> UnixNanos {
329 UnixNanos::default()
330 }
331
332 #[fixture]
333 fn client_id() -> ClientId {
334 ClientId::new("TEST")
335 }
336
337 #[rstest]
338 fn test_cancel_order_serialization(command_id: UUID4, ts_init: UnixNanos) {
339 let command = CancelOrderBuilder::default()
340 .command_id(command_id)
341 .ts_init(ts_init)
342 .build()
343 .unwrap();
344
345 let mut message = Builder::new_default();
346 {
347 let builder = message.init_root::<trading_capnp::cancel_order::Builder>();
348 command.to_capnp(builder);
349 }
350
351 let reader = message
352 .get_root_as_reader::<trading_capnp::cancel_order::Reader>()
353 .expect("Valid capnp message");
354
355 assert!(reader.has_header());
357 let header = reader.get_header().unwrap();
358 assert!(header.has_trader_id());
359 assert!(header.has_client_id());
360 assert!(header.has_strategy_id());
361 assert!(header.has_instrument_id());
362 assert!(header.has_command_id());
363 assert!(header.has_ts_init());
364 }
365
366 #[rstest]
367 fn test_cancel_all_orders_serialization(command_id: UUID4, ts_init: UnixNanos) {
368 let command = CancelAllOrdersBuilder::default()
369 .order_side(OrderSide::Buy)
370 .command_id(command_id)
371 .ts_init(ts_init)
372 .build()
373 .unwrap();
374
375 let mut message = Builder::new_default();
376 {
377 let builder = message.init_root::<trading_capnp::cancel_all_orders::Builder>();
378 command.to_capnp(builder);
379 }
380
381 let reader = message
382 .get_root_as_reader::<trading_capnp::cancel_all_orders::Reader>()
383 .expect("Valid capnp message");
384
385 assert!(reader.has_header());
386 }
387
388 #[rstest]
389 fn test_batch_cancel_orders_serialization(command_id: UUID4, ts_init: UnixNanos) {
390 let cancel1 = CancelOrderBuilder::default()
391 .client_order_id(ClientOrderId::new("O-001"))
392 .command_id(UUID4::new())
393 .ts_init(ts_init)
394 .build()
395 .unwrap();
396
397 let cancel2 = CancelOrderBuilder::default()
398 .client_order_id(ClientOrderId::new("O-002"))
399 .command_id(UUID4::new())
400 .ts_init(ts_init)
401 .build()
402 .unwrap();
403
404 let command = BatchCancelOrdersBuilder::default()
405 .cancels(vec![cancel1, cancel2])
406 .command_id(command_id)
407 .ts_init(ts_init)
408 .build()
409 .unwrap();
410
411 let mut message = Builder::new_default();
412 {
413 let builder = message.init_root::<trading_capnp::batch_cancel_orders::Builder>();
414 command.to_capnp(builder);
415 }
416
417 let reader = message
418 .get_root_as_reader::<trading_capnp::batch_cancel_orders::Reader>()
419 .expect("Valid capnp message");
420
421 assert!(reader.has_header());
422 assert!(reader.has_cancellations());
423 assert_eq!(reader.get_cancellations().unwrap().len(), 2);
424 }
425
426 #[rstest]
427 fn test_modify_order_serialization(command_id: UUID4, ts_init: UnixNanos) {
428 let command = ModifyOrderBuilder::default()
429 .quantity(Some(Quantity::new(100.0, 0)))
430 .price(Some(Price::new(50_000.0, 2)))
431 .trigger_price(Some(Price::new(49_000.0, 2)))
432 .command_id(command_id)
433 .ts_init(ts_init)
434 .build()
435 .unwrap();
436
437 let mut message = Builder::new_default();
438 {
439 let builder = message.init_root::<trading_capnp::modify_order::Builder>();
440 command.to_capnp(builder);
441 }
442
443 let reader = message
444 .get_root_as_reader::<trading_capnp::modify_order::Reader>()
445 .expect("Valid capnp message");
446
447 assert!(reader.has_header());
448 assert!(reader.has_quantity());
449 assert!(reader.has_price());
450 assert!(reader.has_trigger_price());
451 }
452
453 #[rstest]
454 fn test_query_order_serialization(command_id: UUID4, ts_init: UnixNanos) {
455 let command = QueryOrderBuilder::default()
456 .command_id(command_id)
457 .ts_init(ts_init)
458 .build()
459 .unwrap();
460
461 let mut message = Builder::new_default();
462 {
463 let builder = message.init_root::<trading_capnp::query_order::Builder>();
464 command.to_capnp(builder);
465 }
466
467 let reader = message
468 .get_root_as_reader::<trading_capnp::query_order::Reader>()
469 .expect("Valid capnp message");
470
471 assert!(reader.has_header());
472 }
473
474 #[rstest]
475 fn test_query_account_serialization(command_id: UUID4, ts_init: UnixNanos) {
476 let command = QueryAccountBuilder::default()
477 .account_id(AccountId::new("ACC-001"))
478 .command_id(command_id)
479 .ts_init(ts_init)
480 .build()
481 .unwrap();
482
483 let mut message = Builder::new_default();
484 {
485 let builder = message.init_root::<trading_capnp::query_account::Builder>();
486 command.to_capnp(builder);
487 }
488
489 let reader = message
490 .get_root_as_reader::<trading_capnp::query_account::Reader>()
491 .expect("Valid capnp message");
492
493 assert!(reader.has_trader_id());
494 assert!(reader.has_account_id());
495 }
496
497 #[rstest]
498 fn test_submit_order_serialization(command_id: UUID4, ts_init: UnixNanos, client_id: ClientId) {
499 let order = OrderTestBuilder::new(OrderType::Limit)
500 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
501 .side(OrderSide::Buy)
502 .quantity(Quantity::new(1.0, 8))
503 .price(Price::new(50_000.0, 2))
504 .build();
505
506 let command = SubmitOrder::new(
507 order.trader_id(),
508 client_id,
509 order.strategy_id(),
510 order.instrument_id(),
511 order.client_order_id(),
512 order.venue_order_id().unwrap_or_default(),
513 order,
514 None,
515 None,
516 None,
517 command_id,
518 ts_init,
519 )
520 .unwrap();
521
522 let mut message = Builder::new_default();
523 {
524 let builder = message.init_root::<trading_capnp::submit_order::Builder>();
525 command.to_capnp(builder);
526 }
527
528 let reader = message
529 .get_root_as_reader::<trading_capnp::submit_order::Reader>()
530 .expect("Valid capnp message");
531
532 assert!(reader.has_header());
533 assert!(reader.has_order_init());
534 }
535
536 #[rstest]
537 fn test_submit_order_list_serialization(
538 command_id: UUID4,
539 ts_init: UnixNanos,
540 client_id: ClientId,
541 ) {
542 let order1 = OrderTestBuilder::new(OrderType::Limit)
543 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
544 .side(OrderSide::Buy)
545 .quantity(Quantity::new(1.0, 8))
546 .price(Price::new(50_000.0, 2))
547 .build();
548
549 let order2 = OrderTestBuilder::new(OrderType::Limit)
550 .instrument_id(InstrumentId::from("BTCUSDT.BINANCE"))
551 .side(OrderSide::Sell)
552 .quantity(Quantity::new(1.0, 8))
553 .price(Price::new(51_000.0, 2))
554 .build();
555
556 let order_list = OrderList::new(
557 OrderListId::new("OL-001"),
558 InstrumentId::from("BTCUSDT.BINANCE"),
559 order1.strategy_id(),
560 vec![order1.clone(), order2],
561 ts_init,
562 );
563
564 let command = SubmitOrderList::new(
565 order1.trader_id(),
566 client_id,
567 order1.strategy_id(),
568 order1.instrument_id(),
569 order1.client_order_id(),
570 order1.venue_order_id().unwrap_or_default(),
571 order_list,
572 None,
573 None,
574 command_id,
575 ts_init,
576 )
577 .unwrap();
578
579 let mut message = Builder::new_default();
580 {
581 let builder = message.init_root::<trading_capnp::submit_order_list::Builder>();
582 command.to_capnp(builder);
583 }
584
585 let reader = message
586 .get_root_as_reader::<trading_capnp::submit_order_list::Reader>()
587 .expect("Valid capnp message");
588
589 assert!(reader.has_header());
590 assert!(reader.has_order_inits());
591 assert_eq!(reader.get_order_inits().unwrap().len(), 2);
592 }
593
594 #[rstest]
595 fn test_trading_command_enum_serialization(command_id: UUID4, ts_init: UnixNanos) {
596 let cancel = CancelOrderBuilder::default()
597 .command_id(command_id)
598 .ts_init(ts_init)
599 .build()
600 .unwrap();
601
602 let command = TradingCommand::CancelOrder(cancel);
603
604 let mut message = Builder::new_default();
605 {
606 let builder = message.init_root::<trading_capnp::trading_command::Builder>();
607 command.to_capnp(builder);
608 }
609
610 let reader = message
611 .get_root_as_reader::<trading_capnp::trading_command::Reader>()
612 .expect("Valid capnp message");
613
614 assert!(reader.has_cancel_order());
616 }
617}