def fixmsg(*args, **kwargs) -> FixMessage: """ docstring """ returned = FixMessage(*args, **kwargs) returned.codec = codec return returned
def fixmsg(source: str) -> FixMessage: """ docstring """ msg = FixMessage(source) msg.codec = codec return msg
def fixmsg(source): """ Factory function that forces 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. """ # python 2 and 3 compatibility msg = FixMessage(source) msg.codec = CODEC return msg
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
def test_from_buffer(self): """ Tests creating a fix message via the "from_buffer" static function """ buff = b"9=10\x0135=D\x0134=3\x0110=154\x01" msg = FixMessage.from_buffer(buff, Codec()) assert {9, 35, 34, 10} == set(msg.keys()) assert b'10' == msg[9] assert b'D' == msg[35] assert b'3' == msg[34] assert b'154' == msg[10]
def fixmsg(source): msg = FixMessage(source) msg.codec = CODEC return msg
def test_change_spec(self, spec): spec.tags.add_tag(10001, "MyTagName") assert spec.tags.by_tag(10001).name == "MyTagName" assert spec.tags.by_name("MyTagName").tag == 10001 tag54 = spec.tags.by_tag(54) tag54.add_enum_value(name="SELLF", value="SF") assert tag54.enum_by_value("SF") == "SELLF" assert tag54.enum_by_name("SELLF") == "SF" with pytest.raises(TypeError): tag54.del_enum_value() with pytest.raises(ValueError): tag54.del_enum_value(name="SELLF", value="STF") with pytest.raises(KeyError): tag54.del_enum_value(name="SELLTOOF", value="SF") with pytest.raises(KeyError): tag54.del_enum_value(name="SELLTOOF") with pytest.raises(KeyError): tag54.del_enum_value(value="STF") tag54.del_enum_value(value="SF") with pytest.raises(KeyError): tag54.enum_by_name(name="SELLF") tag54.del_enum_value(name="BUY") with pytest.raises(KeyError): tag54.enum_by_value(value="1") tag54.add_enum_value(name="BUY", value="1") assert tag54.enum_by_value("1") == "BUY" 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='|') composition = [(spec.tags.by_tag(i), False) for i in (279, 269, 278, 55, 270, 15, 271, 346)] 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], (six.binary_type, six.text_type)) # 268 is not parsed as a repeating group assert before[270] == '1.37224' # 268 is not parsed as a repeating group, so 270 takes the second value assert isinstance(after[268], RepeatingGroup) with pytest.raises(KeyError): after[270] assert list(after.find_all(270)) == [[268, 0, 270], [268, 1, 270]]
def test_tag_tests(self): ''' test tag-related tests''' a = FixMessage() a.load_fix(self.fixmessage) assert a.tag_contains(7205, 'LSE') assert a.tag_contains(7205, 'S') assert not a.tag_contains(7205, 'F') assert a.tag_icontains(7205, 'lSe') assert a.tag_icontains(7205, 'e') assert not a.tag_icontains(7205, 'F') assert a.tag_exact(7205, b'LSE') assert not a.tag_exact(7205, b'LSEE') assert a.tag_iexact(7205, b'Lse') assert not a.tag_iexact(7205, b'LSsE') assert a.tag_in(7205, (b'LSE', b'CHIX')) assert not a.tag_in(7205, (b'BATE', b'CHIX')) assert a.tag_match_regex(7205, b'[A-Z]{3}') assert not a.tag_match_regex(7205, b'[0-9]{3}') assert not a.tag_exact(7807, b'[0-9]{3}') assert not a.tag_contains(7807, b'[0-9]{3}') assert not a.tag_match_regex(7807, b'[0-9]{3}')
def test_tag_inequalities(self): a = FixMessage() a.load_fix(self.fixmessage) a[31] = 1 a[32] = 2 a[3333] = 'a' a[34] = 'b' assert a.tag_lt(31, a[32]) assert not a.tag_gt(31, a[32]) assert a.tag_le(31, a[32]) assert not a.tag_ge(31, a[32]) assert a.tag_le(31, a[31]) assert a.tag_ge(31, a[31]) assert a.tag_lt(3333, a[34]) assert not a.tag_gt(3333, a[34]) assert a.tag_le(3333, a[34]) assert not a.tag_ge(3333, a[34]) assert a.tag_le(3333, a[3333]) assert a.tag_ge(3333, a[3333]) assert not a.tag_ge(7807, None) assert not a.tag_ge(3333, None) assert not a.tag_le(7807, None) assert not a.tag_le(3333, None) assert not a.tag_lt(7807, None) assert not a.tag_lt(3333, None) assert not a.tag_gt(7807, None) assert not a.tag_gt(3333, None)
def main(spec_filename): """ Illustration of the usage of FixMessage. :param spec_filename: a specification file from Quickfix.org :type spec_filename: ``str`` """ 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) 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
def fixmsg(*args, **kwargs) -> FixMessage: """ docstring """ returned = FixMessage(*args, **kwargs) returned.codec = codec return returned data = ( "8=FIX.4.2, 9=97, 35=6, 45=6, 49=ABC, 56=CAB, 34=14, 52=20201130-10:27:30" "23=115685,28=N,55=BLAH,54=2,44=2200.75,27=S,25=H,10=248") fixMsg = FixMessage().load_fix(data, separator=",") message = fixmsg().load_fix(data, separator=",") message.codec = codec print("Message Type {} ({})".format(fixMsg[35], spec.msg_types[fixMsg[35]].name)) check_tags = (55, 44, 27) for element, required in spec.msg_types[fixMsg[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(