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