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