1use super::FeedError;
6use super::depth::Book;
7use chrono::{DateTime, TimeDelta, Utc};
8use std::collections::VecDeque;
9use std::sync::Arc;
10
11const TIERS: usize = 8;
14
15#[derive(Clone, Debug, PartialEq)]
21pub struct DepthHistoryConfig {
22 pub cadence: TimeDelta,
26 pub rows: usize,
29 pub ticks_per_row: u32,
32 pub columns: usize,
36 pub orders: Option<crate::data::OrderFilter>,
45}
46
47impl Default for DepthHistoryConfig {
48 fn default() -> Self {
49 Self {
50 cadence: TimeDelta::seconds(1),
51 rows: 100,
52 ticks_per_row: 1,
53 columns: 600,
54 orders: None,
55 }
56 }
57}
58
59#[derive(Clone, Debug, Default, PartialEq)]
62pub struct DepthColumn {
63 pub time: DateTime<Utc>,
65 pub span: TimeDelta,
68 pub bids: Vec<(i64, f64)>,
73 pub asks: Vec<(i64, f64)>,
75 pub largest_bid_orders: Vec<f64>,
81 pub largest_ask_orders: Vec<f64>,
84}
85
86#[derive(Clone, Debug)]
97pub struct DepthHistory {
98 config: DepthHistoryConfig,
99 tiers: Vec<VecDeque<Arc<DepthColumn>>>,
104}
105
106impl DepthHistory {
107 pub fn new(config: DepthHistoryConfig) -> Result<Self, FeedError> {
109 if config.cadence < TimeDelta::milliseconds(1) {
110 return Err(FeedError::InvalidData(format!(
111 "depth history cadence must be at least one millisecond, got {}",
112 config.cadence
113 )));
114 }
115 if config.rows == 0 {
116 return Err(FeedError::InvalidData(
117 "depth history rows must be at least 1, got 0".into(),
118 ));
119 }
120 if config.ticks_per_row == 0 {
121 return Err(FeedError::InvalidData(
122 "depth history ticks_per_row must be at least 1, got 0".into(),
123 ));
124 }
125 if config.columns < 2 {
126 return Err(FeedError::InvalidData(format!(
127 "depth history columns must be at least 2, got {}",
128 config.columns
129 )));
130 }
131 Ok(Self {
132 config,
133 tiers: vec![VecDeque::new(); TIERS],
134 })
135 }
136
137 pub fn config(&self) -> &DepthHistoryConfig {
139 &self.config
140 }
141
142 pub fn columns(&self) -> impl DoubleEndedIterator<Item = &DepthColumn> {
144 self.shared().map(Arc::as_ref)
145 }
146
147 pub(crate) fn shared(&self) -> impl DoubleEndedIterator<Item = &Arc<DepthColumn>> {
150 self.tiers.iter().rev().flatten()
151 }
152
153 pub fn merge_page(&mut self, mut page: Vec<DepthColumn>) -> Result<usize, FeedError> {
164 let cadence = self.config.cadence;
165 let valid_side = |side: &[(i64, f64)], largest: &[f64], bid: bool| {
166 side.len() <= self.config.rows
167 && (largest.is_empty() || largest.len() == side.len())
168 && side
169 .iter()
170 .map(|(_, size)| size)
171 .chain(largest)
172 .all(|size| size.is_finite() && *size >= 0.0)
173 && side.windows(2).all(|w| {
174 if bid {
175 w[0].0 > w[1].0
176 } else {
177 w[0].0 < w[1].0
178 }
179 })
180 };
181 if page.iter().any(|c| {
182 start(c.time, cadence) != Some(c.time)
183 || c.span <= TimeDelta::zero()
184 || c.span
185 .num_nanoseconds()
186 .zip(cadence.num_nanoseconds())
187 .is_none_or(|(span, cadence)| span % cadence != 0)
188 || c.time.checked_add_signed(c.span).is_none()
189 || !valid_side(&c.bids, &c.largest_bid_orders, true)
190 || !valid_side(&c.asks, &c.largest_ask_orders, false)
191 }) {
192 return Err(FeedError::InvalidData(
193 "invalid historical depth columns".into(),
194 ));
195 }
196 page.sort_by_key(|c| c.time);
197 let mut held: Vec<_> = self.shared().cloned().collect();
198 let mut accepted = Vec::new();
199 for column in page {
200 let at = held.partition_point(|c| c.time < column.time);
201 let overlaps = (at > 0 && held[at - 1].time + held[at - 1].span > column.time)
202 || held
203 .get(at)
204 .is_some_and(|c| c.time < column.time + column.span);
205 if !overlaps {
206 let column = Arc::new(column);
207 held.insert(at, Arc::clone(&column));
208 accepted.push(column);
209 }
210 }
211 if accepted.is_empty() {
212 return Ok(0);
213 }
214 let mut older = 0;
215 if let Some(k) = self.tiers.iter().rposition(|tier| !tier.is_empty()) {
216 older = accepted.partition_point(|c| c.time < self.tiers[k][0].time);
217 let held = std::mem::take(&mut self.tiers[k]);
218 self.tiers[k] = self.merge_run(k, accepted[..older].iter().cloned().collect(), held);
219 }
220 let placed: Vec<_> = accepted[older..]
223 .iter()
224 .map(|column| (self.insert_column(Arc::clone(column)), column))
225 .collect();
226 let cadence = self.config.cadence;
227 let finer =
228 |(tier, column): &(usize, &Arc<DepthColumn>)| resolution(cadence, column.span) < *tier;
229 for run in placed
230 .chunk_by(|a, b| a.0 == b.0 && finer(a) == finer(b) && a.1.time + a.1.span == b.1.time)
231 {
232 let (tier, first) = run[0];
233 if finer(&run[0]) {
234 let at = self.tiers[tier].partition_point(|c| c.time < first.time);
235 let mut after = self.tiers[tier].split_off(at);
236 let columns = after.drain(..run.len()).collect();
237 let mut merged = self.merge_run(tier, columns, after);
238 self.tiers[tier].append(&mut merged);
239 }
240 }
241 self.compact();
242 let oldest = self.columns().next().expect("accepted columns").time;
243 Ok(accepted.len() - accepted.partition_point(|c| c.time < oldest))
244 }
245
246 #[cfg(feature = "widget")]
247 pub(crate) fn clear(&mut self) {
248 self.tiers.iter_mut().for_each(VecDeque::clear);
249 }
250
251 pub(crate) fn push(&mut self, column: impl Into<Arc<DepthColumn>>) {
253 let column = column.into();
254 if self
255 .columns()
256 .next_back()
257 .is_some_and(|last| last.time + last.span > column.time)
258 {
259 let end = column.time + column.span;
260 for tier in &mut self.tiers {
261 tier.retain(|c| c.time >= end || c.time + c.span <= column.time);
262 }
263 self.insert_column(column);
264 } else {
265 self.tiers[0].push_back(column);
266 }
267 self.compact();
268 }
269
270 fn insert_column(&mut self, column: Arc<DepthColumn>) -> usize {
274 let tier = if let Some(k) = self.tiers.iter().rposition(|tier| !tier.is_empty())
275 && column.time < self.tiers[k][0].time
276 {
277 k
278 } else {
279 let lower = self
280 .tiers
281 .iter()
282 .rposition(|tier| tier.back().is_some_and(|c| c.time > column.time))
283 .unwrap_or(0);
284 let upper = self
285 .tiers
286 .iter()
287 .position(|tier| tier.front().is_some_and(|c| c.time < column.time))
288 .unwrap_or(TIERS - 1);
289 resolution(self.config.cadence, column.span).clamp(lower, upper)
290 };
291 let at = self.tiers[tier].partition_point(|c| c.time < column.time);
292 self.tiers[tier].insert(at, column);
293 tier
294 }
295
296 fn merge_run(
301 &self,
302 tier: usize,
303 mut columns: VecDeque<Arc<DepthColumn>>,
304 mut after: VecDeque<Arc<DepthColumn>>,
305 ) -> VecDeque<Arc<DepthColumn>> {
306 if let Some(span) = self.config.cadence.checked_mul(1 << tier) {
308 while columns
309 .back()
310 .zip(after.front())
311 .is_some_and(|(older, newer)| {
312 older.time + older.span == newer.time && older.span + newer.span <= span
313 })
314 {
315 let older = columns.pop_back().expect("an older column");
316 let newer = after.pop_front().expect("a newer column");
317 after.push_front(Arc::new(merge(&older, &newer, self.config.rows)));
318 }
319 }
320 for _ in 0..tier {
322 let mut merged = VecDeque::new();
323 while let Some(newer) = columns.pop_back() {
324 let column = match columns.back() {
325 Some(older) if older.time + older.span == newer.time => {
326 let older = columns.pop_back().expect("an older column");
327 Arc::new(merge(&older, &newer, self.config.rows))
328 }
329 _ => newer,
330 };
331 merged.push_front(column);
332 }
333 columns = merged;
334 }
335 columns.append(&mut after);
336 columns
337 }
338
339 fn compact(&mut self) {
341 for tier in 0..TIERS {
342 while self.tiers[tier].len() > self.config.columns {
343 let older = self.tiers[tier].pop_front().expect("a full tier");
344 if tier == TIERS - 1 {
345 continue;
346 }
347 let merged = match self.tiers[tier].front() {
349 Some(newer) if older.time + older.span == newer.time => {
350 let newer = self.tiers[tier].pop_front().expect("a newer column");
351 Arc::new(merge(&older, &newer, self.config.rows))
352 }
353 _ => older,
354 };
355 self.tiers[tier + 1].push_back(merged);
356 }
357 }
358 }
359}
360
361pub(crate) struct Sampler {
363 pub(crate) config: DepthHistoryConfig,
364 open: Option<DateTime<Utc>>,
366}
367
368impl Sampler {
369 pub(crate) fn new(config: DepthHistoryConfig) -> Self {
370 Self { config, open: None }
371 }
372
373 pub(crate) fn due(&self, ts: DateTime<Utc>) -> bool {
376 start(ts, self.config.cadence)
377 .zip(self.open)
378 .is_some_and(|(period, open)| period > open)
379 }
380
381 pub(crate) fn lose(&mut self) {
384 self.open = None;
385 }
386
387 pub(crate) fn sample(
394 &mut self,
395 ts: DateTime<Utc>,
396 book: &Book,
397 largest: Option<&Book>,
398 ) -> Option<DepthColumn> {
399 let period = start(ts, self.config.cadence)?;
400 let open = self.open.replace(period.max(self.open.unwrap_or(period)))?;
401 if period <= open {
402 return None;
403 }
404 let (ticks, rows) = (self.config.ticks_per_row, self.config.rows);
405 let [bids, asks] = book.rows(ticks, rows, |a, b| (a + b).min(f64::MAX))?;
406 let [largest_bid_orders, largest_ask_orders]: [Vec<f64>; 2] = largest
407 .and_then(|book| book.rows(ticks, rows, f64::max))
408 .map(|sides| sides.map(|side| side.into_iter().map(|(_, size)| size).collect()))
409 .unwrap_or_default();
410 Some(DepthColumn {
411 time: open,
412 span: period - open,
413 bids,
414 asks,
415 largest_bid_orders,
416 largest_ask_orders,
417 })
418 }
419}
420
421fn resolution(cadence: TimeDelta, span: TimeDelta) -> usize {
423 (0..TIERS - 1)
424 .find(|&k| {
425 cadence
426 .checked_mul(1 << k)
427 .is_none_or(|limit| span <= limit)
428 })
429 .unwrap_or(TIERS - 1)
430}
431
432fn start(ts: DateTime<Utc>, cadence: TimeDelta) -> Option<DateTime<Utc>> {
434 let (ts, cadence) = (ts.timestamp_nanos_opt()?, cadence.num_nanoseconds()?);
435 Some(DateTime::from_timestamp_nanos(
436 ts.checked_sub(ts.rem_euclid(cadence))?,
437 ))
438}
439
440fn merge(older: &DepthColumn, newer: &DepthColumn, rows: usize) -> DepthColumn {
443 let span = older.span + newer.span;
444 let weight = |part: TimeDelta| part.as_seconds_f64() / span.as_seconds_f64();
445 let (a, b) = (weight(older.span), weight(newer.span));
446 let (bids, largest_bid_orders) = merge_side(
447 (&older.bids, &older.largest_bid_orders),
448 (&newer.bids, &newer.largest_bid_orders),
449 (a, b),
450 rows,
451 |row| -row,
452 );
453 let (asks, largest_ask_orders) = merge_side(
454 (&older.asks, &older.largest_ask_orders),
455 (&newer.asks, &newer.largest_ask_orders),
456 (a, b),
457 rows,
458 |row| row,
459 );
460 DepthColumn {
461 time: older.time,
462 span,
463 bids,
464 asks,
465 largest_bid_orders,
466 largest_ask_orders,
467 }
468}
469
470fn merge_side(
474 (older, older_largest): (&[(i64, f64)], &[f64]),
475 (newer, newer_largest): (&[(i64, f64)], &[f64]),
476 (a, b): (f64, f64),
477 rows: usize,
478 order: fn(i128) -> i128,
479) -> (Vec<(i64, f64)>, Vec<f64>) {
480 let known = !older_largest.is_empty() || !newer_largest.is_empty();
481 let mut merged = Vec::with_capacity(rows.min(older.len() + newer.len()));
482 let mut largest = Vec::with_capacity(if known { merged.capacity() } else { 0 });
483 let (mut older, mut newer) = (
484 older.iter().enumerate().peekable(),
485 newer.iter().enumerate().peekable(),
486 );
487 while merged.len() < rows {
488 let rank =
489 |row: Option<&(usize, &(i64, f64))>| row.map(|(_, (row, _))| order(*row as i128));
490 let (row, size, at) = match (rank(older.peek()), rank(newer.peek())) {
491 (None, None) => break,
492 (Some(x), Some(y)) if x == y => {
493 let ((i, (row, x)), (j, (_, y))) = (older.next().unwrap(), newer.next().unwrap());
494 (*row, x * a + y * b, (Some(i), Some(j)))
495 }
496 (Some(x), y) if y.is_none_or(|y| x < y) => {
497 let (i, (row, x)) = older.next().unwrap();
498 (*row, x * a, (Some(i), None))
499 }
500 _ => {
501 let (j, (row, y)) = newer.next().unwrap();
502 (*row, y * b, (None, Some(j)))
503 }
504 };
505 merged.push((row, size));
506 if known {
507 let of = |side: &[f64], at: Option<usize>| {
508 at.and_then(|at| side.get(at)).copied().unwrap_or(0.0)
509 };
510 largest.push(of(older_largest, at.0).max(of(newer_largest, at.1)));
511 }
512 }
513 (merged, largest)
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 fn at(second: i64) -> DateTime<Utc> {
521 DateTime::from_timestamp(second, 0).unwrap()
522 }
523
524 fn column(second: i64, span: i64, bid: f64) -> DepthColumn {
525 DepthColumn {
526 time: at(second),
527 span: TimeDelta::seconds(span),
528 bids: vec![(100, bid)],
529 ..Default::default()
530 }
531 }
532
533 #[test]
538 fn full_tiers_merge_adjacent_columns_by_time_weight() {
539 let older = DepthColumn {
541 time: at(0),
542 span: TimeDelta::microseconds(500),
543 bids: vec![(101, 4.0), (99, 2.0)],
544 asks: vec![(102, 6.0)],
545 ..Default::default()
546 };
547 let newer = DepthColumn {
548 time: at(0) + TimeDelta::microseconds(500),
549 span: TimeDelta::microseconds(1_500),
550 bids: vec![(100, 8.0), (99, 6.0)],
551 asks: vec![(102, 2.0), (103, 4.0)],
552 ..Default::default()
553 };
554 assert_eq!(
556 merge(&older, &newer, 2),
557 DepthColumn {
558 time: at(0),
559 span: TimeDelta::milliseconds(2),
560 bids: vec![(101, 1.0), (100, 6.0)],
561 asks: vec![(102, 3.0), (103, 3.0)],
562 ..Default::default()
563 }
564 );
565
566 let known = |column: &DepthColumn, bids: &[f64], asks: &[f64]| DepthColumn {
569 largest_bid_orders: bids.to_vec(),
570 largest_ask_orders: asks.to_vec(),
571 ..column.clone()
572 };
573 let (older_known, newer_known) = (
574 known(&older, &[3.0, 2.0], &[5.0]),
575 known(&newer, &[7.0, 4.0], &[1.0, 3.0]),
576 );
577 for (older, newer, bids, asks) in [
578 (&older_known, &newer, [3.0, 0.0], [5.0, 0.0]),
579 (&older, &newer_known, [0.0, 7.0], [1.0, 3.0]),
580 (&older_known, &newer_known, [3.0, 7.0], [5.0, 3.0]),
581 ] {
582 let merged = merge(older, newer, 2);
583 assert_eq!(
584 (merged.largest_bid_orders, merged.largest_ask_orders),
585 (bids.to_vec(), asks.to_vec())
586 );
587 }
588
589 let mut history = DepthHistory::new(config_columns(2)).unwrap();
590 for second in 0..1_000 {
591 history.push(column(second, 1, second as f64));
592 }
593 let spans = Vec::from_iter(history.columns().map(|c| c.span.num_seconds()));
594 assert!(history.columns().count() <= 2 * TIERS, "{spans:?}");
595 let times = Vec::from_iter(history.columns().map(|c| c.time));
596 assert!(times.is_sorted(), "{times:?}");
597 assert_eq!((spans.first(), spans.last()), (Some(&128), Some(&1)));
599 let first = history.columns().next().unwrap();
600 let mean = (first.time.timestamp()..first.time.timestamp() + 128).sum::<i64>() / 128;
601 assert_eq!(first.bids, [(100, mean as f64 + 0.5)]);
602
603 let mut history = DepthHistory::new(config_columns(2)).unwrap();
604 for (second, bid) in [(0, 1.0), (5, 3.0), (6, 5.0)] {
605 history.push(column(second, 1, bid));
606 }
607 let held = Vec::from_iter(history.columns().map(|c| (c.time, c.span, c.bids[0].1)));
608 let one = TimeDelta::seconds(1);
609 assert_eq!(
610 held,
611 [(at(0), one, 1.0), (at(5), one, 3.0), (at(6), one, 5.0)]
612 );
613 }
614
615 #[test]
616 fn historical_pages_preserve_live_columns_and_gaps() {
617 #[rustfmt::skip]
618 let cases = [
619 ("overlap", vec![column(1, 1, 1.0), column(2, 1, 99.0)],
620 vec![(1, 1.0), (2, 2.0), (3, 3.0)]),
621 ("gap", vec![column(-4, 1, 4.0)], vec![(-4, 4.0), (2, 2.0), (3, 3.0)]),
622 ("empty", vec![], vec![(2, 2.0), (3, 3.0)]),
623 ];
624 for (name, page, expected) in cases {
625 let mut history = DepthHistory::new(config_columns(10)).unwrap();
626 history.push(column(2, 1, 2.0));
627 history.push(column(3, 1, 3.0));
628 history.merge_page(page).unwrap();
629 let held: Vec<_> = history
630 .columns()
631 .map(|c| (c.time.timestamp(), c.bids[0].1))
632 .collect();
633 assert_eq!(held, expected, "{name}");
634 history.push(column(3, 1, 30.0));
635 assert_eq!(history.columns().filter(|c| c.time == at(3)).count(), 1);
636 assert_eq!(history.columns().next_back().unwrap().bids, [(100, 30.0)]);
637 let before: Vec<_> = history.columns().cloned().collect();
638 assert!(history.merge_page(vec![column(1, -1, 1.0)]).is_err());
639 let edge = DepthColumn {
640 time: DateTime::from_timestamp_nanos(i64::MIN),
641 ..column(1, 1, 1.0)
642 };
643 assert!(history.merge_page(vec![edge]).is_err());
644 for largest in [vec![1.0, 1.0], vec![f64::NAN], vec![-1.0]] {
646 let invalid = DepthColumn {
647 largest_bid_orders: largest,
648 ..column(1, 1, 1.0)
649 };
650 assert!(history.merge_page(vec![invalid]).is_err());
651 }
652 assert_eq!(history.columns().cloned().collect::<Vec<_>>(), before);
653 }
654
655 let mut history = DepthHistory::new(DepthHistoryConfig::default()).unwrap();
657 for second in 0..3_600 {
658 history.push(column(second, 1, second as f64));
659 }
660 for page in 1..=246 {
661 let before = history.columns().next().unwrap().time.timestamp();
662 let older = (before - 600..before)
663 .map(|s| column(s, 1, s.unsigned_abs() as f64))
664 .collect();
665 assert_eq!(history.merge_page(older).unwrap(), 600, "page {page}");
666 assert!(
667 history.columns().next().unwrap().time < at(before),
668 "page {page}"
669 );
670 }
671 assert_eq!(history.columns().next().unwrap().time, at(-41 * 3_600));
672 let mut accepted = 600;
674 for _ in 0..16 {
675 let before = history.columns().next().unwrap().time.timestamp();
676 accepted = history
677 .merge_page((before - 600..before).map(|s| column(s, 1, 1.0)).collect())
678 .unwrap();
679 let oldest = history.columns().next().unwrap().time.timestamp();
680 assert_eq!(accepted, (before - oldest) as usize);
681 if accepted == 0 {
682 break;
683 }
684 }
685 assert_eq!(accepted, 0);
686 }
687
688 #[test]
693 fn history_updates_preserve_held_resolution() {
694 let mut history = DepthHistory::new(config_columns(2)).unwrap();
695 for second in 0..40 {
696 history.push(column(second, 1, second as f64));
697 }
698 let before = history.tiers.clone();
699 for page in [vec![], vec![column(0, 40, 99.0)]] {
700 assert_eq!(history.merge_page(page).unwrap(), 0);
701 assert_eq!(history.tiers, before);
702 }
703 for page in 1..=5 {
704 let older = (-8 * page..-8 * (page - 1))
705 .map(|s| column(s, 1, s.unsigned_abs() as f64))
706 .collect();
707 assert_eq!(history.merge_page(older).unwrap(), 8);
708 for held in before.iter().flatten() {
709 assert!(history.shared().any(|c| Arc::ptr_eq(c, held)));
710 }
711 }
712 assert_eq!(history.columns().next().unwrap().time, at(-40));
713 history.push(column(39, 1, 99.0));
714 for held in before.iter().flatten().filter(|c| c.time < at(39)) {
715 assert!(history.shared().any(|c| Arc::ptr_eq(c, held)));
716 }
717 let paged = history;
718
719 let mut history = DepthHistory::new(config_columns(3)).unwrap();
721 history.merge_page(vec![column(0, 8, 4.0)]).unwrap();
722 history.push(column(20, 1, 20.0));
723 let held: Vec<_> = history.shared().cloned().collect();
724 assert_eq!(
725 history
726 .merge_page(vec![column(8, 4, 8.0), column(16, 1, 16.0)])
727 .unwrap(),
728 2
729 );
730 history.push(column(12, 4, 12.0));
731 for c in held {
732 assert!(history.shared().any(|h| Arc::ptr_eq(h, &c)));
733 }
734 assert_eq!(
735 history
736 .columns()
737 .map(|c| (c.time.timestamp(), c.span.num_seconds()))
738 .collect::<Vec<_>>(),
739 [(0, 8), (8, 4), (12, 4), (16, 1), (20, 1)]
740 );
741 for history in [&paged, &history] {
742 assert!(
743 history
744 .tiers
745 .iter()
746 .all(|tier| tier.len() <= history.config.columns)
747 );
748 let columns: Vec<_> = history.columns().collect();
749 assert!(
750 columns
751 .windows(2)
752 .all(|w| w[0].time + w[0].span <= w[1].time)
753 );
754 }
755
756 for order in [[1, 2, 3], [1, 3, 2]] {
760 let mut history = DepthHistory::new(DepthHistoryConfig::default()).unwrap();
761 history.merge_page(vec![column(0, 16, 1.0)]).unwrap();
762 for page in order {
763 let older = (-24 * page..-24 * (page - 1))
764 .map(|s| column(s, 1, 1.0))
765 .collect();
766 assert_eq!(history.merge_page(older).unwrap(), 24, "{order:?}");
767 }
768 assert_eq!(
769 history
770 .columns()
771 .map(|c| (c.time.timestamp(), c.span.num_seconds()))
772 .collect::<Vec<_>>(),
773 [
774 (-72, 8),
775 (-64, 16),
776 (-48, 16),
777 (-32, 16),
778 (-16, 16),
779 (0, 16)
780 ],
781 "{order:?}"
782 );
783 }
784 }
785
786 fn config_columns(columns: usize) -> DepthHistoryConfig {
787 DepthHistoryConfig {
788 columns,
789 ..Default::default()
790 }
791 }
792}