1use exchange::{TickMultiplier, TickerInfo, Timeframe};
2use serde::{Deserialize, Serialize};
3
4use crate::chart::{comparison, heatmap, kline};
5use crate::panel::{ladder, timeandsales};
6use crate::stream::PersistStreamKind;
7use crate::util::ok_or_default;
8
9use crate::chart::{
10 Basis, ViewConfig,
11 heatmap::HeatmapStudy,
12 indicator::{HeatmapIndicator, KlineIndicator},
13 kline::KlineChartKind,
14};
15
16#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
17pub enum Axis {
18 Horizontal,
19 Vertical,
20}
21
22#[derive(Debug, Clone, Deserialize, Serialize)]
23pub enum Pane {
24 Split {
25 axis: Axis,
26 ratio: f32,
27 a: Box<Pane>,
28 b: Box<Pane>,
29 },
30 Starter {
31 #[serde(deserialize_with = "ok_or_default", default)]
32 link_group: Option<LinkGroup>,
33 },
34 HeatmapChart {
35 layout: ViewConfig,
36 #[serde(deserialize_with = "ok_or_default", default)]
37 studies: Vec<HeatmapStudy>,
38 #[serde(deserialize_with = "ok_or_default", default)]
39 stream_type: Vec<PersistStreamKind>,
40 #[serde(deserialize_with = "ok_or_default")]
41 settings: Settings,
42 #[serde(deserialize_with = "ok_or_default", default)]
43 indicators: Vec<HeatmapIndicator>,
44 #[serde(deserialize_with = "ok_or_default", default)]
45 link_group: Option<LinkGroup>,
46 },
47 KlineChart {
48 layout: ViewConfig,
49 kind: KlineChartKind,
50 #[serde(deserialize_with = "ok_or_default", default)]
51 stream_type: Vec<PersistStreamKind>,
52 #[serde(deserialize_with = "ok_or_default")]
53 settings: Settings,
54 #[serde(deserialize_with = "ok_or_default", default)]
55 indicators: Vec<KlineIndicator>,
56 #[serde(deserialize_with = "ok_or_default", default)]
57 link_group: Option<LinkGroup>,
58 },
59 ComparisonChart {
60 stream_type: Vec<PersistStreamKind>,
61 #[serde(deserialize_with = "ok_or_default")]
62 settings: Settings,
63 #[serde(deserialize_with = "ok_or_default", default)]
64 link_group: Option<LinkGroup>,
65 },
66 TimeAndSales {
67 stream_type: Vec<PersistStreamKind>,
68 settings: Settings,
69 #[serde(deserialize_with = "ok_or_default", default)]
70 link_group: Option<LinkGroup>,
71 },
72 Ladder {
73 stream_type: Vec<PersistStreamKind>,
74 settings: Settings,
75 #[serde(deserialize_with = "ok_or_default", default)]
76 link_group: Option<LinkGroup>,
77 },
78}
79
80impl Default for Pane {
81 fn default() -> Self {
82 Pane::Starter { link_group: None }
83 }
84}
85
86#[derive(Debug, Clone, Deserialize, Serialize, Default)]
87#[serde(default)]
88pub struct Settings {
89 pub tick_multiply: Option<exchange::TickMultiplier>,
90 pub visual_config: Option<VisualConfig>,
91 pub selected_basis: Option<Basis>,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
95pub enum LinkGroup {
96 A,
97 B,
98 C,
99 D,
100 E,
101 F,
102 G,
103 H,
104 I,
105}
106
107impl LinkGroup {
108 pub const ALL: [LinkGroup; 9] = [
109 LinkGroup::A,
110 LinkGroup::B,
111 LinkGroup::C,
112 LinkGroup::D,
113 LinkGroup::E,
114 LinkGroup::F,
115 LinkGroup::G,
116 LinkGroup::H,
117 LinkGroup::I,
118 ];
119}
120
121impl std::fmt::Display for LinkGroup {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 let c = match self {
124 LinkGroup::A => "1",
125 LinkGroup::B => "2",
126 LinkGroup::C => "3",
127 LinkGroup::D => "4",
128 LinkGroup::E => "5",
129 LinkGroup::F => "6",
130 LinkGroup::G => "7",
131 LinkGroup::H => "8",
132 LinkGroup::I => "9",
133 };
134 write!(f, "{c}")
135 }
136}
137
138#[derive(Debug, Clone, Deserialize, Serialize)]
140pub enum VisualConfig {
141 Heatmap(heatmap::Config),
142 TimeAndSales(timeandsales::Config),
143 Kline(kline::Config),
144 Ladder(ladder::Config),
145 Comparison(comparison::Config),
146}
147
148impl VisualConfig {
149 pub fn heatmap(&self) -> Option<heatmap::Config> {
150 match self {
151 Self::Heatmap(cfg) => Some(*cfg),
152 _ => None,
153 }
154 }
155
156 pub fn time_and_sales(&self) -> Option<timeandsales::Config> {
157 match self {
158 Self::TimeAndSales(cfg) => Some(*cfg),
159 _ => None,
160 }
161 }
162
163 pub fn kline(&self) -> Option<kline::Config> {
164 match self {
165 Self::Kline(cfg) => Some(*cfg),
166 _ => None,
167 }
168 }
169
170 pub fn ladder(&self) -> Option<ladder::Config> {
171 match self {
172 Self::Ladder(cfg) => Some(*cfg),
173 _ => None,
174 }
175 }
176
177 pub fn comparison(&self) -> Option<comparison::Config> {
178 match self {
179 Self::Comparison(cfg) => Some(cfg.clone()),
180 _ => None,
181 }
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
186pub enum ContentKind {
187 Starter,
188 HeatmapChart,
189 FootprintChart,
190 CandlestickChart,
191 ComparisonChart,
192 TimeAndSales,
193 Ladder,
194}
195
196impl ContentKind {
197 pub const ALL: [ContentKind; 7] = [
198 ContentKind::Starter,
199 ContentKind::HeatmapChart,
200 ContentKind::FootprintChart,
201 ContentKind::CandlestickChart,
202 ContentKind::ComparisonChart,
203 ContentKind::TimeAndSales,
204 ContentKind::Ladder,
205 ];
206}
207
208impl std::fmt::Display for ContentKind {
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 let s = match self {
211 ContentKind::Starter => "Starter Pane",
212 ContentKind::HeatmapChart => "Heatmap Chart",
213 ContentKind::FootprintChart => "Footprint Chart",
214 ContentKind::CandlestickChart => "Candlestick Chart",
215 ContentKind::ComparisonChart => "Comparison Chart",
216 ContentKind::TimeAndSales => "Time&Sales",
217 ContentKind::Ladder => "DOM/Ladder",
218 };
219 write!(f, "{s}")
220 }
221}
222
223#[derive(Clone, Copy)]
224pub struct PaneSetup {
225 pub ticker_info: exchange::TickerInfo,
226 pub basis: Option<Basis>,
227 pub tick_multiplier: Option<TickMultiplier>,
228 pub price_step: exchange::unit::PriceStep,
229 pub depth_aggr: exchange::adapter::StreamTicksize,
230 pub push_freq: exchange::PushFrequency,
231}
232
233impl PaneSetup {
234 pub fn new(
235 content_kind: ContentKind,
236 base_ticker: TickerInfo,
237 prev_base_ticker: Option<TickerInfo>,
238 current_basis: Option<Basis>,
239 current_tick_multiplier: Option<TickMultiplier>,
240 ) -> Self {
241 let exchange = base_ticker.ticker.exchange;
242
243 let is_client_aggr = exchange.is_depth_client_aggr();
244 let prev_is_client_aggr = prev_base_ticker
245 .map(|ti| ti.ticker.exchange.is_depth_client_aggr())
246 .unwrap_or(is_client_aggr);
247
248 let basis =
249 match content_kind {
250 ContentKind::HeatmapChart => {
251 let current = current_basis.and_then(|b| match b {
252 Basis::Time(tf) if exchange.supports_heatmap_timeframe(tf) => Some(b),
253 _ => None,
254 });
255
256 Some(current.unwrap_or_else(|| Basis::default_heatmap_time(Some(base_ticker))))
257 }
258 ContentKind::Ladder => Some(
259 current_basis.unwrap_or_else(|| Basis::default_heatmap_time(Some(base_ticker))),
260 ),
261 ContentKind::FootprintChart => {
262 let current = current_basis.and_then(|b| match b {
263 Basis::Time(tf) if exchange.supports_kline_timeframe(tf) => Some(b),
264 Basis::Tick(_) => Some(b),
265 _ => None,
266 });
267
268 Some(current.unwrap_or_else(|| {
269 Basis::default_kline_time(Some(base_ticker), Timeframe::M5)
270 }))
271 }
272 ContentKind::CandlestickChart | ContentKind::ComparisonChart => {
273 let current = current_basis.and_then(|b| match b {
274 Basis::Time(tf) if exchange.supports_kline_timeframe(tf) => Some(b),
275 _ => None,
276 });
277
278 Some(current.unwrap_or_else(|| {
279 Basis::default_kline_time(Some(base_ticker), Timeframe::M15)
280 }))
281 }
282 ContentKind::Starter | ContentKind::TimeAndSales => None,
283 };
284
285 let tick_multiplier = match content_kind {
286 ContentKind::HeatmapChart | ContentKind::Ladder => {
287 let tm = if !is_client_aggr && prev_is_client_aggr {
288 TickMultiplier(10)
289 } else if let Some(tm) = current_tick_multiplier {
290 tm
291 } else if is_client_aggr {
292 TickMultiplier(5)
293 } else {
294 TickMultiplier(10)
295 };
296 Some(tm)
297 }
298 ContentKind::FootprintChart => {
299 Some(current_tick_multiplier.unwrap_or(TickMultiplier(50)))
300 }
301 ContentKind::CandlestickChart
302 | ContentKind::ComparisonChart
303 | ContentKind::TimeAndSales
304 | ContentKind::Starter => current_tick_multiplier,
305 };
306
307 let price_step = match tick_multiplier {
308 Some(tm) => tm.multiply_with_min_tick_step(base_ticker),
309 None => base_ticker.min_ticksize.into(),
310 };
311
312 let depth_aggr = exchange.stream_ticksize(tick_multiplier, TickMultiplier(50));
313
314 let push_freq = match content_kind {
315 ContentKind::HeatmapChart if exchange.is_custom_push_freq() => match basis {
316 Some(Basis::Time(tf)) if exchange.supports_heatmap_timeframe(tf) => {
317 exchange::PushFrequency::Custom(tf)
318 }
319 _ => exchange::PushFrequency::ServerDefault,
320 },
321 _ => exchange::PushFrequency::ServerDefault,
322 };
323
324 Self {
325 ticker_info: base_ticker,
326 basis,
327 tick_multiplier,
328 price_step,
329 depth_aggr,
330 push_freq,
331 }
332 }
333}