Skip to main content

egui_charts/data/
orders.rs

1//! Individual resting orders and their queue priority. Aggregated-only feeds need no order data.
2
3use super::FeedError;
4use super::depth::{Lost, MAX_LEVELS, key};
5use super::footprint::meets_threshold;
6use crate::model::{OrderBook, OrderBookLevel, OrderSide, Tick};
7use chrono::{DateTime, TimeDelta, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::{BTreeMap, HashSet, VecDeque};
10
11/// One resting order. Snapshot order establishes queue priority at each price.
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct RestingOrder {
15    /// Exchange order id, unique within the subscription's current book.
16    pub id: u64,
17    /// Buy for bids, sell for asks.
18    pub side: OrderSide,
19    /// Limit price.
20    pub price: f64,
21    /// Remaining size, finite and strictly positive.
22    pub size: f64,
23    /// Host-defined identity of the participant that placed the order, such as a broker or
24    /// market maker number, when the feed supplies one. Never inferred.
25    #[serde(default)]
26    pub participant: Option<u64>,
27    /// When the venue placed the order, when the feed supplies it. A price change that costs
28    /// priority is still the same order unless the venue says otherwise by sending a new one.
29    #[serde(default)]
30    pub placed: Option<DateTime<Utc>>,
31}
32
33impl RestingOrder {
34    /// A resting order with its remaining size and no participant or placement time.
35    pub fn new(id: u64, side: OrderSide, price: f64, size: f64) -> Self {
36        Self {
37            id,
38            side,
39            price,
40            size,
41            participant: None,
42            placed: None,
43        }
44    }
45
46    /// The same order, placed by `participant`.
47    pub fn with_participant(mut self, participant: u64) -> Self {
48        self.participant = Some(participant);
49        self
50    }
51
52    /// The same order, placed at `time`.
53    pub fn with_placed(mut self, time: DateTime<Utc>) -> Self {
54        self.placed = Some(time);
55        self
56    }
57}
58
59/// Which resting orders count when they are summed into levels, for ladders, heatmaps and the
60/// book size study. Every field narrows the set. Filters are exact: an order whose placement
61/// time or participant the feed did not supply is neither guessed at nor dropped for lacking the
62/// field it is not filtered on.
63#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
64#[serde(default)]
65pub struct OrderFilter {
66    /// Orders smaller than this are left out before aggregation. Must be finite and not
67    /// negative, else no order counts.
68    pub min_size: f64,
69    /// Only orders that have rested at least this long at the time of the book, measured from
70    /// the placement time the feed supplied. Orders without one cannot be shown to have rested
71    /// that long and are left out. A negative age counts as zero.
72    pub min_age: Option<TimeDelta>,
73    /// Only orders that have rested less than this long at the time of the book, measured from the
74    /// placement time the feed supplied: the most recently placed ones. Orders without one cannot
75    /// be shown to be that recent and are left out. A negative age counts as zero. With
76    /// [`min_age`](Self::min_age) of the same age, every order with a placement time passes exactly
77    /// one of the two.
78    pub max_age: Option<TimeDelta>,
79    /// Orders of these participants are left out. Orders without a participant stay.
80    pub exclude_participants: Vec<u64>,
81}
82
83impl OrderFilter {
84    /// Whether `order` counts in a book as it stood at `now`.
85    pub fn accepts(&self, order: &RestingOrder, now: DateTime<Utc>) -> bool {
86        self.min_size.is_finite()
87            && self.min_size >= 0.0
88            && order.size >= self.min_size
89            && self.min_age.is_none_or(|age| {
90                order
91                    .placed
92                    .is_some_and(|placed| now - placed >= age.max(TimeDelta::zero()))
93            })
94            && self.max_age.is_none_or(|age| {
95                order
96                    .placed
97                    .is_some_and(|placed| now - placed < age.max(TimeDelta::zero()))
98            })
99            && order
100                .participant
101                .is_none_or(|p| !self.exclude_participants.contains(&p))
102    }
103}
104
105/// Why an order-level view cannot be built.
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107#[non_exhaustive]
108pub enum OrderFilterUnavailable {
109    /// The feed sends aggregate depth only, so no order is known.
110    AggregateDepth,
111    /// The filter excludes participants but no held order names one.
112    Participants,
113    /// The filter has a minimum or maximum age but no held order has a placement time.
114    PlacementTimes,
115}
116
117impl std::fmt::Display for OrderFilterUnavailable {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        f.write_str(match self {
120            Self::AggregateDepth => "the feed sends aggregate depth only",
121            Self::Participants => "no order names its participant",
122            Self::PlacementTimes => "no order has a placement time",
123        })
124    }
125}
126
127impl std::error::Error for OrderFilterUnavailable {}
128
129/// Two sizes that mark an individual resting order as notable and as large.
130#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
131pub struct OrderSizeTiers {
132    /// Orders of at least this size are notable.
133    pub notable: f64,
134    /// Orders of at least this size are large, whatever `notable` is.
135    pub large: f64,
136}
137
138/// How an order's size compares with [`OrderSizeTiers`].
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub enum OrderSizeTier {
141    /// At least the notable size and below the large size.
142    Notable,
143    /// At least the large size.
144    Large,
145}
146
147impl OrderSizeTiers {
148    /// The tier of an order of `size`, or `None` below both. Sizes that are not numbers have none.
149    pub fn tier(&self, size: f64) -> Option<OrderSizeTier> {
150        if size >= self.large {
151            Some(OrderSizeTier::Large)
152        } else if size >= self.notable {
153            Some(OrderSizeTier::Notable)
154        } else {
155            None
156        }
157    }
158}
159
160/// A sequenced change to one resting order. Unknown ids or invalid changes lose the book.
161#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
162#[non_exhaustive]
163pub enum OrderEvent {
164    /// Adds an order at the back of its price queue. Duplicate ids are invalid.
165    Add(RestingOrder),
166    /// Replaces price or remaining size. A changed price always loses priority; otherwise
167    /// `keep_priority` preserves the position only when the venue says it does. Size zero cancels.
168    Modify {
169        /// Venue identifier of the order to change.
170        id: u64,
171        /// Replacement limit price, or `None` to retain the current price.
172        price: Option<f64>,
173        /// Replacement remaining size, or `None` to retain it.
174        size: Option<f64>,
175        /// Whether the venue preserves this order's queue position after the change.
176        keep_priority: bool,
177    },
178    /// Cancels the remaining order.
179    Cancel {
180        /// Venue identifier of the order to change.
181        id: u64,
182    },
183    /// Removes `size` from an order, keeping its priority on a partial fill. A known trade is
184    /// pushed through the trade path with `resting_order_id` set. Never also push it without a
185    /// unique trade id. Size must not exceed the order's remaining size; a relative tolerance
186    /// of 1e-9, as in footprint volume thresholds, exhausts a rounded remainder.
187    Execute {
188        /// Venue identifier of the order to change.
189        id: u64,
190        /// Executed quantity to remove from the order.
191        size: f64,
192        /// Execution associated with this fill, when supplied.
193        trade: Option<Tick>,
194    },
195}
196
197/// The per-order book, including queue order. Snapshot and event methods on `Sink` maintain it;
198/// `Chart::order_queue` reads it. At most 10,000 orders per side, with bounded resync buffering.
199#[derive(Clone, Debug)]
200pub struct OrderQueue {
201    tick: Option<f64>,
202    sequence: Option<u64>,
203    requested: bool,
204    time: DateTime<Utc>,
205    orders: Vec<RestingOrder>,
206    held: VecDeque<(u64, DateTime<Utc>, OrderEvent)>,
207    executions: VecDeque<Tick>,
208}
209
210impl OrderQueue {
211    /// An empty book, keying prices by the instrument's positive finite tick size when known.
212    pub fn new(tick_size: Option<f64>) -> Self {
213        Self {
214            tick: tick_size.filter(|t| t.is_finite() && *t > 0.0),
215            sequence: None,
216            requested: false,
217            time: DateTime::default(),
218            orders: Vec::new(),
219            held: VecDeque::new(),
220            executions: VecDeque::new(),
221        }
222    }
223
224    /// Last applied sequence, or `None` until a usable snapshot or after a gap.
225    pub fn sequence(&self) -> Option<u64> {
226        self.sequence
227    }
228
229    /// All held orders, in queue priority order. A lost book contains none.
230    pub fn orders(&self) -> impl Iterator<Item = &RestingOrder> {
231        self.orders.iter()
232    }
233
234    /// Orders at `price`, first in queue first. Prices share the aggregated book's tick rounding.
235    pub fn queue(&self, side: OrderSide, price: f64) -> impl Iterator<Item = &RestingOrder> {
236        let price = key(self.tick, price);
237        self.orders
238            .iter()
239            .filter(move |o| o.side == side && key(self.tick, o.price) == price)
240    }
241
242    /// Largest resting order at a price, if any. Ties keep the earliest queue priority.
243    pub fn largest_order(&self, side: OrderSide, price: f64) -> Option<&RestingOrder> {
244        self.queue(side, price)
245            .reduce(|a, b| if b.size > a.size { b } else { a })
246    }
247
248    /// Trades produced by accepted executions, drained once. Each fill names its resting order.
249    /// At most 10,000 pending fills; the oldest make room if the host does not drain them.
250    pub fn take_executions(&mut self) -> Vec<Tick> {
251        self.executions.drain(..).collect()
252    }
253
254    /// Aggregated levels for ladders and heatmaps, counting only orders at least `minimum_size`.
255    /// Invalid thresholds return an empty book. Counts and totals refer to the selected orders.
256    pub fn aggregate(&self, symbol: impl Into<String>, minimum_size: f64) -> OrderBook {
257        let filter = OrderFilter {
258            min_size: minimum_size,
259            ..OrderFilter::default()
260        };
261        self.sum(symbol.into(), &filter)
262    }
263
264    /// The levels of the orders `filter` accepts, as the book stood at its last update: what a
265    /// ladder, heatmap and book size study built on the same filter show. Fails when the held
266    /// orders carry none of the metadata a filter field needs, rather than showing the unfiltered
267    /// book as if it were filtered. An empty book is never unavailable.
268    pub fn filtered(
269        &self,
270        symbol: impl Into<String>,
271        filter: &OrderFilter,
272    ) -> Result<OrderBook, OrderFilterUnavailable> {
273        if !self.orders.is_empty() {
274            if !filter.exclude_participants.is_empty()
275                && self.orders.iter().all(|o| o.participant.is_none())
276            {
277                return Err(OrderFilterUnavailable::Participants);
278            }
279            if (filter.min_age.is_some() || filter.max_age.is_some())
280                && self.orders.iter().all(|o| o.placed.is_none())
281            {
282                return Err(OrderFilterUnavailable::PlacementTimes);
283            }
284        }
285        Ok(self.sum(symbol.into(), filter))
286    }
287
288    /// The largest order `filter` accepts at each price of each side, bids from the highest
289    /// price down and then asks from the lowest up. Ties keep the earliest queue priority.
290    pub fn largest_orders(&self, filter: &OrderFilter) -> Vec<&RestingOrder> {
291        let mut largest: BTreeMap<(bool, u64), &RestingOrder> = BTreeMap::new();
292        for order in self.orders.iter().filter(|o| filter.accepts(o, self.time)) {
293            let bid = order.side == OrderSide::Buy;
294            let level = largest.entry((!bid, key(self.tick, order.price)));
295            level
296                .and_modify(|held| {
297                    if order.size > held.size {
298                        *held = order;
299                    }
300                })
301                .or_insert(order);
302        }
303        let (mut bids, asks): (Vec<_>, Vec<_>) =
304            largest.into_iter().partition(|((ask, _), _)| !ask);
305        bids.reverse();
306        bids.into_iter()
307            .chain(asks)
308            .map(|(_, order)| order)
309            .collect()
310    }
311
312    /// The levels of [`largest_orders`](Self::largest_orders), one order each: what a depth
313    /// history of individual orders records next to the sums of [`filtered`](Self::filtered).
314    pub(crate) fn largest_levels(&self, filter: &OrderFilter) -> OrderBook {
315        let largest = self.largest_orders(filter);
316        let levels = |side| {
317            largest
318                .iter()
319                .filter(|o| o.side == side)
320                .map(|o| OrderBookLevel {
321                    price: o.price,
322                    size: o.size,
323                    order_cnt: Some(1),
324                })
325                .collect()
326        };
327        OrderBook {
328            ts: self.time,
329            symbol: String::new(),
330            bids: levels(OrderSide::Buy),
331            asks: levels(OrderSide::Sell),
332        }
333    }
334
335    fn sum(&self, symbol: String, filter: &OrderFilter) -> OrderBook {
336        let mut bids: BTreeMap<u64, OrderBookLevel> = BTreeMap::new();
337        let mut asks: BTreeMap<u64, OrderBookLevel> = BTreeMap::new();
338        for order in self.orders.iter().filter(|o| filter.accepts(o, self.time)) {
339            let side = if order.side == OrderSide::Buy {
340                &mut bids
341            } else {
342                &mut asks
343            };
344            let level = side
345                .entry(key(self.tick, order.price))
346                .or_insert(OrderBookLevel {
347                    price: order.price,
348                    size: 0.0,
349                    order_cnt: Some(0),
350                });
351            level.size = (level.size + order.size).min(f64::MAX);
352            level.order_cnt = level.order_cnt.map(|n| n + 1);
353        }
354        OrderBook {
355            ts: self.time,
356            symbol,
357            bids: bids.into_values().rev().collect(),
358            asks: asks.into_values().collect(),
359        }
360    }
361
362    /// Replaces all orders, then applies buffered events above `sequence`. A malformed snapshot
363    /// clears the book. Errors leave it empty and ready for a replacement snapshot.
364    pub fn snapshot(
365        &mut self,
366        sequence: u64,
367        time: DateTime<Utc>,
368        orders: Vec<RestingOrder>,
369    ) -> Result<(), FeedError> {
370        self.load(sequence, time, orders)
371            .map_err(|(_, error)| error)
372    }
373
374    /// Applies exactly the next sequence. Repeated sequences are ignored. A gap clears the book
375    /// and buffers at most 10,000 following events for the next snapshot.
376    pub fn apply(
377        &mut self,
378        sequence: u64,
379        time: DateTime<Utc>,
380        event: OrderEvent,
381    ) -> Result<(), FeedError> {
382        self.step(sequence, time, event).map_err(|(_, error)| error)
383    }
384
385    pub(crate) fn set_snapshot(
386        &mut self,
387        sequence: u64,
388        time: DateTime<Utc>,
389        orders: Vec<RestingOrder>,
390    ) -> Result<(), Lost> {
391        self.load(sequence, time, orders).map_err(|(lost, _)| lost)
392    }
393
394    pub(crate) fn event(
395        &mut self,
396        sequence: u64,
397        time: DateTime<Utc>,
398        event: OrderEvent,
399    ) -> Result<(), Lost> {
400        self.step(sequence, time, event).map_err(|(lost, _)| lost)
401    }
402
403    fn load(
404        &mut self,
405        sequence: u64,
406        time: DateTime<Utc>,
407        orders: Vec<RestingOrder>,
408    ) -> Result<(), (Lost, FeedError)> {
409        let mut ids = HashSet::new();
410        let bids = orders.iter().filter(|o| o.side == OrderSide::Buy).count();
411        let asks = orders.len() - bids;
412        let error = if bids > MAX_LEVELS {
413            Some(format!(
414                "order book snapshot has {bids} bids, more than {MAX_LEVELS}"
415            ))
416        } else if asks > MAX_LEVELS {
417            Some(format!(
418                "order book snapshot has {asks} asks, more than {MAX_LEVELS}"
419            ))
420        } else {
421            orders.iter().find_map(|order| {
422                if !order.price.is_finite() {
423                    Some(format!(
424                        "snapshot order {} price must be finite, got {:?}",
425                        order.id, order.price
426                    ))
427                } else if !order.size.is_finite() || order.size <= 0.0 {
428                    Some(format!(
429                        "snapshot order {} size must be finite and above zero, got {:?}",
430                        order.id, order.size
431                    ))
432                } else if !ids.insert(order.id) {
433                    Some(format!("snapshot holds order {} twice", order.id))
434                } else {
435                    None
436                }
437            })
438        };
439        if let Some(error) = error {
440            return Err((self.lose(true), FeedError::InvalidData(error)));
441        }
442        self.orders = orders;
443        self.sequence = Some(sequence);
444        self.requested = false;
445        self.time = time;
446        let held = std::mem::take(&mut self.held);
447        let mut result = Ok(());
448        for (sequence, time, event) in held {
449            result = result.and(self.step(sequence, time, event));
450        }
451        result
452    }
453
454    fn step(
455        &mut self,
456        sequence: u64,
457        time: DateTime<Utc>,
458        event: OrderEvent,
459    ) -> Result<(), (Lost, FeedError)> {
460        if self.sequence.is_some_and(|last| sequence <= last) {
461            return Ok(());
462        }
463        if self.sequence.and_then(|s| s.checked_add(1)) != Some(sequence) {
464            if self.held.iter().any(|(held, _, _)| *held == sequence) {
465                return Err((
466                    Lost {
467                        invalid: false,
468                        request: false,
469                    },
470                    FeedError::InvalidData(format!(
471                        "order book sequence {sequence} is already held"
472                    )),
473                ));
474            }
475            let reason = match self.sequence {
476                Some(last) => format!("order book sequence {sequence} does not follow {last}"),
477                None => format!(
478                    "order book has no snapshot; sequence {sequence} is held for the next one"
479                ),
480            };
481            let lost = self.lose(false);
482            if self
483                .held
484                .back()
485                .is_some_and(|(s, _, _)| s.checked_add(1) != Some(sequence))
486            {
487                self.held.clear();
488            }
489            self.held.push_back((sequence, time, event));
490            if self.held.len() > MAX_LEVELS {
491                self.held.pop_front();
492            }
493            return Err((lost, FeedError::InvalidData(reason)));
494        }
495        if let Err(error) = self.change(event) {
496            return Err((self.lose(true), error));
497        }
498        self.sequence = Some(sequence);
499        self.time = time;
500        Ok(())
501    }
502
503    fn change(&mut self, event: OrderEvent) -> Result<(), FeedError> {
504        match event {
505            OrderEvent::Add(order) => {
506                if !order.price.is_finite() {
507                    return Err(FeedError::InvalidData(format!(
508                        "added order {} price must be finite, got {:?}",
509                        order.id, order.price
510                    )));
511                }
512                if !order.size.is_finite() || order.size <= 0.0 {
513                    return Err(FeedError::InvalidData(format!(
514                        "added order {} size must be finite and above zero, got {:?}",
515                        order.id, order.size
516                    )));
517                }
518                if self.orders.iter().any(|o| o.id == order.id) {
519                    return Err(FeedError::InvalidData(format!(
520                        "added order {} is already in the book",
521                        order.id
522                    )));
523                }
524                if self.orders.iter().filter(|o| o.side == order.side).count() == MAX_LEVELS {
525                    let side = if order.side == OrderSide::Buy {
526                        "bid"
527                    } else {
528                        "ask"
529                    };
530                    return Err(FeedError::InvalidData(format!(
531                        "added order {} exceeds the limit of {MAX_LEVELS} {side} orders",
532                        order.id
533                    )));
534                }
535                self.orders.push(order);
536            }
537            OrderEvent::Modify {
538                id,
539                price,
540                size,
541                keep_priority,
542            } => {
543                let at = self.orders.iter().position(|o| o.id == id).ok_or_else(|| {
544                    FeedError::InvalidData(format!("modified order {id} is not in the book"))
545                })?;
546                let mut order = self.orders[at].clone();
547                let old_price = key(self.tick, order.price);
548                if let Some(price) = price {
549                    order.price = price;
550                }
551                if let Some(size) = size {
552                    order.size = size;
553                }
554                if !order.price.is_finite() {
555                    return Err(FeedError::InvalidData(format!(
556                        "modified order {id} price must be finite, got {:?}",
557                        order.price
558                    )));
559                }
560                if !order.size.is_finite() || order.size < 0.0 {
561                    return Err(FeedError::InvalidData(format!(
562                        "modified order {id} size must be finite and at least 0, got {:?}",
563                        order.size
564                    )));
565                }
566                if order.size == 0.0 {
567                    self.orders.remove(at);
568                } else if !keep_priority || old_price != key(self.tick, order.price) {
569                    self.orders.remove(at);
570                    self.orders.push(order);
571                } else {
572                    self.orders[at] = order;
573                }
574            }
575            OrderEvent::Cancel { id } => {
576                let at = self.orders.iter().position(|o| o.id == id).ok_or_else(|| {
577                    FeedError::InvalidData(format!("cancelled order {id} is not in the book"))
578                })?;
579                self.orders.remove(at);
580            }
581            OrderEvent::Execute { id, size, trade } => {
582                let at = self.orders.iter().position(|o| o.id == id).ok_or_else(|| {
583                    FeedError::InvalidData(format!("executed order {id} is not in the book"))
584                })?;
585                let order = &mut self.orders[at];
586                if !size.is_finite() || size <= 0.0 {
587                    return Err(FeedError::InvalidData(format!(
588                        "execution of order {id} size must be finite and above zero, got {size:?}"
589                    )));
590                }
591                if !meets_threshold(order.size, size) {
592                    return Err(FeedError::InvalidData(format!(
593                        "execution of order {id} needs a size above zero and at most the resting {:?}, got {size:?}",
594                        order.size
595                    )));
596                }
597                if let Some(trade) = &trade {
598                    if !trade.price.is_finite() {
599                        return Err(FeedError::InvalidData(format!(
600                            "trade of order {id} price must be finite, got {:?}",
601                            trade.price
602                        )));
603                    }
604                    if !trade.size.is_finite() || trade.size < 0.0 {
605                        return Err(FeedError::InvalidData(format!(
606                            "trade of order {id} size must be finite and at least 0, got {:?}",
607                            trade.size
608                        )));
609                    }
610                    if trade.size != size {
611                        return Err(FeedError::InvalidData(format!(
612                            "trade of order {id} size must equal execution size {size:?}, got {:?}",
613                            trade.size
614                        )));
615                    }
616                    if key(self.tick, trade.price) != key(self.tick, order.price) {
617                        return Err(FeedError::InvalidData(format!(
618                            "trade of order {id} price must share the grid level of {:?}, got {:?}",
619                            order.price, trade.price
620                        )));
621                    }
622                }
623                if let Some(mut trade) = trade {
624                    trade.resting_order_id = Some(id);
625                    if self.executions.len() == MAX_LEVELS {
626                        self.executions.pop_front();
627                    }
628                    self.executions.push_back(trade);
629                }
630                order.size = if meets_threshold(size, order.size) {
631                    0.0
632                } else {
633                    order.size - size
634                };
635                if order.size == 0.0 {
636                    self.orders.remove(at);
637                }
638            }
639        }
640        Ok(())
641    }
642
643    fn lose(&mut self, invalid: bool) -> Lost {
644        self.sequence = None;
645        self.orders.clear();
646        Lost {
647            invalid,
648            request: !std::mem::replace(&mut self.requested, true),
649        }
650    }
651}
652
653#[cfg(test)]
654mod tests {
655    use super::*;
656    use OrderSide::{Buy, Sell};
657
658    #[test]
659    fn orders_preserve_priority_and_derive_levels() {
660        let orders = vec![
661            RestingOrder::new(1, Buy, 100.0, 3.0),
662            RestingOrder::new(2, Buy, 100.0, 5.0),
663            RestingOrder::new(3, Sell, 101.0, 7.0),
664        ];
665        #[rustfmt::skip]
666        let cases = [
667            ("partial execution", 8, OrderEvent::Execute { id: 2, size: 2.0, trade: None }, vec![(100.0, 6.0, 2)], vec![1, 2], None),
668            ("price change", 8, OrderEvent::Modify { id: 1, price: Some(99.0), size: None, keep_priority: true }, vec![(100.0, 5.0, 1), (99.0, 3.0, 1)], vec![2], None),
669            ("keeps priority", 8, OrderEvent::Modify { id: 1, price: None, size: Some(2.0), keep_priority: true }, vec![(100.0, 7.0, 2)], vec![1, 2], None),
670            ("loses priority", 8, OrderEvent::Modify { id: 1, price: None, size: Some(4.0), keep_priority: false }, vec![(100.0, 9.0, 2)], vec![2, 1], None),
671            ("invalid zero-size modify", 8, OrderEvent::Modify { id: 1, price: Some(f64::NAN), size: Some(0.0), keep_priority: true }, vec![], vec![], Some("modified order 1 price must be finite, got NaN")),
672            ("invalid add price", 8, OrderEvent::Add(RestingOrder::new(4, Buy, f64::NAN, 1.0)), vec![], vec![], Some("added order 4 price must be finite, got NaN")),
673            ("invalid add size", 8, OrderEvent::Add(RestingOrder::new(4, Buy, 100.0, 0.0)), vec![], vec![], Some("added order 4 size must be finite and above zero, got 0.0")),
674            ("duplicate add", 8, OrderEvent::Add(RestingOrder::new(1, Buy, 100.0, 1.0)), vec![], vec![], Some("added order 1 is already in the book")),
675            ("unknown modify", 8, OrderEvent::Modify { id: 99, price: None, size: Some(1.0), keep_priority: true }, vec![], vec![], Some("modified order 99 is not in the book")),
676            ("invalid modify size", 8, OrderEvent::Modify { id: 1, price: None, size: Some(-1.0), keep_priority: true }, vec![], vec![], Some("modified order 1 size must be finite and at least 0, got -1.0")),
677            ("unknown execution", 8, OrderEvent::Execute { id: 99, size: 1.0, trade: None }, vec![], vec![], Some("executed order 99 is not in the book")),
678            ("invalid execution size", 8, OrderEvent::Execute { id: 1, size: f64::NAN, trade: None }, vec![], vec![], Some("execution of order 1 size must be finite and above zero, got NaN")),
679            ("invalid trade price", 8, OrderEvent::Execute { id: 1, size: 1.0, trade: Some(Tick::new(DateTime::default(), f64::NAN, 1.0, None)) }, vec![], vec![], Some("trade of order 1 price must be finite, got NaN")),
680            ("invalid trade size", 8, OrderEvent::Execute { id: 1, size: 1.0, trade: Some(Tick::new(DateTime::default(), 100.0, -1.0, None)) }, vec![], vec![], Some("trade of order 1 size must be finite and at least 0, got -1.0")),
681            ("mismatched trade size", 8, OrderEvent::Execute { id: 1, size: 1.0, trade: Some(Tick::new(DateTime::default(), 100.0, 2.0, None)) }, vec![], vec![], Some("trade of order 1 size must equal execution size 1.0, got 2.0")),
682            ("mismatched trade price", 8, OrderEvent::Execute { id: 1, size: 1.0, trade: Some(Tick::new(DateTime::default(), 101.0, 1.0, None)) }, vec![], vec![], Some("trade of order 1 price must share the grid level of 100.0, got 101.0")),
683            ("gap", 9, OrderEvent::Cancel { id: 1 }, vec![], vec![], Some("order book sequence 9 does not follow 7")),
684            ("unknown cancel", 8, OrderEvent::Cancel { id: 99 }, vec![], vec![], Some("cancelled order 99 is not in the book")),
685            ("oversized execution", 8, OrderEvent::Execute { id: 1, size: 4.0, trade: None }, vec![], vec![], Some("execution of order 1 needs a size above zero and at most the resting 3.0, got 4.0")),
686        ];
687        for (name, seq, event, expected, queue, error) in cases {
688            let mut book = OrderQueue::new(Some(0.25));
689            book.snapshot(7, DateTime::default(), orders.clone())
690                .unwrap();
691            let result = book
692                .apply(seq, DateTime::default(), event)
693                .err()
694                .map(|e| e.to_string());
695            assert_eq!(
696                result,
697                error.map(|e| format!("invalid data: {e}")),
698                "{name}"
699            );
700            let lost = error.is_some();
701            let levels = book.aggregate("A", 0.0);
702            let bids: Vec<_> = levels
703                .bids
704                .iter()
705                .map(|l| (l.price, l.size, l.order_cnt.unwrap()))
706                .collect();
707            assert_eq!(bids, expected, "{name}");
708            assert_eq!(
709                book.queue(Buy, 100.0).map(|o| o.id).collect::<Vec<_>>(),
710                queue,
711                "{name}"
712            );
713            if !lost {
714                assert_eq!(levels.asks[0].size, 7.0);
715            }
716        }
717        for (snapshot, message) in [
718            (
719                vec![RestingOrder::new(1, Buy, f64::NAN, 1.0)],
720                "snapshot order 1 price must be finite, got NaN",
721            ),
722            (
723                vec![RestingOrder::new(1, Buy, 100.0, 0.0)],
724                "snapshot order 1 size must be finite and above zero, got 0.0",
725            ),
726            (
727                vec![orders[0].clone(), orders[0].clone()],
728                "snapshot holds order 1 twice",
729            ),
730            (
731                vec![RestingOrder::new(1, Buy, 100.0, 1.0); MAX_LEVELS + 1],
732                "order book snapshot has 10001 bids, more than 10000",
733            ),
734            (
735                vec![RestingOrder::new(1, Sell, 100.0, 1.0); MAX_LEVELS + 1],
736                "order book snapshot has 10001 asks, more than 10000",
737            ),
738        ] {
739            let mut book = OrderQueue::new(None);
740            book.snapshot(7, DateTime::default(), orders.clone())
741                .unwrap();
742            assert_eq!(
743                book.snapshot(8, DateTime::default(), snapshot)
744                    .unwrap_err()
745                    .to_string(),
746                format!("invalid data: {message}")
747            );
748            assert_eq!(book.sequence(), None);
749            assert_eq!(book.orders().count(), 0);
750        }
751        let mut book = OrderQueue::new(None);
752        book.snapshot(7, DateTime::default(), orders.clone())
753            .unwrap();
754        assert_eq!(book.aggregate("A", 4.0).bids[0].size, 5.0);
755        assert_eq!(book.largest_order(Buy, 100.0).unwrap().id, 2);
756        assert!(
757            book.apply(9, DateTime::default(), OrderEvent::Cancel { id: 1 })
758                .is_err()
759        );
760        book.snapshot(8, DateTime::default(), orders).unwrap();
761        assert_eq!(
762            book.queue(Buy, 100.0).map(|o| o.id).collect::<Vec<_>>(),
763            [2]
764        );
765        let full: Vec<_> = (0..MAX_LEVELS)
766            .map(|i| RestingOrder::new(i as u64, Buy, 100.0, 1.0))
767            .collect();
768        book.snapshot(10, DateTime::default(), full).unwrap();
769        assert_eq!(
770            book.apply(
771                11,
772                DateTime::default(),
773                OrderEvent::Add(RestingOrder::new(10_001, Buy, 99.0, 1.0))
774            )
775            .unwrap_err()
776            .to_string(),
777            "invalid data: added order 10001 exceeds the limit of 10000 bid orders"
778        );
779        assert_eq!(book.orders().count(), 0);
780    }
781
782    #[test]
783    fn decimal_executions_consume_the_order_without_losing_sequence() {
784        let mut book = OrderQueue::new(Some(0.25));
785        book.snapshot(
786            1,
787            DateTime::default(),
788            vec![RestingOrder::new(1, Sell, 100.0, 0.3)],
789        )
790        .unwrap();
791        for (sequence, size) in [(2, 0.1), (3, 0.2)] {
792            book.apply(
793                sequence,
794                DateTime::default(),
795                OrderEvent::Execute {
796                    id: 1,
797                    size,
798                    trade: Some(
799                        Tick::new(DateTime::default(), 100.0, size, Some(Buy)).with_id(sequence),
800                    ),
801                },
802            )
803            .unwrap();
804        }
805        assert_eq!(book.sequence(), Some(3));
806        assert!(book.aggregate("ES", 0.0).asks.is_empty());
807        assert_eq!(
808            book.take_executions()
809                .iter()
810                .map(|fill| fill.size)
811                .collect::<Vec<_>>(),
812            [0.1, 0.2]
813        );
814    }
815}