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