Pyfixmsg¶
pyfixmsg
is a library for parsing, manipulating and serialising FIX
messages, primarily geared towards testing. See the example.
Objectives¶
- provide a rich API to compare and manipulate messages.
- (mostly) Message type agnostic,
- (mostly) value types agnostic
- pluggable : load specification XML files, custom specifications or build your own Specification class for repeating groups definitions and message types, define your own codec for custom serialisation or deserialisation quirks.
Dependencies¶
- None for the core library.
- Optional lxml for faster parsing of xml specification files.
- Optional pytest to run the tests.
- Optional spec files from quickfix to get started with standard FIX specifications.
Core classes¶
FixMessage
. Inherits fromdict
. The workhorse class. By default comes with a codec that will parse standard-lookingFIX
, but without support repeating groups.RepeatingGroup
. defines repeating groups of a FixMessage.Codec
defines how to parse a buffer into a FixMessage, and how to serialise it backFixSpec
defines theFIX
specification to follow. Only required for support of repeating group. Defined from Quickfix’s spec XML files.
How to run the tests¶
py.test --spec=/var/tmp/FIX50.xml
will launch the tests against the spec file in /var/tmp. You will need to load
the spec files from quickfix to get the tests to work.
The spec files are not included in this distribution.
Notes¶
This is only a FIX message library. It doesn’t include a FIX session management system or an order management core, or anything similar. It is purely message parsing-manipulation-serialisation. It is however easy to integrate into an order management or a exchange/broker simulator, etc.
API Documentation¶
FixMessage¶
-
class
pyfixmsg.fixmessage.
FixMessage
(*args, **kwargs)[source]¶ Bases:
pyfixmsg.fixmessage.FixFragment
Simple dictionary-like object, for use with FIX raw messages. Note that the tags are converted (when possible) to integers, and that the values are kept as strings. The default separator is
;
, but can be specified. Check the definition ofload_fix()
for details.- Example:
>>> fix = FixMessage() >>> fix.load_fix(line) >>> #print(fix) {6: '0', 8: 'FIX.4.2', 10: '100', 10481: 'A', 14: '0', 15: 'EUR', 10641: 'blabla', 18: '1', 21: '2', 22: '5', 151: '1', 11951: 'HOOF7M0f4BGJ0rkaNTkkeAA', ....
FixMessage also have a
time
attribute, adirection
attribute (inbound : 0, outbound : 1) and arecipient
which is rather where it’s been received from or sent to. FixMessages sort by default on time, and will be considered equal if the dictionary values are the same AND the time is the same.This FixMessage is eager : it will parse the whole fix and store it locally. It is significantly faster in most usage patterns that we observed.
useful shortcut methods :
fix.tag_exact(tag, value) fix.tag_iexact(tag, value) fix.tag_contains(tag, value) fix.tag_icontains(tag, value) fix.tag_match_regex(tag, value)
Note : the tag_* methods don’t support repeating groups
-
FragmentType
¶ alias of
FixFragment
-
__init__
(*args, **kwargs)[source]¶ The constructor uses the
dict()
signature unmodified. You can set the following manually or through a factory function:self.process
an opaque value (to store the process that received or processed the message, defaults to empty string).self.separator
the default separator to use when parsing messages. Defaults to';'
self.time
the time the message has been created or received. Defaults todatetime.utcnow()
self.recipient
opaque value (to store for whom the message was intended)self.direction
Whether the message was received (0
), sent (1
) or unknown (None
)self.typed_values
Whether the values in the message are typed. Defaults toFalse
self.raw_message
If constructed by class methodfrom_buffer
, keep the original formatself.codec
DefaultCodec
to use to parse message. Defaults to a naive codec that doesn’t support repeating groups
-
apply
(update)[source]¶ equivalent to
update()
but if any value in the update dictionary is None and the tag is present in the current message, that tag is removed. Note: naive about repeating groupsParameters: update ( dict
) – map of values to update the state with.
-
fix
¶ Legacy compatibility, will be removed shortly
-
classmethod
from_buffer
(msg_buffer, fix_codec)[source]¶ Create a FixMessage from a buffer and a codec
Parameters: - msg_buffer (
str
) – a buffer as a string - fix_codec (
Codec
) – an object with static encode() and decode() calls
Returns: a FixMessage object
Return type: FixMessage
- msg_buffer (
-
load_fix
(string, process=None, separator=';')[source]¶ Parses a FIX message from a string using default codecs and separators.
Parameters: - string (
bytes
) – the string containing the FIX message to be parsed - process (
unicode
) – Optional originator of the FIX message - separator (
unicode
) – Character delimiting “tag=val” pairs. Optional. By default this is a ‘;’ character. Specifypyfixmsg.SEPARATOR
when parsing standard FIX.
Returns: A parsed fix message
Return type: FixMessage
- string (
-
output_fix
(separator=';', calc_checksum=True, remove_length=False)[source]¶ ouputs itself as a vanilla FIX message. This forces the output to String fix but tries to reuse the spec from the current codec
-
set_or_delete
(tag, value)[source]¶ Sets the tag if value is neither None or the empty string. Deletes the tag otherwise. Only works on top-level tags (not inside repeating groups)
-
tag_contains
(tag, value, case_insensitive=False)[source]¶ Returns True if self[tag] contains value. Returns False otherwise, or if the tag doesnt exist This is a string string comparison
-
tag_exact
(tag, value, case_insensitive=False)[source]¶ Returns True if self[tag] has the exact value. Returns False if the tag doesnt exist or is not exact
-
tag_exact_dict
(dictionary)[source]¶ check that all the keys and values of the passed dict are present and identical in the fixmsg
-
tag_ge
(tag, value)[source]¶ Test tag is greater or equal to value. Uses decimal comparison if possible. Returns False if tag absent
-
tag_gt
(tag, value)[source]¶ Test tag is greater than value. Uses decimal comparison if possible. Returns False if tag absent
-
tag_iexact
(tag, value)[source]¶ Returns True if self[tag] has the exact value (case insensitive). Returns False if the tag doesnt exist or is not exact
-
tag_in
(tag, values)[source]¶ returns True if self[tag] is in values, false otherwise or if the tag doesnt exist
-
tag_le
(tag, value)[source]¶ Test tag is smaller or equal value. Uses decimal comparison if possible. Returns False if tag absent
-
tag_lt
(tag, value)[source]¶ Test tag is smaller than value. Uses decimal comparison if possible. Returns False if tag absent
-
tag_match_regex
(tag, regex)[source]¶ returns True of self[tag] matches regex, false otherwise or if the tag doesnt exist
Note: this property is there to replace a self reference that existed before.
Deprecated.
-
class
pyfixmsg.fixmessage.
FixFragment
(*args, **kwargs)[source]¶ Bases:
dict
Type designed to hold a collection of fix tags and values. This type is used directly for the contents of repeating groups. Whole fix messages are parsed from their wire representation to instances of the
FixMessage
type which inherits from this type.Returns a list of all the tag keys in this message, including flattened tags that are only present in repeating groups. The same tag will not appear twice in the list.
Returns: A list of tag keys (usually strings or ints) Return type: list
-
anywhere
(tag)[source]¶ returns true if the tag is in the message or anywhere inside any contained repeating group
-
find_all
(tag)[source]¶ Generator. Find all instances of the tag in the message or inside repeating groups and returns the path to them one at a time.
- Example, navigate all paths for a given tag:
>>> for path in msg.find_all(self, tag): ... # path here is a list of ints or str keys ... path_msg = msg ... for key in path: ... path_msg = path_msg[key] ... # [...] do something at each level in the path ... path_msg[tag] = # [...] do something with the last level of the path
Returns: a generator of paths where each path is a list of string or integer indices into the message Return type: Generator of list
ofint
orstr
Repeating groups¶
-
class
pyfixmsg.
RepeatingGroup
(*args, **kwargs)[source]¶ Bases:
list
Implementation of repeating groups for pyfixmsg.FixMessage. The repeating group will look like {opening_tag:[FixMessage,FixMessage]} in the fix message a repeating group behaves like a list. You can add two repeating groups, or append a FixMessage to one.
-
__init__
(*args, **kwargs)[source]¶ Maintains
list
’s signature unchanged.Sets * self.number_tag (the tag that contains the number of elements in the group) * self.first_tag (the first repeated tag) * self.standard (reserved)
Returns a list of all the tag keys in any member of this repeating group, The same tag will not appear twice in the generated sequence. The count tag for the repeating group is not included, it is considered as part of the parent message. Order is not guaranteed. @return: A list of tag keys (usually strings or ints) @rtype: C{list}
-
classmethod
create_repeating_group
(tag, standard=True, first_tag=None)[source]¶ creates a group with member. Can’t use __init__ as it would mean overriding the list __init__ which sounds dangerous
-
entry_tag
¶ returns the entry tag for the group and its value as a tuple
-
find_all
(tag)[source]¶ Generator. Find all instances of the tag in the message or inside repeating groups and returns the path to them one at a time.
- Example, navigate all paths for a given tag:
>>> for path in msg.find_all(self, tag): ... # path here is a list of ints or str keys ... path_msg = msg ... for key in path: ... path_msg = path_msg[key] ... # [...] do something at each level in the path ... path_msg[tag] = # [...] do something with the last level of the path
@return: a generator of paths where each path is a list of string or integer indices into the message @rtype: Generator of C{list} of C{int} or C{str}
-
-
class
pyfixmsg.
RepeatingGroupFactory
(tag, standard=True, first_tag=None)[source]¶ Bases:
object
An easy way to create a repeating group for a given tag, without having to define all the tags yourself, takes the standard ones
Codec¶
-
class
pyfixmsg.codecs.stringfix.
Codec
(spec=None, no_groups=False, fragment_class=<type 'dict'>, decode_as=None, decode_all_as_347=False)[source]¶ Bases:
object
FIX codec. Initialise with a
FixSpec
to support repeating groups.This class is used to transform the serialised FIX message into an instance of
fragment_class
, defaultdict
Tags are assumed to be all of typeint
, repeating groups are lists offragment_class
Values can either bytes or unicode or a mix thereof, depending on the constructor arguments.
-
__init__
(spec=None, no_groups=False, fragment_class=<type 'dict'>, decode_as=None, decode_all_as_347=False)[source]¶ Parameters: - spec – the
FixSpec
instance to use to parse messages. If spec is not defined repeating groups will not be parsed correctly, and the logic to handle encoded tags will not be functional. - no_groups – set to
True
to ignore repeating groups - fragment_class – Which dict-like object to return when parsing messages. Also sets the type of members of repeating groups
- decode_as – what encoding to decode all tags. Defaults to None, which returns the raw byte strings. setting to a non-None value makes both non-numerical tags and values to be unicode, using this value for decode.
- decode_all_as_347 – whether to trust tag 347 to decode all other tags or only the Encoded* ones.
If set to False, use 347 normally for Encoded* tags, respect
decode_as
for all other tags. If 347 is not present on the message, the values are left encoded.
- spec – the
-
parse
(buff, delimiter='=', separator='\x01')[source]¶ Parse a FIX message. The FIX message is expected to be a bytestring and the output is a dictionary-like object which type is determined by the
fragment_class
constructor argument and which keys areint
and valuesunicode
. Note that if there is a non-int tag in the message, this will be stored as a key in the original format (i.e. bytestring)Parameters: - buff (
bytestr
orunicode
) – Buffer to parse - delimiter (
unicode
) – A character that separate key and values inside the FIX message. Generally ‘=’. Note the type: because of the way the buffer is tokenised, this needs to be unicode (orstr
in python 2.7*). - separator (
unicode
) – A character that separate key+value pairs inside the FIX message. Generally ‘’. See type observations above.
- buff (
-
FixTag¶
-
class
pyfixmsg.reference.
FixTag
(name, tag, tagtype=None, values=())[source]¶ Bases:
object
Fix tag representation. A fix tag has name, tag (number), type and valid values (enum)
-
__init__
(name, tag, tagtype=None, values=())[source]¶ Parameters: - name (
str
) – Tag name - tag (
int
) – Tag number - tagtype (
str
) – Type as in quickfix’s XML reference documents - values (
tuple((str, str))
with the first element of each tuple being the value of the enum, the second the name of the value.) – The valid enum values
- name (
-
FixSpec¶
-
class
pyfixmsg.reference.
FixSpec
(xml_file, eager=False)[source]¶ Bases:
object
A python-friendly representation of a FIX spec. This class is built from an XML file sourced from Quickfix (http://www.quickfixengine.org/).
It contains the Message Types supported by the specification, as a map (FixSpec.msg_types) of message type value (‘D’, ‘6’, ‘8’, etc..) to MessageType class, and all the fields supported in the spec as a TagReference instance (FixSpec.tags) which can be accessed by tag name or number.
Examples¶
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 | #!/bin/env python
"""
Examples illustrating the usage of FixMessage and associated objects
"""
from __future__ import print_function
import os
import sys
import decimal
import argparse
from copy import copy
from random import randint
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
from pyfixmsg import RepeatingGroup
from pyfixmsg.fixmessage import FixMessage, FixFragment
from pyfixmsg.reference import FixSpec, FixTag
from pyfixmsg.codecs.stringfix import Codec
if sys.version_info.major >= 3:
unicode = str # pylint: disable=C0103,W0622
def main(spec_filename):
"""
Illustration of the usage of FixMessage.
:param spec_filename: a specification file from Quickfix.org
:type spec_filename: ``str``
"""
# For this example you need FIX 4.2 specification
# refer to: path_to_quickfix/FIX42.xml (MS: fsf/quickfix/1.14.3.1ms)
spec = FixSpec(spec_filename)
codec = Codec(spec=spec, # The codec will use the given spec to find repeating groups
fragment_class=FixFragment) # The codec will produce FixFragment objects inside repeating groups
# (default is dict). This is required for the find_all() and anywhere()
# methods to work. It would fail with AttributeError otherwise.
def fixmsg(*args, **kwargs):
"""
Factory function. This allows us to keep the dictionary __init__
arguments unchanged and force the codec to our given spec and avoid
passing codec to serialisation and parsing methods.
The codec defaults to a reasonable parser but without repeating groups.
An alternative method is to use the ``to_wire`` and ``from_wire`` methods
to serialise and parse messages and pass the codec explicitly.
"""
returned = FixMessage(*args, **kwargs)
returned.codec = codec
return returned
########################
# Vanilla tag/value
#########################
data = (b'8=FIX.4.2|9=97|35=6|49=ABC|56=CAB|34=14|52=20100204-09:18:42|'
b'23=115685|28=N|55=BLAH|54=2|44=2200.75|27=S|25=H|10=248|')
msg = fixmsg().load_fix(data, separator='|')
# The message object is a dictionary, with integer keys
# and string values. No validation is performed.
# The spec contains the information, however so it can be retrieved.
print("Message Type {} ({})".format(msg[35],
spec.msg_types[msg[35]].name))
print("Price {} (note type: {}, spec defined type {})".format(
msg[44], type(msg[44]), spec.tags.by_tag(44).type
))
check_tags = (55, 44, 27)
for element, required in spec.msg_types[msg[35]].composition:
if isinstance(element, FixTag) and element.tag in check_tags:
if required:
print("{} is required on this message type".format(element.name))
else:
print("{} is not required on this message type".format(element.name))
print("Spec also allows looking up enums: {} is {}".format(msg[54],
spec.tags.by_tag(54).enum_by_value(msg[54])))
# Although the values are stored as string there are rich operators provided that
# allow to somewhat abstract the types
print("exact comparison with decimal:", msg.tag_exact(44, decimal.Decimal("2200.75")))
print("exact comparing with int:", msg.tag_exact(54, 2))
print("lower than with float:", msg.tag_lt(44, 2500.0))
print("greater than with float:", msg.tag_gt(23, 110000.1))
print("contains, case sensitive and insensitive:", msg.tag_contains(55, "MI"), msg.tag_icontains(55, "blah"))
# Tags manipulation is as for a dictionary
msg[56] = "ABC.1" # There is no enforcement of what tags are used for, so changing 56 is no worry for the lib
msg.update({55: 'ABC123.1', 28: 'M'})
# note regex matching
print("tag 56 changed", msg.tag_match_regex(56, r"..M\.N"))
print("Tag 55 and 28 changed to {} and {}".format(msg[55], msg[28]))
# There are additional tools for updating the messages' content though
none_or_one = randint(0, 1) or None
msg.set_or_delete(27, none_or_one)
msg.apply({25: None, 26: 2})
if none_or_one is None:
print("Got None, the tag is deleted")
assert 27 not in msg
else:
print("Got None, the tag is maintained")
assert msg[27] == 1
assert 25 not in msg
assert msg.tag_exact(26, '2')
########################
# copying messages
#########################
# Because messages maintain a reference to the codec and the spec
# a deepcopy() of messages is extremely inefficient. It is a lot faster
# to serialise-deserialise to copy a message, which is what copy() does.
# Alternatively do a shallow copy through dict constructor.
new_msg = msg.copy()
assert new_msg is not msg
msg.set_len_and_chksum()
# Note that this reverts the type of values that were set manually
assert new_msg[26] != msg[26]
print("tag 26 before {}, after {}".format(type(msg[26]), type(new_msg[26])))
assert all(new_msg.tag_exact(t, msg[t]) for t in msg)
msg = fixmsg().load_fix(data, separator='|')
# if no types have changed, the copy() method returns a message that is identical
# to the original one
new_msg = copy(msg)
assert new_msg == msg
# note you can also use the method copy()
new_msg = msg.copy()
assert new_msg == msg
assert new_msg is not msg
# and codec is not copied
assert new_msg.codec is msg.codec
# note that msg equality is not true when using dict constructor
new_msg = FixMessage(msg)
assert new_msg != msg
# That's because codec, time, recipient and direction are not preserved
# when using this technique, only tags are
assert dict(new_msg) == dict(msg)
########################
# Repeating Groups
#########################
# Repeating groups are indexed by count tag (268 below)
# and stored as a list of FixMessages (technically FixFragments, but close enough)
data = (b'8=FIX.4.2|9=196|35=X|49=A|56=B|34=12|52=20100318-03:21:11.364'
b'|262=A|268=2|279=0|269=0|278=BID|55=EUR/USD|270=1.37215'
b'|15=EUR|271=2500000|346=1|279=0|269=1|278=OFFER|55=EUR/USD'
b'|270=1.37224|15=EUR|271=2503200|346=1|10=171|')
msg = fixmsg().load_fix(data, separator='|')
print("Message Type {} ({})".format(msg[35],
spec.msg_types[msg[35]].name))
print("Repeating group {} looks like {}".format(spec.tags.by_tag(268).name,
msg[268]))
print("Accessing repeating groups at depth: tag 278 "
"in the second member of the group is '{}'".format(msg[268][1][278]))
# finding out if a tag is present can be done at depth
print("Utility functions like anywhere() allow you to find out "
"if a tag is present at depth, or find the path to it: {} is present : {}; "
"it can be found at the following paths {}".format(278, msg.anywhere(278),
list(msg.find_all(278))))
########################
# Customise the spec
#########################
# Sometimes it may be desirable to tweak the spec a bit, especially add a custom tag
# or a custom repeating group.
# Create tag
spec.tags.add_tag(10001, "MyTagName")
assert spec.tags.by_tag(10001).name == "MyTagName"
assert spec.tags.by_name("MyTagName").tag == 10001
# Change enum values
spec.tags.by_tag(54).add_enum_value(name="SELLFAST", value="SF")
assert spec.tags.by_tag(54).enum_by_value("SF") == "SELLFAST"
# Add repeating Group
# let's add repeating group 268 to msg type 'D'
# for illustration purposes, we'll create the group from scratch
data = (b'8=FIX.4.2|9=196|35=D|49=A|56=B|34=12|52=20100318-03:21:11.364'
b'|262=A|268=2|279=0|269=0|278=BID|55=EUR/USD|270=1.37215'
b'|15=EUR|271=2500000|346=1|279=0|269=1|278=OFFER|55=EUR/USD'
b'|270=1.37224|15=EUR|271=2503200|346=1|10=171|')
before = FixMessage()
before.codec = Codec(spec=spec)
before.load_fix(data, separator='|')
# The composition is a iterable of pairs (FixTag, bool), with the bool indicating whether
# the tag is required or not (although it's not enforced in the codec at this time
composition = [(spec.tags.by_tag(i), False) for i in (279, 269, 278, 55, 270, 15, 271, 346)]
# add_group takes a FixTag and the composition
spec.msg_types['D'].add_group(spec.tags.by_tag(268), composition)
after = FixMessage()
after.codec = Codec(spec=spec, fragment_class=FixFragment)
after.load_fix(data, separator='|')
assert isinstance(before[268], (str, unicode)) # 268 is not parsed as a repeating group
assert before[270] == '1.37224' # 268 is not parsed as a repeating group, so 271 takes the second value
assert isinstance(after[268], RepeatingGroup) # After the change, 268 becomes a repeating group
assert list(after.find_all(270)) == [[268, 0, 270], [268, 1, 270]] # and both 270 can be found
if __name__ == "__main__":
PARSER = argparse.ArgumentParser()
PARSER.add_argument("spec_xml")
main(PARSER.parse_args().spec_xml)
|