1use std::{any::Any, ops::Range, sync::Arc};
17
18use nautilus_core::{Params, UUID4, UnixNanos};
19use nautilus_model::{
20 data::{
21 Bar, BarType, BatchView, DataType, FundingRateUpdate, HasTsInit, OrderBookDelta,
22 OrderBookDepth, QuoteTick, TradeTick,
23 },
24 identifiers::{ClientId, InstrumentId, OptionSeriesId, Venue},
25 instruments::InstrumentAny,
26 orderbook::OrderBook,
27 types::Price,
28};
29use serde::{Deserialize, Serialize};
30
31use super::{
32 IntoInstrumentIds, Payload,
33 identifiers::{deserialize_instrument_ids, single_instrument_id},
34};
35
36pub(crate) fn trim_data_to_bounds<T: HasTsInit>(
38 data: &mut Vec<T>,
39 start: Option<UnixNanos>,
40 end: Option<UnixNanos>,
41) {
42 let range = range_for_bounds(data, start, end);
43 data.drain(..range.start);
44 data.truncate(range.len());
45}
46
47pub(crate) fn trim_batch_to_bounds<T: HasTsInit>(
48 data: &mut BatchView<T>,
49 start: Option<UnixNanos>,
50 end: Option<UnixNanos>,
51) {
52 let range = range_for_bounds(data, start, end);
53 if range != (0..data.len()) {
54 *data = data.slice(range.start, range.end);
55 }
56}
57
58fn range_for_bounds<T: HasTsInit>(
59 data: &[T],
60 start: Option<UnixNanos>,
61 end: Option<UnixNanos>,
62) -> Range<usize> {
63 let first = start.map_or(0, |start| {
64 data.iter()
65 .position(|item| item.ts_init() >= start)
66 .unwrap_or(data.len())
67 });
68
69 let end = end.map_or(data.len(), |end| {
70 data.iter()
71 .rposition(|item| item.ts_init() <= end)
72 .map_or(0, |index| index + 1)
73 });
74
75 first..end.max(first)
76}
77
78#[derive(Clone, Debug)]
79pub struct CustomDataResponse {
80 pub correlation_id: UUID4,
81 pub client_id: ClientId,
82 pub venue: Option<Venue>,
83 pub data_type: DataType,
84 pub data: Payload,
85 pub start: Option<UnixNanos>,
86 pub end: Option<UnixNanos>,
87 pub ts_init: UnixNanos,
88 pub params: Option<Params>,
89}
90
91impl CustomDataResponse {
92 #[expect(clippy::too_many_arguments)]
94 pub fn new<T: Any + Send + Sync>(
95 correlation_id: UUID4,
96 client_id: ClientId,
97 venue: Option<Venue>,
98 data_type: DataType,
99 data: T,
100 start: Option<UnixNanos>,
101 end: Option<UnixNanos>,
102 ts_init: UnixNanos,
103 params: Option<Params>,
104 ) -> Self {
105 Self {
106 correlation_id,
107 client_id,
108 venue,
109 data_type,
110 data: Arc::new(data),
111 start,
112 end,
113 ts_init,
114 params,
115 }
116 }
117
118 pub fn as_any(&self) -> &dyn Any {
120 self
121 }
122}
123
124#[derive(Clone, Debug, Serialize, Deserialize)]
125pub struct InstrumentResponse {
126 pub correlation_id: UUID4,
127 pub client_id: ClientId,
128 pub instrument_id: InstrumentId,
129 pub data: InstrumentAny,
130 pub start: Option<UnixNanos>,
131 pub end: Option<UnixNanos>,
132 pub ts_init: UnixNanos,
133 pub params: Option<Params>,
134}
135
136impl InstrumentResponse {
137 pub fn as_any(&self) -> &dyn Any {
139 self
140 }
141
142 #[expect(clippy::too_many_arguments)]
144 pub fn new(
145 correlation_id: UUID4,
146 client_id: ClientId,
147 instrument_id: InstrumentId,
148 data: InstrumentAny,
149 start: Option<UnixNanos>,
150 end: Option<UnixNanos>,
151 ts_init: UnixNanos,
152 params: Option<Params>,
153 ) -> Self {
154 Self {
155 correlation_id,
156 client_id,
157 instrument_id,
158 data,
159 start,
160 end,
161 ts_init,
162 params,
163 }
164 }
165}
166
167#[derive(Clone, Debug, Serialize, Deserialize)]
168pub struct InstrumentsResponse {
169 pub correlation_id: UUID4,
170 pub client_id: ClientId,
171 pub venue: Venue,
172 pub data: Vec<InstrumentAny>,
173 pub start: Option<UnixNanos>,
174 pub end: Option<UnixNanos>,
175 pub ts_init: UnixNanos,
176 pub params: Option<Params>,
177}
178
179impl InstrumentsResponse {
180 pub fn as_any(&self) -> &dyn Any {
182 self
183 }
184
185 #[expect(clippy::too_many_arguments)]
187 pub fn new(
188 correlation_id: UUID4,
189 client_id: ClientId,
190 venue: Venue,
191 data: Vec<InstrumentAny>,
192 start: Option<UnixNanos>,
193 end: Option<UnixNanos>,
194 ts_init: UnixNanos,
195 params: Option<Params>,
196 ) -> Self {
197 Self {
198 correlation_id,
199 client_id,
200 venue,
201 data,
202 start,
203 end,
204 ts_init,
205 params,
206 }
207 }
208}
209
210#[derive(Clone, Debug)]
211pub struct BookResponse {
212 pub correlation_id: UUID4,
213 pub client_id: ClientId,
214 pub instrument_id: InstrumentId,
215 pub data: OrderBook,
216 pub start: Option<UnixNanos>,
217 pub end: Option<UnixNanos>,
218 pub ts_init: UnixNanos,
219 pub params: Option<Params>,
220}
221
222impl BookResponse {
223 pub fn as_any(&self) -> &dyn Any {
225 self
226 }
227
228 #[expect(clippy::too_many_arguments)]
230 pub fn new(
231 correlation_id: UUID4,
232 client_id: ClientId,
233 instrument_id: InstrumentId,
234 data: OrderBook,
235 start: Option<UnixNanos>,
236 end: Option<UnixNanos>,
237 ts_init: UnixNanos,
238 params: Option<Params>,
239 ) -> Self {
240 Self {
241 correlation_id,
242 client_id,
243 instrument_id,
244 data,
245 start,
246 end,
247 ts_init,
248 params,
249 }
250 }
251}
252
253#[derive(Clone, Debug, Serialize, Deserialize)]
254pub struct BookDeltasResponse {
255 pub correlation_id: UUID4,
256 pub client_id: ClientId,
257 pub instrument_id: InstrumentId,
258 pub data: BatchView<OrderBookDelta>,
259 pub start: Option<UnixNanos>,
260 pub end: Option<UnixNanos>,
261 pub ts_init: UnixNanos,
262 pub params: Option<Params>,
263}
264
265impl BookDeltasResponse {
266 pub fn as_any(&self) -> &dyn Any {
268 self
269 }
270
271 #[expect(clippy::too_many_arguments)]
275 pub fn new(
276 correlation_id: UUID4,
277 client_id: ClientId,
278 instrument_id: InstrumentId,
279 data: impl Into<BatchView<OrderBookDelta>>,
280 start: Option<UnixNanos>,
281 end: Option<UnixNanos>,
282 ts_init: UnixNanos,
283 params: Option<Params>,
284 ) -> Self {
285 Self {
286 correlation_id,
287 client_id,
288 instrument_id,
289 data: data.into(),
290 start,
291 end,
292 ts_init,
293 params,
294 }
295 }
296}
297
298#[derive(Clone, Debug, Serialize, Deserialize)]
299pub struct BookDepthResponse {
300 pub correlation_id: UUID4,
301 pub client_id: ClientId,
302 pub instrument_id: InstrumentId,
303 pub data: BatchView<OrderBookDepth>,
304 pub start: Option<UnixNanos>,
305 pub end: Option<UnixNanos>,
306 pub ts_init: UnixNanos,
307 pub params: Option<Params>,
308}
309
310impl BookDepthResponse {
311 pub fn as_any(&self) -> &dyn Any {
313 self
314 }
315
316 #[expect(clippy::too_many_arguments)]
320 pub fn new(
321 correlation_id: UUID4,
322 client_id: ClientId,
323 instrument_id: InstrumentId,
324 data: impl Into<BatchView<OrderBookDepth>>,
325 start: Option<UnixNanos>,
326 end: Option<UnixNanos>,
327 ts_init: UnixNanos,
328 params: Option<Params>,
329 ) -> Self {
330 Self {
331 correlation_id,
332 client_id,
333 instrument_id,
334 data: data.into(),
335 start,
336 end,
337 ts_init,
338 params,
339 }
340 }
341}
342
343#[derive(Clone, Debug, Serialize, Deserialize)]
344pub struct QuotesResponse {
345 pub correlation_id: UUID4,
346 pub client_id: ClientId,
347 #[serde(deserialize_with = "deserialize_instrument_ids")]
348 pub instrument_ids: Vec<InstrumentId>,
349 pub data: BatchView<QuoteTick>,
350 pub start: Option<UnixNanos>,
351 pub end: Option<UnixNanos>,
352 pub ts_init: UnixNanos,
353 pub params: Option<Params>,
354}
355
356impl QuotesResponse {
357 pub fn as_any(&self) -> &dyn Any {
359 self
360 }
361
362 #[expect(clippy::too_many_arguments)]
370 pub fn new(
371 correlation_id: UUID4,
372 client_id: ClientId,
373 instrument_ids: impl IntoInstrumentIds,
374 data: impl Into<BatchView<QuoteTick>>,
375 start: Option<UnixNanos>,
376 end: Option<UnixNanos>,
377 ts_init: UnixNanos,
378 params: Option<Params>,
379 ) -> Self {
380 let instrument_ids = instrument_ids
381 .into_instrument_ids()
382 .expect("invalid instrument ID list");
383
384 Self {
385 correlation_id,
386 client_id,
387 instrument_ids,
388 data: data.into(),
389 start,
390 end,
391 ts_init,
392 params,
393 }
394 }
395
396 pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
402 single_instrument_id(&self.instrument_ids, "response")
403 }
404}
405
406#[derive(Clone, Debug, Serialize, Deserialize)]
407pub struct TradesResponse {
408 pub correlation_id: UUID4,
409 pub client_id: ClientId,
410 #[serde(deserialize_with = "deserialize_instrument_ids")]
411 pub instrument_ids: Vec<InstrumentId>,
412 pub data: BatchView<TradeTick>,
413 pub start: Option<UnixNanos>,
414 pub end: Option<UnixNanos>,
415 pub ts_init: UnixNanos,
416 pub params: Option<Params>,
417}
418
419impl TradesResponse {
420 pub fn as_any(&self) -> &dyn Any {
422 self
423 }
424
425 #[expect(clippy::too_many_arguments)]
433 pub fn new(
434 correlation_id: UUID4,
435 client_id: ClientId,
436 instrument_ids: impl IntoInstrumentIds,
437 data: impl Into<BatchView<TradeTick>>,
438 start: Option<UnixNanos>,
439 end: Option<UnixNanos>,
440 ts_init: UnixNanos,
441 params: Option<Params>,
442 ) -> Self {
443 let instrument_ids = instrument_ids
444 .into_instrument_ids()
445 .expect("invalid instrument ID list");
446
447 Self {
448 correlation_id,
449 client_id,
450 instrument_ids,
451 data: data.into(),
452 start,
453 end,
454 ts_init,
455 params,
456 }
457 }
458
459 pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
465 single_instrument_id(&self.instrument_ids, "response")
466 }
467}
468
469#[derive(Clone, Debug, Serialize, Deserialize)]
470pub struct FundingRatesResponse {
471 pub correlation_id: UUID4,
472 pub client_id: ClientId,
473 pub instrument_id: InstrumentId,
474 pub data: BatchView<FundingRateUpdate>,
475 pub start: Option<UnixNanos>,
476 pub end: Option<UnixNanos>,
477 pub ts_init: UnixNanos,
478 pub params: Option<Params>,
479}
480
481impl FundingRatesResponse {
482 pub fn as_any(&self) -> &dyn Any {
484 self
485 }
486
487 #[expect(clippy::too_many_arguments)]
491 pub fn new(
492 correlation_id: UUID4,
493 client_id: ClientId,
494 instrument_id: InstrumentId,
495 data: impl Into<BatchView<FundingRateUpdate>>,
496 start: Option<UnixNanos>,
497 end: Option<UnixNanos>,
498 ts_init: UnixNanos,
499 params: Option<Params>,
500 ) -> Self {
501 Self {
502 correlation_id,
503 client_id,
504 instrument_id,
505 data: data.into(),
506 start,
507 end,
508 ts_init,
509 params,
510 }
511 }
512}
513
514#[derive(Clone, Debug, Serialize, Deserialize)]
515pub struct OptionChainReferencePriceResponse {
516 pub correlation_id: UUID4,
517 pub client_id: ClientId,
518 pub series_id: OptionSeriesId,
519 pub price: Option<Price>,
520 pub ts_init: UnixNanos,
521 pub params: Option<Params>,
522}
523
524impl OptionChainReferencePriceResponse {
525 pub fn new(
527 correlation_id: UUID4,
528 client_id: ClientId,
529 series_id: OptionSeriesId,
530 price: Option<Price>,
531 ts_init: UnixNanos,
532 params: Option<Params>,
533 ) -> Self {
534 Self {
535 correlation_id,
536 client_id,
537 series_id,
538 price,
539 ts_init,
540 params,
541 }
542 }
543}
544
545#[derive(Clone, Debug, Serialize, Deserialize)]
546pub struct BarsResponse {
547 pub correlation_id: UUID4,
548 pub client_id: ClientId,
549 pub bar_type: BarType,
550 pub data: BatchView<Bar>,
551 pub ts_init: UnixNanos,
552 pub start: Option<UnixNanos>,
553 pub end: Option<UnixNanos>,
554 pub params: Option<Params>,
555}
556
557impl BarsResponse {
558 pub fn as_any(&self) -> &dyn Any {
560 self
561 }
562
563 #[expect(clippy::too_many_arguments)]
567 pub fn new(
568 correlation_id: UUID4,
569 client_id: ClientId,
570 bar_type: BarType,
571 data: impl Into<BatchView<Bar>>,
572 start: Option<UnixNanos>,
573 end: Option<UnixNanos>,
574 ts_init: UnixNanos,
575 params: Option<Params>,
576 ) -> Self {
577 Self {
578 correlation_id,
579 client_id,
580 bar_type,
581 data: data.into(),
582 ts_init,
583 start,
584 end,
585 params,
586 }
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use std::sync::Arc;
593
594 use nautilus_model::data::stubs::quote_audusd;
595 use rstest::{fixture, rstest};
596
597 use super::*;
598 use crate::messages::data::DataResponse;
599
600 #[fixture]
601 fn quote_rows() -> Vec<QuoteTick> {
602 let mut first = quote_audusd();
603 first.ts_event = UnixNanos::from(7);
604 first.ts_init = UnixNanos::from(11);
605 let mut second = first;
606 second.ts_event = UnixNanos::from(17);
607 second.ts_init = UnixNanos::from(23);
608 vec![first, second]
609 }
610
611 #[rstest]
612 fn test_quote_response_shares_supplied_buffer(quote_rows: Vec<QuoteTick>) {
613 let instrument_id = quote_rows[0].instrument_id;
614 let rows = Arc::new(quote_rows);
615 let request_id = UUID4::new();
616 let client_id = ClientId::from("HISTORY");
617 let start = Some(UnixNanos::from(5));
618 let end = Some(UnixNanos::from(29));
619 let ts_init = UnixNanos::from(31);
620 let params = Some(Params::new([("source".into(), "history".into())].into()));
621
622 let response = QuotesResponse::new(
623 request_id,
624 client_id,
625 instrument_id,
626 Arc::clone(&rows),
627 start,
628 end,
629 ts_init,
630 params.clone(),
631 );
632 let serialized = serde_json::to_value(&response).unwrap();
633
634 assert_eq!(response.correlation_id, request_id);
635 assert_eq!(response.client_id, client_id);
636 assert_eq!(response.instrument_ids, vec![instrument_id]);
637 assert_eq!(response.data.as_ref(), rows.as_slice());
638 assert!(Arc::ptr_eq(response.data.arc(), &rows));
639 assert_eq!(response.start, start);
640 assert_eq!(response.end, end);
641 assert_eq!(response.ts_init, ts_init);
642 assert_eq!(response.params, params);
643 assert_eq!(
644 serialized["data"],
645 serde_json::to_value(rows.as_ref()).unwrap()
646 );
647 }
648
649 #[rstest]
650 fn test_quote_response_vector_constructor_keeps_row_allocation(quote_rows: Vec<QuoteTick>) {
651 let instrument_id = quote_rows[0].instrument_id;
652 let rows = quote_rows.clone();
653 let row_ptr = rows.as_ptr();
654
655 let response = QuotesResponse::new(
656 UUID4::new(),
657 ClientId::from("HISTORY"),
658 instrument_id,
659 rows,
660 None,
661 None,
662 UnixNanos::from(31),
663 None,
664 );
665
666 assert_eq!(response.data.as_ref(), "e_rows);
667 assert_eq!(response.data.as_ptr(), row_ptr);
668 }
669
670 #[rstest]
671 #[case::unbounded(None, None)]
672 #[case::exact(Some(11), Some(23))]
673 #[case::wider(Some(5), Some(29))]
674 fn test_shared_response_trim_keeps_existing_allocation(
675 quote_rows: Vec<QuoteTick>,
676 #[case] start: Option<u64>,
677 #[case] end: Option<u64>,
678 ) {
679 let instrument_id = quote_rows[0].instrument_id;
680 let rows = Arc::new(quote_rows);
681 let mut response = DataResponse::Quotes(QuotesResponse::new(
682 UUID4::new(),
683 ClientId::from("HISTORY"),
684 instrument_id,
685 Arc::clone(&rows),
686 start.map(UnixNanos::from),
687 end.map(UnixNanos::from),
688 UnixNanos::from(31),
689 None,
690 ));
691 response.trim_to_bounds();
692
693 let DataResponse::Quotes(response) = response else {
694 unreachable!()
695 };
696
697 assert_eq!(response.data.as_ref(), rows.as_slice());
698 assert!(Arc::ptr_eq(response.data.arc(), &rows));
699 }
700
701 #[rstest]
702 fn test_shared_response_trim_preserves_other_consumers(quote_rows: Vec<QuoteTick>) {
703 let instrument_id = quote_rows[0].instrument_id;
704 let rows = Arc::new(quote_rows.clone());
705 let mut response = DataResponse::Quotes(QuotesResponse::new(
706 UUID4::new(),
707 ClientId::from("HISTORY"),
708 instrument_id,
709 Arc::clone(&rows),
710 Some(UnixNanos::from(23)),
711 Some(UnixNanos::from(23)),
712 UnixNanos::from(31),
713 None,
714 ));
715 response.trim_to_bounds();
716
717 let DataResponse::Quotes(response) = response else {
718 unreachable!()
719 };
720
721 assert_eq!(rows.as_ref(), "e_rows);
722 assert_eq!(response.data.as_ref(), &vec![quote_rows[1]]);
723 assert!(Arc::ptr_eq(response.data.arc(), &rows));
724 assert_eq!(response.data.range(), 1..2);
725 }
726
727 #[rstest]
728 #[case::unbounded(vec![11, 23], None, None, 0..2)]
729 #[case::inclusive(vec![11, 23], Some(11), Some(23), 0..2)]
730 #[case::first(vec![11, 23], None, Some(11), 0..1)]
731 #[case::last(vec![11, 23], Some(23), None, 1..2)]
732 #[case::after(vec![11, 23], Some(29), None, 2..2)]
733 #[case::before(vec![11, 23], None, Some(5), 0..0)]
734 #[case::reversed(vec![11, 23], Some(23), Some(11), 1..1)]
735 #[case::unsorted(vec![11, 37, 23], Some(11), Some(23), 0..3)]
736 #[case::empty(Vec::new(), Some(11), Some(23), 0..0)]
737 fn test_response_trim_preserves_narrowed_backing(
738 #[case] timestamps: Vec<u64>,
739 #[case] start: Option<u64>,
740 #[case] end: Option<u64>,
741 #[case] range: Range<usize>,
742 ) {
743 let visible: Vec<_> = timestamps
744 .into_iter()
745 .map(|ts| QuoteTick {
746 ts_init: UnixNanos::from(ts),
747 ..Default::default()
748 })
749 .collect();
750
751 let mut rows = vec![QuoteTick::default()];
752 rows.extend_from_slice(&visible);
753 rows.push(QuoteTick {
754 ts_init: UnixNanos::from(9999),
755 ..Default::default()
756 });
757
758 let rows = Arc::new(rows);
759 let mut view = BatchView::new(Arc::clone(&rows), 1..visible.len() + 1);
760
761 trim_batch_to_bounds(
762 &mut view,
763 start.map(UnixNanos::from),
764 end.map(UnixNanos::from),
765 );
766
767 assert_eq!(view.as_ref(), &visible[range.clone()]);
768 assert_eq!(view.range(), (1 + range.start)..(1 + range.end));
769 assert!(Arc::ptr_eq(view.arc(), &rows));
770 assert_eq!(&rows[1..rows.len() - 1], visible.as_slice());
771 }
772}