Skip to main content

flowsurface_data/layout/
pane.rs

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