Skip to main content

egui_charts/
data.rs

1//! Data source abstraction for flexible data fetching.
2//!
3//! This module defines the [`DataSource`] trait -- the primary integration point
4//! for connecting external market data to the chart engine.  Any struct that
5//! implements `DataSource` can feed OHLCV bars into the chart, whether the data
6//! comes from a WebSocket stream, a REST API, a local CSV file, or an in-memory
7//! buffer.
8//!
9//! # Architecture
10//!
11//! ```text
12//!  ┌─────────────┐       ┌──────────────┐       ┌──────────────┐
13//!  │  WebSocket   │       │  REST API    │       │  CSV / File  │
14//!  └──────┬───────┘       └──────┬───────┘       └──────┬───────┘
15//!         │                      │                      │
16//!         └──────────┬───────────┴──────────┬───────────┘
17//!                    │                      │
18//!               impl DataSource        impl DataSource
19//!                    │                      │
20//!                    └──────────┬───────────┘
21//!                               │
22//!                      ┌────────▼────────┐
23//!                      │   Chart Engine  │
24//!                      └─────────────────┘
25//! ```
26//!
27//! # Implementing a DataSource
28//!
29//! At minimum, implement [`symbols`](DataSource::symbols),
30//! [`subscribe`](DataSource::subscribe), [`unsubscribe`](DataSource::unsubscribe),
31//! [`poll`](DataSource::poll), [`fetch_historical`](DataSource::fetch_historical),
32//! and [`get_timeframe`](DataSource::get_timeframe).  The remaining methods have
33//! sensible defaults that opt out of optional capabilities (search, marks, server
34//! time).
35//!
36//! # Provided types
37//!
38//! | Type                     | Purpose                                          |
39//! |--------------------------|--------------------------------------------------|
40//! | [`DataSourceError`]      | Error enum for all data source operations         |
41//! | [`DataUpdate`]           | Enum of possible updates returned by `poll()`     |
42//! | [`HistoricalDataRequest`]| Parameters for a historical data fetch            |
43//! | [`SymbolSearchResult`]   | Single result from a symbol search                |
44//! | [`PaginatedSearchResult`]| Paginated wrapper around search results           |
45//! | [`BarMark`]              | Event marker attached to a specific bar           |
46//! | [`TimescaleMark`]        | Event marker on the time axis                     |
47
48pub use crate::model::Bar;
49pub use crate::model::Timeframe;
50use std::error::Error;
51use std::fmt;
52
53/// Error types for data source operations.
54///
55/// All fallible [`DataSource`] methods return this error type.  Use
56/// [`is_recoverable`](DataSourceError::is_recoverable) to decide whether an
57/// automatic retry is appropriate.
58#[derive(Debug, Clone)]
59pub enum DataSourceError {
60    /// Failed to connect to data source
61    ConnError(String),
62    /// Failed to fetch data
63    FetchError(String),
64    /// Symbol not found
65    SymbolNotFound(String),
66    /// Invalid time range
67    InvalidTimeRange(String),
68    /// IO error (cannot be cloned, stores error message)
69    IoError(String),
70    /// Parse error
71    ParseError(String),
72    /// Request was rate limited
73    RateLimited,
74    /// Feature not supported by this data source
75    NotSupported(String),
76}
77
78impl fmt::Display for DataSourceError {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            DataSourceError::ConnError(msg) => write!(f, "Conn error: {msg}"),
82            DataSourceError::FetchError(msg) => write!(f, "Fetch error: {msg}"),
83            DataSourceError::SymbolNotFound(msg) => write!(f, "Symbol not found: {msg}"),
84            DataSourceError::InvalidTimeRange(msg) => write!(f, "Invalid time range: {msg}"),
85            DataSourceError::IoError(msg) => write!(f, "IO error: {msg}"),
86            DataSourceError::ParseError(msg) => write!(f, "Parse error: {msg}"),
87            DataSourceError::RateLimited => write!(f, "Rate limited"),
88            DataSourceError::NotSupported(msg) => write!(f, "Not supported: {msg}"),
89        }
90    }
91}
92
93impl Error for DataSourceError {}
94
95impl From<std::io::Error> for DataSourceError {
96    fn from(err: std::io::Error) -> Self {
97        DataSourceError::IoError(err.to_string())
98    }
99}
100
101impl DataSourceError {
102    /// Check if this error is recoverable (can retry)
103    pub fn is_recoverable(&self) -> bool {
104        matches!(
105            self,
106            DataSourceError::ConnError(_)
107                | DataSourceError::FetchError(_)
108                | DataSourceError::IoError(_)
109        )
110    }
111
112    /// Check if this error is a conn-related error
113    pub fn is_conn_error(&self) -> bool {
114        matches!(self, DataSourceError::ConnError(_))
115    }
116
117    /// Get the error message
118    pub fn message(&self) -> &str {
119        match self {
120            DataSourceError::ConnError(msg)
121            | DataSourceError::FetchError(msg)
122            | DataSourceError::SymbolNotFound(msg)
123            | DataSourceError::InvalidTimeRange(msg)
124            | DataSourceError::IoError(msg)
125            | DataSourceError::ParseError(msg)
126            | DataSourceError::NotSupported(msg) => msg,
127            DataSourceError::RateLimited => "Rate limited",
128        }
129    }
130}
131
132/// An incremental data update returned by [`DataSource::poll`].
133///
134/// The chart engine pattern-matches on these variants to decide how to merge
135/// incoming data into its internal bar buffer.
136#[derive(Debug, Clone)]
137pub enum DataUpdate {
138    /// New bars appended to the right edge (live / streaming data).
139    NewBars {
140        /// Symbol the bars belong to.
141        symbol: String,
142        /// One or more new bars, ordered oldest-first.
143        bars: Vec<Bar>,
144    },
145
146    /// Historical bars prepended to the left edge (lazy-loaded older data).
147    HistoricalBars {
148        /// Symbol the bars belong to.
149        symbol: String,
150        /// Older bars, ordered oldest-first.
151        bars: Vec<Bar>,
152    },
153
154    /// Full dataset replacement (initial load or symbol/timeframe change).
155    FullDataset {
156        /// Symbol the dataset belongs to.
157        symbol: String,
158        /// Complete bar series, ordered oldest-first.
159        bars: Vec<Bar>,
160    },
161
162    /// The set of available symbols has changed.
163    SymbolsChanged(Vec<String>),
164
165    /// Connection status changed (`true` = connected, `false` = disconnected).
166    ConnStatus(bool),
167}
168
169/// Parameters for a historical data fetch via [`DataSource::fetch_historical`].
170///
171/// The chart engine creates this request when the user scrolls left beyond the
172/// currently loaded data range and the data source supports historical fetching.
173#[derive(Debug, Clone)]
174pub struct HistoricalDataRequest {
175    /// Symbol to fetch data for.
176    pub symbol: String,
177    /// Timeframe (bar interval) to fetch.
178    pub timeframe: Timeframe,
179    /// End timestamp in Unix milliseconds (exclusive) -- fetch bars *before* this point.
180    pub end_ts_millis: i64,
181    /// Maximum number of bars to return.
182    pub limit: usize,
183}
184
185/// Symbol search result
186#[derive(Debug, Clone)]
187pub struct SymbolSearchResult {
188    /// Symbol name (e.g., "AAPL")
189    pub symbol: String,
190    /// Full symbol name with exchange (e.g., "NASDAQ:AAPL")
191    pub full_name: String,
192    /// Symbol description
193    pub description: String,
194    /// Exchange name
195    pub exchange: String,
196    /// Symbol type (stock, crypto, forex, etc.)
197    pub symbol_type: String,
198}
199
200/// Bar mark (event on a bar)
201#[derive(Debug, Clone)]
202pub struct BarMark {
203    /// Bar timestamp (Unix milliseconds)
204    pub time: i64,
205    /// Mark color
206    pub color: String,
207    /// Mark label text
208    pub label: String,
209    /// Mark tooltip text
210    pub tooltip: String,
211}
212
213/// Timescale mark (event on the timescale)
214#[derive(Debug, Clone)]
215pub struct TimescaleMark {
216    /// Mark timestamp (Unix milliseconds)
217    pub time: i64,
218    /// Mark color
219    pub color: String,
220    /// Mark label
221    pub label: String,
222    /// Mark tooltip
223    pub tooltip: String,
224}
225
226/// Paginated symbol search result
227#[derive(Debug, Clone)]
228pub struct PaginatedSearchResult {
229    /// Search results for this page
230    pub results: Vec<SymbolSearchResult>,
231    /// Total number of matching results
232    pub total: usize,
233    /// Current offset (number of results skipped)
234    pub offset: usize,
235    /// Whether there are more results available
236    pub has_more: bool,
237}
238
239/// Data source trait -- the primary integration point for feeding market data
240/// into the chart engine.
241///
242/// Implementors supply OHLCV bars from any source (WebSocket, REST, CSV, etc.).
243/// The chart engine calls methods on this trait to subscribe to symbols, poll for
244/// live updates, and request historical data when the user scrolls back in time.
245///
246/// # Required methods
247///
248/// | Method              | Purpose                                         |
249/// |---------------------|-------------------------------------------------|
250/// | `symbols`           | List available instrument identifiers            |
251/// | `subscribe`         | Begin receiving live data for a symbol           |
252/// | `unsubscribe`       | Stop receiving live data for a symbol            |
253/// | `poll`              | Non-blocking check for new data updates          |
254/// | `fetch_historical`  | Load older bars when the user scrolls left       |
255/// | `get_timeframe`     | Timeframe a symbol was subscribed with           |
256///
257/// # Optional capabilities
258///
259/// Override the `supports_*` / `get_*` method pairs to enable:
260/// - **Symbol search** -- typeahead search across instruments
261/// - **Bar marks** -- event annotations on individual bars
262/// - **Timescale marks** -- event annotations on the time axis
263/// - **Server time** -- accurate exchange clock for countdown widget
264///
265/// # Thread safety
266///
267/// `DataSource` requires `Send` so it can be owned by the chart state which may
268/// be passed across threads.  If your implementation wraps a connection handle
269/// that is not `Send`, consider using a channel-based architecture.
270pub trait DataSource: Send {
271    /// Returns the list of symbol identifiers available from this data source.
272    fn symbols(&self) -> Vec<String>;
273
274    /// Subscribe to live updates for `symbol` at the given `timeframe`.
275    ///
276    /// After subscribing, subsequent calls to [`poll`](DataSource::poll) should
277    /// return [`DataUpdate::NewBars`] or [`DataUpdate::FullDataset`] for this
278    /// symbol.
279    fn subscribe(&mut self, symbol: String, timeframe: Timeframe) -> Result<(), DataSourceError>;
280
281    /// Unsubscribe from live updates for `symbol`.
282    fn unsubscribe(&mut self, symbol: String) -> Result<(), DataSourceError>;
283
284    /// Non-blocking poll for data updates since the last call.
285    ///
286    /// The chart engine calls this on every frame.  Return an empty `Vec` when
287    /// there is nothing new.
288    fn poll(&mut self) -> Vec<DataUpdate>;
289
290    /// Fetch historical bars older than the data already loaded.
291    ///
292    /// Called when the user scrolls left beyond the currently loaded range.
293    /// The implementation may block or use internal async machinery -- the chart
294    /// engine will not call this from the UI thread if it would block.
295    fn fetch_historical(
296        &mut self,
297        request: HistoricalDataRequest,
298    ) -> Result<Vec<Bar>, DataSourceError>;
299
300    /// Whether this data source supports [`fetch_historical`](DataSource::fetch_historical).
301    ///
302    /// Return `false` (default) if the data source only provides a fixed dataset.
303    fn supports_historical(&self) -> bool {
304        false
305    }
306
307    /// Whether the data source is currently connected and able to deliver data.
308    ///
309    /// Defaults to `true`. Override to reflect real connection state for
310    /// WebSocket / network-based sources.
311    fn is_connected(&self) -> bool {
312        true
313    }
314
315    /// Returns the timeframe the given symbol was subscribed with, or `None` if
316    /// the symbol is not currently subscribed.
317    fn get_timeframe(&self, symbol: &str) -> Option<Timeframe>;
318
319    /// Search for symbols matching `_user_input`.
320    ///
321    /// Override together with [`supports_symbol_search`](DataSource::supports_symbol_search)
322    /// to enable the symbol-search dialog in the UI.
323    fn search_symbols(
324        &self,
325        _user_input: &str,
326        _exchange: &str,
327        _symbol_type: &str,
328        _max_records: usize,
329    ) -> Result<Vec<SymbolSearchResult>, DataSourceError> {
330        Ok(Vec::new())
331    }
332
333    /// Whether this data source supports the symbol search API.
334    fn supports_symbol_search(&self) -> bool {
335        false
336    }
337
338    /// Returns event marks attached to bars in the time range `[_from, _to]`
339    /// (Unix milliseconds).
340    fn get_marks(
341        &self,
342        _symbol: &str,
343        _from: i64,
344        _to: i64,
345    ) -> Result<Vec<BarMark>, DataSourceError> {
346        Ok(Vec::new())
347    }
348
349    /// Whether this data source provides bar marks.
350    fn supports_marks(&self) -> bool {
351        false
352    }
353
354    /// Returns event marks on the timescale in the range `[_from, _to]`
355    /// (Unix milliseconds).
356    fn get_timescale_marks(
357        &self,
358        _symbol: &str,
359        _from: i64,
360        _to: i64,
361    ) -> Result<Vec<TimescaleMark>, DataSourceError> {
362        Ok(Vec::new())
363    }
364
365    /// Whether this data source provides timescale marks.
366    fn supports_timescale_marks(&self) -> bool {
367        false
368    }
369
370    /// Returns the exchange server time as a Unix timestamp (seconds).
371    ///
372    /// Used by the countdown-to-next-bar widget.  Defaults to the local
373    /// system clock.
374    fn get_server_time(&self) -> Result<i64, DataSourceError> {
375        Ok(chrono::Utc::now().timestamp())
376    }
377
378    /// Whether this data source provides authoritative server time.
379    fn supports_server_time(&self) -> bool {
380        false
381    }
382}