egui_charts/data/study.rs
1//! Additional price or metric histories requested by a study or drawing from the host's data source.
2
3use crate::model::{Bar, BarClock, Instrument, Timeframe};
4use chrono::{DateTime, Utc};
5
6/// A study or drawing's additional history and live subscription. Empty symbols and absent
7/// intervals follow the chart. The host owns symbol identifiers and supplies the market data.
8#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
9pub struct StudyDataRequest {
10 /// Host symbol identifier, or empty for the chart symbol.
11 pub symbol: String,
12 /// Requested interval, or `None` for the chart interval.
13 pub timeframe: Option<Timeframe>,
14 /// Host metric key, or `None` for price bars. Metric points keep their publication times
15 /// and hold their last value; they do not use the requested price-bar interval.
16 #[serde(default)]
17 pub metric: Option<String>,
18}
19
20/// A requested series available during calculation, sorted by time with unique bars.
21/// Metric points use their value for OHLC and zero volume, without interval aggregation.
22/// An unavailable history has no bars; it is never substituted with the chart's prices.
23pub struct StudyData<'a> {
24 /// The declaration this series answers, before resolving chart defaults.
25 pub request: StudyDataRequest,
26 /// Historical and current bars owned by the chart or the host.
27 pub bars: &'a [Bar],
28 /// Boundaries of the requested interval in its exchange calendar.
29 pub clock: BarClock,
30 /// Resolved instrument metadata, when available.
31 pub instrument: Option<&'a Instrument>,
32 /// Resolved calculation interval.
33 pub timeframe: Timeframe,
34}
35
36impl StudyData<'_> {
37 /// Index of the bar containing `time`, retaining missing price intervals as gaps.
38 /// For metrics, the latest point at or before `time`, held until the next publication.
39 pub fn bar_at(&self, time: DateTime<Utc>) -> Option<usize> {
40 if self.request.metric.is_some() {
41 return self
42 .bars
43 .partition_point(|bar| bar.time <= time)
44 .checked_sub(1);
45 }
46 let open = self.clock.bar_open(time);
47 let at = self
48 .bars
49 .partition_point(|bar| self.clock.bar_open(bar.time) <= open)
50 .checked_sub(1)?;
51 self.clock.same_bar(self.bars[at].time, time).then_some(at)
52 }
53
54 /// Latest output available at a chart bar's close. Historical higher-interval outputs
55 /// appear only after their bar closes. Disabling `wait_for_close` also exposes the newest
56 /// developing bar on the chart's latest bar, which may change with each feed update.
57 pub(crate) fn output_at(&self, close: DateTime<Utc>, developing: bool) -> Option<usize> {
58 let end = self
59 .bars
60 .partition_point(|bar| self.clock.bar_open(bar.time) < close);
61 let at = end.checked_sub(1)?;
62 if developing && at + 1 == self.bars.len() {
63 return Some(at);
64 }
65 let closed = self
66 .clock
67 .next_open(self.clock.bar_open(self.bars[at].time))
68 .is_some_and(|end| end <= close);
69 if closed { Some(at) } else { at.checked_sub(1) }
70 }
71}