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