1use std::collections::HashSet;
36
37use bytes::Bytes;
38use nautilus_common::{
39 messages::{
40 data::{
41 BarsResponse, BookDeltasResponse, BookDepthResponse, BookResponse, CustomDataResponse,
42 DataCommand, DataResponse, ForwardPricesResponse, FundingRatesResponse,
43 InstrumentResponse, InstrumentsResponse, QuotesResponse, TradesResponse,
44 },
45 execution::{
46 BatchCancelOrders, BatchModifyOrders, CancelAllOrders, CancelOrder, ExecutionReport,
47 ModifyOrder, QueryAccount, QueryOrder, SubmitOrder, SubmitOrderList, TradingCommand,
48 },
49 },
50 timer::TimeEvent,
51};
52use nautilus_core::{Params, UUID4, UnixNanos};
53use nautilus_model::{
54 data::DataType,
55 events::{
56 AccountState, OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied,
57 OrderEmulated, OrderEventAny, OrderExpired, OrderFilled, OrderInitialized,
58 OrderModifyRejected, OrderPendingCancel, OrderPendingUpdate, OrderRejected, OrderReleased,
59 OrderSubmitted, OrderTriggered, OrderUpdated, PositionAdjusted, PositionChanged,
60 PositionClosed, PositionEvent, PositionOpened,
61 },
62 identifiers::{ClientId, InstrumentId, Venue},
63 reports::{ExecutionMassStatus, FillReport, OrderStatusReport, PositionStatusReport},
64};
65use serde::Serialize;
66use ustr::Ustr;
67
68use crate::{
69 backend::{IndexKey, IndexKind},
70 capture::{
71 encoder::{EncodeError, EncodedPayload},
72 registry::EncoderRegistry,
73 },
74 entry::PayloadType,
75 headers::Headers,
76};
77
78pub const PAYLOAD_TYPE_SUBMIT_ORDER: &str = "SubmitOrder";
80pub const PAYLOAD_TYPE_SUBMIT_ORDER_LIST: &str = "SubmitOrderList";
82pub const PAYLOAD_TYPE_MODIFY_ORDER: &str = "ModifyOrder";
84pub const PAYLOAD_TYPE_BATCH_MODIFY_ORDERS: &str = "BatchModifyOrders";
86pub const PAYLOAD_TYPE_CANCEL_ORDER: &str = "CancelOrder";
88pub const PAYLOAD_TYPE_CANCEL_ALL_ORDERS: &str = "CancelAllOrders";
90pub const PAYLOAD_TYPE_BATCH_CANCEL_ORDERS: &str = "BatchCancelOrders";
92pub const PAYLOAD_TYPE_QUERY_ORDER: &str = "QueryOrder";
94pub const PAYLOAD_TYPE_QUERY_ACCOUNT: &str = "QueryAccount";
96
97pub const PAYLOAD_TYPE_ORDER_INITIALIZED: &str = "OrderInitialized";
99pub const PAYLOAD_TYPE_ORDER_DENIED: &str = "OrderDenied";
101pub const PAYLOAD_TYPE_ORDER_EMULATED: &str = "OrderEmulated";
103pub const PAYLOAD_TYPE_ORDER_RELEASED: &str = "OrderReleased";
105pub const PAYLOAD_TYPE_ORDER_SUBMITTED: &str = "OrderSubmitted";
107pub const PAYLOAD_TYPE_ORDER_ACCEPTED: &str = "OrderAccepted";
109pub const PAYLOAD_TYPE_ORDER_REJECTED: &str = "OrderRejected";
111pub const PAYLOAD_TYPE_ORDER_CANCELED: &str = "OrderCanceled";
113pub const PAYLOAD_TYPE_ORDER_EXPIRED: &str = "OrderExpired";
115pub const PAYLOAD_TYPE_ORDER_TRIGGERED: &str = "OrderTriggered";
117pub const PAYLOAD_TYPE_ORDER_PENDING_UPDATE: &str = "OrderPendingUpdate";
119pub const PAYLOAD_TYPE_ORDER_PENDING_CANCEL: &str = "OrderPendingCancel";
121pub const PAYLOAD_TYPE_ORDER_MODIFY_REJECTED: &str = "OrderModifyRejected";
123pub const PAYLOAD_TYPE_ORDER_CANCEL_REJECTED: &str = "OrderCancelRejected";
125pub const PAYLOAD_TYPE_ORDER_UPDATED: &str = "OrderUpdated";
127pub const PAYLOAD_TYPE_ORDER_FILLED: &str = "OrderFilled";
129pub const PAYLOAD_TYPE_ORDER_STATUS_REPORT: &str = "OrderStatusReport";
131pub const PAYLOAD_TYPE_FILL_REPORT: &str = "FillReport";
133pub const PAYLOAD_TYPE_ORDER_WITH_FILLS: &str = "OrderWithFills";
135pub const PAYLOAD_TYPE_POSITION_STATUS_REPORT: &str = "PositionStatusReport";
137pub const PAYLOAD_TYPE_EXECUTION_MASS_STATUS: &str = "ExecutionMassStatus";
139pub const PAYLOAD_TYPE_POSITION_OPENED: &str = "PositionOpened";
141pub const PAYLOAD_TYPE_POSITION_CHANGED: &str = "PositionChanged";
143pub const PAYLOAD_TYPE_POSITION_CLOSED: &str = "PositionClosed";
145pub const PAYLOAD_TYPE_POSITION_ADJUSTED: &str = "PositionAdjusted";
147pub const PAYLOAD_TYPE_ACCOUNT_STATE: &str = "AccountState";
149pub const PAYLOAD_TYPE_TIME_EVENT: &str = "TimeEvent";
151
152pub const PAYLOAD_TYPE_REQUEST_COMMAND: &str = "RequestCommand";
154pub const PAYLOAD_TYPE_SUBSCRIBE_COMMAND: &str = "SubscribeCommand";
156pub const PAYLOAD_TYPE_UNSUBSCRIBE_COMMAND: &str = "UnsubscribeCommand";
158#[cfg(feature = "defi")]
159pub const PAYLOAD_TYPE_DEFI_REQUEST_COMMAND: &str = "DefiRequestCommand";
161#[cfg(feature = "defi")]
162pub const PAYLOAD_TYPE_DEFI_SUBSCRIBE_COMMAND: &str = "DefiSubscribeCommand";
164#[cfg(feature = "defi")]
165pub const PAYLOAD_TYPE_DEFI_UNSUBSCRIBE_COMMAND: &str = "DefiUnsubscribeCommand";
167
168pub const PAYLOAD_TYPE_CUSTOM_DATA_RESPONSE: &str = "CustomDataResponse";
170pub const PAYLOAD_TYPE_INSTRUMENT_RESPONSE: &str = "InstrumentResponse";
172pub const PAYLOAD_TYPE_INSTRUMENTS_RESPONSE: &str = "InstrumentsResponse";
174pub const PAYLOAD_TYPE_BOOK_RESPONSE: &str = "BookResponse";
176pub const PAYLOAD_TYPE_BOOK_DELTAS_RESPONSE: &str = "BookDeltasResponse";
178pub const PAYLOAD_TYPE_BOOK_DEPTH_RESPONSE: &str = "BookDepthResponse";
180pub const PAYLOAD_TYPE_QUOTES_RESPONSE: &str = "QuotesResponse";
182pub const PAYLOAD_TYPE_TRADES_RESPONSE: &str = "TradesResponse";
184pub const PAYLOAD_TYPE_FUNDING_RATES_RESPONSE: &str = "FundingRatesResponse";
186pub const PAYLOAD_TYPE_FORWARD_PRICES_RESPONSE: &str = "ForwardPricesResponse";
188pub const PAYLOAD_TYPE_BARS_RESPONSE: &str = "BarsResponse";
190
191const PAYLOAD_TYPE_TRADING_COMMAND: &str = "TradingCommand";
196
197const PAYLOAD_TYPE_ORDER_EVENT_ANY: &str = "OrderEventAny";
198
199const PAYLOAD_TYPE_EXECUTION_REPORT: &str = "ExecutionReport";
200
201const PAYLOAD_TYPE_POSITION_EVENT: &str = "PositionEvent";
202
203const PAYLOAD_TYPE_DATA_COMMAND: &str = "DataCommand";
204
205const PAYLOAD_TYPE_DATA_RESPONSE: &str = "DataResponse";
206
207#[cfg(test)]
208pub(crate) const DEFAULT_CAPTURE_PAYLOAD_TYPES: &[&str] = &[
209 PAYLOAD_TYPE_SUBMIT_ORDER,
210 PAYLOAD_TYPE_SUBMIT_ORDER_LIST,
211 PAYLOAD_TYPE_MODIFY_ORDER,
212 PAYLOAD_TYPE_BATCH_MODIFY_ORDERS,
213 PAYLOAD_TYPE_CANCEL_ORDER,
214 PAYLOAD_TYPE_CANCEL_ALL_ORDERS,
215 PAYLOAD_TYPE_BATCH_CANCEL_ORDERS,
216 PAYLOAD_TYPE_QUERY_ORDER,
217 PAYLOAD_TYPE_QUERY_ACCOUNT,
218 PAYLOAD_TYPE_ORDER_INITIALIZED,
219 PAYLOAD_TYPE_ORDER_DENIED,
220 PAYLOAD_TYPE_ORDER_EMULATED,
221 PAYLOAD_TYPE_ORDER_RELEASED,
222 PAYLOAD_TYPE_ORDER_SUBMITTED,
223 PAYLOAD_TYPE_ORDER_ACCEPTED,
224 PAYLOAD_TYPE_ORDER_REJECTED,
225 PAYLOAD_TYPE_ORDER_CANCELED,
226 PAYLOAD_TYPE_ORDER_EXPIRED,
227 PAYLOAD_TYPE_ORDER_TRIGGERED,
228 PAYLOAD_TYPE_ORDER_PENDING_UPDATE,
229 PAYLOAD_TYPE_ORDER_PENDING_CANCEL,
230 PAYLOAD_TYPE_ORDER_MODIFY_REJECTED,
231 PAYLOAD_TYPE_ORDER_CANCEL_REJECTED,
232 PAYLOAD_TYPE_ORDER_UPDATED,
233 PAYLOAD_TYPE_ORDER_FILLED,
234 PAYLOAD_TYPE_ORDER_STATUS_REPORT,
235 PAYLOAD_TYPE_FILL_REPORT,
236 PAYLOAD_TYPE_ORDER_WITH_FILLS,
237 PAYLOAD_TYPE_POSITION_STATUS_REPORT,
238 PAYLOAD_TYPE_EXECUTION_MASS_STATUS,
239 PAYLOAD_TYPE_POSITION_OPENED,
240 PAYLOAD_TYPE_POSITION_CHANGED,
241 PAYLOAD_TYPE_POSITION_CLOSED,
242 PAYLOAD_TYPE_POSITION_ADJUSTED,
243 PAYLOAD_TYPE_ACCOUNT_STATE,
244 PAYLOAD_TYPE_TIME_EVENT,
245 PAYLOAD_TYPE_REQUEST_COMMAND,
246 PAYLOAD_TYPE_SUBSCRIBE_COMMAND,
247 PAYLOAD_TYPE_UNSUBSCRIBE_COMMAND,
248 #[cfg(feature = "defi")]
249 PAYLOAD_TYPE_DEFI_REQUEST_COMMAND,
250 #[cfg(feature = "defi")]
251 PAYLOAD_TYPE_DEFI_SUBSCRIBE_COMMAND,
252 #[cfg(feature = "defi")]
253 PAYLOAD_TYPE_DEFI_UNSUBSCRIBE_COMMAND,
254 PAYLOAD_TYPE_CUSTOM_DATA_RESPONSE,
255 PAYLOAD_TYPE_INSTRUMENT_RESPONSE,
256 PAYLOAD_TYPE_INSTRUMENTS_RESPONSE,
257 PAYLOAD_TYPE_BOOK_RESPONSE,
258 PAYLOAD_TYPE_BOOK_DELTAS_RESPONSE,
259 PAYLOAD_TYPE_BOOK_DEPTH_RESPONSE,
260 PAYLOAD_TYPE_QUOTES_RESPONSE,
261 PAYLOAD_TYPE_TRADES_RESPONSE,
262 PAYLOAD_TYPE_FUNDING_RATES_RESPONSE,
263 PAYLOAD_TYPE_FORWARD_PRICES_RESPONSE,
264 PAYLOAD_TYPE_BARS_RESPONSE,
265];
266
267#[must_use]
272pub fn default_registry() -> EncoderRegistry {
273 let mut registry = EncoderRegistry::new();
274 register_default(&mut registry);
275 registry
276}
277
278pub fn register_default(registry: &mut EncoderRegistry) {
299 registry
300 .register::<SubmitOrder, _>(payload_type(PAYLOAD_TYPE_SUBMIT_ORDER), encode_submit_order);
301 registry
302 .register::<OrderFilled, _>(payload_type(PAYLOAD_TYPE_ORDER_FILLED), encode_order_filled);
303 registry.register::<OrderStatusReport, _>(
304 payload_type(PAYLOAD_TYPE_ORDER_STATUS_REPORT),
305 encode_order_status_report,
306 );
307 registry.register::<FillReport, _>(payload_type(PAYLOAD_TYPE_FILL_REPORT), encode_fill_report);
308 registry.register::<PositionStatusReport, _>(
309 payload_type(PAYLOAD_TYPE_POSITION_STATUS_REPORT),
310 encode_position_status_report,
311 );
312 registry.register::<TradingCommand, _>(
313 payload_type(PAYLOAD_TYPE_TRADING_COMMAND),
314 encode_trading_command,
315 );
316 registry.register::<OrderEventAny, _>(
317 payload_type(PAYLOAD_TYPE_ORDER_EVENT_ANY),
318 encode_order_event_any,
319 );
320 registry.register::<ExecutionReport, _>(
321 payload_type(PAYLOAD_TYPE_EXECUTION_REPORT),
322 encode_execution_report,
323 );
324 registry.register::<PositionEvent, _>(
325 payload_type(PAYLOAD_TYPE_POSITION_EVENT),
326 encode_position_event,
327 );
328 registry.register::<AccountState, _>(
329 payload_type(PAYLOAD_TYPE_ACCOUNT_STATE),
330 encode_account_state,
331 );
332 registry.register::<TimeEvent, _>(payload_type(PAYLOAD_TYPE_TIME_EVENT), encode_time_event);
333 registry
334 .register::<DataCommand, _>(payload_type(PAYLOAD_TYPE_DATA_COMMAND), encode_data_command);
335 registry.register::<DataResponse, _>(
336 payload_type(PAYLOAD_TYPE_DATA_RESPONSE),
337 encode_data_response,
338 );
339
340 register_default_headers(registry);
341 register_default_identities(registry);
342}
343
344fn register_default_headers(registry: &mut EncoderRegistry) {
352 registry.register_headers::<SubmitOrder, _>(extract_submit_order_headers);
353 registry.register_headers::<SubmitOrderList, _>(extract_submit_order_list_headers);
354 registry.register_headers::<ModifyOrder, _>(extract_modify_order_headers);
355 registry.register_headers::<BatchModifyOrders, _>(extract_batch_modify_orders_headers);
356 registry.register_headers::<CancelOrder, _>(extract_cancel_order_headers);
357 registry.register_headers::<CancelAllOrders, _>(extract_cancel_all_orders_headers);
358 registry.register_headers::<BatchCancelOrders, _>(extract_batch_cancel_orders_headers);
359 registry.register_headers::<QueryOrder, _>(extract_query_order_headers);
360 registry.register_headers::<QueryAccount, _>(extract_query_account_headers);
361 registry.register_headers::<TradingCommand, _>(extract_trading_command_headers);
362 registry.register_headers::<DataCommand, _>(extract_data_command_headers);
363 registry.register_headers::<DataResponse, _>(extract_data_response_headers);
364}
365
366fn register_default_identities(registry: &mut EncoderRegistry) {
373 registry.register_identity::<SubmitOrder, _>(|command| Some(command.command_id));
374 registry.register_identity::<OrderFilled, _>(|event| Some(event.event_id));
375 registry.register_identity::<TradingCommand, _>(|c| Some(extract_trading_command_identity(c)));
376 registry.register_identity::<OrderEventAny, _>(|e| Some(extract_order_event_any_identity(e)));
377 registry.register_identity::<AccountState, _>(|state| Some(state.event_id));
378}
379
380fn extract_trading_command_identity(command: &TradingCommand) -> UUID4 {
381 match command {
382 TradingCommand::SubmitOrder(c) => c.command_id,
383 TradingCommand::SubmitOrderList(c) => c.command_id,
384 TradingCommand::ModifyOrder(c) => c.command_id,
385 TradingCommand::ModifyOrders(c) => c.command_id,
386 TradingCommand::CancelOrder(c) => c.command_id,
387 TradingCommand::CancelOrders(c) => c.command_id,
388 TradingCommand::CancelAllOrders(c) => c.command_id,
389 TradingCommand::QueryOrder(c) => c.command_id,
390 TradingCommand::QueryAccount(c) => c.command_id,
391 }
392}
393
394fn extract_order_event_any_identity(event: &OrderEventAny) -> UUID4 {
395 match event {
396 OrderEventAny::Initialized(e) => e.event_id,
397 OrderEventAny::Denied(e) => e.event_id,
398 OrderEventAny::Emulated(e) => e.event_id,
399 OrderEventAny::Released(e) => e.event_id,
400 OrderEventAny::Submitted(e) => e.event_id,
401 OrderEventAny::Accepted(e) => e.event_id,
402 OrderEventAny::Rejected(e) => e.event_id,
403 OrderEventAny::Canceled(e) => e.event_id,
404 OrderEventAny::Expired(e) => e.event_id,
405 OrderEventAny::Triggered(e) => e.event_id,
406 OrderEventAny::PendingUpdate(e) => e.event_id,
407 OrderEventAny::PendingCancel(e) => e.event_id,
408 OrderEventAny::ModifyRejected(e) => e.event_id,
409 OrderEventAny::CancelRejected(e) => e.event_id,
410 OrderEventAny::Updated(e) => e.event_id,
411 OrderEventAny::Filled(e) => e.event_id,
412 }
413}
414
415fn headers_from_fields(correlation_id: Option<UUID4>, causation_id: Option<UUID4>) -> Headers {
416 Headers {
417 correlation_id,
418 causation_id,
419 }
420}
421
422fn extract_submit_order_headers(cmd: &SubmitOrder) -> Headers {
423 headers_from_fields(cmd.correlation_id, cmd.causation_id)
424}
425
426fn extract_submit_order_list_headers(cmd: &SubmitOrderList) -> Headers {
427 headers_from_fields(cmd.correlation_id, cmd.causation_id)
428}
429
430fn extract_modify_order_headers(cmd: &ModifyOrder) -> Headers {
431 headers_from_fields(cmd.correlation_id, cmd.causation_id)
432}
433
434fn extract_batch_modify_orders_headers(cmd: &BatchModifyOrders) -> Headers {
435 headers_from_fields(cmd.correlation_id, cmd.causation_id)
436}
437
438fn extract_cancel_order_headers(cmd: &CancelOrder) -> Headers {
439 headers_from_fields(cmd.correlation_id, cmd.causation_id)
440}
441
442fn extract_cancel_all_orders_headers(cmd: &CancelAllOrders) -> Headers {
443 headers_from_fields(cmd.correlation_id, cmd.causation_id)
444}
445
446fn extract_batch_cancel_orders_headers(cmd: &BatchCancelOrders) -> Headers {
447 headers_from_fields(cmd.correlation_id, cmd.causation_id)
448}
449
450fn extract_query_order_headers(cmd: &QueryOrder) -> Headers {
451 headers_from_fields(cmd.correlation_id, cmd.causation_id)
452}
453
454fn extract_query_account_headers(cmd: &QueryAccount) -> Headers {
455 headers_from_fields(cmd.correlation_id, cmd.causation_id)
456}
457
458fn extract_trading_command_headers(command: &TradingCommand) -> Headers {
462 match command {
463 TradingCommand::SubmitOrder(cmd) => extract_submit_order_headers(cmd),
464 TradingCommand::SubmitOrderList(cmd) => extract_submit_order_list_headers(cmd),
465 TradingCommand::ModifyOrder(cmd) => extract_modify_order_headers(cmd),
466 TradingCommand::ModifyOrders(cmd) => extract_batch_modify_orders_headers(cmd),
467 TradingCommand::CancelOrder(cmd) => extract_cancel_order_headers(cmd),
468 TradingCommand::CancelOrders(cmd) => extract_batch_cancel_orders_headers(cmd),
469 TradingCommand::CancelAllOrders(cmd) => extract_cancel_all_orders_headers(cmd),
470 TradingCommand::QueryOrder(cmd) => extract_query_order_headers(cmd),
471 TradingCommand::QueryAccount(cmd) => extract_query_account_headers(cmd),
472 }
473}
474
475fn extract_data_command_headers(command: &DataCommand) -> Headers {
483 match command {
484 DataCommand::Request(cmd) => headers_from_fields(Some(*cmd.request_id()), None),
485 DataCommand::Subscribe(cmd) => headers_from_fields(cmd.correlation_id(), None),
486 DataCommand::Unsubscribe(cmd) => headers_from_fields(cmd.correlation_id(), None),
487 _ => Headers::empty(),
491 }
492}
493
494fn extract_data_response_headers(response: &DataResponse) -> Headers {
497 headers_from_fields(Some(*response.correlation_id()), None)
498}
499
500fn payload_type(tag: &str) -> PayloadType {
501 Ustr::from(tag)
502}
503
504fn encode_serde<T: Serialize>(value: &T) -> Result<Bytes, EncodeError> {
505 rmp_serde::to_vec_named(value)
506 .map(Bytes::from)
507 .map_err(|e| EncodeError::Serialize(e.to_string()))
508}
509
510pub fn encode_submit_order(message: &SubmitOrder) -> Result<EncodedPayload, EncodeError> {
518 let payload = encode_serde(message)?;
519 let index_keys = vec![IndexKey::new(
520 IndexKind::ClientOrderId,
521 message.client_order_id.to_string(),
522 )];
523 Ok(EncodedPayload::new(payload, index_keys))
524}
525
526pub fn encode_order_filled(message: &OrderFilled) -> Result<EncodedPayload, EncodeError> {
533 let payload = encode_serde(message)?;
534 let index_keys = vec![
535 IndexKey::new(
536 IndexKind::ClientOrderId,
537 message.client_order_id.to_string(),
538 ),
539 IndexKey::new(IndexKind::VenueOrderId, message.venue_order_id.to_string()),
540 ];
541 Ok(EncodedPayload::new(payload, index_keys))
542}
543
544pub fn encode_trading_command(command: &TradingCommand) -> Result<EncodedPayload, EncodeError> {
557 match command {
558 TradingCommand::SubmitOrder(cmd) => {
559 Ok(retag(encode_submit_order(cmd)?, PAYLOAD_TYPE_SUBMIT_ORDER))
560 }
561 TradingCommand::SubmitOrderList(cmd) => encode_submit_order_list(cmd),
562 TradingCommand::ModifyOrder(cmd) => encode_modify_order(cmd),
563 TradingCommand::ModifyOrders(cmd) => encode_batch_modify_orders(cmd),
564 TradingCommand::CancelOrder(cmd) => encode_cancel_order(cmd),
565 TradingCommand::CancelOrders(cmd) => encode_batch_cancel_orders(cmd),
566 TradingCommand::CancelAllOrders(cmd) => encode_cancel_all_orders(cmd),
567 TradingCommand::QueryOrder(cmd) => encode_query_order(cmd),
568 TradingCommand::QueryAccount(cmd) => encode_query_account(cmd),
569 }
570}
571
572pub fn encode_order_event_any(event: &OrderEventAny) -> Result<EncodedPayload, EncodeError> {
583 match event {
584 OrderEventAny::Initialized(e) => encode_order_initialized(e),
585 OrderEventAny::Denied(e) => encode_order_denied(e),
586 OrderEventAny::Emulated(e) => encode_order_emulated(e),
587 OrderEventAny::Released(e) => encode_order_released(e),
588 OrderEventAny::Submitted(e) => encode_order_submitted(e),
589 OrderEventAny::Accepted(e) => encode_order_accepted(e),
590 OrderEventAny::Rejected(e) => encode_order_rejected(e),
591 OrderEventAny::Canceled(e) => encode_order_canceled(e),
592 OrderEventAny::Expired(e) => encode_order_expired(e),
593 OrderEventAny::Triggered(e) => encode_order_triggered(e),
594 OrderEventAny::PendingUpdate(e) => encode_order_pending_update(e),
595 OrderEventAny::PendingCancel(e) => encode_order_pending_cancel(e),
596 OrderEventAny::ModifyRejected(e) => encode_order_modify_rejected(e),
597 OrderEventAny::CancelRejected(e) => encode_order_cancel_rejected(e),
598 OrderEventAny::Updated(e) => encode_order_updated(e),
599 OrderEventAny::Filled(e) => Ok(retag(encode_order_filled(e)?, PAYLOAD_TYPE_ORDER_FILLED)),
600 }
601}
602
603pub fn encode_execution_report(report: &ExecutionReport) -> Result<EncodedPayload, EncodeError> {
620 match report {
621 ExecutionReport::Order(r) => Ok(retag(
622 encode_order_status_report(r)?,
623 PAYLOAD_TYPE_ORDER_STATUS_REPORT,
624 )),
625 ExecutionReport::Fill(r) => encode_fill_report(r),
626 ExecutionReport::OrderWithFills(order, fills) => encode_order_with_fills(order, fills),
627 ExecutionReport::Position(r) => encode_position_status_report(r),
628 ExecutionReport::MassStatus(s) => encode_execution_mass_status(s),
629 }
630}
631
632pub fn encode_fill_report(report: &FillReport) -> Result<EncodedPayload, EncodeError> {
639 let payload = encode_serde(report)?;
640 let mut index_keys = Vec::with_capacity(2);
641 index_keys.push(IndexKey::new(
642 IndexKind::VenueOrderId,
643 report.venue_order_id.to_string(),
644 ));
645
646 if let Some(client_order_id) = &report.client_order_id {
647 index_keys.push(IndexKey::new(
648 IndexKind::ClientOrderId,
649 client_order_id.to_string(),
650 ));
651 }
652
653 Ok(EncodedPayload::with_payload_type(
654 payload_type(PAYLOAD_TYPE_FILL_REPORT),
655 payload,
656 index_keys,
657 ))
658}
659
660pub fn encode_position_status_report(
671 report: &PositionStatusReport,
672) -> Result<EncodedPayload, EncodeError> {
673 let payload = encode_serde(report)?;
674 Ok(EncodedPayload::with_payload_type(
675 payload_type(PAYLOAD_TYPE_POSITION_STATUS_REPORT),
676 payload,
677 Vec::new(),
678 ))
679}
680
681fn encode_order_with_fills(
682 order: &OrderStatusReport,
683 fills: &[FillReport],
684) -> Result<EncodedPayload, EncodeError> {
685 #[derive(Serialize)]
686 struct OrderWithFillsRef<'a> {
687 order_report: &'a OrderStatusReport,
688 fill_reports: &'a [FillReport],
689 }
690
691 let payload = encode_serde(&OrderWithFillsRef {
692 order_report: order,
693 fill_reports: fills,
694 })?;
695 let mut index_keys = Vec::new();
696 let mut seen = HashSet::new();
697 push_unique_index_key(
698 &mut index_keys,
699 &mut seen,
700 IndexKind::VenueOrderId,
701 order.venue_order_id.to_string(),
702 );
703
704 if let Some(client_order_id) = &order.client_order_id {
705 push_unique_index_key(
706 &mut index_keys,
707 &mut seen,
708 IndexKind::ClientOrderId,
709 client_order_id.to_string(),
710 );
711 }
712
713 for fill in fills {
714 push_unique_index_key(
715 &mut index_keys,
716 &mut seen,
717 IndexKind::VenueOrderId,
718 fill.venue_order_id.to_string(),
719 );
720
721 if let Some(client_order_id) = &fill.client_order_id {
722 push_unique_index_key(
723 &mut index_keys,
724 &mut seen,
725 IndexKind::ClientOrderId,
726 client_order_id.to_string(),
727 );
728 }
729 }
730
731 Ok(EncodedPayload::with_payload_type(
732 payload_type(PAYLOAD_TYPE_ORDER_WITH_FILLS),
733 payload,
734 index_keys,
735 ))
736}
737
738fn encode_execution_mass_status(
739 status: &ExecutionMassStatus,
740) -> Result<EncodedPayload, EncodeError> {
741 let payload = encode_serde(status)?;
742 let mut index_keys = Vec::new();
743 let mut seen = HashSet::new();
744
745 let order_reports = status.order_reports();
746
747 for (venue_order_id, report) in &order_reports {
748 push_unique_index_key(
749 &mut index_keys,
750 &mut seen,
751 IndexKind::VenueOrderId,
752 venue_order_id.to_string(),
753 );
754
755 if let Some(client_order_id) = &report.client_order_id {
756 push_unique_index_key(
757 &mut index_keys,
758 &mut seen,
759 IndexKind::ClientOrderId,
760 client_order_id.to_string(),
761 );
762 }
763 }
764
765 let fill_reports = status.fill_reports();
766
767 for (venue_order_id, fills) in &fill_reports {
768 push_unique_index_key(
769 &mut index_keys,
770 &mut seen,
771 IndexKind::VenueOrderId,
772 venue_order_id.to_string(),
773 );
774
775 for fill in fills {
776 if let Some(client_order_id) = &fill.client_order_id {
777 push_unique_index_key(
778 &mut index_keys,
779 &mut seen,
780 IndexKind::ClientOrderId,
781 client_order_id.to_string(),
782 );
783 }
784 }
785 }
786 Ok(EncodedPayload::with_payload_type(
789 payload_type(PAYLOAD_TYPE_EXECUTION_MASS_STATUS),
790 payload,
791 index_keys,
792 ))
793}
794
795fn push_unique_index_key(
796 index_keys: &mut Vec<IndexKey>,
797 seen: &mut HashSet<(IndexKind, String)>,
798 kind: IndexKind,
799 key: String,
800) {
801 if seen.insert((kind, key.clone())) {
802 index_keys.push(IndexKey::new(kind, key));
803 }
804}
805
806pub fn encode_position_event(event: &PositionEvent) -> Result<EncodedPayload, EncodeError> {
819 match event {
820 PositionEvent::PositionOpened(e) => encode_position_opened(e),
821 PositionEvent::PositionChanged(e) => encode_position_changed(e),
822 PositionEvent::PositionClosed(e) => encode_position_closed(e),
823 PositionEvent::PositionAdjusted(e) => encode_position_adjusted(e),
824 }
825}
826
827fn encode_position_opened(event: &PositionOpened) -> Result<EncodedPayload, EncodeError> {
828 let payload = encode_serde(event)?;
829 let index_keys = vec![IndexKey::new(
830 IndexKind::ClientOrderId,
831 event.opening_order_id.to_string(),
832 )];
833 Ok(EncodedPayload::with_payload_type(
834 payload_type(PAYLOAD_TYPE_POSITION_OPENED),
835 payload,
836 index_keys,
837 ))
838}
839
840fn encode_position_changed(event: &PositionChanged) -> Result<EncodedPayload, EncodeError> {
841 let payload = encode_serde(event)?;
842 let index_keys = vec![IndexKey::new(
843 IndexKind::ClientOrderId,
844 event.opening_order_id.to_string(),
845 )];
846 Ok(EncodedPayload::with_payload_type(
847 payload_type(PAYLOAD_TYPE_POSITION_CHANGED),
848 payload,
849 index_keys,
850 ))
851}
852
853fn encode_position_closed(event: &PositionClosed) -> Result<EncodedPayload, EncodeError> {
854 let payload = encode_serde(event)?;
855 let mut index_keys = Vec::new();
856 let mut seen = HashSet::new();
857
858 push_unique_index_key(
859 &mut index_keys,
860 &mut seen,
861 IndexKind::ClientOrderId,
862 event.opening_order_id.to_string(),
863 );
864
865 if let Some(closing_order_id) = &event.closing_order_id {
868 push_unique_index_key(
869 &mut index_keys,
870 &mut seen,
871 IndexKind::ClientOrderId,
872 closing_order_id.to_string(),
873 );
874 }
875
876 Ok(EncodedPayload::with_payload_type(
877 payload_type(PAYLOAD_TYPE_POSITION_CLOSED),
878 payload,
879 index_keys,
880 ))
881}
882
883fn encode_position_adjusted(event: &PositionAdjusted) -> Result<EncodedPayload, EncodeError> {
884 let payload = encode_serde(event)?;
887 Ok(EncodedPayload::with_payload_type(
888 payload_type(PAYLOAD_TYPE_POSITION_ADJUSTED),
889 payload,
890 Vec::new(),
891 ))
892}
893
894fn encode_submit_order_list(cmd: &SubmitOrderList) -> Result<EncodedPayload, EncodeError> {
895 let payload = encode_serde(cmd)?;
896 let index_keys = cmd
897 .order_list
898 .client_order_ids
899 .iter()
900 .map(|cid| IndexKey::new(IndexKind::ClientOrderId, cid.to_string()))
901 .collect();
902 Ok(EncodedPayload::with_payload_type(
903 payload_type(PAYLOAD_TYPE_SUBMIT_ORDER_LIST),
904 payload,
905 index_keys,
906 ))
907}
908
909fn encode_modify_order(cmd: &ModifyOrder) -> Result<EncodedPayload, EncodeError> {
910 encode_with_order_ids(
911 cmd,
912 PAYLOAD_TYPE_MODIFY_ORDER,
913 cmd.client_order_id.to_string(),
914 cmd.venue_order_id.map(|v| v.to_string()),
915 )
916}
917
918fn encode_batch_modify_orders(cmd: &BatchModifyOrders) -> Result<EncodedPayload, EncodeError> {
919 let payload = encode_serde(cmd)?;
920 let mut index_keys = Vec::with_capacity(cmd.modifies.len() * 2);
921 for c in &cmd.modifies {
922 index_keys.push(IndexKey::new(
923 IndexKind::ClientOrderId,
924 c.client_order_id.to_string(),
925 ));
926
927 if let Some(venue) = c.venue_order_id {
928 index_keys.push(IndexKey::new(IndexKind::VenueOrderId, venue.to_string()));
929 }
930 }
931 Ok(EncodedPayload::with_payload_type(
932 payload_type(PAYLOAD_TYPE_BATCH_MODIFY_ORDERS),
933 payload,
934 index_keys,
935 ))
936}
937
938fn encode_cancel_order(cmd: &CancelOrder) -> Result<EncodedPayload, EncodeError> {
939 encode_with_order_ids(
940 cmd,
941 PAYLOAD_TYPE_CANCEL_ORDER,
942 cmd.client_order_id.to_string(),
943 cmd.venue_order_id.map(|v| v.to_string()),
944 )
945}
946
947fn encode_cancel_all_orders(cmd: &CancelAllOrders) -> Result<EncodedPayload, EncodeError> {
948 let payload = encode_serde(cmd)?;
949 Ok(EncodedPayload::with_payload_type(
950 payload_type(PAYLOAD_TYPE_CANCEL_ALL_ORDERS),
951 payload,
952 Vec::new(),
953 ))
954}
955
956fn encode_batch_cancel_orders(cmd: &BatchCancelOrders) -> Result<EncodedPayload, EncodeError> {
957 let payload = encode_serde(cmd)?;
958 let mut index_keys = Vec::with_capacity(cmd.cancels.len() * 2);
959 for c in &cmd.cancels {
960 index_keys.push(IndexKey::new(
961 IndexKind::ClientOrderId,
962 c.client_order_id.to_string(),
963 ));
964
965 if let Some(venue) = c.venue_order_id {
966 index_keys.push(IndexKey::new(IndexKind::VenueOrderId, venue.to_string()));
967 }
968 }
969 Ok(EncodedPayload::with_payload_type(
970 payload_type(PAYLOAD_TYPE_BATCH_CANCEL_ORDERS),
971 payload,
972 index_keys,
973 ))
974}
975
976fn encode_query_order(cmd: &QueryOrder) -> Result<EncodedPayload, EncodeError> {
977 encode_with_order_ids(
978 cmd,
979 PAYLOAD_TYPE_QUERY_ORDER,
980 cmd.client_order_id.to_string(),
981 cmd.venue_order_id.map(|v| v.to_string()),
982 )
983}
984
985fn encode_query_account(cmd: &QueryAccount) -> Result<EncodedPayload, EncodeError> {
986 let payload = encode_serde(cmd)?;
987 Ok(EncodedPayload::with_payload_type(
988 payload_type(PAYLOAD_TYPE_QUERY_ACCOUNT),
989 payload,
990 Vec::new(),
991 ))
992}
993
994fn encode_order_initialized(e: &OrderInitialized) -> Result<EncodedPayload, EncodeError> {
995 encode_with_order_ids(
996 e,
997 PAYLOAD_TYPE_ORDER_INITIALIZED,
998 e.client_order_id.to_string(),
999 None,
1000 )
1001}
1002
1003fn encode_order_denied(e: &OrderDenied) -> Result<EncodedPayload, EncodeError> {
1004 encode_with_order_ids(
1005 e,
1006 PAYLOAD_TYPE_ORDER_DENIED,
1007 e.client_order_id.to_string(),
1008 None,
1009 )
1010}
1011
1012fn encode_order_emulated(e: &OrderEmulated) -> Result<EncodedPayload, EncodeError> {
1013 encode_with_order_ids(
1014 e,
1015 PAYLOAD_TYPE_ORDER_EMULATED,
1016 e.client_order_id.to_string(),
1017 None,
1018 )
1019}
1020
1021fn encode_order_released(e: &OrderReleased) -> Result<EncodedPayload, EncodeError> {
1022 encode_with_order_ids(
1023 e,
1024 PAYLOAD_TYPE_ORDER_RELEASED,
1025 e.client_order_id.to_string(),
1026 None,
1027 )
1028}
1029
1030fn encode_order_submitted(e: &OrderSubmitted) -> Result<EncodedPayload, EncodeError> {
1031 encode_with_order_ids(
1032 e,
1033 PAYLOAD_TYPE_ORDER_SUBMITTED,
1034 e.client_order_id.to_string(),
1035 None,
1036 )
1037}
1038
1039fn encode_order_accepted(e: &OrderAccepted) -> Result<EncodedPayload, EncodeError> {
1040 encode_with_order_ids(
1041 e,
1042 PAYLOAD_TYPE_ORDER_ACCEPTED,
1043 e.client_order_id.to_string(),
1044 Some(e.venue_order_id.to_string()),
1045 )
1046}
1047
1048fn encode_order_rejected(e: &OrderRejected) -> Result<EncodedPayload, EncodeError> {
1049 encode_with_order_ids(
1050 e,
1051 PAYLOAD_TYPE_ORDER_REJECTED,
1052 e.client_order_id.to_string(),
1053 None,
1054 )
1055}
1056
1057fn encode_order_canceled(e: &OrderCanceled) -> Result<EncodedPayload, EncodeError> {
1058 encode_with_order_ids(
1059 e,
1060 PAYLOAD_TYPE_ORDER_CANCELED,
1061 e.client_order_id.to_string(),
1062 e.venue_order_id.map(|v| v.to_string()),
1063 )
1064}
1065
1066fn encode_order_expired(e: &OrderExpired) -> Result<EncodedPayload, EncodeError> {
1067 encode_with_order_ids(
1068 e,
1069 PAYLOAD_TYPE_ORDER_EXPIRED,
1070 e.client_order_id.to_string(),
1071 e.venue_order_id.map(|v| v.to_string()),
1072 )
1073}
1074
1075fn encode_order_triggered(e: &OrderTriggered) -> Result<EncodedPayload, EncodeError> {
1076 encode_with_order_ids(
1077 e,
1078 PAYLOAD_TYPE_ORDER_TRIGGERED,
1079 e.client_order_id.to_string(),
1080 e.venue_order_id.map(|v| v.to_string()),
1081 )
1082}
1083
1084fn encode_order_pending_update(e: &OrderPendingUpdate) -> Result<EncodedPayload, EncodeError> {
1085 encode_with_order_ids(
1086 e,
1087 PAYLOAD_TYPE_ORDER_PENDING_UPDATE,
1088 e.client_order_id.to_string(),
1089 e.venue_order_id.map(|v| v.to_string()),
1090 )
1091}
1092
1093fn encode_order_pending_cancel(e: &OrderPendingCancel) -> Result<EncodedPayload, EncodeError> {
1094 encode_with_order_ids(
1095 e,
1096 PAYLOAD_TYPE_ORDER_PENDING_CANCEL,
1097 e.client_order_id.to_string(),
1098 e.venue_order_id.map(|v| v.to_string()),
1099 )
1100}
1101
1102fn encode_order_modify_rejected(e: &OrderModifyRejected) -> Result<EncodedPayload, EncodeError> {
1103 encode_with_order_ids(
1104 e,
1105 PAYLOAD_TYPE_ORDER_MODIFY_REJECTED,
1106 e.client_order_id.to_string(),
1107 e.venue_order_id.map(|v| v.to_string()),
1108 )
1109}
1110
1111fn encode_order_cancel_rejected(e: &OrderCancelRejected) -> Result<EncodedPayload, EncodeError> {
1112 encode_with_order_ids(
1113 e,
1114 PAYLOAD_TYPE_ORDER_CANCEL_REJECTED,
1115 e.client_order_id.to_string(),
1116 e.venue_order_id.map(|v| v.to_string()),
1117 )
1118}
1119
1120fn encode_order_updated(e: &OrderUpdated) -> Result<EncodedPayload, EncodeError> {
1121 encode_with_order_ids(
1122 e,
1123 PAYLOAD_TYPE_ORDER_UPDATED,
1124 e.client_order_id.to_string(),
1125 e.venue_order_id.map(|v| v.to_string()),
1126 )
1127}
1128
1129fn encode_with_order_ids<T: Serialize>(
1130 value: &T,
1131 tag: &str,
1132 client_order_id: String,
1133 venue_order_id: Option<String>,
1134) -> Result<EncodedPayload, EncodeError> {
1135 let payload = encode_serde(value)?;
1136 let mut index_keys = Vec::with_capacity(2);
1137 index_keys.push(IndexKey::new(IndexKind::ClientOrderId, client_order_id));
1138 if let Some(venue) = venue_order_id {
1139 index_keys.push(IndexKey::new(IndexKind::VenueOrderId, venue));
1140 }
1141 Ok(EncodedPayload::with_payload_type(
1142 payload_type(tag),
1143 payload,
1144 index_keys,
1145 ))
1146}
1147
1148fn retag(mut encoded: EncodedPayload, tag: &str) -> EncodedPayload {
1149 encoded.payload_type = Some(payload_type(tag));
1150 encoded
1151}
1152
1153pub fn encode_order_status_report(
1163 message: &OrderStatusReport,
1164) -> Result<EncodedPayload, EncodeError> {
1165 let payload = encode_serde(message)?;
1166 let mut index_keys = Vec::with_capacity(2);
1167 index_keys.push(IndexKey::new(
1168 IndexKind::VenueOrderId,
1169 message.venue_order_id.to_string(),
1170 ));
1171
1172 if let Some(client_order_id) = &message.client_order_id {
1173 index_keys.push(IndexKey::new(
1174 IndexKind::ClientOrderId,
1175 client_order_id.to_string(),
1176 ));
1177 }
1178 Ok(EncodedPayload::new(payload, index_keys))
1179}
1180
1181pub fn encode_account_state(message: &AccountState) -> Result<EncodedPayload, EncodeError> {
1192 let payload = encode_serde(message)?;
1193 Ok(EncodedPayload::new(payload, Vec::new()))
1194}
1195
1196#[derive(Serialize)]
1197struct TimeEventPayload<'a> {
1198 name: &'a str,
1199 event_id: UUID4,
1200 ts_event: UnixNanos,
1201 ts_init: UnixNanos,
1202}
1203
1204pub fn encode_time_event(event: &TimeEvent) -> Result<EncodedPayload, EncodeError> {
1214 let payload = TimeEventPayload {
1215 name: event.name.as_str(),
1216 event_id: event.event_id,
1217 ts_event: event.ts_event,
1218 ts_init: event.ts_init,
1219 };
1220 Ok(EncodedPayload::new(encode_serde(&payload)?, Vec::new()))
1221}
1222
1223pub fn encode_data_command(command: &DataCommand) -> Result<EncodedPayload, EncodeError> {
1240 match command {
1241 DataCommand::Request(cmd) => {
1242 encode_data_command_category(cmd, PAYLOAD_TYPE_REQUEST_COMMAND)
1243 }
1244 DataCommand::Subscribe(cmd) => {
1245 encode_data_command_category(cmd, PAYLOAD_TYPE_SUBSCRIBE_COMMAND)
1246 }
1247 DataCommand::Unsubscribe(cmd) => {
1248 encode_data_command_category(cmd, PAYLOAD_TYPE_UNSUBSCRIBE_COMMAND)
1249 }
1250 #[cfg(feature = "defi")]
1251 DataCommand::DefiRequest(cmd) => {
1252 encode_data_command_category(cmd, PAYLOAD_TYPE_DEFI_REQUEST_COMMAND)
1253 }
1254 #[cfg(feature = "defi")]
1255 DataCommand::DefiSubscribe(cmd) => {
1256 encode_data_command_category(cmd, PAYLOAD_TYPE_DEFI_SUBSCRIBE_COMMAND)
1257 }
1258 #[cfg(feature = "defi")]
1259 DataCommand::DefiUnsubscribe(cmd) => {
1260 encode_data_command_category(cmd, PAYLOAD_TYPE_DEFI_UNSUBSCRIBE_COMMAND)
1261 }
1262 _ => Err(EncodeError::Serialize(
1263 "unsupported DataCommand variant".to_string(),
1264 )),
1265 }
1266}
1267
1268fn encode_data_command_category<T: Serialize>(
1269 command: &T,
1270 tag: &str,
1271) -> Result<EncodedPayload, EncodeError> {
1272 let payload = encode_serde(command)?;
1273 Ok(EncodedPayload::with_payload_type(
1274 payload_type(tag),
1275 payload,
1276 Vec::new(),
1277 ))
1278}
1279
1280pub fn encode_data_response(response: &DataResponse) -> Result<EncodedPayload, EncodeError> {
1308 match response {
1309 DataResponse::Data(resp) => encode_custom_data_response(resp),
1310 DataResponse::Instrument(resp) => encode_instrument_response(resp),
1311 DataResponse::Instruments(resp) => encode_instruments_response(resp),
1312 DataResponse::Book(resp) => encode_book_response(resp),
1313 DataResponse::BookDeltas(resp) => encode_book_deltas_response(resp),
1314 DataResponse::BookDepth(resp) => encode_book_depth_response(resp),
1315 DataResponse::Quotes(resp) => encode_quotes_response(resp),
1316 DataResponse::Trades(resp) => encode_trades_response(resp),
1317 DataResponse::FundingRates(resp) => encode_funding_rates_response(resp),
1318 DataResponse::ForwardPrices(resp) => encode_forward_prices_response(resp),
1319 DataResponse::Bars(resp) => encode_bars_response(resp),
1320 }
1321}
1322
1323fn encode_custom_data_response(
1324 response: &CustomDataResponse,
1325) -> Result<EncodedPayload, EncodeError> {
1326 #[derive(Serialize)]
1331 struct CustomDataResponseRef<'a> {
1332 correlation_id: &'a UUID4,
1333 client_id: &'a ClientId,
1334 venue: &'a Option<Venue>,
1335 data_type: &'a DataType,
1336 start: &'a Option<UnixNanos>,
1337 end: &'a Option<UnixNanos>,
1338 ts_init: &'a UnixNanos,
1339 params: &'a Option<Params>,
1340 }
1341
1342 let payload = encode_serde(&CustomDataResponseRef {
1343 correlation_id: &response.correlation_id,
1344 client_id: &response.client_id,
1345 venue: &response.venue,
1346 data_type: &response.data_type,
1347 start: &response.start,
1348 end: &response.end,
1349 ts_init: &response.ts_init,
1350 params: &response.params,
1351 })?;
1352 Ok(EncodedPayload::with_payload_type(
1353 payload_type(PAYLOAD_TYPE_CUSTOM_DATA_RESPONSE),
1354 payload,
1355 Vec::new(),
1356 ))
1357}
1358
1359fn encode_instrument_response(
1360 response: &InstrumentResponse,
1361) -> Result<EncodedPayload, EncodeError> {
1362 let payload = encode_serde(response)?;
1363 Ok(EncodedPayload::with_payload_type(
1364 payload_type(PAYLOAD_TYPE_INSTRUMENT_RESPONSE),
1365 payload,
1366 Vec::new(),
1367 ))
1368}
1369
1370fn encode_instruments_response(
1371 response: &InstrumentsResponse,
1372) -> Result<EncodedPayload, EncodeError> {
1373 let payload = encode_serde(response)?;
1374 Ok(EncodedPayload::with_payload_type(
1375 payload_type(PAYLOAD_TYPE_INSTRUMENTS_RESPONSE),
1376 payload,
1377 Vec::new(),
1378 ))
1379}
1380
1381fn encode_book_response(response: &BookResponse) -> Result<EncodedPayload, EncodeError> {
1382 #[derive(Serialize)]
1387 struct BookResponseRef<'a> {
1388 correlation_id: &'a UUID4,
1389 client_id: &'a ClientId,
1390 instrument_id: &'a InstrumentId,
1391 start: &'a Option<UnixNanos>,
1392 end: &'a Option<UnixNanos>,
1393 ts_init: &'a UnixNanos,
1394 params: &'a Option<Params>,
1395 }
1396
1397 let payload = encode_serde(&BookResponseRef {
1398 correlation_id: &response.correlation_id,
1399 client_id: &response.client_id,
1400 instrument_id: &response.instrument_id,
1401 start: &response.start,
1402 end: &response.end,
1403 ts_init: &response.ts_init,
1404 params: &response.params,
1405 })?;
1406 Ok(EncodedPayload::with_payload_type(
1407 payload_type(PAYLOAD_TYPE_BOOK_RESPONSE),
1408 payload,
1409 Vec::new(),
1410 ))
1411}
1412
1413fn encode_quotes_response(response: &QuotesResponse) -> Result<EncodedPayload, EncodeError> {
1414 let payload = encode_serde(response)?;
1415 Ok(EncodedPayload::with_payload_type(
1416 payload_type(PAYLOAD_TYPE_QUOTES_RESPONSE),
1417 payload,
1418 Vec::new(),
1419 ))
1420}
1421
1422fn encode_book_deltas_response(
1423 response: &BookDeltasResponse,
1424) -> Result<EncodedPayload, EncodeError> {
1425 let payload = encode_serde(response)?;
1426 Ok(EncodedPayload::with_payload_type(
1427 payload_type(PAYLOAD_TYPE_BOOK_DELTAS_RESPONSE),
1428 payload,
1429 Vec::new(),
1430 ))
1431}
1432
1433fn encode_book_depth_response(response: &BookDepthResponse) -> Result<EncodedPayload, EncodeError> {
1434 let payload = encode_serde(response)?;
1435 Ok(EncodedPayload::with_payload_type(
1436 payload_type(PAYLOAD_TYPE_BOOK_DEPTH_RESPONSE),
1437 payload,
1438 Vec::new(),
1439 ))
1440}
1441
1442fn encode_trades_response(response: &TradesResponse) -> Result<EncodedPayload, EncodeError> {
1443 let payload = encode_serde(response)?;
1444 Ok(EncodedPayload::with_payload_type(
1445 payload_type(PAYLOAD_TYPE_TRADES_RESPONSE),
1446 payload,
1447 Vec::new(),
1448 ))
1449}
1450
1451fn encode_funding_rates_response(
1452 response: &FundingRatesResponse,
1453) -> Result<EncodedPayload, EncodeError> {
1454 let payload = encode_serde(response)?;
1455 Ok(EncodedPayload::with_payload_type(
1456 payload_type(PAYLOAD_TYPE_FUNDING_RATES_RESPONSE),
1457 payload,
1458 Vec::new(),
1459 ))
1460}
1461
1462fn encode_forward_prices_response(
1463 response: &ForwardPricesResponse,
1464) -> Result<EncodedPayload, EncodeError> {
1465 let payload = encode_serde(response)?;
1466 Ok(EncodedPayload::with_payload_type(
1467 payload_type(PAYLOAD_TYPE_FORWARD_PRICES_RESPONSE),
1468 payload,
1469 Vec::new(),
1470 ))
1471}
1472
1473fn encode_bars_response(response: &BarsResponse) -> Result<EncodedPayload, EncodeError> {
1474 let payload = encode_serde(response)?;
1475 Ok(EncodedPayload::with_payload_type(
1476 payload_type(PAYLOAD_TYPE_BARS_RESPONSE),
1477 payload,
1478 Vec::new(),
1479 ))
1480}
1481
1482#[cfg(test)]
1483mod tests {
1484 use nautilus_common::messages::data::{
1485 RequestCommand, RequestQuotes, SubscribeCommand, SubscribeQuotes, UnsubscribeCommand,
1486 UnsubscribeQuotes,
1487 };
1488 #[cfg(feature = "defi")]
1489 use nautilus_common::messages::defi::{
1490 DefiRequestCommand, DefiSubscribeCommand, DefiUnsubscribeCommand, RequestPoolSnapshot,
1491 SubscribeBlocks, UnsubscribeBlocks,
1492 };
1493 use nautilus_core::{UUID4, UnixNanos};
1494 #[cfg(feature = "defi")]
1495 use nautilus_model::defi::Blockchain;
1496 use nautilus_model::{
1497 data::{Bar, BarType, stubs::stub_depth10},
1498 enums::{
1499 AccountType, BookType, LiquiditySide, OrderSide, OrderStatus, OrderType,
1500 PositionAdjustmentType, PositionSide, PositionSideSpecified, TimeInForce,
1501 },
1502 events::{
1503 PositionAdjusted, PositionChanged, PositionClosed, PositionOpened,
1504 order::spec::{OrderFilledSpec, OrderInitializedSpec, OrderSubmittedSpec},
1505 },
1506 identifiers::{
1507 AccountId, ClientId, ClientOrderId, InstrumentId, OrderListId, PositionId, StrategyId,
1508 TradeId, TraderId, Venue, VenueOrderId,
1509 },
1510 instruments::{InstrumentAny, stubs::currency_pair_ethusdt},
1511 orderbook::OrderBook,
1512 orders::OrderList,
1513 reports::{ExecutionMassStatus, FillReport, PositionStatusReport},
1514 types::{AccountBalance, Currency, Money, Price, Quantity},
1515 };
1516 use rstest::rstest;
1517 use serde::Deserialize;
1518
1519 use super::*;
1520
1521 fn trader_id() -> TraderId {
1522 TraderId::from("TRADER-001")
1523 }
1524
1525 fn strategy_id() -> StrategyId {
1526 StrategyId::from("S-001")
1527 }
1528
1529 fn instrument_id() -> InstrumentId {
1530 InstrumentId::from("ETHUSDT-PERP.BINANCE")
1531 }
1532
1533 fn client_order_id() -> ClientOrderId {
1534 ClientOrderId::from("O-20260510-000001")
1535 }
1536
1537 fn venue_order_id() -> VenueOrderId {
1538 VenueOrderId::from("V-12345")
1539 }
1540
1541 fn make_submit_order() -> SubmitOrder {
1542 let order_init = OrderInitializedSpec::builder()
1543 .instrument_id(instrument_id())
1544 .client_order_id(client_order_id())
1545 .quantity(Quantity::from("1"))
1546 .time_in_force(TimeInForce::Gtc)
1547 .ts_event(UnixNanos::from(1))
1548 .ts_init(UnixNanos::from(2))
1549 .build();
1550 SubmitOrder::new(
1551 trader_id(),
1552 Some(ClientId::from("BINANCE")),
1553 strategy_id(),
1554 instrument_id(),
1555 client_order_id(),
1556 order_init,
1557 None,
1558 None,
1559 None,
1560 UUID4::new(),
1561 UnixNanos::from(3),
1562 None, )
1564 }
1565
1566 fn make_order_filled() -> OrderFilled {
1567 OrderFilledSpec::builder()
1568 .instrument_id(instrument_id())
1569 .client_order_id(client_order_id())
1570 .venue_order_id(venue_order_id())
1571 .account_id(AccountId::from("BINANCE-001"))
1572 .trade_id(TradeId::from("T-9999"))
1573 .last_qty(Quantity::from("1"))
1574 .last_px(Price::from("100.00"))
1575 .currency(Currency::USDT())
1576 .ts_event(UnixNanos::from(10))
1577 .ts_init(UnixNanos::from(11))
1578 .commission(Money::new(0.10, Currency::USDT()))
1579 .build()
1580 }
1581
1582 fn make_order_status_report() -> OrderStatusReport {
1583 OrderStatusReport::new(
1584 AccountId::from("BINANCE-001"),
1585 instrument_id(),
1586 Some(client_order_id()),
1587 venue_order_id(),
1588 OrderSide::Buy,
1589 OrderType::Market,
1590 TimeInForce::Gtc,
1591 OrderStatus::Filled,
1592 Quantity::from("1"),
1593 Quantity::from("1"),
1594 UnixNanos::from(20),
1595 UnixNanos::from(21),
1596 UnixNanos::from(22),
1597 Some(UUID4::new()),
1598 )
1599 }
1600
1601 #[rstest]
1602 fn submit_order_encoder_emits_client_order_id_index() {
1603 let cmd = make_submit_order();
1604 let encoded = encode_submit_order(&cmd).expect("encode");
1605
1606 assert!(!encoded.payload.is_empty());
1607 assert_eq!(encoded.index_keys.len(), 1);
1608 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
1609 assert_eq!(encoded.index_keys[0].key, cmd.client_order_id.to_string());
1610 }
1611
1612 #[rstest]
1613 fn order_filled_encoder_emits_client_and_venue_order_id_indices() {
1614 let event = make_order_filled();
1615 let encoded = encode_order_filled(&event).expect("encode");
1616
1617 assert!(!encoded.payload.is_empty());
1618 assert_eq!(encoded.index_keys.len(), 2);
1619 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
1620 assert_eq!(encoded.index_keys[0].key, event.client_order_id.to_string());
1621 assert_eq!(encoded.index_keys[1].kind, IndexKind::VenueOrderId);
1622 assert_eq!(encoded.index_keys[1].key, event.venue_order_id.to_string());
1623 }
1624
1625 #[rstest]
1626 fn order_status_report_encoder_includes_client_order_id_when_present() {
1627 let report = make_order_status_report();
1628 let encoded = encode_order_status_report(&report).expect("encode");
1629
1630 assert_eq!(encoded.index_keys.len(), 2);
1631 assert_eq!(encoded.index_keys[0].kind, IndexKind::VenueOrderId);
1632 assert_eq!(encoded.index_keys[1].kind, IndexKind::ClientOrderId);
1633 }
1634
1635 #[rstest]
1636 fn order_status_report_encoder_omits_client_order_id_when_absent() {
1637 let mut report = make_order_status_report();
1638 report.client_order_id = None;
1639 let encoded = encode_order_status_report(&report).expect("encode");
1640
1641 assert_eq!(encoded.index_keys.len(), 1);
1642 assert_eq!(encoded.index_keys[0].kind, IndexKind::VenueOrderId);
1643 }
1644
1645 #[rstest]
1646 fn default_registry_covers_published_state_affecting_surface() {
1647 let registry = default_registry();
1648 let expected = [
1649 (
1650 "send_any_value(SubmitOrder) / bare SubmitOrder",
1651 registry.contains::<SubmitOrder>(),
1652 ),
1653 (
1654 "publish_order_event(OrderFilled) / bare OrderFilled",
1655 registry.contains::<OrderFilled>(),
1656 ),
1657 (
1658 "reconciliation.raw.order_status / OrderStatusReport",
1659 registry.contains::<OrderStatusReport>(),
1660 ),
1661 (
1662 "reconciliation.raw.fill / FillReport",
1663 registry.contains::<FillReport>(),
1664 ),
1665 (
1666 "reconciliation.raw.position / PositionStatusReport",
1667 registry.contains::<PositionStatusReport>(),
1668 ),
1669 (
1670 "send_trading_command / TradingCommand",
1671 registry.contains::<TradingCommand>(),
1672 ),
1673 (
1674 "publish_order_event / OrderEventAny",
1675 registry.contains::<OrderEventAny>(),
1676 ),
1677 (
1678 "send_execution_report / ExecutionReport",
1679 registry.contains::<ExecutionReport>(),
1680 ),
1681 (
1682 "publish_position_event / PositionEvent",
1683 registry.contains::<PositionEvent>(),
1684 ),
1685 (
1686 "publish_account_state and send_account_state / AccountState",
1687 registry.contains::<AccountState>(),
1688 ),
1689 (
1690 "time event handler firing / TimeEvent",
1691 registry.contains::<TimeEvent>(),
1692 ),
1693 (
1694 "send_data_command / DataCommand",
1695 registry.contains::<DataCommand>(),
1696 ),
1697 (
1698 "send_data_response / DataResponse",
1699 registry.contains::<DataResponse>(),
1700 ),
1701 ];
1702 let missing: Vec<&str> = expected
1703 .iter()
1704 .filter_map(|(name, registered)| (!*registered).then_some(*name))
1705 .collect();
1706
1707 assert!(
1708 missing.is_empty(),
1709 "missing default event-store encoder registrations for {missing:?}",
1710 );
1711 assert_eq!(
1712 registry.len(),
1713 expected.len(),
1714 "default registry must match the audited state-affecting surface",
1715 );
1716 }
1717
1718 #[rstest]
1719 fn submit_order_payload_round_trips_through_msgpack() {
1720 let cmd = make_submit_order();
1721 let encoded = encode_submit_order(&cmd).expect("encode");
1722
1723 let decoded: SubmitOrder = rmp_serde::from_slice(&encoded.payload).expect("decode");
1724 assert_eq!(decoded, cmd);
1725 }
1726
1727 #[rstest]
1728 fn order_filled_payload_round_trips_through_msgpack() {
1729 let event = make_order_filled();
1730 let encoded = encode_order_filled(&event).expect("encode");
1731
1732 let decoded: OrderFilled = rmp_serde::from_slice(&encoded.payload).expect("decode");
1733 assert_eq!(decoded, event);
1734 }
1735
1736 #[rstest]
1737 fn order_status_report_payload_round_trips_through_msgpack() {
1738 let report = make_order_status_report();
1739 let encoded = encode_order_status_report(&report).expect("encode");
1740
1741 let decoded: OrderStatusReport = rmp_serde::from_slice(&encoded.payload).expect("decode");
1742 assert_eq!(decoded, report);
1743 }
1744
1745 fn make_cancel_order() -> CancelOrder {
1746 CancelOrder::new(
1747 trader_id(),
1748 Some(ClientId::from("BINANCE")),
1749 strategy_id(),
1750 instrument_id(),
1751 client_order_id(),
1752 Some(venue_order_id()),
1753 UUID4::new(),
1754 UnixNanos::from(4),
1755 None,
1756 None, )
1758 }
1759
1760 fn make_query_account() -> QueryAccount {
1761 QueryAccount::new(
1762 trader_id(),
1763 Some(ClientId::from("BINANCE")),
1764 AccountId::from("BINANCE-001"),
1765 UUID4::new(),
1766 UnixNanos::from(5),
1767 None,
1768 None, )
1770 }
1771
1772 fn make_order_submitted() -> OrderSubmitted {
1773 OrderSubmittedSpec::builder()
1774 .instrument_id(instrument_id())
1775 .client_order_id(client_order_id())
1776 .account_id(AccountId::from("BINANCE-001"))
1777 .ts_event(UnixNanos::from(30))
1778 .ts_init(UnixNanos::from(31))
1779 .build()
1780 }
1781
1782 fn make_modify_order(venue: Option<VenueOrderId>) -> ModifyOrder {
1783 ModifyOrder::new(
1784 trader_id(),
1785 Some(ClientId::from("BINANCE")),
1786 strategy_id(),
1787 instrument_id(),
1788 client_order_id(),
1789 venue,
1790 Some(Quantity::from("2")),
1791 Some(Price::from("100.00")),
1792 None,
1793 UUID4::new(),
1794 UnixNanos::from(6),
1795 None,
1796 None, )
1798 }
1799
1800 fn make_batch_modify_orders(modifies: Vec<ModifyOrder>) -> BatchModifyOrders {
1801 BatchModifyOrders::new(
1802 trader_id(),
1803 Some(ClientId::from("BINANCE")),
1804 strategy_id(),
1805 instrument_id(),
1806 modifies,
1807 UUID4::new(),
1808 UnixNanos::from(7),
1809 None,
1810 None, )
1812 }
1813
1814 fn make_cancel_all_orders() -> CancelAllOrders {
1815 CancelAllOrders::new(
1816 trader_id(),
1817 Some(ClientId::from("BINANCE")),
1818 strategy_id(),
1819 instrument_id(),
1820 OrderSide::Buy,
1821 UUID4::new(),
1822 UnixNanos::from(7),
1823 None,
1824 None, )
1826 }
1827
1828 fn make_query_order(venue: Option<VenueOrderId>) -> QueryOrder {
1829 QueryOrder::new(
1830 trader_id(),
1831 Some(ClientId::from("BINANCE")),
1832 strategy_id(),
1833 instrument_id(),
1834 client_order_id(),
1835 venue,
1836 UUID4::new(),
1837 UnixNanos::from(8),
1838 None,
1839 None, )
1841 }
1842
1843 fn make_batch_cancel_orders(cancels: Vec<CancelOrder>) -> BatchCancelOrders {
1844 BatchCancelOrders::new(
1845 trader_id(),
1846 Some(ClientId::from("BINANCE")),
1847 strategy_id(),
1848 instrument_id(),
1849 cancels,
1850 UUID4::new(),
1851 UnixNanos::from(9),
1852 None,
1853 None, )
1855 }
1856
1857 fn make_submit_order_list(client_order_ids: Vec<ClientOrderId>) -> SubmitOrderList {
1858 let order_inits: Vec<OrderInitialized> = client_order_ids
1861 .iter()
1862 .copied()
1863 .map(make_order_initialized_with_id)
1864 .collect();
1865 let order_list = OrderList::new(
1866 OrderListId::from("OL-1"),
1867 instrument_id(),
1868 strategy_id(),
1869 client_order_ids,
1870 UnixNanos::from(10),
1871 );
1872 SubmitOrderList::new(
1873 trader_id(),
1874 Some(ClientId::from("BINANCE")),
1875 strategy_id(),
1876 order_list,
1877 order_inits,
1878 None,
1879 None,
1880 None,
1881 UUID4::new(),
1882 UnixNanos::from(11),
1883 None, )
1885 }
1886
1887 fn make_order_initialized_with_id(client_order_id: ClientOrderId) -> OrderInitialized {
1888 OrderInitialized {
1889 client_order_id,
1890 ..OrderInitialized::default()
1891 }
1892 }
1893
1894 fn ev_initialized() -> OrderEventAny {
1895 OrderEventAny::Initialized(make_order_initialized_with_id(client_order_id()))
1896 }
1897
1898 fn ev_denied() -> OrderEventAny {
1899 OrderEventAny::Denied(OrderDenied {
1900 client_order_id: client_order_id(),
1901 ..Default::default()
1902 })
1903 }
1904
1905 fn ev_emulated() -> OrderEventAny {
1906 OrderEventAny::Emulated(OrderEmulated {
1907 client_order_id: client_order_id(),
1908 ..Default::default()
1909 })
1910 }
1911
1912 fn ev_released() -> OrderEventAny {
1913 OrderEventAny::Released(OrderReleased {
1914 client_order_id: client_order_id(),
1915 ..Default::default()
1916 })
1917 }
1918
1919 fn ev_submitted() -> OrderEventAny {
1920 OrderEventAny::Submitted(make_order_submitted())
1921 }
1922
1923 fn ev_accepted_with_venue(venue: VenueOrderId) -> OrderEventAny {
1924 OrderEventAny::Accepted(OrderAccepted {
1925 client_order_id: client_order_id(),
1926 venue_order_id: venue,
1927 ..Default::default()
1928 })
1929 }
1930
1931 fn ev_rejected() -> OrderEventAny {
1932 OrderEventAny::Rejected(OrderRejected {
1933 client_order_id: client_order_id(),
1934 ..Default::default()
1935 })
1936 }
1937
1938 fn ev_canceled(venue: Option<VenueOrderId>) -> OrderEventAny {
1939 OrderEventAny::Canceled(OrderCanceled {
1940 client_order_id: client_order_id(),
1941 venue_order_id: venue,
1942 ..Default::default()
1943 })
1944 }
1945
1946 fn ev_expired(venue: Option<VenueOrderId>) -> OrderEventAny {
1947 OrderEventAny::Expired(OrderExpired {
1948 client_order_id: client_order_id(),
1949 venue_order_id: venue,
1950 ..Default::default()
1951 })
1952 }
1953
1954 fn ev_triggered(venue: Option<VenueOrderId>) -> OrderEventAny {
1955 OrderEventAny::Triggered(OrderTriggered {
1956 client_order_id: client_order_id(),
1957 venue_order_id: venue,
1958 ..Default::default()
1959 })
1960 }
1961
1962 fn ev_pending_update(venue: Option<VenueOrderId>) -> OrderEventAny {
1963 OrderEventAny::PendingUpdate(OrderPendingUpdate {
1964 client_order_id: client_order_id(),
1965 venue_order_id: venue,
1966 ..Default::default()
1967 })
1968 }
1969
1970 fn ev_pending_cancel(venue: Option<VenueOrderId>) -> OrderEventAny {
1971 OrderEventAny::PendingCancel(OrderPendingCancel {
1972 client_order_id: client_order_id(),
1973 venue_order_id: venue,
1974 ..Default::default()
1975 })
1976 }
1977
1978 fn ev_modify_rejected(venue: Option<VenueOrderId>) -> OrderEventAny {
1979 OrderEventAny::ModifyRejected(OrderModifyRejected {
1980 client_order_id: client_order_id(),
1981 venue_order_id: venue,
1982 ..Default::default()
1983 })
1984 }
1985
1986 fn ev_cancel_rejected(venue: Option<VenueOrderId>) -> OrderEventAny {
1987 OrderEventAny::CancelRejected(OrderCancelRejected {
1988 client_order_id: client_order_id(),
1989 venue_order_id: venue,
1990 ..Default::default()
1991 })
1992 }
1993
1994 fn ev_updated(venue: Option<VenueOrderId>) -> OrderEventAny {
1995 OrderEventAny::Updated(OrderUpdated {
1996 client_order_id: client_order_id(),
1997 venue_order_id: venue,
1998 ..Default::default()
1999 })
2000 }
2001
2002 fn ev_filled() -> OrderEventAny {
2003 OrderEventAny::Filled(make_order_filled())
2004 }
2005
2006 #[rstest]
2007 fn trading_command_envelope_stamps_inner_submit_order_payload_type() {
2008 let cmd = make_submit_order();
2013 let bare = encode_submit_order(&cmd).expect("bare");
2014
2015 let envelope = TradingCommand::SubmitOrder(cmd);
2016 let wrapped = encode_trading_command(&envelope).expect("envelope");
2017
2018 assert_eq!(wrapped.payload, bare.payload);
2019 assert_eq!(wrapped.index_keys, bare.index_keys);
2020 assert_eq!(
2021 wrapped.payload_type.expect("override").as_str(),
2022 PAYLOAD_TYPE_SUBMIT_ORDER,
2023 );
2024 }
2025
2026 #[rstest]
2027 fn trading_command_cancel_order_envelope_emits_client_and_venue_indices() {
2028 let cancel = make_cancel_order();
2029 let envelope = TradingCommand::CancelOrder(cancel.clone());
2030 let wrapped = encode_trading_command(&envelope).expect("envelope");
2031
2032 assert_eq!(
2033 wrapped.payload_type.expect("override").as_str(),
2034 PAYLOAD_TYPE_CANCEL_ORDER,
2035 );
2036 assert_eq!(wrapped.index_keys.len(), 2);
2037 assert_eq!(wrapped.index_keys[0].kind, IndexKind::ClientOrderId);
2038 assert_eq!(
2039 wrapped.index_keys[0].key,
2040 cancel.client_order_id.to_string(),
2041 );
2042 assert_eq!(wrapped.index_keys[1].kind, IndexKind::VenueOrderId);
2043 assert_eq!(
2044 wrapped.index_keys[1].key,
2045 cancel.venue_order_id.expect("set").to_string(),
2046 );
2047
2048 let decoded: CancelOrder = rmp_serde::from_slice(&wrapped.payload).expect("decode");
2049 assert_eq!(decoded, cancel);
2050 }
2051
2052 #[rstest]
2053 fn trading_command_query_account_envelope_records_no_order_indices() {
2054 let envelope = TradingCommand::QueryAccount(make_query_account());
2057 let wrapped = encode_trading_command(&envelope).expect("envelope");
2058
2059 assert_eq!(
2060 wrapped.payload_type.expect("override").as_str(),
2061 PAYLOAD_TYPE_QUERY_ACCOUNT,
2062 );
2063 assert!(wrapped.index_keys.is_empty());
2064 }
2065
2066 #[rstest]
2067 fn order_event_any_envelope_stamps_inner_filled_payload_type() {
2068 let filled = make_order_filled();
2069 let bare = encode_order_filled(&filled).expect("bare");
2070
2071 let envelope = OrderEventAny::Filled(filled);
2072 let wrapped = encode_order_event_any(&envelope).expect("envelope");
2073
2074 assert_eq!(wrapped.payload, bare.payload);
2075 assert_eq!(wrapped.index_keys, bare.index_keys);
2076 assert_eq!(
2077 wrapped.payload_type.expect("override").as_str(),
2078 PAYLOAD_TYPE_ORDER_FILLED,
2079 );
2080 }
2081
2082 #[rstest]
2083 fn order_event_any_submitted_envelope_emits_client_order_id_index() {
2084 let submitted = make_order_submitted();
2085 let envelope = OrderEventAny::Submitted(submitted);
2086 let wrapped = encode_order_event_any(&envelope).expect("envelope");
2087
2088 assert_eq!(
2089 wrapped.payload_type.expect("override").as_str(),
2090 PAYLOAD_TYPE_ORDER_SUBMITTED,
2091 );
2092 assert_eq!(wrapped.index_keys.len(), 1);
2093 assert_eq!(wrapped.index_keys[0].kind, IndexKind::ClientOrderId);
2094 assert_eq!(
2095 wrapped.index_keys[0].key,
2096 submitted.client_order_id.to_string(),
2097 );
2098
2099 let decoded: OrderSubmitted = rmp_serde::from_slice(&wrapped.payload).expect("decode");
2100 assert_eq!(decoded, submitted);
2101 }
2102
2103 #[rstest]
2108 #[case::submit_order(
2109 TradingCommand::SubmitOrder(make_submit_order()),
2110 PAYLOAD_TYPE_SUBMIT_ORDER,
2111 1
2112 )]
2113 #[case::submit_order_list(
2114 TradingCommand::SubmitOrderList(make_submit_order_list(vec![
2115 ClientOrderId::from("O-A"),
2116 ClientOrderId::from("O-B"),
2117 ])),
2118 PAYLOAD_TYPE_SUBMIT_ORDER_LIST,
2119 2,
2120 )]
2121 #[case::modify_order(
2122 TradingCommand::ModifyOrder(make_modify_order(Some(venue_order_id()))),
2123 PAYLOAD_TYPE_MODIFY_ORDER,
2124 2
2125 )]
2126 #[case::batch_modify_orders(
2127 TradingCommand::ModifyOrders(make_batch_modify_orders(vec![
2128 make_modify_order(Some(venue_order_id())),
2129 ])),
2130 PAYLOAD_TYPE_BATCH_MODIFY_ORDERS,
2131 2,
2132 )]
2133 #[case::cancel_order(
2134 TradingCommand::CancelOrder(make_cancel_order()),
2135 PAYLOAD_TYPE_CANCEL_ORDER,
2136 2
2137 )]
2138 #[case::batch_cancel_orders(
2139 TradingCommand::CancelOrders(make_batch_cancel_orders(vec![make_cancel_order()])),
2140 PAYLOAD_TYPE_BATCH_CANCEL_ORDERS,
2141 2,
2142 )]
2143 #[case::cancel_all_orders(
2144 TradingCommand::CancelAllOrders(make_cancel_all_orders()),
2145 PAYLOAD_TYPE_CANCEL_ALL_ORDERS,
2146 0
2147 )]
2148 #[case::query_order(
2149 TradingCommand::QueryOrder(make_query_order(Some(venue_order_id()))),
2150 PAYLOAD_TYPE_QUERY_ORDER,
2151 2
2152 )]
2153 #[case::query_account(
2154 TradingCommand::QueryAccount(make_query_account()),
2155 PAYLOAD_TYPE_QUERY_ACCOUNT,
2156 0
2157 )]
2158 fn trading_command_envelope_stamps_inner_tag_for_every_variant(
2159 #[case] command: TradingCommand,
2160 #[case] expected_tag: &str,
2161 #[case] expected_index_count: usize,
2162 ) {
2163 let encoded = encode_trading_command(&command).expect("encode");
2164 let tag = encoded.payload_type.expect("override").as_str().to_string();
2165
2166 assert_eq!(tag, expected_tag);
2167 assert_ne!(
2168 tag, PAYLOAD_TYPE_TRADING_COMMAND,
2169 "wrapper fallback tag must never reach the writer",
2170 );
2171 assert_eq!(encoded.index_keys.len(), expected_index_count);
2172 }
2173
2174 #[rstest]
2178 #[case::initialized(ev_initialized(), PAYLOAD_TYPE_ORDER_INITIALIZED, false)]
2179 #[case::denied(ev_denied(), PAYLOAD_TYPE_ORDER_DENIED, false)]
2180 #[case::emulated(ev_emulated(), PAYLOAD_TYPE_ORDER_EMULATED, false)]
2181 #[case::released(ev_released(), PAYLOAD_TYPE_ORDER_RELEASED, false)]
2182 #[case::submitted(ev_submitted(), PAYLOAD_TYPE_ORDER_SUBMITTED, false)]
2183 #[case::accepted(
2184 ev_accepted_with_venue(venue_order_id()),
2185 PAYLOAD_TYPE_ORDER_ACCEPTED,
2186 true
2187 )]
2188 #[case::rejected(ev_rejected(), PAYLOAD_TYPE_ORDER_REJECTED, false)]
2189 #[case::canceled(ev_canceled(Some(venue_order_id())), PAYLOAD_TYPE_ORDER_CANCELED, true)]
2190 #[case::expired(ev_expired(Some(venue_order_id())), PAYLOAD_TYPE_ORDER_EXPIRED, true)]
2191 #[case::triggered(
2192 ev_triggered(Some(venue_order_id())),
2193 PAYLOAD_TYPE_ORDER_TRIGGERED,
2194 true
2195 )]
2196 #[case::pending_update(
2197 ev_pending_update(Some(venue_order_id())),
2198 PAYLOAD_TYPE_ORDER_PENDING_UPDATE,
2199 true
2200 )]
2201 #[case::pending_cancel(
2202 ev_pending_cancel(Some(venue_order_id())),
2203 PAYLOAD_TYPE_ORDER_PENDING_CANCEL,
2204 true
2205 )]
2206 #[case::modify_rejected(
2207 ev_modify_rejected(Some(venue_order_id())),
2208 PAYLOAD_TYPE_ORDER_MODIFY_REJECTED,
2209 true
2210 )]
2211 #[case::cancel_rejected(
2212 ev_cancel_rejected(Some(venue_order_id())),
2213 PAYLOAD_TYPE_ORDER_CANCEL_REJECTED,
2214 true
2215 )]
2216 #[case::updated(ev_updated(Some(venue_order_id())), PAYLOAD_TYPE_ORDER_UPDATED, true)]
2217 #[case::filled(ev_filled(), PAYLOAD_TYPE_ORDER_FILLED, true)]
2218 fn order_event_any_envelope_stamps_inner_tag_for_every_variant(
2219 #[case] event: OrderEventAny,
2220 #[case] expected_tag: &str,
2221 #[case] expects_venue_index: bool,
2222 ) {
2223 let encoded = encode_order_event_any(&event).expect("encode");
2224 let tag = encoded.payload_type.expect("override").as_str().to_string();
2225
2226 assert_eq!(tag, expected_tag);
2227 assert_ne!(
2228 tag, PAYLOAD_TYPE_ORDER_EVENT_ANY,
2229 "wrapper fallback tag must never reach the writer",
2230 );
2231 assert_eq!(
2232 encoded.index_keys[0].kind,
2233 IndexKind::ClientOrderId,
2234 "first index must always be ClientOrderId for every order event",
2235 );
2236 assert_eq!(encoded.index_keys[0].key, client_order_id().to_string(),);
2237 if expects_venue_index {
2238 assert_eq!(encoded.index_keys.len(), 2);
2239 assert_eq!(encoded.index_keys[1].kind, IndexKind::VenueOrderId);
2240 } else {
2241 assert_eq!(encoded.index_keys.len(), 1);
2242 }
2243 }
2244
2245 #[rstest]
2248 #[case::cancel_order_some(TradingCommand::CancelOrder(make_cancel_order()), 2)]
2249 #[case::cancel_order_none(
2250 TradingCommand::CancelOrder(CancelOrder {
2251 venue_order_id: None,
2252 ..make_cancel_order()
2253 }),
2254 1,
2255 )]
2256 #[case::modify_order_some(
2257 TradingCommand::ModifyOrder(make_modify_order(Some(venue_order_id()))),
2258 2
2259 )]
2260 #[case::modify_order_none(TradingCommand::ModifyOrder(make_modify_order(None)), 1)]
2261 #[case::batch_modify_orders(
2262 TradingCommand::ModifyOrders(make_batch_modify_orders(vec![
2263 make_modify_order(Some(venue_order_id())),
2264 make_modify_order(None),
2265 ])),
2266 3,
2267 )]
2268 #[case::query_order_some(
2269 TradingCommand::QueryOrder(make_query_order(Some(venue_order_id()))),
2270 2
2271 )]
2272 #[case::query_order_none(TradingCommand::QueryOrder(make_query_order(None)), 1)]
2273 fn trading_command_envelope_index_count_matches_venue_optionality(
2274 #[case] command: TradingCommand,
2275 #[case] expected_index_count: usize,
2276 ) {
2277 let encoded = encode_trading_command(&command).expect("encode");
2278 assert_eq!(encoded.index_keys.len(), expected_index_count);
2279 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
2280 if expected_index_count == 2 {
2281 assert_eq!(encoded.index_keys[1].kind, IndexKind::VenueOrderId);
2282 }
2283 }
2284
2285 #[rstest]
2286 #[case::canceled_some(ev_canceled(Some(venue_order_id())), 2)]
2287 #[case::canceled_none(ev_canceled(None), 1)]
2288 #[case::updated_some(ev_updated(Some(venue_order_id())), 2)]
2289 #[case::updated_none(ev_updated(None), 1)]
2290 #[case::pending_update_some(ev_pending_update(Some(venue_order_id())), 2)]
2291 #[case::pending_update_none(ev_pending_update(None), 1)]
2292 fn order_event_any_envelope_index_count_matches_venue_optionality(
2293 #[case] event: OrderEventAny,
2294 #[case] expected_index_count: usize,
2295 ) {
2296 let encoded = encode_order_event_any(&event).expect("encode");
2297 assert_eq!(encoded.index_keys.len(), expected_index_count);
2298 }
2299
2300 #[rstest]
2301 fn batch_cancel_orders_envelope_indexes_each_child_with_optional_venue() {
2302 let with_venue = make_cancel_order();
2306 let mut without_venue = make_cancel_order();
2307 without_venue.venue_order_id = None;
2308 without_venue.client_order_id = ClientOrderId::from("O-NOVENUE");
2309 let batch = make_batch_cancel_orders(vec![with_venue.clone(), without_venue.clone()]);
2310
2311 let encoded = encode_trading_command(&TradingCommand::CancelOrders(batch)).expect("encode");
2312
2313 assert_eq!(
2314 encoded.payload_type.expect("override").as_str(),
2315 PAYLOAD_TYPE_BATCH_CANCEL_ORDERS,
2316 );
2317 assert_eq!(encoded.index_keys.len(), 3);
2318 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
2319 assert_eq!(
2320 encoded.index_keys[0].key,
2321 with_venue.client_order_id.to_string(),
2322 );
2323 assert_eq!(encoded.index_keys[1].kind, IndexKind::VenueOrderId);
2324 assert_eq!(
2325 encoded.index_keys[1].key,
2326 with_venue.venue_order_id.expect("set").to_string(),
2327 );
2328 assert_eq!(encoded.index_keys[2].kind, IndexKind::ClientOrderId);
2329 assert_eq!(
2330 encoded.index_keys[2].key,
2331 without_venue.client_order_id.to_string(),
2332 );
2333 }
2334
2335 #[rstest]
2336 fn batch_modify_orders_envelope_indexes_each_child_with_optional_venue() {
2337 let with_venue = make_modify_order(Some(venue_order_id()));
2338 let mut without_venue = make_modify_order(None);
2339 without_venue.client_order_id = ClientOrderId::from("O-NOVENUE");
2340 let batch = make_batch_modify_orders(vec![with_venue.clone(), without_venue.clone()]);
2341
2342 let encoded = encode_trading_command(&TradingCommand::ModifyOrders(batch)).expect("encode");
2343
2344 assert_eq!(
2345 encoded.payload_type.expect("override").as_str(),
2346 PAYLOAD_TYPE_BATCH_MODIFY_ORDERS,
2347 );
2348 assert_eq!(encoded.index_keys.len(), 3);
2349 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
2350 assert_eq!(
2351 encoded.index_keys[0].key,
2352 with_venue.client_order_id.to_string(),
2353 );
2354 assert_eq!(encoded.index_keys[1].kind, IndexKind::VenueOrderId);
2355 assert_eq!(
2356 encoded.index_keys[1].key,
2357 with_venue.venue_order_id.expect("set").to_string(),
2358 );
2359 assert_eq!(encoded.index_keys[2].kind, IndexKind::ClientOrderId);
2360 assert_eq!(
2361 encoded.index_keys[2].key,
2362 without_venue.client_order_id.to_string(),
2363 );
2364 }
2365
2366 #[rstest]
2367 fn submit_order_list_envelope_indexes_each_client_order_id() {
2368 let ids = vec![
2372 ClientOrderId::from("O-LIST-1"),
2373 ClientOrderId::from("O-LIST-2"),
2374 ClientOrderId::from("O-LIST-3"),
2375 ];
2376 let cmd = make_submit_order_list(ids.clone());
2377 let encoded =
2378 encode_trading_command(&TradingCommand::SubmitOrderList(cmd)).expect("encode");
2379
2380 assert_eq!(
2381 encoded.payload_type.expect("override").as_str(),
2382 PAYLOAD_TYPE_SUBMIT_ORDER_LIST,
2383 );
2384 assert_eq!(encoded.index_keys.len(), ids.len());
2385 for (idx, expected_id) in ids.iter().enumerate() {
2386 assert_eq!(encoded.index_keys[idx].kind, IndexKind::ClientOrderId);
2387 assert_eq!(encoded.index_keys[idx].key, expected_id.to_string());
2388 }
2389 }
2390
2391 fn make_fill_report() -> FillReport {
2392 FillReport::new(
2393 AccountId::from("BINANCE-001"),
2394 instrument_id(),
2395 venue_order_id(),
2396 TradeId::from("T-1111"),
2397 OrderSide::Buy,
2398 Quantity::from("1"),
2399 Price::from("100.00"),
2400 Money::new(0.10, Currency::USDT()),
2401 LiquiditySide::Taker,
2402 Some(client_order_id()),
2403 None,
2404 UnixNanos::from(40),
2405 UnixNanos::from(41),
2406 None,
2407 )
2408 }
2409
2410 fn make_position_status_report() -> PositionStatusReport {
2411 PositionStatusReport::new(
2412 AccountId::from("BINANCE-001"),
2413 instrument_id(),
2414 PositionSideSpecified::Long,
2415 Quantity::from("1"),
2416 UnixNanos::from(50),
2417 UnixNanos::from(51),
2418 None,
2419 Some(PositionId::from("P-001")),
2420 None,
2421 )
2422 }
2423
2424 fn make_execution_mass_status_with_reports() -> ExecutionMassStatus {
2425 let mut status = ExecutionMassStatus::new(
2426 ClientId::from("BINANCE"),
2427 AccountId::from("BINANCE-001"),
2428 Venue::from("BINANCE"),
2429 UnixNanos::from(60),
2430 None,
2431 );
2432 status.add_order_reports(vec![make_order_status_report()]);
2433 status.add_fill_reports(vec![make_fill_report()]);
2434 status.add_position_reports(vec![make_position_status_report()]);
2435 status
2436 }
2437
2438 #[rstest]
2439 fn execution_report_order_envelope_reuses_bare_status_encoder() {
2440 let report = make_order_status_report();
2445 let bare = encode_order_status_report(&report).expect("bare");
2446
2447 let envelope = ExecutionReport::Order(Box::new(report));
2448 let wrapped = encode_execution_report(&envelope).expect("envelope");
2449
2450 assert_eq!(wrapped.payload, bare.payload);
2451 assert_eq!(wrapped.index_keys, bare.index_keys);
2452 assert_eq!(
2453 wrapped.payload_type.expect("override").as_str(),
2454 PAYLOAD_TYPE_ORDER_STATUS_REPORT,
2455 );
2456 }
2457
2458 #[rstest]
2459 fn execution_report_fill_envelope_emits_venue_and_client_order_id_indices() {
2460 let fill = make_fill_report();
2461 let envelope = ExecutionReport::Fill(Box::new(fill.clone()));
2462 let encoded = encode_execution_report(&envelope).expect("encode");
2463
2464 assert_eq!(
2465 encoded.payload_type.expect("override").as_str(),
2466 PAYLOAD_TYPE_FILL_REPORT,
2467 );
2468 assert_eq!(encoded.index_keys.len(), 2);
2469 assert_eq!(encoded.index_keys[0].kind, IndexKind::VenueOrderId);
2470 assert_eq!(encoded.index_keys[0].key, fill.venue_order_id.to_string());
2471 assert_eq!(encoded.index_keys[1].kind, IndexKind::ClientOrderId);
2472 assert_eq!(
2473 encoded.index_keys[1].key,
2474 fill.client_order_id.expect("set").to_string(),
2475 );
2476
2477 let decoded: FillReport = rmp_serde::from_slice(&encoded.payload).expect("decode");
2478 assert_eq!(decoded, fill);
2479 }
2480
2481 #[rstest]
2482 fn execution_report_fill_envelope_omits_client_order_id_when_absent() {
2483 let mut fill = make_fill_report();
2484 fill.client_order_id = None;
2485 let envelope = ExecutionReport::Fill(Box::new(fill));
2486 let encoded = encode_execution_report(&envelope).expect("encode");
2487
2488 assert_eq!(encoded.index_keys.len(), 1);
2489 assert_eq!(encoded.index_keys[0].kind, IndexKind::VenueOrderId);
2490 }
2491
2492 #[rstest]
2493 fn execution_report_position_envelope_records_no_indices() {
2494 let position = make_position_status_report();
2498 let envelope = ExecutionReport::Position(Box::new(position.clone()));
2499 let encoded = encode_execution_report(&envelope).expect("encode");
2500
2501 assert_eq!(
2502 encoded.payload_type.expect("override").as_str(),
2503 PAYLOAD_TYPE_POSITION_STATUS_REPORT,
2504 );
2505 assert!(encoded.index_keys.is_empty());
2506
2507 let decoded: PositionStatusReport =
2508 rmp_serde::from_slice(&encoded.payload).expect("decode");
2509 assert_eq!(decoded, position);
2510 }
2511
2512 #[rstest]
2513 fn execution_report_order_with_fills_envelope_dedupes_shared_order_ids() {
2514 let order = make_order_status_report();
2518 let fills = vec![make_fill_report()];
2519 let envelope = ExecutionReport::OrderWithFills(Box::new(order.clone()), fills);
2520 let encoded = encode_execution_report(&envelope).expect("encode");
2521
2522 assert_eq!(
2523 encoded.payload_type.expect("override").as_str(),
2524 PAYLOAD_TYPE_ORDER_WITH_FILLS,
2525 );
2526 assert_eq!(encoded.index_keys.len(), 2);
2527 assert_eq!(encoded.index_keys[0].kind, IndexKind::VenueOrderId);
2528 assert_eq!(encoded.index_keys[0].key, order.venue_order_id.to_string());
2529 assert_eq!(encoded.index_keys[1].kind, IndexKind::ClientOrderId);
2530 assert_eq!(
2531 encoded.index_keys[1].key,
2532 order.client_order_id.expect("set").to_string(),
2533 );
2534 }
2535
2536 #[rstest]
2537 fn execution_report_order_with_fills_envelope_indexes_distinct_fill_ids() {
2538 let order = make_order_status_report();
2543 let mut fill = make_fill_report();
2544 fill.client_order_id = Some(ClientOrderId::from("O-EXTRA-001"));
2545 let envelope = ExecutionReport::OrderWithFills(Box::new(order), vec![fill.clone()]);
2546 let encoded = encode_execution_report(&envelope).expect("encode");
2547
2548 assert_eq!(encoded.index_keys.len(), 3);
2549 assert_eq!(encoded.index_keys[2].kind, IndexKind::ClientOrderId);
2550 assert_eq!(
2551 encoded.index_keys[2].key,
2552 fill.client_order_id.expect("set").to_string(),
2553 );
2554 }
2555
2556 #[rstest]
2557 fn execution_report_order_with_fills_payload_round_trips() {
2558 #[derive(serde::Deserialize)]
2559 struct OrderWithFillsOwned {
2560 order_report: OrderStatusReport,
2561 fill_reports: Vec<FillReport>,
2562 }
2563
2564 let order = make_order_status_report();
2565 let fills = vec![make_fill_report()];
2566 let envelope = ExecutionReport::OrderWithFills(Box::new(order.clone()), fills.clone());
2567 let encoded = encode_execution_report(&envelope).expect("encode");
2568
2569 let decoded: OrderWithFillsOwned = rmp_serde::from_slice(&encoded.payload).expect("decode");
2570 assert_eq!(decoded.order_report, order);
2571 assert_eq!(decoded.fill_reports, fills);
2572 }
2573
2574 #[rstest]
2575 fn execution_report_mass_status_envelope_indexes_orders_and_fills() {
2576 let status = make_execution_mass_status_with_reports();
2577 let envelope = ExecutionReport::MassStatus(Box::new(status.clone()));
2578 let encoded = encode_execution_report(&envelope).expect("encode");
2579
2580 assert_eq!(
2581 encoded.payload_type.expect("override").as_str(),
2582 PAYLOAD_TYPE_EXECUTION_MASS_STATUS,
2583 );
2584 assert_eq!(encoded.index_keys.len(), 2);
2587 assert_eq!(encoded.index_keys[0].kind, IndexKind::VenueOrderId);
2588 assert_eq!(encoded.index_keys[0].key, venue_order_id().to_string());
2589 assert_eq!(encoded.index_keys[1].kind, IndexKind::ClientOrderId);
2590 assert_eq!(encoded.index_keys[1].key, client_order_id().to_string());
2591
2592 let decoded: ExecutionMassStatus = rmp_serde::from_slice(&encoded.payload).expect("decode");
2593 assert_eq!(decoded, status);
2594 }
2595
2596 #[rstest]
2597 fn execution_report_mass_status_envelope_indexes_distinct_children() {
2598 let mut status = ExecutionMassStatus::new(
2602 ClientId::from("BINANCE"),
2603 AccountId::from("BINANCE-001"),
2604 Venue::from("BINANCE"),
2605 UnixNanos::from(60),
2606 None,
2607 );
2608 let order_a = make_order_status_report();
2609 let order_b = OrderStatusReport {
2610 client_order_id: Some(ClientOrderId::from("O-B")),
2611 venue_order_id: VenueOrderId::from("V-B"),
2612 ..make_order_status_report()
2613 };
2614 let fill_c = FillReport {
2615 venue_order_id: VenueOrderId::from("V-C"),
2616 client_order_id: Some(ClientOrderId::from("O-C")),
2617 ..make_fill_report()
2618 };
2619 status.add_order_reports(vec![order_a, order_b]);
2620 status.add_fill_reports(vec![fill_c]);
2621
2622 let encoded = encode_execution_report(&ExecutionReport::MassStatus(Box::new(status)))
2623 .expect("encode");
2624
2625 assert_eq!(encoded.index_keys.len(), 6);
2627 let venue_keys: Vec<&str> = encoded
2628 .index_keys
2629 .iter()
2630 .filter(|k| k.kind == IndexKind::VenueOrderId)
2631 .map(|k| k.key.as_str())
2632 .collect();
2633 let client_keys: Vec<&str> = encoded
2634 .index_keys
2635 .iter()
2636 .filter(|k| k.kind == IndexKind::ClientOrderId)
2637 .map(|k| k.key.as_str())
2638 .collect();
2639 assert!(venue_keys.contains(&venue_order_id().to_string().as_str()));
2640 assert!(venue_keys.contains(&"V-B"));
2641 assert!(venue_keys.contains(&"V-C"));
2642 assert!(client_keys.contains(&client_order_id().to_string().as_str()));
2643 assert!(client_keys.contains(&"O-B"));
2644 assert!(client_keys.contains(&"O-C"));
2645 }
2646
2647 #[rstest]
2651 #[case::order(
2652 ExecutionReport::Order(Box::new(make_order_status_report())),
2653 PAYLOAD_TYPE_ORDER_STATUS_REPORT
2654 )]
2655 #[case::fill(
2656 ExecutionReport::Fill(Box::new(make_fill_report())),
2657 PAYLOAD_TYPE_FILL_REPORT
2658 )]
2659 #[case::order_with_fills(
2660 ExecutionReport::OrderWithFills(
2661 Box::new(make_order_status_report()),
2662 vec![make_fill_report()],
2663 ),
2664 PAYLOAD_TYPE_ORDER_WITH_FILLS,
2665 )]
2666 #[case::position(
2667 ExecutionReport::Position(Box::new(make_position_status_report())),
2668 PAYLOAD_TYPE_POSITION_STATUS_REPORT
2669 )]
2670 #[case::mass_status(
2671 ExecutionReport::MassStatus(Box::new(make_execution_mass_status_with_reports())),
2672 PAYLOAD_TYPE_EXECUTION_MASS_STATUS
2673 )]
2674 fn execution_report_envelope_stamps_inner_tag_for_every_variant(
2675 #[case] report: ExecutionReport,
2676 #[case] expected_tag: &str,
2677 ) {
2678 let encoded = encode_execution_report(&report).expect("encode");
2679 let tag = encoded.payload_type.expect("override").as_str().to_string();
2680
2681 assert_eq!(tag, expected_tag);
2682 assert_ne!(
2683 tag, PAYLOAD_TYPE_EXECUTION_REPORT,
2684 "wrapper fallback tag must never reach the writer",
2685 );
2686 }
2687
2688 fn opening_order_id() -> ClientOrderId {
2689 ClientOrderId::from("O-OPEN-001")
2690 }
2691
2692 fn closing_order_id() -> ClientOrderId {
2693 ClientOrderId::from("O-CLOSE-001")
2694 }
2695
2696 fn position_id() -> PositionId {
2697 PositionId::from("P-001")
2698 }
2699
2700 fn make_position_opened() -> PositionOpened {
2701 PositionOpened {
2702 trader_id: trader_id(),
2703 strategy_id: strategy_id(),
2704 instrument_id: instrument_id(),
2705 position_id: position_id(),
2706 account_id: AccountId::from("BINANCE-001"),
2707 opening_order_id: opening_order_id(),
2708 entry: OrderSide::Buy,
2709 side: PositionSide::Long,
2710 signed_qty: 1.0,
2711 quantity: Quantity::from("1"),
2712 last_qty: Quantity::from("1"),
2713 last_px: Price::from("100.00"),
2714 currency: Currency::USDT(),
2715 avg_px_open: 100.0,
2716 event_id: UUID4::new(),
2717 ts_event: UnixNanos::from(70),
2718 ts_init: UnixNanos::from(71),
2719 }
2720 }
2721
2722 fn make_position_changed() -> PositionChanged {
2723 PositionChanged {
2724 trader_id: trader_id(),
2725 strategy_id: strategy_id(),
2726 instrument_id: instrument_id(),
2727 position_id: position_id(),
2728 account_id: AccountId::from("BINANCE-001"),
2729 opening_order_id: opening_order_id(),
2730 entry: OrderSide::Buy,
2731 side: PositionSide::Long,
2732 signed_qty: 2.0,
2733 quantity: Quantity::from("2"),
2734 peak_quantity: Quantity::from("2"),
2735 last_qty: Quantity::from("1"),
2736 last_px: Price::from("101.00"),
2737 currency: Currency::USDT(),
2738 avg_px_open: 100.5,
2739 avg_px_close: None,
2740 realized_return: 0.0,
2741 realized_pnl: None,
2742 unrealized_pnl: Money::new(1.0, Currency::USDT()),
2743 event_id: UUID4::new(),
2744 ts_opened: UnixNanos::from(70),
2745 ts_event: UnixNanos::from(80),
2746 ts_init: UnixNanos::from(81),
2747 }
2748 }
2749
2750 fn make_position_closed() -> PositionClosed {
2751 PositionClosed {
2752 trader_id: trader_id(),
2753 strategy_id: strategy_id(),
2754 instrument_id: instrument_id(),
2755 position_id: position_id(),
2756 account_id: AccountId::from("BINANCE-001"),
2757 opening_order_id: opening_order_id(),
2758 closing_order_id: Some(closing_order_id()),
2759 entry: OrderSide::Buy,
2760 side: PositionSide::Flat,
2761 signed_qty: 0.0,
2762 quantity: Quantity::from("0"),
2763 peak_quantity: Quantity::from("2"),
2764 last_qty: Quantity::from("2"),
2765 last_px: Price::from("102.00"),
2766 currency: Currency::USDT(),
2767 avg_px_open: 100.5,
2768 avg_px_close: Some(102.0),
2769 realized_return: 0.015,
2770 realized_pnl: Some(Money::new(3.0, Currency::USDT())),
2771 unrealized_pnl: Money::new(0.0, Currency::USDT()),
2772 duration: 3_600_000_000_000,
2773 event_id: UUID4::new(),
2774 ts_opened: UnixNanos::from(70),
2775 ts_closed: Some(UnixNanos::from(90)),
2776 ts_event: UnixNanos::from(90),
2777 ts_init: UnixNanos::from(91),
2778 }
2779 }
2780
2781 fn make_position_adjusted() -> PositionAdjusted {
2782 PositionAdjusted::new(
2783 trader_id(),
2784 strategy_id(),
2785 instrument_id(),
2786 position_id(),
2787 AccountId::from("BINANCE-001"),
2788 PositionAdjustmentType::Commission,
2789 None,
2790 None,
2791 None,
2792 UUID4::new(),
2793 UnixNanos::from(100),
2794 UnixNanos::from(101),
2795 )
2796 }
2797
2798 #[rstest]
2799 fn position_event_opened_envelope_emits_opening_order_id_index() {
2800 let opened = make_position_opened();
2801 let envelope = PositionEvent::PositionOpened(opened.clone());
2802 let encoded = encode_position_event(&envelope).expect("encode");
2803
2804 assert_eq!(
2805 encoded.payload_type.expect("override").as_str(),
2806 PAYLOAD_TYPE_POSITION_OPENED,
2807 );
2808 assert_eq!(encoded.index_keys.len(), 1);
2809 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
2810 assert_eq!(
2811 encoded.index_keys[0].key,
2812 opened.opening_order_id.to_string(),
2813 );
2814
2815 let decoded: PositionOpened = rmp_serde::from_slice(&encoded.payload).expect("decode");
2816 assert_eq!(decoded, opened);
2817 }
2818
2819 #[rstest]
2820 fn position_event_changed_envelope_emits_opening_order_id_index() {
2821 let changed = make_position_changed();
2822 let envelope = PositionEvent::PositionChanged(changed.clone());
2823 let encoded = encode_position_event(&envelope).expect("encode");
2824
2825 assert_eq!(
2826 encoded.payload_type.expect("override").as_str(),
2827 PAYLOAD_TYPE_POSITION_CHANGED,
2828 );
2829 assert_eq!(encoded.index_keys.len(), 1);
2830 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
2831 assert_eq!(
2832 encoded.index_keys[0].key,
2833 changed.opening_order_id.to_string(),
2834 );
2835
2836 let decoded: PositionChanged = rmp_serde::from_slice(&encoded.payload).expect("decode");
2837 assert_eq!(decoded, changed);
2838 }
2839
2840 #[rstest]
2841 fn position_event_closed_envelope_indexes_both_opening_and_closing_order_ids() {
2842 let closed = make_position_closed();
2843 let envelope = PositionEvent::PositionClosed(closed.clone());
2844 let encoded = encode_position_event(&envelope).expect("encode");
2845
2846 assert_eq!(
2847 encoded.payload_type.expect("override").as_str(),
2848 PAYLOAD_TYPE_POSITION_CLOSED,
2849 );
2850 assert_eq!(encoded.index_keys.len(), 2);
2851 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
2852 assert_eq!(
2853 encoded.index_keys[0].key,
2854 closed.opening_order_id.to_string(),
2855 );
2856 assert_eq!(encoded.index_keys[1].kind, IndexKind::ClientOrderId);
2857 assert_eq!(
2858 encoded.index_keys[1].key,
2859 closed.closing_order_id.expect("set").to_string(),
2860 );
2861
2862 let decoded: PositionClosed = rmp_serde::from_slice(&encoded.payload).expect("decode");
2863 assert_eq!(decoded, closed);
2864 }
2865
2866 #[rstest]
2867 fn position_event_closed_envelope_omits_closing_order_id_when_absent() {
2868 let mut closed = make_position_closed();
2869 closed.closing_order_id = None;
2870 let envelope = PositionEvent::PositionClosed(closed);
2871 let encoded = encode_position_event(&envelope).expect("encode");
2872
2873 assert_eq!(encoded.index_keys.len(), 1);
2874 assert_eq!(encoded.index_keys[0].kind, IndexKind::ClientOrderId);
2875 assert_eq!(encoded.index_keys[0].key, opening_order_id().to_string());
2876 }
2877
2878 #[rstest]
2879 fn position_event_closed_envelope_dedupes_when_open_and_close_match() {
2880 let mut closed = make_position_closed();
2884 closed.closing_order_id = Some(closed.opening_order_id);
2885 let envelope = PositionEvent::PositionClosed(closed);
2886 let encoded = encode_position_event(&envelope).expect("encode");
2887
2888 assert_eq!(encoded.index_keys.len(), 1);
2889 assert_eq!(encoded.index_keys[0].key, opening_order_id().to_string());
2890 }
2891
2892 #[rstest]
2893 fn position_event_adjusted_envelope_records_no_indices() {
2894 let adjusted = make_position_adjusted();
2899 let envelope = PositionEvent::PositionAdjusted(adjusted);
2900 let encoded = encode_position_event(&envelope).expect("encode");
2901
2902 assert_eq!(
2903 encoded.payload_type.expect("override").as_str(),
2904 PAYLOAD_TYPE_POSITION_ADJUSTED,
2905 );
2906 assert!(encoded.index_keys.is_empty());
2907
2908 let decoded: PositionAdjusted = rmp_serde::from_slice(&encoded.payload).expect("decode");
2909 assert_eq!(decoded, adjusted);
2910 }
2911
2912 #[rstest]
2913 #[case::opened(
2914 PositionEvent::PositionOpened(make_position_opened()),
2915 PAYLOAD_TYPE_POSITION_OPENED
2916 )]
2917 #[case::changed(
2918 PositionEvent::PositionChanged(make_position_changed()),
2919 PAYLOAD_TYPE_POSITION_CHANGED
2920 )]
2921 #[case::closed(
2922 PositionEvent::PositionClosed(make_position_closed()),
2923 PAYLOAD_TYPE_POSITION_CLOSED
2924 )]
2925 #[case::adjusted(
2926 PositionEvent::PositionAdjusted(make_position_adjusted()),
2927 PAYLOAD_TYPE_POSITION_ADJUSTED
2928 )]
2929 fn position_event_envelope_stamps_inner_tag_for_every_variant(
2930 #[case] event: PositionEvent,
2931 #[case] expected_tag: &str,
2932 ) {
2933 let encoded = encode_position_event(&event).expect("encode");
2934 let tag = encoded.payload_type.expect("override").as_str().to_string();
2935
2936 assert_eq!(tag, expected_tag);
2937 assert_ne!(
2938 tag, PAYLOAD_TYPE_POSITION_EVENT,
2939 "wrapper fallback tag must never reach the writer",
2940 );
2941 }
2942
2943 fn make_account_state() -> AccountState {
2944 AccountState::new(
2945 AccountId::from("BINANCE-001"),
2946 AccountType::Cash,
2947 vec![AccountBalance::new(
2948 Money::from("1000000 USD"),
2949 Money::from("0 USD"),
2950 Money::from("1000000 USD"),
2951 )],
2952 vec![],
2953 true,
2954 UUID4::new(),
2955 UnixNanos::from(110),
2956 UnixNanos::from(111),
2957 Some(Currency::USD()),
2958 )
2959 }
2960
2961 #[rstest]
2962 fn account_state_encoder_records_no_indices() {
2963 let state = make_account_state();
2968 let encoded = encode_account_state(&state).expect("encode");
2969
2970 assert!(!encoded.payload.is_empty());
2971 assert!(encoded.index_keys.is_empty());
2972 assert!(
2973 encoded.payload_type.is_none(),
2974 "bare-type encoders inherit the registry's registered tag",
2975 );
2976 }
2977
2978 #[rstest]
2979 fn account_state_payload_round_trips_through_msgpack() {
2980 let state = make_account_state();
2981 let encoded = encode_account_state(&state).expect("encode");
2982
2983 let decoded: AccountState = rmp_serde::from_slice(&encoded.payload).expect("decode");
2984 assert_eq!(decoded, state);
2985 }
2986
2987 #[rstest]
2988 fn account_state_registered_under_canonical_payload_type() {
2989 let registry = default_registry();
2993 let state = make_account_state();
2994 let (tag, encoded) = registry
2995 .encode(&state)
2996 .expect("encode")
2997 .expect("registered");
2998
2999 assert_eq!(tag.as_str(), PAYLOAD_TYPE_ACCOUNT_STATE);
3000 assert!(encoded.index_keys.is_empty());
3001 }
3002
3003 #[derive(Debug, Deserialize, PartialEq, Eq)]
3004 struct DecodedTimeEventPayload {
3005 name: String,
3006 event_id: UUID4,
3007 ts_event: UnixNanos,
3008 ts_init: UnixNanos,
3009 }
3010
3011 #[rstest]
3012 fn time_event_payload_round_trips_through_msgpack() {
3013 let event = TimeEvent::new(
3014 Ustr::from("heartbeat"),
3015 UUID4::new(),
3016 UnixNanos::from(100),
3017 UnixNanos::from(99),
3018 );
3019 let encoded = encode_time_event(&event).expect("encode");
3020
3021 let decoded: DecodedTimeEventPayload =
3022 rmp_serde::from_slice(&encoded.payload).expect("decode");
3023 assert_eq!(
3024 decoded,
3025 DecodedTimeEventPayload {
3026 name: event.name.to_string(),
3027 event_id: event.event_id,
3028 ts_event: event.ts_event,
3029 ts_init: event.ts_init,
3030 },
3031 );
3032 assert!(encoded.index_keys.is_empty());
3033 }
3034
3035 #[rstest]
3036 fn time_event_registered_under_canonical_payload_type() {
3037 let registry = default_registry();
3038 let event = TimeEvent::new(
3039 Ustr::from("heartbeat"),
3040 UUID4::new(),
3041 UnixNanos::from(100),
3042 UnixNanos::from(99),
3043 );
3044 let (tag, encoded) = registry
3045 .encode(&event)
3046 .expect("encode")
3047 .expect("registered");
3048
3049 assert_eq!(tag.as_str(), PAYLOAD_TYPE_TIME_EVENT);
3050 assert!(encoded.index_keys.is_empty());
3051 }
3052
3053 #[rstest]
3054 fn data_command_request_envelope_stamps_request_command_payload_type() {
3055 let request = make_request_command();
3059 let envelope = DataCommand::Request(request.clone());
3060 let encoded = encode_data_command(&envelope).expect("encode");
3061
3062 assert_eq!(
3063 encoded.payload_type.expect("override").as_str(),
3064 PAYLOAD_TYPE_REQUEST_COMMAND,
3065 );
3066 assert!(encoded.index_keys.is_empty());
3067
3068 let decoded: RequestCommand = rmp_serde::from_slice(&encoded.payload).expect("decode");
3069 match (decoded, request) {
3070 (RequestCommand::Quotes(decoded), RequestCommand::Quotes(expected)) => {
3071 assert_eq!(decoded.request_id, expected.request_id);
3072 assert_eq!(decoded.instrument_id, expected.instrument_id);
3073 }
3074 other => panic!("expected RequestCommand::Quotes round trip, was {other:?}"),
3075 }
3076 }
3077
3078 #[rstest]
3079 fn data_command_subscribe_envelope_stamps_subscribe_command_payload_type() {
3080 let subscribe = make_subscribe_command();
3081 let envelope = DataCommand::Subscribe(subscribe.clone());
3082 let encoded = encode_data_command(&envelope).expect("encode");
3083
3084 assert_eq!(
3085 encoded.payload_type.expect("override").as_str(),
3086 PAYLOAD_TYPE_SUBSCRIBE_COMMAND,
3087 );
3088 assert!(encoded.index_keys.is_empty());
3089
3090 let decoded: SubscribeCommand = rmp_serde::from_slice(&encoded.payload).expect("decode");
3091 match (decoded, subscribe) {
3092 (SubscribeCommand::Quotes(decoded), SubscribeCommand::Quotes(expected)) => {
3093 assert_eq!(decoded.command_id, expected.command_id);
3094 assert_eq!(decoded.instrument_id, expected.instrument_id);
3095 }
3096 other => panic!("expected SubscribeCommand::Quotes round trip, was {other:?}"),
3097 }
3098 }
3099
3100 #[rstest]
3101 fn data_command_unsubscribe_envelope_stamps_unsubscribe_command_payload_type() {
3102 let unsubscribe = make_unsubscribe_command();
3103 let envelope = DataCommand::Unsubscribe(unsubscribe.clone());
3104 let encoded = encode_data_command(&envelope).expect("encode");
3105
3106 assert_eq!(
3107 encoded.payload_type.expect("override").as_str(),
3108 PAYLOAD_TYPE_UNSUBSCRIBE_COMMAND,
3109 );
3110 assert!(encoded.index_keys.is_empty());
3111
3112 let decoded: UnsubscribeCommand = rmp_serde::from_slice(&encoded.payload).expect("decode");
3113 match (decoded, unsubscribe) {
3114 (UnsubscribeCommand::Quotes(decoded), UnsubscribeCommand::Quotes(expected)) => {
3115 assert_eq!(decoded.command_id, expected.command_id);
3116 assert_eq!(decoded.instrument_id, expected.instrument_id);
3117 }
3118 other => panic!("expected UnsubscribeCommand::Quotes round trip, was {other:?}"),
3119 }
3120 }
3121
3122 #[rstest]
3123 fn data_command_registered_under_category_payload_type() {
3124 let registry = default_registry();
3127 let envelope = DataCommand::Subscribe(make_subscribe_command());
3128 let (tag, encoded) = registry
3129 .encode(&envelope)
3130 .expect("encode")
3131 .expect("registered");
3132
3133 assert_eq!(tag.as_str(), PAYLOAD_TYPE_SUBSCRIBE_COMMAND);
3134 assert!(encoded.index_keys.is_empty());
3135 }
3136
3137 #[cfg(feature = "defi")]
3138 #[rstest]
3139 fn data_command_defi_request_envelope_stamps_defi_request_command_payload_type() {
3140 let request = make_defi_request_command();
3141 let envelope = DataCommand::DefiRequest(request.clone());
3142 let encoded = encode_data_command(&envelope).expect("encode");
3143
3144 assert_eq!(
3145 encoded.payload_type.expect("override").as_str(),
3146 PAYLOAD_TYPE_DEFI_REQUEST_COMMAND,
3147 );
3148 assert!(encoded.index_keys.is_empty());
3149
3150 let decoded: DefiRequestCommand = rmp_serde::from_slice(&encoded.payload).expect("decode");
3151 match (decoded, request) {
3152 (
3153 DefiRequestCommand::PoolSnapshot(decoded),
3154 DefiRequestCommand::PoolSnapshot(expected),
3155 ) => {
3156 assert_eq!(decoded.request_id, expected.request_id);
3157 assert_eq!(decoded.instrument_id, expected.instrument_id);
3158 assert_eq!(decoded.client_id, expected.client_id);
3159 assert_eq!(decoded.ts_init, expected.ts_init);
3160 }
3161 }
3162 }
3163
3164 #[cfg(feature = "defi")]
3165 #[rstest]
3166 fn data_command_defi_subscribe_envelope_stamps_defi_subscribe_command_payload_type() {
3167 let subscribe = make_defi_subscribe_command();
3168 let envelope = DataCommand::DefiSubscribe(subscribe.clone());
3169 let encoded = encode_data_command(&envelope).expect("encode");
3170
3171 assert_eq!(
3172 encoded.payload_type.expect("override").as_str(),
3173 PAYLOAD_TYPE_DEFI_SUBSCRIBE_COMMAND,
3174 );
3175 assert!(encoded.index_keys.is_empty());
3176
3177 let decoded: DefiSubscribeCommand =
3178 rmp_serde::from_slice(&encoded.payload).expect("decode");
3179
3180 match (decoded, subscribe) {
3181 (DefiSubscribeCommand::Blocks(decoded), DefiSubscribeCommand::Blocks(expected)) => {
3182 assert_eq!(decoded.command_id, expected.command_id);
3183 assert_eq!(decoded.chain, expected.chain);
3184 assert_eq!(decoded.client_id, expected.client_id);
3185 assert_eq!(decoded.ts_init, expected.ts_init);
3186 }
3187 other => panic!("expected DefiSubscribeCommand::Blocks round trip, was {other:?}"),
3188 }
3189 }
3190
3191 #[cfg(feature = "defi")]
3192 #[rstest]
3193 fn data_command_defi_unsubscribe_envelope_stamps_defi_unsubscribe_command_payload_type() {
3194 let unsubscribe = make_defi_unsubscribe_command();
3195 let envelope = DataCommand::DefiUnsubscribe(unsubscribe.clone());
3196 let encoded = encode_data_command(&envelope).expect("encode");
3197
3198 assert_eq!(
3199 encoded.payload_type.expect("override").as_str(),
3200 PAYLOAD_TYPE_DEFI_UNSUBSCRIBE_COMMAND,
3201 );
3202 assert!(encoded.index_keys.is_empty());
3203
3204 let decoded: DefiUnsubscribeCommand =
3205 rmp_serde::from_slice(&encoded.payload).expect("decode");
3206
3207 match (decoded, unsubscribe) {
3208 (DefiUnsubscribeCommand::Blocks(decoded), DefiUnsubscribeCommand::Blocks(expected)) => {
3209 assert_eq!(decoded.command_id, expected.command_id);
3210 assert_eq!(decoded.chain, expected.chain);
3211 assert_eq!(decoded.client_id, expected.client_id);
3212 assert_eq!(decoded.ts_init, expected.ts_init);
3213 }
3214 other => panic!("expected DefiUnsubscribeCommand::Blocks round trip, was {other:?}"),
3215 }
3216 }
3217
3218 fn client_id() -> ClientId {
3219 ClientId::from("BINANCE")
3220 }
3221
3222 fn venue() -> Venue {
3223 Venue::from("BINANCE")
3224 }
3225
3226 fn correlation_id() -> UUID4 {
3227 UUID4::new()
3228 }
3229
3230 fn bar_type() -> BarType {
3231 BarType::from("ETHUSDT-PERP.BINANCE-1-MINUTE-LAST-EXTERNAL")
3232 }
3233
3234 fn data_type() -> DataType {
3235 DataType::new("Bar", None, None)
3236 }
3237
3238 fn make_request_command() -> RequestCommand {
3239 RequestCommand::Quotes(RequestQuotes::new(
3240 instrument_id(),
3241 None,
3242 None,
3243 None,
3244 Some(client_id()),
3245 correlation_id(),
3246 UnixNanos::from(197),
3247 None,
3248 ))
3249 }
3250
3251 fn make_subscribe_command() -> SubscribeCommand {
3252 SubscribeCommand::Quotes(SubscribeQuotes::new(
3253 instrument_id(),
3254 Some(client_id()),
3255 Some(venue()),
3256 correlation_id(),
3257 UnixNanos::from(198),
3258 Some(correlation_id()),
3259 None,
3260 ))
3261 }
3262
3263 fn make_unsubscribe_command() -> UnsubscribeCommand {
3264 UnsubscribeCommand::Quotes(UnsubscribeQuotes::new(
3265 instrument_id(),
3266 Some(client_id()),
3267 Some(venue()),
3268 correlation_id(),
3269 UnixNanos::from(199),
3270 Some(correlation_id()),
3271 None,
3272 ))
3273 }
3274
3275 #[cfg(feature = "defi")]
3276 fn make_defi_request_command() -> DefiRequestCommand {
3277 DefiRequestCommand::PoolSnapshot(RequestPoolSnapshot::new(
3278 instrument_id(),
3279 Some(client_id()),
3280 correlation_id(),
3281 UnixNanos::from(196),
3282 None,
3283 ))
3284 }
3285
3286 #[cfg(feature = "defi")]
3287 fn make_defi_subscribe_command() -> DefiSubscribeCommand {
3288 DefiSubscribeCommand::Blocks(SubscribeBlocks::new(
3289 Blockchain::Ethereum,
3290 Some(client_id()),
3291 correlation_id(),
3292 UnixNanos::from(195),
3293 None,
3294 ))
3295 }
3296
3297 #[cfg(feature = "defi")]
3298 fn make_defi_unsubscribe_command() -> DefiUnsubscribeCommand {
3299 DefiUnsubscribeCommand::Blocks(UnsubscribeBlocks::new(
3300 Blockchain::Ethereum,
3301 Some(client_id()),
3302 correlation_id(),
3303 UnixNanos::from(194),
3304 None,
3305 ))
3306 }
3307
3308 fn make_custom_data_response() -> CustomDataResponse {
3309 CustomDataResponse::new(
3310 correlation_id(),
3311 client_id(),
3312 Some(venue()),
3313 data_type(),
3314 (),
3315 None,
3316 None,
3317 UnixNanos::from(200),
3318 None,
3319 )
3320 }
3321
3322 fn make_instrument_response() -> InstrumentResponse {
3323 InstrumentResponse::new(
3324 correlation_id(),
3325 client_id(),
3326 instrument_id(),
3327 InstrumentAny::CurrencyPair(currency_pair_ethusdt()),
3328 None,
3329 None,
3330 UnixNanos::from(201),
3331 None,
3332 )
3333 }
3334
3335 fn make_instruments_response() -> InstrumentsResponse {
3336 InstrumentsResponse::new(
3337 correlation_id(),
3338 client_id(),
3339 venue(),
3340 vec![InstrumentAny::CurrencyPair(currency_pair_ethusdt())],
3341 None,
3342 None,
3343 UnixNanos::from(202),
3344 None,
3345 )
3346 }
3347
3348 fn make_book_response() -> BookResponse {
3349 BookResponse::new(
3350 correlation_id(),
3351 client_id(),
3352 instrument_id(),
3353 OrderBook::new(instrument_id(), BookType::L2_MBP),
3354 None,
3355 None,
3356 UnixNanos::from(203),
3357 None,
3358 )
3359 }
3360
3361 fn make_book_deltas_response() -> BookDeltasResponse {
3362 BookDeltasResponse::new(
3363 correlation_id(),
3364 client_id(),
3365 instrument_id(),
3366 Vec::new(),
3367 None,
3368 None,
3369 UnixNanos::from(204),
3370 None,
3371 )
3372 }
3373
3374 fn make_book_depth_response() -> BookDepthResponse {
3375 let mut depth = stub_depth10();
3376 depth.instrument_id = instrument_id();
3377 BookDepthResponse::new(
3378 correlation_id(),
3379 client_id(),
3380 instrument_id(),
3381 vec![depth],
3382 None,
3383 None,
3384 UnixNanos::from(205),
3385 None,
3386 )
3387 }
3388
3389 fn make_quotes_response() -> QuotesResponse {
3390 QuotesResponse::new(
3391 correlation_id(),
3392 client_id(),
3393 instrument_id(),
3394 Vec::new(),
3395 None,
3396 None,
3397 UnixNanos::from(206),
3398 None,
3399 )
3400 }
3401
3402 fn make_trades_response() -> TradesResponse {
3403 TradesResponse::new(
3404 correlation_id(),
3405 client_id(),
3406 instrument_id(),
3407 Vec::new(),
3408 None,
3409 None,
3410 UnixNanos::from(207),
3411 None,
3412 )
3413 }
3414
3415 fn make_funding_rates_response() -> FundingRatesResponse {
3416 FundingRatesResponse::new(
3417 correlation_id(),
3418 client_id(),
3419 instrument_id(),
3420 Vec::new(),
3421 None,
3422 None,
3423 UnixNanos::from(208),
3424 None,
3425 )
3426 }
3427
3428 fn make_forward_prices_response() -> ForwardPricesResponse {
3429 ForwardPricesResponse::new(
3430 correlation_id(),
3431 client_id(),
3432 venue(),
3433 Vec::new(),
3434 UnixNanos::from(209),
3435 None,
3436 )
3437 }
3438
3439 fn make_bars_response() -> BarsResponse {
3440 BarsResponse::new(
3441 correlation_id(),
3442 client_id(),
3443 bar_type(),
3444 Vec::<Bar>::new(),
3445 None,
3446 None,
3447 UnixNanos::from(210),
3448 None,
3449 )
3450 }
3451
3452 #[rstest]
3453 fn data_response_custom_data_envelope_stamps_inner_tag_with_no_indices() {
3454 let response = make_custom_data_response();
3458 let envelope = DataResponse::Data(response);
3459 let encoded = encode_data_response(&envelope).expect("encode");
3460
3461 assert_eq!(
3462 encoded.payload_type.expect("override").as_str(),
3463 PAYLOAD_TYPE_CUSTOM_DATA_RESPONSE,
3464 );
3465 assert!(encoded.index_keys.is_empty());
3466 assert!(!encoded.payload.is_empty());
3467 }
3468
3469 #[rstest]
3470 fn data_response_custom_data_payload_round_trips_metadata() {
3471 #[derive(serde::Deserialize)]
3475 struct CustomDataResponseOwned {
3476 correlation_id: UUID4,
3477 client_id: ClientId,
3478 venue: Option<Venue>,
3479 ts_init: UnixNanos,
3480 }
3481
3482 let response = make_custom_data_response();
3483 let envelope = DataResponse::Data(response.clone());
3484 let encoded = encode_data_response(&envelope).expect("encode");
3485
3486 let decoded: CustomDataResponseOwned =
3487 rmp_serde::from_slice(&encoded.payload).expect("decode");
3488 assert_eq!(decoded.correlation_id, response.correlation_id);
3489 assert_eq!(decoded.client_id, response.client_id);
3490 assert_eq!(decoded.venue, response.venue);
3491 assert_eq!(decoded.ts_init, response.ts_init);
3492 }
3493
3494 #[rstest]
3495 fn data_response_book_envelope_stamps_inner_tag_with_no_indices() {
3496 let response = make_book_response();
3497 let envelope = DataResponse::Book(response);
3498 let encoded = encode_data_response(&envelope).expect("encode");
3499
3500 assert_eq!(
3501 encoded.payload_type.expect("override").as_str(),
3502 PAYLOAD_TYPE_BOOK_RESPONSE,
3503 );
3504 assert!(encoded.index_keys.is_empty());
3505 assert!(!encoded.payload.is_empty());
3506 }
3507
3508 #[rstest]
3509 fn data_response_book_payload_round_trips_metadata() {
3510 #[derive(serde::Deserialize)]
3513 struct BookResponseOwned {
3514 correlation_id: UUID4,
3515 instrument_id: InstrumentId,
3516 }
3517
3518 let response = make_book_response();
3519 let envelope = DataResponse::Book(response.clone());
3520 let encoded = encode_data_response(&envelope).expect("encode");
3521
3522 let decoded: BookResponseOwned = rmp_serde::from_slice(&encoded.payload).expect("decode");
3523 assert_eq!(decoded.correlation_id, response.correlation_id);
3524 assert_eq!(decoded.instrument_id, response.instrument_id);
3525 }
3526
3527 #[rstest]
3528 fn data_response_book_depth_payload_round_trips() {
3529 let response = make_book_depth_response();
3530 let envelope = DataResponse::BookDepth(response.clone());
3531 let encoded = encode_data_response(&envelope).expect("encode");
3532
3533 assert_eq!(
3534 encoded.payload_type.expect("override").as_str(),
3535 PAYLOAD_TYPE_BOOK_DEPTH_RESPONSE,
3536 );
3537 assert!(encoded.index_keys.is_empty());
3538
3539 let decoded: BookDepthResponse = rmp_serde::from_slice(&encoded.payload).expect("decode");
3540 assert_eq!(decoded.correlation_id, response.correlation_id);
3541 assert_eq!(decoded.instrument_id, response.instrument_id);
3542 assert_eq!(decoded.data, response.data);
3543 }
3544
3545 #[rstest]
3546 fn data_response_quotes_payload_round_trips() {
3547 let response = make_quotes_response();
3548 let envelope = DataResponse::Quotes(response.clone());
3549 let encoded = encode_data_response(&envelope).expect("encode");
3550
3551 assert_eq!(
3552 encoded.payload_type.expect("override").as_str(),
3553 PAYLOAD_TYPE_QUOTES_RESPONSE,
3554 );
3555 assert!(encoded.index_keys.is_empty());
3556
3557 let decoded: QuotesResponse = rmp_serde::from_slice(&encoded.payload).expect("decode");
3558 assert_eq!(decoded.correlation_id, response.correlation_id);
3559 assert_eq!(decoded.instrument_id, response.instrument_id);
3560 assert_eq!(decoded.data, response.data);
3561 }
3562
3563 #[rstest]
3564 fn data_response_trades_payload_round_trips() {
3565 let response = make_trades_response();
3566 let envelope = DataResponse::Trades(response.clone());
3567 let encoded = encode_data_response(&envelope).expect("encode");
3568
3569 assert_eq!(
3570 encoded.payload_type.expect("override").as_str(),
3571 PAYLOAD_TYPE_TRADES_RESPONSE,
3572 );
3573
3574 let decoded: TradesResponse = rmp_serde::from_slice(&encoded.payload).expect("decode");
3575 assert_eq!(decoded.correlation_id, response.correlation_id);
3576 }
3577
3578 #[rstest]
3579 fn data_response_bars_payload_round_trips() {
3580 let response = make_bars_response();
3581 let envelope = DataResponse::Bars(response.clone());
3582 let encoded = encode_data_response(&envelope).expect("encode");
3583
3584 assert_eq!(
3585 encoded.payload_type.expect("override").as_str(),
3586 PAYLOAD_TYPE_BARS_RESPONSE,
3587 );
3588
3589 let decoded: BarsResponse = rmp_serde::from_slice(&encoded.payload).expect("decode");
3590 assert_eq!(decoded.correlation_id, response.correlation_id);
3591 assert_eq!(decoded.bar_type, response.bar_type);
3592 }
3593
3594 #[rstest]
3595 fn data_response_instrument_payload_round_trips() {
3596 let response = make_instrument_response();
3597 let envelope = DataResponse::Instrument(Box::new(response.clone()));
3598 let encoded = encode_data_response(&envelope).expect("encode");
3599
3600 assert_eq!(
3601 encoded.payload_type.expect("override").as_str(),
3602 PAYLOAD_TYPE_INSTRUMENT_RESPONSE,
3603 );
3604
3605 let decoded: InstrumentResponse = rmp_serde::from_slice(&encoded.payload).expect("decode");
3606 assert_eq!(decoded.correlation_id, response.correlation_id);
3607 assert_eq!(decoded.instrument_id, response.instrument_id);
3608 }
3609
3610 #[rstest]
3615 #[case::data(
3616 DataResponse::Data(make_custom_data_response()),
3617 PAYLOAD_TYPE_CUSTOM_DATA_RESPONSE
3618 )]
3619 #[case::instrument(
3620 DataResponse::Instrument(Box::new(make_instrument_response())),
3621 PAYLOAD_TYPE_INSTRUMENT_RESPONSE
3622 )]
3623 #[case::instruments(
3624 DataResponse::Instruments(make_instruments_response()),
3625 PAYLOAD_TYPE_INSTRUMENTS_RESPONSE
3626 )]
3627 #[case::book(DataResponse::Book(make_book_response()), PAYLOAD_TYPE_BOOK_RESPONSE)]
3628 #[case::book_deltas(
3629 DataResponse::BookDeltas(make_book_deltas_response()),
3630 PAYLOAD_TYPE_BOOK_DELTAS_RESPONSE
3631 )]
3632 #[case::book_depth(
3633 DataResponse::BookDepth(make_book_depth_response()),
3634 PAYLOAD_TYPE_BOOK_DEPTH_RESPONSE
3635 )]
3636 #[case::quotes(
3637 DataResponse::Quotes(make_quotes_response()),
3638 PAYLOAD_TYPE_QUOTES_RESPONSE
3639 )]
3640 #[case::trades(
3641 DataResponse::Trades(make_trades_response()),
3642 PAYLOAD_TYPE_TRADES_RESPONSE
3643 )]
3644 #[case::funding_rates(
3645 DataResponse::FundingRates(make_funding_rates_response()),
3646 PAYLOAD_TYPE_FUNDING_RATES_RESPONSE
3647 )]
3648 #[case::forward_prices(
3649 DataResponse::ForwardPrices(make_forward_prices_response()),
3650 PAYLOAD_TYPE_FORWARD_PRICES_RESPONSE
3651 )]
3652 #[case::bars(DataResponse::Bars(make_bars_response()), PAYLOAD_TYPE_BARS_RESPONSE)]
3653 fn data_response_envelope_stamps_inner_tag_for_every_variant(
3654 #[case] response: DataResponse,
3655 #[case] expected_tag: &str,
3656 ) {
3657 let encoded = encode_data_response(&response).expect("encode");
3658 let tag = encoded.payload_type.expect("override").as_str().to_string();
3659
3660 assert_eq!(tag, expected_tag);
3661 assert_ne!(
3662 tag, PAYLOAD_TYPE_DATA_RESPONSE,
3663 "wrapper fallback tag must never reach the writer",
3664 );
3665 assert!(
3666 encoded.index_keys.is_empty(),
3667 "DataResponse correlation_id has no matching IndexKind today",
3668 );
3669 }
3670
3671 #[rstest]
3672 fn data_response_registered_under_canonical_payload_type() {
3673 let registry = default_registry();
3677 let envelope = DataResponse::Quotes(make_quotes_response());
3678 let (tag, encoded) = registry
3679 .encode(&envelope)
3680 .expect("encode")
3681 .expect("registered");
3682
3683 assert_eq!(tag.as_str(), PAYLOAD_TYPE_QUOTES_RESPONSE);
3684 assert!(encoded.index_keys.is_empty());
3685 }
3686
3687 #[rstest]
3688 fn data_response_headers_extractor_surfaces_correlation_id() {
3689 let registry = default_registry();
3694 let response = make_quotes_response();
3695 let expected = response.correlation_id;
3696 let envelope = DataResponse::Quotes(response);
3697
3698 let headers = registry
3699 .headers_for_any(&envelope as &dyn std::any::Any)
3700 .expect("registered");
3701 assert_eq!(headers.correlation_id, Some(expected));
3702 assert_eq!(headers.causation_id, None);
3703 }
3704
3705 #[rstest]
3706 fn data_command_request_headers_use_request_id_as_correlation() {
3707 let registry = default_registry();
3712 let request = make_quotes_request();
3713 let expected = request.request_id;
3714 let envelope = DataCommand::Request(RequestCommand::Quotes(request));
3715
3716 let headers = registry
3717 .headers_for_any(&envelope as &dyn std::any::Any)
3718 .expect("registered");
3719 assert_eq!(headers.correlation_id, Some(expected));
3720 }
3721
3722 #[rstest]
3723 fn data_command_subscribe_headers_surface_correlation_id() {
3724 let registry = default_registry();
3728 let subscribe = make_subscribe_command();
3729 let expected = subscribe.correlation_id();
3730 let envelope = DataCommand::Subscribe(subscribe);
3731
3732 let headers = registry
3733 .headers_for_any(&envelope as &dyn std::any::Any)
3734 .expect("registered");
3735 assert_eq!(headers.correlation_id, expected);
3736 }
3737
3738 #[rstest]
3739 fn data_command_unsubscribe_headers_surface_correlation_id() {
3740 let registry = default_registry();
3741 let unsubscribe = make_unsubscribe_command();
3742 let expected = unsubscribe.correlation_id();
3743 let envelope = DataCommand::Unsubscribe(unsubscribe);
3744
3745 let headers = registry
3746 .headers_for_any(&envelope as &dyn std::any::Any)
3747 .expect("registered");
3748 assert_eq!(headers.correlation_id, expected);
3749 }
3750
3751 #[rstest]
3752 #[case::submit_order(trading_command_submit_order)]
3753 #[case::submit_order_list(trading_command_submit_order_list)]
3754 #[case::modify_order(trading_command_modify_order)]
3755 #[case::batch_modify_orders(trading_command_batch_modify_orders)]
3756 #[case::cancel_order(trading_command_cancel_order)]
3757 #[case::cancel_all_orders(trading_command_cancel_all_orders)]
3758 #[case::batch_cancel_orders(trading_command_batch_cancel_orders)]
3759 #[case::query_order(trading_command_query_order)]
3760 #[case::query_account(trading_command_query_account)]
3761 fn trading_command_extractor_surfaces_both_headers(
3762 #[case] builder: fn() -> (TradingCommand, UUID4, UUID4),
3763 ) {
3764 let (envelope, corr, caus) = builder();
3769 let registry = default_registry();
3770
3771 let headers = registry
3772 .headers_for_any(&envelope as &dyn std::any::Any)
3773 .expect("registered");
3774 assert_eq!(headers.correlation_id, Some(corr));
3775 assert_eq!(headers.causation_id, Some(caus));
3776 }
3777
3778 fn trading_command_submit_order() -> (TradingCommand, UUID4, UUID4) {
3779 let corr = UUID4::new();
3780 let caus = UUID4::new();
3781 let mut cmd = make_submit_order();
3782 cmd.correlation_id = Some(corr);
3783 cmd.causation_id = Some(caus);
3784 (TradingCommand::SubmitOrder(cmd), corr, caus)
3785 }
3786
3787 fn trading_command_submit_order_list() -> (TradingCommand, UUID4, UUID4) {
3788 let corr = UUID4::new();
3789 let caus = UUID4::new();
3790 let mut cmd = make_submit_order_list(vec![client_order_id()]);
3791 cmd.correlation_id = Some(corr);
3792 cmd.causation_id = Some(caus);
3793 (TradingCommand::SubmitOrderList(cmd), corr, caus)
3794 }
3795
3796 fn trading_command_modify_order() -> (TradingCommand, UUID4, UUID4) {
3797 let corr = UUID4::new();
3798 let caus = UUID4::new();
3799 let mut cmd = make_modify_order(Some(venue_order_id()));
3800 cmd.correlation_id = Some(corr);
3801 cmd.causation_id = Some(caus);
3802 (TradingCommand::ModifyOrder(cmd), corr, caus)
3803 }
3804
3805 fn trading_command_batch_modify_orders() -> (TradingCommand, UUID4, UUID4) {
3806 let corr = UUID4::new();
3807 let caus = UUID4::new();
3808 let mut cmd = make_batch_modify_orders(vec![make_modify_order(Some(venue_order_id()))]);
3809 cmd.correlation_id = Some(corr);
3810 cmd.causation_id = Some(caus);
3811 (TradingCommand::ModifyOrders(cmd), corr, caus)
3812 }
3813
3814 fn trading_command_cancel_order() -> (TradingCommand, UUID4, UUID4) {
3815 let corr = UUID4::new();
3816 let caus = UUID4::new();
3817 let mut cmd = make_cancel_order();
3818 cmd.correlation_id = Some(corr);
3819 cmd.causation_id = Some(caus);
3820 (TradingCommand::CancelOrder(cmd), corr, caus)
3821 }
3822
3823 fn trading_command_batch_cancel_orders() -> (TradingCommand, UUID4, UUID4) {
3824 let corr = UUID4::new();
3825 let caus = UUID4::new();
3826 let mut cmd = make_batch_cancel_orders(vec![make_cancel_order()]);
3827 cmd.correlation_id = Some(corr);
3828 cmd.causation_id = Some(caus);
3829 (TradingCommand::CancelOrders(cmd), corr, caus)
3830 }
3831
3832 fn trading_command_cancel_all_orders() -> (TradingCommand, UUID4, UUID4) {
3833 let corr = UUID4::new();
3834 let caus = UUID4::new();
3835 let mut cmd = make_cancel_all_orders();
3836 cmd.correlation_id = Some(corr);
3837 cmd.causation_id = Some(caus);
3838 (TradingCommand::CancelAllOrders(cmd), corr, caus)
3839 }
3840
3841 fn trading_command_query_order() -> (TradingCommand, UUID4, UUID4) {
3842 let corr = UUID4::new();
3843 let caus = UUID4::new();
3844 let mut cmd = make_query_order(Some(venue_order_id()));
3845 cmd.correlation_id = Some(corr);
3846 cmd.causation_id = Some(caus);
3847 (TradingCommand::QueryOrder(cmd), corr, caus)
3848 }
3849
3850 fn trading_command_query_account() -> (TradingCommand, UUID4, UUID4) {
3851 let corr = UUID4::new();
3852 let caus = UUID4::new();
3853 let mut cmd = make_query_account();
3854 cmd.correlation_id = Some(corr);
3855 cmd.causation_id = Some(caus);
3856 (TradingCommand::QueryAccount(cmd), corr, caus)
3857 }
3858
3859 #[rstest]
3860 #[case::data(data_response_data())]
3861 #[case::instrument(data_response_instrument())]
3862 #[case::instruments(data_response_instruments())]
3863 #[case::book(data_response_book())]
3864 #[case::book_deltas(data_response_book_deltas())]
3865 #[case::book_depth(data_response_book_depth())]
3866 #[case::quotes(data_response_quotes())]
3867 #[case::trades(data_response_trades())]
3868 #[case::funding_rates(data_response_funding_rates())]
3869 #[case::forward_prices(data_response_forward_prices())]
3870 #[case::bars(data_response_bars())]
3871 fn data_response_extractor_surfaces_correlation_id_for_every_variant(
3872 #[case] envelope_with_expected: (DataResponse, UUID4),
3873 ) {
3874 let (envelope, expected) = envelope_with_expected;
3878 let registry = default_registry();
3879
3880 let headers = registry
3881 .headers_for_any(&envelope as &dyn std::any::Any)
3882 .expect("registered");
3883 assert_eq!(headers.correlation_id, Some(expected));
3884 assert_eq!(headers.causation_id, None);
3885 }
3886
3887 fn data_response_data() -> (DataResponse, UUID4) {
3888 let resp = make_custom_data_response();
3889 let expected = resp.correlation_id;
3890 (DataResponse::Data(resp), expected)
3891 }
3892
3893 fn data_response_instrument() -> (DataResponse, UUID4) {
3894 let resp = make_instrument_response();
3895 let expected = resp.correlation_id;
3896 (DataResponse::Instrument(Box::new(resp)), expected)
3897 }
3898
3899 fn data_response_instruments() -> (DataResponse, UUID4) {
3900 let resp = make_instruments_response();
3901 let expected = resp.correlation_id;
3902 (DataResponse::Instruments(resp), expected)
3903 }
3904
3905 fn data_response_book() -> (DataResponse, UUID4) {
3906 let resp = make_book_response();
3907 let expected = resp.correlation_id;
3908 (DataResponse::Book(resp), expected)
3909 }
3910
3911 fn data_response_book_deltas() -> (DataResponse, UUID4) {
3912 let resp = make_book_deltas_response();
3913 let expected = resp.correlation_id;
3914 (DataResponse::BookDeltas(resp), expected)
3915 }
3916
3917 fn data_response_book_depth() -> (DataResponse, UUID4) {
3918 let resp = make_book_depth_response();
3919 let expected = resp.correlation_id;
3920 (DataResponse::BookDepth(resp), expected)
3921 }
3922
3923 fn data_response_quotes() -> (DataResponse, UUID4) {
3924 let resp = make_quotes_response();
3925 let expected = resp.correlation_id;
3926 (DataResponse::Quotes(resp), expected)
3927 }
3928
3929 fn data_response_trades() -> (DataResponse, UUID4) {
3930 let resp = make_trades_response();
3931 let expected = resp.correlation_id;
3932 (DataResponse::Trades(resp), expected)
3933 }
3934
3935 fn data_response_funding_rates() -> (DataResponse, UUID4) {
3936 let resp = make_funding_rates_response();
3937 let expected = resp.correlation_id;
3938 (DataResponse::FundingRates(resp), expected)
3939 }
3940
3941 fn data_response_forward_prices() -> (DataResponse, UUID4) {
3942 let resp = make_forward_prices_response();
3943 let expected = resp.correlation_id;
3944 (DataResponse::ForwardPrices(resp), expected)
3945 }
3946
3947 fn data_response_bars() -> (DataResponse, UUID4) {
3948 let resp = make_bars_response();
3949 let expected = resp.correlation_id;
3950 (DataResponse::Bars(resp), expected)
3951 }
3952
3953 fn make_quotes_request() -> RequestQuotes {
3954 RequestQuotes {
3955 instrument_id: InstrumentId::from("EUR/USD.SIM"),
3956 start: None,
3957 end: None,
3958 limit: None,
3959 client_id: None,
3960 request_id: UUID4::new(),
3961 ts_init: UnixNanos::default(),
3962 params: None,
3963 }
3964 }
3965}