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}