1use crate::presentation::serialization::string_as_float_opt;
2use lightstreamer_rs::subscription::ItemUpdate;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
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 item_name: String,
36 item_pos: i32,
38 fields: PriceFields,
40 changed_fields: PriceFields,
42 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 PriceData {
263 pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
273 let item_name = item_update.item_name.clone().unwrap_or_default();
275
276 let item_pos = item_update.item_pos as i32;
278
279 let is_snapshot = item_update.is_snapshot;
281
282 let fields = Self::create_price_fields(&item_update.fields)?;
284
285 let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
287 for (key, value) in &item_update.changed_fields {
288 changed_fields_map.insert(key.clone(), Some(value.clone()));
289 }
290 let changed_fields = Self::create_price_fields(&changed_fields_map)?;
291
292 Ok(PriceData {
293 item_name,
294 item_pos,
295 fields,
296 changed_fields,
297 is_snapshot,
298 })
299 }
300
301 fn create_price_fields(
303 fields_map: &HashMap<String, Option<String>>,
304 ) -> Result<PriceFields, String> {
305 let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
307
308 let parse_float = |key: &str| -> Result<Option<f64>, String> {
310 match get_field(key) {
311 Some(val) if !val.is_empty() => val
312 .parse::<f64>()
313 .map(Some)
314 .map_err(|_| format!("Failed to parse {} as float: {}", key, val)),
315 _ => Ok(None),
316 }
317 };
318
319 let dealing_flag = match get_field("DLG_FLAG").as_deref() {
321 Some("CLOSED") => Some(DealingFlag::Closed),
322 Some("CALL") => Some(DealingFlag::Call),
323 Some("DEAL") => Some(DealingFlag::Deal),
324 Some("EDIT") => Some(DealingFlag::Edit),
325 Some("CLOSINGONLY") => Some(DealingFlag::ClosingOnly),
326 Some("DEALNOEDIT") => Some(DealingFlag::DealNoEdit),
327 Some("AUCTION") => Some(DealingFlag::Auction),
328 Some("AUCTIONNOEDIT") => Some(DealingFlag::AuctionNoEdit),
329 Some("SUSPEND") => Some(DealingFlag::Suspend),
330 Some(unknown) => return Err(format!("Unknown dealing flag: {}", unknown)),
331 None => None,
332 };
333
334 Ok(PriceFields {
335 mid_open: parse_float("MID_OPEN")?,
336 high: parse_float("HIGH")?,
337 low: parse_float("LOW")?,
338 bid_quote_id: get_field("BIDQUOTEID"),
339 ask_quote_id: get_field("ASKQUOTEID"),
340
341 bid_price1: parse_float("BIDPRICE1")?,
343 bid_price2: parse_float("BIDPRICE2")?,
344 bid_price3: parse_float("BIDPRICE3")?,
345 bid_price4: parse_float("BIDPRICE4")?,
346 bid_price5: parse_float("BIDPRICE5")?,
347
348 ask_price1: parse_float("ASKPRICE1")?,
350 ask_price2: parse_float("ASKPRICE2")?,
351 ask_price3: parse_float("ASKPRICE3")?,
352 ask_price4: parse_float("ASKPRICE4")?,
353 ask_price5: parse_float("ASKPRICE5")?,
354
355 bid_size1: parse_float("BIDSIZE1")?,
357 bid_size2: parse_float("BIDSIZE2")?,
358 bid_size3: parse_float("BIDSIZE3")?,
359 bid_size4: parse_float("BIDSIZE4")?,
360 bid_size5: parse_float("BIDSIZE5")?,
361
362 ask_size1: parse_float("ASKSIZE1")?,
364 ask_size2: parse_float("ASKSIZE2")?,
365 ask_size3: parse_float("ASKSIZE3")?,
366 ask_size4: parse_float("ASKSIZE4")?,
367 ask_size5: parse_float("ASKSIZE5")?,
368
369 currency0: get_field("CURRENCY0"),
371 currency1: get_field("CURRENCY1"),
372 currency2: get_field("CURRENCY2"),
373 currency3: get_field("CURRENCY3"),
374 currency4: get_field("CURRENCY4"),
375 currency5: get_field("CURRENCY5"),
376
377 c1_bid_size: parse_float("C1BIDSIZE1-5")?,
379 c2_bid_size: parse_float("C2BIDSIZE1-5")?,
380 c3_bid_size: parse_float("C3BIDSIZE1-5")?,
381 c4_bid_size: parse_float("C4BIDSIZE1-5")?,
382 c5_bid_size: parse_float("C5BIDSIZE1-5")?,
383
384 c1_ask_size: parse_float("C1ASKSIZE1-5")?,
386 c2_ask_size: parse_float("C2ASKSIZE1-5")?,
387 c3_ask_size: parse_float("C3ASKSIZE1-5")?,
388 c4_ask_size: parse_float("C4ASKSIZE1-5")?,
389 c5_ask_size: parse_float("C5ASKSIZE1-5")?,
390
391 timestamp: parse_float("TIMESTAMP")?,
392 dealing_flag,
393 })
394 }
395}
396
397impl fmt::Display for PriceData {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 let json = serde_json::to_string(self).map_err(|_| fmt::Error)?;
400 write!(f, "{}", json)
401 }
402}
403
404impl From<&ItemUpdate> for PriceData {
405 fn from(item_update: &ItemUpdate) -> Self {
406 Self::from_item_update(item_update).unwrap_or_default()
407 }
408}