Skip to main content

flowsurface_data/layout/
pane.rs

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