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    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 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}