1use super::Exchange;
4use crate::{
5 DomainError,
6 identifiers::{Figi, Isin, Symbol},
7};
8use serde::{Deserialize, Serialize};
9use std::{borrow::Cow, str::FromStr};
10
11#[cfg(feature = "dataframe")]
12use df_derive::ToDataFrame;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
16#[non_exhaustive]
17pub enum AssetKind {
18 #[default]
20 Equity,
21 Crypto,
23 Fund,
25 Index,
27 Forex,
29 Bond,
31 Commodity,
33 Option,
35 Future,
37 REIT,
39 Warrant,
41 Convertible,
43 NFT,
45 PerpetualFuture,
47 LeveragedToken,
49 LPToken,
51 LST,
53 RWA,
55}
56
57crate::string_enum_closed_with_code!(
58 AssetKind,
59 "AssetKind",
60 {
61 "EQUITY" => AssetKind::Equity,
62 "CRYPTO" => AssetKind::Crypto,
63 "FUND" => AssetKind::Fund,
64 "INDEX" => AssetKind::Index,
65 "FOREX" => AssetKind::Forex,
66 "BOND" => AssetKind::Bond,
67 "COMMODITY" => AssetKind::Commodity,
68 "OPTION" => AssetKind::Option,
69 "FUTURE" => AssetKind::Future,
70 "REIT" => AssetKind::REIT,
71 "WARRANT" => AssetKind::Warrant,
72 "CONVERTIBLE" => AssetKind::Convertible,
73 "NFT" => AssetKind::NFT,
74 "PERPETUAL_FUTURE" => AssetKind::PerpetualFuture,
75 "LEVERAGED_TOKEN" => AssetKind::LeveragedToken,
76 "LP_TOKEN" => AssetKind::LPToken,
77 "LST" => AssetKind::LST,
78 "RWA" => AssetKind::RWA
79 },
80 {
81 "STOCK" => AssetKind::Equity,
82 "FX" => AssetKind::Forex,
83 }
84);
85
86crate::impl_display_via_code!(AssetKind);
87
88impl AssetKind {
89 #[must_use]
91 pub const fn full_name(&self) -> &'static str {
92 match self {
93 Self::Equity => "Equity",
94 Self::Crypto => "Crypto",
95 Self::Fund => "Fund",
96 Self::Index => "Index",
97 Self::Forex => "Forex",
98 Self::Bond => "Bond",
99 Self::Commodity => "Commodity",
100 Self::Option => "Option",
101 Self::Future => "Future",
102 Self::REIT => "REIT",
103 Self::Warrant => "Warrant",
104 Self::Convertible => "Convertible",
105 Self::NFT => "NFT",
106 Self::PerpetualFuture => "Perpetual Future",
107 Self::LeveragedToken => "Leveraged Token",
108 Self::LPToken => "LP Token",
109 Self::LST => "Liquid Staking Token",
110 Self::RWA => "Real-World Asset",
111 }
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
126#[cfg_attr(feature = "dataframe", derive(ToDataFrame))]
127pub struct Instrument {
128 #[cfg_attr(feature = "dataframe", df_derive(as_string))]
129 figi: Option<Figi>,
130 #[cfg_attr(feature = "dataframe", df_derive(as_string))]
131 isin: Option<Isin>,
132 #[cfg_attr(feature = "dataframe", df_derive(as_string))]
133 symbol: Symbol,
134 #[cfg_attr(feature = "dataframe", df_derive(as_string))]
135 exchange: Option<Exchange>,
136 #[cfg_attr(feature = "dataframe", df_derive(as_string))]
137 kind: AssetKind,
138}
139
140impl Instrument {
141 #[cfg_attr(
145 feature = "tracing",
146 tracing::instrument(level = "debug", skip(self), err)
147 )]
148 pub fn try_set_isin(&mut self, isin: &str) -> Result<(), DomainError> {
149 self.isin = Some(Isin::new(isin)?);
150 Ok(())
151 }
152
153 #[cfg_attr(
159 feature = "tracing",
160 tracing::instrument(level = "debug", skip(self), err)
161 )]
162 pub fn try_set_figi(&mut self, figi: &str) -> Result<(), DomainError> {
163 self.figi = Some(Figi::new(figi)?);
164 Ok(())
165 }
166
167 #[cfg_attr(
172 feature = "tracing",
173 tracing::instrument(level = "debug", skip(self), err)
174 )]
175 pub fn try_with_isin(mut self, isin: &str) -> Result<Self, DomainError> {
176 self.try_set_isin(isin)?;
177 Ok(self)
178 }
179
180 #[cfg_attr(
185 feature = "tracing",
186 tracing::instrument(level = "debug", skip(self), err)
187 )]
188 pub fn try_with_figi(mut self, figi: &str) -> Result<Self, DomainError> {
189 self.try_set_figi(figi)?;
190 Ok(self)
191 }
192
193 #[cfg_attr(
198 feature = "tracing",
199 tracing::instrument(level = "debug", skip(symbol), err)
200 )]
201 pub fn try_new(
202 symbol: impl AsRef<str>,
203 kind: AssetKind,
204 figi: Option<&str>,
205 isin: Option<&str>,
206 exchange: Option<Exchange>,
207 ) -> Result<Self, DomainError> {
208 let symbol = Symbol::new(symbol.as_ref())?;
209 let mut instrument = Self {
210 figi: None,
211 isin: None,
212 symbol,
213 exchange,
214 kind,
215 };
216
217 if let Some(figi_value) = figi {
218 instrument.try_set_figi(figi_value)?;
219 }
220 if let Some(isin_value) = isin {
221 instrument.try_set_isin(isin_value)?;
222 }
223
224 Ok(instrument)
225 }
226
227 #[cfg_attr(
233 feature = "tracing",
234 tracing::instrument(level = "debug", skip(symbol), err)
235 )]
236 pub fn from_symbol(symbol: impl AsRef<str>, kind: AssetKind) -> Result<Self, DomainError> {
237 Ok(Self {
238 figi: None,
239 isin: None,
240 symbol: Symbol::new(symbol.as_ref())?,
241 exchange: None,
242 kind,
243 })
244 }
245
246 #[cfg_attr(
252 feature = "tracing",
253 tracing::instrument(level = "debug", skip(symbol), err)
254 )]
255 pub fn from_symbol_and_exchange(
256 symbol: impl AsRef<str>,
257 exchange: Exchange,
258 kind: AssetKind,
259 ) -> Result<Self, DomainError> {
260 Ok(Self {
261 figi: None,
262 isin: None,
263 symbol: Symbol::new(symbol.as_ref())?,
264 exchange: Some(exchange),
265 kind,
266 })
267 }
268
269 #[must_use]
289 pub fn unique_key(&self) -> Cow<'_, str> {
290 if let Some(figi) = &self.figi {
291 return Cow::Borrowed(figi.as_ref());
292 }
293 if let Some(isin) = &self.isin {
294 return Cow::Borrowed(isin.as_ref());
295 }
296 if let Some(exchange) = &self.exchange {
297 return Cow::Owned(format!("{}@{}", self.symbol, exchange.code()));
298 }
299 Cow::Borrowed(self.symbol.as_str())
300 }
301
302 #[must_use]
304 pub const fn is_globally_identified(&self) -> bool {
305 self.figi.is_some() || self.isin.is_some()
306 }
307
308 #[must_use]
310 pub const fn figi(&self) -> Option<&Figi> {
311 self.figi.as_ref()
312 }
313
314 #[must_use]
316 pub fn figi_str(&self) -> Option<&str> {
317 self.figi.as_ref().map(AsRef::as_ref)
318 }
319
320 #[must_use]
322 pub const fn isin(&self) -> Option<&Isin> {
323 self.isin.as_ref()
324 }
325
326 #[must_use]
328 pub fn isin_str(&self) -> Option<&str> {
329 self.isin.as_ref().map(AsRef::as_ref)
330 }
331
332 #[must_use]
334 pub const fn symbol(&self) -> &Symbol {
335 &self.symbol
336 }
337
338 #[must_use]
340 pub fn symbol_str(&self) -> &str {
341 self.symbol.as_str()
342 }
343
344 #[must_use]
346 pub const fn exchange(&self) -> Option<&Exchange> {
347 self.exchange.as_ref()
348 }
349
350 #[must_use]
352 pub const fn kind(&self) -> &AssetKind {
353 &self.kind
354 }
355
356 #[must_use]
358 pub const fn has_figi(&self) -> bool {
359 self.figi.is_some()
360 }
361
362 #[must_use]
364 pub const fn has_isin(&self) -> bool {
365 self.isin.is_some()
366 }
367
368 #[must_use]
370 pub const fn has_exchange(&self) -> bool {
371 self.exchange.is_some()
372 }
373}