ig-client 0.11.3

This crate provides a client for the IG Markets API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
use crate::presentation::serialization::string_as_float_opt;
use lightstreamer_rs::subscription::ItemUpdate;
use pretty_simple_display::{DebugPretty, DisplaySimple};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Market dealing status flags indicating trading availability
#[repr(u8)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum DealingFlag {
    /// Market is closed for trading
    #[default]
    Closed,
    /// Market is in call phase
    Call,
    /// Market is open for dealing
    Deal,
    /// Market is open for editing orders
    Edit,
    /// Market is open for closing positions only
    ClosingsOnly,
    /// Market is open for dealing but not editing
    DealNoEdit,
    /// Market is in auction phase
    Auction,
    /// Market is in auction phase without editing
    AuctionNoEdit,
    /// Market trading is suspended
    Suspend,
}

/// Structure for price data received from the IG Markets API
/// Contains information about market prices and related data
#[derive(DebugPretty, Clone, DisplaySimple, Serialize, Deserialize, Default)]
pub struct PriceData {
    /// Name of the item (usually the market ID)
    pub item_name: String,
    /// Position of the item in the subscription
    pub item_pos: i32,
    /// All price fields for this market
    pub fields: PriceFields,
    /// Fields that have changed in this update
    pub changed_fields: PriceFields,
    /// Whether this is a snapshot or an update
    pub is_snapshot: bool,
}

/// Price field data containing bid, offer, and market status information
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct PriceFields {
    /// The opening price at the middle of the bid-ask spread
    #[serde(rename = "MID_OPEN")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mid_open: Option<f64>,

    /// The highest price reached during the trading session
    #[serde(rename = "HIGH")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub high: Option<f64>,

    /// The lowest price reached during the trading session
    #[serde(rename = "LOW")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub low: Option<f64>,

    /// Current bid price for the instrument
    #[serde(rename = "BID")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid: Option<f64>,

    /// Current offer (ask) price for the instrument
    #[serde(rename = "OFFER")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub offer: Option<f64>,

    /// Price change from previous close
    #[serde(rename = "CHANGE")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub change: Option<f64>,

    /// Percentage change from previous close
    #[serde(rename = "CHANGE_PCT")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub change_pct: Option<f64>,

    /// Market data delay in seconds
    #[serde(rename = "MARKET_DELAY")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub market_delay: Option<f64>,

    /// Current market state (e.g., "OPEN", "CLOSED", "SUSPENDED")
    #[serde(rename = "MARKET_STATE")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub market_state: Option<String>,

    /// Timestamp of the last price update
    #[serde(rename = "UPDATE_TIME")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub update_time: Option<String>,

    /// Unique identifier for the bid quote
    #[serde(rename = "BIDQUOTEID")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_quote_id: Option<String>,

    /// Unique identifier for the ask quote
    #[serde(rename = "ASKQUOTEID")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_quote_id: Option<String>,

    // Bid ladder prices
    /// First level bid price in the order book
    #[serde(rename = "BIDPRICE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_price1: Option<f64>,

    /// Second level bid price in the order book
    #[serde(rename = "BIDPRICE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_price2: Option<f64>,

    /// Third level bid price in the order book
    #[serde(rename = "BIDPRICE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_price3: Option<f64>,

    /// Fourth level bid price in the order book
    #[serde(rename = "BIDPRICE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_price4: Option<f64>,

    /// Fifth level bid price in the order book
    #[serde(rename = "BIDPRICE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_price5: Option<f64>,

    // Ask ladder prices
    /// First level ask price in the order book
    #[serde(rename = "ASKPRICE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_price1: Option<f64>,

    /// Second level ask price in the order book
    #[serde(rename = "ASKPRICE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_price2: Option<f64>,

    /// Third level ask price in the order book
    #[serde(rename = "ASKPRICE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_price3: Option<f64>,

    /// Fourth level ask price in the order book
    #[serde(rename = "ASKPRICE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_price4: Option<f64>,

    /// Fifth level ask price in the order book
    #[serde(rename = "ASKPRICE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_price5: Option<f64>,

    // Bid sizes
    /// Volume available at the first level bid price
    #[serde(rename = "BIDSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_size1: Option<f64>,

    /// Volume available at the second level bid price
    #[serde(rename = "BIDSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_size2: Option<f64>,

    /// Volume available at the third level bid price
    #[serde(rename = "BIDSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_size3: Option<f64>,

    /// Volume available at the fourth level bid price
    #[serde(rename = "BIDSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_size4: Option<f64>,

    /// Volume available at the fifth level bid price
    #[serde(rename = "BIDSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bid_size5: Option<f64>,

    // Ask sizes
    /// Volume available at the first level ask price
    #[serde(rename = "ASKSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_size1: Option<f64>,

    /// Volume available at the second level ask price
    #[serde(rename = "ASKSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_size2: Option<f64>,

    /// Volume available at the third level ask price
    #[serde(rename = "ASKSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_size3: Option<f64>,

    /// Volume available at the fourth level ask price
    #[serde(rename = "ASKSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_size4: Option<f64>,

    /// Volume available at the fifth level ask price
    #[serde(rename = "ASKSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ask_size5: Option<f64>,

    /// Base currency code for the trading pair
    #[serde(rename = "CURRENCY0")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub currency0: Option<String>,

    /// First alternative currency code
    #[serde(rename = "CURRENCY1")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub currency1: Option<String>,

    /// Second alternative currency code
    #[serde(rename = "CURRENCY2")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub currency2: Option<String>,

    /// Third alternative currency code
    #[serde(rename = "CURRENCY3")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub currency3: Option<String>,

    /// Fourth alternative currency code
    #[serde(rename = "CURRENCY4")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub currency4: Option<String>,

    /// Fifth alternative currency code
    #[serde(rename = "CURRENCY5")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub currency5: Option<String>,

    /// Bid size for currency 1 at level 1
    #[serde(rename = "C1BIDSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_bid_size_1: Option<f64>,

    /// Bid size for currency 1 at level 2
    #[serde(rename = "C1BIDSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_bid_size_2: Option<f64>,

    /// Bid size for currency 1 at level 3
    #[serde(rename = "C1BIDSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_bid_size_3: Option<f64>,

    /// Bid size for currency 1 at level 4
    #[serde(rename = "C1BIDSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_bid_size_4: Option<f64>,

    /// Bid size for currency 1 at level 5
    #[serde(rename = "C1BIDSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_bid_size_5: Option<f64>,

    /// Bid size for currency 2 at level 1
    #[serde(rename = "C2BIDSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_bid_size_1: Option<f64>,

    /// Bid size for currency 2 at level 2
    #[serde(rename = "C2BIDSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_bid_size_2: Option<f64>,

    /// Bid size for currency 2 at level 3
    #[serde(rename = "C2BIDSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_bid_size_3: Option<f64>,

    /// Bid size for currency 2 at level 4
    #[serde(rename = "C2BIDSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_bid_size_4: Option<f64>,

    /// Bid size for currency 2 at level 5
    #[serde(rename = "C2BIDSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_bid_size_5: Option<f64>,

    /// Bid size for currency 3 at level 1
    #[serde(rename = "C3BIDSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_bid_size_1: Option<f64>,

    /// Bid size for currency 3 at level 2
    #[serde(rename = "C3BIDSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_bid_size_2: Option<f64>,

    /// Bid size for currency 3 at level 3
    #[serde(rename = "C3BIDSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_bid_size_3: Option<f64>,

    /// Bid size for currency 3 at level 4
    #[serde(rename = "C3BIDSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_bid_size_4: Option<f64>,

    /// Bid size for currency 3 at level 5
    #[serde(rename = "C3BIDSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_bid_size_5: Option<f64>,

    /// Bid size for currency 4 at level 1
    #[serde(rename = "C4BIDSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_bid_size_1: Option<f64>,

    /// Bid size for currency 4 at level 2
    #[serde(rename = "C4BIDSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_bid_size_2: Option<f64>,

    /// Bid size for currency 4 at level 3
    #[serde(rename = "C4BIDSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_bid_size_3: Option<f64>,

    /// Bid size for currency 4 at level 4
    #[serde(rename = "C4BIDSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_bid_size_4: Option<f64>,

    /// Bid size for currency 4 at level 5
    #[serde(rename = "C4BIDSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_bid_size_5: Option<f64>,

    /// Bid size for currency 5 at level 1
    #[serde(rename = "C5BIDSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_bid_size_1: Option<f64>,

    /// Bid size for currency 5 at level 2
    #[serde(rename = "C5BIDSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_bid_size_2: Option<f64>,

    /// Bid size for currency 5 at level 3
    #[serde(rename = "C5BIDSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_bid_size_3: Option<f64>,

    /// Bid size for currency 5 at level 4
    #[serde(rename = "C5BIDSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_bid_size_4: Option<f64>,

    /// Bid size for currency 5 at level 5
    #[serde(rename = "C5BIDSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_bid_size_5: Option<f64>,

    // Ask sizes for different currencies
    /// Ask size for currency 1 at level 1
    #[serde(rename = "C1ASKSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_ask_size_1: Option<f64>,

    /// Ask size for currency 1 at level 2
    #[serde(rename = "C1ASKSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_ask_size_2: Option<f64>,

    /// Ask size for currency 1 at level 3
    #[serde(rename = "C1ASKSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_ask_size_3: Option<f64>,

    /// Ask size for currency 1 at level 4
    #[serde(rename = "C1ASKSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_ask_size_4: Option<f64>,

    /// Ask size for currency 1 at level 5
    #[serde(rename = "C1ASKSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1_ask_size_5: Option<f64>,

    /// Ask size for currency 2 at level 1
    #[serde(rename = "C2ASKSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_ask_size_1: Option<f64>,

    /// Ask size for currency 2 at level 2
    #[serde(rename = "C2ASKSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_ask_size_2: Option<f64>,

    /// Ask size for currency 2 at level 3
    #[serde(rename = "C2ASKSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_ask_size_3: Option<f64>,

    /// Ask size for currency 2 at level 4
    #[serde(rename = "C2ASKSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_ask_size_4: Option<f64>,

    /// Ask size for currency 2 at level 5
    #[serde(rename = "C2ASKSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2_ask_size_5: Option<f64>,

    /// Ask size for currency 3 at level 1
    #[serde(rename = "C3ASKSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_ask_size_1: Option<f64>,

    /// Ask size for currency 3 at level 2
    #[serde(rename = "C3ASKSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_ask_size_2: Option<f64>,

    /// Ask size for currency 3 at level 3
    #[serde(rename = "C3ASKSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_ask_size_3: Option<f64>,

    /// Ask size for currency 3 at level 4
    #[serde(rename = "C3ASKSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_ask_size_4: Option<f64>,

    /// Ask size for currency 3 at level 5
    #[serde(rename = "C3ASKSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3_ask_size_5: Option<f64>,

    /// Ask size for currency 4 at level 1
    #[serde(rename = "C4ASKSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_ask_size_1: Option<f64>,

    /// Ask size for currency 4 at level 2
    #[serde(rename = "C4ASKSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_ask_size_2: Option<f64>,

    /// Ask size for currency 4 at level 3
    #[serde(rename = "C4ASKSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_ask_size_3: Option<f64>,

    /// Ask size for currency 4 at level 4
    #[serde(rename = "C4ASKSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_ask_size_4: Option<f64>,

    /// Ask size for currency 4 at level 5
    #[serde(rename = "C4ASKSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4_ask_size_5: Option<f64>,

    /// Ask size for currency 5 at level 1
    #[serde(rename = "C5ASKSIZE1")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_ask_size_1: Option<f64>,

    /// Ask size for currency 5 at level 2
    #[serde(rename = "C5ASKSIZE2")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_ask_size_2: Option<f64>,

    /// Ask size for currency 5 at level 3
    #[serde(rename = "C5ASKSIZE3")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_ask_size_3: Option<f64>,

    /// Ask size for currency 5 at level 4
    #[serde(rename = "C5ASKSIZE4")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_ask_size_4: Option<f64>,

    /// Ask size for currency 5 at level 5
    #[serde(rename = "C5ASKSIZE5")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c5_ask_size_5: Option<f64>,

    /// The timestamp of the price update in UTC milliseconds since epoch
    #[serde(rename = "TIMESTAMP")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timestamp: Option<f64>,

    /// Dealing status flag indicating trading availability/state of the market
    #[serde(rename = "DLG_FLAG")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dealing_flag: Option<DealingFlag>,

    /// Price change versus open
    #[serde(rename = "NET_CHG")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub net_chg: Option<f64>,

    /// Percentage change versus open
    #[serde(rename = "NET_CHG_PCT")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub net_chg_pct: Option<f64>,

    /// Delayed price flag (0 = false, 1 = true)
    #[serde(rename = "DELAY")]
    #[serde(with = "string_as_float_opt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub delay: Option<f64>,
}

impl PriceData {
    /// Converts a Lightstreamer ItemUpdate to a PriceData object
    ///
    /// # Arguments
    ///
    /// * `item_update` - The ItemUpdate from Lightstreamer containing price data
    ///
    /// # Returns
    ///
    /// A Result containing either the parsed PriceData or an error message
    pub fn from_item_update(item_update: &ItemUpdate) -> Result<Self, String> {
        // Extract the item_name, defaulting to an empty string if None
        let item_name = item_update.item_name.clone().unwrap_or_default();

        // Convert item_pos from usize to i32
        let item_pos = item_update.item_pos as i32;

        // Extract is_snapshot
        let is_snapshot = item_update.is_snapshot;

        // Convert fields
        let fields = Self::create_price_fields(&item_update.fields)?;

        // Convert changed_fields by first creating a HashMap<String, Option<String>>
        let mut changed_fields_map: HashMap<String, Option<String>> = HashMap::new();
        for (key, value) in &item_update.changed_fields {
            changed_fields_map.insert(key.clone(), Some(value.clone()));
        }
        let changed_fields = Self::create_price_fields(&changed_fields_map)?;

        Ok(PriceData {
            item_name,
            item_pos,
            fields,
            changed_fields,
            is_snapshot,
        })
    }

    // Helper method to create PriceFields from a HashMap
    fn create_price_fields(
        fields_map: &HashMap<String, Option<String>>,
    ) -> Result<PriceFields, String> {
        // Helper function to safely get a field value
        let get_field = |key: &str| -> Option<String> { fields_map.get(key).cloned().flatten() };

        // Helper function to parse float values
        let parse_float = |key: &str| -> Result<Option<f64>, String> {
            match get_field(key) {
                Some(val) if !val.is_empty() => val
                    .parse::<f64>()
                    .map(Some)
                    .map_err(|_| format!("Failed to parse {key} as float: {val}")),
                _ => Ok(None),
            }
        };

        // Parse dealing flag (case-insensitive to handle potential lowercase conversion).
        // An empty string (produced when the server sends '#' for null) is treated as None.
        let dealing_flag = match get_field("DLG_FLAG")
            .as_deref()
            .map(str::trim)
            .filter(|s| !s.is_empty())
            .map(|s| s.to_uppercase())
            .as_deref()
        {
            Some("CLOSED") => Some(DealingFlag::Closed),
            Some("CALL") => Some(DealingFlag::Call),
            Some("DEAL") => Some(DealingFlag::Deal),
            Some("EDIT") => Some(DealingFlag::Edit),
            Some("CLOSINGSONLY") | Some("CLOSINGONLY") => Some(DealingFlag::ClosingsOnly),
            Some("DEALNOEDIT") => Some(DealingFlag::DealNoEdit),
            Some("AUCTION") => Some(DealingFlag::Auction),
            Some("AUCTIONNOEDIT") => Some(DealingFlag::AuctionNoEdit),
            Some("SUSPEND") => Some(DealingFlag::Suspend),
            Some(unknown) => return Err(format!("Unknown dealing flag: {unknown}")),
            None => None,
        };

        Ok(PriceFields {
            mid_open: parse_float("MID_OPEN")?,
            high: parse_float("HIGH")?,
            low: parse_float("LOW")?,
            bid: parse_float("BID")?,
            offer: parse_float("OFFER")?,
            change: parse_float("CHANGE")?,
            change_pct: parse_float("CHANGE_PCT")?,
            market_delay: parse_float("MARKET_DELAY")?,
            market_state: get_field("MARKET_STATE"),
            update_time: get_field("UPDATE_TIME"),

            bid_quote_id: get_field("BIDQUOTEID"),
            ask_quote_id: get_field("ASKQUOTEID"),

            // Bid ladder prices
            bid_price1: parse_float("BIDPRICE1")?,
            bid_price2: parse_float("BIDPRICE2")?,
            bid_price3: parse_float("BIDPRICE3")?,
            bid_price4: parse_float("BIDPRICE4")?,
            bid_price5: parse_float("BIDPRICE5")?,

            // Ask ladder prices
            ask_price1: parse_float("ASKPRICE1")?,
            ask_price2: parse_float("ASKPRICE2")?,
            ask_price3: parse_float("ASKPRICE3")?,
            ask_price4: parse_float("ASKPRICE4")?,
            ask_price5: parse_float("ASKPRICE5")?,

            // Bid sizes
            bid_size1: parse_float("BIDSIZE1")?,
            bid_size2: parse_float("BIDSIZE2")?,
            bid_size3: parse_float("BIDSIZE3")?,
            bid_size4: parse_float("BIDSIZE4")?,
            bid_size5: parse_float("BIDSIZE5")?,

            // Ask sizes
            ask_size1: parse_float("ASKSIZE1")?,
            ask_size2: parse_float("ASKSIZE2")?,
            ask_size3: parse_float("ASKSIZE3")?,
            ask_size4: parse_float("ASKSIZE4")?,
            ask_size5: parse_float("ASKSIZE5")?,

            // Currencies
            currency0: get_field("CURRENCY0"),
            currency1: get_field("CURRENCY1"),
            currency2: get_field("CURRENCY2"),
            currency3: get_field("CURRENCY3"),
            currency4: get_field("CURRENCY4"),
            currency5: get_field("CURRENCY5"),

            // Bid size thresholds (expanded 1..5 for C1..C5)
            c1_bid_size_1: parse_float("C1BIDSIZE1")?,
            c1_bid_size_2: parse_float("C1BIDSIZE2")?,
            c1_bid_size_3: parse_float("C1BIDSIZE3")?,
            c1_bid_size_4: parse_float("C1BIDSIZE4")?,
            c1_bid_size_5: parse_float("C1BIDSIZE5")?,

            c2_bid_size_1: parse_float("C2BIDSIZE1")?,
            c2_bid_size_2: parse_float("C2BIDSIZE2")?,
            c2_bid_size_3: parse_float("C2BIDSIZE3")?,
            c2_bid_size_4: parse_float("C2BIDSIZE4")?,
            c2_bid_size_5: parse_float("C2BIDSIZE5")?,

            c3_bid_size_1: parse_float("C3BIDSIZE1")?,
            c3_bid_size_2: parse_float("C3BIDSIZE2")?,
            c3_bid_size_3: parse_float("C3BIDSIZE3")?,
            c3_bid_size_4: parse_float("C3BIDSIZE4")?,
            c3_bid_size_5: parse_float("C3BIDSIZE5")?,

            c4_bid_size_1: parse_float("C4BIDSIZE1")?,
            c4_bid_size_2: parse_float("C4BIDSIZE2")?,
            c4_bid_size_3: parse_float("C4BIDSIZE3")?,
            c4_bid_size_4: parse_float("C4BIDSIZE4")?,
            c4_bid_size_5: parse_float("C4BIDSIZE5")?,

            c5_bid_size_1: parse_float("C5BIDSIZE1")?,
            c5_bid_size_2: parse_float("C5BIDSIZE2")?,
            c5_bid_size_3: parse_float("C5BIDSIZE3")?,
            c5_bid_size_4: parse_float("C5BIDSIZE4")?,
            c5_bid_size_5: parse_float("C5BIDSIZE5")?,

            // Ask size thresholds (expanded 1..5 for C1..C5)
            c1_ask_size_1: parse_float("C1ASKSIZE1")?,
            c1_ask_size_2: parse_float("C1ASKSIZE2")?,
            c1_ask_size_3: parse_float("C1ASKSIZE3")?,
            c1_ask_size_4: parse_float("C1ASKSIZE4")?,
            c1_ask_size_5: parse_float("C1ASKSIZE5")?,

            c2_ask_size_1: parse_float("C2ASKSIZE1")?,
            c2_ask_size_2: parse_float("C2ASKSIZE2")?,
            c2_ask_size_3: parse_float("C2ASKSIZE3")?,
            c2_ask_size_4: parse_float("C2ASKSIZE4")?,
            c2_ask_size_5: parse_float("C2ASKSIZE5")?,

            c3_ask_size_1: parse_float("C3ASKSIZE1")?,
            c3_ask_size_2: parse_float("C3ASKSIZE2")?,
            c3_ask_size_3: parse_float("C3ASKSIZE3")?,
            c3_ask_size_4: parse_float("C3ASKSIZE4")?,
            c3_ask_size_5: parse_float("C3ASKSIZE5")?,

            c4_ask_size_1: parse_float("C4ASKSIZE1")?,
            c4_ask_size_2: parse_float("C4ASKSIZE2")?,
            c4_ask_size_3: parse_float("C4ASKSIZE3")?,
            c4_ask_size_4: parse_float("C4ASKSIZE4")?,
            c4_ask_size_5: parse_float("C4ASKSIZE5")?,

            c5_ask_size_1: parse_float("C5ASKSIZE1")?,
            c5_ask_size_2: parse_float("C5ASKSIZE2")?,
            c5_ask_size_3: parse_float("C5ASKSIZE3")?,
            c5_ask_size_4: parse_float("C5ASKSIZE4")?,
            c5_ask_size_5: parse_float("C5ASKSIZE5")?,

            timestamp: parse_float("TIMESTAMP")?,
            dealing_flag,
            net_chg: parse_float("NET_CHG")?,
            net_chg_pct: parse_float("NET_CHG_PCT")?,
            delay: parse_float("DELAY")?,
        })
    }
}

impl From<&ItemUpdate> for PriceData {
    fn from(item_update: &ItemUpdate) -> Self {
        PriceData::from_item_update(item_update).unwrap_or_else(|e| {
            tracing::warn!(error = %e, "failed to convert ItemUpdate to PriceData, returning default");
            PriceData::default()
        })
    }
}