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}