def on_timer_tick_received(self): """ This is the Twisted timer callback when the heartbeat interval has elapsed. """ if self.heartbeat <= 0: return now = datetime.datetime.now() # Have we received a testrequest response before we timed out? if (self._testrequest_id is not None and (now - self._testrequest_time).seconds > 2*self.heartbeat): raise FIXTimeoutError('testrequest response timeout') # if heartbeat seconds/2 have elapsed since the last time # a message was sent, send a heartbeat if (now - self._last_send_time).seconds > (self.heartbeat/2): self.transport.send_message( FIXMessage(source=[(35, FIX.HEARTBEAT), (49, self.link_config['sender_compid']), (56, self.link_config['target_compid'])])) # if heartbeat seconds + "some transmission time" have elapsed # since a message was received, send a TestRequest if (now - self._last_received_time).seconds > self.heartbeat: testrequest_id = "TR{0}".format(format_time(now)) self._testrequest_time = now self._testrequest_id = testrequest_id self.transport.send_message( FIXMessage(source=[(35, FIX.TEST_REQUEST), (112, testrequest_id), (49, self.link_config['sender_compid']), (56, self.link_config['target_compid'])]))
def test_to_binary(self): mess = FIXMessage() mess[8] = 'FIX.4.2' mess[9] = '---' mess[35] = 'A' mess[49] = 'SERVER' mess[56] = 'CLIENT' mess[34] = 177 mess[52] = '20090107-18:15:16' mess[98] = 0 mess[108] = 30 mess[10] = '---' data = mess.to_binary() # BodyLength(9) and Checksum(10) should be updated after # the to_binary() was called. self.assertEquals('65', mess[9]) self.assertEquals('062', mess[10]) self.assertEquals(to_fix('8=FIX.4.2', '9=65', '35=A', '49=SERVER', '56=CLIENT', '34=177', '52=20090107-18:15:16', '98=0', '108=30', '10=062'), data)
def test_nested_group_from_list(self): """ Call to_binary() on a nested grouped message from a list """ # As its difficult to test this, convert the (unordered) # dict into an OrderedDict() before inserting (sorting by tag). # This makes it easier to do the comparison. mess = FIXMessage(header_fields=[8, 9], source=to_ordered_dict([(8, 'FIX.4.2'), (100, [{ 101: 'abc', 102: 'def' }, { 101: 'ghi', 103: 'jkl' }, { 101: 'mno', 200: [{ 201: 'aaa', 202: 'bbb' }] }]), (99, 'X')])) self.assertEquals( to_fix('8=FIX.4.2', '9=73', '100=3', '101=abc', '102=def', '101=ghi', '103=jkl', '101=mno', '200=1', '201=aaa', '202=bbb', '99=X', '10=034'), mess.to_binary())
def test_header_fields(self): mess = FIXMessage() self.assertEquals(5, len(mess)) self.assertTrue(8 in mess) self.assertTrue(9 in mess) self.assertTrue(35 in mess) self.assertTrue(49 in mess) self.assertTrue(56 in mess) items = [(k, v) for k, v in mess.items()] # 8,9 are required to be first and second fields, respectively self.assertEquals(8, items[0][0]) self.assertEquals(9, items[1][0]) # Custom required fields mess = FIXMessage(header_fields=[1024, 8, 9]) self.assertTrue(8 in mess) self.assertTrue(9 in mess) self.assertTrue(35 not in mess) self.assertTrue(1024 in mess) items = [(k, v) for k, v in mess.items()] self.assertEquals(1024, items[0][0]) self.assertEquals(8, items[1][0]) self.assertEquals(9, items[2][0])
def test_nested_group_from_list(self): """ Call to_binary() on a nested grouped message from a list """ # As its difficult to test this, convert the (unordered) # dict into an OrderedDict() before inserting (sorting by tag). # This makes it easier to do the comparison. mess = FIXMessage( header_fields=[8, 9], source=to_ordered_dict([(8, 'FIX.4.2'), (100, [{101: 'abc', 102: 'def'}, {101: 'ghi', 103: 'jkl'}, {101: 'mno', 200: [ {201: 'aaa', 202: 'bbb'}]}]), (99, 'X')])) self.assertEquals(to_fix('8=FIX.4.2', '9=73', '100=3', '101=abc', '102=def', '101=ghi', '103=jkl', '101=mno', '200=1', '201=aaa', '202=bbb', '99=X', '10=034'), mess.to_binary())
def test_msg_type(self): mess = FIXMessage() self.assertEquals('', mess[35]) self.assertEquals('', mess.msg_type()) mess[35] = FIX.LOGON self.assertEquals(FIX.LOGON, mess[35]) self.assertEquals(FIX.LOGON, mess.msg_type())
def test_group_from_list(self): """ Call to_binary() on a grouped message from a list """ mess = FIXMessage(header_fields=[8, 9], source=[(8, 'FIX.4.2'), (9, '25'), (49, 'SERVER'), (56, 'CLIENT'), (99, 'X')]) self.assertEquals( to_fix('8=FIX.4.2', '9=25', '49=SERVER', '56=CLIENT', '99=X', '10=239'), mess.to_binary())
def test_to_binary_include(self): mess = FIXMessage() mess[8] = 'FIX.4.2' mess[9] = '---' mess[35] = 'A' mess[49] = 'SERVER' mess[56] = 'CLIENT' mess[177] = 'hello' data = mess.to_binary(include=[8, 9, 35, 177]) self.assertEquals( to_fix('8=FIX.4.2', '9=15', '35=A', '177=hello', '10=212'), data)
def test_to_binary_binarydata(self): mess = FIXMessage(header_fields=[8, 9]) mess[8] = 'FIX.4.2' mess[9] = '---' mess[110] = 2 mess[111] = '\x01\x02a\xbbbcd' data = mess.to_binary() self.assertEquals( to_fix('8=FIX.4.2', '9=18', '110=2', '111=\x01\x02a\xbbbcd', '10=026'), data)
def test_to_binary_exclude(self): mess = FIXMessage() mess[8] = 'FIX.4.2' mess[9] = '---' mess[35] = 'A' mess[49] = 'SERVER' mess[56] = 'CLIENT' mess[99] = 'X' mess[177] = 'hello' data = mess.to_binary(exclude=[35, 177]) self.assertEquals( to_fix('8=FIX.4.2', '9=25', '49=SERVER', '56=CLIENT', '99=X', '10=239'), data)
def test_group_from_list(self): """ Call to_binary() on a grouped message from a list """ mess = FIXMessage(header_fields=[8, 9], source=[(8, 'FIX.4.2'), (9, '25'), (49, 'SERVER'), (56, 'CLIENT'), (99, 'X')]) self.assertEquals(to_fix('8=FIX.4.2', '9=25', '49=SERVER', '56=CLIENT', '99=X', '10=239'), mess.to_binary())
def test_to_binary_binarydata(self): mess = FIXMessage(header_fields=[8, 9]) mess[8] = 'FIX.4.2' mess[9] = '---' mess[110] = 2 mess[111] = '\x01\x02a\xbbbcd' data = mess.to_binary() self.assertEquals(to_fix('8=FIX.4.2', '9=18', '110=2', '111=\x01\x02a\xbbbcd', '10=026'), data)
def logout_message(client): """ Generates a FIX logout message """ return FIXMessage( source=[(35, FIX.LOGOUT), (49, client.sender_compid), (56, client.target_compid)])
def test_to_binary_include(self): mess = FIXMessage() mess[8] = 'FIX.4.2' mess[9] = '---' mess[35] = 'A' mess[49] = 'SERVER' mess[56] = 'CLIENT' mess[177] = 'hello' data = mess.to_binary(include=[8, 9, 35, 177]) self.assertEquals(to_fix('8=FIX.4.2', '9=15', '35=A', '177=hello', '10=212'), data)
def new_order_message(client, **kwargs): """ Generates a new order message. Arguments: Returns: Raises: ValueError """ # Required parameters for sym in ['symbol', 'side', 'order_type']: if sym not in kwargs: raise ValueError("{0} must have a value".format(sym)) # optional parameters extra_tags = kwargs.get('extra_tags', []) return FIXMessage(source=[ (35, FIX.NEWORDER_SINGLE), (49, client.sender_compid), (56, client.target_compid), (11, client.get_next_orderid()), (21, '1'), # handlInst (55, kwargs['symbol']), (54, kwargs['side']), (60, format_time(datetime.datetime.now())), (40, kwargs['order_type']), ] + extra_tags)
def test_to_binary_group(self): """ Call to_binary() on a grouped message """ mess = FIXMessage(header_fields=[8, 9]) tags = collections.OrderedDict() mess[8] = 'FIX.4.2' mess[9] = '---' tags[110] = 2 tags[111] = 'abcd' mess[100] = [ tags, ] data = mess.to_binary() self.assertEquals( to_fix('8=FIX.4.2', '9=21', '100=1', '110=2', '111=abcd', '10=086'), data)
def execution_report(client, prev_message, **kwargs): """ Generates an execution report Arguments: client prev_message exec_trans_type: exec_type: ord_status: leaves_qty: cum_qty: avg_px: Returns: Raises: ValueError """ # Required parameters for sym in [ 'exec_trans_type', 'exec_type', 'ord_status', 'leaves_qty', 'cum_qty', 'avg_px' ]: if sym not in kwargs: raise ValueError("{0} must have a value".format(sym)) # optional parameters extra_tags = kwargs.get('extra_tags', []) exec_id = kwargs.get('exec_id', None) or client.get_next_orderid() message = FIXMessage(source=prev_message) message.update([ (35, FIX.EXECUTION_REPORT), (49, client.sender_compid), (56, client.target_compid), (11, prev_message[11]), (37, client.get_next_orderid()), (17, exec_id), (20, kwargs['exec_trans_type']), (150, kwargs['exec_type']), (39, kwargs['ord_status']), (151, kwargs['leaves_qty']), (14, kwargs['cum_qty']), (6, kwargs['avg_px']), ] + extra_tags) return message
def test_simple_send(self): """ test simple sending """ self.assertEquals(0, self.transport.message_sent_count) self.transport.send_message( FIXMessage(source=[ (8, 'FIX.4.2'), (35, 'A'), ])) self.assertEquals(1, self.transport.message_sent_count)
def test_to_binary_exclude(self): mess = FIXMessage() mess[8] = 'FIX.4.2' mess[9] = '---' mess[35] = 'A' mess[49] = 'SERVER' mess[56] = 'CLIENT' mess[99] = 'X' mess[177] = 'hello' data = mess.to_binary(exclude=[35, 177]) self.assertEquals(to_fix('8=FIX.4.2', '9=25', '49=SERVER', '56=CLIENT', '99=X', '10=239'), data)
def execution_report(client, prev_message, **kwargs): """ Generates an execution report Arguments: client prev_message exec_trans_type: exec_type: ord_status: leaves_qty: cum_qty: avg_px: Returns: Raises: ValueError """ # Required parameters for sym in ['exec_trans_type', 'exec_type', 'ord_status', 'leaves_qty', 'cum_qty', 'avg_px']: if sym not in kwargs: raise ValueError("{0} must have a value".format(sym)) # optional parameters extra_tags = kwargs.get('extra_tags', []) exec_id = kwargs.get('exec_id', None) or client.get_next_orderid() message = FIXMessage(source=prev_message) message.update([ (35, FIX.EXECUTION_REPORT), (49, client.sender_compid), (56, client.target_compid), (11, prev_message[11]), (37, client.get_next_orderid()), (17, exec_id), (20, kwargs['exec_trans_type']), (150, kwargs['exec_type']), (39, kwargs['ord_status']), (151, kwargs['leaves_qty']), (14, kwargs['cum_qty']), (6, kwargs['avg_px']), ] + extra_tags) return message
def test_to_binary_group(self): """ Call to_binary() on a grouped message """ mess = FIXMessage(header_fields=[8, 9]) tags = collections.OrderedDict() mess[8] = 'FIX.4.2' mess[9] = '---' tags[110] = 2 tags[111] = 'abcd' mess[100] = [tags, ] data = mess.to_binary() self.assertEquals(to_fix('8=FIX.4.2', '9=21', '100=1', '110=2', '111=abcd', '10=086'), data)
def test_send_seqno(self): """ test send sequence numbering """ seqno = self.protocol._send_seqno self.assertEquals(0, self.transport.message_sent_count) self.transport.send_message( FIXMessage(source=[ (8, 'FIX.4.2'), (35, 'A'), ])) self.assertEquals(1, self.transport.message_sent_count) self.assertEquals(seqno + 1, self.protocol._send_seqno)
def __init__(self, receiver, **kwargs): """ FIXParser initialization Args: receiver: This is an observer that receives the message and error notifications from the parser. There are two callbacks: on_message_received(message) on_error_received(error) header_fields: A list of header tags. This only affects the sending of the message. The order of the input fields is not validated. binary_fields: A list of tags indicating binary fields. Note that binary fields come in pairs. The first field contains the length of the data and the second field contains the actual data. The convention is that the IDs are sequential. For example, if the length field is tag 123, then tag 124 contains the data. Note that only the first field should be included in this list. group_fields: A dictionary of fields that belong to a group. The key is the group ID field that maps to a list of IDs that belong to the group. When specifying the field list for a group, include BOTH fields of a binary field. max_length: Maximum length of a FIX message supported (Default: 2048). debug: Set to True for more debugging output """ self.is_parsing = False self._receiver = receiver self._header_fields = kwargs.get('header_fields', [8, 9, 35, 49, 56]) self._binary_fields = kwargs.get('binary_fields', list()) self._group_fields = kwargs.get('group_fields', list()) self._max_length = kwargs.get('max_length', 2048) self._debug = kwargs.get('debug', False) self._buffer = b'' self.is_receiving_data = False self._message = FIXMessage(header_fields=self._header_fields) self._checksum = 0 self._message_length = 0 # used for binary field processing self._binary_length = -1 self._binary_tag = 0 # used for groups processing self._level_stack = list() self._logger = logging.getLogger(__name__)
def test_to_binary(self): mess = FIXMessage() mess[8] = 'FIX.4.2' mess[9] = '---' mess[35] = 'A' mess[49] = 'SERVER' mess[56] = 'CLIENT' mess[34] = 177 mess[52] = '20090107-18:15:16' mess[98] = 0 mess[108] = 30 mess[10] = '---' data = mess.to_binary() # BodyLength(9) and Checksum(10) should be updated after # the to_binary() was called. self.assertEquals('65', mess[9]) self.assertEquals('062', mess[10]) self.assertEquals( to_fix('8=FIX.4.2', '9=65', '35=A', '49=SERVER', '56=CLIENT', '34=177', '52=20090107-18:15:16', '98=0', '108=30', '10=062'), data)
def on_message_received(self, message, message_length, checksum): """ This is the callback from the parser when a message has been received. """ # verify required tags for tag in self.link_config['required_fields']: if tag not in message or len(str(message[tag])) == 0: raise FIXDataError(tag, 'missing field: id:{0}'.format(tag)) # verify the protocol version if 'protocol_version' in self.link_config: if self.link_config['protocol_version'] != message[8]: raise FIXDataError( 8, 'version mismatch: expect:{0} received:{1}'.format( self.link_config['protocol_version'], message[8] )) # verify the length and checksum if message_length != int(message[9]): raise FIXDataError( 9, 'length mismatch: expect:{0} received:{1}'.format( message_length, int(message[9]))) if checksum != int(message[10]): raise FIXDataError( 10, 'checksum mismatch: expect:{0} received:{1}'.format( checksum, message[10])) self._last_received_time = datetime.datetime.now() # Have we received our testrequest response? if (message.msg_type() == FIX.HEARTBEAT and message.get(112, '') == self._testrequest_id): self._testrequest_time = None self._testrequest_id = None # We have received a testrequest and need to send a response if message.msg_type() == FIX.TEST_REQUEST: self.transport.send_message( FIXMessage(source=[(35, FIX.HEARTBEAT), (112, message[112]), (49, self.link_config['sender_compid']), (56, self.link_config['target_compid'])])) if (not self.filter_heartbeat or (message.msg_type() not in {FIX.HEARTBEAT, FIX.TEST_REQUEST})): self.transport.on_message_received(message)
def test_send_with_missing_fields(self): """ send with missing fields """ self.assertEquals(0, self.transport.message_sent_count) message = FIXMessage(source=[ (8, 'FIX.4.2'), (35, 'A'), ]) self.assertTrue(34 not in message) self.assertTrue(52 not in message) self.transport.send_message(message) self.assertEquals(1, self.transport.message_sent_count) # check for seqno(34) and sendtime(52) self.assertTrue(34 in self.transport.last_message_sent) self.assertTrue(52 in self.transport.last_message_sent)
def test_verify(self): mess = FIXMessage() mess[8] = 'FIX.4.2' mess[9] = '---' mess[35] = 'A' mess[49] = 'SERVER' mess[56] = 'CLIENT' mess[99] = 'X' mess[177] = 'hello' self.assertTrue(mess.verify(fields=[(8, 'FIX.4.2'), (35, 'A')])) self.assertFalse(mess.verify(fields=[(8, 'NOFIX')])) self.assertTrue(mess.verify(exists=[8, 35, 177])) self.assertFalse(mess.verify(exists=[9, 8, 2000])) self.assertTrue(mess.verify(not_exists=[2000, 20001])) self.assertFalse(mess.verify(not_exists=[177])) self.assertTrue( mess.verify(fields=[(99, 'X')], exists=[56, 99, 177], not_exists=[2001, 2002, 2003]))
def reset(self, flush_buffer=False): """ Reset the protocol state so that it is ready to accept a new message. """ self.is_parsing = False self._message = FIXMessage(header_fields=self._header_fields) self._checksum = 0 self._message_length = 0 # used for binary field processing self._binary_length = -1 self._binary_tag = 0 # used for groups processing self._level_stack = list() if flush_buffer: self._buffer = b''
def test_verify(self): mess = FIXMessage() mess[8] = 'FIX.4.2' mess[9] = '---' mess[35] = 'A' mess[49] = 'SERVER' mess[56] = 'CLIENT' mess[99] = 'X' mess[177] = 'hello' self.assertTrue(mess.verify(fields=[(8, 'FIX.4.2'), (35, 'A')])) self.assertFalse(mess.verify(fields=[(8, 'NOFIX')])) self.assertTrue(mess.verify(exists=[8, 35, 177])) self.assertFalse(mess.verify(exists=[9, 8, 2000])) self.assertTrue(mess.verify(not_exists=[2000, 20001])) self.assertFalse(mess.verify(not_exists=[177])) self.assertTrue(mess.verify(fields=[(99, 'X')], exists=[56, 99, 177], not_exists=[2001, 2002, 2003]))
def logon_message(client): """ Generates a FIX logon message """ return FIXMessage(source=[(35, FIX.LOGON), ( 49, client.sender_compid), (56, client.target_compid), ( 98, 0), (108, client.protocol.heartbeat)])