Skip to main content

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