flowsurface_data/layout/
pane.rs

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/// Defines the specific configuration for different types of pane settings.
139#[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}