Skip to main content

egui_charts/data/
replay.rs

1//! One event clock for host-supplied trades, quotes and depth across instruments.
2
3use 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/// Recorded market data. Book sequencing is independent of the replay's merge sequence.
15#[derive(Clone, Debug, PartialEq)]
16#[non_exhaustive]
17pub enum MarketEvent {
18    /// One raw fill.
19    Trade(Tick),
20    /// Best bid and ask.
21    Quote(Level1Quote),
22    /// A periodic aggregated book snapshot, also a depth seek anchor.
23    DepthSnapshot {
24        /// Exchange sequence number for this book stream.
25        sequence: u64,
26        /// Aggregated price levels in this snapshot or delta.
27        book: OrderBook,
28    },
29    /// Changed aggregated levels.
30    DepthDelta {
31        /// Exchange sequence number for this book stream.
32        sequence: u64,
33        /// Aggregated price levels in this snapshot or delta.
34        book: OrderBook,
35    },
36    /// A periodic per-order snapshot in queue order, also a depth seek anchor.
37    OrderSnapshot {
38        /// Exchange sequence number for this book stream.
39        sequence: u64,
40        /// Resting orders in queue order.
41        orders: Vec<RestingOrder>,
42    },
43    /// One sequenced resting order change.
44    Order {
45        /// Exchange sequence number for this book stream.
46        sequence: u64,
47        /// Change to one resting order.
48        event: OrderEvent,
49    },
50}
51
52/// One instrument's event at an observation instant. The host supplies a merge sequence to
53/// break timestamp ties within a symbol, including ties between trades and book messages.
54#[derive(Clone, Debug, PartialEq)]
55#[non_exhaustive]
56pub struct ReplayEvent {
57    /// Instrument routed to the corresponding subscription by the host.
58    pub symbol: String,
59    /// Observation time used by the shared clock. Payload timestamps must agree.
60    pub time: DateTime<Utc>,
61    /// Tie breaker within a symbol; independent of exchange book sequence numbers.
62    pub sequence: u64,
63    /// Market data observed at this instant.
64    pub event: MarketEvent,
65}
66
67impl ReplayEvent {
68    /// An event with an explicit observation time and tie-breaking sequence.
69    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    /// Pushes into this symbol's sink using recorded market time. Hosts call this outside egui
84    /// context locks, exactly as with live feed pushes. Does not perform routing or disk I/O.
85    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/// A deterministic clock merging all instruments by time, sequence, then symbol. Advancing uses
121/// wall-clock elapsed time times the speed, never a bar index. Hosts own the input and scheduling.
122/// Seek rebuilds trades from the start and depth from the latest preceding snapshot per symbol.
123///
124/// ```
125/// use egui_charts::data::{MarketEvent, ReplayClock, ReplayEvent};
126/// use egui_charts::model::Tick;
127/// use std::time::Duration;
128/// let time = chrono::Utc::now();
129/// let event = ReplayEvent::new("A", time, 0,
130///     MarketEvent::Trade(Tick::new(time, 100.0, 2.0, None)));
131/// let mut clock = ReplayClock::new(vec![event]).unwrap();
132/// clock.set_speed(2.0).unwrap();
133/// assert_eq!(clock.advance(Duration::ZERO).unwrap().len(), 1);
134/// assert!(clock.is_finished());
135/// ```
136#[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    /// Sorts events into deterministic replay order. Duplicate time/sequence keys within one
147    /// symbol or disagreeing payload timestamps/symbols are invalid; equal keys across symbols
148    /// are ordered by symbol. Start at the first event, with no events consumed yet.
149    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    /// Current market time.
178    pub fn time(&self) -> DateTime<Utc> {
179        self.time
180    }
181    /// Playback multiplier; zero pauses the clock.
182    pub fn speed(&self) -> f64 {
183        self.speed
184    }
185    /// Sets a finite non-negative multiplier, preserving sub-nanosecond elapsed time.
186    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    /// Whether every recorded event has been consumed.
196    pub fn is_finished(&self) -> bool {
197        self.cursor == self.events.len()
198    }
199
200    /// Advances by real elapsed time at the selected speed, returning newly due events once,
201    /// including events at the new time. A paused clock consumes nothing. Overflow is an error
202    /// without changing the cursor or time. Sub-nanosecond progress is retained between calls.
203    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    /// Seeks inclusively to `time`. Before pushing these events, call `Sink::reset_replay` on
231    /// every participating sink. Trades rebuild bars from the beginning; book messages before
232    /// each symbol's latest snapshot are skipped. A symbol without a snapshot starts empty and
233    /// follows normal gap/resync rules. Subsequent `advance` returns only events after the seek.
234    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
272/// A recording-backed feed and playback driver for every symbol in one session.
273///
274/// Attach the same `Arc<ReplayDriver>` to each chart with `ChartBuilder::with_feed`. Call
275/// [`advance`](Self::advance) outside egui context locks before drawing, even while paused, to
276/// publish the shared time to quiet symbols. Zero speed pauses; seeking is inclusive. Hosts own
277/// capture, storage and scheduling. Bar aggregation, book gaps and trade deduplication use the
278/// live sink's rules. A new subscription reconstructs the current position, including when its
279/// interval changes. No history request can return events beyond the playback position.
280///
281/// ```
282/// use egui_charts::data::{MarketEvent, RecordingWriter, ReplayDriver, ReplayEvent};
283/// use egui_charts::model::{Instrument, Tick};
284/// use std::time::Duration;
285/// let time = chrono::Utc::now();
286/// let mut capture = RecordingWriter::new(Vec::new())?;
287/// capture.write(&ReplayEvent::new("A", time, 0,
288///     MarketEvent::Trade(Tick::new(time, 100.0, 2.0, None))))?;
289/// let bytes = capture.finish()?;
290/// let replay = ReplayDriver::load(bytes.as_slice(), vec![Instrument::new("A")])?;
291/// replay.advance(Duration::ZERO)?;
292/// replay.set_speed(0.0)?;
293/// replay.seek(time);
294/// # Ok::<(), Box<dyn std::error::Error>>(())
295/// ```
296pub struct ReplayDriver {
297    state: Mutex<Playback>,
298    // Serialize pushes without blocking readouts or unsubscribe while a waker waits on egui.
299    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    /// Validates a complete recording and its host-supplied instrument metadata. Every recorded
310    /// symbol must have exactly one instrument; its tick size is needed for price-based bars.
311    /// Playback starts at the first event, with none consumed, at normal speed.
312    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    /// Loads the same stream that [`super::RecordingWriter`] writes. Read errors retain their
338    /// original kind; malformed recordings and duplicate merge keys report `InvalidData`.
339    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    /// The inclusive playback position shared by all subscriptions.
354    pub fn time(&self) -> DateTime<Utc> {
355        self.state().clock.time()
356    }
357
358    /// First and last recorded observation instants, for transport bounds; `None` when empty.
359    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    /// Playback multiplier; zero pauses.
368    pub fn speed(&self) -> f64 {
369        self.state().clock.speed()
370    }
371
372    /// Sets a finite, non-negative playback multiplier.
373    pub fn set_speed(&self, speed: f64) -> Result<(), FeedError> {
374        self.state().clock.set_speed(speed)
375    }
376
377    /// Whether every event has been published.
378    pub fn is_finished(&self) -> bool {
379        self.state().clock.is_finished()
380    }
381
382    /// Advances once by wall-clock elapsed time and routes due events by symbol. Publishes the
383    /// resulting time to every sink, including quiet symbols and paused frames. Clock errors
384    /// leave the position and subscriptions unchanged.
385    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    /// Reconstructs every active subscription through `time`, clearing all later state first.
411    /// Replays book history from the beginning as well as trades: starting at the latest book
412    /// snapshot would lose earlier heatmap columns. No later snapshot or execution is exposed.
413    /// Call outside egui context locks, as with live pushes.
414    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        // The subscription rebuilds all bars through the cursor, including its partial bar.
468        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(); // 1 wall second * 2 = market second 2.
939        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)); // Cross both snapshots backward; none of their size 99 may survive.
987        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}