1pub mod bar;
23pub mod batch;
24pub mod bet;
25pub mod black_scholes;
26pub mod close;
27pub mod custom;
28pub mod data_type;
29pub mod delta;
30pub mod deltas;
31pub mod depth;
32pub mod funding;
33pub mod greeks;
34pub mod option_chain;
35pub mod order;
36pub mod prices;
37pub mod quote;
38pub mod registry;
39pub mod status;
40pub mod trade;
41
42#[cfg(any(test, feature = "test-support"))]
43pub mod stubs;
44
45use std::{
46 fmt::{Debug, Display},
47 ops::{Deref, Range},
48 str::FromStr,
49 sync::Arc,
50};
51
52use nautilus_core::UnixNanos;
53use serde::{Deserialize, Serialize};
54
55#[cfg(feature = "defi")]
56use crate::defi::DefiData;
57use crate::{
58 for_each_data_type,
59 identifiers::InstrumentId,
60 instruments::{Instrument, InstrumentAny},
61};
62
63#[rustfmt::skip] pub use bar::{Bar, BarSpecification, BarType};
66pub use black_scholes::Greeks;
67pub use close::InstrumentClose;
68#[cfg(feature = "python")]
69pub use custom::PythonCustomDataWrapper;
70pub use custom::{
71 CustomData, CustomDataTrait, ensure_custom_data_json_registered, register_custom_data_json,
72};
73#[cfg(feature = "python")]
74pub use custom::{
75 get_python_data_class, reconstruct_python_custom_data, register_python_data_class,
76};
77pub use data_type::DataType;
78pub use delta::OrderBookDelta;
79pub use deltas::OrderBookDeltas;
80pub use depth::{DEPTH_INLINE_LEN, DEPTH10_LEN, OrderBookDepth};
81pub use funding::FundingRateUpdate;
82pub use greeks::{
83 BlackScholesGreeksResult, GreeksData, HasGreeks, OptionGreekValues, PortfolioGreeks,
84 YieldCurveData, black_scholes_greeks, imply_vol_and_greeks, refine_vol_and_greeks,
85};
86pub use option_chain::{OptionChainSlice, OptionGreeks, OptionStrikeData, StrikeRange};
87pub use order::{BookOrder, NULL_ORDER};
88pub use prices::{IndexPriceUpdate, MarkPriceUpdate};
89pub use quote::QuoteTick;
90#[cfg(feature = "arrow")]
91pub use registry::{
92 ArrowDecoder, ArrowEncoder, decode_custom_from_arrow, encode_custom_to_arrow,
93 ensure_arrow_registered, get_arrow_schema, register_arrow, validate_custom_arrow_schema,
94};
95#[cfg(feature = "python")]
96pub use registry::{
97 PyExtractor, ensure_py_extractor_registered, ensure_rust_extractor_factory_registered,
98 ensure_rust_extractor_registered, get_rust_extractor, register_py_extractor,
99 register_rust_extractor, register_rust_extractor_factory, try_extract_from_py,
100};
101pub use registry::{
102 deserialize_custom_from_json, ensure_json_deserializer_registered, register_json_deserializer,
103};
104pub use status::InstrumentStatus;
105pub use trade::TradeTick;
106
107pub const ARROW_ENUM_DICTIONARY: &str = "Dictionary(Int8, Utf8)";
109pub const ARROW_TIMESTAMP_NANOSECOND: &str = "Timestamp(Nanosecond, Some(\"UTC\"))";
111
112#[derive(Debug)]
117pub enum Data {
118 Custom(CustomData),
119 Instrument(Box<InstrumentAny>),
120 BookDelta(OrderBookDelta),
121 BookDeltas(Box<OrderBookDeltas>),
122 BookDepth(Box<OrderBookDepth>), Quote(QuoteTick),
124 Trade(TradeTick),
125 Bar(Bar),
126 MarkPrice(MarkPriceUpdate),
127 IndexPrice(IndexPriceUpdate),
128 FundingRate(FundingRateUpdate),
129 OptionGreeks(OptionGreeks),
130 InstrumentStatus(InstrumentStatus),
131 InstrumentClose(InstrumentClose),
132 #[cfg(feature = "defi")]
133 Defi(Box<DefiData>), }
135
136#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
141pub enum NautilusDataType {
142 Custom { type_name: String },
144 Instrument,
146 OrderBookDelta,
148 OrderBookDepth,
150 QuoteTick,
152 TradeTick,
154 Bar,
156 MarkPriceUpdate,
158 IndexPriceUpdate,
160 FundingRateUpdate,
162 OptionGreeks,
164 InstrumentStatus,
166 InstrumentClose,
168 #[cfg(feature = "defi")]
170 Defi,
171}
172
173impl Display for NautilusDataType {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 match self {
176 Self::Custom { type_name } => write!(f, "Custom:{type_name}"),
177 Self::Instrument => f.write_str("Instrument"),
178 Self::OrderBookDelta => f.write_str("OrderBookDelta"),
179 Self::OrderBookDepth => f.write_str("OrderBookDepth"),
180 Self::QuoteTick => f.write_str("QuoteTick"),
181 Self::TradeTick => f.write_str("TradeTick"),
182 Self::Bar => f.write_str("Bar"),
183 Self::MarkPriceUpdate => f.write_str("MarkPriceUpdate"),
184 Self::IndexPriceUpdate => f.write_str("IndexPriceUpdate"),
185 Self::FundingRateUpdate => f.write_str("FundingRateUpdate"),
186 Self::OptionGreeks => f.write_str("OptionGreeks"),
187 Self::InstrumentStatus => f.write_str("InstrumentStatus"),
188 Self::InstrumentClose => f.write_str("InstrumentClose"),
189 #[cfg(feature = "defi")]
190 Self::Defi => f.write_str("Defi"),
191 }
192 }
193}
194
195impl FromStr for NautilusDataType {
196 type Err = anyhow::Error;
197
198 fn from_str(s: &str) -> anyhow::Result<Self> {
199 match s {
200 custom if custom.starts_with("Custom:") => Ok(Self::Custom {
201 type_name: custom.trim_start_matches("Custom:").to_string(),
202 }),
203 "Instrument" | "instruments" | "instrument" => Ok(Self::Instrument),
204 "QuoteTick" | "quotes" | "quote" | "quote_tick" => Ok(Self::QuoteTick),
205 "TradeTick" | "trades" | "trade" | "trade_tick" => Ok(Self::TradeTick),
206 "Bar" | "bars" | "bar" => Ok(Self::Bar),
207 "OrderBookDelta" | "OrderBookDeltas" | "order_book_deltas" | "order_book_delta" => {
208 Ok(Self::OrderBookDelta)
209 }
210 "OrderBookDepth" | "order_book_depths" => Ok(Self::OrderBookDepth),
211 "MarkPriceUpdate" | "mark_price_updates" | "mark_prices" | "mark_price_update" => {
212 Ok(Self::MarkPriceUpdate)
213 }
214 "IndexPriceUpdate" | "index_price_updates" | "index_prices" | "index_price_update" => {
215 Ok(Self::IndexPriceUpdate)
216 }
217 "FundingRateUpdate" | "funding_rate_update" | "funding_rates" => {
218 Ok(Self::FundingRateUpdate)
219 }
220 "InstrumentStatus" | "instrument_status" => Ok(Self::InstrumentStatus),
221 "OptionGreeks" | "option_greeks" => Ok(Self::OptionGreeks),
222 "InstrumentClose" | "instrument_closes" | "instrument_close" => {
223 Ok(Self::InstrumentClose)
224 }
225 #[cfg(feature = "defi")]
226 "Defi" => Ok(Self::Defi),
227 _ => anyhow::bail!("Invalid `NautilusDataType`: '{s}'"),
228 }
229 }
230}
231
232impl NautilusDataType {
233 #[must_use]
235 pub fn from_data(data: &Data) -> Self {
236 match data {
237 Data::Custom(c) => Self::Custom {
238 type_name: c.data_type.type_name().to_string(),
239 },
240 Data::Instrument(_) => Self::Instrument,
241 Data::Quote(_) => Self::QuoteTick,
242 Data::Trade(_) => Self::TradeTick,
243 Data::Bar(_) => Self::Bar,
244 Data::BookDelta(_) | Data::BookDeltas(_) => Self::OrderBookDelta,
245 Data::BookDepth(_) => Self::OrderBookDepth,
246 Data::MarkPrice(_) => Self::MarkPriceUpdate,
247 Data::IndexPrice(_) => Self::IndexPriceUpdate,
248 Data::FundingRate(_) => Self::FundingRateUpdate,
249 Data::InstrumentStatus(_) => Self::InstrumentStatus,
250 Data::OptionGreeks(_) => Self::OptionGreeks,
251 Data::InstrumentClose(_) => Self::InstrumentClose,
252 #[cfg(feature = "defi")]
253 Data::Defi(_) => Self::Defi,
254 }
255 }
256}
257
258#[derive(Clone, Copy, Debug)]
260pub enum DataRef<'a> {
261 Instrument(&'a InstrumentAny),
262 BookDelta(&'a OrderBookDelta),
263 BookDeltas(&'a OrderBookDeltas),
264 BookDepth(&'a OrderBookDepth),
265 Quote(&'a QuoteTick),
266 Trade(&'a TradeTick),
267 Bar(&'a Bar),
268 MarkPrice(&'a MarkPriceUpdate),
269 IndexPrice(&'a IndexPriceUpdate),
270 FundingRate(&'a FundingRateUpdate),
271 OptionGreeks(&'a OptionGreeks),
272 InstrumentStatus(&'a InstrumentStatus),
273 InstrumentClose(&'a InstrumentClose),
274 Custom(&'a CustomData),
275 #[cfg(feature = "defi")]
276 Defi(&'a DefiData),
277}
278
279impl<'a> From<&'a Data> for DataRef<'a> {
280 fn from(data: &'a Data) -> Self {
281 match data {
282 Data::Custom(custom) => Self::Custom(custom),
283 Data::Instrument(instrument) => Self::Instrument(instrument),
284 Data::BookDelta(delta) => Self::BookDelta(delta),
285 Data::BookDeltas(deltas) => Self::BookDeltas(deltas),
286 Data::BookDepth(depth) => Self::BookDepth(depth),
287 Data::Quote(quote) => Self::Quote(quote),
288 Data::Trade(trade) => Self::Trade(trade),
289 Data::Bar(bar) => Self::Bar(bar),
290 Data::MarkPrice(mark_price) => Self::MarkPrice(mark_price),
291 Data::IndexPrice(index_price) => Self::IndexPrice(index_price),
292 Data::FundingRate(funding_rate) => Self::FundingRate(funding_rate),
293 Data::OptionGreeks(greeks) => Self::OptionGreeks(greeks),
294 Data::InstrumentStatus(status) => Self::InstrumentStatus(status),
295 Data::InstrumentClose(close) => Self::InstrumentClose(close),
296 #[cfg(feature = "defi")]
297 Data::Defi(defi) => Self::Defi(defi),
298 }
299 }
300}
301
302impl DataRef<'_> {
303 #[must_use]
305 pub fn instrument_id(&self) -> InstrumentId {
306 match self {
307 Self::Custom(custom) => custom
308 .data_type
309 .identifier()
310 .and_then(|s| InstrumentId::from_str(s).ok())
311 .or_else(|| {
312 custom
313 .data_type
314 .metadata()
315 .and_then(|m| m.get_str("instrument_id"))
316 .and_then(|s| InstrumentId::from_str(s).ok())
317 })
318 .unwrap_or_else(|| InstrumentId::from("NULL.NULL")),
319 Self::Instrument(instrument) => instrument.id(),
320 Self::BookDelta(delta) => delta.instrument_id,
321 Self::BookDeltas(deltas) => deltas.instrument_id,
322 Self::BookDepth(depth) => depth.instrument_id,
323 Self::Quote(quote) => quote.instrument_id,
324 Self::Trade(trade) => trade.instrument_id,
325 Self::Bar(bar) => bar.bar_type.instrument_id(),
326 Self::MarkPrice(mark_price) => mark_price.instrument_id,
327 Self::IndexPrice(index_price) => index_price.instrument_id,
328 Self::FundingRate(funding_rate) => funding_rate.instrument_id,
329 Self::OptionGreeks(greeks) => greeks.instrument_id,
330 Self::InstrumentStatus(status) => status.instrument_id,
331 Self::InstrumentClose(close) => close.instrument_id,
332 #[cfg(feature = "defi")]
333 Self::Defi(defi) => defi.instrument_id(),
334 }
335 }
336
337 #[must_use]
339 pub fn is_order_book_data(&self) -> bool {
340 matches!(
341 self,
342 Self::BookDelta(_) | Self::BookDeltas(_) | Self::BookDepth(_)
343 )
344 }
345
346 #[must_use]
348 pub fn to_owned_data(&self) -> Data {
349 match self {
350 Self::Custom(custom) => Data::Custom((**custom).clone()),
351 Self::Instrument(instrument) => Data::Instrument(Box::new((*instrument).clone())),
352 Self::BookDelta(delta) => Data::BookDelta(**delta),
353 Self::BookDeltas(deltas) => Data::BookDeltas(Box::new((**deltas).clone())),
354 Self::BookDepth(depth) => Data::BookDepth(Box::new((*depth).clone())),
355 Self::Quote(quote) => Data::Quote(**quote),
356 Self::Trade(trade) => Data::Trade(**trade),
357 Self::Bar(bar) => Data::Bar(**bar),
358 Self::MarkPrice(mark_price) => Data::MarkPrice(**mark_price),
359 Self::IndexPrice(index_price) => Data::IndexPrice(**index_price),
360 Self::FundingRate(funding_rate) => Data::FundingRate(**funding_rate),
361 Self::OptionGreeks(greeks) => Data::OptionGreeks(**greeks),
362 Self::InstrumentStatus(status) => Data::InstrumentStatus(**status),
363 Self::InstrumentClose(close) => Data::InstrumentClose(**close),
364 #[cfg(feature = "defi")]
365 Self::Defi(defi) => Data::Defi(Box::new((**defi).clone())),
366 }
367 }
368}
369
370impl HasTsInit for DataRef<'_> {
371 fn ts_init(&self) -> UnixNanos {
372 match self {
373 Self::Custom(custom) => custom.data.ts_init(),
374 Self::Instrument(instrument) => Instrument::ts_init(*instrument),
375 Self::BookDelta(delta) => delta.ts_init,
376 Self::BookDeltas(deltas) => deltas.ts_init,
377 Self::BookDepth(depth) => depth.ts_init,
378 Self::Quote(quote) => quote.ts_init,
379 Self::Trade(trade) => trade.ts_init,
380 Self::Bar(bar) => bar.ts_init,
381 Self::MarkPrice(mark_price) => mark_price.ts_init,
382 Self::IndexPrice(index_price) => index_price.ts_init,
383 Self::FundingRate(funding_rate) => funding_rate.ts_init,
384 Self::OptionGreeks(greeks) => greeks.ts_init,
385 Self::InstrumentStatus(status) => status.ts_init,
386 Self::InstrumentClose(close) => close.ts_init,
387 #[cfg(feature = "defi")]
388 Self::Defi(defi) => defi.ts_init(),
389 }
390 }
391}
392
393#[derive(Clone, Debug)]
395#[expect(
396 clippy::rc_buffer,
397 reason = "Backtest batch views share full Vec batches by design"
398)]
399pub struct BatchView<T> {
400 data: Arc<Vec<T>>,
401 range: Range<usize>,
402}
403
404impl<T> BatchView<T> {
405 #[must_use]
411 pub fn new(data: Arc<Vec<T>>, range: Range<usize>) -> Self {
412 assert!(range.start <= range.end, "invalid batch view range");
413 assert!(
414 range.end <= data.len(),
415 "batch view range exceeds data length"
416 );
417 Self { data, range }
418 }
419
420 #[must_use]
421 pub fn full(data: Arc<Vec<T>>) -> Self {
422 let len = data.len();
423 Self {
424 data,
425 range: 0..len,
426 }
427 }
428
429 #[must_use]
430 pub fn arc(&self) -> &Arc<Vec<T>> {
431 &self.data
432 }
433
434 #[must_use]
435 pub fn range(&self) -> Range<usize> {
436 self.range.clone()
437 }
438
439 #[must_use]
445 pub fn slice(&self, start: usize, end: usize) -> Self {
446 assert!(start <= end, "invalid batch slice range");
447 assert!(end <= self.len(), "batch slice range exceeds view length");
448 Self {
449 data: self.data.clone(),
450 range: (self.range.start + start)..(self.range.start + end),
451 }
452 }
453 pub fn make_mut(&mut self) -> &mut [T]
455 where
456 T: Clone,
457 {
458 &mut Arc::make_mut(&mut self.data)[self.range.clone()]
459 }
460}
461
462impl<T> From<Vec<T>> for BatchView<T> {
463 fn from(data: Vec<T>) -> Self {
464 Self::full(Arc::new(data))
465 }
466}
467
468impl<T> From<Arc<Vec<T>>> for BatchView<T> {
469 fn from(data: Arc<Vec<T>>) -> Self {
470 Self::full(data)
471 }
472}
473
474impl<T> AsRef<[T]> for BatchView<T> {
475 fn as_ref(&self) -> &[T] {
476 self
477 }
478}
479
480impl<T> Deref for BatchView<T> {
481 type Target = [T];
482
483 fn deref(&self) -> &Self::Target {
484 &self.data[self.range.clone()]
485 }
486}
487
488#[derive(Clone, Debug)]
490pub enum DataBatch {
491 Instrument(BatchView<InstrumentAny>),
492 BookDelta(BatchView<OrderBookDelta>),
493 BookDeltas(BatchView<OrderBookDeltas>),
494 BookDepth(BatchView<OrderBookDepth>),
495 Quote(BatchView<QuoteTick>),
496 Trade(BatchView<TradeTick>),
497 Bar(BatchView<Bar>),
498 MarkPrice(BatchView<MarkPriceUpdate>),
499 IndexPrice(BatchView<IndexPriceUpdate>),
500 FundingRate(BatchView<FundingRateUpdate>),
501 OptionGreeks(BatchView<OptionGreeks>),
502 InstrumentStatus(BatchView<InstrumentStatus>),
503 InstrumentClose(BatchView<InstrumentClose>),
504 Custom(BatchView<CustomData>),
505 #[cfg(feature = "defi")]
506 Defi(BatchView<DefiData>),
507}
508
509macro_rules! data_batch_from_data_vec {
510 (
511 ($data_type:ident, $input:ident);
512 $(($variant:ident, $type:ident, $data:ident, $batch:ident, $prefix:literal)),+ $(,)?
513 ) => {
514 match $data_type {
515 NautilusDataType::Custom { .. } => {
516 let expected_len = $input.len();
517 let custom = $input
518 .into_iter()
519 .filter_map(|item| match item {
520 Data::Custom(custom) => Some(custom),
521 _ => None,
522 })
523 .collect::<Vec<_>>();
524 anyhow::ensure!(
525 custom.len() == expected_len,
526 "catalog query for {} returned rows with another data type",
527 $data_type,
528 );
529 Ok(Self::Custom(custom.into()))
530 }
531 $(
532 NautilusDataType::$variant => Ok(Self::$batch(
533 to_variant_for_batch::<$type>($data_type, $input)?.into(),
534 )),
535 )+
536 #[cfg(feature = "defi")]
537 NautilusDataType::Defi => {
538 let expected_len = $input.len();
539 let defi = $input
540 .into_iter()
541 .filter_map(|item| match item {
542 Data::Defi(defi) => Some(*defi),
543 _ => None,
544 })
545 .collect::<Vec<_>>();
546 anyhow::ensure!(
547 defi.len() == expected_len,
548 "catalog query for {} returned rows with another data type",
549 $data_type,
550 );
551 Ok(Self::Defi(defi.into()))
552 }
553 }
554 };
555}
556
557impl DataBatch {
558 pub fn from_data_vec_for_type(
567 data_type: &NautilusDataType,
568 data: Vec<Data>,
569 ) -> anyhow::Result<Self> {
570 if matches!(data_type, NautilusDataType::OrderBookDelta)
571 && matches!(data.first(), Some(Data::BookDeltas(_)))
572 {
573 return Ok(Self::BookDeltas(
574 to_variant_for_batch::<OrderBookDeltas>(data_type, data)?.into(),
575 ));
576 }
577
578 crate::for_each_data_type!(data_batch_from_data_vec, data_type, data)
579 }
580
581 pub fn from_data_vec_grouped(data: &[Data]) -> anyhow::Result<Vec<Self>> {
589 let mut groups = Vec::<(NautilusDataType, Vec<Data>)>::new();
590
591 for item in data.iter().cloned() {
592 let data_type = NautilusDataType::from_data(&item);
593 let is_deltas = matches!(item, Data::BookDeltas(_));
594
595 if let Some((_, group)) = groups.iter_mut().find(|(group_type, group)| {
596 group_type == &data_type
597 && group
598 .first()
599 .is_some_and(|item| matches!(item, Data::BookDeltas(_)) == is_deltas)
600 }) {
601 group.push(item);
602 } else {
603 groups.push((data_type, vec![item]));
604 }
605 }
606
607 groups
608 .into_iter()
609 .map(|(data_type, data)| Self::from_data_vec_for_type(&data_type, data))
610 .collect()
611 }
612
613 #[must_use]
614 pub fn len(&self) -> usize {
615 match self {
616 Self::Custom(data) => data.len(),
617 Self::Instrument(data) => data.len(),
618 Self::BookDelta(data) => data.len(),
619 Self::BookDeltas(data) => data.len(),
620 Self::BookDepth(data) => data.len(),
621 Self::Quote(data) => data.len(),
622 Self::Trade(data) => data.len(),
623 Self::Bar(data) => data.len(),
624 Self::MarkPrice(data) => data.len(),
625 Self::IndexPrice(data) => data.len(),
626 Self::FundingRate(data) => data.len(),
627 Self::OptionGreeks(data) => data.len(),
628 Self::InstrumentStatus(data) => data.len(),
629 Self::InstrumentClose(data) => data.len(),
630 #[cfg(feature = "defi")]
631 Self::Defi(data) => data.len(),
632 }
633 }
634
635 #[must_use]
636 pub fn is_empty(&self) -> bool {
637 self.len() == 0
638 }
639
640 #[must_use]
642 pub fn data_type_name(&self) -> &'static str {
643 match self {
644 Self::Custom(_) => "custom",
645 Self::Instrument(_) => "instruments",
646 Self::BookDelta(_) => "order_book_deltas",
647 Self::BookDeltas(_) => "order_book_deltas_batches",
648 Self::BookDepth(_) => "order_book_depths",
649 Self::Quote(_) => "quotes",
650 Self::Trade(_) => "trades",
651 Self::Bar(_) => "bars",
652 Self::MarkPrice(_) => "mark_prices",
653 Self::IndexPrice(_) => "index_prices",
654 Self::FundingRate(_) => "funding_rates",
655 Self::OptionGreeks(_) => "option_greeks",
656 Self::InstrumentStatus(_) => "instrument_status",
657 Self::InstrumentClose(_) => "instrument_closes",
658 #[cfg(feature = "defi")]
659 Self::Defi(_) => "defi",
660 }
661 }
662
663 #[must_use]
664 pub fn is_monotonically_increasing_by_init(&self) -> bool {
665 (0..self.len())
666 .filter_map(|index| self.get(index))
667 .map(|data| data.ts_init())
668 .is_sorted()
669 }
670
671 #[must_use]
672 pub fn get(&self, index: usize) -> Option<DataRef<'_>> {
673 match self {
674 Self::Custom(data) => data.get(index).map(DataRef::Custom),
675 Self::Instrument(data) => data.get(index).map(DataRef::Instrument),
676 Self::BookDelta(data) => data.get(index).map(DataRef::BookDelta),
677 Self::BookDeltas(data) => data.get(index).map(DataRef::BookDeltas),
678 Self::BookDepth(data) => data.get(index).map(DataRef::BookDepth),
679 Self::Quote(data) => data.get(index).map(DataRef::Quote),
680 Self::Trade(data) => data.get(index).map(DataRef::Trade),
681 Self::Bar(data) => data.get(index).map(DataRef::Bar),
682 Self::MarkPrice(data) => data.get(index).map(DataRef::MarkPrice),
683 Self::IndexPrice(data) => data.get(index).map(DataRef::IndexPrice),
684 Self::FundingRate(data) => data.get(index).map(DataRef::FundingRate),
685 Self::OptionGreeks(data) => data.get(index).map(DataRef::OptionGreeks),
686 Self::InstrumentStatus(data) => data.get(index).map(DataRef::InstrumentStatus),
687 Self::InstrumentClose(data) => data.get(index).map(DataRef::InstrumentClose),
688 #[cfg(feature = "defi")]
689 Self::Defi(data) => data.get(index).map(DataRef::Defi),
690 }
691 }
692
693 #[must_use]
694 pub fn aligned_chunk(&self, start: usize, chunk_size: Option<usize>) -> Option<(Self, usize)> {
695 let len = self.len();
696 if start >= len {
697 return None;
698 }
699
700 let mut end = match chunk_size {
701 Some(size) => len.min(start + size.max(1)),
702 None => len,
703 };
704
705 if end < len
706 && let Some(boundary_ts) = self.get(end - 1).map(|data| data.ts_init())
707 {
708 while end < len
709 && self
710 .get(end)
711 .is_some_and(|data| data.ts_init() == boundary_ts)
712 {
713 end += 1;
714 }
715 }
716
717 Some((self.slice(start, end), end))
718 }
719
720 #[must_use]
721 pub fn slice(&self, start: usize, end: usize) -> Self {
722 if start == 0 && end == self.len() {
723 return self.clone();
724 }
725
726 match self {
727 Self::Custom(data) => Self::Custom(data.slice(start, end)),
728 Self::Instrument(data) => Self::Instrument(data.slice(start, end)),
729 Self::BookDelta(data) => Self::BookDelta(data.slice(start, end)),
730 Self::BookDeltas(data) => Self::BookDeltas(data.slice(start, end)),
731 Self::BookDepth(data) => Self::BookDepth(data.slice(start, end)),
732 Self::Quote(data) => Self::Quote(data.slice(start, end)),
733 Self::Trade(data) => Self::Trade(data.slice(start, end)),
734 Self::Bar(data) => Self::Bar(data.slice(start, end)),
735 Self::MarkPrice(data) => Self::MarkPrice(data.slice(start, end)),
736 Self::IndexPrice(data) => Self::IndexPrice(data.slice(start, end)),
737 Self::FundingRate(data) => Self::FundingRate(data.slice(start, end)),
738 Self::OptionGreeks(data) => Self::OptionGreeks(data.slice(start, end)),
739 Self::InstrumentStatus(data) => Self::InstrumentStatus(data.slice(start, end)),
740 Self::InstrumentClose(data) => Self::InstrumentClose(data.slice(start, end)),
741 #[cfg(feature = "defi")]
742 Self::Defi(data) => Self::Defi(data.slice(start, end)),
743 }
744 }
745
746 #[must_use]
747 pub fn to_data_vec_for_compat(&self) -> Vec<Data> {
748 (0..self.len())
749 .filter_map(|index| self.get(index).map(|item| item.to_owned_data()))
750 .collect()
751 }
752}
753
754pub trait IntoDataBatch: Sized {
756 #[must_use]
758 fn into_batch(data: Vec<Self>) -> DataBatch;
759}
760
761impl<T: IntoDataBatch> From<Vec<T>> for DataBatch {
762 fn from(data: Vec<T>) -> Self {
763 T::into_batch(data)
764 }
765}
766
767macro_rules! impl_into_data_batch {
768 ($(($variant:ident, $type:ident, $data:ident, $batch:ident, $prefix:literal)),+ $(,)?) => {
769 $(
770 impl IntoDataBatch for $type {
771 fn into_batch(data: Vec<Self>) -> DataBatch {
772 DataBatch::$batch(data.into())
773 }
774 }
775 )+
776 };
777}
778
779for_each_data_type!(impl_into_data_batch);
780
781impl IntoDataBatch for OrderBookDeltas {
782 fn into_batch(data: Vec<Self>) -> DataBatch {
783 DataBatch::BookDeltas(data.into())
784 }
785}
786
787impl IntoDataBatch for CustomData {
788 fn into_batch(data: Vec<Self>) -> DataBatch {
789 DataBatch::Custom(data.into())
790 }
791}
792
793#[cfg(feature = "defi")]
794impl IntoDataBatch for DefiData {
795 fn into_batch(data: Vec<Self>) -> DataBatch {
796 DataBatch::Defi(data.into())
797 }
798}
799
800pub trait FromDataBatch: Sized {
802 fn from_batch(batch: DataBatch) -> anyhow::Result<Vec<Self>>;
808}
809
810macro_rules! impl_from_data_batch {
811 ($(($variant:ident, $type:ident, $data:ident, $batch:ident, $prefix:literal)),+ $(,)?) => {
812 $(
813 impl FromDataBatch for $type {
814 fn from_batch(batch: DataBatch) -> anyhow::Result<Vec<Self>> {
815 match batch {
816 DataBatch::$batch(data) => Ok(data.as_ref().to_vec()),
817 _ => anyhow::bail!(
818 "expected a {} batch, found another data batch variant",
819 stringify!($batch),
820 ),
821 }
822 }
823 }
824 )+
825 };
826}
827
828for_each_data_type!(impl_from_data_batch);
829
830impl FromDataBatch for CustomData {
831 fn from_batch(batch: DataBatch) -> anyhow::Result<Vec<Self>> {
832 match batch {
833 DataBatch::Custom(data) => Ok(data.as_ref().to_vec()),
834 _ => anyhow::bail!("expected a Custom batch, found another data batch variant"),
835 }
836 }
837}
838
839#[cfg(feature = "defi")]
840impl FromDataBatch for DefiData {
841 fn from_batch(batch: DataBatch) -> anyhow::Result<Vec<Self>> {
842 match batch {
843 DataBatch::Defi(data) => Ok(data.as_ref().to_vec()),
844 _ => anyhow::bail!("expected a Defi batch, found another data batch variant"),
845 }
846 }
847}
848
849#[derive(
851 Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, strum::Display, strum::EnumIter,
852)]
853pub enum NautilusRecordType {
854 AccountState,
855 OrderInitialized,
856 OrderDenied,
857 OrderEmulated,
858 OrderSubmitted,
859 OrderAccepted,
860 OrderRejected,
861 OrderPendingCancel,
862 OrderCanceled,
863 OrderCancelRejected,
864 OrderExpired,
865 OrderTriggered,
866 OrderPendingUpdate,
867 OrderReleased,
868 OrderModifyRejected,
869 OrderUpdated,
870 OrderFilled,
871 OrderFillVoided,
872 PositionOpened,
873 PositionChanged,
874 PositionClosed,
875 PositionAdjusted,
876 OrderSnapshot,
877 PositionSnapshot,
878 OrderStatusReport,
879 FillReport,
880 PositionStatusReport,
881 ExecutionMassStatus,
882 #[cfg(feature = "defi")]
883 Defi,
884}
885
886impl FromStr for NautilusRecordType {
887 type Err = anyhow::Error;
888
889 fn from_str(s: &str) -> anyhow::Result<Self> {
890 match s {
891 "AccountState" | "account_state" => Ok(Self::AccountState),
892 "OrderInitialized" | "order_initialized" => Ok(Self::OrderInitialized),
893 "OrderDenied" | "order_denied" => Ok(Self::OrderDenied),
894 "OrderEmulated" | "order_emulated" => Ok(Self::OrderEmulated),
895 "OrderSubmitted" | "order_submitted" => Ok(Self::OrderSubmitted),
896 "OrderAccepted" | "order_accepted" => Ok(Self::OrderAccepted),
897 "OrderRejected" | "order_rejected" => Ok(Self::OrderRejected),
898 "OrderPendingCancel" | "order_pending_cancel" => Ok(Self::OrderPendingCancel),
899 "OrderCanceled" | "order_canceled" => Ok(Self::OrderCanceled),
900 "OrderCancelRejected" | "order_cancel_rejected" => Ok(Self::OrderCancelRejected),
901 "OrderExpired" | "order_expired" => Ok(Self::OrderExpired),
902 "OrderTriggered" | "order_triggered" => Ok(Self::OrderTriggered),
903 "OrderPendingUpdate" | "order_pending_update" => Ok(Self::OrderPendingUpdate),
904 "OrderReleased" | "order_released" => Ok(Self::OrderReleased),
905 "OrderModifyRejected" | "order_modify_rejected" => Ok(Self::OrderModifyRejected),
906 "OrderUpdated" | "order_updated" => Ok(Self::OrderUpdated),
907 "OrderFilled" | "order_filled" => Ok(Self::OrderFilled),
908 "OrderFillVoided" | "order_fill_voided" => Ok(Self::OrderFillVoided),
909 "PositionOpened" | "position_opened" => Ok(Self::PositionOpened),
910 "PositionChanged" | "position_changed" => Ok(Self::PositionChanged),
911 "PositionClosed" | "position_closed" => Ok(Self::PositionClosed),
912 "PositionAdjusted" | "position_adjusted" => Ok(Self::PositionAdjusted),
913 "OrderSnapshot" | "order_snapshot" => Ok(Self::OrderSnapshot),
914 "PositionSnapshot" | "position_snapshot" => Ok(Self::PositionSnapshot),
915 "OrderStatusReport" | "order_status_report" => Ok(Self::OrderStatusReport),
916 "FillReport" | "fill_report" => Ok(Self::FillReport),
917 "PositionStatusReport" | "position_status_report" => Ok(Self::PositionStatusReport),
918 "ExecutionMassStatus" | "execution_mass_status" => Ok(Self::ExecutionMassStatus),
919 "custom" | "CustomData" => {
920 anyhow::bail!("custom data queries require NautilusDataType::Custom")
921 }
922 #[cfg(feature = "defi")]
923 "Defi" | "defi" => Ok(Self::Defi),
924 _ => anyhow::bail!("Invalid `NautilusRecordType`: '{s}'"),
925 }
926 }
927}
928
929impl<'de> Deserialize<'de> for Data {
930 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
931 where
932 D: serde::Deserializer<'de>,
933 {
934 use serde::de::Error;
935 let value = serde_json::Value::deserialize(deserializer)?;
936 let type_name = value
937 .get("type")
938 .and_then(|v| v.as_str())
939 .ok_or_else(|| D::Error::custom("Missing 'type' field in Data"))?;
940
941 match type_name {
942 "Instrument" => Ok(Self::Instrument(Box::new(
943 serde_json::from_value(
944 value
945 .get("data")
946 .cloned()
947 .ok_or_else(|| D::Error::custom("Missing 'data' field for Instrument"))?,
948 )
949 .map_err(D::Error::custom)?,
950 ))),
951 "OrderBookDelta" => Ok(Self::BookDelta(
952 serde_json::from_value(value).map_err(D::Error::custom)?,
953 )),
954 "OrderBookDeltas" => Ok(Self::BookDeltas(
955 serde_json::from_value(value).map_err(D::Error::custom)?,
956 )),
957 "OrderBookDepth" => Ok(Self::BookDepth(
958 serde_json::from_value(value).map_err(D::Error::custom)?,
959 )),
960 "QuoteTick" => Ok(Self::Quote(
961 serde_json::from_value(value).map_err(D::Error::custom)?,
962 )),
963 "TradeTick" => Ok(Self::Trade(
964 serde_json::from_value(value).map_err(D::Error::custom)?,
965 )),
966 "Bar" => Ok(Self::Bar(
967 serde_json::from_value(value).map_err(D::Error::custom)?,
968 )),
969 "MarkPriceUpdate" => Ok(Self::MarkPrice(
970 serde_json::from_value(value).map_err(D::Error::custom)?,
971 )),
972 "IndexPriceUpdate" => Ok(Self::IndexPrice(
973 serde_json::from_value(value).map_err(D::Error::custom)?,
974 )),
975 "FundingRateUpdate" => Ok(Self::FundingRate(
976 serde_json::from_value(value).map_err(D::Error::custom)?,
977 )),
978 "OptionGreeks" => Ok(Self::OptionGreeks(
979 serde_json::from_value(value).map_err(D::Error::custom)?,
980 )),
981 "InstrumentStatus" => Ok(Self::InstrumentStatus(
982 serde_json::from_value(value).map_err(D::Error::custom)?,
983 )),
984 "InstrumentClose" => Ok(Self::InstrumentClose(
985 serde_json::from_value(value).map_err(D::Error::custom)?,
986 )),
987 _ => {
988 if let Some(data) =
989 deserialize_custom_from_json(type_name, &value).map_err(D::Error::custom)?
990 {
991 Ok(data)
992 } else {
993 Err(D::Error::custom(format!("Unknown Data type: {type_name}")))
994 }
995 }
996 }
997 }
998}
999
1000impl Clone for Data {
1001 fn clone(&self) -> Self {
1002 match self {
1003 Self::Custom(x) => Self::Custom(x.clone()),
1004 Self::Instrument(x) => Self::Instrument(x.clone()),
1005 Self::BookDelta(x) => Self::BookDelta(*x),
1006 Self::BookDeltas(x) => Self::BookDeltas(x.clone()),
1007 Self::BookDepth(x) => Self::BookDepth(x.clone()),
1008 Self::Quote(x) => Self::Quote(*x),
1009 Self::Trade(x) => Self::Trade(*x),
1010 Self::Bar(x) => Self::Bar(*x),
1011 Self::MarkPrice(x) => Self::MarkPrice(*x),
1012 Self::IndexPrice(x) => Self::IndexPrice(*x),
1013 Self::FundingRate(x) => Self::FundingRate(*x),
1014 Self::OptionGreeks(x) => Self::OptionGreeks(*x),
1015 Self::InstrumentStatus(x) => Self::InstrumentStatus(*x),
1016 Self::InstrumentClose(x) => Self::InstrumentClose(*x),
1017 #[cfg(feature = "defi")]
1018 Self::Defi(x) => Self::Defi(x.clone()),
1019 }
1020 }
1021}
1022
1023impl PartialEq for Data {
1024 fn eq(&self, other: &Self) -> bool {
1025 match (self, other) {
1026 (Self::Custom(a), Self::Custom(b)) => a == b,
1027 (Self::Instrument(a), Self::Instrument(b)) => a == b,
1028 (Self::BookDelta(a), Self::BookDelta(b)) => a == b,
1029 (Self::BookDeltas(a), Self::BookDeltas(b)) => a == b,
1030 (Self::BookDepth(a), Self::BookDepth(b)) => a == b,
1031 (Self::Quote(a), Self::Quote(b)) => a == b,
1032 (Self::Trade(a), Self::Trade(b)) => a == b,
1033 (Self::Bar(a), Self::Bar(b)) => a == b,
1034 (Self::MarkPrice(a), Self::MarkPrice(b)) => a == b,
1035 (Self::IndexPrice(a), Self::IndexPrice(b)) => a == b,
1036 (Self::FundingRate(a), Self::FundingRate(b)) => a == b,
1037 (Self::OptionGreeks(a), Self::OptionGreeks(b)) => a == b,
1038 (Self::InstrumentStatus(a), Self::InstrumentStatus(b)) => a == b,
1039 (Self::InstrumentClose(a), Self::InstrumentClose(b)) => a == b,
1040 #[cfg(feature = "defi")]
1041 (Self::Defi(a), Self::Defi(b)) => a == b,
1042 _ => false,
1043 }
1044 }
1045}
1046
1047impl Serialize for Data {
1048 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1049 where
1050 S: serde::Serializer,
1051 {
1052 match self {
1053 Self::Custom(x) => x.serialize(serializer),
1054 Self::Instrument(instrument) => serde_json::json!({
1055 "type": "Instrument",
1056 "data": instrument,
1057 })
1058 .serialize(serializer),
1059 Self::BookDelta(x) => x.serialize(serializer),
1060 Self::BookDeltas(x) => x.serialize(serializer),
1061 Self::BookDepth(x) => x.serialize(serializer),
1062 Self::Quote(x) => x.serialize(serializer),
1063 Self::Trade(x) => x.serialize(serializer),
1064 Self::Bar(x) => x.serialize(serializer),
1065 Self::MarkPrice(x) => x.serialize(serializer),
1066 Self::IndexPrice(x) => x.serialize(serializer),
1067 Self::FundingRate(x) => x.serialize(serializer),
1068 Self::OptionGreeks(x) => x.serialize(serializer),
1069 Self::InstrumentStatus(x) => x.serialize(serializer),
1070 Self::InstrumentClose(x) => x.serialize(serializer),
1071 #[cfg(feature = "defi")]
1072 Self::Defi(_) => Err(serde::ser::Error::custom(
1073 "Data::Defi serialization is not supported",
1074 )),
1075 }
1076 }
1077}
1078
1079macro_rules! impl_data_conversions {
1080 ($variant:ident, $type:ty) => {
1081 impl TryFrom<Data> for $type {
1082 type Error = ();
1083
1084 fn try_from(value: Data) -> Result<Self, Self::Error> {
1085 match value {
1086 Data::$variant(x) => Ok(x),
1087 _ => Err(()),
1088 }
1089 }
1090 }
1091
1092 impl From<$type> for Data {
1093 fn from(value: $type) -> Self {
1094 Self::$variant(value)
1095 }
1096 }
1097 };
1098}
1099
1100impl TryFrom<Data> for OrderBookDepth {
1101 type Error = ();
1102
1103 fn try_from(value: Data) -> Result<Self, Self::Error> {
1104 match value {
1105 Data::BookDepth(x) => Ok(*x),
1106 _ => Err(()),
1107 }
1108 }
1109}
1110
1111impl TryFrom<Data> for InstrumentAny {
1112 type Error = ();
1113
1114 fn try_from(value: Data) -> Result<Self, Self::Error> {
1115 match value {
1116 Data::Instrument(instrument) => Ok(*instrument),
1117 _ => Err(()),
1118 }
1119 }
1120}
1121
1122impl TryFrom<Data> for OrderBookDeltas {
1123 type Error = ();
1124
1125 fn try_from(value: Data) -> Result<Self, Self::Error> {
1126 match value {
1127 Data::BookDeltas(deltas) => Ok(*deltas),
1128 _ => Err(()),
1129 }
1130 }
1131}
1132
1133impl_data_conversions!(Quote, QuoteTick);
1134impl_data_conversions!(BookDelta, OrderBookDelta);
1135impl_data_conversions!(Trade, TradeTick);
1136impl_data_conversions!(Bar, Bar);
1137impl_data_conversions!(MarkPrice, MarkPriceUpdate);
1138impl_data_conversions!(IndexPrice, IndexPriceUpdate);
1139impl_data_conversions!(FundingRate, FundingRateUpdate);
1140impl_data_conversions!(OptionGreeks, OptionGreeks);
1141impl_data_conversions!(InstrumentStatus, InstrumentStatus);
1142impl_data_conversions!(InstrumentClose, InstrumentClose);
1143
1144#[must_use]
1149pub fn to_variant<T: TryFrom<Data>>(data: Vec<Data>) -> Vec<T> {
1150 data.into_iter()
1151 .filter_map(|d| T::try_from(d).ok())
1152 .collect()
1153}
1154
1155fn to_variant_for_batch<T: TryFrom<Data>>(
1156 data_type: &NautilusDataType,
1157 data: Vec<Data>,
1158) -> anyhow::Result<Vec<T>> {
1159 let expected_len = data.len();
1160 let converted = to_variant(data);
1161 anyhow::ensure!(
1162 converted.len() == expected_len,
1163 "catalog query for {data_type} returned rows with another data type",
1164 );
1165 Ok(converted)
1166}
1167
1168impl Data {
1169 #[must_use]
1171 pub fn instrument_id(&self) -> InstrumentId {
1172 DataRef::from(self).instrument_id()
1173 }
1174
1175 #[must_use]
1177 pub fn is_order_book_data(&self) -> bool {
1178 DataRef::from(self).is_order_book_data()
1179 }
1180}
1181
1182impl From<InstrumentAny> for Data {
1183 fn from(value: InstrumentAny) -> Self {
1184 Self::Instrument(Box::new(value))
1185 }
1186}
1187
1188pub trait HasTsInit {
1194 fn ts_init(&self) -> UnixNanos;
1196}
1197
1198impl HasTsInit for Data {
1199 fn ts_init(&self) -> UnixNanos {
1200 DataRef::from(self).ts_init()
1201 }
1202}
1203
1204pub fn is_monotonically_increasing_by_init<T: HasTsInit>(data: &[T]) -> bool {
1208 data.array_windows()
1209 .all(|[a, b]| a.ts_init() <= b.ts_init())
1210}
1211
1212impl From<OrderBookDeltas> for Data {
1213 fn from(value: OrderBookDeltas) -> Self {
1214 Self::BookDeltas(Box::new(value))
1215 }
1216}
1217
1218impl From<OrderBookDepth> for Data {
1219 fn from(value: OrderBookDepth) -> Self {
1220 Self::BookDepth(Box::new(value))
1221 }
1222}
1223
1224#[cfg(feature = "defi")]
1225impl From<DefiData> for Data {
1226 fn from(value: DefiData) -> Self {
1227 Self::Defi(Box::new(value))
1228 }
1229}
1230
1231#[macro_export]
1239macro_rules! for_each_data_type {
1240 ($macro:ident) => {
1241 $macro! {
1242 (Instrument, InstrumentAny, Instrument, Instrument, "instruments"),
1243 (QuoteTick, QuoteTick, Quote, Quote, "quotes"),
1244 (TradeTick, TradeTick, Trade, Trade, "trades"),
1245 (Bar, Bar, Bar, Bar, "bars"),
1246 (OrderBookDelta, OrderBookDelta, BookDelta, BookDelta, "order_book_deltas"),
1247 (OrderBookDepth, OrderBookDepth, BookDepth, BookDepth, "order_book_depths"),
1248 (MarkPriceUpdate, MarkPriceUpdate, MarkPrice, MarkPrice, "mark_prices"),
1249 (IndexPriceUpdate, IndexPriceUpdate, IndexPrice, IndexPrice, "index_prices"),
1250 (FundingRateUpdate, FundingRateUpdate, FundingRate, FundingRate, "funding_rates"),
1251 (InstrumentStatus, InstrumentStatus, InstrumentStatus, InstrumentStatus, "instrument_status"),
1252 (OptionGreeks, OptionGreeks, OptionGreeks, OptionGreeks, "option_greeks"),
1253 (InstrumentClose, InstrumentClose, InstrumentClose, InstrumentClose, "instrument_closes"),
1254 }
1255 };
1256 ($macro:ident, $($args:tt)*) => {
1257 $macro! {
1258 ($($args)*);
1259 (Instrument, InstrumentAny, Instrument, Instrument, "instruments"),
1260 (QuoteTick, QuoteTick, Quote, Quote, "quotes"),
1261 (TradeTick, TradeTick, Trade, Trade, "trades"),
1262 (Bar, Bar, Bar, Bar, "bars"),
1263 (OrderBookDelta, OrderBookDelta, BookDelta, BookDelta, "order_book_deltas"),
1264 (OrderBookDepth, OrderBookDepth, BookDepth, BookDepth, "order_book_depths"),
1265 (MarkPriceUpdate, MarkPriceUpdate, MarkPrice, MarkPrice, "mark_prices"),
1266 (IndexPriceUpdate, IndexPriceUpdate, IndexPrice, IndexPrice, "index_prices"),
1267 (FundingRateUpdate, FundingRateUpdate, FundingRate, FundingRate, "funding_rates"),
1268 (InstrumentStatus, InstrumentStatus, InstrumentStatus, InstrumentStatus, "instrument_status"),
1269 (OptionGreeks, OptionGreeks, OptionGreeks, OptionGreeks, "option_greeks"),
1270 (InstrumentClose, InstrumentClose, InstrumentClose, InstrumentClose, "instrument_closes"),
1271 }
1272 };
1273}
1274
1275#[cfg(test)]
1276mod tests {
1277 use std::sync::Arc;
1278
1279 use rstest::*;
1280
1281 use super::*;
1282 use crate::instruments::stubs::crypto_perpetual_ethusdt;
1283
1284 #[rstest]
1285 fn test_depth_family_matches_both_macro_forms() {
1286 macro_rules! family_rows {
1287 (($marker:ident); $($rows:tt)*) => { family_rows!($($rows)*) };
1288 ($(($family:ident, $type:ident, $data:ident, $batch:ident, $prefix:literal)),+ $(,)?) => {
1289 vec![$((stringify!($family), stringify!($type), stringify!($data), stringify!($batch), $prefix)),+]
1290 };
1291 }
1292 let rows = for_each_data_type!(family_rows);
1293 let rows_with_args = for_each_data_type!(family_rows, context);
1294 let depth = rows
1295 .iter()
1296 .find(|row| row.4 == "order_book_depths")
1297 .copied()
1298 .unwrap();
1299 assert_eq!(rows, rows_with_args);
1300 assert_eq!(
1301 depth,
1302 (
1303 "OrderBookDepth",
1304 "OrderBookDepth",
1305 "BookDepth",
1306 "BookDepth",
1307 "order_book_depths"
1308 )
1309 );
1310 }
1311
1312 #[rstest]
1313 fn data_instrument_json_roundtrips() {
1314 let data = Data::from(InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt()));
1315
1316 let encoded = serde_json::to_string(&data).unwrap();
1317 let decoded = serde_json::from_str::<Data>(&encoded).unwrap();
1318
1319 assert_eq!(decoded, data);
1320 }
1321
1322 #[rstest]
1323 #[case(NautilusDataType::Instrument, "Instrument")]
1324 #[case(NautilusDataType::QuoteTick, "QuoteTick")]
1325 #[case(NautilusDataType::OrderBookDelta, "OrderBookDelta")]
1326 #[case(
1327 NautilusDataType::Custom {
1328 type_name: "Example".to_string()
1329 },
1330 "Custom:Example"
1331 )]
1332 fn nautilus_data_type_display_from_str_roundtrips(
1333 #[case] nautilus_data_type: NautilusDataType,
1334 #[case] expected: &str,
1335 ) {
1336 assert_eq!(nautilus_data_type.to_string(), expected);
1337 assert_eq!(
1338 expected.parse::<NautilusDataType>().unwrap(),
1339 nautilus_data_type
1340 );
1341 }
1342
1343 #[rstest]
1344 #[case("instruments", NautilusDataType::Instrument)]
1345 #[case("quotes", NautilusDataType::QuoteTick)]
1346 #[case("order_book_deltas", NautilusDataType::OrderBookDelta)]
1347 #[case("instrument_closes", NautilusDataType::InstrumentClose)]
1348 #[case("OrderBookDeltas", NautilusDataType::OrderBookDelta)]
1350 #[case("OrderBookDepth", NautilusDataType::OrderBookDepth)]
1351 fn nautilus_data_type_storage_names_parse(
1352 #[case] value: &str,
1353 #[case] expected: NautilusDataType,
1354 ) {
1355 assert_eq!(value.parse::<NautilusDataType>().unwrap(), expected);
1356 }
1357
1358 #[rstest]
1359 #[case("OrderBookDepth10")]
1360 #[case("order_book_depth10")]
1361 fn nautilus_data_type_rejects_former_depth10_spellings(#[case] value: &str) {
1362 assert!(value.parse::<NautilusDataType>().is_err());
1363 }
1364
1365 #[rstest]
1366 #[case("OrderBook")]
1367 #[case("order_book")]
1368 fn nautilus_data_type_rejects_order_book_spellings(#[case] value: &str) {
1369 assert!(value.parse::<NautilusDataType>().is_err());
1370 }
1371
1372 #[rstest]
1373 #[case(NautilusRecordType::AccountState, "AccountState")]
1374 #[case(NautilusRecordType::OrderFilled, "OrderFilled")]
1375 #[case(NautilusRecordType::OrderFillVoided, "OrderFillVoided")]
1376 #[case(NautilusRecordType::ExecutionMassStatus, "ExecutionMassStatus")]
1377 fn nautilus_record_type_display_from_str_roundtrips(
1378 #[case] record_type: NautilusRecordType,
1379 #[case] expected: &str,
1380 ) {
1381 assert_eq!(record_type.to_string(), expected);
1382 assert_eq!(expected.parse::<NautilusRecordType>().unwrap(), record_type);
1383 }
1384
1385 #[rstest]
1386 #[case("account_state", NautilusRecordType::AccountState)]
1387 #[case("order_filled", NautilusRecordType::OrderFilled)]
1388 #[case("order_fill_voided", NautilusRecordType::OrderFillVoided)]
1389 #[case("position_snapshot", NautilusRecordType::PositionSnapshot)]
1390 fn nautilus_record_type_storage_names_parse(
1391 #[case] value: &str,
1392 #[case] expected: NautilusRecordType,
1393 ) {
1394 assert_eq!(value.parse::<NautilusRecordType>().unwrap(), expected);
1395 }
1396
1397 #[rstest]
1398 fn data_batches_group_mixed_rows_in_first_seen_order() {
1399 let data = vec![
1400 Data::Quote(QuoteTick::default()),
1401 Data::Trade(TradeTick::default()),
1402 Data::Quote(QuoteTick::default()),
1403 ];
1404
1405 let batches = DataBatch::from_data_vec_grouped(&data).unwrap();
1406
1407 assert_eq!(batches.len(), 2);
1408 assert!(matches!(&batches[0], DataBatch::Quote(rows) if rows.len() == 2));
1409 assert!(matches!(&batches[1], DataBatch::Trade(rows) if rows.len() == 1));
1410 }
1411
1412 #[rstest]
1413 fn data_batches_preserve_order_book_delta_event_batches() {
1414 let instrument_id = InstrumentId::from("AUD/USD.SIM");
1415 let delta = OrderBookDelta::clear(instrument_id, 1, UnixNanos::from(2), UnixNanos::from(3));
1416 let data = vec![Data::BookDeltas(Box::new(OrderBookDeltas::new(
1417 instrument_id,
1418 vec![delta],
1419 )))];
1420
1421 let batches = DataBatch::from_data_vec_grouped(&data).unwrap();
1422
1423 assert_eq!(batches.len(), 1);
1424 assert!(
1425 matches!(&batches[0], DataBatch::BookDeltas(rows) if rows.len() == 1 && rows[0].deltas == vec![delta])
1426 );
1427 }
1428 #[rstest]
1429 fn test_batch_view_make_mut_reuses_unshared_backing() {
1430 let mut view = BatchView::from(vec![3, 1, 2]);
1431 let backing = Arc::as_ptr(view.arc());
1432
1433 view.make_mut().sort_unstable();
1434
1435 assert!(std::ptr::eq(Arc::as_ptr(view.arc()), backing));
1436 assert_eq!(view.as_ref(), &[1, 2, 3]);
1437 }
1438
1439 #[rstest]
1440 fn test_batch_view_make_mut_clones_shared_backing_within_range() {
1441 let source = BatchView::new(Arc::new(vec![9, 3, 1, 2]), 1..4);
1442 let mut view = source.clone();
1443
1444 view.make_mut().sort_unstable();
1445
1446 assert!(!Arc::ptr_eq(source.arc(), view.arc()));
1447 assert_eq!(source.as_ref(), &[3, 1, 2]);
1448 assert_eq!(view.as_ref(), &[1, 2, 3]);
1449 assert_eq!(view.arc().as_slice(), &[9, 1, 2, 3]);
1450 }
1451}