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