sdb_core/postprocessing/
level.rs

1//! Level is the transpose of updates
2//! normally updates are of shape [time -> price -> size]
3//! this is [price -> time -> size] to keep track of
4//! size changes on each price level over time.
5use std::collections::{BTreeMap, HashMap};
6use crate::postprocessing::histogram::{Histogram, BinCount};
7use crate::utils::fill_digits;
8use crate::dtf::update::Update;
9
10type Price = u64;
11type Time = u32;
12type Size = f32;
13
14/// data structure for storing levels
15#[derive(Debug)]
16pub struct Levels {
17    levels: HashMap<Price, BTreeMap<Time, Size>>,
18}
19
20impl Levels {
21    /// converts a slice of Update to [price, time, size]
22    /// see how price levels evolve over time...
23    pub fn from(ups: &[Update], step_bins: BinCount, tick_bins: BinCount, m: f64) -> Levels {
24        let (price_hist, step_hist) = Histogram::from(&ups, step_bins, tick_bins, m);
25        // println!("{:?}", step_hist);
26
27        // build map for levels
28        let mut map = HashMap::new();
29        for up in ups.iter() {
30            let price = price_hist.to_bin(up.price as f64);
31            let time = step_hist.to_bin((fill_digits(up.ts) / 1000) as f64);
32            match (price, time) {
33                (Some(p), Some(t)) => {
34                    let price_level = map.entry(p.to_bits()).or_insert(
35                        BTreeMap::<Time, Size>::new(),
36                    );
37                    (*price_level).insert(t as Time, up.size);
38                }
39                (None, _) => {
40                    continue;
41                }
42                (_, None) => {
43                    continue;
44                }
45            }
46        }
47
48        Levels { levels: map }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::dtf;
56    static FNAME: &str = "../../test/test-data/bt_btcnav.dtf";
57
58    #[test]
59    pub fn test_levels() {
60        // rebin price
61        let tick_bins = 10; // or 9 thresholds
62        let step_bins = 10;
63        let records = dtf::file_format::decode(FNAME, Some(100)).unwrap();
64        {
65            let prices = records
66                .iter()
67                .map(|up| up.price as f64)
68                .collect::<Vec<f64>>();
69            let price_hist = Histogram::new(&prices, tick_bins, 2.0);
70            let mut dict = BTreeMap::new();
71            for up in records.iter() {
72                if let Some(binned_val) = price_hist.to_bin(up.price as f64) {
73                    let entry = dict.entry(binned_val.to_bits()).or_insert(0);
74                    (*entry) += 1;
75                }
76            }
77            assert_eq!(price_hist.boundaries.len(), tick_bins);
78            assert_eq!(price_hist.bins.clone().unwrap().len(), tick_bins);
79
80            for (val, bin) in dict.values().zip(price_hist.bins.unwrap().iter()) {
81                assert_eq!(val, bin);
82            }
83        }
84
85        let levels = Levels::from(records.as_slice(), step_bins, tick_bins, 2.);
86        assert_eq!(
87            levels.levels.keys().collect::<Vec<_>>().len(),
88            tick_bins - 1
89        );
90        for level in levels.levels.values() {
91            assert!(level.keys().collect::<Vec<_>>().len() <= (step_bins - 1));
92        }
93
94    }
95}