flowsurface_data/panel/
timeandsales.rs1use std::time::Duration;
2
3use exchange::UnixMs;
4use exchange::unit::{Price, 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)]
35pub struct TradeDisplay {
36 pub time_str: String,
37 pub price: Price,
38 pub qty: Qty,
39 pub is_sell: bool,
40}
41
42#[derive(Debug, Clone)]
43pub struct TradeEntry {
44 pub ts_ms: UnixMs,
45 pub display: TradeDisplay,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Copy)]
49pub enum StackedBar {
50 Compact(StackedBarRatio),
51 Full(StackedBarRatio),
52}
53
54impl StackedBar {
55 pub fn ratio(self) -> StackedBarRatio {
56 match self {
57 StackedBar::Compact(r) | StackedBar::Full(r) => r,
58 }
59 }
60
61 pub fn with_ratio(self, r: StackedBarRatio) -> Self {
62 match self {
63 StackedBar::Compact(_) => StackedBar::Compact(r),
64 StackedBar::Full(_) => StackedBar::Full(r),
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Copy)]
70pub enum StackedBarRatio {
71 Count,
72 #[default]
73 Volume,
74 AverageSize,
75}
76
77impl std::fmt::Display for StackedBarRatio {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 StackedBarRatio::Count => write!(f, "Count"),
81 StackedBarRatio::AverageSize => write!(f, "Average trade size"),
82 StackedBarRatio::Volume => write!(f, "Volume"),
83 }
84 }
85}
86
87impl StackedBarRatio {
88 pub const ALL: [StackedBarRatio; 3] = [
89 StackedBarRatio::Count,
90 StackedBarRatio::Volume,
91 StackedBarRatio::AverageSize,
92 ];
93}
94
95#[derive(Debug, Clone, Copy)]
96pub enum HistAggValues {
97 Count { buy: u64, sell: u64 },
98 Qty { buy: Qty, sell: Qty },
99}
100
101#[derive(Default)]
102pub struct HistAgg {
103 buy_count: u64,
104 sell_count: u64,
105 buy_sum: Qty,
106 sell_sum: Qty,
107}
108
109impl HistAgg {
110 fn average_qty(sum: Qty, count: u64) -> Qty {
111 if count == 0 {
112 return Qty::ZERO;
113 }
114
115 let c = count.min(i64::MAX as u64) as i64;
116 let half = c / 2;
117 let rounded = if sum.units >= 0 {
118 sum.units.saturating_add(half).div_euclid(c)
119 } else {
120 sum.units.saturating_sub(half).div_euclid(c)
121 };
122
123 Qty::from_units(rounded)
124 }
125
126 pub fn add(&mut self, trade: &TradeDisplay) {
127 let qty = trade.qty;
128
129 if trade.is_sell {
130 self.sell_count += 1;
131 self.sell_sum += qty;
132 } else {
133 self.buy_count += 1;
134 self.buy_sum += qty;
135 }
136 }
137
138 pub fn remove(&mut self, trade: &TradeDisplay) {
139 let qty = trade.qty;
140
141 if trade.is_sell {
142 self.sell_count = self.sell_count.saturating_sub(1);
143 self.sell_sum = if self.sell_sum.units >= qty.units {
144 self.sell_sum - qty
145 } else {
146 Qty::ZERO
147 };
148 } else {
149 self.buy_count = self.buy_count.saturating_sub(1);
150 self.buy_sum = if self.buy_sum.units >= qty.units {
151 self.buy_sum - qty
152 } else {
153 Qty::ZERO
154 };
155 }
156 }
157
158 pub fn values_for(&self, ratio_kind: StackedBarRatio) -> Option<HistAggValues> {
159 match ratio_kind {
160 StackedBarRatio::Count => {
161 let buy = self.buy_count;
162 let sell = self.sell_count;
163 let total = buy.saturating_add(sell);
164
165 if total == 0 {
166 return None;
167 }
168
169 Some(HistAggValues::Count { buy, sell })
170 }
171 StackedBarRatio::Volume => {
172 let buy = self.buy_sum.to_f32_lossy();
173 let sell = self.sell_sum.to_f32_lossy();
174 let total = buy + sell;
175
176 if total <= 0.0 {
177 return None;
178 }
179
180 Some(HistAggValues::Qty {
181 buy: self.buy_sum,
182 sell: self.sell_sum,
183 })
184 }
185 StackedBarRatio::AverageSize => {
186 let buy_avg = Self::average_qty(self.buy_sum, self.buy_count);
187 let sell_avg = Self::average_qty(self.sell_sum, self.sell_count);
188
189 let denom = buy_avg.units.saturating_add(sell_avg.units);
190 if denom <= 0 {
191 return None;
192 }
193
194 Some(HistAggValues::Qty {
195 buy: buy_avg,
196 sell: sell_avg,
197 })
198 }
199 }
200 }
201}