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