Skip to main content

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}