Skip to main content

egui_charts/data/
market.rs

1//! What a feed answers for the market panels beside the chart: news, highlights, the event
2//! calendars, the screener and the hotlists.
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7/// The stories a news or highlights panel asks [`Datafeed::news`](super::Datafeed::news) for.
8#[derive(Clone, Debug, PartialEq, Eq)]
9#[non_exhaustive]
10pub struct NewsQuery {
11    /// Stories about any of these symbols, or about the whole market when empty.
12    pub symbols: Vec<String>,
13    /// Only stories of these topics, in the values the feed supports, or of every topic when
14    /// empty.
15    pub topics: Vec<String>,
16    /// Only stories from these providers, in the identifiers the feed uses, or from every
17    /// provider when empty.
18    pub providers: Vec<String>,
19    /// Short summaries of what moves the market or the symbol, instead of headlines.
20    pub highlights: bool,
21    /// Stories wanted at most.
22    pub limit: usize,
23}
24
25/// One story, already formatted for display.
26#[derive(Clone, Debug, PartialEq)]
27#[non_exhaustive]
28pub struct NewsStory {
29    /// Identifies the story among the stories of one answer.
30    pub id: String,
31    /// When the story was published; the panel offers to show the chart at this time.
32    pub time: DateTime<Utc>,
33    /// Headline displayed in the news list.
34    pub headline: String,
35    /// The publisher or provider, shown beside the time. May be empty.
36    pub source: String,
37    /// The story's text, shown when it is opened, and under the headline of a highlight. May be
38    /// empty.
39    pub body: String,
40    /// The symbols the story is about.
41    pub symbols: Vec<String>,
42    /// A link to the full story, which the host opens.
43    pub url: Option<String>,
44}
45
46impl NewsStory {
47    /// A story with a headline and nothing else: set the other fields as known.
48    pub fn new(id: impl Into<String>, time: DateTime<Utc>, headline: impl Into<String>) -> Self {
49        Self {
50            id: id.into(),
51            time,
52            headline: headline.into(),
53            source: String::new(),
54            body: String::new(),
55            symbols: Vec::new(),
56            url: None,
57        }
58    }
59}
60
61/// The calendar an event belongs to.
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
63#[non_exhaustive]
64pub enum CalendarKind {
65    /// Economic releases and central bank decisions.
66    Economic,
67    /// Company earnings releases.
68    Earnings,
69    /// Dividend events.
70    Dividends,
71    /// Initial public offerings.
72    Ipos,
73}
74
75/// How much an economic event is expected to move the market.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub enum Importance {
78    /// Low importance.
79    Low,
80    /// Medium importance.
81    Medium,
82    /// High importance.
83    High,
84}
85
86/// The events a calendar panel asks [`Datafeed::calendar`](super::Datafeed::calendar) for. The
87/// feed applies the filters.
88#[derive(Clone, Debug, PartialEq, Eq)]
89#[non_exhaustive]
90pub struct CalendarQuery {
91    /// Calendar category, such as earnings or economic releases.
92    pub kind: CalendarKind,
93    /// Events at or after this time.
94    pub from: DateTime<Utc>,
95    /// Events before this time.
96    pub to: DateTime<Utc>,
97    /// Only events of these countries, in the codes the feed uses, or of every country when empty.
98    pub countries: Vec<String>,
99    /// Only events at least this important, when set. Set for economic events only.
100    pub importance: Option<Importance>,
101}
102
103/// One calendar event, its figures already formatted for display.
104///
105/// `actual`, `forecast` and `prior` are the reported figure, the consensus and the previous
106/// figure: the release for an economic event, the earnings per share for earnings, the amount
107/// per share for a dividend and the offer price for an IPO. Leave unknown figures `None`.
108#[derive(Clone, Debug, PartialEq)]
109#[non_exhaustive]
110pub struct CalendarEvent {
111    /// Identifies the event among the events of one answer.
112    pub id: String,
113    /// Calendar category, such as earnings or economic releases.
114    pub kind: CalendarKind,
115    /// When the figure is released, the earnings reported, the dividend goes ex or the shares
116    /// list.
117    pub time: DateTime<Utc>,
118    /// The country, in the codes the feed uses, such as `"US"`.
119    pub country: String,
120    /// The release, such as `"CPI (YoY)"`, or the company's name.
121    pub title: String,
122    /// The company's symbol, for earnings, dividends and IPOs; the panel offers to open it.
123    pub symbol: Option<String>,
124    /// Importance assigned by the host, when applicable.
125    pub importance: Option<Importance>,
126    /// Published figure, formatted by the host, when available.
127    pub actual: Option<String>,
128    /// Expected figure, formatted by the host, when available.
129    pub forecast: Option<String>,
130    /// Previous figure, formatted by the host, when available.
131    pub prior: Option<String>,
132}
133
134impl CalendarEvent {
135    /// An event with a title and nothing else: set the other fields as known.
136    pub fn new(
137        id: impl Into<String>,
138        kind: CalendarKind,
139        time: DateTime<Utc>,
140        title: impl Into<String>,
141    ) -> Self {
142        Self {
143            id: id.into(),
144            kind,
145            time,
146            country: String::new(),
147            title: title.into(),
148            symbol: None,
149            importance: None,
150            actual: None,
151            forecast: None,
152            prior: None,
153        }
154    }
155}
156
157/// A column the screener shows, filters and sorts by.
158#[derive(Clone, Debug, PartialEq, Eq)]
159#[non_exhaustive]
160pub struct ScreenerColumn {
161    /// Identifies the column to the feed, such as `"change_percent"`.
162    pub id: String,
163    /// The heading, such as `"Chg %"`.
164    pub title: String,
165}
166
167impl ScreenerColumn {
168    /// The column `id` headed `title`.
169    pub fn new(id: impl Into<String>, title: impl Into<String>) -> Self {
170        Self {
171            id: id.into(),
172            title: title.into(),
173        }
174    }
175}
176
177/// A named set of columns the user switches between, such as `"Overview"` or `"Performance"`.
178#[derive(Clone, Debug, PartialEq, Eq)]
179#[non_exhaustive]
180pub struct ScreenerColumnSet {
181    /// Display name.
182    pub name: String,
183    /// Columns offered by this set, in display order.
184    pub columns: Vec<ScreenerColumn>,
185}
186
187impl ScreenerColumnSet {
188    /// The set `name` of `columns`, in display order.
189    pub fn new(name: impl Into<String>, columns: Vec<ScreenerColumn>) -> Self {
190        Self {
191            name: name.into(),
192            columns,
193        }
194    }
195}
196
197/// A market the screener screens, such as stocks, funds, bonds, coins or the pairs of one
198/// exchange, as the host names it.
199#[derive(Clone, Debug, PartialEq, Eq)]
200#[non_exhaustive]
201pub struct ScreenerUniverse {
202    /// Identifies the universe to the feed, such as `"coin"`.
203    pub id: String,
204    /// The name shown, such as `"Coins"`.
205    pub name: String,
206}
207
208impl ScreenerUniverse {
209    /// The universe `id` named `name`.
210    pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
211        Self {
212            id: id.into(),
213            name: name.into(),
214        }
215    }
216}
217
218/// How a screener filter compares a column's value.
219#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
220pub enum ScreenerOp {
221    /// Greater than the filter's value.
222    Above,
223    /// Less than the filter's value.
224    Below,
225}
226
227/// One condition every row must meet.
228#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
229pub struct ScreenerFilter {
230    /// The [`ScreenerColumn::id`] compared.
231    pub column: String,
232    /// Comparison applied to the selected column.
233    pub op: ScreenerOp,
234    /// Comparison threshold.
235    pub value: f64,
236}
237
238/// The rows a screener asks [`Datafeed::screen`](super::Datafeed::screen) for. The feed
239/// filters, sorts and limits.
240#[derive(Clone, Debug, PartialEq)]
241#[non_exhaustive]
242pub struct ScreenerQuery {
243    /// The [`ScreenerUniverse::id`] to screen, or `None` when the host offers no universes.
244    pub universe: Option<String>,
245    /// What the user wrote to describe the screen, or empty for none. The feed interprets it
246    /// together with the other criteria, answering
247    /// [`Datafeed::screen_text`](super::Datafeed::screen_text).
248    pub text: String,
249    /// The [`ScreenerColumn::id`]s each row answers a value for, in order.
250    pub columns: Vec<String>,
251    /// Conditions every row meets.
252    pub filters: Vec<ScreenerFilter>,
253    /// The column to sort by, or the feed's own order when `None`.
254    pub sort_by: Option<String>,
255    /// Sort from the largest value.
256    pub descending: bool,
257    /// Rows to skip, for the next page of the same screen.
258    pub offset: usize,
259    /// Rows wanted at most.
260    pub limit: usize,
261}
262
263/// What a feed answers for a screen that has a text description.
264#[derive(Clone, Debug, PartialEq)]
265#[non_exhaustive]
266pub struct ScreenerTextAnswer {
267    /// The rows that pass the screen, as for [`Datafeed::screen`](super::Datafeed::screen).
268    pub rows: Vec<ScreenerRow>,
269    /// How the feed understood the description, such as the criteria it applied, shown above the
270    /// rows. May be empty.
271    pub explanation: String,
272}
273
274impl ScreenerTextAnswer {
275    /// The answer `rows`, understood as `explanation`.
276    pub fn new(rows: Vec<ScreenerRow>, explanation: impl Into<String>) -> Self {
277        Self {
278            rows,
279            explanation: explanation.into(),
280        }
281    }
282}
283
284/// One symbol that passes the screen, with one formatted value per query column.
285#[derive(Clone, Debug, PartialEq, Eq)]
286#[non_exhaustive]
287pub struct ScreenerRow {
288    /// Instrument identifier used by the host.
289    pub symbol: String,
290    /// The values of [`ScreenerQuery::columns`], in that order, already formatted.
291    pub values: Vec<String>,
292}
293
294impl ScreenerRow {
295    /// The row of `symbol` with `values` in the order of the query's columns.
296    pub fn new(symbol: impl Into<String>, values: Vec<String>) -> Self {
297        Self {
298            symbol: symbol.into(),
299            values,
300        }
301    }
302}
303
304/// The symbols of one hotlist that a hotlists panel asks
305/// [`Datafeed::hotlist`](super::Datafeed::hotlist) for.
306#[derive(Clone, Debug, PartialEq, Eq)]
307#[non_exhaustive]
308pub struct HotlistQuery {
309    /// The list, one of the names the host gave the panel, such as `"Top gainers"`.
310    pub list: String,
311    /// Symbols wanted at most.
312    pub limit: usize,
313}
314
315/// One symbol of a hotlist, with its last price and its change since the previous close.
316#[derive(Clone, Debug, PartialEq)]
317#[non_exhaustive]
318pub struct HotlistEntry {
319    /// Instrument identifier used by the host.
320    pub symbol: String,
321    /// Last traded price.
322    pub last: f64,
323    /// The change since the previous close in percent, such as `2.5` for 2.5 %.
324    pub change_percent: f64,
325    /// Decimals of the price, or a default from its magnitude when `None`.
326    pub price_decimals: Option<u8>,
327}
328
329impl HotlistEntry {
330    /// `symbol` last at `last`, `change_percent` from the previous close.
331    pub fn new(symbol: impl Into<String>, last: f64, change_percent: f64) -> Self {
332        Self {
333            symbol: symbol.into(),
334            last,
335            change_percent,
336            price_decimals: None,
337        }
338    }
339}