Skip to main content

flowsurface_data/panel/
timeandsales.rs

1use std::time::Duration;
2
3use exchange::Trade;
4use exchange::unit::Qty;
5use serde::{Deserialize, Serialize};
6
7use crate::util::ok_or_default;
8
9const TRADE_RETENTION_MS: u64 = 120_000;
10
11#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
12pub struct Config {
13    pub trade_size_filter: f32,
14    #[serde(default = "default_buffer_filter")]
15    pub trade_retention: Duration,
16    #[serde(deserialize_with = "ok_or_default", default)]
17    pub stacked_bar: Option<StackedBar>,
18}
19
20impl Default for Config {
21    fn default() -> Self {
22        Config {
23            trade_size_filter: 0.0,
24            trade_retention: Duration::from_millis(TRADE_RETENTION_MS),
25            stacked_bar: StackedBar::Compact(StackedBarRatio::default()).into(),
26        }
27    }
28}
29
30fn default_buffer_filter() -> Duration {
31    Duration::from_millis(TRADE_RETENTION_MS)
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Copy)]
35pub enum StackedBar {
36    Compact(StackedBarRatio),
37    Full(StackedBarRatio),
38}
39
40impl StackedBar {
41    pub fn ratio(self) -> StackedBarRatio {
42        match self {
43            StackedBar::Compact(r) | StackedBar::Full(r) => r,
44        }
45    }
46
47    pub fn with_ratio(self, r: StackedBarRatio) -> Self {
48        match self {
49            StackedBar::Compact(_) => StackedBar::Compact(r),
50            StackedBar::Full(_) => StackedBar::Full(r),
51        }
52    }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Copy)]
56pub enum StackedBarRatio {
57    Count,
58    #[default]
59    Volume,
60    AverageSize,
61}
62
63impl std::fmt::Display for StackedBarRatio {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        match self {
66            StackedBarRatio::Count => write!(f, "Count"),
67            StackedBarRatio::AverageSize => write!(f, "Average trade size"),
68            StackedBarRatio::Volume => write!(f, "Volume"),
69        }
70    }
71}
72
73impl StackedBarRatio {
74    pub const ALL: [StackedBarRatio; 3] = [
75        StackedBarRatio::Count,
76        StackedBarRatio::Volume,
77        StackedBarRatio::AverageSize,
78    ];
79}
80
81#[derive(Debug, Clone, Copy)]
82pub enum HistAggValues {
83    Count { buy: u64, sell: u64 },
84    Qty { buy: Qty, sell: Qty },
85}
86
87#[derive(Default)]
88pub struct HistAgg {
89    buy_count: u64,
90    sell_count: u64,
91    buy_sum: Qty,
92    sell_sum: Qty,
93}
94
95impl HistAgg {
96    fn average_qty(sum: Qty, count: u64) -> Qty {
97        if count == 0 {
98            return Qty::ZERO;
99        }
100
101        let c = count.min(i64::MAX as u64) as i64;
102        let half = c / 2;
103        let rounded = if sum.units >= 0 {
104            sum.units.saturating_add(half).div_euclid(c)
105        } else {
106            sum.units.saturating_sub(half).div_euclid(c)
107        };
108
109        Qty::from_units(rounded)
110    }
111
112    pub fn add(&mut self, trade: &Trade) {
113        let qty = trade.qty;
114
115        if trade.is_sell {
116            self.sell_count += 1;
117            self.sell_sum += qty;
118        } else {
119            self.buy_count += 1;
120            self.buy_sum += qty;
121        }
122    }
123
124    pub fn remove(&mut self, trade: &Trade) {
125        let qty = trade.qty;
126
127        if trade.is_sell {
128            self.sell_count = self.sell_count.saturating_sub(1);
129            self.sell_sum = if self.sell_sum.units >= qty.units {
130                self.sell_sum - qty
131            } else {
132                Qty::ZERO
133            };
134        } else {
135            self.buy_count = self.buy_count.saturating_sub(1);
136            self.buy_sum = if self.buy_sum.units >= qty.units {
137                self.buy_sum - qty
138            } else {
139                Qty::ZERO
140            };
141        }
142    }
143
144    pub fn values_for(&self, ratio_kind: StackedBarRatio) -> Option<HistAggValues> {
145        match ratio_kind {
146            StackedBarRatio::Count => {
147                let buy = self.buy_count;
148                let sell = self.sell_count;
149                let total = buy.saturating_add(sell);
150
151                if total == 0 {
152                    return None;
153                }
154
155                Some(HistAggValues::Count { buy, sell })
156            }
157            StackedBarRatio::Volume => {
158                let buy = self.buy_sum.to_f32_lossy();
159                let sell = self.sell_sum.to_f32_lossy();
160                let total = buy + sell;
161
162                if total <= 0.0 {
163                    return None;
164                }
165
166                Some(HistAggValues::Qty {
167                    buy: self.buy_sum,
168                    sell: self.sell_sum,
169                })
170            }
171            StackedBarRatio::AverageSize => {
172                let buy_avg = Self::average_qty(self.buy_sum, self.buy_count);
173                let sell_avg = Self::average_qty(self.sell_sum, self.sell_count);
174
175                let denom = buy_avg.units.saturating_add(sell_avg.units);
176                if denom <= 0 {
177                    return None;
178                }
179
180                Some(HistAggValues::Qty {
181                    buy: buy_avg,
182                    sell: sell_avg,
183                })
184            }
185        }
186    }
187}