cfix/
messages.rs

1use crate::types::{Config, Field, OrderType, Side, SubID};
2use chrono::Utc;
3use std::collections::HashMap;
4
5// Response
6#[derive(Debug, Clone)]
7pub struct ResponseMessage {
8    message: String,
9    field_idx: HashMap<u32, usize>,
10    fields: Vec<(u32, String)>,
11}
12
13impl ResponseMessage {
14    pub fn new(message: &str, delimiter: &str) -> Self {
15        let message = message.replace(delimiter, "|");
16        let mut idx_map = HashMap::new();
17        let fields = message
18            .split("|")
19            .filter(|field| !field.is_empty() && field.contains("="))
20            .enumerate()
21            .map(|(idx, field)| {
22                let parts = field.split("=").collect::<Vec<_>>();
23                let value = &parts[1..].join("=");
24
25                let field = parts[0].parse::<u32>().unwrap();
26                if !idx_map.contains_key(&field) {
27                    idx_map.insert(parts[0].parse::<u32>().unwrap(), idx);
28                }
29                (field, value.to_string())
30            })
31            .collect::<Vec<_>>();
32        Self {
33            message,
34            field_idx: idx_map,
35            fields,
36        }
37    }
38
39    pub fn matching_field_value(&self, msg_type: &str, field: Field, value: &str) -> bool {
40        (self.get_message_type() == msg_type)
41            && (self
42                .get_field_value(field)
43                .map(|v| v == value)
44                .unwrap_or(false))
45    }
46
47    pub fn get_field_value(&self, field: Field) -> Option<String> {
48        self.field_idx
49            .get(&(field as u32))
50            .map(|idx| self.fields[*idx].1.clone())
51        // self.fields.get(&(field as u32)).map(|v| v.clone())
52    }
53
54    pub fn get_message_type(&self) -> &str {
55        self.field_idx
56            .get(&(Field::MsgType as u32))
57            .map(|idx| &self.fields[*idx].1)
58            .unwrap()
59    }
60
61    pub fn get_message(&self) -> &str {
62        &self.message
63    }
64
65    pub fn get_repeating_groups(
66        &self,
67        count_key: Field,
68        start_field: Field,
69        end_field: Option<Field>,
70    ) -> Vec<HashMap<Field, String>> {
71        // FIX later : better algorithms
72        let count_key = count_key as u32;
73
74        let mut count = None;
75        let mut result = vec![];
76        let mut item = HashMap::new();
77        // located more than 8
78        if let Some(start_idx) = self.field_idx.get(&(count_key as u32)) {
79            for (k, v) in &self.fields[*start_idx..] {
80                match count {
81                    Some(0) => {
82                        return result;
83                    }
84                    None => {
85                        if *k == count_key as u32 {
86                            count = Some(v.parse::<usize>().unwrap());
87                        }
88                        continue;
89                    }
90                    _ => {
91                        let key = Field::try_from(*k)
92                            .expect("Failed to parse u32 to Field in get_repeating_groups");
93
94                        match end_field {
95                            Some(end_key) => {
96                                // exclude the checksum
97                                if key != Field::CheckSum {
98                                    item.insert(key, v.clone());
99                                }
100
101                                if key == end_key {
102                                    result.push(item.clone());
103                                    item.clear();
104                                    count = count.map(|c| c - 1);
105                                }
106                            }
107                            None => {
108                                if key == start_field && !item.is_empty() {
109                                    result.push(item.clone());
110                                    item.clear();
111                                    count = count.map(|c| c - 1);
112                                }
113
114                                if key != Field::CheckSum {
115                                    item.insert(key, v.clone());
116                                }
117                            }
118                        }
119                        // if Some(key) == end_field
120                        //     || (end_field.is_none() && key == start_field && !item.is_empty())
121                        // {
122                        // }
123                        // item.insert(key, v.clone());
124                    }
125                }
126            }
127            result.push(item.clone());
128        }
129        result
130    }
131}
132
133fn format_field<T: std::fmt::Display>(field: Field, value: T) -> String {
134    format!("{}={}", field as u32, value)
135}
136// Request
137
138// motivated from the cTraderFixPy .
139pub trait RequestMessage: Send {
140    fn build(
141        &self,
142        sub_id: SubID,
143        sequence_number: u32,
144        delimiter: &str,
145        config: &Config,
146    ) -> String {
147        let body = self.get_body(delimiter, config);
148        let header = self.get_header(
149            sub_id,
150            body.as_ref().map(|s| s.len()).unwrap_or(0),
151            sequence_number,
152            delimiter,
153            config,
154        );
155        let header_and_body = match body {
156            Some(body) => format!("{}{}{}{}", header, delimiter, body, delimiter),
157            None => format!("{}{}", header, delimiter),
158        };
159        let trailer = self.get_trailer(&header_and_body);
160        format!("{}{}{}", header_and_body, trailer, delimiter)
161    }
162
163    fn get_header(
164        &self,
165        sub_id: SubID,
166        len_body: usize,
167        sequence_number: u32,
168        delimiter: &str,
169        config: &Config,
170    ) -> String {
171        let fields = vec![
172            format_field(Field::MsgType, self.get_message_type()),
173            format_field(Field::SenderCompID, &config.sender_comp_id),
174            format_field(Field::TargetCompID, "CSERVER"),
175            format_field(Field::TargetSubID, sub_id.to_string()),
176            format_field(Field::SenderSubID, sub_id.to_string()),
177            format_field(Field::MsgSeqNum, sequence_number),
178            format_field(Field::SendingTime, Utc::now().format("%Y%m%d-%H:%M:%S")),
179        ];
180        let fields_joined = fields.join(delimiter);
181        format!(
182            "8=FIX.4.4{}9={}{}{}",
183            delimiter,
184            len_body + fields_joined.len() + 2,
185            delimiter,
186            fields_joined
187        )
188    }
189
190    fn get_trailer(&self, header_and_body: &str) -> String {
191        let message_bytes = header_and_body.as_bytes();
192        let checksum = message_bytes.iter().map(|byte| *byte as u32).sum::<u32>() % 256;
193        format!("10={:03}", checksum)
194    }
195
196    fn get_body(&self, delimiter: &str, config: &Config) -> Option<String>;
197
198    fn get_message_type(&self) -> &str;
199}
200
201#[derive(Debug, Clone, Default)]
202pub struct LogonReq {
203    pub encryption_scheme: i32,
204    pub reset_seq_num: Option<bool>,
205}
206
207impl LogonReq {
208    pub fn new(reset_seq_num: Option<bool>) -> Self {
209        Self {
210            encryption_scheme: 0,
211            reset_seq_num,
212        }
213    }
214}
215
216impl RequestMessage for LogonReq {
217    fn get_body(&self, delimiter: &str, config: &Config) -> Option<String> {
218        let mut fields = vec![
219            format_field(Field::EncryptMethod, self.encryption_scheme),
220            format_field(Field::HeartBtInt, config.heart_beat),
221            format_field(Field::Username, &config.username),
222            format_field(Field::Password, &config.password),
223        ];
224
225        match self.reset_seq_num {
226            Some(true) => {
227                fields.push("141=Y".to_string());
228            } // Field::ResetSeqNumFlag
229            _ => {}
230        }
231
232        Some(fields.join(delimiter))
233    }
234
235    fn get_message_type(&self) -> &str {
236        "A"
237    }
238}
239
240#[derive(Debug, Clone, Default)]
241pub struct LogoutReq;
242
243impl RequestMessage for LogoutReq {
244    fn get_body(&self, _delimiter: &str, _config: &Config) -> Option<String> {
245        None
246    }
247
248    fn get_message_type(&self) -> &str {
249        "5"
250    }
251}
252
253#[derive(Debug, Clone, Default)]
254pub struct HeartbeatReq {
255    test_req_id: Option<String>,
256}
257
258impl HeartbeatReq {
259    pub fn new(test_req_id: Option<String>) -> Self {
260        Self { test_req_id }
261    }
262}
263
264impl RequestMessage for HeartbeatReq {
265    fn get_body(&self, _delimiter: &str, _config: &Config) -> Option<String> {
266        self.test_req_id
267            .as_ref()
268            .map(|test_req_id| format_field(Field::TestReqID, test_req_id))
269    }
270
271    fn get_message_type(&self) -> &str {
272        "0"
273    }
274}
275
276#[derive(Debug, Clone, Default)]
277pub struct TestReq {
278    test_req_id: String,
279}
280
281impl TestReq {
282    pub fn new(test_req_id: String) -> Self {
283        Self { test_req_id }
284    }
285}
286
287impl RequestMessage for TestReq {
288    fn get_body(&self, _delimiter: &str, _config: &Config) -> Option<String> {
289        Some(format_field(Field::TestReqID, &self.test_req_id))
290    }
291
292    fn get_message_type(&self) -> &str {
293        "1"
294    }
295}
296
297#[derive(Debug, Clone, Default)]
298pub struct ResendReq {
299    begin_seq_no: u32,
300    end_seq_no: u32,
301}
302
303impl ResendReq {
304    pub fn new(begin_seq_no: u32, end_seq_no: u32) -> Self {
305        Self {
306            begin_seq_no,
307            end_seq_no,
308        }
309    }
310}
311
312impl RequestMessage for ResendReq {
313    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
314        let fields = vec![
315            format_field(Field::BeginSeqNo, self.begin_seq_no),
316            format_field(Field::EndSeqNo, self.end_seq_no),
317        ];
318        Some(fields.join(delimiter))
319    }
320
321    fn get_message_type(&self) -> &str {
322        "2"
323    }
324}
325
326#[derive(Debug, Clone, Default)]
327pub struct SequenceReset {
328    gap_fill_flag: Option<bool>,
329    new_seq_no: u32,
330}
331impl SequenceReset {
332    pub fn new(gap_fill_flag: Option<bool>, new_seq_no: u32) -> Self {
333        Self {
334            gap_fill_flag,
335            new_seq_no,
336        }
337    }
338}
339
340impl RequestMessage for SequenceReset {
341    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
342        let mut fields = vec![format_field(Field::NewSeqNo, self.new_seq_no)];
343
344        if let Some(gap_fill_flag) = self.gap_fill_flag {
345            fields.push(format_field(
346                Field::GapFillFlag,
347                if gap_fill_flag { "Y" } else { "N" },
348            ));
349        }
350
351        Some(fields.join(delimiter))
352    }
353
354    fn get_message_type(&self) -> &str {
355        "4"
356    }
357}
358
359#[derive(Debug, Clone, Default)]
360pub struct MarketDataReq {
361    md_req_id: String,
362    subscription_req_type: char,
363    market_depth: u32,
364    md_update_type: Option<u32>,
365    no_md_entry_types: u32,
366    md_entry_type: Vec<char>,
367    no_related_sym: u32,
368    symbol: u32,
369}
370
371impl MarketDataReq {
372    pub fn new(
373        md_req_id: String,
374        subscription_req_type: char,
375        market_depth: u32,
376        md_update_type: Option<u32>,
377        md_entry_type: &[char],
378        no_related_sym: u32,
379        symbol: u32,
380    ) -> Self {
381        Self {
382            md_req_id,
383            subscription_req_type,
384            market_depth,
385            md_update_type,
386            no_md_entry_types: md_entry_type.len() as u32,
387            md_entry_type: md_entry_type.into(),
388            no_related_sym,
389            symbol,
390        }
391    }
392}
393
394impl RequestMessage for MarketDataReq {
395    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
396        let mut fields = vec![
397            format_field(Field::MDReqID, &self.md_req_id),
398            format_field(Field::SubscriptionRequestType, self.subscription_req_type),
399            format_field(Field::MarketDepth, self.market_depth),
400            format_field(Field::NoMDEntryTypes, self.no_md_entry_types),
401        ];
402        // order important
403        self.md_entry_type
404            .iter()
405            .for_each(|c| fields.push(format_field(Field::MDEntryType, c)));
406
407        fields.extend([
408            format_field(Field::NoRelatedSym, self.no_related_sym),
409            format_field(Field::Symbol, &self.symbol),
410        ]);
411
412        if let Some(md_update_type) = self.md_update_type {
413            fields.push(format_field(Field::MDUpdateType, md_update_type));
414        }
415
416        Some(fields.join(delimiter))
417    }
418
419    fn get_message_type(&self) -> &str {
420        "V"
421    }
422}
423
424#[derive(Debug, Clone, Default)]
425pub struct NewOrderSingleReq {
426    pub cl_ord_id: String,
427    pub symbol: u32,
428    pub side: Side,
429    pub transact_time: Option<chrono::NaiveDateTime>,
430    pub order_qty: f64,
431    pub ord_type: OrderType,
432    pub price: Option<f64>,
433    pub stop_px: Option<f64>,
434    pub expire_time: Option<chrono::NaiveDateTime>,
435    pub pos_maint_rpt_id: Option<String>,
436    pub designation: Option<String>,
437}
438
439impl NewOrderSingleReq {
440    pub fn new(
441        cl_ord_id: String,
442        symbol: u32,
443        side: Side,
444        transact_time: Option<chrono::NaiveDateTime>,
445        order_qty: f64,
446        ord_type: OrderType,
447        price: Option<f64>,
448        stop_px: Option<f64>,
449        expire_time: Option<chrono::NaiveDateTime>,
450        pos_maint_rpt_id: Option<String>,
451        designation: Option<String>,
452    ) -> Self {
453        Self {
454            cl_ord_id,
455            symbol,
456            side,
457            transact_time,
458            order_qty,
459            ord_type,
460            price,
461            stop_px,
462            expire_time,
463            pos_maint_rpt_id,
464            designation,
465        }
466    }
467}
468
469impl RequestMessage for NewOrderSingleReq {
470    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
471        let mut fields = vec![
472            format_field(Field::ClOrdId, &self.cl_ord_id),
473            format_field(Field::Symbol, &self.symbol),
474            format_field(Field::Side, self.side as u32),
475            format_field(
476                Field::TransactTime,
477                self.transact_time.map_or_else(
478                    || Utc::now().format("%Y%m%d-%H:%M:%S").to_string(),
479                    |d| d.format("%Y%m%d-%H:%M:%S").to_string(),
480                ),
481            ),
482            format_field(Field::OrderQty, self.order_qty),
483            format_field(Field::OrdType, self.ord_type as u32),
484        ];
485
486        if let Some(price) = self.price {
487            fields.push(format_field(Field::Price, price));
488        }
489        if let Some(stop_px) = self.stop_px {
490            fields.push(format_field(Field::StopPx, stop_px));
491        }
492        if let Some(expire_time) = self.expire_time {
493            fields.push(format_field(
494                Field::ExpireTime,
495                expire_time.format("%Y%m%d-%H:%M:%S"),
496            ));
497        }
498        if let Some(pos_maint_rpt_id) = &self.pos_maint_rpt_id {
499            fields.push(format_field(Field::PosMaintRptID, pos_maint_rpt_id));
500        }
501        if let Some(designation) = &self.designation {
502            fields.push(format_field(Field::Designation, designation));
503        }
504
505        Some(fields.join(delimiter))
506    }
507
508    fn get_message_type(&self) -> &str {
509        "D"
510    }
511}
512
513#[derive(Debug, Clone, Default)]
514pub struct OrderStatusReq {
515    pub cl_ord_id: String,
516    pub side: Option<Side>,
517}
518
519impl RequestMessage for OrderStatusReq {
520    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
521        let mut fields = vec![format_field(Field::ClOrdId, &self.cl_ord_id)];
522
523        if let Some(side) = self.side {
524            fields.push(format_field(Field::Side, side as u32));
525        }
526
527        Some(fields.join(delimiter))
528    }
529
530    fn get_message_type(&self) -> &str {
531        "H"
532    }
533}
534
535impl OrderStatusReq {
536    pub fn new(cl_ord_id: String, side: Option<Side>) -> Self {
537        Self { cl_ord_id, side }
538    }
539}
540
541#[derive(Debug, Clone, Default)]
542pub struct OrderMassStatusReq {
543    pub mass_status_req_id: String,
544    pub mass_status_req_type: u32,
545    pub issue_date: Option<chrono::NaiveDateTime>,
546}
547
548impl OrderMassStatusReq {
549    pub fn new(
550        mass_status_req_id: String,
551        mass_status_req_type: u32,
552        issue_date: Option<chrono::NaiveDateTime>,
553    ) -> Self {
554        Self {
555            mass_status_req_id,
556            mass_status_req_type,
557            issue_date,
558        }
559    }
560}
561
562impl RequestMessage for OrderMassStatusReq {
563    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
564        let mut fields = vec![
565            format_field(Field::MassStatusReqID, &self.mass_status_req_id),
566            format_field(Field::MassStatusReqType, self.mass_status_req_type),
567        ];
568
569        if let Some(issue_date) = self.issue_date {
570            fields.push(format_field(
571                Field::IssueDate,
572                issue_date.format("%Y%m%d-%H:%M:%S"),
573            ));
574        }
575
576        Some(fields.join(delimiter))
577    }
578
579    fn get_message_type(&self) -> &str {
580        "AF"
581    }
582}
583
584#[derive(Debug, Clone, Default)]
585pub struct PositionsReq {
586    pub pos_req_id: String,
587    pub pos_maint_rpt_id: Option<String>,
588}
589
590impl PositionsReq {
591    pub fn new(pos_req_id: String, pos_maint_rpt_id: Option<String>) -> Self {
592        Self {
593            pos_req_id,
594            pos_maint_rpt_id,
595        }
596    }
597}
598
599impl RequestMessage for PositionsReq {
600    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
601        let mut fields = vec![format_field(Field::PosReqID, &self.pos_req_id)];
602
603        if let Some(pos_maint_rpt_id) = &self.pos_maint_rpt_id {
604            fields.push(format_field(Field::PosMaintRptID, pos_maint_rpt_id));
605        }
606
607        Some(fields.join(delimiter))
608    }
609
610    fn get_message_type(&self) -> &str {
611        "AN"
612    }
613}
614
615#[derive(Debug, Clone, Default)]
616pub struct OrderCancelReq {
617    pub orig_cl_ord_id: String,
618    pub order_id: Option<String>,
619    pub cl_ord_id: String,
620}
621impl OrderCancelReq {
622    pub fn new(orig_cl_ord_id: String, order_id: Option<String>, cl_ord_id: String) -> Self {
623        Self {
624            orig_cl_ord_id,
625            order_id,
626            cl_ord_id,
627        }
628    }
629}
630impl RequestMessage for OrderCancelReq {
631    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
632        let mut fields = vec![
633            format_field(Field::OrigClOrdID, &self.orig_cl_ord_id),
634            format_field(Field::ClOrdId, &self.cl_ord_id),
635        ];
636
637        if let Some(order_id) = &self.order_id {
638            fields.push(format_field(Field::OrderID, order_id));
639        }
640
641        Some(fields.join(delimiter))
642    }
643
644    fn get_message_type(&self) -> &str {
645        "F"
646    }
647}
648
649#[derive(Debug, Clone, Default)]
650pub struct OrderCancelReplaceReq {
651    pub orig_cl_ord_id: String,
652    pub order_id: Option<String>,
653    pub cl_ord_id: String,
654    pub order_qty: f64,
655    pub price: Option<f64>,
656    pub stop_px: Option<f64>,
657    pub expire_time: Option<chrono::NaiveDateTime>,
658}
659
660impl OrderCancelReplaceReq {
661    pub fn new(
662        orig_cl_ord_id: String,
663        order_id: Option<String>,
664        cl_ord_id: String,
665        order_qty: f64,
666        price: Option<f64>,
667        stop_px: Option<f64>,
668        expire_time: Option<chrono::NaiveDateTime>,
669    ) -> Self {
670        Self {
671            orig_cl_ord_id,
672            order_id,
673            cl_ord_id,
674            order_qty,
675            price,
676            stop_px,
677            expire_time,
678        }
679    }
680}
681
682impl RequestMessage for OrderCancelReplaceReq {
683    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
684        let mut fields = vec![
685            format_field(Field::OrigClOrdID, &self.orig_cl_ord_id),
686            format_field(Field::ClOrdId, &self.cl_ord_id),
687            format_field(Field::OrderQty, self.order_qty),
688        ];
689
690        if let Some(order_id) = &self.order_id {
691            fields.push(format_field(Field::OrderID, order_id));
692        }
693        if let Some(price) = self.price {
694            fields.push(format_field(Field::Price, price));
695        }
696        if let Some(stop_px) = self.stop_px {
697            fields.push(format_field(Field::StopPx, stop_px));
698        }
699        if let Some(expire_time) = self.expire_time {
700            fields.push(format_field(
701                Field::ExpireTime,
702                expire_time.format("%Y%m%d-%H:%M:%S"),
703            ));
704        }
705
706        Some(fields.join(delimiter))
707    }
708
709    fn get_message_type(&self) -> &str {
710        "G"
711    }
712}
713
714#[derive(Debug, Clone, Default)]
715pub struct SecurityListReq {
716    pub security_req_id: String,
717    pub security_list_req_type: u32,
718    pub symbol: Option<String>,
719}
720
721impl SecurityListReq {
722    pub fn new(
723        security_req_id: String,
724        security_list_req_type: u32,
725        symbol: Option<String>,
726    ) -> Self {
727        Self {
728            security_req_id,
729            security_list_req_type,
730            symbol,
731        }
732    }
733}
734
735impl RequestMessage for SecurityListReq {
736    fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
737        let mut fields = vec![
738            format_field(Field::SecurityReqID, &self.security_req_id),
739            format_field(Field::SecurityListRequestType, self.security_list_req_type),
740        ];
741
742        if let Some(symbol) = &self.symbol {
743            fields.push(format_field(Field::Symbol, symbol));
744        }
745
746        Some(fields.join(delimiter))
747    }
748
749    fn get_message_type(&self) -> &str {
750        "x"
751    }
752}
753
754#[cfg(test)]
755mod tests {
756    use super::ResponseMessage;
757    use crate::types::{Field, DELIMITER};
758    #[test]
759    fn test_parse_repeating_group_spot_market() {
760        let res = "8=FIX.4.4|9=134|35=W|34=2|49=CSERVER|50=QUOTE|52=20170117-10:26:54.630|56=live.theBroker.12345|57=any_string|55=1|268=2|269=0|270=1.06625|269=1|270=1.0663|10=118|".to_string().replace("|", DELIMITER);
761        let msg = ResponseMessage::new(&res, DELIMITER);
762        let result = msg.get_repeating_groups(
763            Field::NoMDEntries,
764            Field::MDEntryType,
765            Some(Field::MDEntryPx),
766        );
767        assert_eq!(result.len(), 2);
768        assert_eq!(result[0].len(), 2);
769        assert_eq!(result[1].len(), 2);
770
771        let result = msg.get_repeating_groups(Field::NoMDEntries, Field::MDEntryType, None);
772
773        assert_eq!(result.len(), 2);
774        assert_eq!(result[0].len(), 2);
775        assert_eq!(result[1].len(), 2);
776    }
777
778    #[test]
779    fn test_parse_repeating_group_depth_market() {
780        let res = "8=FIX.4.4|9=310|35=W|34=2|49=CSERVER|50=QUOTE|52=20180925-12:05:28.284|56=live.theBroker.12345|57=Quote|55=1|268=6|269=1|270=1.11132|271=3000000|278=16|269=1|270=1.11134|271=5000000|278=17|269=1|270=1.11133|271=3000000|278=15|269=0|270=1.1112|271=2000000|278=12|269=0|270=1.11121|271=1000000|278=13|269=0|270=1.11122|271=3000000|278=14|10=247|".to_string().replace("|", DELIMITER);
781        let msg = ResponseMessage::new(&res, DELIMITER);
782        let result = msg.get_repeating_groups(Field::NoMDEntries, Field::MDEntryType, None);
783
784        assert_eq!(6, result.len());
785        for group in result.into_iter() {
786            assert_eq!(4, group.len());
787            assert!(group.contains_key(&Field::MDEntryID));
788            assert!(group.contains_key(&Field::MDEntrySize));
789            assert!(group.contains_key(&Field::MDEntryPx));
790            assert!(group.contains_key(&Field::MDEntryType));
791        }
792    }
793    #[test]
794    fn test_parse_repeating_group_market_incre() {
795        let res = "8=FIX.4.4|9=376|35=X|34=3|49=CSERVER|50=QUOTE|52=20170117-11:13:44.555|56=live.theBroker.12345|57=any_string|268=8|279=0|269=0|278=7491|55=1|270=1.06897|271=1000000|279=0|269=0|278=7490|55=1|270=1.06898|271=1000000|279=0|269=0|278=7489|55=1|270=1.06874|271=32373000|279=0|269=1|278=7496|55=1|270=1.06931|271=34580000|279=2|278=7477|55=1|279=2|278=7468|55=1|279=2|278=7467|55=1|279=2|278=7484|55=1|10=192|
796".to_string().replace("|", DELIMITER);
797        let msg = ResponseMessage::new(&res, DELIMITER);
798        let result = msg.get_repeating_groups(Field::NoMDEntries, Field::MDUpdateAction, None);
799
800        assert_eq!(8, result.len());
801        for group in result.into_iter() {
802            match group.get(&Field::MDUpdateAction).unwrap().as_str() {
803                "0" => {
804                    assert_eq!(6, group.len());
805                    assert!(group.contains_key(&Field::Symbol));
806                    assert!(group.contains_key(&Field::MDEntryID));
807                    assert!(group.contains_key(&Field::MDEntryPx));
808                    assert!(group.contains_key(&Field::MDEntryType));
809                    assert!(group.contains_key(&Field::MDEntrySize));
810                }
811                "2" => {
812                    assert_eq!(3, group.len());
813                    assert!(group.contains_key(&Field::Symbol));
814                    assert!(group.contains_key(&Field::MDEntryID));
815                }
816                _ => {
817                    assert!(false);
818                }
819            }
820        }
821    }
822}