1

I am having a complete mare with quickfix/Python. I'm not new to FIX, nor am I new to quickfix/J, but the Python version is not playing ball.

I have a custom spec which effectively redefines how a MassQuote, 35=i, message looks.

A couple of SO posts suggest (1, 2) that I can create some custom groups that will let me work with custom messages, I'm unable to get this to work.

Is it because I have not loaded in my custom XML before I call fix.Message()? My assumption is that I ought be able to do all this 'standalone', without being inside a session. Currently I'm testing this offline.

import quickfix as fix

class NoQuoteSets(fix.Group): # 296
    def __init__(self):
        order = fix.IntArray(3)
        order[0] = 302  # QuoteSetID
        order[1] = 295  # NoQuoteEntries
        order[2] = 0
        super().__init__(296, 302, order)

class NoQuoteEntries(fix.Group): # 295
    def __init__(self):
        order = fix.IntArray(7)
        order[0] = 299  # QuoteEntryID
        order[1] = 106  # Issuer
        order[2] = 134  # BidSize
        order[3] = 135  # OfferSize
        order[4] = 188  # BidSpotRate
        order[5] = 190  # OfferSpotRate
        order[6] = 0
        super().__init__(295, 299, order)

# example message taken from the spec, broken out for easier reading
MASS_QUOTE = "8=FIX.4.4|9=264|35=i|34=1113826|49=XCT|52=20171106-14:57:08.528|56=Q001|" + \
    "296=5|" + \
    "302=32|295=1|" + \
        "299=0|106=1|134=1250000|188=1.80699|190=1.80709|" + \
    "302=35|295=1|" + \
        "299=0|106=1|190=148.051|" + \
    "302=40|295=1|" + \
        "299=0|106=1|190=1.30712|" + \
    "302=37|295=1|" + \
        "299=0|106=1|190=1.95713|" + \
    "302=34|295=1|" + \
        "299=0|135=500000|10=167|"

m = fix.Message(MASS_QUOTE.replace("|", "\x01"))

m.getField(fix.NoQuoteSets().getTag())   # tag is in the message
m.groupCount(fix.NoQuoteSets().getTag()) # no groups though...

m.getField(fix.NoQuoteEntries().getTag())
m.groupCount(fix.NoQuoteEntries().getTag())

m.hasGroup(NoQuoteSets())
m.hasGroup(NoQuoteEntries())

Result of the get/group functions:

>>> m.getField(fix.NoQuoteSets().getTag())      # tag is in the message
'5'
>>> m.groupCount(fix.NoQuoteSets().getTag())    # no groups though...
0
>>> m.getField(fix.NoQuoteEntries().getTag())   # I guess it just gets the first occurrence?
'1'
>>> m.groupCount(fix.NoQuoteEntries().getTag()) # no groups found here either...
0
>>> m.hasGroup(NoQuoteSets())                   # can't find my custom class either
False
>>> m.hasGroup(NoQuoteEntries())                # sad times
False

Snippet of the xml where I (re)define what a MassQuote message is:

<message name='MassQuote' msgtype='i' msgcat='app'>
 <field name='QuoteID' required='N' />
 <group name='NoQuoteSets' required='Y'>
  <field name='QuoteSetID' required='N' />
  <group name='NoQuoteEntries' required='Y'>
   <field name='QuoteEntryID' required='N' />
   <field name='Issuer' required='N' />
   <field name='BidSize' required='N' />
   <field name='OfferSize' required='N' />
   <field name='BidSpotRate' required='N' />
   <field name='OfferSpotRate' required='N' />
  </group>
 </group>
</message>

If I try to validate message m against my xml file, it throws an unhelpful exception:

>>> d = fix.DataDictionary()
>>> d.readFromURL("/path/to/foo.xml")
>>> d.validate(m)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/mark/.local/lib/python3.8/site-packages/quickfix.py", line 39970, in validate
    return _quickfix.DataDictionary_validate(self, *args)
quickfix.FIXException: Tag not defined for this message type

I can't see any fields in the message that aren't in the xml definition, but maybe I need to sleep on it.

Other notes, I'm using Python 3.8 and installed QuickFIX (1.15.1) via pip.

mkst
  • 565
  • 6
  • 16

1 Answers1

3

Figured it out.

First, define a new class that extends quickfix.Message

import quickfix as fix

class MassQuote(fix.Message):
    """Custom flavour of a MassQuote"""
    def __init__(self, *args):
        super().__init__(*args)
        self.getHeader().setField(fix.MsgType('i'))
    class NoQuoteSets(fix.Group): # 296
        def __init__(self):
            order = fix.IntArray(3)
            order[0] = 302  # QuoteSetID
            order[1] = 295  # NoQuoteEntries
            order[2] = 0
            super().__init__(296, 302, order)
        class NoQuoteEntries(fix.Group): # 295
            def __init__(self):
                order = fix.IntArray(7)
                order[0] = 299  # QuoteEntryID
                order[1] = 106  # Issuer
                order[2] = 134  # BidSize
                order[3] = 135  # OfferSize
                order[4] = 188  # BidSpotRate
                order[5] = 190  # OfferSpotRate
                order[6] = 0
                super().__init__(295, 299, order)

Then create a quickfix.DataDictionary and read in the XML spec:

data_dictionary = fix.DataDictionary()
data_dictionary.readFromURL("/path/to/foo.xml")

Now you can instantiate a the custom FIX message via constructor Message(fix_string, data_dictionary):

MASS_QUOTE = "8=FIX.4.4|9=264|35=i|34=1113826|49=XCT|52=20171106-14:57:08.528|56=Q001|" + \
    "296=5|" + \
    "302=32|295=1|" + \
        "299=0|106=1|134=1250000|188=1.80699|190=1.80709|" + \
    "302=35|295=1|" + \
        "299=0|106=1|190=148.051|" + \
    "302=40|295=1|" + \
        "299=0|106=1|190=1.30712|" + \
    "302=37|295=1|" + \
        "299=0|106=1|190=1.95713|" + \
    "302=34|295=1|" + \
        "299=0|135=500000|10=167|"

mass_quote = MassQuote(MASS_QUOTE.replace("|", "\x01"), data_dictionary)

You can then sanity check:

data_dictionary.validate(mass_quote)

In order to extract the groups you need to create a 'group' object and work with it:

mass_quote.getField(fix.NoQuoteSets().getTag())    # '5' => sanity check
mass_quote.groupCount(fix.NoQuoteSets().getTag())  # 5   => nice!

# define the object to hold the group
quote_set = MassQuote.NoQuoteSets()
# copy 1st group into object
mass_quote.getGroup(1, quote_set)                  # Swig Object of type 'Group *' at 0x7f747f9652a0>
# now we can access fields of this group
quote_set_id = quote_set.getField(302)             # '32'

If we want to access nested groups, then we call:

# define object to hold nested entry
entry = MassQuote.NoQuoteSets.NoQuoteEntries() 
# copy 1st group into object
quote_set.getGroup(1, entry)
# now we can access fields of this group
entry_id = int(entry.getField(299))
mkst
  • 565
  • 6
  • 16