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