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