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(sint64, tag = "20")]
194 pub options_type: i64,
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)]
276#[allow(clippy::enum_variant_names)]
277pub enum MarketHoursTypeProto {
278 PreMarket = 0,
279 RegularMarket = 1,
280 PostMarket = 2,
281 ExtendedHoursMarket = 3,
282}
283
284impl PricingData {
285 pub(crate) fn from_base64(encoded: &str) -> Result<Self, PricingDecodeError> {
287 let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, encoded)
288 .map_err(|e| PricingDecodeError::Base64(e.to_string()))?;
289
290 Self::decode(&bytes[..]).map_err(|e| PricingDecodeError::Protobuf(e.to_string()))
291 }
292}
293
294#[derive(Clone, Debug, Default, Serialize, Deserialize)]
299#[serde(rename_all = "camelCase")]
300#[allow(missing_docs)]
301pub struct PriceUpdate {
302 pub id: String,
303 pub price: f32,
304 pub time: i64,
305 pub currency: String,
306 pub exchange: String,
307 pub quote_type: QuoteType,
308 pub market_hours: MarketHoursType,
309 pub change_percent: f32,
310 pub day_volume: i64,
311 pub day_high: f32,
312 pub day_low: f32,
313 pub change: f32,
314 pub short_name: String,
315 pub expire_date: i64,
316 pub open_price: f32,
317 pub previous_close: f32,
318 pub strike_price: f32,
319 pub underlying_symbol: String,
320 pub open_interest: i64,
321 pub options_type: OptionType,
322 pub mini_option: i64,
323 pub last_size: i64,
324 pub bid: f32,
325 pub bid_size: i64,
326 pub ask: f32,
327 pub ask_size: i64,
328 pub price_hint: i64,
329 pub vol_24hr: i64,
330 pub vol_all_currencies: i64,
331 pub from_currency: String,
332 pub last_market: String,
333 pub circulating_supply: f64,
334 pub market_cap: f64,
335}
336
337impl From<PricingData> for PriceUpdate {
338 fn from(data: PricingData) -> Self {
339 Self {
340 id: data.id,
341 price: data.price,
342 time: data.time,
343 currency: data.currency,
344 exchange: data.exchange,
345 quote_type: QuoteType::from(data.quote_type),
346 market_hours: MarketHoursType::from(data.market_hours),
347 change_percent: data.change_percent,
348 day_volume: data.day_volume,
349 day_high: data.day_high,
350 day_low: data.day_low,
351 change: data.change,
352 short_name: data.short_name,
353 expire_date: data.expire_date,
354 open_price: data.open_price,
355 previous_close: data.previous_close,
356 strike_price: data.strike_price,
357 underlying_symbol: data.underlying_symbol,
358 open_interest: data.open_interest,
359 options_type: OptionType::from(data.options_type as i32),
360 mini_option: data.mini_option,
361 last_size: data.last_size,
362 bid: data.bid,
363 bid_size: data.bid_size,
364 ask: data.ask,
365 ask_size: data.ask_size,
366 price_hint: data.price_hint,
367 vol_24hr: data.vol_24hr,
368 vol_all_currencies: data.vol_all_currencies,
369 from_currency: data.from_currency,
370 last_market: data.last_market,
371 circulating_supply: data.circulating_supply,
372 market_cap: data.market_cap,
373 }
374 }
375}
376
377#[derive(Debug, Clone)]
379pub(crate) enum PricingDecodeError {
380 Base64(String),
382 Protobuf(String),
384}
385
386impl std::fmt::Display for PricingDecodeError {
387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388 match self {
389 PricingDecodeError::Base64(e) => write!(f, "Base64 decode error: {}", e),
390 PricingDecodeError::Protobuf(e) => write!(f, "Protobuf decode error: {}", e),
391 }
392 }
393}
394
395impl std::error::Error for PricingDecodeError {}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_quote_type_from_i32() {
403 assert_eq!(QuoteType::from(8), QuoteType::Equity);
404 assert_eq!(QuoteType::from(41), QuoteType::Cryptocurrency);
405 assert_eq!(QuoteType::from(20), QuoteType::Etf);
406 assert_eq!(QuoteType::from(999), QuoteType::None);
407 }
408
409 #[test]
410 fn test_market_hours_from_i32() {
411 assert_eq!(MarketHoursType::from(0), MarketHoursType::PreMarket);
412 assert_eq!(MarketHoursType::from(1), MarketHoursType::RegularMarket);
413 assert_eq!(MarketHoursType::from(2), MarketHoursType::PostMarket);
414 }
415}