ig_client/presentation/
price.rs

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/// Market dealing status flags indicating trading availability
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
9#[serde(rename_all = "UPPERCASE")]
10pub enum DealingFlag {
11    /// Market is closed for trading
12    #[default]
13    Closed,
14    /// Market is in call phase
15    Call,
16    /// Market is open for dealing
17    Deal,
18    /// Market is open for editing orders
19    Edit,
20    /// Market is open for closing positions only
21    ClosingOnly,
22    /// Market is open for dealing but not editing
23    DealNoEdit,
24    /// Market is in auction phase
25    Auction,
26    /// Market is in auction phase without editing
27    AuctionNoEdit,
28    /// Market trading is suspended
29    Suspend,
30}
31
32/// Structure for price data received from the IG Markets API
33/// Contains information about market prices and related data
34#[derive(DebugPretty, Clone, DisplaySimple, Serialize, Deserialize, Default)]
35pub struct PriceData {
36    /// Name of the item (usually the market ID)
37    pub item_name: String,
38    /// Position of the item in the subscription
39    pub item_pos: i32,
40    /// All price fields for this market
41    pub fields: PriceFields,
42    /// Fields that have changed in this update
43    pub changed_fields: PriceFields,
44    /// Whether this is a snapshot or an update
45    pub is_snapshot: bool,
46}
47
48/// Price field data containing bid, offer, and market status information
49#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
50pub struct PriceFields {
51    /// The opening price at the middle of the bid-ask spread
52    #[serde(rename = "MID_OPEN")]
53    #[serde(with = "string_as_float_opt")]
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub mid_open: Option<f64>,
56
57    /// The highest price reached during the trading session
58    #[serde(rename = "HIGH")]
59    #[serde(with = "string_as_float_opt")]
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub high: Option<f64>,
62
63    /// The lowest price reached during the trading session
64    #[serde(rename = "LOW")]
65    #[serde(with = "string_as_float_opt")]
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub low: Option<f64>,
68
69    /// Unique identifier for the bid quote
70    #[serde(rename = "BIDQUOTEID")]
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub bid_quote_id: Option<String>,
73
74    /// Unique identifier for the ask quote
75    #[serde(rename = "ASKQUOTEID")]
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub ask_quote_id: Option<String>,
78
79    // Bid ladder prices
80    /// First level bid price in the order book
81    #[serde(rename = "BIDPRICE1")]
82    #[serde(with = "string_as_float_opt")]
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub bid_price1: Option<f64>,
85
86    /// Second level bid price in the order book
87    #[serde(rename = "BIDPRICE2")]
88    #[serde(with = "string_as_float_opt")]
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub bid_price2: Option<f64>,
91
92    /// Third level bid price in the order book
93    #[serde(rename = "BIDPRICE3")]
94    #[serde(with = "string_as_float_opt")]
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub bid_price3: Option<f64>,
97
98    /// Fourth level bid price in the order book
99    #[serde(rename = "BIDPRICE4")]
100    #[serde(with = "string_as_float_opt")]
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub bid_price4: Option<f64>,
103
104    /// Fifth level bid price in the order book
105    #[serde(rename = "BIDPRICE5")]
106    #[serde(with = "string_as_float_opt")]
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub bid_price5: Option<f64>,
109
110    // Ask ladder prices
111    /// First level ask price in the order book
112    #[serde(rename = "ASKPRICE1")]
113    #[serde(with = "string_as_float_opt")]
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub ask_price1: Option<f64>,
116
117    /// Second level ask price in the order book
118    #[serde(rename = "ASKPRICE2")]
119    #[serde(with = "string_as_float_opt")]
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub ask_price2: Option<f64>,
122
123    /// Third level ask price in the order book
124    #[serde(rename = "ASKPRICE3")]
125    #[serde(with = "string_as_float_opt")]
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub ask_price3: Option<f64>,
128
129    /// Fourth level ask price in the order book
130    #[serde(rename = "ASKPRICE4")]
131    #[serde(with = "string_as_float_opt")]
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub ask_price4: Option<f64>,
134
135    /// Fifth level ask price in the order book
136    #[serde(rename = "ASKPRICE5")]
137    #[serde(with = "string_as_float_opt")]
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub ask_price5: Option<f64>,
140
141    // Bid sizes
142    /// Volume available at the first level bid price
143    #[serde(rename = "BIDSIZE1")]
144    #[serde(with = "string_as_float_opt")]
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub bid_size1: Option<f64>,
147
148    /// Volume available at the second level bid price
149    #[serde(rename = "BIDSIZE2")]
150    #[serde(with = "string_as_float_opt")]
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub bid_size2: Option<f64>,
153
154    /// Volume available at the third level bid price
155    #[serde(rename = "BIDSIZE3")]
156    #[serde(with = "string_as_float_opt")]
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub bid_size3: Option<f64>,
159
160    /// Volume available at the fourth level bid price
161    #[serde(rename = "BIDSIZE4")]
162    #[serde(with = "string_as_float_opt")]
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub bid_size4: Option<f64>,
165
166    /// Volume available at the fifth level bid price
167    #[serde(rename = "BIDSIZE5")]
168    #[serde(with = "string_as_float_opt")]
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub bid_size5: Option<f64>,
171
172    // Ask sizes
173    /// Volume available at the first level ask price
174    #[serde(rename = "ASKSIZE1")]
175    #[serde(with = "string_as_float_opt")]
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub ask_size1: Option<f64>,
178
179    /// Volume available at the second level ask price
180    #[serde(rename = "ASKSIZE2")]
181    #[serde(with = "string_as_float_opt")]
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub ask_size2: Option<f64>,
184
185    /// Volume available at the third level ask price
186    #[serde(rename = "ASKSIZE3")]
187    #[serde(with = "string_as_float_opt")]
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub ask_size3: Option<f64>,
190
191    /// Volume available at the fourth level ask price
192    #[serde(rename = "ASKSIZE4")]
193    #[serde(with = "string_as_float_opt")]
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub ask_size4: Option<f64>,
196
197    /// Volume available at the fifth level ask price
198    #[serde(rename = "ASKSIZE5")]
199    #[serde(with = "string_as_float_opt")]
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub ask_size5: Option<f64>,
202
203    /// Base currency code for the trading pair
204    #[serde(rename = "CURRENCY0")]
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub currency0: Option<String>,
207
208    /// First alternative currency code
209    #[serde(rename = "CURRENCY1")]
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub currency1: Option<String>,
212
213    /// Second alternative currency code
214    #[serde(rename = "CURRENCY2")]
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub currency2: Option<String>,
217
218    /// Third alternative currency code
219    #[serde(rename = "CURRENCY3")]
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub currency3: Option<String>,
222
223    /// Fourth alternative currency code
224    #[serde(rename = "CURRENCY4")]
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub currency4: Option<String>,
227
228    /// Fifth alternative currency code
229    #[serde(rename = "CURRENCY5")]
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub currency5: Option<String>,
232
233    /// Bid size for currency 1 at level 1
234    #[serde(rename = "C1BIDSIZE1")]
235    #[serde(with = "string_as_float_opt")]
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub c1_bid_size_1: Option<f64>,
238
239    /// Bid size for currency 1 at level 2
240    #[serde(rename = "C1BIDSIZE2")]
241    #[serde(with = "string_as_float_opt")]
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub c1_bid_size_2: Option<f64>,
244
245    /// Bid size for currency 1 at level 3
246    #[serde(rename = "C1BIDSIZE3")]
247    #[serde(with = "string_as_float_opt")]
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub c1_bid_size_3: Option<f64>,
250
251    /// Bid size for currency 1 at level 4
252    #[serde(rename = "C1BIDSIZE4")]
253    #[serde(with = "string_as_float_opt")]
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub c1_bid_size_4: Option<f64>,
256
257    /// Bid size for currency 1 at level 5
258    #[serde(rename = "C1BIDSIZE5")]
259    #[serde(with = "string_as_float_opt")]
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub c1_bid_size_5: Option<f64>,
262
263    /// Bid size for currency 2 at level 1
264    #[serde(rename = "C2BIDSIZE1")]
265    #[serde(with = "string_as_float_opt")]
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub c2_bid_size_1: Option<f64>,
268
269    /// Bid size for currency 2 at level 2
270    #[serde(rename = "C2BIDSIZE2")]
271    #[serde(with = "string_as_float_opt")]
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub c2_bid_size_2: Option<f64>,
274
275    /// Bid size for currency 2 at level 3
276    #[serde(rename = "C2BIDSIZE3")]
277    #[serde(with = "string_as_float_opt")]
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub c2_bid_size_3: Option<f64>,
280
281    /// Bid size for currency 2 at level 4
282    #[serde(rename = "C2BIDSIZE4")]
283    #[serde(with = "string_as_float_opt")]
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub c2_bid_size_4: Option<f64>,
286
287    /// Bid size for currency 2 at level 5
288    #[serde(rename = "C2BIDSIZE5")]
289    #[serde(with = "string_as_float_opt")]
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub c2_bid_size_5: Option<f64>,
292
293    /// Bid size for currency 3 at level 1
294    #[serde(rename = "C3BIDSIZE1")]
295    #[serde(with = "string_as_float_opt")]
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub c3_bid_size_1: Option<f64>,
298
299    /// Bid size for currency 3 at level 2
300    #[serde(rename = "C3BIDSIZE2")]
301    #[serde(with = "string_as_float_opt")]
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub c3_bid_size_2: Option<f64>,
304
305    /// Bid size for currency 3 at level 3
306    #[serde(rename = "C3BIDSIZE3")]
307    #[serde(with = "string_as_float_opt")]
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub c3_bid_size_3: Option<f64>,
310
311    /// Bid size for currency 3 at level 4
312    #[serde(rename = "C3BIDSIZE4")]
313    #[serde(with = "string_as_float_opt")]
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub c3_bid_size_4: Option<f64>,
316
317    /// Bid size for currency 3 at level 5
318    #[serde(rename = "C3BIDSIZE5")]
319    #[serde(with = "string_as_float_opt")]
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub c3_bid_size_5: Option<f64>,
322
323    /// Bid size for currency 4 at level 1
324    #[serde(rename = "C4BIDSIZE1")]
325    #[serde(with = "string_as_float_opt")]
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub c4_bid_size_1: Option<f64>,
328
329    /// Bid size for currency 4 at level 2
330    #[serde(rename = "C4BIDSIZE2")]
331    #[serde(with = "string_as_float_opt")]
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub c4_bid_size_2: Option<f64>,
334
335    /// Bid size for currency 4 at level 3
336    #[serde(rename = "C4BIDSIZE3")]
337    #[serde(with = "string_as_float_opt")]
338    #[serde(skip_serializing_if = "Option::is_none")]
339    pub c4_bid_size_3: Option<f64>,
340
341    /// Bid size for currency 4 at level 4
342    #[serde(rename = "C4BIDSIZE4")]
343    #[serde(with = "string_as_float_opt")]
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub c4_bid_size_4: Option<f64>,
346
347    /// Bid size for currency 4 at level 5
348    #[serde(rename = "C4BIDSIZE5")]
349    #[serde(with = "string_as_float_opt")]
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub c4_bid_size_5: Option<f64>,
352
353    /// Bid size for currency 5 at level 1
354    #[serde(rename = "C5BIDSIZE1")]
355    #[serde(with = "string_as_float_opt")]
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub c5_bid_size_1: Option<f64>,
358
359    /// Bid size for currency 5 at level 2
360    #[serde(rename = "C5BIDSIZE2")]
361    #[serde(with = "string_as_float_opt")]
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub c5_bid_size_2: Option<f64>,
364
365    /// Bid size for currency 5 at level 3
366    #[serde(rename = "C5BIDSIZE3")]
367    #[serde(with = "string_as_float_opt")]
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub c5_bid_size_3: Option<f64>,
370
371    /// Bid size for currency 5 at level 4
372    #[serde(rename = "C5BIDSIZE4")]
373    #[serde(with = "string_as_float_opt")]
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub c5_bid_size_4: Option<f64>,
376
377    /// Bid size for currency 5 at level 5
378    #[serde(rename = "C5BIDSIZE5")]
379    #[serde(with = "string_as_float_opt")]
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub c5_bid_size_5: Option<f64>,
382
383    // Ask sizes for different currencies
384    /// Ask size for currency 1 at level 1
385    #[serde(rename = "C1ASKSIZE1")]
386    #[serde(with = "string_as_float_opt")]
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub c1_ask_size_1: Option<f64>,
389
390    /// Ask size for currency 1 at level 2
391    #[serde(rename = "C1ASKSIZE2")]
392    #[serde(with = "string_as_float_opt")]
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub c1_ask_size_2: Option<f64>,
395
396    /// Ask size for currency 1 at level 3
397    #[serde(rename = "C1ASKSIZE3")]
398    #[serde(with = "string_as_float_opt")]
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub c1_ask_size_3: Option<f64>,
401
402    /// Ask size for currency 1 at level 4
403    #[serde(rename = "C1ASKSIZE4")]
404    #[serde(with = "string_as_float_opt")]
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub c1_ask_size_4: Option<f64>,
407
408    /// Ask size for currency 1 at level 5
409    #[serde(rename = "C1ASKSIZE5")]
410    #[serde(with = "string_as_float_opt")]
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub c1_ask_size_5: Option<f64>,
413
414    /// Ask size for currency 2 at level 1
415    #[serde(rename = "C2ASKSIZE1")]
416    #[serde(with = "string_as_float_opt")]
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub c2_ask_size_1: Option<f64>,
419
420    /// Ask size for currency 2 at level 2
421    #[serde(rename = "C2ASKSIZE2")]
422    #[serde(with = "string_as_float_opt")]
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub c2_ask_size_2: Option<f64>,
425
426    /// Ask size for currency 2 at level 3
427    #[serde(rename = "C2ASKSIZE3")]
428    #[serde(with = "string_as_float_opt")]
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub c2_ask_size_3: Option<f64>,
431
432    /// Ask size for currency 2 at level 4
433    #[serde(rename = "C2ASKSIZE4")]
434    #[serde(with = "string_as_float_opt")]
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub c2_ask_size_4: Option<f64>,
437
438    /// Ask size for currency 2 at level 5
439    #[serde(rename = "C2ASKSIZE5")]
440    #[serde(with = "string_as_float_opt")]
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub c2_ask_size_5: Option<f64>,
443
444    /// Ask size for currency 3 at level 1
445    #[serde(rename = "C3ASKSIZE1")]
446    #[serde(with = "string_as_float_opt")]
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub c3_ask_size_1: Option<f64>,
449
450    /// Ask size for currency 3 at level 2
451    #[serde(rename = "C3ASKSIZE2")]
452    #[serde(with = "string_as_float_opt")]
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub c3_ask_size_2: Option<f64>,
455
456    /// Ask size for currency 3 at level 3
457    #[serde(rename = "C3ASKSIZE3")]
458    #[serde(with = "string_as_float_opt")]
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub c3_ask_size_3: Option<f64>,
461
462    /// Ask size for currency 3 at level 4
463    #[serde(rename = "C3ASKSIZE4")]
464    #[serde(with = "string_as_float_opt")]
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub c3_ask_size_4: Option<f64>,
467
468    /// Ask size for currency 3 at level 5
469    #[serde(rename = "C3ASKSIZE5")]
470    #[serde(with = "string_as_float_opt")]
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub c3_ask_size_5: Option<f64>,
473
474    /// Ask size for currency 4 at level 1
475    #[serde(rename = "C4ASKSIZE1")]
476    #[serde(with = "string_as_float_opt")]
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub c4_ask_size_1: Option<f64>,
479
480    /// Ask size for currency 4 at level 2
481    #[serde(rename = "C4ASKSIZE2")]
482    #[serde(with = "string_as_float_opt")]
483    #[serde(skip_serializing_if = "Option::is_none")]
484    pub c4_ask_size_2: Option<f64>,
485
486    /// Ask size for currency 4 at level 3
487    #[serde(rename = "C4ASKSIZE3")]
488    #[serde(with = "string_as_float_opt")]
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub c4_ask_size_3: Option<f64>,
491
492    /// Ask size for currency 4 at level 4
493    #[serde(rename = "C4ASKSIZE4")]
494    #[serde(with = "string_as_float_opt")]
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub c4_ask_size_4: Option<f64>,
497
498    /// Ask size for currency 4 at level 5
499    #[serde(rename = "C4ASKSIZE5")]
500    #[serde(with = "string_as_float_opt")]
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub c4_ask_size_5: Option<f64>,
503
504    /// Ask size for currency 5 at level 1
505    #[serde(rename = "C5ASKSIZE1")]
506    #[serde(with = "string_as_float_opt")]
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub c5_ask_size_1: Option<f64>,
509
510    /// Ask size for currency 5 at level 2
511    #[serde(rename = "C5ASKSIZE2")]
512    #[serde(with = "string_as_float_opt")]
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub c5_ask_size_2: Option<f64>,
515
516    /// Ask size for currency 5 at level 3
517    #[serde(rename = "C5ASKSIZE3")]
518    #[serde(with = "string_as_float_opt")]
519    #[serde(skip_serializing_if = "Option::is_none")]
520    pub c5_ask_size_3: Option<f64>,
521
522    /// Ask size for currency 5 at level 4
523    #[serde(rename = "C5ASKSIZE4")]
524    #[serde(with = "string_as_float_opt")]
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub c5_ask_size_4: Option<f64>,
527
528    /// Ask size for currency 5 at level 5
529    #[serde(rename = "C5ASKSIZE5")]
530    #[serde(with = "string_as_float_opt")]
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub c5_ask_size_5: Option<f64>,
533
534    /// The timestamp of the price update in UTC milliseconds since epoch
535    #[serde(rename = "TIMESTAMP")]
536    #[serde(with = "string_as_float_opt")]
537    #[serde(skip_serializing_if = "Option::is_none")]
538    pub timestamp: Option<f64>,
539
540    /// Dealing status flag indicating trading availability/state of the market
541    #[serde(rename = "DLG_FLAG")]
542    #[serde(skip_serializing_if = "Option::is_none")]
543    pub dealing_flag: Option<DealingFlag>,
544}
545
546impl PriceData {
547    /// Converts a Lightstreamer ItemUpdate to a PriceData object
548    ///
549    /// # Arguments
550    ///
551    /// * `item_update` - The ItemUpdate from Lightstreamer containing price data
552    ///
553    /// # Returns
554    ///
555    /// A Result containing either the parsed PriceData or an error message
556    pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
557        // Extract the item_name, defaulting to an empty string if None
558        let item_name = item_update.item_name.clone().unwrap_or_default();
559
560        // Convert item_pos from usize to i32
561        let item_pos = item_update.item_pos as i32;
562
563        // Extract is_snapshot
564        let is_snapshot = item_update.is_snapshot;
565
566        // Convert fields
567        let fields = Self::create_price_fields(&item_update.fields)?;
568
569        // Convert changed_fields by first creating a HashMap<String, Option<String>>
570        let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
571        for (key, value) in &item_update.changed_fields {
572            changed_fields_map.insert(key.clone(), Some(value.clone()));
573        }
574        let changed_fields = Self::create_price_fields(&changed_fields_map)?;
575
576        Ok(PriceData {
577            item_name,
578            item_pos,
579            fields,
580            changed_fields,
581            is_snapshot,
582        })
583    }
584
585    // Helper method to create PriceFields from a HashMap
586    fn create_price_fields(
587        fields_map: &HashMap<String, Option<String>>,
588    ) -> Result<PriceFields, String> {
589        // Helper function to safely get a field value
590        let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };
591
592        // Helper function to parse float values
593        let parse_float = |key: &str| -> Result<Option<f64>, String> {
594            match get_field(key) {
595                Some(val) if !val.is_empty() => val
596                    .parse::<f64>()
597                    .map(Some)
598                    .map_err(|_| format!("Failed to parse {key} as float: {val}")),
599                _ => Ok(None),
600            }
601        };
602
603        // Parse dealing flag
604        let dealing_flag = match get_field("DLG_FLAG").as_deref() {
605            Some("CLOSED") => Some(DealingFlag::Closed),
606            Some("CALL") => Some(DealingFlag::Call),
607            Some("DEAL") => Some(DealingFlag::Deal),
608            Some("EDIT") => Some(DealingFlag::Edit),
609            Some("CLOSINGONLY") => Some(DealingFlag::ClosingOnly),
610            Some("DEALNOEDIT") => Some(DealingFlag::DealNoEdit),
611            Some("AUCTION") => Some(DealingFlag::Auction),
612            Some("AUCTIONNOEDIT") => Some(DealingFlag::AuctionNoEdit),
613            Some("SUSPEND") => Some(DealingFlag::Suspend),
614            Some(unknown) => return Err(format!("Unknown dealing flag: {unknown}")),
615            None => None,
616        };
617
618        Ok(PriceFields {
619            mid_open: parse_float("MID_OPEN")?,
620            high: parse_float("HIGH")?,
621            low: parse_float("LOW")?,
622            bid_quote_id: get_field("BIDQUOTEID"),
623            ask_quote_id: get_field("ASKQUOTEID"),
624
625            // Bid ladder prices
626            bid_price1: parse_float("BIDPRICE1")?,
627            bid_price2: parse_float("BIDPRICE2")?,
628            bid_price3: parse_float("BIDPRICE3")?,
629            bid_price4: parse_float("BIDPRICE4")?,
630            bid_price5: parse_float("BIDPRICE5")?,
631
632            // Ask ladder prices
633            ask_price1: parse_float("ASKPRICE1")?,
634            ask_price2: parse_float("ASKPRICE2")?,
635            ask_price3: parse_float("ASKPRICE3")?,
636            ask_price4: parse_float("ASKPRICE4")?,
637            ask_price5: parse_float("ASKPRICE5")?,
638
639            // Bid sizes
640            bid_size1: parse_float("BIDSIZE1")?,
641            bid_size2: parse_float("BIDSIZE2")?,
642            bid_size3: parse_float("BIDSIZE3")?,
643            bid_size4: parse_float("BIDSIZE4")?,
644            bid_size5: parse_float("BIDSIZE5")?,
645
646            // Ask sizes
647            ask_size1: parse_float("ASKSIZE1")?,
648            ask_size2: parse_float("ASKSIZE2")?,
649            ask_size3: parse_float("ASKSIZE3")?,
650            ask_size4: parse_float("ASKSIZE4")?,
651            ask_size5: parse_float("ASKSIZE5")?,
652
653            // Currencies
654            currency0: get_field("CURRENCY0"),
655            currency1: get_field("CURRENCY1"),
656            currency2: get_field("CURRENCY2"),
657            currency3: get_field("CURRENCY3"),
658            currency4: get_field("CURRENCY4"),
659            currency5: get_field("CURRENCY5"),
660
661            // Bid size thresholds (expanded 1..5 for C1..C5)
662            c1_bid_size_1: parse_float("C1BIDSIZE1")?,
663            c1_bid_size_2: parse_float("C1BIDSIZE2")?,
664            c1_bid_size_3: parse_float("C1BIDSIZE3")?,
665            c1_bid_size_4: parse_float("C1BIDSIZE4")?,
666            c1_bid_size_5: parse_float("C1BIDSIZE5")?,
667
668            c2_bid_size_1: parse_float("C2BIDSIZE1")?,
669            c2_bid_size_2: parse_float("C2BIDSIZE2")?,
670            c2_bid_size_3: parse_float("C2BIDSIZE3")?,
671            c2_bid_size_4: parse_float("C2BIDSIZE4")?,
672            c2_bid_size_5: parse_float("C2BIDSIZE5")?,
673
674            c3_bid_size_1: parse_float("C3BIDSIZE1")?,
675            c3_bid_size_2: parse_float("C3BIDSIZE2")?,
676            c3_bid_size_3: parse_float("C3BIDSIZE3")?,
677            c3_bid_size_4: parse_float("C3BIDSIZE4")?,
678            c3_bid_size_5: parse_float("C3BIDSIZE5")?,
679
680            c4_bid_size_1: parse_float("C4BIDSIZE1")?,
681            c4_bid_size_2: parse_float("C4BIDSIZE2")?,
682            c4_bid_size_3: parse_float("C4BIDSIZE3")?,
683            c4_bid_size_4: parse_float("C4BIDSIZE4")?,
684            c4_bid_size_5: parse_float("C4BIDSIZE5")?,
685
686            c5_bid_size_1: parse_float("C5BIDSIZE1")?,
687            c5_bid_size_2: parse_float("C5BIDSIZE2")?,
688            c5_bid_size_3: parse_float("C5BIDSIZE3")?,
689            c5_bid_size_4: parse_float("C5BIDSIZE4")?,
690            c5_bid_size_5: parse_float("C5BIDSIZE5")?,
691
692            // Ask size thresholds (expanded 1..5 for C1..C5)
693            c1_ask_size_1: parse_float("C1ASKSIZE1")?,
694            c1_ask_size_2: parse_float("C1ASKSIZE2")?,
695            c1_ask_size_3: parse_float("C1ASKSIZE3")?,
696            c1_ask_size_4: parse_float("C1ASKSIZE4")?,
697            c1_ask_size_5: parse_float("C1ASKSIZE5")?,
698
699            c2_ask_size_1: parse_float("C2ASKSIZE1")?,
700            c2_ask_size_2: parse_float("C2ASKSIZE2")?,
701            c2_ask_size_3: parse_float("C2ASKSIZE3")?,
702            c2_ask_size_4: parse_float("C2ASKSIZE4")?,
703            c2_ask_size_5: parse_float("C2ASKSIZE5")?,
704
705            c3_ask_size_1: parse_float("C3ASKSIZE1")?,
706            c3_ask_size_2: parse_float("C3ASKSIZE2")?,
707            c3_ask_size_3: parse_float("C3ASKSIZE3")?,
708            c3_ask_size_4: parse_float("C3ASKSIZE4")?,
709            c3_ask_size_5: parse_float("C3ASKSIZE5")?,
710
711            c4_ask_size_1: parse_float("C4ASKSIZE1")?,
712            c4_ask_size_2: parse_float("C4ASKSIZE2")?,
713            c4_ask_size_3: parse_float("C4ASKSIZE3")?,
714            c4_ask_size_4: parse_float("C4ASKSIZE4")?,
715            c4_ask_size_5: parse_float("C4ASKSIZE5")?,
716
717            c5_ask_size_1: parse_float("C5ASKSIZE1")?,
718            c5_ask_size_2: parse_float("C5ASKSIZE2")?,
719            c5_ask_size_3: parse_float("C5ASKSIZE3")?,
720            c5_ask_size_4: parse_float("C5ASKSIZE4")?,
721            c5_ask_size_5: parse_float("C5ASKSIZE5")?,
722
723            timestamp: parse_float("TIMESTAMP")?,
724            dealing_flag,
725        })
726    }
727}
728
729impl From<&ItemUpdate> for PriceData {
730    fn from(item_update: &ItemUpdate) -> Self {
731        PriceData::from_item_update(item_update).unwrap_or_default()
732    }
733}