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