openpine_vm/symbol_info.rs
1use openpine_macros::Enum;
2use serde::{Deserialize, Serialize};
3use time::OffsetDateTime;
4
5use crate::{
6 Currency, Market, UnknownMarketError,
7 symbol::{InvalidSymbolError, Symbol},
8 time::TimeZone,
9};
10
11/// The type of market the symbol belongs to.
12#[derive(Debug, Copy, Clone, PartialEq, Eq, Enum, Serialize, Deserialize)]
13#[openpine(rename_all = "lowercase")]
14pub enum SymbolType {
15 /// Stock.
16 Stock,
17 /// Fund/ETF.
18 Fund,
19 /// Depositary receipt.
20 Dr,
21 /// Right.
22 Right,
23 /// Bond.
24 Bond,
25 /// Warrant.
26 Warrant,
27 /// Structured product.
28 Structured,
29 /// Index.
30 Index,
31 /// Forex pair.
32 Forex,
33 /// Futures contract.
34 Futures,
35 /// Spread.
36 Spread,
37 /// Economic indicator.
38 Economic,
39 /// Fundamental data.
40 Fundamental,
41 /// Cryptocurrency.
42 Crypto,
43 /// Spot market instrument.
44 Spot,
45 /// Swap.
46 Swap,
47 /// Option contract.
48 Option,
49 /// Commodity.
50 Commodity,
51}
52
53/// How volume values should be interpreted.
54#[derive(Debug, Copy, Clone, PartialEq, Eq, Enum, Serialize, Deserialize)]
55#[openpine(rename_all = "lowercase")]
56pub enum VolumeType {
57 /// For base currency
58 Base,
59 /// For quote currency
60 Quote,
61 /// For the number of transactions
62 Tick,
63}
64
65/// Symbol metadata used by `syminfo.*` builtins.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SymbolInfo {
68 /// The symbol.
69 symbol: Symbol,
70 /// The market the symbol belongs to.
71 market: Market,
72 /// The description of the symbol.
73 description: Option<String>,
74 /// The type of market the symbol belongs to.
75 type_: Option<SymbolType>,
76 /// Returns the two-letter code of the country where the symbol is traded,
77 /// in the ISO 3166-1 alpha-2 format, or `None` if the exchange is not
78 /// directly tied to a specific country.
79 ///
80 /// For example, on "NASDAQ:AAPL" it will return "US", on "LSE:AAPL" it
81 /// will return "GB", and on "BITSTAMP:BTCUSD" it will return `None`.
82 country: Option<String>,
83 /// Representing a symbol's associated International Securities
84 /// Identification Number (ISIN).
85 ///
86 /// An ISIN is a 12-character alphanumeric code that uniquely identifies a
87 /// security globally.
88 ///
89 /// For example, the ISIN for Apple Inc. is "US0378331005".
90 isin: Option<String>,
91 /// Root for derivatives like futures contract.
92 ///
93 /// For example, for the futures contract "ESZ4" (E-mini S&P 500 December
94 /// 2024), the root would be "ES".
95 root: Option<String>,
96 /// Returns a whole number used to calculate the smallest increment between
97 /// a symbol's price movements (`syminfo.mintick`).
98 ///
99 /// It is the numerator in the `syminfo.mintick` formula: `syminfo.min_move
100 /// / syminfo.price_scale = syminfo.mintick`.
101 min_move: i32,
102 /// Returns a whole number used to calculate the smallest increment between
103 /// a symbol's price movements (`syminfo.mintick`).
104 ///
105 /// It is the denominator in the `syminfo.mintick` formula:
106 /// `syminfo.min_move / syminfo.price_scale = syminfo.mintick`.
107 price_scale: i32,
108 /// The chart price of a security multiplied by the point value equals the
109 /// actual price of the traded security.
110 ///
111 /// For all types of security except futures, the point value is usually
112 /// equal to 1 and can therefore be ignored. For futures, the prices shown
113 /// on the chart are either the cost of a single futures contract, in which
114 /// case the point value is 1, or the price of a single unit of the
115 /// underlying commodity, in which case the point value represents the
116 /// number of units included in a single contract.
117 point_value: f64,
118 /// Returns a string containing the code representing the currency of the
119 /// symbol's prices.
120 ///
121 /// For example, this variable returns "USD" for "NASDAQ:AAPL" and "JPY" for
122 /// "EURJPY".
123 currency: Currency,
124 /// Representing the start of the last day of the current futures contract.
125 expiration_date: Option<OffsetDateTime>,
126 /// The ticker identifier of the underlying contract
127 current_contract: Option<String>,
128 /// The base currency of the symbol.
129 ///
130 /// For example, in the pair "EURUSD", the base currency is "EUR", in the
131 /// pair "BTCUSDT", the base currency is "BTC".
132 base_currency: Option<Currency>,
133 /// Number of employees in the company (for stocks).
134 employees: Option<i64>,
135 /// Industry of the company (for stocks).
136 industry: Option<String>,
137 /// Sector of the company (for stocks).
138 sector: Option<String>,
139 /// Minimum contract size for the symbol.
140 min_contract: Option<f64>,
141 /// The number of buy recommendations from analysts covering this stock.
142 recommendations_buy: Option<i32>,
143 /// The number of strong buy recommendations from analysts covering this
144 /// stock.
145 recommendations_buy_strong: Option<i32>,
146 /// The number of hold recommendations from analysts covering this stock.
147 recommendations_hold: Option<i32>,
148 /// The number of sell recommendations from analysts covering this stock.
149 recommendations_sell: Option<i32>,
150 /// The number of strong sell recommendations from analysts covering this
151 /// stock.
152 recommendations_sell_strong: Option<i32>,
153 /// The date of the latest recommendations update.
154 recommendations_date: Option<OffsetDateTime>,
155 /// The total number of recommendations from analysts covering this stock.
156 recommendations_total: Option<i32>,
157 /// The number of shareholders the company has.
158 shareholders: Option<i64>,
159 /// The total number of shares outstanding a company has available,
160 /// excluding any of its restricted shares.
161 shares_outstanding_float: Option<f64>,
162 /// The total number of shares outstanding a company has available,
163 /// including restricted shares held by insiders, major shareholders, and
164 /// employees.
165 shares_outstanding_total: Option<f64>,
166 /// The latest average yearly price target for the symbol predicted by
167 target_price_average: Option<f64>,
168 /// The date of the latest target price update.
169 target_price_date: Option<OffsetDateTime>,
170 /// The latest total number of price target predictions for the current
171 /// symbol.
172 target_price_estimates: Option<f64>,
173 /// The last highest yearly price target for the symbol predicted by
174 /// analysts.
175 target_price_high: Option<f64>,
176 /// The last lowest yearly price target for the symbol predicted by
177 /// analysts.
178 target_price_low: Option<f64>,
179 /// The median of the last yearly price targets for the symbol predicted by
180 /// analysts.
181 target_price_median: Option<f64>,
182 /// The volume type of the current symbol.
183 volume_type: Option<VolumeType>,
184}
185
186/// Errors returned when creating a [`SymbolInfo`].
187#[derive(Debug, thiserror::Error)]
188#[non_exhaustive]
189pub enum CreateSymbolInfoError {
190 /// The input symbol string is invalid.
191 #[error(transparent)]
192 InvalidSymbol(#[from] InvalidSymbolError),
193 /// The market prefix is unknown.
194 #[error(transparent)]
195 UnknownMarket(#[from] UnknownMarketError),
196}
197
198impl SymbolInfo {
199 /// Creates a [`SymbolInfo`] by merging a symbol string with
200 /// [`PartialSymbolInfo`] overrides.
201 ///
202 /// Any `None` field in `partial` falls back to the default value derived
203 /// from the symbol's exchange prefix via the [`Market`] helpers.
204 ///
205 /// # Errors
206 ///
207 /// Returns [`CreateSymbolInfoError`] if the symbol format is invalid or
208 /// the exchange prefix is not recognised (and `partial.market` is also
209 /// `None`).
210 pub(crate) fn from_partial(
211 symbol: &str,
212 partial: crate::data_provider::PartialSymbolInfo,
213 ) -> Result<Self, CreateSymbolInfoError> {
214 let symbol: Symbol = symbol.parse()?;
215 let market = match partial.market {
216 Some(m) => m,
217 None => symbol.prefix().parse()?,
218 };
219 Ok(SymbolInfo {
220 symbol,
221 market,
222 description: partial.description,
223 type_: partial.type_.or_else(|| Some(market.default_type())),
224 country: partial
225 .country
226 .or_else(|| Some(market.default_country().to_string())),
227 isin: partial.isin,
228 root: partial.root,
229 min_move: partial
230 .min_move
231 .unwrap_or_else(|| market.default_min_move()),
232 price_scale: partial
233 .price_scale
234 .unwrap_or_else(|| market.default_price_scale()),
235 point_value: partial.point_value.unwrap_or(1.0),
236 currency: partial
237 .currency
238 .unwrap_or_else(|| market.default_currency()),
239 expiration_date: partial.expiration_date,
240 current_contract: partial.current_contract,
241 base_currency: partial.base_currency,
242 employees: partial.employees,
243 industry: partial.industry,
244 sector: partial.sector,
245 min_contract: partial.min_contract,
246 recommendations_buy: partial.recommendations_buy,
247 recommendations_buy_strong: partial.recommendations_buy_strong,
248 recommendations_hold: partial.recommendations_hold,
249 recommendations_sell: partial.recommendations_sell,
250 recommendations_sell_strong: partial.recommendations_sell_strong,
251 recommendations_date: partial.recommendations_date,
252 recommendations_total: partial.recommendations_total,
253 shareholders: partial.shareholders,
254 shares_outstanding_float: partial.shares_outstanding_float,
255 shares_outstanding_total: partial.shares_outstanding_total,
256 target_price_average: partial.target_price_average,
257 target_price_date: partial.target_price_date,
258 target_price_estimates: partial.target_price_estimates,
259 target_price_high: partial.target_price_high,
260 target_price_low: partial.target_price_low,
261 target_price_median: partial.target_price_median,
262 volume_type: partial
263 .volume_type
264 .or_else(|| Some(market.default_volume_type())),
265 })
266 }
267
268 /// Sets the description of the symbol.
269 pub fn with_description(mut self, description: impl Into<String>) -> Self {
270 self.description = Some(description.into());
271 self
272 }
273
274 /// Sets the type of the symbol.
275 pub fn with_type(mut self, type_: SymbolType) -> Self {
276 self.type_ = Some(type_);
277 self
278 }
279
280 /// Sets the country of the symbol.
281 pub fn with_country(mut self, country: impl Into<String>) -> Self {
282 self.country = Some(country.into());
283 self
284 }
285
286 /// Sets the ISIN of the symbol.
287 pub fn with_isin(mut self, isin: impl Into<String>) -> Self {
288 self.isin = Some(isin.into());
289 self
290 }
291
292 /// Sets the root of the symbol.
293 pub fn with_root(mut self, root: impl Into<String>) -> Self {
294 self.root = Some(root.into());
295 self
296 }
297
298 /// Sets the min move of the symbol.
299 pub fn with_min_move(mut self, min_move: i32) -> Self {
300 self.min_move = min_move;
301 self
302 }
303
304 /// Sets the price scale of the symbol.
305 pub fn with_price_scale(mut self, price_scale: i32) -> Self {
306 self.price_scale = price_scale;
307 self
308 }
309
310 /// Sets the point value of the symbol.
311 pub fn with_point_value(mut self, point_value: f64) -> Self {
312 self.point_value = point_value;
313 self
314 }
315
316 /// Sets the currency of the symbol.
317 pub fn with_currency(mut self, currency: Currency) -> Self {
318 self.currency = currency;
319 self
320 }
321
322 /// Sets the expiration date of the symbol.
323 pub fn with_expiration_date(mut self, expiration_date: OffsetDateTime) -> Self {
324 self.expiration_date = Some(expiration_date);
325 self
326 }
327
328 /// Sets the current contract of the symbol.
329 pub fn with_current_contract(mut self, current_contract: impl Into<String>) -> Self {
330 self.current_contract = Some(current_contract.into());
331 self
332 }
333
334 /// Sets the base currency of the symbol.
335 pub fn with_base_currency(mut self, base_currency: Currency) -> Self {
336 self.base_currency = Some(base_currency);
337 self
338 }
339
340 /// Sets the number of employees of the symbol.
341 pub fn with_employees(mut self, employees: i64) -> Self {
342 self.employees = Some(employees);
343 self
344 }
345
346 /// Sets the industry of the symbol.
347 pub fn with_industry(mut self, industry: impl Into<String>) -> Self {
348 self.industry = Some(industry.into());
349 self
350 }
351
352 /// Sets the sector of the symbol.
353 pub fn with_sector(mut self, sector: impl Into<String>) -> Self {
354 self.sector = Some(sector.into());
355 self
356 }
357
358 /// Sets the minimum contract size of the symbol.
359 pub fn with_min_contract(mut self, min_contract: f64) -> Self {
360 self.min_contract = Some(min_contract);
361 self
362 }
363
364 /// Sets the volume type of the symbol.
365 pub fn with_volume_type(mut self, volume_type: VolumeType) -> Self {
366 self.volume_type = Some(volume_type);
367 self
368 }
369
370 /// Returns the symbol.
371 pub(crate) fn symbol(&self) -> &Symbol {
372 &self.symbol
373 }
374
375 /// Returns the market the symbol belongs to.
376 pub fn market(&self) -> &Market {
377 &self.market
378 }
379
380 /// Returns the description of the symbol.
381 pub fn description(&self) -> Option<&String> {
382 self.description.as_ref()
383 }
384
385 /// Returns the type of market the symbol belongs to.
386 pub fn type_(&self) -> Option<SymbolType> {
387 self.type_
388 }
389
390 /// Returns the two-letter code of the country where the symbol is traded,
391 /// in the ISO 3166-1 alpha-2 format, or `None` if the exchange is not
392 /// directly tied to a specific country.
393 ///
394 /// For example:
395 /// - "NASDAQ:AAPL" returns "US"
396 /// - "LSE:AAPL" returns "GB"
397 /// - "BITSTAMP:BTCUSD" returns `None`
398 pub fn country(&self) -> Option<&String> {
399 self.country.as_ref()
400 }
401
402 /// Returns the International Securities Identification Number (ISIN) of the
403 /// symbol.
404 ///
405 /// An ISIN is a 12-character alphanumeric code that uniquely identifies a
406 /// security globally.
407 ///
408 /// For example:
409 /// - The ISIN for Apple Inc. is "US0378331005".
410 pub fn isin(&self) -> Option<&String> {
411 self.isin.as_ref()
412 }
413
414 /// Returns the root for derivatives like futures contracts.
415 ///
416 /// For example:
417 /// - For the futures contract "ESZ4" (E-mini S&P 500 December 2024), the
418 /// root is "ES".
419 pub fn root(&self) -> Option<&String> {
420 self.root.as_ref()
421 }
422
423 /// Returns the smallest increment between a symbol's price movements
424 /// (`syminfo.mintick`).
425 ///
426 /// This is the numerator in the `syminfo.mintick` formula:
427 /// `syminfo.min_move / syminfo.price_scale = syminfo.mintick`.
428 pub fn min_move(&self) -> i32 {
429 self.min_move
430 }
431
432 /// Returns the denominator used to calculate the smallest increment between
433 /// a symbol's price movements (`syminfo.mintick`).
434 ///
435 /// This is the denominator in the `syminfo.mintick` formula:
436 /// `syminfo.min_move / syminfo.price_scale = syminfo.mintick`.
437 pub fn price_scale(&self) -> i32 {
438 self.price_scale
439 }
440
441 /// Returns the point value of the symbol.
442 ///
443 /// The chart price of a security multiplied by the point value equals the
444 /// actual price of the traded security.
445 ///
446 /// For all types of security except futures, the point value is usually
447 /// equal to 1 and can therefore be ignored.
448 pub fn point_value(&self) -> f64 {
449 self.point_value
450 }
451
452 /// Returns the currency of the symbol's prices.
453 ///
454 /// For example:
455 /// - "NASDAQ:AAPL" returns "USD"
456 /// - "EURJPY" returns "JPY"
457 pub fn currency(&self) -> Currency {
458 self.currency
459 }
460
461 /// Returns the timezone of the exchange of the chart's main series.
462 pub fn timezone(&self) -> TimeZone {
463 self.market.timezone()
464 }
465
466 /// Returns the start of the last day of the current futures contract.
467 pub fn expiration_date(&self) -> Option<&OffsetDateTime> {
468 self.expiration_date.as_ref()
469 }
470
471 /// Returns the ticker identifier of the underlying contract.
472 pub fn current_contract(&self) -> Option<&String> {
473 self.current_contract.as_ref()
474 }
475
476 /// Returns the base currency of the symbol.
477 ///
478 /// For example:
479 /// - In the pair "EURUSD", the base currency is "EUR".
480 /// - In the pair "BTCUSDT", the base currency is "BTC".
481 pub fn base_currency(&self) -> Option<&Currency> {
482 self.base_currency.as_ref()
483 }
484
485 /// Returns the number of employees in the company (for stocks).
486 pub fn employees(&self) -> Option<i64> {
487 self.employees
488 }
489
490 /// Returns the industry of the company (for stocks).
491 pub fn industry(&self) -> Option<&String> {
492 self.industry.as_ref()
493 }
494
495 /// Returns the sector of the company (for stocks).
496 pub fn sector(&self) -> Option<&String> {
497 self.sector.as_ref()
498 }
499
500 /// Returns the minimum contract size for the symbol.
501 pub fn min_contract(&self) -> Option<f64> {
502 self.min_contract
503 }
504
505 /// Returns the volume type of the current symbol.
506 pub fn volume_type(&self) -> Option<VolumeType> {
507 self.volume_type
508 }
509
510 /// Returns the number of buy recommendations from analysts covering this
511 /// stock.
512 pub fn recommendations_buy(&self) -> Option<i32> {
513 self.recommendations_buy
514 }
515
516 /// Returns the number of strong buy recommendations from analysts covering
517 /// this stock.
518 pub fn recommendations_buy_strong(&self) -> Option<i32> {
519 self.recommendations_buy_strong
520 }
521
522 /// Returns the number of hold recommendations from analysts covering this
523 /// stock.
524 pub fn recommendations_hold(&self) -> Option<i32> {
525 self.recommendations_hold
526 }
527
528 /// Returns the number of sell recommendations from analysts covering this
529 /// stock.
530 pub fn recommendations_sell(&self) -> Option<i32> {
531 self.recommendations_sell
532 }
533
534 /// Returns the number of strong sell recommendations from analysts covering
535 /// this stock.
536 pub fn recommendations_sell_strong(&self) -> Option<i32> {
537 self.recommendations_sell_strong
538 }
539
540 /// Returns the date of the latest recommendations update.
541 pub fn recommendations_date(&self) -> Option<&OffsetDateTime> {
542 self.recommendations_date.as_ref()
543 }
544
545 /// Returns the total number of recommendations from analysts covering this
546 /// stock.
547 pub fn recommendations_total(&self) -> Option<i32> {
548 self.recommendations_total
549 }
550
551 /// Returns the number of shareholders the company has.
552 pub fn shareholders(&self) -> Option<i64> {
553 self.shareholders
554 }
555
556 /// Returns the total number of shares outstanding a company has available,
557 /// excluding any of its restricted shares.
558 pub fn shares_outstanding_float(&self) -> Option<f64> {
559 self.shares_outstanding_float
560 }
561
562 /// Returns the total number of shares outstanding a company has available,
563 /// including restricted shares held by insiders, major shareholders, and
564 /// employees.
565 pub fn shares_outstanding_total(&self) -> Option<f64> {
566 self.shares_outstanding_total
567 }
568
569 /// Returns the latest average yearly price target for the symbol predicted
570 /// by analysts.
571 pub fn target_price_average(&self) -> Option<f64> {
572 self.target_price_average
573 }
574
575 /// Returns the date of the latest target price update.
576 pub fn target_price_date(&self) -> Option<&OffsetDateTime> {
577 self.target_price_date.as_ref()
578 }
579
580 /// Returns the total number of price target predictions for the current
581 /// symbol.
582 pub fn target_price_estimates(&self) -> Option<f64> {
583 self.target_price_estimates
584 }
585
586 /// Returns the last highest yearly price target for the symbol predicted by
587 /// analysts.
588 pub fn target_price_high(&self) -> Option<f64> {
589 self.target_price_high
590 }
591
592 /// Returns the last lowest yearly price target for the symbol predicted by
593 /// analysts.
594 pub fn target_price_low(&self) -> Option<f64> {
595 self.target_price_low
596 }
597
598 /// Returns the median of the last yearly price targets for the symbol
599 /// predicted by analysts.
600 pub fn target_price_median(&self) -> Option<f64> {
601 self.target_price_median
602 }
603
604 /// Returns the min tick value for the current symbol.
605 pub fn min_tick(&self) -> f64 {
606 self.min_move as f64 / self.price_scale as f64
607 }
608}