1use crate::impl_json_display;
2use crate::presentation::serialization::string_as_float_opt;
3use lightstreamer_rs::subscription::ItemUpdate;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
8pub enum DealingFlag {
9 #[serde(rename = "CLOSED")]
10 #[default]
11 Closed,
12 #[serde(rename = "CALL")]
13 Call,
14 #[serde(rename = "DEAL")]
15 Deal,
16 #[serde(rename = "EDIT")]
17 Edit,
18 #[serde(rename = "CLOSINGONLY")]
19 ClosingOnly,
20 #[serde(rename = "DEALNOEDIT")]
21 DealNoEdit,
22 #[serde(rename = "AUCTION")]
23 Auction,
24 #[serde(rename = "AUCTIONNOEDIT")]
25 AuctionNoEdit,
26 #[serde(rename = "SUSPEND")]
27 Suspend,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, Default)]
33pub struct PriceData {
34 pub item_name: String,
36 pub item_pos: i32,
38 pub fields: PriceFields,
40 pub changed_fields: PriceFields,
42 pub is_snapshot: bool,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, Default)]
47pub struct PriceFields {
48 #[serde(rename = "MID_OPEN")]
49 #[serde(with = "string_as_float_opt")]
50 #[serde(default)]
51 mid_open: Option<f64>,
52
53 #[serde(rename = "HIGH")]
54 #[serde(with = "string_as_float_opt")]
55 #[serde(default)]
56 high: Option<f64>,
57
58 #[serde(rename = "LOW")]
59 #[serde(with = "string_as_float_opt")]
60 #[serde(default)]
61 low: Option<f64>,
62
63 #[serde(rename = "BIDQUOTEID")]
64 #[serde(default)]
65 bid_quote_id: Option<String>,
66
67 #[serde(rename = "ASKQUOTEID")]
68 #[serde(default)]
69 ask_quote_id: Option<String>,
70
71 #[serde(rename = "BIDPRICE1")]
73 #[serde(with = "string_as_float_opt")]
74 #[serde(default)]
75 bid_price1: Option<f64>,
76
77 #[serde(rename = "BIDPRICE2")]
78 #[serde(with = "string_as_float_opt")]
79 #[serde(default)]
80 bid_price2: Option<f64>,
81
82 #[serde(rename = "BIDPRICE3")]
83 #[serde(with = "string_as_float_opt")]
84 #[serde(default)]
85 bid_price3: Option<f64>,
86
87 #[serde(rename = "BIDPRICE4")]
88 #[serde(with = "string_as_float_opt")]
89 #[serde(default)]
90 bid_price4: Option<f64>,
91
92 #[serde(rename = "BIDPRICE5")]
93 #[serde(with = "string_as_float_opt")]
94 #[serde(default)]
95 bid_price5: Option<f64>,
96
97 #[serde(rename = "ASKPRICE1")]
99 #[serde(with = "string_as_float_opt")]
100 #[serde(default)]
101 ask_price1: Option<f64>,
102
103 #[serde(rename = "ASKPRICE2")]
104 #[serde(with = "string_as_float_opt")]
105 #[serde(default)]
106 ask_price2: Option<f64>,
107
108 #[serde(rename = "ASKPRICE3")]
109 #[serde(with = "string_as_float_opt")]
110 #[serde(default)]
111 ask_price3: Option<f64>,
112
113 #[serde(rename = "ASKPRICE4")]
114 #[serde(with = "string_as_float_opt")]
115 #[serde(default)]
116 ask_price4: Option<f64>,
117
118 #[serde(rename = "ASKPRICE5")]
119 #[serde(with = "string_as_float_opt")]
120 #[serde(default)]
121 ask_price5: Option<f64>,
122
123 #[serde(rename = "BIDSIZE1")]
125 #[serde(with = "string_as_float_opt")]
126 #[serde(default)]
127 bid_size1: Option<f64>,
128
129 #[serde(rename = "BIDSIZE2")]
130 #[serde(with = "string_as_float_opt")]
131 #[serde(default)]
132 bid_size2: Option<f64>,
133
134 #[serde(rename = "BIDSIZE3")]
135 #[serde(with = "string_as_float_opt")]
136 #[serde(default)]
137 bid_size3: Option<f64>,
138
139 #[serde(rename = "BIDSIZE4")]
140 #[serde(with = "string_as_float_opt")]
141 #[serde(default)]
142 bid_size4: Option<f64>,
143
144 #[serde(rename = "BIDSIZE5")]
145 #[serde(with = "string_as_float_opt")]
146 #[serde(default)]
147 bid_size5: Option<f64>,
148
149 #[serde(rename = "ASKSIZE1")]
151 #[serde(with = "string_as_float_opt")]
152 #[serde(default)]
153 ask_size1: Option<f64>,
154
155 #[serde(rename = "ASKSIZE2")]
156 #[serde(with = "string_as_float_opt")]
157 #[serde(default)]
158 ask_size2: Option<f64>,
159
160 #[serde(rename = "ASKSIZE3")]
161 #[serde(with = "string_as_float_opt")]
162 #[serde(default)]
163 ask_size3: Option<f64>,
164
165 #[serde(rename = "ASKSIZE4")]
166 #[serde(with = "string_as_float_opt")]
167 #[serde(default)]
168 ask_size4: Option<f64>,
169
170 #[serde(rename = "ASKSIZE5")]
171 #[serde(with = "string_as_float_opt")]
172 #[serde(default)]
173 ask_size5: Option<f64>,
174
175 #[serde(rename = "CURRENCY0")]
177 #[serde(default)]
178 currency0: Option<String>,
179
180 #[serde(rename = "CURRENCY1")]
181 #[serde(default)]
182 currency1: Option<String>,
183
184 #[serde(rename = "CURRENCY2")]
185 #[serde(default)]
186 currency2: Option<String>,
187
188 #[serde(rename = "CURRENCY3")]
189 #[serde(default)]
190 currency3: Option<String>,
191
192 #[serde(rename = "CURRENCY4")]
193 #[serde(default)]
194 currency4: Option<String>,
195
196 #[serde(rename = "CURRENCY5")]
197 #[serde(default)]
198 currency5: Option<String>,
199
200 #[serde(rename = "C1BIDSIZE1-5")]
202 #[serde(with = "string_as_float_opt")]
203 #[serde(default)]
204 c1_bid_size: Option<f64>,
205
206 #[serde(rename = "C2BIDSIZE1-5")]
207 #[serde(with = "string_as_float_opt")]
208 #[serde(default)]
209 c2_bid_size: Option<f64>,
210
211 #[serde(rename = "C3BIDSIZE1-5")]
212 #[serde(with = "string_as_float_opt")]
213 #[serde(default)]
214 c3_bid_size: Option<f64>,
215
216 #[serde(rename = "C4BIDSIZE1-5")]
217 #[serde(with = "string_as_float_opt")]
218 #[serde(default)]
219 c4_bid_size: Option<f64>,
220
221 #[serde(rename = "C5BIDSIZE1-5")]
222 #[serde(with = "string_as_float_opt")]
223 #[serde(default)]
224 c5_bid_size: Option<f64>,
225
226 #[serde(rename = "C1ASKSIZE1-5")]
228 #[serde(with = "string_as_float_opt")]
229 #[serde(default)]
230 c1_ask_size: Option<f64>,
231
232 #[serde(rename = "C2ASKSIZE1-5")]
233 #[serde(with = "string_as_float_opt")]
234 #[serde(default)]
235 c2_ask_size: Option<f64>,
236
237 #[serde(rename = "C3ASKSIZE1-5")]
238 #[serde(with = "string_as_float_opt")]
239 #[serde(default)]
240 c3_ask_size: Option<f64>,
241
242 #[serde(rename = "C4ASKSIZE1-5")]
243 #[serde(with = "string_as_float_opt")]
244 #[serde(default)]
245 c4_ask_size: Option<f64>,
246
247 #[serde(rename = "C5ASKSIZE1-5")]
248 #[serde(with = "string_as_float_opt")]
249 #[serde(default)]
250 c5_ask_size: Option<f64>,
251
252 #[serde(rename = "TIMESTAMP")]
253 #[serde(with = "string_as_float_opt")]
254 #[serde(default)]
255 timestamp: Option<f64>,
256
257 #[serde(rename = "DLG_FLAG")]
258 #[serde(default)]
259 dealing_flag: Option<DealingFlag>,
260}
261
262impl_json_display!(PriceFields);
263
264impl PriceData {
265 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
275 let item_name = item_update.item_name.clone().unwrap_or_default();
277
278 let item_pos = item_update.item_pos as i32;
280
281 let is_snapshot = item_update.is_snapshot;
283
284 let fields = Self::create_price_fields(&item_update.fields)?;
286
287 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
289 for (key, value) in &item_update.changed_fields {
290 changed_fields_map.insert(key.clone(), Some(value.clone()));
291 }
292 let changed_fields = Self::create_price_fields(&changed_fields_map)?;
293
294 Ok(PriceData {
295 item_name,
296 item_pos,
297 fields,
298 changed_fields,
299 is_snapshot,
300 })
301 }
302
303 fn create_price_fields(
305 fields_map: &HashMap<String, Option<String>>,
306 ) -> Result<PriceFields, String> {
307 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
309
310 let parse_float = |key: &str| -> Result<Option<f64>, String> {
312 match get_field(key) {
313 Some(val) if !val.is_empty() => val
314 .parse::<f64>()
315 .map(Some)
316 .map_err(|_| format!("Failed to parse {} as float: {}", key, val)),
317 _ => Ok(None),
318 }
319 };
320
321 let dealing_flag = match get_field("DLG_FLAG").as_deref() {
323 Some("CLOSED") => Some(DealingFlag::Closed),
324 Some("CALL") => Some(DealingFlag::Call),
325 Some("DEAL") => Some(DealingFlag::Deal),
326 Some("EDIT") => Some(DealingFlag::Edit),
327 Some("CLOSINGONLY") => Some(DealingFlag::ClosingOnly),
328 Some("DEALNOEDIT") => Some(DealingFlag::DealNoEdit),
329 Some("AUCTION") => Some(DealingFlag::Auction),
330 Some("AUCTIONNOEDIT") => Some(DealingFlag::AuctionNoEdit),
331 Some("SUSPEND") => Some(DealingFlag::Suspend),
332 Some(unknown) => return Err(format!("Unknown dealing flag: {}", unknown)),
333 None => None,
334 };
335
336 Ok(PriceFields {
337 mid_open: parse_float("MID_OPEN")?,
338 high: parse_float("HIGH")?,
339 low: parse_float("LOW")?,
340 bid_quote_id: get_field("BIDQUOTEID"),
341 ask_quote_id: get_field("ASKQUOTEID"),
342
343 bid_price1: parse_float("BIDPRICE1")?,
345 bid_price2: parse_float("BIDPRICE2")?,
346 bid_price3: parse_float("BIDPRICE3")?,
347 bid_price4: parse_float("BIDPRICE4")?,
348 bid_price5: parse_float("BIDPRICE5")?,
349
350 ask_price1: parse_float("ASKPRICE1")?,
352 ask_price2: parse_float("ASKPRICE2")?,
353 ask_price3: parse_float("ASKPRICE3")?,
354 ask_price4: parse_float("ASKPRICE4")?,
355 ask_price5: parse_float("ASKPRICE5")?,
356
357 bid_size1: parse_float("BIDSIZE1")?,
359 bid_size2: parse_float("BIDSIZE2")?,
360 bid_size3: parse_float("BIDSIZE3")?,
361 bid_size4: parse_float("BIDSIZE4")?,
362 bid_size5: parse_float("BIDSIZE5")?,
363
364 ask_size1: parse_float("ASKSIZE1")?,
366 ask_size2: parse_float("ASKSIZE2")?,
367 ask_size3: parse_float("ASKSIZE3")?,
368 ask_size4: parse_float("ASKSIZE4")?,
369 ask_size5: parse_float("ASKSIZE5")?,
370
371 currency0: get_field("CURRENCY0"),
373 currency1: get_field("CURRENCY1"),
374 currency2: get_field("CURRENCY2"),
375 currency3: get_field("CURRENCY3"),
376 currency4: get_field("CURRENCY4"),
377 currency5: get_field("CURRENCY5"),
378
379 c1_bid_size: parse_float("C1BIDSIZE1-5")?,
381 c2_bid_size: parse_float("C2BIDSIZE1-5")?,
382 c3_bid_size: parse_float("C3BIDSIZE1-5")?,
383 c4_bid_size: parse_float("C4BIDSIZE1-5")?,
384 c5_bid_size: parse_float("C5BIDSIZE1-5")?,
385
386 c1_ask_size: parse_float("C1ASKSIZE1-5")?,
388 c2_ask_size: parse_float("C2ASKSIZE1-5")?,
389 c3_ask_size: parse_float("C3ASKSIZE1-5")?,
390 c4_ask_size: parse_float("C4ASKSIZE1-5")?,
391 c5_ask_size: parse_float("C5ASKSIZE1-5")?,
392
393 timestamp: parse_float("TIMESTAMP")?,
394 dealing_flag,
395 })
396 }
397}
398
399impl_json_display!(PriceData);
400
401impl From<&ItemUpdate> for PriceData {
402 fn from(item_update: &ItemUpdate) -> Self {
403 Self::from_item_update(item_update).unwrap_or_default()
404 }
405}