1pub mod comparison;
2pub mod heatmap;
3pub mod indicator;
4pub mod kline;
5
6use exchange::UnixMs;
7use exchange::{Timeframe, unit::Price};
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10
11use self::kline::KlineDataPoint;
12use super::aggr::{
13 self,
14 ticks::TickAggr,
15 time::{DataPoint, TimeSeries},
16};
17pub use kline::KlineChartKind;
18
19pub enum PlotData<D: DataPoint> {
25 TimeBased(TimeSeries<D>),
26 TickBased(TickAggr),
27}
28
29impl<D: DataPoint> PlotData<D> {
30 pub fn map_basis_series<T, FT, FK>(&self, map_time: FT, map_tick: FK) -> BasisSeries<T>
32 where
33 FT: FnOnce(&TimeSeries<D>) -> BTreeMap<UnixMs, T>,
34 FK: FnOnce(&TickAggr) -> BTreeMap<u64, T>,
35 {
36 match self {
37 PlotData::TimeBased(timeseries) => BasisSeries::time(map_time(timeseries)),
38 PlotData::TickBased(tickseries) => BasisSeries::tick(map_tick(tickseries)),
39 }
40 }
41
42 pub fn latest_y_midpoint(&self, calculate_target_y: impl Fn(exchange::Kline) -> f32) -> f32 {
43 match self {
44 PlotData::TimeBased(timeseries) => timeseries
45 .latest_kline()
46 .map_or(0.0, |kline| calculate_target_y(*kline)),
47 PlotData::TickBased(tick_aggr) => tick_aggr
48 .latest_dp()
49 .map_or(0.0, |(dp, _)| calculate_target_y(dp.kline)),
50 }
51 }
52
53 pub fn visible_price_range(
54 &self,
55 start_interval: u64,
56 end_interval: u64,
57 ) -> Option<(f32, f32)> {
58 match self {
59 PlotData::TimeBased(timeseries) => timeseries
60 .min_max_price_in_range(UnixMs::new(start_interval), UnixMs::new(end_interval)),
61 PlotData::TickBased(tick_aggr) => {
62 tick_aggr.min_max_price_in_range(start_interval as usize, end_interval as usize)
63 }
64 }
65 }
66}
67
68impl PlotData<KlineDataPoint> {
69 pub fn visible_footprint_price_range(
70 &self,
71 start_interval: u64,
72 end_interval: u64,
73 ) -> Option<(Price, Price)> {
74 match self {
75 PlotData::TickBased(tick_aggr) => tick_aggr
76 .min_max_footprint_price_in_range(start_interval as usize, end_interval as usize),
77 PlotData::TimeBased(timeseries) => timeseries
78 .min_max_footprint_price_in_range(UnixMs(start_interval), UnixMs(end_interval)),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
85pub enum Basis {
86 Time(exchange::Timeframe),
88
89 Tick(aggr::TickCount),
91}
92
93impl Basis {
94 pub fn is_time(&self) -> bool {
95 matches!(self, Basis::Time(_))
96 }
97
98 pub fn default_kline_time(
99 ticker_info: Option<exchange::TickerInfo>,
100 fallback: Timeframe,
101 ) -> Self {
102 let interval = ticker_info.map_or(fallback, |info| {
103 let exchange = info.exchange();
104
105 if exchange.supports_kline_timeframe(fallback) {
106 fallback
107 } else {
108 Timeframe::KLINE
109 .iter()
110 .copied()
111 .find(|tf| exchange.supports_kline_timeframe(*tf))
112 .unwrap_or(fallback)
113 }
114 });
115
116 interval.into()
117 }
118
119 pub fn default_heatmap_time(ticker_info: Option<exchange::TickerInfo>) -> Self {
120 let fallback = Timeframe::MS500;
121
122 let interval = ticker_info.map_or(fallback, |info| {
123 let ex = info.exchange();
124 Timeframe::HEATMAP
125 .iter()
126 .copied()
127 .find(|tf| ex.supports_heatmap_timeframe(*tf))
128 .unwrap_or(fallback)
129 });
130
131 interval.into()
132 }
133}
134
135#[derive(Debug, Clone)]
141pub enum BasisSeries<T> {
142 Time(BTreeMap<UnixMs, T>),
143 Tick(BTreeMap<u64, T>),
144}
145
146impl<T> Default for BasisSeries<T> {
147 fn default() -> Self {
148 Self::Time(BTreeMap::new())
149 }
150}
151
152impl<T> BasisSeries<T> {
153 pub fn time(data: BTreeMap<UnixMs, T>) -> Self {
154 Self::Time(data)
155 }
156
157 pub fn tick(data: BTreeMap<u64, T>) -> Self {
158 Self::Tick(data)
159 }
160
161 pub fn time_mut(&mut self) -> Option<&mut BTreeMap<UnixMs, T>> {
162 match self {
163 Self::Time(data) => Some(data),
164 Self::Tick(_) => None,
165 }
166 }
167
168 pub fn tick_mut(&mut self) -> Option<&mut BTreeMap<u64, T>> {
169 match self {
170 Self::Tick(data) => Some(data),
171 Self::Time(_) => None,
172 }
173 }
174
175 pub fn map<U, F>(&self, mut f: F) -> BasisSeries<U>
176 where
177 F: FnMut(&T) -> U,
178 {
179 match self {
180 Self::Time(data) => BasisSeries::Time(data.iter().map(|(&k, v)| (k, f(v))).collect()),
181 Self::Tick(data) => BasisSeries::Tick(data.iter().map(|(&k, v)| (k, f(v))).collect()),
182 }
183 }
184}
185
186impl std::fmt::Display for Basis {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 match self {
189 Basis::Time(timeframe) => write!(f, "{timeframe}"),
190 Basis::Tick(count) => write!(f, "{count}"),
191 }
192 }
193}
194
195impl From<exchange::Timeframe> for Basis {
196 fn from(timeframe: exchange::Timeframe) -> Self {
197 Self::Time(timeframe)
198 }
199}
200
201#[derive(Debug, Clone, Deserialize, Serialize, Default)]
202pub struct ViewConfig {
203 pub splits: Vec<f32>,
204 pub autoscale: Option<Autoscale>,
205}
206
207#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq)]
208pub enum Autoscale {
209 #[default]
210 CenterLatest,
211 FitToVisible,
212}
213
214#[derive(Debug, Clone, Deserialize, Serialize)]
215pub enum Study {
216 Heatmap(Vec<heatmap::HeatmapStudy>),
217 Footprint(Vec<kline::FootprintStudy>),
218}