1use exchange::adapter::PersistStreamKind;
2use exchange::{TickMultiplier, TickerInfo, Timeframe};
3use serde::{Deserialize, Serialize};
4
5use crate::chart::{comparison, heatmap, kline};
6use crate::panel::{ladder, timeandsales};
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 tick_size: f32,
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 = match content_kind {
249 ContentKind::HeatmapChart => {
250 let current = current_basis.and_then(|b| match b {
251 Basis::Time(tf) if exchange.supports_heatmap_timeframe(tf) => Some(b),
252 _ => None,
253 });
254 Some(current.unwrap_or_else(|| Basis::default_heatmap_time(Some(base_ticker))))
255 }
256 ContentKind::Ladder => Some(
257 current_basis.unwrap_or_else(|| Basis::default_heatmap_time(Some(base_ticker))),
258 ),
259 ContentKind::FootprintChart => {
260 Some(current_basis.unwrap_or(Basis::Time(Timeframe::M5)))
261 }
262 ContentKind::CandlestickChart | ContentKind::ComparisonChart => {
263 Some(current_basis.unwrap_or(Basis::Time(Timeframe::M15)))
264 }
265 ContentKind::Starter | ContentKind::TimeAndSales => None,
266 };
267
268 let tick_multiplier = match content_kind {
269 ContentKind::HeatmapChart | ContentKind::Ladder => {
270 let tm = if !is_client_aggr && prev_is_client_aggr {
271 TickMultiplier(10)
272 } else if let Some(tm) = current_tick_multiplier {
273 tm
274 } else if is_client_aggr {
275 TickMultiplier(5)
276 } else {
277 TickMultiplier(10)
278 };
279 Some(tm)
280 }
281 ContentKind::FootprintChart => {
282 Some(current_tick_multiplier.unwrap_or(TickMultiplier(50)))
283 }
284 ContentKind::CandlestickChart
285 | ContentKind::ComparisonChart
286 | ContentKind::TimeAndSales
287 | ContentKind::Starter => current_tick_multiplier,
288 };
289
290 let tick_size = match tick_multiplier {
291 Some(tm) => tm.multiply_with_min_tick_size(base_ticker),
292 None => base_ticker.min_ticksize.into(),
293 };
294
295 let depth_aggr = exchange.stream_ticksize(tick_multiplier, TickMultiplier(50));
296
297 let push_freq = match content_kind {
298 ContentKind::HeatmapChart if exchange.is_custom_push_freq() => match basis {
299 Some(Basis::Time(tf)) if exchange.supports_heatmap_timeframe(tf) => {
300 exchange::PushFrequency::Custom(tf)
301 }
302 _ => exchange::PushFrequency::ServerDefault,
303 },
304 _ => exchange::PushFrequency::ServerDefault,
305 };
306
307 Self {
308 ticker_info: base_ticker,
309 basis,
310 tick_multiplier,
311 tick_size,
312 depth_aggr,
313 push_freq,
314 }
315 }
316}