1use super::{
4 Datafeed, DepthHistory, DepthPage, DepthRequest, FeedError, HistoryPage, HistoryRequest,
5 OrderEvent, RecordingReader, Reply, RestingOrder, Sink, Subscription, SubscriptionId,
6};
7use crate::model::{Instrument, Level1Quote, OrderBook, Tick};
8use chrono::{DateTime, TimeDelta, Utc};
9use std::collections::{HashMap, HashSet};
10use std::io::{self, Read};
11use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
12use std::time::Duration;
13
14#[derive(Clone, Debug, PartialEq)]
16#[non_exhaustive]
17pub enum MarketEvent {
18 Trade(Tick),
20 Quote(Level1Quote),
22 DepthSnapshot {
24 sequence: u64,
26 book: OrderBook,
28 },
29 DepthDelta {
31 sequence: u64,
33 book: OrderBook,
35 },
36 OrderSnapshot {
38 sequence: u64,
40 orders: Vec<RestingOrder>,
42 },
43 Order {
45 sequence: u64,
47 event: OrderEvent,
49 },
50}
51
52#[derive(Clone, Debug, PartialEq)]
55#[non_exhaustive]
56pub struct ReplayEvent {
57 pub symbol: String,
59 pub time: DateTime<Utc>,
61 pub sequence: u64,
63 pub event: MarketEvent,
65}
66
67impl ReplayEvent {
68 pub fn new(
70 symbol: impl Into<String>,
71 time: DateTime<Utc>,
72 sequence: u64,
73 event: MarketEvent,
74 ) -> Self {
75 Self {
76 symbol: symbol.into(),
77 time,
78 sequence,
79 event,
80 }
81 }
82
83 pub fn push_to(&self, sink: &Sink) {
86 sink.set_server_time(self.time);
87 match &self.event {
88 MarketEvent::Trade(trade) => sink.push_tick(trade.clone()),
89 MarketEvent::Quote(quote) => sink.push_quote(quote.clone()),
90 MarketEvent::DepthSnapshot { sequence, book } => {
91 sink.push_depth_snapshot(*sequence, book.clone())
92 }
93 MarketEvent::DepthDelta { sequence, book } => sink.push_depth_delta(*sequence, book),
94 MarketEvent::OrderSnapshot { sequence, orders } => {
95 sink.push_order_snapshot(*sequence, self.time, orders.clone())
96 }
97 MarketEvent::Order { sequence, event } => {
98 sink.push_order_event(*sequence, self.time, event.clone())
99 }
100 }
101 }
102
103 fn book_event(&self) -> bool {
104 matches!(
105 self.event,
106 MarketEvent::DepthSnapshot { .. }
107 | MarketEvent::DepthDelta { .. }
108 | MarketEvent::OrderSnapshot { .. }
109 | MarketEvent::Order { .. }
110 )
111 }
112 fn snapshot(&self) -> bool {
113 matches!(
114 self.event,
115 MarketEvent::DepthSnapshot { .. } | MarketEvent::OrderSnapshot { .. }
116 )
117 }
118}
119
120#[derive(Clone, Debug)]
137pub struct ReplayClock {
138 events: Arc<[ReplayEvent]>,
139 cursor: usize,
140 time: DateTime<Utc>,
141 speed: f64,
142 fractional_nanos: f64,
143}
144
145impl ReplayClock {
146 pub fn new(mut events: Vec<ReplayEvent>) -> Result<Self, FeedError> {
150 events
151 .sort_by(|a, b| (a.time, a.sequence, &a.symbol).cmp(&(b.time, b.sequence, &b.symbol)));
152 if let Some(event) = events.iter().find(|event| !valid(event)) {
153 return Err(FeedError::InvalidData(format!(
154 "replay event of {:?} at {} with sequence {} is invalid",
155 event.symbol, event.time, event.sequence
156 )));
157 }
158 if let Some(pair) = events.windows(2).find(|w| {
159 (w[0].time, w[0].sequence, &w[0].symbol) == (w[1].time, w[1].sequence, &w[1].symbol)
160 }) {
161 let event = &pair[0];
162 return Err(FeedError::InvalidData(format!(
163 "replay events of {:?} at {} share sequence {}",
164 event.symbol, event.time, event.sequence
165 )));
166 }
167 let time = events.first().map_or(DateTime::default(), |e| e.time);
168 Ok(Self {
169 events: events.into(),
170 cursor: 0,
171 time,
172 speed: 1.0,
173 fractional_nanos: 0.0,
174 })
175 }
176
177 pub fn time(&self) -> DateTime<Utc> {
179 self.time
180 }
181 pub fn speed(&self) -> f64 {
183 self.speed
184 }
185 pub fn set_speed(&mut self, speed: f64) -> Result<(), FeedError> {
187 if !speed.is_finite() || speed < 0.0 {
188 return Err(FeedError::InvalidData(format!(
189 "replay speed must be finite and at least 0, got {speed:?}"
190 )));
191 }
192 self.speed = speed;
193 Ok(())
194 }
195 pub fn is_finished(&self) -> bool {
197 self.cursor == self.events.len()
198 }
199
200 pub fn advance(&mut self, elapsed: Duration) -> Result<&[ReplayEvent], FeedError> {
204 if self.speed == 0.0 {
205 return Ok(&[]);
206 }
207 let nanos = elapsed.as_secs_f64() * self.speed * 1e9 + self.fractional_nanos;
208 if !nanos.is_finite() || nanos >= i64::MAX as f64 {
209 return Err(FeedError::InvalidData(
210 "replay elapsed time overflow".into(),
211 ));
212 }
213 let time = self
214 .time
215 .checked_add_signed(TimeDelta::nanoseconds(nanos as i64))
216 .ok_or_else(|| FeedError::InvalidData("replay clock overflow".into()))?;
217 self.fractional_nanos = nanos.fract();
218 self.time = time;
219 let start = self.cursor;
220 self.cursor = self.events.partition_point(|e| e.time <= time);
221 Ok(&self.events[start..self.cursor])
222 }
223
224 fn position(&mut self, time: DateTime<Utc>) {
225 self.time = time;
226 self.fractional_nanos = 0.0;
227 self.cursor = self.events.partition_point(|event| event.time <= time);
228 }
229
230 pub fn seek(&mut self, time: DateTime<Utc>) -> impl Iterator<Item = ReplayEvent> {
235 self.position(time);
236 let events = &self.events[..self.cursor];
237 let mut anchors = HashMap::new();
238 for (at, event) in events.iter().enumerate().filter(|(_, e)| e.snapshot()) {
239 anchors.insert(event.symbol.as_str(), at);
240 }
241 events.iter().enumerate().filter_map(move |(at, event)| {
242 if event.book_event()
243 && anchors
244 .get(event.symbol.as_str())
245 .is_some_and(|anchor| at < *anchor)
246 {
247 if let MarketEvent::Order {
248 event:
249 OrderEvent::Execute {
250 id,
251 trade: Some(trade),
252 ..
253 },
254 ..
255 } = &event.event
256 {
257 let trade = trade.clone().with_resting_order_id(*id);
258 return Some(ReplayEvent::new(
259 &event.symbol,
260 event.time,
261 event.sequence,
262 MarketEvent::Trade(trade),
263 ));
264 }
265 return None;
266 }
267 Some(event.clone())
268 })
269 }
270}
271
272pub struct ReplayDriver {
297 state: Mutex<Playback>,
298 dispatch: Mutex<()>,
300}
301
302struct Playback {
303 clock: ReplayClock,
304 instruments: HashMap<String, Instrument>,
305 subscriptions: HashMap<SubscriptionId, (String, Sink)>,
306}
307
308impl ReplayDriver {
309 pub fn new(events: Vec<ReplayEvent>, instruments: Vec<Instrument>) -> Result<Self, FeedError> {
313 let count = instruments.len();
314 let instruments: HashMap<_, _> = instruments
315 .into_iter()
316 .map(|instrument| (instrument.symbol.clone(), instrument))
317 .collect();
318 if count != instruments.len()
319 || events
320 .iter()
321 .any(|event| !instruments.contains_key(&event.symbol))
322 {
323 return Err(FeedError::InvalidData(
324 "missing or duplicate replay instrument".into(),
325 ));
326 }
327 Ok(Self {
328 state: Mutex::new(Playback {
329 clock: ReplayClock::new(events)?,
330 instruments,
331 subscriptions: HashMap::new(),
332 }),
333 dispatch: Mutex::new(()),
334 })
335 }
336
337 pub fn load(reader: impl Read, instruments: Vec<Instrument>) -> io::Result<Self> {
340 let mut reader = RecordingReader::new(reader)?;
341 let mut events = Vec::new();
342 while let Some(event) = reader.read()? {
343 events.push(event);
344 }
345 Self::new(events, instruments)
346 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
347 }
348
349 fn state(&self) -> MutexGuard<'_, Playback> {
350 self.state.lock().unwrap_or_else(PoisonError::into_inner)
351 }
352
353 pub fn time(&self) -> DateTime<Utc> {
355 self.state().clock.time()
356 }
357
358 pub fn time_range(&self) -> Option<(DateTime<Utc>, DateTime<Utc>)> {
360 let state = self.state();
361 Some((
362 state.clock.events.first()?.time,
363 state.clock.events.last()?.time,
364 ))
365 }
366
367 pub fn speed(&self) -> f64 {
369 self.state().clock.speed()
370 }
371
372 pub fn set_speed(&self, speed: f64) -> Result<(), FeedError> {
374 self.state().clock.set_speed(speed)
375 }
376
377 pub fn is_finished(&self) -> bool {
379 self.state().clock.is_finished()
380 }
381
382 pub fn advance(&self, elapsed: Duration) -> Result<(), FeedError> {
386 let _dispatch = self.dispatch.lock().unwrap_or_else(PoisonError::into_inner);
387 let (events, start, end, time, sinks) = {
388 let mut state = self.state();
389 let count = state.clock.advance(elapsed)?.len();
390 let end = state.clock.cursor;
391 (
392 Arc::clone(&state.clock.events),
393 end - count,
394 end,
395 state.clock.time(),
396 state.sinks(),
397 )
398 };
399 for event in &events[start..end] {
400 for (symbol, sink) in &sinks {
401 if *symbol == event.symbol {
402 event.push_to(sink);
403 }
404 }
405 }
406 publish_time(time, &sinks);
407 Ok(())
408 }
409
410 pub fn seek(&self, time: DateTime<Utc>) {
415 let _dispatch = self.dispatch.lock().unwrap_or_else(PoisonError::into_inner);
416 let (events, end, sinks) = {
417 let mut state = self.state();
418 state.clock.position(time);
419 (
420 Arc::clone(&state.clock.events),
421 state.clock.cursor,
422 state.sinks(),
423 )
424 };
425 for (symbol, sink) in &sinks {
426 rebuild(&events[..end], symbol, sink, time);
427 }
428 publish_time(time, &sinks);
429 }
430}
431
432impl Playback {
433 fn sinks(&mut self) -> Vec<(String, Sink)> {
434 self.subscriptions.retain(|_, (_, sink)| sink.is_active());
435 self.subscriptions.values().cloned().collect()
436 }
437}
438
439fn rebuild(events: &[ReplayEvent], symbol: &str, sink: &Sink, time: DateTime<Utc>) {
440 sink.reset_replay(time);
441 for event in events {
442 if event.symbol == symbol {
443 event.push_to(sink);
444 }
445 }
446}
447
448fn publish_time(time: DateTime<Utc>, sinks: &[(String, Sink)]) {
449 for (_, sink) in sinks {
450 sink.set_server_time(time);
451 sink.advance_time(time);
452 }
453}
454
455impl Datafeed for ReplayDriver {
456 fn resolve(&self, symbol: &str, reply: Reply<Instrument>) {
457 let instrument = self
458 .state()
459 .instruments
460 .get(symbol)
461 .cloned()
462 .ok_or(FeedError::NotSupported);
463 reply.send(instrument);
464 }
465
466 fn history(&self, _: HistoryRequest, reply: Reply<HistoryPage>) {
467 reply.send(Ok(HistoryPage::new(Vec::new(), true)));
469 }
470
471 fn subscribe(&self, subscription: Subscription) {
472 let _dispatch = self.dispatch.lock().unwrap_or_else(PoisonError::into_inner);
473 let position = {
474 let mut state = self.state();
475 if !state.instruments.contains_key(&subscription.symbol)
476 || subscription.conversion.is_some()
477 {
478 None
479 } else {
480 state.subscriptions.insert(
481 subscription.id,
482 (subscription.symbol.clone(), subscription.sink.clone()),
483 );
484 Some((
485 Arc::clone(&state.clock.events),
486 state.clock.cursor,
487 state.clock.time(),
488 ))
489 }
490 };
491 let Some((events, end, time)) = position else {
492 subscription.sink.report_error(FeedError::NotSupported);
493 return;
494 };
495 rebuild(
496 &events[..end],
497 &subscription.symbol,
498 &subscription.sink,
499 time,
500 );
501 publish_time(time, &[(subscription.symbol, subscription.sink)]);
502 }
503
504 fn unsubscribe(&self, id: SubscriptionId) {
505 self.state().subscriptions.remove(&id);
506 }
507
508 fn depth_history(&self, request: DepthRequest, reply: Reply<DepthPage>) {
509 let _dispatch = self.dispatch.lock().unwrap_or_else(PoisonError::into_inner);
510 if request.conversion.is_some()
511 || !request.tick_size.is_finite()
512 || request.tick_size <= 0.0
513 {
514 reply.send(Err(FeedError::NotSupported));
515 return;
516 }
517 if let Err(error) = DepthHistory::new(request.config.clone()) {
518 reply.send(Err(error));
519 return;
520 }
521 let (events, end, time) = {
522 let state = self.state();
523 let time = state.clock.time();
524 let horizon = request
525 .before
526 .and_then(|before| before.checked_add_signed(request.config.cadence))
527 .map_or(time, |before| before.min(time));
528 let end = state
529 .clock
530 .events
531 .partition_point(|event| event.time <= horizon)
532 .min(state.clock.cursor);
533 (Arc::clone(&state.clock.events), end, horizon)
534 };
535 let inbox = Arc::new(super::inbox::Inbox::default());
536 let sink = inbox.subscribe(
537 request.symbol.clone(),
538 crate::model::Timeframe::Min1.into(),
539 0,
540 Some(request.tick_size),
541 );
542 inbox.sample_depth(sink.id, Some(request.config));
543 sink.reset_replay(time);
544 for event in &events[..end] {
545 if event.symbol == request.symbol && event.book_event() {
546 event.push_to(&sink);
547 }
548 }
549 sink.set_server_time(time);
550 sink.advance_time(time);
551 let (_, updates) = inbox
552 .drain()
553 .updates
554 .pop()
555 .expect("replay depth subscription");
556 let history = updates.replay_depth.expect("replay depth history");
557 let mut columns: Vec<_> = history
558 .columns()
559 .filter(|column| {
560 request.before.is_none_or(|before| column.time < before)
561 && request.since.is_none_or(|since| column.time >= since)
562 })
563 .cloned()
564 .collect();
565 let start = columns.len().saturating_sub(request.count);
566 reply.send(Ok(DepthPage::new(columns.split_off(start), start == 0)));
567 }
568}
569
570pub(super) fn valid(event: &ReplayEvent) -> bool {
571 match &event.event {
572 MarketEvent::Trade(t) => t.ts == event.time && super::trades::valid(t),
573 MarketEvent::Quote(q) => {
574 q.ts == event.time
575 && q.symbol == event.symbol
576 && [q.bid, q.ask, q.bid_size, q.ask_size]
577 .iter()
578 .all(|v| v.is_finite())
579 && q.bid_size >= 0.0
580 && q.ask_size >= 0.0
581 }
582 MarketEvent::DepthSnapshot { book, .. } | MarketEvent::DepthDelta { book, .. } => {
583 book.ts == event.time
584 && book.symbol == event.symbol
585 && book.bids.len().max(book.asks.len()) <= super::depth::MAX_LEVELS
586 && book
587 .bids
588 .iter()
589 .chain(&book.asks)
590 .all(|l| l.price.is_finite() && l.size.is_finite() && l.size >= 0.0)
591 }
592 MarketEvent::OrderSnapshot { orders, .. } => {
593 let bids = orders
594 .iter()
595 .filter(|o| o.side == crate::model::OrderSide::Buy)
596 .count();
597 let mut ids = HashSet::new();
598 bids <= super::depth::MAX_LEVELS
599 && orders.len() - bids <= super::depth::MAX_LEVELS
600 && orders.iter().all(|o| valid_order(o) && ids.insert(o.id))
601 }
602 MarketEvent::Order { event: order, .. } => match order {
603 OrderEvent::Add(order) => valid_order(order),
604 OrderEvent::Modify { price, size, .. } => {
605 price.is_none_or(f64::is_finite) && size.is_none_or(|s| s.is_finite() && s >= 0.0)
606 }
607 OrderEvent::Cancel { .. } => true,
608 OrderEvent::Execute { size, trade, .. } => {
609 size.is_finite()
610 && *size > 0.0
611 && trade.as_ref().is_none_or(|t| {
612 t.ts == event.time && super::trades::valid(t) && t.size == *size
613 })
614 }
615 },
616 }
617}
618
619fn valid_order(order: &RestingOrder) -> bool {
620 order.price.is_finite() && order.size.is_finite() && order.size > 0.0
621}
622
623#[cfg(test)]
624mod tests {
625 use super::super::inbox::Inbox;
626 use super::*;
627 use crate::model::{OrderBookLevel, OrderSide, Timeframe};
628 use std::sync::Arc;
629
630 fn at(s: i64) -> DateTime<Utc> {
631 DateTime::from_timestamp(s, 0).unwrap()
632 }
633 fn trade(symbol: &str, s: i64, seq: u64, price: f64) -> ReplayEvent {
634 ReplayEvent::new(
635 symbol,
636 at(s),
637 seq,
638 MarketEvent::Trade(Tick::new(at(s), price, 1.0, Some(OrderSide::Buy)).with_id(seq)),
639 )
640 }
641 fn snapshot(s: i64, seq: u64, size: f64) -> ReplayEvent {
642 ReplayEvent::new(
643 "A",
644 at(s),
645 seq,
646 MarketEvent::DepthSnapshot {
647 sequence: seq,
648 book: OrderBook {
649 ts: at(s),
650 symbol: "A".into(),
651 bids: vec![OrderBookLevel {
652 price: 99.0,
653 size,
654 order_cnt: None,
655 }],
656 asks: vec![],
657 },
658 },
659 )
660 }
661
662 #[test]
663 fn shared_clock_orders_events_scales_elapsed_time_and_seeks_from_snapshots() {
664 let events = vec![
665 trade("B", 2, 3, 20.0),
666 snapshot(0, 0, 5.0),
667 trade("A", 1, 2, 11.0),
668 trade("A", 1, 1, 10.0),
669 snapshot(2, 4, 7.0),
670 trade("A", 3, 5, 12.0),
671 ];
672 let mut clock = ReplayClock::new(events).unwrap();
673 clock.set_speed(2.0).unwrap();
674 let due: Vec<_> = clock
675 .advance(Duration::from_millis(500))
676 .unwrap()
677 .iter()
678 .map(|e| (e.symbol.as_str(), e.sequence))
679 .collect();
680 assert_eq!(due, [("A", 0), ("A", 1), ("A", 2)]);
681 clock.set_speed(0.0).unwrap();
682 assert!(clock.advance(Duration::from_secs(10)).unwrap().is_empty());
683 assert_eq!(clock.time(), at(1));
684 clock.set_speed(1.0).unwrap();
685 let inbox = Arc::new(Inbox::default());
686 let a = inbox.subscribe("A".into(), Timeframe::Min1.into(), 100, Some(1.0));
687 let b = inbox.subscribe("B".into(), Timeframe::Min1.into(), 100, Some(1.0));
688 for cutoff in [3, 1, 2, -1, 3] {
689 a.reset_replay(at(cutoff));
690 b.reset_replay(at(cutoff));
691 let due: Vec<_> = clock.seek(at(cutoff)).collect();
692 if cutoff >= 2 {
693 assert!(!due.iter().any(|e| e.sequence == 0));
694 }
695 for event in due {
696 event.push_to(if event.symbol == "A" { &a } else { &b });
697 }
698 let updates = inbox.drain().updates;
699 let a = &updates.iter().find(|(id, _)| *id == a.id).unwrap().1;
700 let b = &updates.iter().find(|(id, _)| *id == b.id).unwrap().1;
701 assert!(a.reset && b.reset);
702 assert_eq!(
703 a.trades.iter().count(),
704 if cutoff >= 3 {
705 3
706 } else if cutoff >= 1 {
707 2
708 } else {
709 0
710 }
711 );
712 assert_eq!(b.trades.iter().count(), usize::from(cutoff >= 2));
713 if cutoff >= 0 {
714 assert_eq!(
715 a.depth.as_ref().unwrap().bids[0].size,
716 if cutoff >= 2 { 7.0 } else { 5.0 }
717 );
718 }
719 }
720 assert!(clock.is_finished());
721 assert!(clock.set_speed(f64::NAN).is_err());
722 let previous = clock.time();
723 assert!(clock.advance(Duration::MAX).is_err());
724 assert_eq!(clock.time(), previous);
725 let order = RestingOrder::new(9, OrderSide::Sell, 100.0, 4.0);
726 let mut orders = ReplayClock::new(vec![
727 ReplayEvent::new(
728 "A",
729 at(0),
730 0,
731 MarketEvent::OrderSnapshot {
732 sequence: 0,
733 orders: vec![order.clone()],
734 },
735 ),
736 ReplayEvent::new(
737 "A",
738 at(1),
739 1,
740 MarketEvent::Order {
741 sequence: 1,
742 event: OrderEvent::Execute {
743 id: 9,
744 size: 2.0,
745 trade: Some(Tick::new(at(1), 100.0, 2.0, Some(OrderSide::Buy)).with_id(55)),
746 },
747 },
748 ),
749 ReplayEvent::new(
750 "A",
751 at(2),
752 2,
753 MarketEvent::OrderSnapshot {
754 sequence: 2,
755 orders: vec![RestingOrder { size: 2.0, ..order }],
756 },
757 ),
758 ])
759 .unwrap();
760 a.reset_replay(at(2));
761 for event in orders.seek(at(2)) {
762 event.push_to(&a);
763 }
764 let (_, updates) = inbox
765 .drain()
766 .updates
767 .into_iter()
768 .find(|(id, _)| *id == a.id)
769 .unwrap();
770 let fills: Vec<_> = updates.trades.iter().collect();
771 assert_eq!((fills.len(), fills[0].resting_order_id), (1, Some(9)));
772 assert_eq!(updates.orders.unwrap().orders().next().unwrap().size, 2.0);
773 let mut fractional = ReplayClock::new(vec![trade("A", 0, 0, 1.0)]).unwrap();
774 fractional.set_speed(0.5).unwrap();
775 fractional.advance(Duration::from_nanos(1)).unwrap();
776 assert_eq!(fractional.time(), at(0));
777 fractional.advance(Duration::from_nanos(1)).unwrap();
778 assert_eq!(fractional.time(), at(0) + TimeDelta::nanoseconds(1));
779 }
780
781 #[test]
782 fn recording_driver_routes_two_symbols_and_rewinds_bars_quotes_books_and_heatmap() {
783 use super::super::{DepthHistoryConfig, RecordingWriter};
784 let order = RestingOrder::new(9, OrderSide::Sell, 20.0, 6.0)
785 .with_participant(42)
786 .with_placed(at(-30));
787 let quote = |symbol: &str, time, sequence, bid| {
788 ReplayEvent::new(
789 symbol,
790 at(time),
791 sequence,
792 MarketEvent::Quote(Level1Quote {
793 ts: at(time),
794 symbol: symbol.into(),
795 bid,
796 ask: bid + 1.0,
797 bid_size: 3.0,
798 ask_size: 4.0,
799 }),
800 )
801 };
802 let delta = |time, sequence, size| {
803 let mut event = snapshot(time, sequence, size);
804 if let MarketEvent::DepthSnapshot { sequence, book } = event.event {
805 event.event = MarketEvent::DepthDelta { sequence, book };
806 }
807 event
808 };
809 let events = vec![
810 snapshot(0, 0, 5.0),
811 ReplayEvent::new(
812 "B",
813 at(0),
814 0,
815 MarketEvent::OrderSnapshot {
816 sequence: 0,
817 orders: vec![order.clone()],
818 },
819 ),
820 trade("A", 0, 10, 10.0),
821 trade("B", 0, 10, 20.0),
822 delta(1, 1, 7.0),
823 ReplayEvent::new(
824 "B",
825 at(1),
826 1,
827 MarketEvent::Order {
828 sequence: 1,
829 event: OrderEvent::Execute {
830 id: 9,
831 size: 2.0,
832 trade: Some(Tick::new(at(1), 20.0, 2.0, Some(OrderSide::Buy)).with_id(55)),
833 },
834 },
835 ),
836 quote("A", 1, 20, 9.0),
837 quote("B", 1, 20, 19.0),
838 delta(2, 2, 8.0),
839 trade("A", 2, 11, 12.0),
840 ReplayEvent::new(
841 "B",
842 at(2),
843 2,
844 MarketEvent::Order {
845 sequence: 2,
846 event: OrderEvent::Modify {
847 id: 9,
848 price: None,
849 size: Some(3.0),
850 keep_priority: true,
851 },
852 },
853 ),
854 snapshot(3, 3, 99.0),
855 ReplayEvent::new(
856 "B",
857 at(3),
858 3,
859 MarketEvent::OrderSnapshot {
860 sequence: 3,
861 orders: vec![RestingOrder {
862 size: 99.0,
863 ..order
864 }],
865 },
866 ),
867 quote("A", 3, 20, 98.0),
868 trade("A", 4, 12, 99.0),
869 ];
870 let mut writer = RecordingWriter::new(Vec::new()).unwrap();
871 writer.capture(events.iter().cloned().map(Ok)).unwrap();
872 let bytes = writer.finish().unwrap();
873 let load = || {
874 ReplayDriver::load(
875 bytes.as_slice(),
876 vec![
877 Instrument::new("A").with_tick_size(1.0),
878 Instrument::new("B").with_tick_size(1.0),
879 ],
880 )
881 .unwrap()
882 };
883 let connect = |driver: &ReplayDriver| {
884 let inbox = Arc::new(Inbox::default());
885 let mut sinks = Vec::new();
886 for symbol in ["A", "B"] {
887 let sink = inbox.subscribe(symbol.into(), Timeframe::Min1.into(), 100, Some(1.0));
888 inbox.sample_depth(sink.id, Some(DepthHistoryConfig::default()));
889 driver.subscribe(Subscription {
890 id: sink.id,
891 symbol: symbol.into(),
892 timeframe: Timeframe::Min1,
893 clock: Timeframe::Min1.into(),
894 extended_hours: true,
895 conversion: None,
896 sink: sink.clone(),
897 });
898 sinks.push(sink);
899 }
900 inbox.drain();
901 (inbox, sinks)
902 };
903 let state = |inbox: &Inbox, sinks: &[Sink]| {
904 let mut updates = inbox.drain().updates;
905 sinks
906 .iter()
907 .map(|sink| {
908 let (_, update) = updates
909 .swap_remove(updates.iter().position(|(id, _)| *id == sink.id).unwrap());
910 assert_eq!(
911 (update.rejected, update.rejected_depth, update.late_ticks),
912 (0, 0, 0)
913 );
914 (
915 update
916 .bars
917 .into_values()
918 .map(|(bar, _)| bar)
919 .collect::<Vec<_>>(),
920 update.trades.iter().cloned().collect::<Vec<_>>(),
921 update.quote,
922 update.depth,
923 update
924 .orders
925 .map(|queue| queue.orders().cloned().collect::<Vec<_>>()),
926 update
927 .replay_depth
928 .map(|history| history.columns().cloned().collect())
929 .unwrap_or(update.depth_columns),
930 update.time,
931 )
932 })
933 .collect::<Vec<_>>()
934 };
935 let fresh = load();
936 let (fresh_inbox, fresh_sinks) = connect(&fresh);
937 fresh.set_speed(2.0).unwrap();
938 fresh.advance(Duration::from_secs(1)).unwrap(); let expected = state(&fresh_inbox, &fresh_sinks);
940 assert_eq!(
941 expected[0].0,
942 [crate::model::Bar::new(at(0), 10.0, 12.0, 10.0, 12.0, 2.0)]
943 );
944 assert_eq!(
945 expected[1].0,
946 [crate::model::Bar::new(at(0), 20.0, 20.0, 20.0, 20.0, 3.0)]
947 );
948 assert_eq!(expected[0].3.as_ref().unwrap().bids[0].size, 8.0);
949 assert_eq!(expected[1].4.as_ref().unwrap()[0].size, 3.0);
950 assert_eq!(expected[1].4.as_ref().unwrap()[0].placed, Some(at(-30)));
951 assert_eq!(
952 expected[0]
953 .5
954 .iter()
955 .map(|column| column.bids[0].1)
956 .collect::<Vec<_>>(),
957 [5.0, 7.0]
958 );
959 assert_eq!(
960 expected[1]
961 .5
962 .iter()
963 .map(|column| column.asks[0].1)
964 .collect::<Vec<_>>(),
965 [6.0, 4.0]
966 );
967 let replay = load();
968 let (inbox, sinks) = connect(&replay);
969 let fresh_later = load();
970 let (later_inbox, later_sinks) = connect(&fresh_later);
971 fresh_later.advance(Duration::from_secs(4)).unwrap();
972 let later = state(&later_inbox, &later_sinks);
973 assert_eq!(later[1].5.back().unwrap().time, at(3));
974 assert_eq!(
975 later[1].5.back().unwrap().asks[0].1,
976 99.0,
977 "the shared clock must sample a quiet symbol's standing book"
978 );
979 replay.seek(at(4));
980 assert_eq!(
981 state(&inbox, &sinks),
982 later,
983 "seek must retain heatmap history before the latest snapshots"
984 );
985 replay.set_speed(0.0).unwrap();
986 replay.seek(at(2)); assert_eq!(state(&inbox, &sinks), expected);
988 replay.advance(Duration::from_secs(20)).unwrap();
989 assert_eq!(replay.time(), at(2));
990 assert!(
991 state(&inbox, &sinks)
992 .iter()
993 .all(|update| update.6 == Some(at(2)))
994 );
995 replay.set_speed(0.5).unwrap();
996 replay.advance(Duration::from_secs(1)).unwrap();
997 let quiet = at(2) + TimeDelta::milliseconds(500);
998 assert_eq!(replay.time(), quiet);
999 assert!(
1000 state(&inbox, &sinks)
1001 .iter()
1002 .all(|update| update.6 == Some(quiet))
1003 );
1004 replay.seek(at(-1));
1005 assert!(
1006 state(&inbox, &sinks)
1007 .iter()
1008 .all(|update| update.0.is_empty()
1009 && update.1.is_empty()
1010 && update.2.is_none()
1011 && update.3.is_none()
1012 && update.4.is_none()
1013 && update.5.is_empty())
1014 );
1015
1016 let mut capture = RecordingWriter::new(Vec::new()).unwrap();
1017 assert_eq!(
1018 capture
1019 .capture([
1020 Ok(events[0].clone()),
1021 Err(io::Error::new(
1022 io::ErrorKind::ConnectionReset,
1023 "capture stream failed"
1024 )),
1025 ])
1026 .unwrap_err()
1027 .kind(),
1028 io::ErrorKind::ConnectionReset
1029 );
1030 assert!(
1031 RecordingReader::new(capture.finish().unwrap().as_slice())
1032 .unwrap()
1033 .read()
1034 .unwrap()
1035 .is_some()
1036 );
1037
1038 struct FailedRead;
1039 impl Read for FailedRead {
1040 fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
1041 Err(io::Error::new(
1042 io::ErrorKind::ConnectionReset,
1043 "capture stream failed",
1044 ))
1045 }
1046 }
1047 assert_eq!(
1048 ReplayDriver::load(bytes.as_slice().chain(FailedRead), Vec::new())
1049 .err()
1050 .unwrap()
1051 .kind(),
1052 io::ErrorKind::ConnectionReset
1053 );
1054 }
1055
1056 #[cfg(not(target_arch = "wasm32"))]
1057 #[test]
1058 fn readouts_and_unsubscribe_do_not_wait_for_playback_wakers() {
1059 use std::sync::mpsc;
1060 let replay = Arc::new(
1061 ReplayDriver::new(vec![trade("A", 0, 0, 10.0)], vec![Instrument::new("A")]).unwrap(),
1062 );
1063 let inbox = Arc::new(Inbox::default());
1064 let sink = inbox.subscribe("A".into(), Timeframe::Min1.into(), 100, None);
1065 let id = sink.id;
1066 replay.subscribe(Subscription {
1067 id,
1068 symbol: "A".into(),
1069 timeframe: Timeframe::Min1,
1070 clock: Timeframe::Min1.into(),
1071 extended_hours: true,
1072 conversion: None,
1073 sink,
1074 });
1075 inbox.drain();
1076 let (woken, waking) = mpsc::channel();
1077 let (release, resume) = mpsc::channel();
1078 let resume = Mutex::new(resume);
1079 inbox.set_waker(move || {
1080 woken.send(()).unwrap();
1081 resume.lock().unwrap().recv().unwrap();
1082 });
1083 let producer = replay.clone();
1084 let pushing = std::thread::spawn(move || producer.advance(Duration::ZERO).unwrap());
1085 waking.recv_timeout(Duration::from_secs(5)).unwrap();
1086 let (finished, finish) = mpsc::channel();
1087 let removing = std::thread::spawn(move || {
1088 let time = replay.time();
1089 replay.unsubscribe(id);
1090 finished.send(time).unwrap();
1091 });
1092 let result = finish.recv_timeout(Duration::from_secs(5));
1093 release.send(()).unwrap();
1094 pushing.join().unwrap();
1095 removing.join().unwrap();
1096 assert_eq!(
1097 result.unwrap(),
1098 at(0),
1099 "a UI readout or drop must not wait on its own waker"
1100 );
1101 }
1102}