2

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.

1 Answer 1

5

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))
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.