1use prost::Message;
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
10#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
11#[allow(missing_docs)]
12#[derive(Default)]
13pub enum QuoteType {
14 #[default]
15 None,
16 AltSymbol,
17 Heartbeat,
18 Equity,
19 Index,
20 MutualFund,
21 MoneyMarket,
22 Option,
23 Currency,
24 Warrant,
25 Bond,
26 Future,
27 Etf,
28 Commodity,
29 EcnQuote,
30 Cryptocurrency,
31 Indicator,
32 Industry,
33}
34
35impl From<i32> for QuoteType {
36 fn from(value: i32) -> Self {
37 match value {
38 0 => QuoteType::None,
39 5 => QuoteType::AltSymbol,
40 7 => QuoteType::Heartbeat,
41 8 => QuoteType::Equity,
42 9 => QuoteType::Index,
43 11 => QuoteType::MutualFund,
44 12 => QuoteType::MoneyMarket,
45 13 => QuoteType::Option,
46 14 => QuoteType::Currency,
47 15 => QuoteType::Warrant,
48 17 => QuoteType::Bond,
49 18 => QuoteType::Future,
50 20 => QuoteType::Etf,
51 23 => QuoteType::Commodity,
52 28 => QuoteType::EcnQuote,
53 41 => QuoteType::Cryptocurrency,
54 42 => QuoteType::Indicator,
55 1000 => QuoteType::Industry,
56 _ => QuoteType::None,
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
63#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
64#[allow(missing_docs)]
65#[derive(Default)]
66pub enum OptionType {
67 #[default]
68 Call,
69 Put,
70}
71
72impl From<i32> for OptionType {
73 fn from(value: i32) -> Self {
74 match value {
75 0 => OptionType::Call,
76 1 => OptionType::Put,
77 _ => OptionType::Call,
78 }
79 }
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
84#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
85#[allow(missing_docs)]
86#[derive(Default)]
87pub enum MarketHoursType {
88 #[default]
89 PreMarket,
90 RegularMarket,
91 PostMarket,
92 ExtendedHoursMarket,
93}
94
95impl From<i32> for MarketHoursType {
96 fn from(value: i32) -> Self {
97 match value {
98 0 => MarketHoursType::PreMarket,
99 1 => MarketHoursType::RegularMarket,
100 2 => MarketHoursType::PostMarket,
101 3 => MarketHoursType::ExtendedHoursMarket,
102 _ => MarketHoursType::PreMarket,
103 }
104 }
105}
106
107#[derive(Clone, PartialEq, Message)]
112pub(crate) struct PricingData {
113 #[prost(string, tag = "1")]
115 pub id: String,
116
117 #[prost(float, tag = "2")]
119 pub price: f32,
120
121 #[prost(sint64, tag = "3")]
123 pub time: i64,
124
125 #[prost(string, tag = "4")]
127 pub currency: String,
128
129 #[prost(string, tag = "5")]
131 pub exchange: String,
132
133 #[prost(enumeration = "QuoteTypeProto", tag = "6")]
135 pub quote_type: i32,
136
137 #[prost(enumeration = "MarketHoursTypeProto", tag = "7")]
139 pub market_hours: i32,
140
141 #[prost(float, tag = "8")]
143 pub change_percent: f32,
144
145 #[prost(sint64, tag = "9")]
147 pub day_volume: i64,
148
149 #[prost(float, tag = "10")]
151 pub day_high: f32,
152
153 #[prost(float, tag = "11")]
155 pub day_low: f32,
156
157 #[prost(float, tag = "12")]
159 pub change: f32,
160
161 #[prost(string, tag = "13")]
163 pub short_name: String,
164
165 #[prost(sint64, tag = "14")]
167 pub expire_date: i64,
168
169 #[prost(float, tag = "15")]
171 pub open_price: f32,
172
173 #[prost(float, tag = "16")]
175 pub previous_close: f32,
176
177 #[prost(float, tag = "17")]
179 pub strike_price: f32,
180
181 #[prost(string, tag = "18")]
183 pub underlying_symbol: String,
184
185 #[prost(sint64, tag = "19")]
187 pub open_interest: i64,
188
189 #[prost(enumeration = "OptionTypeProto", tag = "20")]
191 pub options_type: i32,
192
193 #[prost(sint64, tag = "21")]
195 pub mini_option: i64,
196
197 #[prost(sint64, tag = "22")]
199 pub last_size: i64,
200
201 #[prost(float, tag = "23")]
203 pub bid: f32,
204
205 #[prost(sint64, tag = "24")]
207 pub bid_size: i64,
208
209 #[prost(float, tag = "25")]
211 pub ask: f32,
212
213 #[prost(sint64, tag = "26")]
215 pub ask_size: i64,
216
217 #[prost(sint64, tag = "27")]
219 pub price_hint: i64,
220
221 #[prost(sint64, tag = "28")]
223 pub vol_24hr: i64,
224
225 #[prost(sint64, tag = "29")]
227 pub vol_all_currencies: i64,
228
229 #[prost(string, tag = "30")]
231 pub from_currency: String,
232
233 #[prost(string, tag = "31")]
235 pub last_market: String,
236
237 #[prost(double, tag = "32")]
239 pub circulating_supply: f64,
240
241 #[prost(double, tag = "33")]
243 pub market_cap: f64,
244}
245
246#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, prost::Enumeration)]
248#[repr(i32)]
249pub enum QuoteTypeProto {
250 None = 0,
251 AltSymbol = 5,
252 Heartbeat = 7,
253 Equity = 8,
254 Index = 9,
255 MutualFund = 11,
256 MoneyMarket = 12,
257 Option = 13,
258 Currency = 14,
259 Warrant = 15,
260 Bond = 17,
261 Future = 18,
262 Etf = 20,
263 Commodity = 23,
264 EcnQuote = 28,
265 Cryptocurrency = 41,
266 Indicator = 42,
267 Industry = 1000,
268}
269
270#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, prost::Enumeration)]
272#[repr(i32)]
273pub enum OptionTypeProto {
274 Call = 0,
275 Put = 1,
276}
277
278#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, prost::Enumeration)]
280#[repr(i32)]
281#[allow(clippy::enum_variant_names)]
282pub enum MarketHoursTypeProto {
283 PreMarket = 0,
284 RegularMarket = 1,
285 PostMarket = 2,
286 ExtendedHoursMarket = 3,
287}
288
289impl PricingData {
290 pub(crate) fn from_base64(encoded: &str) -> Result<Self, PricingDecodeError> {
292 let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, encoded)
293 .map_err(|e| PricingDecodeError::Base64(e.to_string()))?;
294
295 Self::decode(&bytes[..]).map_err(|e| PricingDecodeError::Protobuf(e.to_string()))
296 }
297}
298
299#[derive(Clone, Debug, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305#[allow(missing_docs)]
306pub struct PriceUpdate {
307 pub id: String,
308 pub price: f32,
309 pub time: i64,
310 pub currency: String,
311 pub exchange: String,
312 pub quote_type: QuoteType,
313 pub market_hours: MarketHoursType,
314 pub change_percent: f32,
315 pub day_volume: i64,
316 pub day_high: f32,
317 pub day_low: f32,
318 pub change: f32,
319 pub short_name: String,
320 pub expire_date: i64,
321 pub open_price: f32,
322 pub previous_close: f32,
323 pub strike_price: f32,
324 pub underlying_symbol: String,
325 pub open_interest: i64,
326 pub options_type: OptionType,
327 pub mini_option: i64,
328 pub last_size: i64,
329 pub bid: f32,
330 pub bid_size: i64,
331 pub ask: f32,
332 pub ask_size: i64,
333 pub price_hint: i64,
334 pub vol_24hr: i64,
335 pub vol_all_currencies: i64,
336 pub from_currency: String,
337 pub last_market: String,
338 pub circulating_supply: f64,
339 pub market_cap: f64,
340}
341
342impl From<PricingData> for PriceUpdate {
343 fn from(data: PricingData) -> Self {
344 Self {
345 id: data.id,
346 price: data.price,
347 time: data.time,
348 currency: data.currency,
349 exchange: data.exchange,
350 quote_type: QuoteType::from(data.quote_type),
351 market_hours: MarketHoursType::from(data.market_hours),
352 change_percent: data.change_percent,
353 day_volume: data.day_volume,
354 day_high: data.day_high,
355 day_low: data.day_low,
356 change: data.change,
357 short_name: data.short_name,
358 expire_date: data.expire_date,
359 open_price: data.open_price,
360 previous_close: data.previous_close,
361 strike_price: data.strike_price,
362 underlying_symbol: data.underlying_symbol,
363 open_interest: data.open_interest,
364 options_type: OptionType::from(data.options_type),
365 mini_option: data.mini_option,
366 last_size: data.last_size,
367 bid: data.bid,
368 bid_size: data.bid_size,
369 ask: data.ask,
370 ask_size: data.ask_size,
371 price_hint: data.price_hint,
372 vol_24hr: data.vol_24hr,
373 vol_all_currencies: data.vol_all_currencies,
374 from_currency: data.from_currency,
375 last_market: data.last_market,
376 circulating_supply: data.circulating_supply,
377 market_cap: data.market_cap,
378 }
379 }
380}
381
382#[derive(Debug, Clone)]
384pub(crate) enum PricingDecodeError {
385 Base64(String),
387 Protobuf(String),
389}
390
391impl std::fmt::Display for PricingDecodeError {
392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393 match self {
394 PricingDecodeError::Base64(e) => write!(f, "Base64 decode error: {}", e),
395 PricingDecodeError::Protobuf(e) => write!(f, "Protobuf decode error: {}", e),
396 }
397 }
398}
399
400impl std::error::Error for PricingDecodeError {}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn test_quote_type_from_i32() {
408 assert_eq!(QuoteType::from(8), QuoteType::Equity);
409 assert_eq!(QuoteType::from(41), QuoteType::Cryptocurrency);
410 assert_eq!(QuoteType::from(20), QuoteType::Etf);
411 assert_eq!(QuoteType::from(999), QuoteType::None);
412 }
413
414 #[test]
415 fn test_market_hours_from_i32() {
416 assert_eq!(MarketHoursType::from(0), MarketHoursType::PreMarket);
417 assert_eq!(MarketHoursType::from(1), MarketHoursType::RegularMarket);
418 assert_eq!(MarketHoursType::from(2), MarketHoursType::PostMarket);
419 }
420}