1use std::{collections::HashMap, fmt::Display, hash::Hash};
19
20use indexmap::IndexMap;
21use nautilus_core::{UnixNanos, correctness::FAILED, serialization::Serializable};
22use serde::{Deserialize, Serialize};
23
24use super::{
25 HasTsInit,
26 order::{BookOrder, NULL_ORDER},
27};
28use crate::{
29 enums::{BookAction, RecordFlag},
30 identifiers::InstrumentId,
31 types::{fixed::FIXED_SIZE_BINARY, quantity::check_positive_quantity},
32};
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
36#[serde(tag = "type")]
37#[cfg_attr(
38 feature = "python",
39 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
40)]
41#[cfg_attr(
42 feature = "python",
43 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
44)]
45pub struct OrderBookDelta {
46 pub instrument_id: InstrumentId,
48 pub action: BookAction,
50 pub order: BookOrder,
52 pub flags: u8,
54 pub sequence: u64,
56 pub ts_event: UnixNanos,
58 pub ts_init: UnixNanos,
60}
61
62impl OrderBookDelta {
63 pub fn new_checked(
73 instrument_id: InstrumentId,
74 action: BookAction,
75 order: BookOrder,
76 flags: u8,
77 sequence: u64,
78 ts_event: UnixNanos,
79 ts_init: UnixNanos,
80 ) -> anyhow::Result<Self> {
81 if matches!(action, BookAction::Add | BookAction::Update) {
82 check_positive_quantity(order.size, stringify!(order.size))?;
83 }
84
85 Ok(Self {
86 instrument_id,
87 action,
88 order,
89 flags,
90 sequence,
91 ts_event,
92 ts_init,
93 })
94 }
95
96 #[must_use]
102 pub fn new(
103 instrument_id: InstrumentId,
104 action: BookAction,
105 order: BookOrder,
106 flags: u8,
107 sequence: u64,
108 ts_event: UnixNanos,
109 ts_init: UnixNanos,
110 ) -> Self {
111 Self::new_checked(
112 instrument_id,
113 action,
114 order,
115 flags,
116 sequence,
117 ts_event,
118 ts_init,
119 )
120 .expect(FAILED)
121 }
122
123 #[must_use]
125 pub fn clear(
126 instrument_id: InstrumentId,
127 sequence: u64,
128 ts_event: UnixNanos,
129 ts_init: UnixNanos,
130 ) -> Self {
131 Self {
132 instrument_id,
133 action: BookAction::Clear,
134 order: NULL_ORDER,
135 flags: RecordFlag::F_SNAPSHOT as u8,
136 sequence,
137 ts_event,
138 ts_init,
139 }
140 }
141
142 #[cfg_attr(not(feature = "python"), allow(dead_code))]
144 #[must_use]
145 pub(crate) const fn is_add(&self) -> bool {
146 matches!(self.action, BookAction::Add)
147 }
148
149 #[cfg_attr(not(feature = "python"), allow(dead_code))]
151 #[must_use]
152 pub(crate) const fn is_update(&self) -> bool {
153 matches!(self.action, BookAction::Update)
154 }
155
156 #[cfg_attr(not(feature = "python"), allow(dead_code))]
158 #[must_use]
159 pub(crate) const fn is_delete(&self) -> bool {
160 matches!(self.action, BookAction::Delete)
161 }
162
163 #[cfg_attr(not(feature = "python"), allow(dead_code))]
165 #[must_use]
166 pub(crate) const fn is_clear(&self) -> bool {
167 matches!(self.action, BookAction::Clear)
168 }
169
170 #[must_use]
172 pub fn get_metadata(
173 instrument_id: &InstrumentId,
174 price_precision: u8,
175 size_precision: u8,
176 ) -> HashMap<String, String> {
177 let mut metadata = HashMap::new();
178 metadata.insert("instrument_id".to_string(), instrument_id.to_string());
179 metadata.insert("price_precision".to_string(), price_precision.to_string());
180 metadata.insert("size_precision".to_string(), size_precision.to_string());
181 metadata
182 }
183
184 #[must_use]
186 pub fn get_fields() -> IndexMap<String, String> {
187 let mut metadata = IndexMap::new();
188 metadata.insert("action".to_string(), "UInt8".to_string());
189 metadata.insert("side".to_string(), "UInt8".to_string());
190 metadata.insert("price".to_string(), FIXED_SIZE_BINARY.to_string());
191 metadata.insert("size".to_string(), FIXED_SIZE_BINARY.to_string());
192 metadata.insert("order_id".to_string(), "UInt64".to_string());
193 metadata.insert("flags".to_string(), "UInt8".to_string());
194 metadata.insert("sequence".to_string(), "UInt64".to_string());
195 metadata.insert("ts_event".to_string(), "UInt64".to_string());
196 metadata.insert("ts_init".to_string(), "UInt64".to_string());
197 metadata
198 }
199}
200
201impl Display for OrderBookDelta {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(
204 f,
205 "{},{},{},{},{},{},{}",
206 self.instrument_id,
207 self.action,
208 self.order,
209 self.flags,
210 self.sequence,
211 self.ts_event,
212 self.ts_init
213 )
214 }
215}
216
217impl Serializable for OrderBookDelta {}
218
219impl HasTsInit for OrderBookDelta {
220 fn ts_init(&self) -> UnixNanos {
221 self.ts_init
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use std::{
228 collections::hash_map::DefaultHasher,
229 hash::{Hash, Hasher},
230 };
231
232 use nautilus_core::{
233 UnixNanos,
234 serialization::{
235 Serializable,
236 msgpack::{FromMsgPack, ToMsgPack},
237 },
238 };
239 use rstest::rstest;
240
241 use crate::{
242 data::{BookOrder, HasTsInit, OrderBookDelta, stubs::*},
243 enums::{BookAction, OrderSide, RecordFlag},
244 identifiers::InstrumentId,
245 types::{Price, Quantity},
246 };
247
248 fn create_test_delta() -> OrderBookDelta {
249 let order = BookOrder::new(
250 OrderSide::Buy,
251 Price::from("1.0500"),
252 Quantity::from("100000"),
253 12345,
254 );
255 OrderBookDelta::new(
256 InstrumentId::from("EURUSD.SIM"),
257 BookAction::Add,
258 order,
259 0,
260 123,
261 UnixNanos::from(1_000_000_000),
262 UnixNanos::from(2_000_000_000),
263 )
264 }
265
266 #[rstest]
267 fn test_order_book_delta_new() {
268 let delta = create_test_delta();
269
270 assert_eq!(delta.instrument_id, InstrumentId::from("EURUSD.SIM"));
271 assert_eq!(delta.action, BookAction::Add);
272 assert_eq!(delta.order.side, OrderSide::Buy.into());
273 assert_eq!(delta.order.price, Price::from("1.0500"));
274 assert_eq!(delta.order.size, Quantity::from("100000"));
275 assert_eq!(delta.order.order_id, 12345);
276 assert_eq!(delta.flags, 0);
277 assert_eq!(delta.sequence, 123);
278 assert_eq!(delta.ts_event, UnixNanos::from(1_000_000_000));
279 assert_eq!(delta.ts_init, UnixNanos::from(2_000_000_000));
280 }
281
282 #[rstest]
283 fn test_order_book_delta_new_checked_valid() {
284 let order = BookOrder::new(
285 OrderSide::Sell,
286 Price::from("1.0505"),
287 Quantity::from("50000"),
288 67890,
289 );
290 let result = OrderBookDelta::new_checked(
291 InstrumentId::from("GBPUSD.SIM"),
292 BookAction::Update,
293 order,
294 16,
295 456,
296 UnixNanos::from(500_000_000),
297 UnixNanos::from(1_500_000_000),
298 );
299
300 assert!(result.is_ok());
301 let delta = result.unwrap();
302 assert_eq!(delta.instrument_id, InstrumentId::from("GBPUSD.SIM"));
303 assert_eq!(delta.action, BookAction::Update);
304 assert_eq!(delta.order.side, OrderSide::Sell.into());
305 assert_eq!(delta.flags, 16);
306 }
307
308 #[rstest]
309 #[should_panic(expected = "invalid `Quantity` for 'order.size' not positive, was 0")]
310 fn test_order_book_delta_new_with_zero_size_panics() {
311 let instrument_id = InstrumentId::from("AAPL.XNAS");
312 let action = BookAction::Add;
313 let price = Price::from("100.00");
314 let zero_size = Quantity::from(0);
315 let side = OrderSide::Buy;
316 let order_id = 123_456;
317 let flags = 0;
318 let sequence = 1;
319 let ts_event = UnixNanos::from(0);
320 let ts_init = UnixNanos::from(1);
321
322 let order = BookOrder::new(side, price, zero_size, order_id);
323
324 let _ = OrderBookDelta::new(
325 instrument_id,
326 action,
327 order,
328 flags,
329 sequence,
330 ts_event,
331 ts_init,
332 );
333 }
334
335 #[rstest]
336 fn test_order_book_delta_new_checked_with_zero_size_error() {
337 let instrument_id = InstrumentId::from("AAPL.XNAS");
338 let action = BookAction::Add;
339 let price = Price::from("100.00");
340 let zero_size = Quantity::from(0);
341 let side = OrderSide::Buy;
342 let order_id = 123_456;
343 let flags = 0;
344 let sequence = 1;
345 let ts_event = UnixNanos::from(0);
346 let ts_init = UnixNanos::from(1);
347
348 let order = BookOrder::new(side, price, zero_size, order_id);
349
350 let result = OrderBookDelta::new_checked(
351 instrument_id,
352 action,
353 order,
354 flags,
355 sequence,
356 ts_event,
357 ts_init,
358 );
359
360 assert!(result.is_err());
361 assert!(
362 result
363 .unwrap_err()
364 .to_string()
365 .contains("invalid `Quantity` for 'order.size' not positive")
366 );
367 }
368
369 #[rstest]
370 fn test_order_book_delta_new_checked_delete_with_zero_size_ok() {
371 let order = BookOrder::new(
372 OrderSide::Buy,
373 Price::from("100.00"),
374 Quantity::from(0),
375 123_456,
376 );
377 let result = OrderBookDelta::new_checked(
378 InstrumentId::from("TEST.SIM"),
379 BookAction::Delete,
380 order,
381 0,
382 1,
383 UnixNanos::from(0),
384 UnixNanos::from(1),
385 );
386
387 assert!(result.is_ok());
388 }
389
390 #[rstest]
391 fn test_order_book_delta_clear() {
392 let instrument_id = InstrumentId::from("BTCUSD.CRYPTO");
393 let sequence = 999;
394 let ts_event = UnixNanos::from(3_000_000_000);
395 let ts_init = UnixNanos::from(4_000_000_000);
396
397 let delta = OrderBookDelta::clear(instrument_id, sequence, ts_event, ts_init);
398
399 assert_eq!(delta.instrument_id, instrument_id);
400 assert_eq!(delta.action, BookAction::Clear);
401 assert!(delta.order.price.is_zero());
402 assert!(delta.order.size.is_zero());
403 assert_eq!(delta.order.side, None);
404 assert_eq!(delta.order.order_id, 0);
405 assert_eq!(delta.flags, RecordFlag::F_SNAPSHOT as u8);
406 assert_eq!(delta.sequence, sequence);
407 assert_eq!(delta.ts_event, ts_event);
408 assert_eq!(delta.ts_init, ts_init);
409 }
410
411 #[rstest]
412 fn test_get_metadata() {
413 let instrument_id = InstrumentId::from("EURUSD.SIM");
414 let metadata = OrderBookDelta::get_metadata(&instrument_id, 5, 8);
415
416 assert_eq!(metadata.len(), 3);
417 assert_eq!(
418 metadata.get("instrument_id"),
419 Some(&"EURUSD.SIM".to_string())
420 );
421 assert_eq!(metadata.get("price_precision"), Some(&"5".to_string()));
422 assert_eq!(metadata.get("size_precision"), Some(&"8".to_string()));
423 }
424
425 #[rstest]
426 fn test_get_fields() {
427 let fields = OrderBookDelta::get_fields();
428
429 assert_eq!(fields.len(), 9);
430 assert_eq!(fields.get("action"), Some(&"UInt8".to_string()));
431 assert_eq!(fields.get("side"), Some(&"UInt8".to_string()));
432
433 #[cfg(feature = "high-precision")]
434 {
435 assert_eq!(
436 fields.get("price"),
437 Some(&"FixedSizeBinary(16)".to_string())
438 );
439 assert_eq!(fields.get("size"), Some(&"FixedSizeBinary(16)".to_string()));
440 }
441 #[cfg(not(feature = "high-precision"))]
442 {
443 assert_eq!(fields.get("price"), Some(&"FixedSizeBinary(8)".to_string()));
444 assert_eq!(fields.get("size"), Some(&"FixedSizeBinary(8)".to_string()));
445 }
446
447 assert_eq!(fields.get("order_id"), Some(&"UInt64".to_string()));
448 assert_eq!(fields.get("flags"), Some(&"UInt8".to_string()));
449 assert_eq!(fields.get("sequence"), Some(&"UInt64".to_string()));
450 assert_eq!(fields.get("ts_event"), Some(&"UInt64".to_string()));
451 assert_eq!(fields.get("ts_init"), Some(&"UInt64".to_string()));
452 }
453
454 #[rstest]
455 #[case::add(BookAction::Add, (true, false, false, false))]
456 #[case::update(BookAction::Update, (false, true, false, false))]
457 #[case::delete(BookAction::Delete, (false, false, true, false))]
458 #[case::clear(BookAction::Clear, (false, false, false, true))]
459 fn test_order_book_delta_action_predicates(
460 #[case] action: BookAction,
461 #[case] expected: (bool, bool, bool, bool),
462 ) {
463 let order = BookOrder::new(
464 OrderSide::Buy,
465 Price::from("100.00"),
466 if matches!(action, BookAction::Delete | BookAction::Clear) {
467 Quantity::from(0)
468 } else {
469 Quantity::from("1000")
470 },
471 123_456,
472 );
473
474 let result = if matches!(action, BookAction::Clear) {
475 Ok(OrderBookDelta::clear(
476 InstrumentId::from("TEST.SIM"),
477 1,
478 UnixNanos::from(1_000_000_000),
479 UnixNanos::from(2_000_000_000),
480 ))
481 } else {
482 OrderBookDelta::new_checked(
483 InstrumentId::from("TEST.SIM"),
484 action,
485 order,
486 0,
487 1,
488 UnixNanos::from(1_000_000_000),
489 UnixNanos::from(2_000_000_000),
490 )
491 };
492
493 assert!(result.is_ok());
494 let delta = result.unwrap();
495 assert_eq!(delta.action, action);
496 assert_eq!(
497 (
498 delta.is_add(),
499 delta.is_update(),
500 delta.is_delete(),
501 delta.is_clear(),
502 ),
503 expected
504 );
505 }
506
507 #[rstest]
508 #[case(OrderSide::Buy)]
509 #[case(OrderSide::Sell)]
510 fn test_order_book_delta_with_different_sides(#[case] side: OrderSide) {
511 let order = BookOrder::new(side, Price::from("100.00"), Quantity::from("1000"), 123_456);
512
513 let delta = OrderBookDelta::new(
514 InstrumentId::from("TEST.SIM"),
515 BookAction::Add,
516 order,
517 0,
518 1,
519 UnixNanos::from(1_000_000_000),
520 UnixNanos::from(2_000_000_000),
521 );
522
523 assert_eq!(delta.order.side, side.into());
524 }
525
526 #[rstest]
527 fn test_order_book_delta_has_ts_init() {
528 let delta = create_test_delta();
529 assert_eq!(delta.ts_init(), UnixNanos::from(2_000_000_000));
530 }
531
532 #[rstest]
533 fn test_order_book_delta_display() {
534 let delta = create_test_delta();
535 let display_str = format!("{delta}");
536
537 assert!(display_str.contains("EURUSD.SIM"));
538 assert!(display_str.contains("ADD"));
539 assert!(display_str.contains("BUY"));
540 assert!(display_str.contains("1.0500"));
541 assert!(display_str.contains("100000"));
542 assert!(display_str.contains("12345"));
543 assert!(display_str.contains("123"));
544 }
545
546 #[rstest]
547 fn test_order_book_delta_with_zero_timestamps() {
548 let order = BookOrder::new(
549 OrderSide::Buy,
550 Price::from("100.00"),
551 Quantity::from("1000"),
552 123_456,
553 );
554 let delta = OrderBookDelta::new(
555 InstrumentId::from("TEST.SIM"),
556 BookAction::Add,
557 order,
558 0,
559 0,
560 UnixNanos::from(0),
561 UnixNanos::from(0),
562 );
563
564 assert_eq!(delta.sequence, 0);
565 assert_eq!(delta.ts_event, UnixNanos::from(0));
566 assert_eq!(delta.ts_init, UnixNanos::from(0));
567 }
568
569 #[rstest]
570 fn test_order_book_delta_with_max_values() {
571 let order = BookOrder::new(
572 OrderSide::Sell,
573 Price::from("999999.9999"),
574 Quantity::from("999999999.9999"),
575 u64::MAX,
576 );
577 let delta = OrderBookDelta::new(
578 InstrumentId::from("TEST.SIM"),
579 BookAction::Update,
580 order,
581 u8::MAX,
582 u64::MAX,
583 UnixNanos::from(u64::MAX),
584 UnixNanos::from(u64::MAX),
585 );
586
587 assert_eq!(delta.flags, u8::MAX);
588 assert_eq!(delta.sequence, u64::MAX);
589 assert_eq!(delta.order.order_id, u64::MAX);
590 assert_eq!(delta.ts_event, UnixNanos::from(u64::MAX));
591 assert_eq!(delta.ts_init, UnixNanos::from(u64::MAX));
592 }
593
594 #[rstest]
595 fn test_new() {
596 let instrument_id = InstrumentId::from("AAPL.XNAS");
597 let action = BookAction::Add;
598 let price = Price::from("100.00");
599 let size = Quantity::from("10");
600 let side = OrderSide::Buy;
601 let order_id = 123_456;
602 let flags = 0;
603 let sequence = 1;
604 let ts_event = 1;
605 let ts_init = 2;
606
607 let order = BookOrder::new(side, price, size, order_id);
608
609 let delta = OrderBookDelta::new(
610 instrument_id,
611 action,
612 order,
613 flags,
614 sequence,
615 ts_event.into(),
616 ts_init.into(),
617 );
618
619 assert_eq!(delta.instrument_id, instrument_id);
620 assert_eq!(delta.action, action);
621 assert_eq!(delta.order.price, price);
622 assert_eq!(delta.order.size, size);
623 assert_eq!(delta.order.side, side.into());
624 assert_eq!(delta.order.order_id, order_id);
625 assert_eq!(delta.flags, flags);
626 assert_eq!(delta.sequence, sequence);
627 assert_eq!(delta.ts_event, ts_event);
628 assert_eq!(delta.ts_init, ts_init);
629 }
630
631 #[rstest]
632 fn test_clear() {
633 let instrument_id = InstrumentId::from("AAPL.XNAS");
634 let sequence = 1;
635 let ts_event = 2;
636 let ts_init = 3;
637
638 let delta = OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
639
640 assert_eq!(delta.instrument_id, instrument_id);
641 assert_eq!(delta.action, BookAction::Clear);
642 assert!(delta.order.price.is_zero());
643 assert!(delta.order.size.is_zero());
644 assert_eq!(delta.order.side, None);
645 assert_eq!(delta.order.order_id, 0);
646 assert_eq!(delta.flags, 32);
647 assert_eq!(delta.sequence, sequence);
648 assert_eq!(delta.ts_event, ts_event);
649 assert_eq!(delta.ts_init, ts_init);
650 }
651
652 #[rstest]
653 fn test_order_book_delta_hash() {
654 let delta1 = create_test_delta();
655 let delta2 = create_test_delta();
656
657 let mut hasher1 = DefaultHasher::new();
658 let mut hasher2 = DefaultHasher::new();
659
660 delta1.hash(&mut hasher1);
661 delta2.hash(&mut hasher2);
662
663 assert_eq!(hasher1.finish(), hasher2.finish());
664 }
665
666 #[rstest]
667 fn test_order_book_delta_hash_different_deltas() {
668 let delta1 = create_test_delta();
669 let order2 = BookOrder::new(
670 OrderSide::Sell,
671 Price::from("1.0505"),
672 Quantity::from("50000"),
673 67890,
674 );
675 let delta2 = OrderBookDelta::new(
676 InstrumentId::from("EURUSD.SIM"),
677 BookAction::Add,
678 order2,
679 0,
680 123,
681 UnixNanos::from(1_000_000_000),
682 UnixNanos::from(2_000_000_000),
683 );
684
685 let mut hasher1 = DefaultHasher::new();
686 let mut hasher2 = DefaultHasher::new();
687
688 delta1.hash(&mut hasher1);
689 delta2.hash(&mut hasher2);
690
691 assert_ne!(hasher1.finish(), hasher2.finish());
692 }
693
694 #[rstest]
695 fn test_order_book_delta_partial_eq() {
696 let delta1 = create_test_delta();
697 let delta2 = create_test_delta();
698
699 assert_eq!(delta1, delta2);
701
702 let order3 = BookOrder::new(
704 OrderSide::Buy,
705 Price::from("1.0500"),
706 Quantity::from("100000"),
707 12345,
708 );
709 let delta3 = OrderBookDelta::new(
710 InstrumentId::from("GBPUSD.SIM"),
711 BookAction::Add,
712 order3,
713 0,
714 123,
715 UnixNanos::from(1_000_000_000),
716 UnixNanos::from(2_000_000_000),
717 );
718
719 assert_ne!(delta1, delta3);
720 }
721
722 #[rstest]
723 fn test_order_book_delta_clone() {
724 let delta1 = create_test_delta();
725 let delta2 = delta1;
726
727 assert_eq!(delta1, delta2);
728 assert_eq!(delta1.instrument_id, delta2.instrument_id);
729 assert_eq!(delta1.action, delta2.action);
730 assert_eq!(delta1.order, delta2.order);
731 assert_eq!(delta1.flags, delta2.flags);
732 assert_eq!(delta1.sequence, delta2.sequence);
733 assert_eq!(delta1.ts_event, delta2.ts_event);
734 assert_eq!(delta1.ts_init, delta2.ts_init);
735 }
736
737 #[rstest]
738 fn test_order_book_delta_debug() {
739 let delta = create_test_delta();
740 let debug_str = format!("{delta:?}");
741
742 assert!(debug_str.contains("OrderBookDelta"));
743 assert!(debug_str.contains("EURUSD.SIM"));
744 assert!(debug_str.contains("Add"));
745 assert!(debug_str.contains("BUY"));
746 assert!(debug_str.contains("1.0500"));
747 }
748
749 #[rstest]
750 fn test_order_book_delta_serialization() {
751 let delta = create_test_delta();
752
753 let json = serde_json::to_string(&delta).unwrap();
754 let deserialized: OrderBookDelta = serde_json::from_str(&json).unwrap();
755
756 assert_order_book_delta_fields(&delta, &deserialized);
757 }
758
759 #[rstest]
760 fn test_json_serialization(stub_delta: OrderBookDelta) {
761 let delta = stub_delta;
762 let serialized = delta.to_json_bytes().unwrap();
763 let deserialized = OrderBookDelta::from_json_bytes(serialized.as_ref()).unwrap();
764 assert_order_book_delta_fields(&delta, &deserialized);
765 }
766
767 #[rstest]
768 fn test_msgpack_serialization(stub_delta: OrderBookDelta) {
769 let delta = stub_delta;
770 let serialized = delta.to_msgpack_bytes().unwrap();
771 let deserialized = OrderBookDelta::from_msgpack_bytes(serialized.as_ref()).unwrap();
772 assert_order_book_delta_fields(&delta, &deserialized);
773 }
774
775 fn assert_order_book_delta_fields(expected: &OrderBookDelta, actual: &OrderBookDelta) {
776 assert_eq!(expected.instrument_id, actual.instrument_id);
777 assert_eq!(expected.action, actual.action);
778 assert_eq!(expected.order.side, actual.order.side);
779 assert_eq!(expected.order.price, actual.order.price);
780 assert_eq!(expected.order.size, actual.order.size);
781 assert_eq!(expected.order.order_id, actual.order.order_id);
782 assert_eq!(expected.flags, actual.flags);
783 assert_eq!(expected.sequence, actual.sequence);
784 assert_eq!(expected.ts_event, actual.ts_event);
785 assert_eq!(expected.ts_init, actual.ts_init);
786 }
787}