如何以可读格式记录QuickFix消息

本文关键字:记录 QuickFix 消息 格式 | 更新日期: 2023-09-27 18:00:01

我想以某种解析模式记录QuickFix消息,比如标记名,值

我找不到现有的功能。我正在使用QuickFix.Net.

我正在考虑提供某种方法,可以迭代所有呈现的标签,并使用数据字典对其进行解析。

如何以可读格式记录QuickFix消息

有一个Java示例http://www.quickfixj.org/confluence/display/qfj/Using+消息+元数据,可以转换为.NET并且可能被证明是有用的:

public class MetadataExample {
    public static void main(String[] args) throws Exception {
        DataDictionary dd = new DataDictionary("FIX44.xml");
        Message m = new Message("8=FIX.4.4'0019=247'00135=s'00134=5'001"
                + "49=sender'00152=20060319-09:08:20.881'001"
                + "56=target'00122=8'00140=2'00144=9'00148=ABC'00155=ABC'001"
                + "60=20060319-09:08:19'001548=184214'001549=2'001"
                + "550=0'001552=2'00154=1'001453=2'001448=8'001447=D'001"
                + "452=4'001448=AAA35777'001447=D'001452=3'00138=9'00154=2'001"
                + "453=2'001448=8'001447=D'001452=4'001448=aaa'001447=D'001"
                + "452=3'00138=9'00110=056'001", dd);
        MessagePrinter printer = new MessagePrinter();
        printer.print(System.out, dd, m);
    }
}
public class MessagePrinter {
    public void print(DataDictionary dd, Message message) throws FieldNotFound {
        String msgType = message.getHeader().getString(MsgType.FIELD);
        printFieldMap("", dd, msgType, message.getHeader());
        printFieldMap("", dd, msgType, message);
        printFieldMap("", dd, msgType, message.getTrailer());
    }
    private void printFieldMap(String prefix, DataDictionary dd, String msgType, FieldMap fieldMap)
            throws FieldNotFound {
        Iterator fieldIterator = fieldMap.iterator();
        while (fieldIterator.hasNext()) {
            Field field = (Field) fieldIterator.next();
            if (!isGroupCountField(dd, field)) {
                String value = fieldMap.getString(field.getTag());
                if (dd.hasFieldValue(field.getTag())) {
                    value = dd.getValueName(field.getTag(), fieldMap.getString(field.getTag())) + " (" + value + ")";
                }
                System.out.println(prefix + dd.getFieldName(field.getTag()) + ": " + value);
            }
        }
        Iterator groupsKeys = fieldMap.groupKeyIterator();
        while (groupsKeys.hasNext()) {
            int groupCountTag = ((Integer) groupsKeys.next()).intValue();
            System.out.println(prefix + dd.getFieldName(groupCountTag) + ": count = "
                    + fieldMap.getInt(groupCountTag));
            Group g = new Group(groupCountTag, 0);
            int i = 1;
            while (fieldMap.hasGroup(i, groupCountTag)) {
                if (i > 1) {
                    System.out.println(prefix + "  ----");
                }
                fieldMap.getGroup(i, g);
                printFieldMap(prefix + "  ", dd, msgType, g);
                i++;
            }
        }
    }
    private boolean isGroupCountField(DataDictionary dd, Field field) {
        return dd.getFieldTypeEnum(field.getTag()) == FieldType.NumInGroup;
    }
}

输出:

BeginString: FIX.4.4
BodyLength: 247
MsgSeqNum: 5
MsgType: NewOrderCross (s)
SenderCompID: sender
SendingTime: 20060319-09:08:20.881
TargetCompID: target
SecurityIDSource: EXCHANGE_SYMBOL (8)
OrdType: LIMIT (2)
Price: 9
SecurityID: ABC
Symbol: ABC
TransactTime: 20060319-09:08:19
CrossID: 184214
CrossType: CROSS_TRADE_WHICH_IS_EXECUTED_PARTIALLY_AND_THE_REST_IS_CANCELLED (2)
CrossPrioritization: NONE (0)
NoSides: count = 2
  OrderQty: 9
  Side: BUY (1)
  NoPartyIDs: count = 2
    PartyIDSource: PROPRIETARY_CUSTOM_CODE (D)
    PartyID: 8
    PartyRole: CLEARING_FIRM (4)
    ----
    PartyIDSource: PROPRIETARY_CUSTOM_CODE (D)
    PartyID: AAA35777
    PartyRole: CLIENT_ID (3)
  ----
  OrderQty: 9
  Side: SELL (2)
  NoPartyIDs: count = 2
    PartyIDSource: PROPRIETARY_CUSTOM_CODE (D)
    PartyID: 8
    PartyRole: CLEARING_FIRM (4)
    ----
    PartyIDSource: PROPRIETARY_CUSTOM_CODE (D)
    PartyID: aaa
    PartyRole: CLIENT_ID (3)
CheckSum: 056

quickfix中没有以可读格式解析消息的方法。另一种选择是,当你在onMessage中处理传入的FIX消息时,你无论如何都会解析它来读取消息。在这里,您可以列出文件或数据库的标记名和值。但是,任何这样做的操作都可能会降低Quickfix引擎的速度,因为写入文件或DB总是很慢。所以要小心!!

另一种选择是将消息记录到数据库而不是文件中,然后在数据库中完成所有工作,但这意味着要对消息进行两次解析,即在引擎和数据库中。但是你会在想读什么和不想读什么方面有更多的灵活性。

您可以使用QuickFix.Message.InitializeXML方法获得QuickFix.MMessage.ToXML()来输出某个字典的标记。

例如。

QuickFix.Message msg = new QuickFix.Message();
// Create your message
QuickFix.Message.InitializeXML(@"c:'quickfix'spec'FIX44.xml");
Console.WriteLine(msg.ToXML());

它将为您提供每个字段的标记,但如果字段是枚举值,则不会提供字段内容的含义。

这是我在输出窗口中看到的示例:

<message>
<header>
    <field name="BeginString" number="8"><![CDATA[FIX.4.4]]></field>
    <field name="MsgType" number="35" enum="Heartbeat"><![CDATA[0]]></field>
    <field name="MsgSeqNum" number="34"><![CDATA[10]]></field>
    <field name="SenderCompID" number="49"><![CDATA[VCMVCON]]></field>
    <field name="SendingTime" number="52"><![CDATA[20110815-09:35:33.782]]></field>
    <field name="TargetCompID" number="56"><![CDATA[BLPVCON]]></field>
  </header>
  <body>
    <field name="TestReqID" number="112"><![CDATA[R.0001.0010.000A.093519]]></field>
  </body>
  <trailer>
  </trailer>
</message>

我接受了Martin Vsettika的Java答案,并用C#制作了自己的版本,关键的区别在于我选择使用StringBuilder而不是打印出每一行,并且我添加了一个扩展方法。我选择了一种类似于JSON的格式,使其生成实际有效的JSON根本不需要太多工作。

public static class MessageDecoder
{
    public static string Decode(this Message message, DataDictionary dataDictionary)
    {
        return DecodeMessage(message, dataDictionary);
    }
    public static string DecodeMessage(Message message, DataDictionary dataDictionary)
    {
        var messageStr = new StringBuilder("FIX {'n");
        var msgType = message.Header.GetString(Tags.MsgType);
        DecodeFieldMap("  ", dataDictionary, messageStr, msgType, message.Header);
        DecodeFieldMap("  ", dataDictionary, messageStr, msgType, message);
        DecodeFieldMap("  ", dataDictionary, messageStr, msgType, message.Trailer);
        messageStr.Append("}");
        return messageStr.ToString();
    }
    private static void DecodeFieldMap(string prefix, DataDictionary dd, StringBuilder str, string msgType, FieldMap fieldMap)
    {
        foreach (var kvp in fieldMap)
        {
            if (dd.IsGroup(msgType, kvp.Key)) continue;
            var field = dd.FieldsByTag[kvp.Key];
            var value = fieldMap.GetString(field.Tag);
            if (dd.FieldHasValue(field.Tag, value))
            {
                value = $"{field.EnumDict[value]} ({value})";
            }
            str.AppendFormat("{0}{1} = {2};'n", prefix, field.Name, value);
        }
        foreach (var groupTag in fieldMap.GetGroupTags())
        {
            var groupField = dd.FieldsByTag[groupTag];
            str.AppendFormat("{0}{1} (count {2}) {{'n", prefix, groupField.Name, fieldMap.GetInt(groupTag));
            for (var i = 1; i <= fieldMap.GetInt(groupTag); i++)
            {
                var group = fieldMap.GetGroup(i, groupTag);
                var groupPrefix = prefix + "  ";
                str.AppendFormat("{0}{{'n", groupPrefix);
                DecodeFieldMap(groupPrefix + "  ", dd, str, msgType, group);
                str.AppendFormat("{0}}},'n", groupPrefix);
            }
            str.Remove(str.Length - 2, 1); // Remove last ","
            str.AppendFormat("{0}}};'n", prefix);
        }
    }
}

输出如下(我修剪了一堆字段):

FIX {
  BeginString = FIX.4.4;
  BodyLength = 821;
  MsgSeqNum = 123;
  MsgType = EXECUTION_REPORT (8);
  PossDupFlag = YES (Y);
  SendingTime = 20200125-02:26:17.405;
  ExecID = 76615:3975388;
  ExecInst = WORK (2);
  LastMkt = XCEC;
  OrdStatus = EXPIRED (C);
  OrdType = LIMIT (2);
  OpenClose = OPEN (O);
  SecurityDesc = Copper;
  MaturityDate = 20200129;
  CustOrderCapacity = ALL_OTHER (4);
  ManualOrderIndicator = MANUAL (Y);
  NoPartyIDs (count 2) {
    {
      PartyIDSource = PROPRIETARY (D);
      PartyID = 0;
      PartyRole = ACCOUNT_TYPE (205);
    },
    {
      PartyIDSource = PROPRIETARY (D);
      PartyID = FIX_UAT;
      PartyRole = CLEARING_ACCOUNT (83);
    }
  };
  NoSecurityAltID (count 2) {
    {
      SecurityAltID = 1HGF0;
      SecurityAltIDSource = RIC_CODE (5);
    },
    {
      SecurityAltID = 170831;
      SecurityAltIDSource = EXCHANGE_SECURITY_ID (8);
    }
  };
  CheckSum = 077;
}

下面是Python中的一个答案。根据用户的需要,这里提供了三种解决方案。

  1. 标记的XML
  2. 字段列表(嵌入的组)
  3. 类似JSON的输出

在快速修复Python库中,FieldMap字段和组键迭代器不会公开。因此,方法是首先生成XML并在树上进行迭代。也无法访问DataDictionary的getFieldType方法,因此必须对字典进行预处理以存储字段类型,以便转换和处理组。

import quickfix as fix
import xml.etree.ElementTree as ET
from collections import OrderedDict
import json
string = "8=FIX.4.4'0019=247'00135=s'00134=5'00149=sender'00152=20060319-09:08:20.881'00156=target'00122=8'00140=2'00144=9'00148=ABC'00155=ABC'00160=20060319-09:08:19'001548=184214'001549=2'001550=0'001552=2'00154=1'001453=2'001448=8'001447=D'001452=4'001448=AAA35777'001447=D'001452=3'00138=9'00154=2'001453=2'001448=8'001447=D'001452=4'001448=aaa'001447=D'001452=3'00138=9'00110=056'001"
# Load data dictionary
data_dictionary_xml = "FIX44.xml"
data_dictionary = fix.DataDictionary(data_dictionary_xml)
fix.Message().InitializeXML(data_dictionary_xml)
# String as fix message according to dictionary
message = fix.Message(string, data_dictionary, True)
# Marked-up XML
xml = message.toXML()
print(xml)
def get_field_type_map(data_dictionary_xml):
  """Preprocess DataDictionary to get field types."""
  field_type_map = {}
  with open(data_dictionary_xml, "r") as f:
    xml = f.read()
    tree = ET.fromstring(xml)
    fields = tree.find("fields")
    for field in fields.iter("field"):
      field_type_map[field.attrib["number"]] = field.attrib["type"]
  return field_type_map
field_type_map = get_field_type_map(data_dictionary_xml)
INT_TYPES = ["INT", "LENGTH", "NUMINGROUP", "QTY", "SEQNUM"]
FLOAT_TYPES = ["FLOAT", "PERCENTAGE", "PRICE", "PRICEOFFSET"]
BOOL_TYPES = ["BOOLEAN"]
DATETIME_TYPES = ["LOCALMKTDATE", "MONTHYEAR", "UTCDATEONLY", "UTCTIMEONLY", "UTCTIMESTAMP"]
STRING_TYPES = ["AMT", "CHAR", "COUNTRY", "CURRENCY", "DATA", "EXCHANGE", "MULTIPLEVALUESTRING", "STRING"]
def field_map_to_list(field_map, field_type_map):
  fields = []
  field_iter = iter([el for el in field_map if el.tag == "field"])
  group_iter = iter([el for el in field_map if el.tag == "group"])
  for field in field_iter:
    # Extract raw value
    raw = field.text
    # Type the raw value
    field_type = field_type_map.get(field.attrib["number"])
    if field_type in INT_TYPES:
      value = int(raw)
    elif field_type in FLOAT_TYPES:
      value = float(raw)
    elif field_type in BOOL_TYPES:
      value = bool(int(raw))
    elif field_type in DATETIME_TYPES:
      value = str(raw)
    elif field_type in STRING_TYPES:
      value = str(raw)
    else:
      value = str(raw)
    # field.attrib should contain "name", "number", "enum"
    _field = {
      **field.attrib,
      "type": field_type,
      "raw": raw,
      "value": value,
    }
    # If NUMINGROUP type then iterate groups the number indicated
    # This assumes groups are in the same order as their field keys
    if field_type == "NUMINGROUP":
      groups = []
      for _ in range(value):
        group = next(group_iter)
        # Parse group as field map
        group_fields = field_map_to_list(group, field_type_map)
        groups.append(group_fields)
      _field["groups"] = groups
    fields.append(_field)
  return fields
def field_map_to_dict(field_map, field_type_map):
  fields = OrderedDict()
  field_iter = iter([el for el in field_map if el.tag == "field"])
  group_iter = iter([el for el in field_map if el.tag == "group"])
  for field in field_iter:
    # Define key
    # field.attrib should contain "name", "number", "enum"
    key = field.attrib.get("name") or field.attrib.get("number")
    # Extract raw value
    raw = field.text
    # Type the raw value
    field_type = field_type_map.get(field.attrib["number"])
    if field_type in INT_TYPES:
      value = int(raw)
    elif field_type in FLOAT_TYPES:
      value = float(raw)
    elif field_type in BOOL_TYPES:
      value = bool(int(raw))
    elif field_type in DATETIME_TYPES:
      value = str(raw)
    elif field_type in STRING_TYPES:
      value = str(raw)
    else:
      value = str(raw)
    # If NUMINGROUP type then iterate groups the number indicated
    # This assumes groups are in the same order as their field keys
    if field_type == "NUMINGROUP":
      groups = []
      for _ in range(value):
        group = next(group_iter)
        # Parse group as field map
        group_fields = field_map_to_dict(group, field_type_map)
        groups.append(group_fields)
      fields[key] = groups
    else:
      # Preference enum above value
      fields[key] = field.attrib.get("enum") or value
  return fields
def parse_message_xml(xml, field_type_map, as_dict=False):
  parsed = OrderedDict()
  tree = ET.fromstring(xml)
  for field_map in tree:
    if not as_dict:
      parsed[field_map.tag] = field_map_to_list(field_map, field_type_map)
    else:
      parsed[field_map.tag] = field_map_to_dict(field_map, field_type_map)
  return parsed
# List of fields (groups embedded)
parsed = parse_message_xml(xml, field_type_map, as_dict=False)
print(json.dumps(parsed, indent=True))
# JSON-like output
parsed = parse_message_xml(xml, field_type_map, as_dict=True)
print(json.dumps(parsed, indent=True))

输出:

<message>
  <header>
    <field name="BeginString" number="8"><![CDATA[FIX.4.4]]></field>
    <field name="BodyLength" number="9"><![CDATA[247]]></field>
    <field name="MsgType" number="35" enum="NewOrderCross"><![CDATA[s]]></field>
    <field name="MsgSeqNum" number="34"><![CDATA[5]]></field>
    <field name="SenderCompID" number="49"><![CDATA[sender]]></field>
    <field name="SendingTime" number="52"><![CDATA[20060319-09:08:20.881]]></field>
    <field name="TargetCompID" number="56"><![CDATA[target]]></field>
  </header>
  <body>
    <field name="SecurityIDSource" number="22" enum="EXCHANGE_SYMBOL"><![CDATA[8]]></field>
    <field name="OrdType" number="40" enum="LIMIT"><![CDATA[2]]></field>
    <field name="Price" number="44"><![CDATA[9]]></field>
    <field name="SecurityID" number="48"><![CDATA[ABC]]></field>
    <field name="Symbol" number="55"><![CDATA[ABC]]></field>
    <field name="TransactTime" number="60"><![CDATA[20060319-09:08:19]]></field>
    <field name="CrossID" number="548"><![CDATA[184214]]></field>
    <field name="CrossType" number="549" enum="CROSS_TRADE_WHICH_IS_EXECUTED_PARTIALLY_AND_THE_REST_IS_CANCELLED_ONE_SIDE_IS_FULLY_EXECUTED_THE_OTHER_SIDE_IS_PARTIALLY_EXECUTED_WITH_THE_REMAINDER_BEING_CANCELLED_THIS_IS_EQUIVALENT_TO_AN_IMMEDIATE_OR_CANCEL_ON_THE_OTHER_SIDE_NOTE_THE_CROSSPRIORITZATION"><![CDATA[2]]></field>
    <field name="CrossPrioritization" number="550" enum="NONE"><![CDATA[0]]></field>
    <field name="NoSides" number="552" enum="BOTH_SIDES"><![CDATA[2]]></field>
    <group>
      <field name="Side" number="54" enum="BUY"><![CDATA[1]]></field>
      <field name="NoPartyIDs" number="453"><![CDATA[2]]></field>
      <field name="OrderQty" number="38"><![CDATA[9]]></field>
      <group>
        <field name="PartyID" number="448"><![CDATA[8]]></field>
        <field name="PartyIDSource" number="447" enum="PROPRIETARY_CUSTOM_CODE"><![CDATA[D]]></field>
        <field name="PartyRole" number="452" enum="CLEARING_FIRM"><![CDATA[4]]></field>
      </group>
      <group>
        <field name="PartyID" number="448"><![CDATA[AAA35777]]></field>
        <field name="PartyIDSource" number="447" enum="PROPRIETARY_CUSTOM_CODE"><![CDATA[D]]></field>
        <field name="PartyRole" number="452" enum="CLIENT_ID"><![CDATA[3]]></field>
      </group>
    </group>
    <group>
      <field name="Side" number="54" enum="SELL"><![CDATA[2]]></field>
      <field name="NoPartyIDs" number="453"><![CDATA[2]]></field>
      <field name="OrderQty" number="38"><![CDATA[9]]></field>
      <group>
        <field name="PartyID" number="448"><![CDATA[8]]></field>
        <field name="PartyIDSource" number="447" enum="PROPRIETARY_CUSTOM_CODE"><![CDATA[D]]></field>
        <field name="PartyRole" number="452" enum="CLEARING_FIRM"><![CDATA[4]]></field>
      </group>
      <group>
        <field name="PartyID" number="448"><![CDATA[aaa]]></field>
        <field name="PartyIDSource" number="447" enum="PROPRIETARY_CUSTOM_CODE"><![CDATA[D]]></field>
        <field name="PartyRole" number="452" enum="CLIENT_ID"><![CDATA[3]]></field>
      </group>
    </group>
  </body>
  <trailer>
    <field name="CheckSum" number="10"><![CDATA[056]]></field>
  </trailer>
</message>
{
 "header": [
  {
   "number": "8",
   "name": "BeginString",
   "value": "FIX.4.4",
   "raw": "FIX.4.4",
   "type": "STRING"
  },
  {
   "number": "9",
   "name": "BodyLength",
   "value": 247,
   "raw": "247",
   "type": "LENGTH"
  },
  {
   "number": "35",
   "name": "MsgType",
   "enum": "NewOrderCross",
   "value": "s",
   "raw": "s",
   "type": "STRING"
  },
  {
   "number": "34",
   "name": "MsgSeqNum",
   "value": 5,
   "raw": "5",
   "type": "SEQNUM"
  },
  {
   "number": "49",
   "name": "SenderCompID",
   "value": "sender",
   "raw": "sender",
   "type": "STRING"
  },
  {
   "number": "52",
   "name": "SendingTime",
   "value": "20060319-09:08:20.881",
   "raw": "20060319-09:08:20.881",
   "type": "UTCTIMESTAMP"
  },
  {
   "number": "56",
   "name": "TargetCompID",
   "value": "target",
   "raw": "target",
   "type": "STRING"
  }
 ],
 "body": [
  {
   "number": "22",
   "name": "SecurityIDSource",
   "enum": "EXCHANGE_SYMBOL",
   "value": "8",
   "raw": "8",
   "type": "STRING"
  },
  {
   "number": "40",
   "name": "OrdType",
   "enum": "LIMIT",
   "value": "2",
   "raw": "2",
   "type": "CHAR"
  },
  {
   "number": "44",
   "name": "Price",
   "value": 9.0,
   "raw": "9",
   "type": "PRICE"
  },
  {
   "number": "48",
   "name": "SecurityID",
   "value": "ABC",
   "raw": "ABC",
   "type": "STRING"
  },
  {
   "number": "55",
   "name": "Symbol",
   "value": "ABC",
   "raw": "ABC",
   "type": "STRING"
  },
  {
   "number": "60",
   "name": "TransactTime",
   "value": "20060319-09:08:19",
   "raw": "20060319-09:08:19",
   "type": "UTCTIMESTAMP"
  },
  {
   "number": "548",
   "name": "CrossID",
   "value": "184214",
   "raw": "184214",
   "type": "STRING"
  },
  {
   "number": "549",
   "name": "CrossType",
   "enum": "CROSS_TRADE_WHICH_IS_EXECUTED_PARTIALLY_AND_THE_REST_IS_CANCELLED_ONE_SIDE_IS_FULLY_EXECUTED_THE_OTHER_SIDE_IS_PARTIALLY_EXECUTED_WITH_THE_REMAINDER_BEING_CANCELLED_THIS_IS_EQUIVALENT_TO_AN_IMMEDIATE_OR_CANCEL_ON_THE_OTHER_SIDE_NOTE_THE_CROSSPRIORITZATION",
   "value": 2,
   "raw": "2",
   "type": "INT"
  },
  {
   "number": "550",
   "name": "CrossPrioritization",
   "enum": "NONE",
   "value": 0,
   "raw": "0",
   "type": "INT"
  },
  {
   "number": "552",
   "name": "NoSides",
   "enum": "BOTH_SIDES",
   "value": 2,
   "raw": "2",
   "type": "NUMINGROUP",
   "groups": [
    [
     {
      "number": "54",
      "name": "Side",
      "enum": "BUY",
      "value": "1",
      "raw": "1",
      "type": "CHAR"
     },
     {
      "number": "453",
      "name": "NoPartyIDs",
      "groups": [
       [
        {
         "number": "448",
         "name": "PartyID",
         "value": "8",
         "raw": "8",
         "type": "STRING"
        },
        {
         "number": "447",
         "name": "PartyIDSource",
         "enum": "PROPRIETARY_CUSTOM_CODE",
         "value": "D",
         "raw": "D",
         "type": "CHAR"
        },
        {
         "number": "452",
         "name": "PartyRole",
         "enum": "CLEARING_FIRM",
         "value": 4,
         "raw": "4",
         "type": "INT"
        }
       ],
       [
        {
         "number": "448",
         "name": "PartyID",
         "value": "AAA35777",
         "raw": "AAA35777",
         "type": "STRING"
        },
        {
         "number": "447",
         "name": "PartyIDSource",
         "enum": "PROPRIETARY_CUSTOM_CODE",
         "value": "D",
         "raw": "D",
         "type": "CHAR"
        },
        {
         "number": "452",
         "name": "PartyRole",
         "enum": "CLIENT_ID",
         "value": 3,
         "raw": "3",
         "type": "INT"
        }
       ]
      ],
      "value": 2,
      "raw": "2",
      "type": "NUMINGROUP"
     },
     {
      "number": "38",
      "name": "OrderQty",
      "value": 9,
      "raw": "9",
      "type": "QTY"
     }
    ],
    [
     {
      "number": "54",
      "name": "Side",
      "enum": "SELL",
      "value": "2",
      "raw": "2",
      "type": "CHAR"
     },
     {
      "number": "453",
      "name": "NoPartyIDs",
      "groups": [
       [
        {
         "number": "448",
         "name": "PartyID",
         "value": "8",
         "raw": "8",
         "type": "STRING"
        },
        {
         "number": "447",
         "name": "PartyIDSource",
         "enum": "PROPRIETARY_CUSTOM_CODE",
         "value": "D",
         "raw": "D",
         "type": "CHAR"
        },
        {
         "number": "452",
         "name": "PartyRole",
         "enum": "CLEARING_FIRM",
         "value": 4,
         "raw": "4",
         "type": "INT"
        }
       ],
       [
        {
         "number": "448",
         "name": "PartyID",
         "value": "aaa",
         "raw": "aaa",
         "type": "STRING"
        },
        {
         "number": "447",
         "name": "PartyIDSource",
         "enum": "PROPRIETARY_CUSTOM_CODE",
         "value": "D",
         "raw": "D",
         "type": "CHAR"
        },
        {
         "number": "452",
         "name": "PartyRole",
         "enum": "CLIENT_ID",
         "value": 3,
         "raw": "3",
         "type": "INT"
        }
       ]
      ],
      "value": 2,
      "raw": "2",
      "type": "NUMINGROUP"
     },
     {
      "number": "38",
      "name": "OrderQty",
      "value": 9,
      "raw": "9",
      "type": "QTY"
     }
    ]
   ]
  }
 ],
 "trailer": [
  {
   "number": "10",
   "name": "CheckSum",
   "value": "056",
   "raw": "056",
   "type": "STRING"
  }
 ]
}
{
 "header": {
  "BeginString": "FIX.4.4",
  "BodyLength": 247,
  "MsgType": "NewOrderCross",
  "MsgSeqNum": 5,
  "SenderCompID": "sender",
  "SendingTime": "20060319-09:08:20.881",
  "TargetCompID": "target"
 },
 "body": {
  "SecurityIDSource": "EXCHANGE_SYMBOL",
  "OrdType": "LIMIT",
  "Price": 9.0,
  "SecurityID": "ABC",
  "Symbol": "ABC",
  "TransactTime": "20060319-09:08:19",
  "CrossID": "184214",
  "CrossType": "CROSS_TRADE_WHICH_IS_EXECUTED_PARTIALLY_AND_THE_REST_IS_CANCELLED_ONE_SIDE_IS_FULLY_EXECUTED_THE_OTHER_SIDE_IS_PARTIALLY_EXECUTED_WITH_THE_REMAINDER_BEING_CANCELLED_THIS_IS_EQUIVALENT_TO_AN_IMMEDIATE_OR_CANCEL_ON_THE_OTHER_SIDE_NOTE_THE_CROSSPRIORITZATION",
  "CrossPrioritization": "NONE",
  "NoSides": [
   {
    "Side": "BUY",
    "NoPartyIDs": [
     {
      "PartyID": "8",
      "PartyIDSource": "PROPRIETARY_CUSTOM_CODE",
      "PartyRole": "CLEARING_FIRM"
     },
     {
      "PartyID": "AAA35777",
      "PartyIDSource": "PROPRIETARY_CUSTOM_CODE",
      "PartyRole": "CLIENT_ID"
     }
    ],
    "OrderQty": 9
   },
   {
    "Side": "SELL",
    "NoPartyIDs": [
     {
      "PartyID": "8",
      "PartyIDSource": "PROPRIETARY_CUSTOM_CODE",
      "PartyRole": "CLEARING_FIRM"
     },
     {
      "PartyID": "aaa",
      "PartyIDSource": "PROPRIETARY_CUSTOM_CODE",
      "PartyRole": "CLIENT_ID"
     }
    ],
    "OrderQty": 9
   }
  ]
 },
 "trailer": {
  "CheckSum": "056"
 }
}