flowsurface_data/panel/
timeandsales.rs1use 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}