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}