├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── client_example.py ├── examine_store.py ├── protocol_demo.py └── server_example.py ├── pyfix ├── FIX44.json ├── FIX44 │ ├── __init__.py │ ├── fixtags.py │ ├── messages.py │ └── msgtype.py ├── __init__.py ├── client_connection.py ├── codec.py ├── connection.py ├── engine.py ├── event.py ├── journaler.py ├── message.py ├── server_connection.py ├── session.py └── transaction.py ├── setup.py └── tests ├── __init__.py ├── codec_tests.py ├── event_tests.py ├── journaler_tests.py └── message_tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | *.seq 59 | .idea 60 | *.store 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '3.4' 4 | - nightly 5 | script: python -m unittest tests/*.py 6 | notifications: 7 | flowdock: 8 | secure: A8zGWUnV46Uc6v55z9JvMUUQ/H/931FPFQf98e91kvK2509OrbG23sKEhz2m4U6Ayz4gNu8Ows6aNan/OpKPQNzzD5zE/GKwIkoVjoZn3pnn6txdL0JtTo4rXWSrSY4pKzMIh+uBhdqijMLVPjWYs6EHuZcnTZawAje3w+QXeuhaO6X9NIz71S3QZXOCSA7I0iDZtuuq51rGo4ts5stR7a9ouKxSo/xMKj5Ng9oTYZjRtGC/Op9IV6OrfE9xsajZEWF3Q5cMXRhBUl9hJbtKY7HikmfI0cY+iXipK1N0030A7lBk/naoNkCvhxWhTOMnGBd0Vq1RxTbD7fUrN6MjKK4m3kGqkUxEWOGQn2ACQGvgzkYgH3ruu+nMWO7RT+SMhVeYTngSZ/++QtCy0KYMuaAmtuOLnUQPAviYVpfs3dzooOQunqOWEOrAsmHAZiOeXsSGh2IoKW+41z+NXGbF3tmwtewEo2ZzHHriqaNxE47QgfdrHE5vUptJVDcckaNvmL/YBht9gkYsARdRVVVIYNmMIaNFZBrmsDAAdypB/HIcI8opQtBAaFrVfBoLb4lQo2cWOG3hM8sIPevxga+1WfG7ZuXUSltHmti673GnO0zX0Qr8nHTWy3U5SCYHmzlrhICa1g9m6iB7MUQQBeLLTeUj1DwLiTr4QQd+x4/7kXs= 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyFIX [![Build Status](https://travis-ci.org/wannabegeek/PyFIX.svg?branch=master)](https://travis-ci.org/wannabegeek/PyFIX) 2 | Open Source implementation of a FIX (Financial Information eXchange) Engine implemented in Python 3 | 4 | See here [http://fixprotocol.org/] for more information on what FIX is. 5 | 6 | ## Installation 7 | 8 | This package requires Python 3 to run. 9 | 10 | Install in the normal python way 11 | ``` 12 | python setup.py install 13 | ``` 14 | and it should install with no errors 15 | 16 | ## Usage 17 | Using the module should be simple. There is an examples directory, which is the probably best place to start. 18 | 19 | ### Session Setup 20 | Create an `EventManager` object instance, this handles all the timers and socket data required by the FIX engine, however, you can add to events to the manager if required. 21 | 22 | Either you can create a `FIXClient` or a `FIXServer`. The Client initiates the connection and also initaiates the Logon sequence, a Server would sit there waiting for inbound connections, and expect a Logon message to be sent. 23 | ```python 24 | self.eventMgr = EventManager() 25 | self.client = FIXClient(self.eventMgr, "pyfix.FIX44", "TARGET", "SENDER") 26 | 27 | # tell the client to start the connection sequence 28 | self.client.start('localhost', int("9898")) 29 | 30 | # enter the event loop waiting for something to happen 31 | while True: 32 | self.eventMgr.waitForEvent() 33 | 34 | ``` 35 | 36 | The argument "pyfix.FIX44" specified the module which is used as the protocol, this is dynamically loaded, to you can create and specify your own if required. 37 | 38 | If you want to do something useful, other than just watching the session level bits work, you'll probably want to register for connection status changes (you'll need to do this be fore starting the event loop); 39 | 40 | ```python 41 | self.client.addConnectionListener(self.onConnect, ConnectionState.CONNECTED) 42 | self.client.addConnectionListener(self.onDisconnect, ConnectionState.DISCONNECTED) 43 | ``` 44 | 45 | The implementatino of thouse methods would be something like this; 46 | ```python 47 | def onConnect(self, session): 48 | logging.info("Established connection to %s" % (session.address(), )) 49 | session.addMessageHandler(self.onLogin, MessageDirection.INBOUND, self.client.protocol.msgtype.LOGON) 50 | 51 | def onDisconnect(self, session): 52 | logging.info("%s has disconnected" % (session.address(), )) 53 | session.removeMsgHandler(self.onLogin, MessageDirection.INBOUND, self.client.protocol.msgtype.LOGON) 54 | ``` 55 | in the code above, we are registering to be called back whenever we receive (`MessageDirection.INBOUND`) a logon request `MsgType[35]=A` on that session. 56 | 57 | That is pretty much it for the session setup. 58 | 59 | ### Message construction and sending 60 | 61 | Constructing a message is simple, and is just a matter of adding the fields you require. 62 | The session level tags will be added when the message is encoded by the codec. Setting any of the following session tags will result in the tag being duplicated in the message 63 | - BeginString 64 | - BodyLength 65 | - MsgType 66 | - MsgSeqNo 67 | - SendingTime 68 | - SenderCompID 69 | - TargetCompID 70 | - CheckSum 71 | 72 | Example of building a simple message 73 | 74 | ```python 75 | def sendOrder(self, connectionHandler): 76 | self.clOrdID = self.clOrdID + 1 77 | # get the codec we are currently using for this session 78 | codec = connectionHandler.codec 79 | 80 | # create a new message 81 | msg = FIXMessage(codec.protocol.msgtype.NEWORDERSINGLE) 82 | 83 | # ...and add some data to it 84 | msg.setField(codec.protocol.fixtags.Price, random.random() * 1000) 85 | msg.setField(codec.protocol.fixtags.OrderQty, int(random.random() * 10000)) 86 | msg.setField(codec.protocol.fixtags.Symbol, "VOD.L") 87 | msg.setField(codec.protocol.fixtags.SecurityID, "GB00BH4HKS39") 88 | msg.setField(codec.protocol.fixtags.SecurityIDSource, "4") 89 | msg.setField(codec.protocol.fixtags.Symbol, "VOD.L") 90 | msg.setField(codec.protocol.fixtags.Account, "TEST") 91 | msg.setField(codec.protocol.fixtags.HandlInst, "1") 92 | msg.setField(codec.protocol.fixtags.ExDestination, "XLON") 93 | msg.setField(codec.protocol.fixtags.Side, int(random.random() * 2)) 94 | msg.setField(codec.protocol.fixtags.ClOrdID, str(self.clOrdID)) 95 | msg.setField(codec.protocol.fixtags.Currency, "GBP") 96 | 97 | # send the message on the session 98 | connectionHandler.sendMsg(codec.pack(msg, connectionHandler.session)) 99 | ``` 100 | 101 | A message (which is a subclass of `FIXContext`) can also hold instances of `FIXContext`, these will be treated as repeating groups. For example 102 | 103 | ``` 104 | msg = FIXMessage(codec.protocol.msgtype.NEWORDERSINGLE) 105 | msg.setField(codec.protocol.fixtags.Symbol, "VOD.L") 106 | msg.setField(codec.protocol.fixtags.SecurityID, "GB00BH4HKS39") 107 | msg.setField(codec.protocol.fixtags.SecurityIDSource, "4") 108 | 109 | rptgrp1 = FIXContext() 110 | rptgrp1.setField(codec.protocol.fixtags.PartyID, "It's Me") 111 | rptgrp1.setField(codec.protocol.fixtags.PartyIDSource, "1") 112 | rptgrp1.setField(codec.protocol.fixtags.PartyRole, "2") 113 | 114 | msg.addRepeatingGroup(codec.protocol.fixtags.NoPartyIDs, rptgrp1) 115 | 116 | rptgrp2 = FIXContext() 117 | rptgrp2.setField(codec.protocol.fixtags.PartyID, "Someone Else") 118 | rptgrp2.setField(codec.protocol.fixtags.PartyIDSource, "2") 119 | rptgrp2.setField(codec.protocol.fixtags.PartyRole, "8") 120 | msg.addRepeatingGroup(codec.protocol.fixtags.NoPartyIDs, rptgrp2) 121 | 122 | ``` 123 | This will result in a message like the following 124 | ``` 125 | 8=FIX.4.4|9=144|35=D|49=sender|56=target|34=1|52=20150619-11:08:54.000|55=VOD.L|48=GB00BH4HKS39|22=4|453=2|448=It's Me|447=1|452=2|448=Someone Else|447=2|452=8|10=073| 126 | ``` 127 | 128 | To send the message you need a handle on the session you want to use, this is provided to you in the callback methods. e.g. in the code above we registered for Logon callbacks using 129 | ``` 130 | session.addMessageHandler(self.onLogin, MessageDirection.INBOUND, self.client.protocol.msgtype.LOGON) 131 | ``` 132 | 133 | the signature for the callback is something like; 134 | ``` 135 | def onLogin(self, connectionHandler, msg): 136 | logging.info("Logged in") 137 | ``` 138 | -------------------------------------------------------------------------------- /examples/client_example.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import logging 3 | import random 4 | from pyfix.connection import ConnectionState, MessageDirection 5 | from pyfix.client_connection import FIXClient 6 | from pyfix.engine import FIXEngine 7 | from pyfix.message import FIXMessage 8 | from pyfix.event import TimerEventRegistration 9 | 10 | class Side(Enum): 11 | buy = 1 12 | sell = 2 13 | 14 | class Client(FIXEngine): 15 | def __init__(self): 16 | FIXEngine.__init__(self, "client_example.store") 17 | self.clOrdID = 0 18 | self.msgGenerator = None 19 | 20 | # create a FIX Client using the FIX 4.4 standard 21 | self.client = FIXClient(self, "pyfix.FIX44", "TARGET", "SENDER") 22 | 23 | # we register some listeners since we want to know when the connection goes up or down 24 | self.client.addConnectionListener(self.onConnect, ConnectionState.CONNECTED) 25 | self.client.addConnectionListener(self.onDisconnect, ConnectionState.DISCONNECTED) 26 | 27 | # start our event listener indefinitely 28 | self.client.start('localhost', int("9898")) 29 | while True: 30 | self.eventManager.waitForEventWithTimeout(10.0) 31 | 32 | # some clean up before we shut down 33 | self.client.removeConnectionListener(self.onConnect, ConnectionState.CONNECTED) 34 | self.client.removeConnectionListener(self.onConnect, ConnectionState.DISCONNECTED) 35 | 36 | def onConnect(self, session): 37 | logging.info("Established connection to %s" % (session.address(), )) 38 | # register to receive message notifications on the session which has just been created 39 | session.addMessageHandler(self.onLogin, MessageDirection.INBOUND, self.client.protocol.msgtype.LOGON) 40 | session.addMessageHandler(self.onExecutionReport, MessageDirection.INBOUND, self.client.protocol.msgtype.EXECUTIONREPORT) 41 | 42 | def onDisconnect(self, session): 43 | logging.info("%s has disconnected" % (session.address(), )) 44 | # we need to clean up our handlers, since this session is disconnected now 45 | session.removeMessageHandler(self.onLogin, MessageDirection.INBOUND, self.client.protocol.msgtype.LOGON) 46 | session.removeMessageHandler(self.onExecutionReport, MessageDirection.INBOUND, self.client.protocol.msgtype.EXECUTIONREPORT) 47 | if self.msgGenerator: 48 | self.eventManager.unregisterHandler(self.msgGenerator) 49 | 50 | def sendOrder(self, connectionHandler): 51 | self.clOrdID = self.clOrdID + 1 52 | codec = connectionHandler.codec 53 | msg = FIXMessage(codec.protocol.msgtype.NEWORDERSINGLE) 54 | msg.setField(codec.protocol.fixtags.Price, "%0.2f" % (random.random() * 2 + 10)) 55 | msg.setField(codec.protocol.fixtags.OrderQty, int(random.random() * 100)) 56 | msg.setField(codec.protocol.fixtags.Symbol, "VOD.L") 57 | msg.setField(codec.protocol.fixtags.SecurityID, "GB00BH4HKS39") 58 | msg.setField(codec.protocol.fixtags.SecurityIDSource, "4") 59 | msg.setField(codec.protocol.fixtags.Account, "TEST") 60 | msg.setField(codec.protocol.fixtags.HandlInst, "1") 61 | msg.setField(codec.protocol.fixtags.ExDestination, "XLON") 62 | msg.setField(codec.protocol.fixtags.Side, int(random.random() * 2) + 1) 63 | msg.setField(codec.protocol.fixtags.ClOrdID, str(self.clOrdID)) 64 | msg.setField(codec.protocol.fixtags.Currency, "GBP") 65 | 66 | connectionHandler.sendMsg(msg) 67 | side = Side(int(msg.getField(codec.protocol.fixtags.Side))) 68 | logging.debug("---> [%s] %s: %s %s %s@%s" % (codec.protocol.msgtype.msgTypeToName(msg.msgType), msg.getField(codec.protocol.fixtags.ClOrdID), msg.getField(codec.protocol.fixtags.Symbol), side.name, msg.getField(codec.protocol.fixtags.OrderQty), msg.getField(codec.protocol.fixtags.Price))) 69 | 70 | 71 | def onLogin(self, connectionHandler, msg): 72 | logging.info("Logged in") 73 | 74 | # lets do something like send and order every 3 seconds 75 | self.msgGenerator = TimerEventRegistration(lambda type, closure: self.sendOrder(closure), 0.5, connectionHandler) 76 | self.eventManager.registerHandler(self.msgGenerator) 77 | 78 | def onExecutionReport(self, connectionHandler, msg): 79 | codec = connectionHandler.codec 80 | if codec.protocol.fixtags.ExecType in msg: 81 | if msg.getField(codec.protocol.fixtags.ExecType) == "0": 82 | side = Side(int(msg.getField(codec.protocol.fixtags.Side))) 83 | logging.debug("<--- [%s] %s: %s %s %s@%s" % (codec.protocol.msgtype.msgTypeToName(msg.getField(codec.protocol.fixtags.MsgType)), msg.getField(codec.protocol.fixtags.ClOrdID), msg.getField(codec.protocol.fixtags.Symbol), side.name, msg.getField(codec.protocol.fixtags.OrderQty), msg.getField(codec.protocol.fixtags.Price))) 84 | elif msg.getField(codec.protocol.fixtags.ExecType) == "4": 85 | reason = "Unknown" if codec.protocol.fixtags.Text not in msg else msg.getField(codec.protocol.fixtags.Text) 86 | logging.info("Order Rejected '%s'" % (reason,)) 87 | else: 88 | logging.error("Received execution report without ExecType") 89 | 90 | def main(): 91 | logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG) 92 | client = Client() 93 | logging.info("All done... shutting down") 94 | 95 | if __name__ == '__main__': 96 | main() 97 | -------------------------------------------------------------------------------- /examples/examine_store.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | from pyfix.journaler import Journaler 4 | from pyfix.message import MessageDirection 5 | 6 | 7 | def main(): 8 | logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) 9 | 10 | parser = argparse.ArgumentParser(description='Examine the contents of the store file.') 11 | group = parser.add_mutually_exclusive_group() 12 | group.add_argument('-l', '--list', dest='list', action='store_true', help='list available streams') 13 | group.add_argument('-s', '--sessions', dest='sessions', nargs='+', action='store', metavar=('s1', 's2'), help='session to examine') 14 | parser.add_argument('filename', action='store', help='filename of the store file') 15 | parser.add_argument('-d', '--direction', dest='direction', choices=['in', 'out', 'both'], action='store', default="both", help='filename of the store file') 16 | 17 | args = parser.parse_args() 18 | 19 | journal = Journaler(args.filename) 20 | 21 | if args.list is True: 22 | # list all sessions 23 | row_format ="{:^15}|" * 3 24 | separator_format ="{:->15}|" * 3 25 | for session in journal.sessions(): 26 | print(row_format.format("Session Id", "TargetCompId", "SenderCompId")) 27 | print(separator_format.format("", "", "")) 28 | print(row_format.format(session.key, session.targetCompId, session.senderCompId)) 29 | print(separator_format.format("", "", "")) 30 | else: 31 | # list all messages in that stream 32 | direction = None if args.direction == 'both' else MessageDirection.INBOUND if args.direction == "in" else MessageDirection.OUTBOUND 33 | for (seqNo, msg, msgDirection, session) in journal.getAllMsgs(args.sessions, direction): 34 | d = "---->" if msgDirection == MessageDirection.OUTBOUND.value else "<----" 35 | print("{:>3} {:^5} [{:>5}] {}".format(session, d, seqNo, msg)) 36 | 37 | if __name__ == '__main__': 38 | main() 39 | -------------------------------------------------------------------------------- /examples/protocol_demo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | class tag(object): 4 | def __init__(self, name, value, values): 5 | self.name = name 6 | self.value = value 7 | for k, v in values: 8 | setattr(self, k, v) 9 | 10 | def __eq__(self, other): 11 | if type(other) == tag: 12 | return other.name == self.name and other.value == self.value 13 | else: 14 | return self.value == other 15 | 16 | def __str__(self): 17 | return self.value 18 | 19 | def __hash__(self): 20 | return hash(self.name + self.value) 21 | 22 | __repr__ = __str__ 23 | 24 | class msgtype(object): 25 | pass 26 | 27 | class tags(object): 28 | def __init__(self): 29 | self.tags = {} 30 | self._addTag("MsgType", "35", [("ExecutionReport", "8"), ("NewOrderSingle", "D")]) 31 | 32 | def _addTag(self, key, value, values): 33 | t = tag(key, value, values) 34 | setattr(self, key, t) 35 | self.tags[t] = key 36 | 37 | class Demo(object): 38 | def __init__(self): 39 | t =tags() 40 | logging.debug("%s = %s" % (t.tags[t.MsgType], t.MsgType)) 41 | logging.debug("ExecutionReport: %s" % (t.MsgType.ExecutionReport,)) 42 | 43 | def main(): 44 | logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG) 45 | server = Demo() 46 | logging.info("All done... shutting down") 47 | 48 | if __name__ == '__main__': 49 | main() 50 | -------------------------------------------------------------------------------- /examples/server_example.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import logging 3 | from pyfix.connection import ConnectionState, MessageDirection 4 | from pyfix.engine import FIXEngine 5 | from pyfix.message import FIXMessage 6 | from pyfix.server_connection import FIXServer 7 | 8 | class Side(Enum): 9 | buy = 1 10 | sell = 2 11 | 12 | class Server(FIXEngine): 13 | def __init__(self): 14 | FIXEngine.__init__(self, "server_example.store") 15 | # create a FIX Server using the FIX 4.4 standard 16 | self.server = FIXServer(self, "pyfix.FIX44") 17 | 18 | # we register some listeners since we want to know when the connection goes up or down 19 | self.server.addConnectionListener(self.onConnect, ConnectionState.CONNECTED) 20 | self.server.addConnectionListener(self.onDisconnect, ConnectionState.DISCONNECTED) 21 | 22 | # start our event listener indefinitely 23 | self.server.start('', int("9898")) 24 | while True: 25 | self.eventManager.waitForEventWithTimeout(10.0) 26 | 27 | # some clean up before we shut down 28 | self.server.removeConnectionListener(self.onConnect, ConnectionState.CONNECTED) 29 | self.server.removeConnectionListener(self.onConnect, ConnectionState.DISCONNECTED) 30 | 31 | def validateSession(self, targetCompId, senderCompId): 32 | logging.info("Received login request for %s / %s" % (senderCompId, targetCompId)) 33 | return True 34 | 35 | def onConnect(self, session): 36 | logging.info("Accepted new connection from %s" % (session.address(), )) 37 | # register to receive message notifications on the session which has just been created 38 | session.addMessageHandler(self.onLogin, MessageDirection.OUTBOUND, self.server.protocol.msgtype.LOGON) 39 | session.addMessageHandler(self.onNewOrder, MessageDirection.INBOUND, self.server.protocol.msgtype.NEWORDERSINGLE) 40 | 41 | def onDisconnect(self, session): 42 | logging.info("%s has disconnected" % (session.address(), )) 43 | # we need to clean up our handlers, since this session is disconnected now 44 | session.removeMessageHandler(self.onLogin, MessageDirection.OUTBOUND, self.server.protocol.msgtype.LOGON) 45 | session.removeMessageHandler(self.onNewOrder, MessageDirection.INBOUND, self.server.protocol.msgtype.NEWORDERSINGLE) 46 | 47 | def onLogin(self, connectionHandler, msg): 48 | codec = connectionHandler.codec 49 | logging.info("[" + msg[codec.protocol.fixtags.SenderCompID] + "] <---- " + codec.protocol.msgtype.msgTypeToName(msg[codec.protocol.fixtags.MsgType])) 50 | 51 | def onNewOrder(self, connectionHandler, request): 52 | codec = connectionHandler.codec 53 | try: 54 | side = Side(int(request.getField(codec.protocol.fixtags.Side))) 55 | logging.debug("<--- [%s] %s: %s %s %s@%s" % (codec.protocol.msgtype.msgTypeToName(request.getField(codec.protocol.fixtags.MsgType)), request.getField(codec.protocol.fixtags.ClOrdID), request.getField(codec.protocol.fixtags.Symbol), side.name, request.getField(codec.protocol.fixtags.OrderQty), request.getField(codec.protocol.fixtags.Price))) 56 | 57 | # respond with an ExecutionReport Ack 58 | msg = FIXMessage(codec.protocol.msgtype.EXECUTIONREPORT) 59 | msg.setField(codec.protocol.fixtags.Price, request.getField(codec.protocol.fixtags.Price)) 60 | msg.setField(codec.protocol.fixtags.OrderQty, request.getField(codec.protocol.fixtags.OrderQty)) 61 | msg.setField(codec.protocol.fixtags.Symbol, request.getField(codec.protocol.fixtags.OrderQty)) 62 | msg.setField(codec.protocol.fixtags.SecurityID, "GB00BH4HKS39") 63 | msg.setField(codec.protocol.fixtags.SecurityIDSource, "4") 64 | msg.setField(codec.protocol.fixtags.Symbol, request.getField(codec.protocol.fixtags.Symbol)) 65 | msg.setField(codec.protocol.fixtags.Account, request.getField(codec.protocol.fixtags.Account)) 66 | msg.setField(codec.protocol.fixtags.HandlInst, "1") 67 | msg.setField(codec.protocol.fixtags.OrdStatus, "0") 68 | msg.setField(codec.protocol.fixtags.ExecType, "0") 69 | msg.setField(codec.protocol.fixtags.LeavesQty, "0") 70 | msg.setField(codec.protocol.fixtags.Side, request.getField(codec.protocol.fixtags.Side)) 71 | msg.setField(codec.protocol.fixtags.ClOrdID, request.getField(codec.protocol.fixtags.ClOrdID)) 72 | msg.setField(codec.protocol.fixtags.Currency, request.getField(codec.protocol.fixtags.Currency)) 73 | 74 | connectionHandler.sendMsg(msg) 75 | logging.debug("---> [%s] %s: %s %s %s@%s" % (codec.protocol.msgtype.msgTypeToName(msg.msgType), msg.getField(codec.protocol.fixtags.ClOrdID), request.getField(codec.protocol.fixtags.Symbol), side.name, request.getField(codec.protocol.fixtags.OrderQty), request.getField(codec.protocol.fixtags.Price))) 76 | except Exception as e: 77 | msg = FIXMessage(codec.protocol.msgtype.EXECUTIONREPORT) 78 | msg.setField(codec.protocol.fixtags.OrdStatus, "4") 79 | msg.setField(codec.protocol.fixtags.ExecType, "4") 80 | msg.setField(codec.protocol.fixtags.LeavesQty, "0") 81 | msg.setField(codec.protocol.fixtags.Text, str(e)) 82 | msg.setField(codec.protocol.fixtags.ClOrdID, request.getField(codec.protocol.fixtags.ClOrdID)) 83 | 84 | connectionHandler.sendMsg(msg) 85 | 86 | 87 | def main(): 88 | logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG) 89 | server = Server() 90 | logging.info("All done... shutting down") 91 | 92 | if __name__ == '__main__': 93 | main() 94 | -------------------------------------------------------------------------------- /pyfix/FIX44.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "Account": "1", 4 | "AdvId": "2", 5 | "AdvRefID": "3", 6 | "AdvSide": "4", 7 | "AdvTransType": "5", 8 | "AvgPx": "6", 9 | "BeginSeqNo": "7", 10 | "BeginString": "8", 11 | "BodyLength": "9", 12 | "CheckSum": "10", 13 | "ClOrdID": "11", 14 | "Commission": "12", 15 | "CommType": "13", 16 | "CumQty": "14", 17 | "Currency": "15", 18 | "EndSeqNo": "16", 19 | "ExecID": "17", 20 | "ExecInst": "18", 21 | "ExecRefID": "19", 22 | "ExecTransType": "20", 23 | "HandlInst": "21", 24 | "SecurityIDSource": "22", 25 | "IOIID": "23", 26 | "IOIOthSvc": "24", 27 | "IOIQltyInd": "25", 28 | "IOIRefID": "26", 29 | "IOIQty": "27", 30 | "IOITransType": "28", 31 | "LastCapacity": "29", 32 | "LastMkt": "30", 33 | "LastPx": "31", 34 | "LastQty": "32", 35 | "NoLinesOfText": "33", 36 | "MsgSeqNum": "34", 37 | "MsgType": { 38 | "tag": "35", 39 | "values": { 40 | "HEARTBEAT": "0", 41 | "TESTREQUEST": "1", 42 | "RESENDREQUEST": "2", 43 | "REJECT": "3", 44 | "SEQUENCERESET": "4", 45 | "LOGOUT": "5", 46 | "IOI": "6", 47 | "ADVERTISEMENT": "7", 48 | "EXECUTIONREPORT": "8", 49 | "ORDERCANCELREJECT": "9", 50 | "QUOTESTATUSREQUEST": "a", 51 | "LOGON": "A", 52 | "DERIVATIVESECURITYLIST": "AA", 53 | "NEWORDERMULTILEG": "AB", 54 | "MULTILEGORDERCANCELREPLACE": "AC", 55 | "TRADECAPTUREREPORTREQUEST": "AD", 56 | "TRADECAPTUREREPORT": "AE", 57 | "ORDERMASSSTATUSREQUEST": "AF", 58 | "QUOTEREQUESTREJECT": "AG", 59 | "RFQREQUEST": "AH", 60 | "QUOTESTATUSREPORT": "AI", 61 | "QUOTERESPONSE": "AJ", 62 | "CONFIRMATION": "AK", 63 | "POSITIONMAINTENANCEREQUEST": "AL", 64 | "POSITIONMAINTENANCEREPORT": "AM", 65 | "REQUESTFORPOSITIONS": "AN", 66 | "REQUESTFORPOSITIONSACK": "AO", 67 | "POSITIONREPORT": "AP", 68 | "TRADECAPTUREREPORTREQUESTACK": "AQ", 69 | "TRADECAPTUREREPORTACK": "AR", 70 | "ALLOCATIONREPORT": "AS", 71 | "ALLOCATIONREPORTACK": "AT", 72 | "CONFIRMATIONACK": "AU", 73 | "SETTLEMENTINSTRUCTIONREQUEST": "AV", 74 | "ASSIGNMENTREPORT": "AW", 75 | "COLLATERALREQUEST": "AX", 76 | "COLLATERALASSIGNMENT": "AY", 77 | "COLLATERALRESPONSE": "AZ", 78 | "NEWS": "B", 79 | "MASSQUOTEACKNOWLEDGEMENT": "b", 80 | "COLLATERALREPORT": "BA", 81 | "COLLATERALINQUIRY": "BB", 82 | "NETWORKCOUNTERPARTYSYSTEMSTATUSREQUEST": "BC", 83 | "NETWORKCOUNTERPARTYSYSTEMSTATUSRESPONSE": "BD", 84 | "USERREQUEST": "BE", 85 | "USERRESPONSE": "BF", 86 | "COLLATERALINQUIRYACK": "BG", 87 | "CONFIRMATIONREQUEST": "BH", 88 | "EMAIL": "C", 89 | "SECURITYDEFINITIONREQUEST": "c", 90 | "SECURITYDEFINITION": "d", 91 | "NEWORDERSINGLE": "D", 92 | "SECURITYSTATUSREQUEST": "e", 93 | "NEWORDERLIST": "E", 94 | "ORDERCANCELREQUEST": "F", 95 | "SECURITYSTATUS": "f", 96 | "ORDERCANCELREPLACEREQUEST": "G", 97 | "TRADINGSESSIONSTATUSREQUEST": "g", 98 | "ORDERSTATUSREQUEST": "H", 99 | "TRADINGSESSIONSTATUS": "h", 100 | "MASSQUOTE": "i", 101 | "BUSINESSMESSAGEREJECT": "j", 102 | "ALLOCATIONINSTRUCTION": "J", 103 | "BIDREQUEST": "k", 104 | "LISTCANCELREQUEST": "K", 105 | "BIDRESPONSE": "l", 106 | "LISTEXECUTE": "L", 107 | "LISTSTRIKEPRICE": "m", 108 | "LISTSTATUSREQUEST": "M", 109 | "XMLNONFIX": "n", 110 | "LISTSTATUS": "N", 111 | "REGISTRATIONINSTRUCTIONS": "o", 112 | "REGISTRATIONINSTRUCTIONSRESPONSE": "p", 113 | "ALLOCATIONINSTRUCTIONACK": "P", 114 | "ORDERMASSCANCELREQUEST": "q", 115 | "DONTKNOWTRADEDK": "Q", 116 | "QUOTEREQUEST": "R", 117 | "ORDERMASSCANCELREPORT": "r", 118 | "QUOTE": "S", 119 | "NEWORDERCROSS": "s", 120 | "SETTLEMENTINSTRUCTIONS": "T", 121 | "CROSSORDERCANCELREPLACEREQUEST": "t", 122 | "CROSSORDERCANCELREQUEST": "u", 123 | "MARKETDATAREQUEST": "V", 124 | "SECURITYTYPEREQUEST": "v", 125 | "SECURITYTYPES": "w", 126 | "MARKETDATASNAPSHOTFULLREFRESH": "W", 127 | "SECURITYLISTREQUEST": "x", 128 | "MARKETDATAINCREMENTALREFRESH": "X", 129 | "MARKETDATAREQUESTREJECT": "Y", 130 | "SECURITYLIST": "y", 131 | "QUOTECANCEL": "Z", 132 | "DERIVATIVESECURITYLISTREQUEST": "z" 133 | } 134 | }, 135 | "NewSeqNo": "36", 136 | "OrderID": "37", 137 | "OrderQty": "38", 138 | "OrdStatus": "39", 139 | "OrdType": "40", 140 | "OrigClOrdID": "41", 141 | "OrigTime": "42", 142 | "PossDupFlag": "43", 143 | "Price": "44", 144 | "RefSeqNum": "45", 145 | "RelatdSym": "46", 146 | "Rule80A": "47", 147 | "SecurityID": "48", 148 | "SenderCompID": "49", 149 | "SenderSubID": "50", 150 | "SendingDate": "51", 151 | "SendingTime": "52", 152 | "Quantity": "53", 153 | "Side": "54", 154 | "Symbol": "55", 155 | "TargetCompID": "56", 156 | "TargetSubID": "57", 157 | "Text": "58", 158 | "TimeInForce": "59", 159 | "TransactTime": "60", 160 | "Urgency": "61", 161 | "ValidUntilTime": "62", 162 | "SettlType": "63", 163 | "SettlDate": "64", 164 | "SymbolSfx": "65", 165 | "ListID": "66", 166 | "ListSeqNo": "67", 167 | "TotNoOrders": "68", 168 | "ListExecInst": "69", 169 | "AllocID": "70", 170 | "AllocTransType": "71", 171 | "RefAllocID": "72", 172 | "NoOrders": "73", 173 | "AvgPxPrecision": "74", 174 | "TradeDate": "75", 175 | "ExecBroker": "76", 176 | "PositionEffect": "77", 177 | "NoAllocs": "78", 178 | "AllocAccount": "79", 179 | "AllocQty": "80", 180 | "ProcessCode": "81", 181 | "NoRpts": "82", 182 | "RptSeq": "83", 183 | "CxlQty": "84", 184 | "NoDlvyInst": "85", 185 | "DlvyInst": "86", 186 | "AllocStatus": "87", 187 | "AllocRejCode": "88", 188 | "Signature": "89", 189 | "SecureDataLen": "90", 190 | "SecureData": "91", 191 | "BrokerOfCredit": "92", 192 | "SignatureLength": "93", 193 | "EmailType": "94", 194 | "RawDataLength": "95", 195 | "RawData": "96", 196 | "PossResend": "97", 197 | "EncryptMethod": "98", 198 | "StopPx": "99", 199 | "ExDestination": "100", 200 | "CxlRejReason": "102", 201 | "OrdRejReason": "103", 202 | "IOIQualifier": "104", 203 | "WaveNo": "105", 204 | "Issuer": "106", 205 | "SecurityDesc": "107", 206 | "HeartBtInt": "108", 207 | "ClientID": "109", 208 | "MinQty": "110", 209 | "MaxFloor": "111", 210 | "TestReqID": "112", 211 | "ReportToExch": "113", 212 | "LocateReqd": "114", 213 | "OnBehalfOfCompID": "115", 214 | "OnBehalfOfSubID": "116", 215 | "QuoteID": "117", 216 | "NetMoney": "118", 217 | "SettlCurrAmt": "119", 218 | "SettlCurrency": "120", 219 | "ForexReq": "121", 220 | "OrigSendingTime": "122", 221 | "GapFillFlag": "123", 222 | "NoExecs": "124", 223 | "CxlType": "125", 224 | "ExpireTime": "126", 225 | "DKReason": "127", 226 | "DeliverToCompID": "128", 227 | "DeliverToSubID": "129", 228 | "IOINaturalFlag": "130", 229 | "QuoteReqID": "131", 230 | "BidPx": "132", 231 | "OfferPx": "133", 232 | "BidSize": "134", 233 | "OfferSize": "135", 234 | "NoMiscFees": "136", 235 | "MiscFeeAmt": "137", 236 | "MiscFeeCurr": "138", 237 | "MiscFeeType": "139", 238 | "PrevClosePx": "140", 239 | "ResetSeqNumFlag": "141", 240 | "SenderLocationID": "142", 241 | "TargetLocationID": "143", 242 | "OnBehalfOfLocationID": "144", 243 | "DeliverToLocationID": "145", 244 | "NoRelatedSym": "146", 245 | "Subject": "147", 246 | "Headline": "148", 247 | "URLLink": "149", 248 | "ExecType": "150", 249 | "LeavesQty": "151", 250 | "CashOrderQty": "152", 251 | "AllocAvgPx": "153", 252 | "AllocNetMoney": "154", 253 | "SettlCurrFxRate": "155", 254 | "SettlCurrFxRateCalc": "156", 255 | "NumDaysInterest": "157", 256 | "AccruedInterestRate": "158", 257 | "AccruedInterestAmt": "159", 258 | "SettlInstMode": "160", 259 | "AllocText": "161", 260 | "SettlInstID": "162", 261 | "SettlInstTransType": "163", 262 | "EmailThreadID": "164", 263 | "SettlInstSource": "165", 264 | "SettlLocation": "166", 265 | "SecurityType": "167", 266 | "EffectiveTime": "168", 267 | "StandInstDbType": "169", 268 | "StandInstDbName": "170", 269 | "StandInstDbID": "171", 270 | "SettlDeliveryType": "172", 271 | "SettlDepositoryCode": "173", 272 | "SettlBrkrCode": "174", 273 | "SettlInstCode": "175", 274 | "SecuritySettlAgentName": "176", 275 | "SecuritySettlAgentCode": "177", 276 | "SecuritySettlAgentAcctNum": "178", 277 | "SecuritySettlAgentAcctName": "179", 278 | "SecuritySettlAgentContactName": "180", 279 | "SecuritySettlAgentContactPhone": "181", 280 | "CashSettlAgentName": "182", 281 | "CashSettlAgentCode": "183", 282 | "CashSettlAgentAcctNum": "184", 283 | "CashSettlAgentAcctName": "185", 284 | "CashSettlAgentContactName": "186", 285 | "CashSettlAgentContactPhone": "187", 286 | "BidSpotRate": "188", 287 | "BidForwardPoints": "189", 288 | "OfferSpotRate": "190", 289 | "OfferForwardPoints": "191", 290 | "OrderQty2": "192", 291 | "SettlDate2": "193", 292 | "LastSpotRate": "194", 293 | "LastForwardPoints": "195", 294 | "AllocLinkID": "196", 295 | "AllocLinkType": "197", 296 | "SecondaryOrderID": "198", 297 | "NoIOIQualifiers": "199", 298 | "MaturityMonthYear": "200", 299 | "PutOrCall": "201", 300 | "StrikePrice": "202", 301 | "CoveredOrUncovered": "203", 302 | "CustomerOrFirm": "204", 303 | "MaturityDay": "205", 304 | "OptAttribute": "206", 305 | "SecurityExchange": "207", 306 | "NotifyBrokerOfCredit": "208", 307 | "AllocHandlInst": "209", 308 | "MaxShow": "210", 309 | "PegOffsetValue": "211", 310 | "XmlDataLen": "212", 311 | "XmlData": "213", 312 | "SettlInstRefID": "214", 313 | "NoRoutingIDs": "215", 314 | "RoutingType": "216", 315 | "RoutingID": "217", 316 | "Spread": "218", 317 | "Benchmark": "219", 318 | "BenchmarkCurveCurrency": "220", 319 | "BenchmarkCurveName": "221", 320 | "BenchmarkCurvePoint": "222", 321 | "CouponRate": "223", 322 | "CouponPaymentDate": "224", 323 | "IssueDate": "225", 324 | "RepurchaseTerm": "226", 325 | "RepurchaseRate": "227", 326 | "Factor": "228", 327 | "TradeOriginationDate": "229", 328 | "ExDate": "230", 329 | "ContractMultiplier": "231", 330 | "NoStipulations": "232", 331 | "StipulationType": "233", 332 | "StipulationValue": "234", 333 | "YieldType": "235", 334 | "Yield": "236", 335 | "TotalTakedown": "237", 336 | "Concession": "238", 337 | "RepoCollateralSecurityType": "239", 338 | "RedemptionDate": "240", 339 | "UnderlyingCouponPaymentDate": "241", 340 | "UnderlyingIssueDate": "242", 341 | "UnderlyingRepoCollateralSecurityType": "243", 342 | "UnderlyingRepurchaseTerm": "244", 343 | "UnderlyingRepurchaseRate": "245", 344 | "UnderlyingFactor": "246", 345 | "UnderlyingRedemptionDate": "247", 346 | "LegCouponPaymentDate": "248", 347 | "LegIssueDate": "249", 348 | "LegRepoCollateralSecurityType": "250", 349 | "LegRepurchaseTerm": "251", 350 | "LegRepurchaseRate": "252", 351 | "LegFactor": "253", 352 | "LegRedemptionDate": "254", 353 | "CreditRating": "255", 354 | "UnderlyingCreditRating": "256", 355 | "LegCreditRating": "257", 356 | "TradedFlatSwitch": "258", 357 | "BasisFeatureDate": "259", 358 | "BasisFeaturePrice": "260", 359 | "MDReqID": "262", 360 | "SubscriptionRequestType": "263", 361 | "MarketDepth": "264", 362 | "MDUpdateType": "265", 363 | "AggregatedBook": "266", 364 | "NoMDEntryTypes": "267", 365 | "NoMDEntries": "268", 366 | "MDEntryType": "269", 367 | "MDEntryPx": "270", 368 | "MDEntrySize": "271", 369 | "MDEntryDate": "272", 370 | "MDEntryTime": "273", 371 | "TickDirection": "274", 372 | "MDMkt": "275", 373 | "QuoteCondition": "276", 374 | "TradeCondition": "277", 375 | "MDEntryID": "278", 376 | "MDUpdateAction": "279", 377 | "MDEntryRefID": "280", 378 | "MDReqRejReason": "281", 379 | "MDEntryOriginator": "282", 380 | "LocationID": "283", 381 | "DeskID": "284", 382 | "DeleteReason": "285", 383 | "OpenCloseSettlFlag": "286", 384 | "SellerDays": "287", 385 | "MDEntryBuyer": "288", 386 | "MDEntrySeller": "289", 387 | "MDEntryPositionNo": "290", 388 | "FinancialStatus": "291", 389 | "CorporateAction": "292", 390 | "DefBidSize": "293", 391 | "DefOfferSize": "294", 392 | "NoQuoteEntries": "295", 393 | "NoQuoteSets": "296", 394 | "QuoteStatus": "297", 395 | "QuoteCancelType": "298", 396 | "QuoteEntryID": "299", 397 | "QuoteRejectReason": "300", 398 | "QuoteResponseLevel": "301", 399 | "QuoteSetID": "302", 400 | "QuoteRequestType": "303", 401 | "TotNoQuoteEntries": "304", 402 | "UnderlyingSecurityIDSource": "305", 403 | "UnderlyingIssuer": "306", 404 | "UnderlyingSecurityDesc": "307", 405 | "UnderlyingSecurityExchange": "308", 406 | "UnderlyingSecurityID": "309", 407 | "UnderlyingSecurityType": "310", 408 | "UnderlyingSymbol": "311", 409 | "UnderlyingSymbolSfx": "312", 410 | "UnderlyingMaturityMonthYear": "313", 411 | "UnderlyingMaturityDay": "314", 412 | "UnderlyingPutOrCall": "315", 413 | "UnderlyingStrikePrice": "316", 414 | "UnderlyingOptAttribute": "317", 415 | "UnderlyingCurrency": "318", 416 | "RatioQty": "319", 417 | "SecurityReqID": "320", 418 | "SecurityRequestType": "321", 419 | "SecurityResponseID": "322", 420 | "SecurityResponseType": "323", 421 | "SecurityStatusReqID": "324", 422 | "UnsolicitedIndicator": "325", 423 | "SecurityTradingStatus": "326", 424 | "HaltReason": "327", 425 | "InViewOfCommon": "328", 426 | "DueToRelated": "329", 427 | "BuyVolume": "330", 428 | "SellVolume": "331", 429 | "HighPx": "332", 430 | "LowPx": "333", 431 | "Adjustment": "334", 432 | "TradSesReqID": "335", 433 | "TradingSessionID": "336", 434 | "ContraTrader": "337", 435 | "TradSesMethod": "338", 436 | "TradSesMode": "339", 437 | "TradSesStatus": "340", 438 | "TradSesStartTime": "341", 439 | "TradSesOpenTime": "342", 440 | "TradSesPreCloseTime": "343", 441 | "TradSesCloseTime": "344", 442 | "TradSesEndTime": "345", 443 | "NumberOfOrders": "346", 444 | "MessageEncoding": "347", 445 | "EncodedIssuerLen": "348", 446 | "EncodedIssuer": "349", 447 | "EncodedSecurityDescLen": "350", 448 | "EncodedSecurityDesc": "351", 449 | "EncodedListExecInstLen": "352", 450 | "EncodedListExecInst": "353", 451 | "EncodedTextLen": "354", 452 | "EncodedText": "355", 453 | "EncodedSubjectLen": "356", 454 | "EncodedSubject": "357", 455 | "EncodedHeadlineLen": "358", 456 | "EncodedHeadline": "359", 457 | "EncodedAllocTextLen": "360", 458 | "EncodedAllocText": "361", 459 | "EncodedUnderlyingIssuerLen": "362", 460 | "EncodedUnderlyingIssuer": "363", 461 | "EncodedUnderlyingSecurityDescLen": "364", 462 | "EncodedUnderlyingSecurityDesc": "365", 463 | "AllocPrice": "366", 464 | "QuoteSetValidUntilTime": "367", 465 | "QuoteEntryRejectReason": "368", 466 | "LastMsgSeqNumProcessed": "369", 467 | "OnBehalfOfSendingTime": "370", 468 | "RefTagID": "371", 469 | "RefMsgType": "372", 470 | "SessionRejectReason": "373", 471 | "BidRequestTransType": "374", 472 | "ContraBroker": "375", 473 | "ComplianceID": "376", 474 | "SolicitedFlag": "377", 475 | "ExecRestatementReason": "378", 476 | "BusinessRejectRefID": "379", 477 | "BusinessRejectReason": "380", 478 | "GrossTradeAmt": "381", 479 | "NoContraBrokers": "382", 480 | "MaxMessageSize": "383", 481 | "NoMsgTypes": "384", 482 | "MsgDirection": "385", 483 | "NoTradingSessions": "386", 484 | "TotalVolumeTraded": "387", 485 | "DiscretionInst": "388", 486 | "DiscretionOffsetValue": "389", 487 | "BidID": "390", 488 | "ClientBidID": "391", 489 | "ListName": "392", 490 | "TotNoRelatedSym": "393", 491 | "BidType": "394", 492 | "NumTickets": "395", 493 | "SideValue1": "396", 494 | "SideValue2": "397", 495 | "NoBidDescriptors": "398", 496 | "BidDescriptorType": "399", 497 | "BidDescriptor": "400", 498 | "SideValueInd": "401", 499 | "LiquidityPctLow": "402", 500 | "LiquidityPctHigh": "403", 501 | "LiquidityValue": "404", 502 | "EFPTrackingError": "405", 503 | "FairValue": "406", 504 | "OutsideIndexPct": "407", 505 | "ValueOfFutures": "408", 506 | "LiquidityIndType": "409", 507 | "WtAverageLiquidity": "410", 508 | "ExchangeForPhysical": "411", 509 | "OutMainCntryUIndex": "412", 510 | "CrossPercent": "413", 511 | "ProgRptReqs": "414", 512 | "ProgPeriodInterval": "415", 513 | "IncTaxInd": "416", 514 | "NumBidders": "417", 515 | "BidTradeType": "418", 516 | "BasisPxType": "419", 517 | "NoBidComponents": "420", 518 | "Country": "421", 519 | "TotNoStrikes": "422", 520 | "PriceType": "423", 521 | "DayOrderQty": "424", 522 | "DayCumQty": "425", 523 | "DayAvgPx": "426", 524 | "GTBookingInst": "427", 525 | "NoStrikes": "428", 526 | "ListStatusType": "429", 527 | "NetGrossInd": "430", 528 | "ListOrderStatus": "431", 529 | "ExpireDate": "432", 530 | "ListExecInstType": "433", 531 | "CxlRejResponseTo": "434", 532 | "UnderlyingCouponRate": "435", 533 | "UnderlyingContractMultiplier": "436", 534 | "ContraTradeQty": "437", 535 | "ContraTradeTime": "438", 536 | "ClearingFirm": "439", 537 | "ClearingAccount": "440", 538 | "LiquidityNumSecurities": "441", 539 | "MultiLegReportingType": "442", 540 | "StrikeTime": "443", 541 | "ListStatusText": "444", 542 | "EncodedListStatusTextLen": "445", 543 | "EncodedListStatusText": "446", 544 | "PartyIDSource": "447", 545 | "PartyID": "448", 546 | "TotalVolumeTradedDate": "449", 547 | "TotalVolumeTraded": "450", 548 | "NetChgPrevDay": "451", 549 | "PartyRole": "452", 550 | "NoPartyIDs": "453", 551 | "NoSecurityAltID": "454", 552 | "SecurityAltID": "455", 553 | "SecurityAltIDSource": "456", 554 | "NoUnderlyingSecurityAltID": "457", 555 | "UnderlyingSecurityAltID": "458", 556 | "UnderlyingSecurityAltIDSource": "459", 557 | "Product": "460", 558 | "CFICode": "461", 559 | "UnderlyingProduct": "462", 560 | "UnderlyingCFICode": "463", 561 | "TestMessageIndicator": "464", 562 | "QuantityType": "465", 563 | "BookingRefID": "466", 564 | "IndividualAllocID": "467", 565 | "RoundingDirection": "468", 566 | "RoundingModulus": "469", 567 | "CountryOfIssue": "470", 568 | "StateOrProvinceOfIssue": "471", 569 | "LocaleOfIssue": "472", 570 | "NoRegistDtls": "473", 571 | "MailingDtls": "474", 572 | "InvestorCountryOfResidence": "475", 573 | "PaymentRef": "476", 574 | "DistribPaymentMethod": "477", 575 | "CashDistribCurr": "478", 576 | "CommCurrency": "479", 577 | "CancellationRights": "480", 578 | "MoneyLaunderingStatus": "481", 579 | "MailingInst": "482", 580 | "TransBkdTime": "483", 581 | "ExecPriceType": "484", 582 | "ExecPriceAdjustment": "485", 583 | "DateOfBirth": "486", 584 | "TradeReportTransType": "487", 585 | "CardHolderName": "488", 586 | "CardNumber": "489", 587 | "CardExpDate": "490", 588 | "CardIssNum": "491", 589 | "PaymentMethod": "492", 590 | "RegistAcctType": "493", 591 | "Designation": "494", 592 | "TaxAdvantageType": "495", 593 | "RegistRejReasonText": "496", 594 | "FundRenewWaiv": "497", 595 | "CashDistribAgentName": "498", 596 | "CashDistribAgentCode": "499", 597 | "CashDistribAgentAcctNumber": "500", 598 | "CashDistribPayRef": "501", 599 | "CashDistribAgentAcctName": "502", 600 | "CardStartDate": "503", 601 | "PaymentDate": "504", 602 | "PaymentRemitterID": "505", 603 | "RegistStatus": "506", 604 | "RegistRejReasonCode": "507", 605 | "RegistRefID": "508", 606 | "RegistDtls": "509", 607 | "NoDistribInsts": "510", 608 | "RegistEmail": "511", 609 | "DistribPercentage": "512", 610 | "RegistID": "513", 611 | "RegistTransType": "514", 612 | "ExecValuationPoint": "515", 613 | "OrderPercent": "516", 614 | "OwnershipType": "517", 615 | "NoContAmts": "518", 616 | "ContAmtType": "519", 617 | "ContAmtValue": "520", 618 | "ContAmtCurr": "521", 619 | "OwnerType": "522", 620 | "PartySubID": "523", 621 | "NestedPartyID": "524", 622 | "NestedPartyIDSource": "525", 623 | "SecondaryClOrdID": "526", 624 | "SecondaryExecID": "527", 625 | "OrderCapacity": "528", 626 | "OrderRestrictions": "529", 627 | "MassCancelRequestType": "530", 628 | "MassCancelResponse": "531", 629 | "MassCancelRejectReason": "532", 630 | "TotalAffectedOrders": "533", 631 | "NoAffectedOrders": "534", 632 | "AffectedOrderID": "535", 633 | "AffectedSecondaryOrderID": "536", 634 | "QuoteType": "537", 635 | "NestedPartyRole": "538", 636 | "NoNestedPartyIDs": "539", 637 | "TotalAccruedInterestAmt": "540", 638 | "MaturityDate": "541", 639 | "UnderlyingMaturityDate": "542", 640 | "InstrRegistry": "543", 641 | "CashMargin": "544", 642 | "NestedPartySubID": "545", 643 | "Scope": "546", 644 | "MDImplicitDelete": "547", 645 | "CrossID": "548", 646 | "CrossType": "549", 647 | "CrossPrioritization": "550", 648 | "OrigCrossID": "551", 649 | "NoSides": "552", 650 | "Username": "553", 651 | "Password": "554", 652 | "NoLegs": "555", 653 | "LegCurrency": "556", 654 | "TotNoSecurityTypes": "557", 655 | "NoSecurityTypes": "558", 656 | "SecurityListRequestType": "559", 657 | "SecurityRequestResult": "560", 658 | "RoundLot": "561", 659 | "MinTradeVol": "562", 660 | "MultiLegRptTypeReq": "563", 661 | "LegPositionEffect": "564", 662 | "LegCoveredOrUncovered": "565", 663 | "LegPrice": "566", 664 | "TradSesStatusRejReason": "567", 665 | "TradeRequestID": "568", 666 | "TradeRequestType": "569", 667 | "PreviouslyReported": "570", 668 | "TradeReportID": "571", 669 | "TradeReportRefID": "572", 670 | "MatchStatus": "573", 671 | "MatchType": "574", 672 | "OddLot": "575", 673 | "NoClearingInstructions": "576", 674 | "ClearingInstruction": "577", 675 | "TradeInputSource": "578", 676 | "TradeInputDevice": "579", 677 | "NoDates": "580", 678 | "AccountType": "581", 679 | "CustOrderCapacity": "582", 680 | "ClOrdLinkID": "583", 681 | "MassStatusReqID": "584", 682 | "MassStatusReqType": "585", 683 | "OrigOrdModTime": "586", 684 | "LegSettlType": "587", 685 | "LegSettlDate": "588", 686 | "DayBookingInst": "589", 687 | "BookingUnit": "590", 688 | "PreallocMethod": "591", 689 | "UnderlyingCountryOfIssue": "592", 690 | "UnderlyingStateOrProvinceOfIssue": "593", 691 | "UnderlyingLocaleOfIssue": "594", 692 | "UnderlyingInstrRegistry": "595", 693 | "LegCountryOfIssue": "596", 694 | "LegStateOrProvinceOfIssue": "597", 695 | "LegLocaleOfIssue": "598", 696 | "LegInstrRegistry": "599", 697 | "LegSymbol": "600", 698 | "LegSymbolSfx": "601", 699 | "LegSecurityID": "602", 700 | "LegSecurityIDSource": "603", 701 | "NoLegSecurityAltID": "604", 702 | "LegSecurityAltID": "605", 703 | "LegSecurityAltIDSource": "606", 704 | "LegProduct": "607", 705 | "LegCFICode": "608", 706 | "LegSecurityType": "609", 707 | "LegMaturityMonthYear": "610", 708 | "LegMaturityDate": "611", 709 | "LegStrikePrice": "612", 710 | "LegOptAttribute": "613", 711 | "LegContractMultiplier": "614", 712 | "LegCouponRate": "615", 713 | "LegSecurityExchange": "616", 714 | "LegIssuer": "617", 715 | "EncodedLegIssuerLen": "618", 716 | "EncodedLegIssuer": "619", 717 | "LegSecurityDesc": "620", 718 | "EncodedLegSecurityDescLen": "621", 719 | "EncodedLegSecurityDesc": "622", 720 | "LegRatioQty": "623", 721 | "LegSide": "624", 722 | "TradingSessionSubID": "625", 723 | "AllocType": "626", 724 | "NoHops": "627", 725 | "HopCompID": "628", 726 | "HopSendingTime": "629", 727 | "HopRefID": "630", 728 | "MidPx": "631", 729 | "BidYield": "632", 730 | "MidYield": "633", 731 | "OfferYield": "634", 732 | "ClearingFeeIndicator": "635", 733 | "WorkingIndicator": "636", 734 | "LegLastPx": "637", 735 | "PriorityIndicator": "638", 736 | "PriceImprovement": "639", 737 | "Price2": "640", 738 | "LastForwardPoints2": "641", 739 | "BidForwardPoints2": "642", 740 | "OfferForwardPoints2": "643", 741 | "RFQReqID": "644", 742 | "MktBidPx": "645", 743 | "MktOfferPx": "646", 744 | "MinBidSize": "647", 745 | "MinOfferSize": "648", 746 | "QuoteStatusReqID": "649", 747 | "LegalConfirm": "650", 748 | "UnderlyingLastPx": "651", 749 | "UnderlyingLastQty": "652", 750 | "SecDefStatus": "653", 751 | "LegRefID": "654", 752 | "ContraLegRefID": "655", 753 | "SettlCurrBidFxRate": "656", 754 | "SettlCurrOfferFxRate": "657", 755 | "QuoteRequestRejectReason": "658", 756 | "SideComplianceID": "659", 757 | "AcctIDSource": "660", 758 | "AllocAcctIDSource": "661", 759 | "BenchmarkPrice": "662", 760 | "BenchmarkPriceType": "663", 761 | "ConfirmID": "664", 762 | "ConfirmStatus": "665", 763 | "ConfirmTransType": "666", 764 | "ContractSettlMonth": "667", 765 | "DeliveryForm": "668", 766 | "LastParPx": "669", 767 | "NoLegAllocs": "670", 768 | "LegAllocAccount": "671", 769 | "LegIndividualAllocID": "672", 770 | "LegAllocQty": "673", 771 | "LegAllocAcctIDSource": "674", 772 | "LegSettlCurrency": "675", 773 | "LegBenchmarkCurveCurrency": "676", 774 | "LegBenchmarkCurveName": "677", 775 | "LegBenchmarkCurvePoint": "678", 776 | "LegBenchmarkPrice": "679", 777 | "LegBenchmarkPriceType": "680", 778 | "LegBidPx": "681", 779 | "LegIOIQty": "682", 780 | "NoLegStipulations": "683", 781 | "LegOfferPx": "684", 782 | "LegOrderQty": "685", 783 | "LegPriceType": "686", 784 | "LegQty": "687", 785 | "LegStipulationType": "688", 786 | "LegStipulationValue": "689", 787 | "LegSwapType": "690", 788 | "Pool": "691", 789 | "QuotePriceType": "692", 790 | "QuoteRespID": "693", 791 | "QuoteRespType": "694", 792 | "QuoteQualifier": "695", 793 | "YieldRedemptionDate": "696", 794 | "YieldRedemptionPrice": "697", 795 | "YieldRedemptionPriceType": "698", 796 | "BenchmarkSecurityID": "699", 797 | "ReversalIndicator": "700", 798 | "YieldCalcDate": "701", 799 | "NoPositions": "702", 800 | "PosType": "703", 801 | "LongQty": "704", 802 | "ShortQty": "705", 803 | "PosQtyStatus": "706", 804 | "PosAmtType": "707", 805 | "PosAmt": "708", 806 | "PosTransType": "709", 807 | "PosReqID": "710", 808 | "NoUnderlyings": "711", 809 | "PosMaintAction": "712", 810 | "OrigPosReqRefID": "713", 811 | "PosMaintRptRefID": "714", 812 | "ClearingBusinessDate": "715", 813 | "SettlSessID": "716", 814 | "SettlSessSubID": "717", 815 | "AdjustmentType": "718", 816 | "ContraryInstructionIndicator": "719", 817 | "PriorSpreadIndicator": "720", 818 | "PosMaintRptID": "721", 819 | "PosMaintStatus": "722", 820 | "PosMaintResult": "723", 821 | "PosReqType": "724", 822 | "ResponseTransportType": "725", 823 | "ResponseDestination": "726", 824 | "TotalNumPosReports": "727", 825 | "PosReqResult": "728", 826 | "PosReqStatus": "729", 827 | "SettlPrice": "730", 828 | "SettlPriceType": "731", 829 | "UnderlyingSettlPrice": "732", 830 | "UnderlyingSettlPriceType": "733", 831 | "PriorSettlPrice": "734", 832 | "NoQuoteQualifiers": "735", 833 | "AllocSettlCurrency": "736", 834 | "AllocSettlCurrAmt": "737", 835 | "InterestAtMaturity": "738", 836 | "LegDatedDate": "739", 837 | "LegPool": "740", 838 | "AllocInterestAtMaturity": "741", 839 | "AllocAccruedInterestAmt": "742", 840 | "DeliveryDate": "743", 841 | "AssignmentMethod": "744", 842 | "AssignmentUnit": "745", 843 | "OpenInterest": "746", 844 | "ExerciseMethod": "747", 845 | "TotNumTradeReports": "748", 846 | "TradeRequestResult": "749", 847 | "TradeRequestStatus": "750", 848 | "TradeReportRejectReason": "751", 849 | "SideMultiLegReportingType": "752", 850 | "NoPosAmt": "753", 851 | "AutoAcceptIndicator": "754", 852 | "AllocReportID": "755", 853 | "NoNested2PartyIDs": "756", 854 | "Nested2PartyID": "757", 855 | "Nested2PartyIDSource": "758", 856 | "Nested2PartyRole": "759", 857 | "Nested2PartySubID": "760", 858 | "BenchmarkSecurityIDSource": "761", 859 | "SecuritySubType": "762", 860 | "UnderlyingSecuritySubType": "763", 861 | "LegSecuritySubType": "764", 862 | "AllowableOneSidednessPct": "765", 863 | "AllowableOneSidednessValue": "766", 864 | "AllowableOneSidednessCurr": "767", 865 | "NoTrdRegTimestamps": "768", 866 | "TrdRegTimestamp": "769", 867 | "TrdRegTimestampType": "770", 868 | "TrdRegTimestampOrigin": "771", 869 | "ConfirmRefID": "772", 870 | "ConfirmType": "773", 871 | "ConfirmRejReason": "774", 872 | "BookingType": "775", 873 | "IndividualAllocRejCode": "776", 874 | "SettlInstMsgID": "777", 875 | "NoSettlInst": "778", 876 | "LastUpdateTime": "779", 877 | "AllocSettlInstType": "780", 878 | "NoSettlPartyIDs": "781", 879 | "SettlPartyID": "782", 880 | "SettlPartyIDSource": "783", 881 | "SettlPartyRole": "784", 882 | "SettlPartySubID": "785", 883 | "SettlPartySubIDType": "786", 884 | "DlvyInstType": "787", 885 | "TerminationType": "788", 886 | "NextExpectedMsgSeqNum": "789", 887 | "OrdStatusReqID": "790", 888 | "SettlInstReqID": "791", 889 | "SettlInstReqRejCode": "792", 890 | "SecondaryAllocID": "793", 891 | "AllocReportType": "794", 892 | "AllocReportRefID": "795", 893 | "AllocCancReplaceReason": "796", 894 | "CopyMsgIndicator": "797", 895 | "AllocAccountType": "798", 896 | "OrderAvgPx": "799", 897 | "OrderBookingQty": "800", 898 | "NoSettlPartySubIDs": "801", 899 | "NoPartySubIDs": "802", 900 | "PartySubIDType": "803", 901 | "NoNestedPartySubIDs": "804", 902 | "NestedPartySubIDType": "805", 903 | "NoNested2PartySubIDs": "806", 904 | "Nested2PartySubIDType": "807", 905 | "AllocIntermedReqType": "808", 906 | "UnderlyingPx": "810", 907 | "PriceDelta": "811", 908 | "ApplQueueMax": "812", 909 | "ApplQueueDepth": "813", 910 | "ApplQueueResolution": "814", 911 | "ApplQueueAction": "815", 912 | "NoAltMDSource": "816", 913 | "AltMDSourceID": "817", 914 | "SecondaryTradeReportID": "818", 915 | "AvgPxIndicator": "819", 916 | "TradeLinkID": "820", 917 | "OrderInputDevice": "821", 918 | "UnderlyingTradingSessionID": "822", 919 | "UnderlyingTradingSessionSubID": "823", 920 | "TradeLegRefID": "824", 921 | "ExchangeRule": "825", 922 | "TradeAllocIndicator": "826", 923 | "ExpirationCycle": "827", 924 | "TrdType": "828", 925 | "TrdSubType": "829", 926 | "TransferReason": "830", 927 | "AsgnReqID": "831", 928 | "TotNumAssignmentReports": "832", 929 | "AsgnRptID": "833", 930 | "ThresholdAmount": "834", 931 | "PegMoveType": "835", 932 | "PegOffsetType": "836", 933 | "PegLimitType": "837", 934 | "PegRoundDirection": "838", 935 | "PeggedPrice": "839", 936 | "PegScope": "840", 937 | "DiscretionMoveType": "841", 938 | "DiscretionOffsetType": "842", 939 | "DiscretionLimitType": "843", 940 | "DiscretionRoundDirection": "844", 941 | "DiscretionPrice": "845", 942 | "DiscretionScope": "846", 943 | "TargetStrategy": "847", 944 | "TargetStrategyParameters": "848", 945 | "ParticipationRate": "849", 946 | "TargetStrategyPerformance": "850", 947 | "LastLiquidityInd": "851", 948 | "PublishTrdIndicator": "852", 949 | "ShortSaleReason": "853", 950 | "QtyType": "854", 951 | "SecondaryTrdType": "855", 952 | "TradeReportType": "856", 953 | "AllocNoOrdersType": "857", 954 | "SharedCommission": "858", 955 | "ConfirmReqID": "859", 956 | "AvgParPx": "860", 957 | "ReportedPx": "861", 958 | "NoCapacities": "862", 959 | "OrderCapacityQty": "863", 960 | "NoEvents": "864", 961 | "EventType": "865", 962 | "EventDate": "866", 963 | "EventPx": "867", 964 | "EventText": "868", 965 | "PctAtRisk": "869", 966 | "NoInstrAttrib": "870", 967 | "InstrAttribType": "871", 968 | "InstrAttribValue": "872", 969 | "DatedDate": "873", 970 | "InterestAccrualDate": "874", 971 | "CPProgram": "875", 972 | "CPRegType": "876", 973 | "UnderlyingCPProgram": "877", 974 | "UnderlyingCPRegType": "878", 975 | "UnderlyingQty": "879", 976 | "TrdMatchID": "880", 977 | "SecondaryTradeReportRefID": "881", 978 | "UnderlyingDirtyPrice": "882", 979 | "UnderlyingEndPrice": "883", 980 | "UnderlyingStartValue": "884", 981 | "UnderlyingCurrentValue": "885", 982 | "UnderlyingEndValue": "886", 983 | "NoUnderlyingStips": "887", 984 | "UnderlyingStipType": "888", 985 | "UnderlyingStipValue": "889", 986 | "MaturityNetMoney": "890", 987 | "MiscFeeBasis": "891", 988 | "TotNoAllocs": "892", 989 | "LastFragment": "893", 990 | "CollReqID": "894", 991 | "CollAsgnReason": "895", 992 | "CollInquiryQualifier": "896", 993 | "NoTrades": "897", 994 | "MarginRatio": "898", 995 | "MarginExcess": "899", 996 | "TotalNetValue": "900", 997 | "CashOutstanding": "901", 998 | "CollAsgnID": "902", 999 | "CollAsgnTransType": "903", 1000 | "CollRespID": "904", 1001 | "CollAsgnRespType": "905", 1002 | "CollAsgnRejectReason": "906", 1003 | "CollAsgnRefID": "907", 1004 | "CollRptID": "908", 1005 | "CollInquiryID": "909", 1006 | "CollStatus": "910", 1007 | "TotNumReports": "911", 1008 | "LastRptRequested": "912", 1009 | "AgreementDesc": "913", 1010 | "AgreementID": "914", 1011 | "AgreementDate": "915", 1012 | "StartDate": "916", 1013 | "EndDate": "917", 1014 | "AgreementCurrency": "918", 1015 | "DeliveryType": "919", 1016 | "EndAccruedInterestAmt": "920", 1017 | "StartCash": "921", 1018 | "EndCash": "922", 1019 | "UserRequestID": "923", 1020 | "UserRequestType": "924", 1021 | "NewPassword": "925", 1022 | "UserStatus": "926", 1023 | "UserStatusText": "927", 1024 | "StatusValue": "928", 1025 | "StatusText": "929", 1026 | "RefCompID": "930", 1027 | "RefSubID": "931", 1028 | "NetworkResponseID": "932", 1029 | "NetworkRequestID": "933", 1030 | "LastNetworkResponseID": "934", 1031 | "NetworkRequestType": "935", 1032 | "NoCompIDs": "936", 1033 | "NetworkStatusResponseType": "937", 1034 | "NoCollInquiryQualifier": "938", 1035 | "TrdRptStatus": "939", 1036 | "AffirmStatus": "940", 1037 | "UnderlyingStrikeCurrency": "941", 1038 | "LegStrikeCurrency": "942", 1039 | "TimeBracket": "943", 1040 | "CollAction": "944", 1041 | "CollInquiryStatus": "945", 1042 | "CollInquiryResult": "946", 1043 | "StrikeCurrency": "947", 1044 | "NoNested3PartyIDs": "948", 1045 | "Nested3PartyID": "949", 1046 | "Nested3PartyIDSource": "950", 1047 | "Nested3PartyRole": "951", 1048 | "NoNested3PartySubIDs": "952", 1049 | "Nested3PartySubID": "953", 1050 | "Nested3PartySubIDType": "954", 1051 | "LegContractSettlMonth": "955", 1052 | "LegInterestAccrualDate": "956" 1053 | }, 1054 | "repeatingGroupIdentifiers": { 1055 | "NoSecurityAltID" : ["SecurityAltID", "SecurityAltIDSource"], 1056 | "NoMiscFees" : ["MiscFeeAmt", "MiscFeeCurr", "MiscFeeType", "MiscFeeBasis"], 1057 | "NoClearingInstructions" : ["ClearingInstruction"], 1058 | "NoEvents" : ["EventType", "EventDate", "EventPx", "EventText"], 1059 | "NoInstrAttrib" : ["InstrAttribType", "InstrAttribValue"], 1060 | "NoLegSecurityAltID" : ["LegSecurityAltID", "LegSecurityAltIDSource"], 1061 | "NoLegStipulations" : ["LegStipulationType", "LegStipulationValue"], 1062 | "NoNestedPartyIDs" : ["NestedPartyID", "NestedPartyIDSource", "NestedPartyRole", "NoNestedPartySubIDs"], 1063 | "NoNestedPartySubIDs" : ["NestedPartySubID", "NestedPartySubIDType"], 1064 | "NoNested2PartyIDs" : ["Nested2PartyID", "Nested2PartyIDSource", "Nested2PartyRole", "NoNested2PartySubIDs"], 1065 | "NoNested2PartySubIDs" : ["Nested2PartySubID", "Nested2PartySubIDType"], 1066 | "NoNested3PartyIDs" : ["Nested3PartyID", "Nested3PartyIDSource", "Nested3PartyRole", "NoNested3PartySubIDs"], 1067 | "NoNested3PartySubIDs" : ["Nested3PartySubID", "Nested3PartySubIDType"], 1068 | "NoPartyIDs" : ["PartyID", "PartyIDSource", "PartyRole", "NoPartySubIDs"], 1069 | "NoPartySubIDs" : ["PartySubID", "PartySubIDType"], 1070 | "NoPosAmt" : ["PosAmtType", "PosAmt"], 1071 | "NoPositions" : ["PosType", "LongQty", "ShortQty", "PosQtyStatus"], 1072 | "NoDlvyInst" : ["SettlInstSource", "DlvyInstType", "NoSettlPartyIDs"], 1073 | "NoSettlPartyIDs" : ["SettlPartyID", "SettlPartyIDSource", "SettlPartyRole", "NoSettlPartySubIDs"], 1074 | "NoSettlPartySubIDs" : ["SettlPartySubID", "SettlPartySubIDType"], 1075 | "NoStipulations" : ["StipulationType", "StipulationValue"], 1076 | "NoTrdRegTimestamps" : ["TrdRegTimestamp", "TrdRegTimestampType", "TrdRegTimestampOrigin"], 1077 | "NoUnderlyingSecurityAltID" : ["UnderlyingSecurityAltID", "UnderlyingSecurityAltIDSource"], 1078 | "NoUnderlyingStips" : ["UnderlyingStipType", "UnderlyingStipValue"], 1079 | "NoOrders" : ["ClOrdID", "OrderID", "SecondaryOrderID", "SecondaryClOrdID", "ListID", "OrderQty", "OrderAvgPx", "OrderBookingQty"], 1080 | "NoExecs" : ["LastQty", "ExecID", "SecondaryExecID", "LastPx", "LastParPx", "LastCapacity"], 1081 | "NoUnderlyings" : ["UnderlyingSymbol", "UnderlyingSymbolSfx", "UnderlyingSecurityID", "UnderlyingSecurityIDSource", "NoUnderlyingSecurityAltID", "UnderlyingProduct", "UnderlyingCFICode", "UnderlyingSecurityType", "UnderlyingSecuritySubType", "UnderlyingMaturityMonthYear", "UnderlyingMaturityDate", "UnderlyingPutOrCall", "UnderlyingCouponPaymentDate", "UnderlyingIssueDate", "UnderlyingRepoCollateralSecurityType", "UnderlyingRepurchaseTerm", "UnderlyingRepurchaseRate", "UnderlyingFactor", "UnderlyingCreditRating", "UnderlyingInstrRegistry", "UnderlyingCountryOfIssue", "UnderlyingStateOrProvinceOfIssue", "UnderlyingLocaleOfIssue", "UnderlyingRedemptionDate", "UnderlyingStrikePrice", "UnderlyingStrikeCurrency", "UnderlyingOptAttribute", "UnderlyingContractMultiplier", "UnderlyingCouponRate", "UnderlyingSecurityExchange", "UnderlyingIssuer", "EncodedUnderlyingIssuerLen", "EncodedUnderlyingIssuer", "UnderlyingSecurityDesc", "EncodedUnderlyingSecurityDescLen", "EncodedUnderlyingSecurityDesc", "UnderlyingCPProgram", "UnderlyingCPRegType", "UnderlyingCurrency", "UnderlyingQty", "UnderlyingPx", "UnderlyingDirtyPrice", "UnderlyingEndPrice", "UnderlyingStartValue", "UnderlyingCurrentValue", "UnderlyingEndValue", "NoUnderlyingStips"], 1082 | "NoAllocs" : ["AllocAccount", "AllocAcctIDSource", "MatchStatus", "AllocPrice", "AllocQty", "IndividualAllocID", "ProcessCode", "NoNestedPartyIDs", "NotifyBrokerOfCredit", "AllocHandlInst", "AllocText", "EncodedAllocTextLen", "EncodedAllocText", "Commission", "CommType", "CommCurrency", "FundRenewWaiv", "AllocAvgPx", "AllocNetMoney", "SettlCurrAmt", "AllocSettlCurrAmt", "SettlCurrency", "AllocSettlCurrency", "SettlCurrFxRate", "SettlCurrFxRateCalc", "AllocAccruedInterestAmt", "AllocInterestAtMaturity", "NoMiscFees", "NoClearingInstructions", "AllocSettlInstType", "SettlDeliveryType", "StandInstDbType", "StandInstDbName", "StandInstDbID", "NoDlvyInst"], 1083 | "NoLegs" : ["LegSymbol", "LegSymbolSfx", "LegSecurityID", "LegSecurityIDSource", "NoLegSecurityAltID", "LegProduct", "LegCFICode", "LegSecurityType", "LegSecuritySubType", "LegMaturityMonthYear", "LegMaturityDate", "LegCouponPaymentDate", "LegIssueDate", "LegRepoCollateralSecurityType", "LegRepurchaseTerm", "LegRepurchaseRate", "LegFactor", "LegCreditRating", "LegInstrRegistry", "LegCountryOfIssue", "LegStateOrProvinceOfIssue", "LegLocaleOfIssue", "LegRedemptionDate", "LegStrikePrice", "LegStrikeCurrency", "LegOptAttribute", "LegContractMultiplier", "LegCouponRate", "LegSecurityExchange", "LegIssuer", "EncodedIssuerLen", "EncodedLegIssuer", "LegSecurityDesc", "EncodedLegSecurityDescLen", "EncodedLegSecurityDesc", "LegRatioQty", "LegSide", "LegCurrency", "LegPool", "LegDatedDate", "LegContractSettlMonth", "LegInterestAccrualDate"] 1084 | } 1085 | } -------------------------------------------------------------------------------- /pyfix/FIX44/__init__.py: -------------------------------------------------------------------------------- 1 | from pyfix.FIX44 import msgtype, messages 2 | 3 | __author__ = 'tom' 4 | 5 | beginstring = 'FIX.4.4' 6 | 7 | -------------------------------------------------------------------------------- /pyfix/FIX44/fixtags.py: -------------------------------------------------------------------------------- 1 | Account = "1" 2 | AdvId = "2" 3 | AdvRefID = "3" 4 | AdvSide = "4" 5 | AdvTransType = "5" 6 | AvgPx = "6" 7 | BeginSeqNo = "7" 8 | BeginString = "8" 9 | BodyLength = "9" 10 | CheckSum = "10" 11 | ClOrdID = "11" 12 | Commission = "12" 13 | CommType = "13" 14 | CumQty = "14" 15 | Currency = "15" 16 | EndSeqNo = "16" 17 | ExecID = "17" 18 | ExecInst = "18" 19 | ExecRefID = "19" 20 | ExecTransType = "20" 21 | HandlInst = "21" 22 | SecurityIDSource = "22" 23 | IOIID = "23" 24 | IOIOthSvc = "24" 25 | IOIQltyInd = "25" 26 | IOIRefID = "26" 27 | IOIQty = "27" 28 | IOITransType = "28" 29 | LastCapacity = "29" 30 | LastMkt = "30" 31 | LastPx = "31" 32 | LastQty = "32" 33 | NoLinesOfText = "33" 34 | MsgSeqNum = "34" 35 | MsgType = "35" 36 | NewSeqNo = "36" 37 | OrderID = "37" 38 | OrderQty = "38" 39 | OrdStatus = "39" 40 | OrdType = "40" 41 | OrigClOrdID = "41" 42 | OrigTime = "42" 43 | PossDupFlag = "43" 44 | Price = "44" 45 | RefSeqNum = "45" 46 | RelatdSym = "46" 47 | Rule80A = "47" 48 | SecurityID = "48" 49 | SenderCompID = "49" 50 | SenderSubID = "50" 51 | SendingDate = "51" 52 | SendingTime = "52" 53 | Quantity = "53" 54 | Side = "54" 55 | Symbol = "55" 56 | TargetCompID = "56" 57 | TargetSubID = "57" 58 | Text = "58" 59 | TimeInForce = "59" 60 | TransactTime = "60" 61 | Urgency = "61" 62 | ValidUntilTime = "62" 63 | SettlType = "63" 64 | SettlDate = "64" 65 | SymbolSfx = "65" 66 | ListID = "66" 67 | ListSeqNo = "67" 68 | TotNoOrders = "68" 69 | ListExecInst = "69" 70 | AllocID = "70" 71 | AllocTransType = "71" 72 | RefAllocID = "72" 73 | NoOrders = "73" 74 | AvgPxPrecision = "74" 75 | TradeDate = "75" 76 | ExecBroker = "76" 77 | PositionEffect = "77" 78 | NoAllocs = "78" 79 | AllocAccount = "79" 80 | AllocQty = "80" 81 | ProcessCode = "81" 82 | NoRpts = "82" 83 | RptSeq = "83" 84 | CxlQty = "84" 85 | NoDlvyInst = "85" 86 | DlvyInst = "86" 87 | AllocStatus = "87" 88 | AllocRejCode = "88" 89 | Signature = "89" 90 | SecureDataLen = "90" 91 | SecureData = "91" 92 | BrokerOfCredit = "92" 93 | SignatureLength = "93" 94 | EmailType = "94" 95 | RawDataLength = "95" 96 | RawData = "96" 97 | PossResend = "97" 98 | EncryptMethod = "98" 99 | StopPx = "99" 100 | ExDestination = "100" 101 | CxlRejReason = "102" 102 | OrdRejReason = "103" 103 | IOIQualifier = "104" 104 | WaveNo = "105" 105 | Issuer = "106" 106 | SecurityDesc = "107" 107 | HeartBtInt = "108" 108 | ClientID = "109" 109 | MinQty = "110" 110 | MaxFloor = "111" 111 | TestReqID = "112" 112 | ReportToExch = "113" 113 | LocateReqd = "114" 114 | OnBehalfOfCompID = "115" 115 | OnBehalfOfSubID = "116" 116 | QuoteID = "117" 117 | NetMoney = "118" 118 | SettlCurrAmt = "119" 119 | SettlCurrency = "120" 120 | ForexReq = "121" 121 | OrigSendingTime = "122" 122 | GapFillFlag = "123" 123 | NoExecs = "124" 124 | CxlType = "125" 125 | ExpireTime = "126" 126 | DKReason = "127" 127 | DeliverToCompID = "128" 128 | DeliverToSubID = "129" 129 | IOINaturalFlag = "130" 130 | QuoteReqID = "131" 131 | BidPx = "132" 132 | OfferPx = "133" 133 | BidSize = "134" 134 | OfferSize = "135" 135 | NoMiscFees = "136" 136 | MiscFeeAmt = "137" 137 | MiscFeeCurr = "138" 138 | MiscFeeType = "139" 139 | PrevClosePx = "140" 140 | ResetSeqNumFlag = "141" 141 | SenderLocationID = "142" 142 | TargetLocationID = "143" 143 | OnBehalfOfLocationID = "144" 144 | DeliverToLocationID = "145" 145 | NoRelatedSym = "146" 146 | Subject = "147" 147 | Headline = "148" 148 | URLLink = "149" 149 | ExecType = "150" 150 | LeavesQty = "151" 151 | CashOrderQty = "152" 152 | AllocAvgPx = "153" 153 | AllocNetMoney = "154" 154 | SettlCurrFxRate = "155" 155 | SettlCurrFxRateCalc = "156" 156 | NumDaysInterest = "157" 157 | AccruedInterestRate = "158" 158 | AccruedInterestAmt = "159" 159 | SettlInstMode = "160" 160 | AllocText = "161" 161 | SettlInstID = "162" 162 | SettlInstTransType = "163" 163 | EmailThreadID = "164" 164 | SettlInstSource = "165" 165 | SettlLocation = "166" 166 | SecurityType = "167" 167 | EffectiveTime = "168" 168 | StandInstDbType = "169" 169 | StandInstDbName = "170" 170 | StandInstDbID = "171" 171 | SettlDeliveryType = "172" 172 | SettlDepositoryCode = "173" 173 | SettlBrkrCode = "174" 174 | SettlInstCode = "175" 175 | SecuritySettlAgentName = "176" 176 | SecuritySettlAgentCode = "177" 177 | SecuritySettlAgentAcctNum = "178" 178 | SecuritySettlAgentAcctName = "179" 179 | SecuritySettlAgentContactName = "180" 180 | SecuritySettlAgentContactPhone = "181" 181 | CashSettlAgentName = "182" 182 | CashSettlAgentCode = "183" 183 | CashSettlAgentAcctNum = "184" 184 | CashSettlAgentAcctName = "185" 185 | CashSettlAgentContactName = "186" 186 | CashSettlAgentContactPhone = "187" 187 | BidSpotRate = "188" 188 | BidForwardPoints = "189" 189 | OfferSpotRate = "190" 190 | OfferForwardPoints = "191" 191 | OrderQty2 = "192" 192 | SettlDate2 = "193" 193 | LastSpotRate = "194" 194 | LastForwardPoints = "195" 195 | AllocLinkID = "196" 196 | AllocLinkType = "197" 197 | SecondaryOrderID = "198" 198 | NoIOIQualifiers = "199" 199 | MaturityMonthYear = "200" 200 | PutOrCall = "201" 201 | StrikePrice = "202" 202 | CoveredOrUncovered = "203" 203 | CustomerOrFirm = "204" 204 | MaturityDay = "205" 205 | OptAttribute = "206" 206 | SecurityExchange = "207" 207 | NotifyBrokerOfCredit = "208" 208 | AllocHandlInst = "209" 209 | MaxShow = "210" 210 | PegOffsetValue = "211" 211 | XmlDataLen = "212" 212 | XmlData = "213" 213 | SettlInstRefID = "214" 214 | NoRoutingIDs = "215" 215 | RoutingType = "216" 216 | RoutingID = "217" 217 | Spread = "218" 218 | Benchmark = "219" 219 | BenchmarkCurveCurrency = "220" 220 | BenchmarkCurveName = "221" 221 | BenchmarkCurvePoint = "222" 222 | CouponRate = "223" 223 | CouponPaymentDate = "224" 224 | IssueDate = "225" 225 | RepurchaseTerm = "226" 226 | RepurchaseRate = "227" 227 | Factor = "228" 228 | TradeOriginationDate = "229" 229 | ExDate = "230" 230 | ContractMultiplier = "231" 231 | NoStipulations = "232" 232 | StipulationType = "233" 233 | StipulationValue = "234" 234 | YieldType = "235" 235 | Yield = "236" 236 | TotalTakedown = "237" 237 | Concession = "238" 238 | RepoCollateralSecurityType = "239" 239 | RedemptionDate = "240" 240 | UnderlyingCouponPaymentDate = "241" 241 | UnderlyingIssueDate = "242" 242 | UnderlyingRepoCollateralSecurityType = "243" 243 | UnderlyingRepurchaseTerm = "244" 244 | UnderlyingRepurchaseRate = "245" 245 | UnderlyingFactor = "246" 246 | UnderlyingRedemptionDate = "247" 247 | LegCouponPaymentDate = "248" 248 | LegIssueDate = "249" 249 | LegRepoCollateralSecurityType = "250" 250 | LegRepurchaseTerm = "251" 251 | LegRepurchaseRate = "252" 252 | LegFactor = "253" 253 | LegRedemptionDate = "254" 254 | CreditRating = "255" 255 | UnderlyingCreditRating = "256" 256 | LegCreditRating = "257" 257 | TradedFlatSwitch = "258" 258 | BasisFeatureDate = "259" 259 | BasisFeaturePrice = "260" 260 | MDReqID = "262" 261 | SubscriptionRequestType = "263" 262 | MarketDepth = "264" 263 | MDUpdateType = "265" 264 | AggregatedBook = "266" 265 | NoMDEntryTypes = "267" 266 | NoMDEntries = "268" 267 | MDEntryType = "269" 268 | MDEntryPx = "270" 269 | MDEntrySize = "271" 270 | MDEntryDate = "272" 271 | MDEntryTime = "273" 272 | TickDirection = "274" 273 | MDMkt = "275" 274 | QuoteCondition = "276" 275 | TradeCondition = "277" 276 | MDEntryID = "278" 277 | MDUpdateAction = "279" 278 | MDEntryRefID = "280" 279 | MDReqRejReason = "281" 280 | MDEntryOriginator = "282" 281 | LocationID = "283" 282 | DeskID = "284" 283 | DeleteReason = "285" 284 | OpenCloseSettlFlag = "286" 285 | SellerDays = "287" 286 | MDEntryBuyer = "288" 287 | MDEntrySeller = "289" 288 | MDEntryPositionNo = "290" 289 | FinancialStatus = "291" 290 | CorporateAction = "292" 291 | DefBidSize = "293" 292 | DefOfferSize = "294" 293 | NoQuoteEntries = "295" 294 | NoQuoteSets = "296" 295 | QuoteStatus = "297" 296 | QuoteCancelType = "298" 297 | QuoteEntryID = "299" 298 | QuoteRejectReason = "300" 299 | QuoteResponseLevel = "301" 300 | QuoteSetID = "302" 301 | QuoteRequestType = "303" 302 | TotNoQuoteEntries = "304" 303 | UnderlyingSecurityIDSource = "305" 304 | UnderlyingIssuer = "306" 305 | UnderlyingSecurityDesc = "307" 306 | UnderlyingSecurityExchange = "308" 307 | UnderlyingSecurityID = "309" 308 | UnderlyingSecurityType = "310" 309 | UnderlyingSymbol = "311" 310 | UnderlyingSymbolSfx = "312" 311 | UnderlyingMaturityMonthYear = "313" 312 | UnderlyingMaturityDay = "314" 313 | UnderlyingPutOrCall = "315" 314 | UnderlyingStrikePrice = "316" 315 | UnderlyingOptAttribute = "317" 316 | UnderlyingCurrency = "318" 317 | RatioQty = "319" 318 | SecurityReqID = "320" 319 | SecurityRequestType = "321" 320 | SecurityResponseID = "322" 321 | SecurityResponseType = "323" 322 | SecurityStatusReqID = "324" 323 | UnsolicitedIndicator = "325" 324 | SecurityTradingStatus = "326" 325 | HaltReason = "327" 326 | InViewOfCommon = "328" 327 | DueToRelated = "329" 328 | BuyVolume = "330" 329 | SellVolume = "331" 330 | HighPx = "332" 331 | LowPx = "333" 332 | Adjustment = "334" 333 | TradSesReqID = "335" 334 | TradingSessionID = "336" 335 | ContraTrader = "337" 336 | TradSesMethod = "338" 337 | TradSesMode = "339" 338 | TradSesStatus = "340" 339 | TradSesStartTime = "341" 340 | TradSesOpenTime = "342" 341 | TradSesPreCloseTime = "343" 342 | TradSesCloseTime = "344" 343 | TradSesEndTime = "345" 344 | NumberOfOrders = "346" 345 | MessageEncoding = "347" 346 | EncodedIssuerLen = "348" 347 | EncodedIssuer = "349" 348 | EncodedSecurityDescLen = "350" 349 | EncodedSecurityDesc = "351" 350 | EncodedListExecInstLen = "352" 351 | EncodedListExecInst = "353" 352 | EncodedTextLen = "354" 353 | EncodedText = "355" 354 | EncodedSubjectLen = "356" 355 | EncodedSubject = "357" 356 | EncodedHeadlineLen = "358" 357 | EncodedHeadline = "359" 358 | EncodedAllocTextLen = "360" 359 | EncodedAllocText = "361" 360 | EncodedUnderlyingIssuerLen = "362" 361 | EncodedUnderlyingIssuer = "363" 362 | EncodedUnderlyingSecurityDescLen = "364" 363 | EncodedUnderlyingSecurityDesc = "365" 364 | AllocPrice = "366" 365 | QuoteSetValidUntilTime = "367" 366 | QuoteEntryRejectReason = "368" 367 | LastMsgSeqNumProcessed = "369" 368 | OnBehalfOfSendingTime = "370" 369 | RefTagID = "371" 370 | RefMsgType = "372" 371 | SessionRejectReason = "373" 372 | BidRequestTransType = "374" 373 | ContraBroker = "375" 374 | ComplianceID = "376" 375 | SolicitedFlag = "377" 376 | ExecRestatementReason = "378" 377 | BusinessRejectRefID = "379" 378 | BusinessRejectReason = "380" 379 | GrossTradeAmt = "381" 380 | NoContraBrokers = "382" 381 | MaxMessageSize = "383" 382 | NoMsgTypes = "384" 383 | MsgDirection = "385" 384 | NoTradingSessions = "386" 385 | TotalVolumeTraded = "387" 386 | DiscretionInst = "388" 387 | DiscretionOffsetValue = "389" 388 | BidID = "390" 389 | ClientBidID = "391" 390 | ListName = "392" 391 | TotNoRelatedSym = "393" 392 | BidType = "394" 393 | NumTickets = "395" 394 | SideValue1 = "396" 395 | SideValue2 = "397" 396 | NoBidDescriptors = "398" 397 | BidDescriptorType = "399" 398 | BidDescriptor = "400" 399 | SideValueInd = "401" 400 | LiquidityPctLow = "402" 401 | LiquidityPctHigh = "403" 402 | LiquidityValue = "404" 403 | EFPTrackingError = "405" 404 | FairValue = "406" 405 | OutsideIndexPct = "407" 406 | ValueOfFutures = "408" 407 | LiquidityIndType = "409" 408 | WtAverageLiquidity = "410" 409 | ExchangeForPhysical = "411" 410 | OutMainCntryUIndex = "412" 411 | CrossPercent = "413" 412 | ProgRptReqs = "414" 413 | ProgPeriodInterval = "415" 414 | IncTaxInd = "416" 415 | NumBidders = "417" 416 | BidTradeType = "418" 417 | BasisPxType = "419" 418 | NoBidComponents = "420" 419 | Country = "421" 420 | TotNoStrikes = "422" 421 | PriceType = "423" 422 | DayOrderQty = "424" 423 | DayCumQty = "425" 424 | DayAvgPx = "426" 425 | GTBookingInst = "427" 426 | NoStrikes = "428" 427 | ListStatusType = "429" 428 | NetGrossInd = "430" 429 | ListOrderStatus = "431" 430 | ExpireDate = "432" 431 | ListExecInstType = "433" 432 | CxlRejResponseTo = "434" 433 | UnderlyingCouponRate = "435" 434 | UnderlyingContractMultiplier = "436" 435 | ContraTradeQty = "437" 436 | ContraTradeTime = "438" 437 | ClearingFirm = "439" 438 | ClearingAccount = "440" 439 | LiquidityNumSecurities = "441" 440 | MultiLegReportingType = "442" 441 | StrikeTime = "443" 442 | ListStatusText = "444" 443 | EncodedListStatusTextLen = "445" 444 | EncodedListStatusText = "446" 445 | PartyIDSource = "447" 446 | PartyID = "448" 447 | TotalVolumeTradedDate = "449" 448 | TotalVolumeTraded = "450" 449 | NetChgPrevDay = "451" 450 | PartyRole = "452" 451 | NoPartyIDs = "453" 452 | NoSecurityAltID = "454" 453 | SecurityAltID = "455" 454 | SecurityAltIDSource = "456" 455 | NoUnderlyingSecurityAltID = "457" 456 | UnderlyingSecurityAltID = "458" 457 | UnderlyingSecurityAltIDSource = "459" 458 | Product = "460" 459 | CFICode = "461" 460 | UnderlyingProduct = "462" 461 | UnderlyingCFICode = "463" 462 | TestMessageIndicator = "464" 463 | QuantityType = "465" 464 | BookingRefID = "466" 465 | IndividualAllocID = "467" 466 | RoundingDirection = "468" 467 | RoundingModulus = "469" 468 | CountryOfIssue = "470" 469 | StateOrProvinceOfIssue = "471" 470 | LocaleOfIssue = "472" 471 | NoRegistDtls = "473" 472 | MailingDtls = "474" 473 | InvestorCountryOfResidence = "475" 474 | PaymentRef = "476" 475 | DistribPaymentMethod = "477" 476 | CashDistribCurr = "478" 477 | CommCurrency = "479" 478 | CancellationRights = "480" 479 | MoneyLaunderingStatus = "481" 480 | MailingInst = "482" 481 | TransBkdTime = "483" 482 | ExecPriceType = "484" 483 | ExecPriceAdjustment = "485" 484 | DateOfBirth = "486" 485 | TradeReportTransType = "487" 486 | CardHolderName = "488" 487 | CardNumber = "489" 488 | CardExpDate = "490" 489 | CardIssNum = "491" 490 | PaymentMethod = "492" 491 | RegistAcctType = "493" 492 | Designation = "494" 493 | TaxAdvantageType = "495" 494 | RegistRejReasonText = "496" 495 | FundRenewWaiv = "497" 496 | CashDistribAgentName = "498" 497 | CashDistribAgentCode = "499" 498 | CashDistribAgentAcctNumber = "500" 499 | CashDistribPayRef = "501" 500 | CashDistribAgentAcctName = "502" 501 | CardStartDate = "503" 502 | PaymentDate = "504" 503 | PaymentRemitterID = "505" 504 | RegistStatus = "506" 505 | RegistRejReasonCode = "507" 506 | RegistRefID = "508" 507 | RegistDtls = "509" 508 | NoDistribInsts = "510" 509 | RegistEmail = "511" 510 | DistribPercentage = "512" 511 | RegistID = "513" 512 | RegistTransType = "514" 513 | ExecValuationPoint = "515" 514 | OrderPercent = "516" 515 | OwnershipType = "517" 516 | NoContAmts = "518" 517 | ContAmtType = "519" 518 | ContAmtValue = "520" 519 | ContAmtCurr = "521" 520 | OwnerType = "522" 521 | PartySubID = "523" 522 | NestedPartyID = "524" 523 | NestedPartyIDSource = "525" 524 | SecondaryClOrdID = "526" 525 | SecondaryExecID = "527" 526 | OrderCapacity = "528" 527 | OrderRestrictions = "529" 528 | MassCancelRequestType = "530" 529 | MassCancelResponse = "531" 530 | MassCancelRejectReason = "532" 531 | TotalAffectedOrders = "533" 532 | NoAffectedOrders = "534" 533 | AffectedOrderID = "535" 534 | AffectedSecondaryOrderID = "536" 535 | QuoteType = "537" 536 | NestedPartyRole = "538" 537 | NoNestedPartyIDs = "539" 538 | TotalAccruedInterestAmt = "540" 539 | MaturityDate = "541" 540 | UnderlyingMaturityDate = "542" 541 | InstrRegistry = "543" 542 | CashMargin = "544" 543 | NestedPartySubID = "545" 544 | Scope = "546" 545 | MDImplicitDelete = "547" 546 | CrossID = "548" 547 | CrossType = "549" 548 | CrossPrioritization = "550" 549 | OrigCrossID = "551" 550 | NoSides = "552" 551 | Username = "553" 552 | Password = "554" 553 | NoLegs = "555" 554 | LegCurrency = "556" 555 | TotNoSecurityTypes = "557" 556 | NoSecurityTypes = "558" 557 | SecurityListRequestType = "559" 558 | SecurityRequestResult = "560" 559 | RoundLot = "561" 560 | MinTradeVol = "562" 561 | MultiLegRptTypeReq = "563" 562 | LegPositionEffect = "564" 563 | LegCoveredOrUncovered = "565" 564 | LegPrice = "566" 565 | TradSesStatusRejReason = "567" 566 | TradeRequestID = "568" 567 | TradeRequestType = "569" 568 | PreviouslyReported = "570" 569 | TradeReportID = "571" 570 | TradeReportRefID = "572" 571 | MatchStatus = "573" 572 | MatchType = "574" 573 | OddLot = "575" 574 | NoClearingInstructions = "576" 575 | ClearingInstruction = "577" 576 | TradeInputSource = "578" 577 | TradeInputDevice = "579" 578 | NoDates = "580" 579 | AccountType = "581" 580 | CustOrderCapacity = "582" 581 | ClOrdLinkID = "583" 582 | MassStatusReqID = "584" 583 | MassStatusReqType = "585" 584 | OrigOrdModTime = "586" 585 | LegSettlType = "587" 586 | LegSettlDate = "588" 587 | DayBookingInst = "589" 588 | BookingUnit = "590" 589 | PreallocMethod = "591" 590 | UnderlyingCountryOfIssue = "592" 591 | UnderlyingStateOrProvinceOfIssue = "593" 592 | UnderlyingLocaleOfIssue = "594" 593 | UnderlyingInstrRegistry = "595" 594 | LegCountryOfIssue = "596" 595 | LegStateOrProvinceOfIssue = "597" 596 | LegLocaleOfIssue = "598" 597 | LegInstrRegistry = "599" 598 | LegSymbol = "600" 599 | LegSymbolSfx = "601" 600 | LegSecurityID = "602" 601 | LegSecurityIDSource = "603" 602 | NoLegSecurityAltID = "604" 603 | LegSecurityAltID = "605" 604 | LegSecurityAltIDSource = "606" 605 | LegProduct = "607" 606 | LegCFICode = "608" 607 | LegSecurityType = "609" 608 | LegMaturityMonthYear = "610" 609 | LegMaturityDate = "611" 610 | LegStrikePrice = "612" 611 | LegOptAttribute = "613" 612 | LegContractMultiplier = "614" 613 | LegCouponRate = "615" 614 | LegSecurityExchange = "616" 615 | LegIssuer = "617" 616 | EncodedLegIssuerLen = "618" 617 | EncodedLegIssuer = "619" 618 | LegSecurityDesc = "620" 619 | EncodedLegSecurityDescLen = "621" 620 | EncodedLegSecurityDesc = "622" 621 | LegRatioQty = "623" 622 | LegSide = "624" 623 | TradingSessionSubID = "625" 624 | AllocType = "626" 625 | NoHops = "627" 626 | HopCompID = "628" 627 | HopSendingTime = "629" 628 | HopRefID = "630" 629 | MidPx = "631" 630 | BidYield = "632" 631 | MidYield = "633" 632 | OfferYield = "634" 633 | ClearingFeeIndicator = "635" 634 | WorkingIndicator = "636" 635 | LegLastPx = "637" 636 | PriorityIndicator = "638" 637 | PriceImprovement = "639" 638 | Price2 = "640" 639 | LastForwardPoints2 = "641" 640 | BidForwardPoints2 = "642" 641 | OfferForwardPoints2 = "643" 642 | RFQReqID = "644" 643 | MktBidPx = "645" 644 | MktOfferPx = "646" 645 | MinBidSize = "647" 646 | MinOfferSize = "648" 647 | QuoteStatusReqID = "649" 648 | LegalConfirm = "650" 649 | UnderlyingLastPx = "651" 650 | UnderlyingLastQty = "652" 651 | SecDefStatus = "653" 652 | LegRefID = "654" 653 | ContraLegRefID = "655" 654 | SettlCurrBidFxRate = "656" 655 | SettlCurrOfferFxRate = "657" 656 | QuoteRequestRejectReason = "658" 657 | SideComplianceID = "659" 658 | AcctIDSource = "660" 659 | AllocAcctIDSource = "661" 660 | BenchmarkPrice = "662" 661 | BenchmarkPriceType = "663" 662 | ConfirmID = "664" 663 | ConfirmStatus = "665" 664 | ConfirmTransType = "666" 665 | ContractSettlMonth = "667" 666 | DeliveryForm = "668" 667 | LastParPx = "669" 668 | NoLegAllocs = "670" 669 | LegAllocAccount = "671" 670 | LegIndividualAllocID = "672" 671 | LegAllocQty = "673" 672 | LegAllocAcctIDSource = "674" 673 | LegSettlCurrency = "675" 674 | LegBenchmarkCurveCurrency = "676" 675 | LegBenchmarkCurveName = "677" 676 | LegBenchmarkCurvePoint = "678" 677 | LegBenchmarkPrice = "679" 678 | LegBenchmarkPriceType = "680" 679 | LegBidPx = "681" 680 | LegIOIQty = "682" 681 | NoLegStipulations = "683" 682 | LegOfferPx = "684" 683 | LegOrderQty = "685" 684 | LegPriceType = "686" 685 | LegQty = "687" 686 | LegStipulationType = "688" 687 | LegStipulationValue = "689" 688 | LegSwapType = "690" 689 | Pool = "691" 690 | QuotePriceType = "692" 691 | QuoteRespID = "693" 692 | QuoteRespType = "694" 693 | QuoteQualifier = "695" 694 | YieldRedemptionDate = "696" 695 | YieldRedemptionPrice = "697" 696 | YieldRedemptionPriceType = "698" 697 | BenchmarkSecurityID = "699" 698 | ReversalIndicator = "700" 699 | YieldCalcDate = "701" 700 | NoPositions = "702" 701 | PosType = "703" 702 | LongQty = "704" 703 | ShortQty = "705" 704 | PosQtyStatus = "706" 705 | PosAmtType = "707" 706 | PosAmt = "708" 707 | PosTransType = "709" 708 | PosReqID = "710" 709 | NoUnderlyings = "711" 710 | PosMaintAction = "712" 711 | OrigPosReqRefID = "713" 712 | PosMaintRptRefID = "714" 713 | ClearingBusinessDate = "715" 714 | SettlSessID = "716" 715 | SettlSessSubID = "717" 716 | AdjustmentType = "718" 717 | ContraryInstructionIndicator = "719" 718 | PriorSpreadIndicator = "720" 719 | PosMaintRptID = "721" 720 | PosMaintStatus = "722" 721 | PosMaintResult = "723" 722 | PosReqType = "724" 723 | ResponseTransportType = "725" 724 | ResponseDestination = "726" 725 | TotalNumPosReports = "727" 726 | PosReqResult = "728" 727 | PosReqStatus = "729" 728 | SettlPrice = "730" 729 | SettlPriceType = "731" 730 | UnderlyingSettlPrice = "732" 731 | UnderlyingSettlPriceType = "733" 732 | PriorSettlPrice = "734" 733 | NoQuoteQualifiers = "735" 734 | AllocSettlCurrency = "736" 735 | AllocSettlCurrAmt = "737" 736 | InterestAtMaturity = "738" 737 | LegDatedDate = "739" 738 | LegPool = "740" 739 | AllocInterestAtMaturity = "741" 740 | AllocAccruedInterestAmt = "742" 741 | DeliveryDate = "743" 742 | AssignmentMethod = "744" 743 | AssignmentUnit = "745" 744 | OpenInterest = "746" 745 | ExerciseMethod = "747" 746 | TotNumTradeReports = "748" 747 | TradeRequestResult = "749" 748 | TradeRequestStatus = "750" 749 | TradeReportRejectReason = "751" 750 | SideMultiLegReportingType = "752" 751 | NoPosAmt = "753" 752 | AutoAcceptIndicator = "754" 753 | AllocReportID = "755" 754 | NoNested2PartyIDs = "756" 755 | Nested2PartyID = "757" 756 | Nested2PartyIDSource = "758" 757 | Nested2PartyRole = "759" 758 | Nested2PartySubID = "760" 759 | BenchmarkSecurityIDSource = "761" 760 | SecuritySubType = "762" 761 | UnderlyingSecuritySubType = "763" 762 | LegSecuritySubType = "764" 763 | AllowableOneSidednessPct = "765" 764 | AllowableOneSidednessValue = "766" 765 | AllowableOneSidednessCurr = "767" 766 | NoTrdRegTimestamps = "768" 767 | TrdRegTimestamp = "769" 768 | TrdRegTimestampType = "770" 769 | TrdRegTimestampOrigin = "771" 770 | ConfirmRefID = "772" 771 | ConfirmType = "773" 772 | ConfirmRejReason = "774" 773 | BookingType = "775" 774 | IndividualAllocRejCode = "776" 775 | SettlInstMsgID = "777" 776 | NoSettlInst = "778" 777 | LastUpdateTime = "779" 778 | AllocSettlInstType = "780" 779 | NoSettlPartyIDs = "781" 780 | SettlPartyID = "782" 781 | SettlPartyIDSource = "783" 782 | SettlPartyRole = "784" 783 | SettlPartySubID = "785" 784 | SettlPartySubIDType = "786" 785 | DlvyInstType = "787" 786 | TerminationType = "788" 787 | NextExpectedMsgSeqNum = "789" 788 | OrdStatusReqID = "790" 789 | SettlInstReqID = "791" 790 | SettlInstReqRejCode = "792" 791 | SecondaryAllocID = "793" 792 | AllocReportType = "794" 793 | AllocReportRefID = "795" 794 | AllocCancReplaceReason = "796" 795 | CopyMsgIndicator = "797" 796 | AllocAccountType = "798" 797 | OrderAvgPx = "799" 798 | OrderBookingQty = "800" 799 | NoSettlPartySubIDs = "801" 800 | NoPartySubIDs = "802" 801 | PartySubIDType = "803" 802 | NoNestedPartySubIDs = "804" 803 | NestedPartySubIDType = "805" 804 | NoNested2PartySubIDs = "806" 805 | Nested2PartySubIDType = "807" 806 | AllocIntermedReqType = "808" 807 | UnderlyingPx = "810" 808 | PriceDelta = "811" 809 | ApplQueueMax = "812" 810 | ApplQueueDepth = "813" 811 | ApplQueueResolution = "814" 812 | ApplQueueAction = "815" 813 | NoAltMDSource = "816" 814 | AltMDSourceID = "817" 815 | SecondaryTradeReportID = "818" 816 | AvgPxIndicator = "819" 817 | TradeLinkID = "820" 818 | OrderInputDevice = "821" 819 | UnderlyingTradingSessionID = "822" 820 | UnderlyingTradingSessionSubID = "823" 821 | TradeLegRefID = "824" 822 | ExchangeRule = "825" 823 | TradeAllocIndicator = "826" 824 | ExpirationCycle = "827" 825 | TrdType = "828" 826 | TrdSubType = "829" 827 | TransferReason = "830" 828 | AsgnReqID = "831" 829 | TotNumAssignmentReports = "832" 830 | AsgnRptID = "833" 831 | ThresholdAmount = "834" 832 | PegMoveType = "835" 833 | PegOffsetType = "836" 834 | PegLimitType = "837" 835 | PegRoundDirection = "838" 836 | PeggedPrice = "839" 837 | PegScope = "840" 838 | DiscretionMoveType = "841" 839 | DiscretionOffsetType = "842" 840 | DiscretionLimitType = "843" 841 | DiscretionRoundDirection = "844" 842 | DiscretionPrice = "845" 843 | DiscretionScope = "846" 844 | TargetStrategy = "847" 845 | TargetStrategyParameters = "848" 846 | ParticipationRate = "849" 847 | TargetStrategyPerformance = "850" 848 | LastLiquidityInd = "851" 849 | PublishTrdIndicator = "852" 850 | ShortSaleReason = "853" 851 | QtyType = "854" 852 | SecondaryTrdType = "855" 853 | TradeReportType = "856" 854 | AllocNoOrdersType = "857" 855 | SharedCommission = "858" 856 | ConfirmReqID = "859" 857 | AvgParPx = "860" 858 | ReportedPx = "861" 859 | NoCapacities = "862" 860 | OrderCapacityQty = "863" 861 | NoEvents = "864" 862 | EventType = "865" 863 | EventDate = "866" 864 | EventPx = "867" 865 | EventText = "868" 866 | PctAtRisk = "869" 867 | NoInstrAttrib = "870" 868 | InstrAttribType = "871" 869 | InstrAttribValue = "872" 870 | DatedDate = "873" 871 | InterestAccrualDate = "874" 872 | CPProgram = "875" 873 | CPRegType = "876" 874 | UnderlyingCPProgram = "877" 875 | UnderlyingCPRegType = "878" 876 | UnderlyingQty = "879" 877 | TrdMatchID = "880" 878 | SecondaryTradeReportRefID = "881" 879 | UnderlyingDirtyPrice = "882" 880 | UnderlyingEndPrice = "883" 881 | UnderlyingStartValue = "884" 882 | UnderlyingCurrentValue = "885" 883 | UnderlyingEndValue = "886" 884 | NoUnderlyingStips = "887" 885 | UnderlyingStipType = "888" 886 | UnderlyingStipValue = "889" 887 | MaturityNetMoney = "890" 888 | MiscFeeBasis = "891" 889 | TotNoAllocs = "892" 890 | LastFragment = "893" 891 | CollReqID = "894" 892 | CollAsgnReason = "895" 893 | CollInquiryQualifier = "896" 894 | NoTrades = "897" 895 | MarginRatio = "898" 896 | MarginExcess = "899" 897 | TotalNetValue = "900" 898 | CashOutstanding = "901" 899 | CollAsgnID = "902" 900 | CollAsgnTransType = "903" 901 | CollRespID = "904" 902 | CollAsgnRespType = "905" 903 | CollAsgnRejectReason = "906" 904 | CollAsgnRefID = "907" 905 | CollRptID = "908" 906 | CollInquiryID = "909" 907 | CollStatus = "910" 908 | TotNumReports = "911" 909 | LastRptRequested = "912" 910 | AgreementDesc = "913" 911 | AgreementID = "914" 912 | AgreementDate = "915" 913 | StartDate = "916" 914 | EndDate = "917" 915 | AgreementCurrency = "918" 916 | DeliveryType = "919" 917 | EndAccruedInterestAmt = "920" 918 | StartCash = "921" 919 | EndCash = "922" 920 | UserRequestID = "923" 921 | UserRequestType = "924" 922 | NewPassword = "925" 923 | UserStatus = "926" 924 | UserStatusText = "927" 925 | StatusValue = "928" 926 | StatusText = "929" 927 | RefCompID = "930" 928 | RefSubID = "931" 929 | NetworkResponseID = "932" 930 | NetworkRequestID = "933" 931 | LastNetworkResponseID = "934" 932 | NetworkRequestType = "935" 933 | NoCompIDs = "936" 934 | NetworkStatusResponseType = "937" 935 | NoCollInquiryQualifier = "938" 936 | TrdRptStatus = "939" 937 | AffirmStatus = "940" 938 | UnderlyingStrikeCurrency = "941" 939 | LegStrikeCurrency = "942" 940 | TimeBracket = "943" 941 | CollAction = "944" 942 | CollInquiryStatus = "945" 943 | CollInquiryResult = "946" 944 | StrikeCurrency = "947" 945 | NoNested3PartyIDs = "948" 946 | Nested3PartyID = "949" 947 | Nested3PartyIDSource = "950" 948 | Nested3PartyRole = "951" 949 | NoNested3PartySubIDs = "952" 950 | Nested3PartySubID = "953" 951 | Nested3PartySubIDType = "954" 952 | LegContractSettlMonth = "955" 953 | LegInterestAccrualDate = "956" 954 | 955 | tags = {} 956 | for x in dir(): 957 | tags[str(globals()[x])] = x 958 | 959 | 960 | def tagToName(n): 961 | try: 962 | return tags[n] 963 | except KeyError: 964 | return str(n) 965 | 966 | def repeatingGroupIdentifiers(): 967 | return { 968 | NoSecurityAltID : [SecurityAltID, SecurityAltIDSource], 969 | NoMiscFees : [MiscFeeAmt, MiscFeeCurr, MiscFeeType, MiscFeeBasis], 970 | NoClearingInstructions : [ClearingInstruction, ], 971 | NoEvents : [EventType, EventDate, EventPx, EventText], 972 | NoInstrAttrib : [InstrAttribType, InstrAttribValue], 973 | NoLegSecurityAltID : [LegSecurityAltID, LegSecurityAltIDSource], 974 | NoLegStipulations : [LegStipulationType, LegStipulationValue], 975 | NoNestedPartyIDs : [NestedPartyID, NestedPartyIDSource, NestedPartyRole, NoNestedPartySubIDs], 976 | NoNestedPartySubIDs : [NestedPartySubID, NestedPartySubIDType], 977 | NoNested2PartyIDs : [Nested2PartyID, Nested2PartyIDSource, Nested2PartyRole, NoNested2PartySubIDs], 978 | NoNested2PartySubIDs : [Nested2PartySubID, Nested2PartySubIDType], 979 | NoNested3PartyIDs : [Nested3PartyID, Nested3PartyIDSource, Nested3PartyRole, NoNested3PartySubIDs], 980 | NoNested3PartySubIDs : [Nested3PartySubID, Nested3PartySubIDType], 981 | NoPartyIDs : [PartyID, PartyIDSource, PartyRole, NoPartySubIDs], 982 | NoPartySubIDs : [PartySubID, PartySubIDType], 983 | NoPosAmt : [PosAmtType, PosAmt], 984 | NoPositions : [PosType, LongQty, ShortQty, PosQtyStatus], 985 | NoDlvyInst : [SettlInstSource, DlvyInstType, NoSettlPartyIDs], 986 | NoSettlPartyIDs : [SettlPartyID, SettlPartyIDSource, SettlPartyRole, NoSettlPartySubIDs], 987 | NoSettlPartySubIDs : [SettlPartySubID, SettlPartySubIDType], 988 | NoStipulations : [StipulationType, StipulationValue], 989 | NoTrdRegTimestamps : [TrdRegTimestamp, TrdRegTimestampType, TrdRegTimestampOrigin], 990 | NoUnderlyingSecurityAltID : [UnderlyingSecurityAltID, UnderlyingSecurityAltIDSource], 991 | NoUnderlyingStips : [UnderlyingStipType, UnderlyingStipValue], 992 | NoOrders : [ClOrdID, OrderID, SecondaryOrderID, SecondaryClOrdID, ListID, OrderQty, OrderAvgPx, OrderBookingQty], 993 | NoExecs : [LastQty, ExecID, SecondaryExecID, LastPx, LastParPx, LastCapacity], 994 | NoUnderlyings : [UnderlyingSymbol, UnderlyingSymbolSfx, UnderlyingSecurityID, UnderlyingSecurityIDSource, NoUnderlyingSecurityAltID, UnderlyingProduct, UnderlyingCFICode, UnderlyingSecurityType, UnderlyingSecuritySubType, UnderlyingMaturityMonthYear, UnderlyingMaturityDate, UnderlyingPutOrCall, UnderlyingCouponPaymentDate, UnderlyingIssueDate, UnderlyingRepoCollateralSecurityType, UnderlyingRepurchaseTerm, UnderlyingRepurchaseRate, UnderlyingFactor, UnderlyingCreditRating, UnderlyingInstrRegistry, UnderlyingCountryOfIssue, UnderlyingStateOrProvinceOfIssue, UnderlyingLocaleOfIssue, UnderlyingRedemptionDate, UnderlyingStrikePrice, UnderlyingStrikeCurrency, UnderlyingOptAttribute, UnderlyingContractMultiplier, UnderlyingCouponRate, UnderlyingSecurityExchange, UnderlyingIssuer, EncodedUnderlyingIssuerLen, EncodedUnderlyingIssuer, UnderlyingSecurityDesc, EncodedUnderlyingSecurityDescLen, EncodedUnderlyingSecurityDesc, UnderlyingCPProgram, UnderlyingCPRegType, UnderlyingCurrency, UnderlyingQty, UnderlyingPx, UnderlyingDirtyPrice, UnderlyingEndPrice, UnderlyingStartValue, UnderlyingCurrentValue, UnderlyingEndValue, NoUnderlyingStips], 995 | NoAllocs : [AllocAccount, AllocAcctIDSource, MatchStatus, AllocPrice, AllocQty, IndividualAllocID, ProcessCode, NoNestedPartyIDs, NotifyBrokerOfCredit, AllocHandlInst, AllocText, EncodedAllocTextLen, EncodedAllocText, Commission, CommType, CommCurrency, FundRenewWaiv, AllocAvgPx, AllocNetMoney, SettlCurrAmt, AllocSettlCurrAmt, SettlCurrency, AllocSettlCurrency, SettlCurrFxRate, SettlCurrFxRateCalc, AllocAccruedInterestAmt, AllocInterestAtMaturity, NoMiscFees, NoClearingInstructions, AllocSettlInstType, SettlDeliveryType, StandInstDbType, StandInstDbName, StandInstDbID, NoDlvyInst], 996 | NoLegs : [LegSymbol, LegSymbolSfx, LegSecurityID, LegSecurityIDSource, NoLegSecurityAltID, LegProduct, LegCFICode, LegSecurityType, LegSecuritySubType, LegMaturityMonthYear, LegMaturityDate, LegCouponPaymentDate, LegIssueDate, LegRepoCollateralSecurityType, LegRepurchaseTerm, LegRepurchaseRate, LegFactor, LegCreditRating, LegInstrRegistry, LegCountryOfIssue, LegStateOrProvinceOfIssue, LegLocaleOfIssue, LegRedemptionDate, LegStrikePrice, LegStrikeCurrency, LegOptAttribute, LegContractMultiplier, LegCouponRate, LegSecurityExchange, LegIssuer, EncodedIssuerLen, EncodedLegIssuer, LegSecurityDesc, EncodedLegSecurityDescLen, EncodedLegSecurityDesc, LegRatioQty, LegSide, LegCurrency, LegPool, LegDatedDate, LegContractSettlMonth, LegInterestAccrualDate] 997 | } -------------------------------------------------------------------------------- /pyfix/FIX44/messages.py: -------------------------------------------------------------------------------- 1 | from pyfix.FIX44 import msgtype, fixtags 2 | from pyfix.message import FIXMessage 3 | 4 | class Messages(object): 5 | 6 | @staticmethod 7 | def logon(): 8 | msg = FIXMessage(msgtype.LOGON) 9 | msg.setField(fixtags.EncryptMethod, 0) 10 | msg.setField(fixtags.HeartBtInt, 30) 11 | return msg 12 | 13 | @staticmethod 14 | def logout(): 15 | msg = FIXMessage(msgtype.LOGOUT) 16 | return msg 17 | 18 | @staticmethod 19 | def heartbeat(): 20 | msg = FIXMessage(msgtype.HEARTBEAT) 21 | return msg 22 | 23 | @staticmethod 24 | def test_request(): 25 | msg = FIXMessage(msgtype.TESTREQUEST) 26 | return msg 27 | 28 | @staticmethod 29 | def sequence_reset(respondingTo, isGapFill): 30 | msg = FIXMessage(msgtype.SEQUENCERESET) 31 | msg.setField(fixtags.GapFillFlag, 'Y' if isGapFill else 'N') 32 | msg.setField(fixtags.MsgSeqNum, respondingTo[fixtags.BeginSeqNo]) 33 | return msg 34 | # 35 | # @staticmethod 36 | # def sequence_reset(beginSeqNo, endSeqNo, isGapFill): 37 | # msg = FIXMessage(msgtype.SEQUENCERESET) 38 | # msg.setField(fixtags.GapFillFlag, 'Y' if isGapFill else 'N') 39 | # msg.setField(fixtags.MsgSeqNum, respondingTo[fixtags.BeginSeqNo]) 40 | # return msg 41 | 42 | 43 | @staticmethod 44 | def resend_request(beginSeqNo, endSeqNo = '0'): 45 | msg = FIXMessage(msgtype.RESENDREQUEST) 46 | msg.setField(fixtags.BeginSeqNo, str(beginSeqNo)) 47 | msg.setField(fixtags.EndSeqNo, str(endSeqNo)) 48 | return msg -------------------------------------------------------------------------------- /pyfix/FIX44/msgtype.py: -------------------------------------------------------------------------------- 1 | HEARTBEAT = "0" 2 | TESTREQUEST = "1" 3 | RESENDREQUEST = "2" 4 | REJECT = "3" 5 | SEQUENCERESET = "4" 6 | LOGOUT = "5" 7 | IOI = "6" 8 | ADVERTISEMENT = "7" 9 | EXECUTIONREPORT = "8" 10 | ORDERCANCELREJECT = "9" 11 | QUOTESTATUSREQUEST = "a" 12 | LOGON = "A" 13 | DERIVATIVESECURITYLIST = "AA" 14 | NEWORDERMULTILEG = "AB" 15 | MULTILEGORDERCANCELREPLACE = "AC" 16 | TRADECAPTUREREPORTREQUEST = "AD" 17 | TRADECAPTUREREPORT = "AE" 18 | ORDERMASSSTATUSREQUEST = "AF" 19 | QUOTEREQUESTREJECT = "AG" 20 | RFQREQUEST = "AH" 21 | QUOTESTATUSREPORT = "AI" 22 | QUOTERESPONSE = "AJ" 23 | CONFIRMATION = "AK" 24 | POSITIONMAINTENANCEREQUEST = "AL" 25 | POSITIONMAINTENANCEREPORT = "AM" 26 | REQUESTFORPOSITIONS = "AN" 27 | REQUESTFORPOSITIONSACK = "AO" 28 | POSITIONREPORT = "AP" 29 | TRADECAPTUREREPORTREQUESTACK = "AQ" 30 | TRADECAPTUREREPORTACK = "AR" 31 | ALLOCATIONREPORT = "AS" 32 | ALLOCATIONREPORTACK = "AT" 33 | CONFIRMATIONACK = "AU" 34 | SETTLEMENTINSTRUCTIONREQUEST = "AV" 35 | ASSIGNMENTREPORT = "AW" 36 | COLLATERALREQUEST = "AX" 37 | COLLATERALASSIGNMENT = "AY" 38 | COLLATERALRESPONSE = "AZ" 39 | NEWS = "B" 40 | MASSQUOTEACKNOWLEDGEMENT = "b" 41 | COLLATERALREPORT = "BA" 42 | COLLATERALINQUIRY = "BB" 43 | NETWORKCOUNTERPARTYSYSTEMSTATUSREQUEST = "BC" 44 | NETWORKCOUNTERPARTYSYSTEMSTATUSRESPONSE = "BD" 45 | USERREQUEST = "BE" 46 | USERRESPONSE = "BF" 47 | COLLATERALINQUIRYACK = "BG" 48 | CONFIRMATIONREQUEST = "BH" 49 | EMAIL = "C" 50 | SECURITYDEFINITIONREQUEST = "c" 51 | SECURITYDEFINITION = "d" 52 | NEWORDERSINGLE = "D" 53 | SECURITYSTATUSREQUEST = "e" 54 | NEWORDERLIST = "E" 55 | ORDERCANCELREQUEST = "F" 56 | SECURITYSTATUS = "f" 57 | ORDERCANCELREPLACEREQUEST = "G" 58 | TRADINGSESSIONSTATUSREQUEST = "g" 59 | ORDERSTATUSREQUEST = "H" 60 | TRADINGSESSIONSTATUS = "h" 61 | MASSQUOTE = "i" 62 | BUSINESSMESSAGEREJECT = "j" 63 | ALLOCATIONINSTRUCTION = "J" 64 | BIDREQUEST = "k" 65 | LISTCANCELREQUEST = "K" 66 | BIDRESPONSE = "l" 67 | LISTEXECUTE = "L" 68 | LISTSTRIKEPRICE = "m" 69 | LISTSTATUSREQUEST = "M" 70 | XMLNONFIX = "n" 71 | LISTSTATUS = "N" 72 | REGISTRATIONINSTRUCTIONS = "o" 73 | REGISTRATIONINSTRUCTIONSRESPONSE = "p" 74 | ALLOCATIONINSTRUCTIONACK = "P" 75 | ORDERMASSCANCELREQUEST = "q" 76 | DONTKNOWTRADEDK = "Q" 77 | QUOTEREQUEST = "R" 78 | ORDERMASSCANCELREPORT = "r" 79 | QUOTE = "S" 80 | NEWORDERCROSS = "s" 81 | SETTLEMENTINSTRUCTIONS = "T" 82 | CROSSORDERCANCELREPLACEREQUEST = "t" 83 | CROSSORDERCANCELREQUEST = "u" 84 | MARKETDATAREQUEST = "V" 85 | SECURITYTYPEREQUEST = "v" 86 | SECURITYTYPES = "w" 87 | MARKETDATASNAPSHOTFULLREFRESH = "W" 88 | SECURITYLISTREQUEST = "x" 89 | MARKETDATAINCREMENTALREFRESH = "X" 90 | MARKETDATAREQUESTREJECT = "Y" 91 | SECURITYLIST = "y" 92 | QUOTECANCEL = "Z" 93 | DERIVATIVESECURITYLISTREQUEST = "z" 94 | 95 | sessionMessageTypes = [HEARTBEAT, TESTREQUEST, RESENDREQUEST, REJECT, SEQUENCERESET, LOGOUT, LOGON, XMLNONFIX] 96 | 97 | tags = {} 98 | for x in dir(): 99 | tags[str(globals()[x])] = x 100 | 101 | def msgTypeToName(n): 102 | try: 103 | return tags[n] 104 | except KeyError: 105 | return str(n) 106 | -------------------------------------------------------------------------------- /pyfix/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wannabegeek/PyFIX/b903e70ce79cefc2525200f84ee4f90c5bf4c686/pyfix/__init__.py -------------------------------------------------------------------------------- /pyfix/client_connection.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import socket 3 | from pyfix.journaler import DuplicateSeqNoError 4 | from pyfix.message import FIXMessage 5 | from pyfix.session import FIXSession 6 | from pyfix.connection import FIXEndPoint, ConnectionState, MessageDirection, FIXConnectionHandler 7 | from pyfix.event import TimerEventRegistration 8 | 9 | class FIXClientConnectionHandler(FIXConnectionHandler): 10 | def __init__(self, engine, protocol, targetCompId, senderCompId, sock=None, addr=None, observer=None, targetSubId = None, senderSubId = None, heartbeatTimeout = 30): 11 | FIXConnectionHandler.__init__(self, engine, protocol, sock, addr, observer) 12 | 13 | self.targetCompId = targetCompId 14 | self.senderCompId = senderCompId 15 | self.targetSubId = targetSubId 16 | self.senderSubId = senderSubId 17 | self.heartbeatPeriod = float(heartbeatTimeout) 18 | 19 | # we need to send a login request. 20 | self.session = self.engine.getOrCreateSessionFromCompIds(self.targetCompId, self.senderCompId) 21 | if self.session is None: 22 | raise RuntimeError("Failed to create client session") 23 | 24 | self.sendMsg(protocol.messages.Messages.logon()) 25 | 26 | def handleSessionMessage(self, msg): 27 | protocol = self.codec.protocol 28 | responses = [] 29 | 30 | recvSeqNo = msg[protocol.fixtags.MsgSeqNum] 31 | 32 | msgType = msg[protocol.fixtags.MsgType] 33 | targetCompId = msg[protocol.fixtags.TargetCompID] 34 | senderCompId = msg[protocol.fixtags.SenderCompID] 35 | 36 | if msgType == protocol.msgtype.LOGON: 37 | if self.connectionState == ConnectionState.LOGGED_IN: 38 | logging.warning("Client session already logged in - ignoring login request") 39 | else: 40 | try: 41 | self.connectionState = ConnectionState.LOGGED_IN 42 | self.heartbeatPeriod = float(msg[protocol.fixtags.HeartBtInt]) 43 | self.registerLoggedIn() 44 | except DuplicateSeqNoError: 45 | logging.error("Failed to process login request with duplicate seq no") 46 | self.disconnect() 47 | return 48 | elif self.connectionState == ConnectionState.LOGGED_IN: 49 | # compids are reversed here 50 | if not self.session.validateCompIds(senderCompId, targetCompId): 51 | logging.error("Received message with unexpected comp ids") 52 | self.disconnect() 53 | return 54 | 55 | if msgType == protocol.msgtype.LOGOUT: 56 | self.connectionState = ConnectionState.LOGGED_OUT 57 | self.registerLoggedOut() 58 | self.handle_close() 59 | elif msgType == protocol.msgtype.TESTREQUEST: 60 | responses.append(protocol.messages.Messages.heartbeat()) 61 | elif msgType == protocol.msgtype.RESENDREQUEST: 62 | responses.extend(self._handleResendRequest(msg)) 63 | elif msgType == protocol.msgtype.SEQUENCERESET: 64 | # we can treat GapFill and SequenceReset in the same way 65 | # in both cases we will just reset the seq number to the 66 | # NewSeqNo received in the message 67 | newSeqNo = msg[protocol.fixtags.NewSeqNo] 68 | if msg[protocol.fixtags.GapFillFlag] == "Y": 69 | logging.info("Received SequenceReset(GapFill) filling gap from %s to %s" % (recvSeqNo, newSeqNo)) 70 | self.session.setRecvSeqNo(int(newSeqNo) - 1) 71 | recvSeqNo = newSeqNo 72 | else: 73 | logging.warning("Can't process message, counterparty is not logged in") 74 | 75 | return (recvSeqNo, responses) 76 | 77 | 78 | 79 | class FIXClient(FIXEndPoint): 80 | def __init__(self, engine, protocol, targetCompId, senderCompId, targetSubId = None, senderSubId = None, heartbeatTimeout = 30): 81 | self.targetCompId = targetCompId 82 | self.senderCompId = senderCompId 83 | self.targetSubId = targetSubId 84 | self.senderSubId = senderSubId 85 | self.heartbeatTimeout = heartbeatTimeout 86 | 87 | FIXEndPoint.__init__(self, engine, protocol) 88 | 89 | def tryConnecting(self, type, closure): 90 | try: 91 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 92 | logging.debug("Attempting Connection to " + self.host + ":" + str(self.port)) 93 | self.socket.connect((self.host, self.port)) 94 | if self.connectionRetryTimer is not None: 95 | self.engine.eventManager.unregisterHandler(self.connectionRetryTimer) 96 | self.connectionRetryTimer = None 97 | self.connected() 98 | except socket.error as why: 99 | logging.error("Connection failed, trying again in 5s") 100 | if self.connectionRetryTimer is None: 101 | self.connectionRetryTimer = TimerEventRegistration(self.tryConnecting, 5.0) 102 | self.engine.eventManager.registerHandler(self.connectionRetryTimer) 103 | 104 | def start(self, host, port): 105 | self.host = host 106 | self.port = port 107 | self.connections = [] 108 | self.connectionRetryTimer = None 109 | 110 | self.tryConnecting(None, None) 111 | 112 | def connected(self): 113 | self.addr = (self.host, self.port) 114 | logging.info("Connected to %s" % repr(self.addr)) 115 | connection = FIXClientConnectionHandler(self.engine, self.protocol, self.targetCompId, self.senderCompId, self.socket, self.addr, self, self.targetSubId, self.senderSubId, self.heartbeatTimeout) 116 | self.connections.append(connection) 117 | for handler in filter(lambda x: x[1] == ConnectionState.CONNECTED, self.connectionHandlers): 118 | handler[0](connection) 119 | 120 | def notifyDisconnect(self, connection): 121 | FIXEndPoint.notifyDisconnect(self, connection) 122 | self.tryConnecting(None, None) 123 | 124 | def stop(self): 125 | logging.info("Stopping client connections") 126 | for connection in self.connections: 127 | connection.disconnect() 128 | self.socket.close() 129 | 130 | -------------------------------------------------------------------------------- /pyfix/codec.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import logging 3 | from pyfix.message import FIXMessage, FIXContext 4 | 5 | class EncodingError(Exception): 6 | pass 7 | 8 | class DecodingError(Exception): 9 | pass 10 | 11 | class RepeatingGroupContext(FIXContext): 12 | def __init__(self, tag, repeatingGroupTags, parent): 13 | self.tag = tag 14 | self.repeatingGroupTags = repeatingGroupTags 15 | self.parent = parent 16 | FIXContext.__init__(self) 17 | 18 | class Codec(object): 19 | def __init__(self, protocol): 20 | self.protocol = protocol 21 | self.SOH = '\x01' 22 | 23 | @staticmethod 24 | def current_datetime(): 25 | return datetime.utcnow().strftime("%Y%m%d-%H:%M:%S.%f")[:-3] 26 | 27 | def _addTag(self, body, t, msg): 28 | if msg.isRepeatingGroup(t): 29 | count, groups = msg.getRepeatingGroup(t) 30 | body.append("%s=%s" % (t, count)) 31 | for group in groups: 32 | for tag in group.tags: 33 | self._addTag(body, tag, group) 34 | else: 35 | body.append("%s=%s" % (t, msg[t])) 36 | 37 | def encode(self, msg, session): 38 | # Create body 39 | body = [] 40 | 41 | msgType = msg.msgType 42 | 43 | body.append("%s=%s" % (self.protocol.fixtags.SenderCompID, session.senderCompId)) 44 | body.append("%s=%s" % (self.protocol.fixtags.TargetCompID, session.targetCompId)) 45 | 46 | seqNo = 0 47 | if msgType == self.protocol.msgtype.SEQUENCERESET: 48 | if self.protocol.fixtags.GapFillFlag in msg and msg[self.protocol.fixtags.GapFillFlag] == "Y": 49 | # in this case the sequence number should already be on the message 50 | try: 51 | seqNo = msg[self.protocol.fixtags.MsgSeqNum] 52 | except KeyError: 53 | raise EncodingError("SequenceReset with GapFill='Y' must have the MsgSeqNum already populated") 54 | else: 55 | msg[self.protocol.fixtags.NewSeqNo] = session.allocateSndSeqNo() 56 | seqNo = msg[self.protocol.fixtags.MsgSeqNum] 57 | else: 58 | # if we have the PossDupFlag set, we need to send the message with the same seqNo 59 | if self.protocol.fixtags.PossDupFlag in msg and msg[self.protocol.fixtags.PossDupFlag] == "Y": 60 | try: 61 | seqNo = msg[self.protocol.fixtags.MsgSeqNum] 62 | except KeyError: 63 | raise EncodingError("Failed to encode message with PossDupFlay=Y but no previous MsgSeqNum") 64 | else: 65 | seqNo = session.allocateSndSeqNo() 66 | 67 | body.append("%s=%s" % (self.protocol.fixtags.MsgSeqNum, seqNo)) 68 | body.append("%s=%s" % (self.protocol.fixtags.SendingTime, self.current_datetime())) 69 | 70 | for t in msg.tags: 71 | self._addTag(body, t, msg) 72 | 73 | # Enable easy change when debugging 74 | SEP = self.SOH 75 | 76 | body = self.SOH.join(body) + self.SOH 77 | 78 | # Create header 79 | header = [] 80 | msgType = "%s=%s" % (self.protocol.fixtags.MsgType, msgType) 81 | header.append("%s=%s" % (self.protocol.fixtags.BeginString, self.protocol.beginstring)) 82 | header.append("%s=%i" % (self.protocol.fixtags.BodyLength, len(body) + len(msgType) + 1)) 83 | header.append(msgType) 84 | 85 | fixmsg = self.SOH.join(header) + self.SOH + body 86 | 87 | cksum = sum([ord(i) for i in list(fixmsg)]) % 256 88 | fixmsg = fixmsg + "%s=%0.3i" % (self.protocol.fixtags.CheckSum, cksum) 89 | 90 | #print len(fixmsg) 91 | 92 | return fixmsg + SEP 93 | 94 | def decode(self, rawmsg): 95 | #msg = rawmsg.rstrip(os.linesep).split(SOH) 96 | try: 97 | rawmsg = rawmsg.decode('utf-8') 98 | msg = rawmsg.split(self.SOH) 99 | msg = msg[:-1] 100 | 101 | if len(msg) < 3: # at a minumum we require BeginString, BodyLength & Checksum 102 | return (None, 0) 103 | 104 | tag, value = msg[0].split('=', 1) 105 | if tag != self.protocol.fixtags.BeginString: 106 | logging.error("*** BeginString missing or not 1st field *** [" + tag + "]") 107 | elif value != self.protocol.beginstring: 108 | logging.error("FIX Version unexpected (Recv: %s Expected: %s)" % (value, self.protocol.beginstring)) 109 | 110 | tag, value = msg[1].split('=', 1) 111 | msgLength = len(msg[0]) + len(msg[1]) + len('10=000') + 3 112 | if tag != self.protocol.fixtags.BodyLength: 113 | logging.error("*** BodyLength missing or not 2nd field *** [" + tag + "]") 114 | else: 115 | msgLength += int(value) 116 | 117 | # do we have a complete message on the sockt 118 | if msgLength > len(rawmsg): 119 | return (None, 0) 120 | else: 121 | remainingMsgFragment = msgLength 122 | 123 | # resplit our message 124 | msg = rawmsg[:msgLength].split(self.SOH) 125 | msg = msg[:-1] 126 | decodedMsg = FIXMessage("UNKNOWN") 127 | 128 | # logging.debug("\t-----------------------------------------") 129 | # logging.debug("\t" + "|".join(msg)) 130 | 131 | repeatingGroups = [] 132 | repeatingGroupTags = self.protocol.fixtags.repeatingGroupIdentifiers() 133 | currentContext = decodedMsg 134 | 135 | for m in msg: 136 | tag, value = m.split('=', 1) 137 | t = None 138 | try: 139 | t = self.protocol.fixtags.tagToName(tag) 140 | except KeyError: 141 | logging.info("\t%s(Unknown): %s" % (tag, value)) 142 | t = "{unknown}" 143 | 144 | if tag == self.protocol.fixtags.CheckSum: 145 | cksum = ((sum([ord(i) for i in list(self.SOH.join(msg[:-1]))]) + 1) % 256) 146 | if cksum != int(value): 147 | logging.warning("\tCheckSum: %s (INVALID) expecting %s" % (int(value), cksum)) 148 | elif tag == self.protocol.fixtags.MsgType: 149 | try: 150 | msgType = self.protocol.msgtype.msgTypeToName(value) 151 | decodedMsg.setMsgType(value) 152 | except KeyError: 153 | logging.error('*** MsgType "%s" not supported ***') 154 | 155 | if tag in repeatingGroupTags: # found the start of a repeating group 156 | if type(currentContext) is RepeatingGroupContext: # i.e. we are already in a repeating group 157 | while repeatingGroups and tag not in currentContext.repeatingGroupTags: 158 | currentContext.parent.addRepeatingGroup(currentContext.tag, currentContext) 159 | currentContext = currentContext.parent 160 | del repeatingGroups[-1] # pop the completed group off the stack 161 | 162 | ctx = RepeatingGroupContext(tag, repeatingGroupTags[tag], currentContext) 163 | repeatingGroups.append(ctx) 164 | currentContext = ctx 165 | elif repeatingGroups: # we have 1 or more repeating groups in progress & our tag isn't the start of a group 166 | while repeatingGroups and tag not in currentContext.repeatingGroupTags: 167 | currentContext.parent.addRepeatingGroup(currentContext.tag, currentContext) 168 | currentContext = currentContext.parent 169 | del repeatingGroups[-1] # pop the completed group off the stack 170 | 171 | if tag in currentContext.tags: 172 | # if the repeating group already contains this field, start the next 173 | currentContext.parent.addRepeatingGroup(currentContext.tag, currentContext) 174 | ctx = RepeatingGroupContext(currentContext.tag, currentContext.repeatingGroupTags, currentContext.parent) 175 | del repeatingGroups[-1] # pop the completed group off the stack 176 | repeatingGroups.append(ctx) 177 | currentContext = ctx 178 | 179 | # else add it to the current one 180 | currentContext.setField(tag, value) 181 | else: 182 | # this isn't a repeating group field, so just add it normally 183 | decodedMsg.setField(tag, value) 184 | 185 | return (decodedMsg, remainingMsgFragment) 186 | except UnicodeDecodeError as why: 187 | logging.error("Failed to parse message %s" % (why, )) 188 | return (None, 0) 189 | -------------------------------------------------------------------------------- /pyfix/connection.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import sys 3 | from pyfix.codec import Codec 4 | from pyfix.journaler import DuplicateSeqNoError 5 | from pyfix.message import FIXMessage, MessageDirection 6 | 7 | from pyfix.session import * 8 | from enum import Enum 9 | from pyfix.event import FileDescriptorEventRegistration, EventType, TimerEventRegistration 10 | 11 | class ConnectionState(Enum): 12 | UNKNOWN = 0 13 | DISCONNECTED = 1 14 | CONNECTED = 2 15 | LOGGED_IN = 3 16 | LOGGED_OUT = 4 17 | 18 | class FIXException(Exception): 19 | class FIXExceptionReason(Enum): 20 | NOT_CONNECTED = 0 21 | DECODE_ERROR = 1 22 | ENCODE_ERROR = 2 23 | 24 | def __init__(self, reason, description = None): 25 | super(Exception, self).__init__(description) 26 | self.reason = reason 27 | 28 | class SessionWarning(Exception): 29 | pass 30 | 31 | class SessionError(Exception): 32 | pass 33 | 34 | class FIXConnectionHandler(object): 35 | def __init__(self, engine, protocol, sock=None, addr=None, observer=None): 36 | self.codec = Codec(protocol) 37 | self.engine = engine 38 | self.connectionState = ConnectionState.CONNECTED 39 | self.session = None 40 | self.addr = addr 41 | self.observer = observer 42 | self.msgBuffer = b'' 43 | self.heartbeatPeriod = 30.0 44 | self.msgHandlers = [] 45 | self.sock = sock 46 | self.heartbeatTimerRegistration = None 47 | self.expectedHeartbeatRegistration = None 48 | self.socketEvent = FileDescriptorEventRegistration(self.handle_read, sock, EventType.READ) 49 | self.engine.eventManager.registerHandler(self.socketEvent) 50 | 51 | def address(self): 52 | return self.addr 53 | 54 | def disconnect(self): 55 | self.handle_close() 56 | 57 | def _notifyMessageObservers(self, msg, direction, persistMessage=True): 58 | if persistMessage is True: 59 | self.engine.journaller.persistMsg(msg, self.session, direction) 60 | for handler in filter(lambda x: (x[1] is None or x[1] == direction) and (x[2] is None or x[2] == msg.msgType), self.msgHandlers): 61 | handler[0](self, msg) 62 | 63 | def addMessageHandler(self, handler, direction = None, msgType = None): 64 | self.msgHandlers.append((handler, direction, msgType)) 65 | 66 | def removeMessageHandler(self, handler, direction = None, msgType = None): 67 | remove = filter(lambda x: x[0] == handler and 68 | (x[1] == direction or direction is None) and 69 | (x[2] == msgType or msgType is None), self.msgHandlers) 70 | for h in remove: 71 | self.msgHandlers.remove(h) 72 | 73 | def _sendHeartbeat(self): 74 | self.sendMsg(self.codec.protocol.messages.Messages.heartbeat()) 75 | 76 | def _expectedHeartbeat(self, type, closure): 77 | logging.warning("Expected heartbeat from peer %s" % (self.expectedHeartbeatRegistration ,)) 78 | self.sendMsg(self.codec.protocol.messages.Messages.test_request()) 79 | 80 | def registerLoggedIn(self): 81 | self.heartbeatTimerRegistration = TimerEventRegistration(lambda type, closure: self._sendHeartbeat(), self.heartbeatPeriod) 82 | self.engine.eventManager.registerHandler(self.heartbeatTimerRegistration) 83 | # register timeout for 10% more than we expect 84 | self.expectedHeartbeatRegistration = TimerEventRegistration(self._expectedHeartbeat, self.heartbeatPeriod * 1.10) 85 | self.engine.eventManager.registerHandler(self.expectedHeartbeatRegistration) 86 | 87 | 88 | def registerLoggedOut(self): 89 | if self.heartbeatTimerRegistration is not None: 90 | self.engine.eventManager.unregisterHandler(self.heartbeatTimerRegistration) 91 | self.heartbeatTimerRegistration = None 92 | if self.expectedHeartbeatRegistration is not None: 93 | self.engine.eventManager.unregisterHandler(self.expectedHeartbeatRegistration) 94 | self.expectedHeartbeatRegistration = None 95 | 96 | def _handleResendRequest(self, msg): 97 | protocol = self.codec.protocol 98 | responses = [] 99 | 100 | beginSeqNo = msg[protocol.fixtags.BeginSeqNo] 101 | endSeqNo = msg[protocol.fixtags.EndSeqNo] 102 | if int(endSeqNo) == 0: 103 | endSeqNo = sys.maxsize 104 | logging.info("Received resent request from %s to %s", beginSeqNo, endSeqNo) 105 | replayMsgs = self.engine.journaller.recoverMsgs(self.session, MessageDirection.OUTBOUND, beginSeqNo, endSeqNo) 106 | gapFillBegin = int(beginSeqNo) 107 | gapFillEnd = int(beginSeqNo) 108 | for replayMsg in replayMsgs: 109 | msgSeqNum = int(replayMsg[protocol.fixtags.MsgSeqNum]) 110 | if replayMsg[protocol.fixtags.MsgType] in protocol.msgtype.sessionMessageTypes: 111 | gapFillEnd = msgSeqNum + 1 112 | else: 113 | if self.engine.shouldResendMessage(self.session, replayMsg): 114 | if gapFillBegin < gapFillEnd: 115 | # we need to send a gap fill message 116 | gapFillMsg = FIXMessage(protocol.msgtype.SEQUENCERESET) 117 | gapFillMsg.setField(protocol.fixtags.GapFillFlag, 'Y') 118 | gapFillMsg.setField(protocol.fixtags.MsgSeqNum, gapFillBegin) 119 | gapFillMsg.setField(protocol.fixtags.NewSeqNo, str(gapFillEnd)) 120 | responses.append(gapFillMsg) 121 | 122 | # and then resent the replayMsg 123 | replayMsg.removeField(protocol.fixtags.BeginString) 124 | replayMsg.removeField(protocol.fixtags.BodyLength) 125 | replayMsg.removeField(protocol.fixtags.SendingTime) 126 | replayMsg.removeField(protocol.fixtags.SenderCompID) 127 | replayMsg.removeField(protocol.fixtags.TargetCompID) 128 | replayMsg.removeField(protocol.fixtags.CheckSum) 129 | replayMsg.setField(protocol.fixtags.PossDupFlag, "Y") 130 | responses.append(replayMsg) 131 | 132 | gapFillBegin = msgSeqNum + 1 133 | else: 134 | gapFillEnd = msgSeqNum + 1 135 | responses.append(replayMsg) 136 | 137 | if gapFillBegin < gapFillEnd: 138 | # we need to send a gap fill message 139 | gapFillMsg = FIXMessage(protocol.msgtype.SEQUENCERESET) 140 | gapFillMsg.setField(protocol.fixtags.GapFillFlag, 'Y') 141 | gapFillMsg.setField(protocol.fixtags.MsgSeqNum, gapFillBegin) 142 | gapFillMsg.setField(protocol.fixtags.NewSeqNo, str(gapFillEnd)) 143 | responses.append(gapFillMsg) 144 | 145 | return responses 146 | 147 | def handle_read(self, type, closure): 148 | protocol = self.codec.protocol 149 | try: 150 | msg = self.sock.recv(8192) 151 | if msg: 152 | self.msgBuffer = self.msgBuffer + msg 153 | (decodedMsg, parsedLength) = self.codec.decode(self.msgBuffer) 154 | self.msgBuffer = self.msgBuffer[parsedLength:] 155 | while decodedMsg is not None and self.connectionState != ConnectionState.DISCONNECTED: 156 | self.processMessage(decodedMsg) 157 | (decodedMsg, parsedLength) = self.codec.decode(self.msgBuffer) 158 | self.msgBuffer = self.msgBuffer[parsedLength:] 159 | if self.expectedHeartbeatRegistration is not None: 160 | self.expectedHeartbeatRegistration.reset() 161 | else: 162 | logging.debug("Connection has been closed") 163 | self.disconnect() 164 | except ConnectionError as why: 165 | logging.debug("Connection has been closed %s" % (why, )) 166 | self.disconnect() 167 | 168 | def handleSessionMessage(self, msg): 169 | return -1 170 | 171 | def processMessage(self, decodedMsg): 172 | protocol = self.codec.protocol 173 | 174 | beginString = decodedMsg[protocol.fixtags.BeginString] 175 | if beginString != protocol.beginstring: 176 | logging.warning("FIX BeginString is incorrect (expected: %s received: %s)", (protocol.beginstring, beginString)) 177 | self.disconnect() 178 | return 179 | 180 | msgType = decodedMsg[protocol.fixtags.MsgType] 181 | 182 | try: 183 | responses = [] 184 | if msgType in protocol.msgtype.sessionMessageTypes: 185 | (recvSeqNo, responses) = self.handleSessionMessage(decodedMsg) 186 | else: 187 | recvSeqNo = decodedMsg[protocol.fixtags.MsgSeqNum] 188 | 189 | # validate the seq number 190 | (seqNoState, lastKnownSeqNo) = self.session.validateRecvSeqNo(recvSeqNo) 191 | 192 | if seqNoState is False: 193 | # We should send a resend request 194 | logging.info("Requesting resend of messages: %s to %s" % (lastKnownSeqNo, 0)) 195 | responses.append(protocol.messages.Messages.resend_request(lastKnownSeqNo, 0)) 196 | # we still need to notify if we are processing Logon message 197 | if msgType == protocol.msgtype.LOGON: 198 | self._notifyMessageObservers(decodedMsg, MessageDirection.INBOUND, False) 199 | else: 200 | self.session.setRecvSeqNo(recvSeqNo) 201 | self._notifyMessageObservers(decodedMsg, MessageDirection.INBOUND) 202 | 203 | 204 | for m in responses: 205 | self.sendMsg(m) 206 | 207 | except SessionWarning as sw: 208 | logging.warning(sw) 209 | except SessionError as se: 210 | logging.error(se) 211 | self.disconnect() 212 | except DuplicateSeqNoError: 213 | try: 214 | if decodedMsg[protocol.fixtags.PossDupFlag] == "Y": 215 | logging.debug("Received duplicate message with PossDupFlag set") 216 | except KeyError: 217 | pass 218 | finally: 219 | logging.error("Failed to process message with duplicate seq no (MsgSeqNum: %s) (and no PossDupFlag='Y') - disconnecting" % (recvSeqNo, )) 220 | self.disconnect() 221 | 222 | 223 | def handle_close(self): 224 | if self.connectionState != ConnectionState.DISCONNECTED: 225 | logging.info("Client disconnected") 226 | self.registerLoggedOut() 227 | self.sock.close() 228 | self.connectionState = ConnectionState.DISCONNECTED 229 | self.msgHandlers.clear() 230 | if self.observer is not None: 231 | self.observer.notifyDisconnect(self) 232 | self.engine.eventManager.unregisterHandler(self.socketEvent) 233 | 234 | 235 | def sendMsg(self, msg): 236 | if self.connectionState != ConnectionState.CONNECTED and self.connectionState != ConnectionState.LOGGED_IN: 237 | raise FIXException(FIXException.FIXExceptionReason.NOT_CONNECTED) 238 | 239 | encodedMsg = self.codec.encode(msg, self.session).encode('utf-8') 240 | self.sock.send(encodedMsg) 241 | if self.heartbeatTimerRegistration is not None: 242 | self.heartbeatTimerRegistration.reset() 243 | 244 | decodedMsg, junk = self.codec.decode(encodedMsg) 245 | 246 | try: 247 | self._notifyMessageObservers(decodedMsg, MessageDirection.OUTBOUND) 248 | except DuplicateSeqNoError: 249 | logging.error("We have sent a message with a duplicate seq no, failed to persist it (MsgSeqNum: %s)" % (decodedMsg[self.codec.protocol.fixtags.MsgSeqNum])) 250 | 251 | 252 | class FIXEndPoint(object): 253 | def __init__(self, engine, protocol): 254 | self.engine = engine 255 | self.protocol = importlib.import_module(protocol) 256 | 257 | self.connections = [] 258 | self.connectionHandlers = [] 259 | 260 | def writable(self): 261 | return True 262 | 263 | def start(self, host, port): 264 | pass 265 | 266 | def stop(self): 267 | pass 268 | 269 | def addConnectionListener(self, handler, filter): 270 | self.connectionHandlers.append((handler, filter)) 271 | 272 | def removeConnectionListener(self, handler, filter): 273 | for s in self.connectionHandlers: 274 | if s == (handler, filter): 275 | self.connectionHandlers.remove(s) 276 | 277 | def notifyDisconnect(self, connection): 278 | self.connections.remove(connection) 279 | for handler in filter(lambda x: x[1] == ConnectionState.DISCONNECTED, self.connectionHandlers): 280 | handler[0](connection) 281 | 282 | -------------------------------------------------------------------------------- /pyfix/engine.py: -------------------------------------------------------------------------------- 1 | from pyfix.event import EventManager 2 | from pyfix.journaler import Journaler 3 | 4 | class FIXEngine(object): 5 | def __init__(self, journalfile = None): 6 | self.eventManager = EventManager() 7 | self.journaller = Journaler(journalfile) 8 | self.sessions = {} 9 | 10 | # We load all sessions from the journal and add to our list 11 | for session in self.journaller.sessions(): 12 | self.sessions[session.key] = session 13 | 14 | def validateSession(self, targetCompId, senderCompId): 15 | # this make any session we receive valid 16 | return True 17 | 18 | def shouldResendMessage(self, session, msg): 19 | # we should resend all application messages 20 | return True 21 | 22 | def createSession(self, targetCompId, senderCompId): 23 | if self.findSessionByCompIds(targetCompId, senderCompId) is None: 24 | session = self.journaller.createSession(targetCompId, senderCompId) 25 | self.sessions[session.key] = session 26 | else: 27 | raise RuntimeError("Failed to add session with duplicate key") 28 | return session 29 | 30 | def getSession(self, identifier): 31 | try: 32 | return self.sessions[identifier] 33 | except KeyError: 34 | return None 35 | 36 | def findSessionByCompIds(self, targetCompId, senderCompId): 37 | sessions = [x for x in self.sessions.values() if x.targetCompId == targetCompId and x.senderCompId == senderCompId] 38 | if sessions is not None and len(sessions) != 0: 39 | return sessions[0] 40 | return None 41 | 42 | def getOrCreateSessionFromCompIds(self, targetCompId, senderCompId): 43 | session = self.findSessionByCompIds(targetCompId, senderCompId) 44 | if session is None: 45 | if self.validateSession(targetCompId, senderCompId): 46 | session = self.createSession(targetCompId, senderCompId) 47 | 48 | return session 49 | -------------------------------------------------------------------------------- /pyfix/event.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import datetime 3 | import os 4 | from select import select, error 5 | import errno 6 | import time 7 | 8 | class EventType(Enum): 9 | NONE = 0 10 | READ = 1 11 | WRITE = 2 12 | TIMEOUT = 4 13 | READWRITE = READ | WRITE 14 | 15 | class EventRegistration(object): 16 | def __init__(self, callback, closure=None): 17 | self.callback = callback 18 | self.closure = closure 19 | 20 | class TimerEventRegistration(EventRegistration): 21 | class TimeoutState(Enum): 22 | NONE = 0 23 | START = 1 24 | PROGRESS = 2 25 | 26 | def __init__(self, callback, timeout, closure=None): 27 | EventRegistration.__init__(self, callback, closure) 28 | self.timeout = timeout 29 | self.timeoutState = TimerEventRegistration.TimeoutState.START 30 | self.timeLeft = timeout 31 | self.lastTime = None 32 | 33 | def reset(self): 34 | self.timeLeft = self.timeout 35 | 36 | def __str__(self): 37 | return "TimerEvent interval: %s, remaining: %s" % (self.timeout, self.timeLeft) 38 | 39 | 40 | class FileDescriptorEventRegistration(EventRegistration): 41 | 42 | def __init__(self, callback, fileDescriptor, eventType, closure=None): 43 | EventRegistration.__init__(self, callback, closure) 44 | self.fd = fileDescriptor 45 | self.eventType = eventType 46 | 47 | def __str__(self): 48 | return "FileDescriptorEvent fd: %s, type: %s" % (self.fd, self.eventType.name) 49 | 50 | 51 | class _Event(object): 52 | def __init__(self, fd, filter): 53 | self.fd = fd 54 | self.filter = filter 55 | 56 | class EventLoop(object): 57 | def add(self, event): 58 | pass 59 | 60 | def remove(self, event): 61 | pass 62 | 63 | def run(self, timeout): 64 | pass 65 | 66 | class SelectEventLoop(EventLoop): 67 | def __init__(self): 68 | self.readSet = [] 69 | self.writeSet = [] 70 | 71 | def add(self, event): 72 | if (event.filter.value & EventType.READ.value) == EventType.READ.value: 73 | self.readSet.append(event.fd) 74 | if (event.filter.value & EventType.WRITE.value) == EventType.WRITE.value: 75 | self.writeSet.append(event.fd) 76 | 77 | def remove(self, event): 78 | if event.filter.value & EventType.READ.value == EventType.READ.value: 79 | self.readSet.remove(event.fd) 80 | if event.filter.value & EventType.WRITE.value == EventType.WRITE.value: 81 | self.writeSet.remove(event.fd) 82 | 83 | def run(self, timeout): 84 | if len(self.readSet) == 0 and len(self.writeSet) ==0: 85 | time.sleep(timeout) 86 | return [] 87 | else: 88 | while True: 89 | try: 90 | readReady, writeReady, exceptReady = select(self.readSet, self.writeSet, [], timeout) 91 | events = [] 92 | for r in readReady: 93 | events.append(_Event(r, EventType.READ)) 94 | for r in writeReady: 95 | events.append(_Event(r, EventType.WRITE)) 96 | return events 97 | except error as why: 98 | if os.name == 'posix': 99 | if why[0] != errno.EAGAIN and why[0] != errno.EINTR: 100 | break 101 | else: 102 | if why[0] == errno.WSAEADDRINUSE: 103 | break 104 | 105 | 106 | class EventManager(object): 107 | def __init__(self): 108 | self.eventLoop = SelectEventLoop() 109 | self.handlers = [] 110 | 111 | def waitForEvent(self): 112 | self.waitForEventWithTimeout(None) 113 | 114 | def waitForEventWithTimeout(self, timeout): 115 | if not self.handlers: 116 | raise RuntimeError("Failed to start event loop without any handlers") 117 | 118 | timeout = self._setTimeout(timeout) 119 | events = self.eventLoop.run(timeout) 120 | self._serviceEvents(events) 121 | 122 | def _setTimeout(self, timeout): 123 | nowTime = datetime.datetime.utcnow() 124 | duration = timeout 125 | 126 | for handler in self.handlers: 127 | if type(handler) is TimerEventRegistration: 128 | if handler.timeoutState == TimerEventRegistration.TimeoutState.START: 129 | handler.timeoutState = TimerEventRegistration.TimeoutState.PROGRESS 130 | 131 | handler.lastTime = nowTime 132 | if duration is None or handler.timeLeft < duration: 133 | duration = handler.timeLeft 134 | 135 | return duration 136 | 137 | def _serviceEvents(self, events): 138 | nowTime = datetime.datetime.utcnow() 139 | for handler in self.handlers: 140 | if isinstance(handler, FileDescriptorEventRegistration): 141 | for event in events: 142 | if event.fd == handler.fd: 143 | type = handler.eventType.value & event.filter.value 144 | if type != EventType.NONE: 145 | handler.callback(type, handler.closure) 146 | elif isinstance(handler, TimerEventRegistration): 147 | if handler.timeoutState == TimerEventRegistration.TimeoutState.PROGRESS: 148 | elapsedTime = nowTime - handler.lastTime 149 | handler.timeLeft -= elapsedTime.total_seconds() 150 | if handler.timeLeft <= 0.0: 151 | handler.timeLeft = handler.timeout 152 | handler.callback(EventType.TIMEOUT, handler.closure) 153 | 154 | 155 | def registerHandler(self, handler): 156 | if isinstance(handler, TimerEventRegistration): 157 | pass 158 | elif isinstance(handler, FileDescriptorEventRegistration): 159 | self.eventLoop.add(_Event(handler.fd, handler.eventType)) 160 | else: 161 | raise RuntimeError("Trying to register invalid handler") 162 | self.handlers.append(handler) 163 | 164 | def unregisterHandler(self, handler): 165 | if self.isRegistered(handler): 166 | self.handlers.remove(handler) 167 | if isinstance(handler, FileDescriptorEventRegistration): 168 | self.eventLoop.remove(_Event(handler.fd, handler.eventType)) 169 | 170 | 171 | def isRegistered(self, handler): 172 | return handler in self.handlers 173 | -------------------------------------------------------------------------------- /pyfix/journaler.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import pickle 3 | from pyfix.message import MessageDirection 4 | from pyfix.session import FIXSession 5 | 6 | 7 | class DuplicateSeqNoError(Exception): 8 | pass 9 | 10 | class Journaler(object): 11 | def __init__(self, filename = None): 12 | if filename is None: 13 | self.conn = sqlite3.connect(":memory:") 14 | else: 15 | self.conn = sqlite3.connect(filename) 16 | 17 | self.cursor = self.conn.cursor() 18 | self.cursor.execute("CREATE TABLE IF NOT EXISTS message(" 19 | "seqNo INTEGER NOT NULL," 20 | "session TEXT NOT NULL," 21 | "direction INTEGER NOT NULL," 22 | "msg TEXT," 23 | "PRIMARY KEY (seqNo, session, direction))") 24 | 25 | self.cursor.execute("CREATE TABLE IF NOT EXISTS session(" 26 | "sessionId INTEGER PRIMARY KEY AUTOINCREMENT," 27 | "targetCompId TEXT NOT NULL," 28 | "senderCompId TEXT NOT NULL," 29 | "outboundSeqNo INTEGER DEFAULT 0," 30 | "inboundSeqNo INTEGER DEFAULT 0," 31 | "UNIQUE (targetCompId, senderCompId))") 32 | 33 | def sessions(self): 34 | sessions = [] 35 | self.cursor.execute("SELECT sessionId, targetCompId, senderCompId, outboundSeqNo, inboundSeqNo FROM session") 36 | for sessionInfo in self.cursor: 37 | session = FIXSession(sessionInfo[0], sessionInfo[1], sessionInfo[2]) 38 | session.sndSeqNum = sessionInfo[3] 39 | session.nextExpectedMsgSeqNum = sessionInfo[4] + 1 40 | sessions.append(session) 41 | 42 | return sessions 43 | 44 | def createSession(self, targetCompId, senderCompId): 45 | session = None 46 | try: 47 | self.cursor.execute("INSERT INTO session(targetCompId, senderCompId) VALUES(?, ?)", (targetCompId, senderCompId)) 48 | sessionId = self.cursor.lastrowid 49 | self.conn.commit() 50 | session = FIXSession(sessionId, targetCompId, senderCompId) 51 | except sqlite3.IntegrityError: 52 | raise RuntimeError("Session already exists for TargetCompId: %s SenderCompId: %s" % (targetCompId, senderCompId)) 53 | 54 | return session 55 | 56 | def persistMsg(self, msg, session, direction): 57 | msgStr = pickle.dumps(msg) 58 | seqNo = msg["34"] 59 | try: 60 | self.cursor.execute("INSERT INTO message VALUES(?, ?, ?, ?)", (seqNo, session.key, direction.value, msgStr)) 61 | if direction == MessageDirection.OUTBOUND: 62 | self.cursor.execute("UPDATE session SET outboundSeqNo=?", (seqNo,)) 63 | elif direction == MessageDirection.INBOUND: 64 | self.cursor.execute("UPDATE session SET inboundSeqNo=?", (seqNo,)) 65 | 66 | self.conn.commit() 67 | except sqlite3.IntegrityError as e: 68 | raise DuplicateSeqNoError("%s is a duplicate" % (seqNo, )) 69 | 70 | def recoverMsg(self, session, direction, seqNo): 71 | try: 72 | msgs = self.recoverMsgs(session, direction, seqNo, seqNo) 73 | return msgs[0] 74 | except IndexError: 75 | return None 76 | 77 | def recoverMsgs(self, session, direction, startSeqNo, endSeqNo): 78 | self.cursor.execute("SELECT msg FROM message WHERE session = ? AND direction = ? AND seqNo >= ? AND seqNo <= ? ORDER BY seqNo", (session.key, direction.value, startSeqNo, endSeqNo)) 79 | msgs = [] 80 | for msg in self.cursor: 81 | msgs.append(pickle.loads(msg[0])) 82 | return msgs 83 | 84 | def getAllMsgs(self, sessions = [], direction = None): 85 | sql = "SELECT seqNo, msg, direction, session FROM message" 86 | clauses = [] 87 | args = [] 88 | if sessions is not None and len(sessions) != 0: 89 | clauses.append("session in (" + ','.join('?'*len(sessions)) + ")") 90 | args.extend(sessions) 91 | if direction is not None: 92 | clauses.append("direction = ?") 93 | args.append(direction.value) 94 | 95 | if clauses: 96 | sql = sql + " WHERE " + " AND ".join(clauses) 97 | 98 | sql = sql + " ORDER BY rowid" 99 | 100 | self.cursor.execute(sql, tuple(args)) 101 | msgs = [] 102 | for msg in self.cursor: 103 | msgs.append((msg[0], pickle.loads(msg[1]), msg[2], msg[3])) 104 | 105 | return msgs -------------------------------------------------------------------------------- /pyfix/message.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from enum import Enum 3 | 4 | class MessageDirection(Enum): 5 | INBOUND = 0 6 | OUTBOUND = 1 7 | 8 | class _FIXRepeatingGroupContainer: 9 | def __init__(self): 10 | self.groups = [] 11 | 12 | def addGroup(self, group, index): 13 | if index == -1: 14 | self.groups.append(group) 15 | else: 16 | self.groups.insert(index, group) 17 | 18 | def removeGroup(self, index): 19 | del self.groups[index] 20 | 21 | def getGroup(self, index): 22 | return self.groups[index] 23 | 24 | def __str__(self): 25 | return str(len(self.groups)) + "=>" + str(self.groups) 26 | 27 | __repr__ = __str__ 28 | 29 | class FIXContext(object): 30 | def __init__(self): 31 | self.tags = OrderedDict() 32 | 33 | def setField(self, tag, value): 34 | self.tags[tag] = value 35 | 36 | def removeField(self, tag): 37 | try: 38 | del self.tags[tag] 39 | except KeyError: 40 | pass 41 | 42 | def getField(self, tag): 43 | return self.tags[tag] 44 | 45 | def addRepeatingGroup(self, tag, group, index=-1): 46 | if tag in self.tags: 47 | groupContainer = self.tags[tag] 48 | groupContainer.addGroup(group, index) 49 | else: 50 | groupContainer = _FIXRepeatingGroupContainer() 51 | groupContainer.addGroup(group, index) 52 | self.tags[tag] = groupContainer 53 | 54 | def removeRepeatingGroupByIndex(self, tag, index=-1): 55 | if self.isRepeatingGroup(tag): 56 | try: 57 | if index == -1: 58 | del self.tags[tag] 59 | pass 60 | else: 61 | groups = self.tags[tag] 62 | groups.removeGroup(index) 63 | except KeyError: 64 | pass 65 | 66 | def getRepeatingGroup(self, tag): 67 | if self.isRepeatingGroup(tag): 68 | return (len(self.tags[tag].groups), self.tags[tag].groups) 69 | return None 70 | 71 | def getRepeatingGroupByTag(self, tag, identifierTag, identifierValue): 72 | if self.isRepeatingGroup(tag): 73 | for group in self.tags[tag].groups: 74 | if identifierTag in group.tags: 75 | if group.getField(identifierTag) == identifierValue: 76 | return group 77 | return None 78 | 79 | def getRepeatingGroupByIndex(self, tag, index): 80 | if self.isRepeatingGroup(tag): 81 | return self.tags[tag].groups[index] 82 | return None 83 | 84 | def __getitem__(self, tag): 85 | return self.getField(tag) 86 | 87 | def __setitem__(self, tag, value): 88 | self.setField(tag, value) 89 | 90 | def isRepeatingGroup(self, tag): 91 | return type(self.tags[tag]) is _FIXRepeatingGroupContainer 92 | 93 | def __contains__(self, item): 94 | return item in self.tags 95 | 96 | def __str__(self): 97 | r= "" 98 | allTags = [] 99 | for tag in self.tags: 100 | allTags.append("%s=%s" % (tag, self.tags[tag])) 101 | r += "|".join(allTags) 102 | return r 103 | 104 | def __eq__(self, other): 105 | # if our string representation looks the same, the objects are equivalent 106 | return self.__str__() == other.__str__() 107 | 108 | __repr__ = __str__ 109 | 110 | class FIXMessage(FIXContext): 111 | def __init__(self, msgType): 112 | self.msgType = msgType 113 | FIXContext.__init__(self) 114 | 115 | def setMsgType(self, msgType): 116 | self.msgType = msgType 117 | -------------------------------------------------------------------------------- /pyfix/server_connection.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import socket 3 | from pyfix.journaler import DuplicateSeqNoError 4 | from pyfix.session import FIXSession 5 | from pyfix.connection import FIXEndPoint, ConnectionState, MessageDirection, FIXConnectionHandler 6 | from pyfix.event import FileDescriptorEventRegistration, EventType 7 | 8 | class FIXServerConnectionHandler(FIXConnectionHandler): 9 | def __init__(self, engine, protocol, sock=None, addr=None, observer=None): 10 | FIXConnectionHandler.__init__(self, engine, protocol, sock, addr, observer) 11 | 12 | def handleSessionMessage(self, msg): 13 | protocol = self.codec.protocol 14 | 15 | recvSeqNo = msg[protocol.fixtags.MsgSeqNum] 16 | 17 | msgType = msg[protocol.fixtags.MsgType] 18 | targetCompId = msg[protocol.fixtags.TargetCompID] 19 | senderCompId = msg[protocol.fixtags.SenderCompID] 20 | responses = [] 21 | 22 | if msgType == protocol.msgtype.LOGON: 23 | if self.connectionState == ConnectionState.LOGGED_IN: 24 | logging.warning("Client session already logged in - ignoring login request") 25 | else: 26 | # compids are reversed here... 27 | self.session = self.engine.getOrCreateSessionFromCompIds(senderCompId, targetCompId) 28 | if self.session is not None: 29 | try: 30 | self.connectionState = ConnectionState.LOGGED_IN 31 | self.heartbeatPeriod = float(msg[protocol.fixtags.HeartBtInt]) 32 | responses.append(protocol.messages.Messages.logon()) 33 | self.registerLoggedIn() 34 | except DuplicateSeqNoError: 35 | logging.error("Failed to process login request with duplicate seq no") 36 | self.disconnect() 37 | return 38 | else: 39 | logging.warning("Rejected login attempt for invalid session (SenderCompId: %s, TargetCompId: %s)" % (senderCompId, targetCompId)) 40 | self.disconnect() 41 | return # we have to return here since self.session won't be valid 42 | elif self.connectionState == ConnectionState.LOGGED_IN: 43 | # compids are reversed here 44 | if not self.session.validateCompIds(senderCompId, targetCompId): 45 | logging.error("Received message with unexpected comp ids") 46 | self.disconnect() 47 | return 48 | 49 | if msgType == protocol.msgtype.LOGOUT: 50 | self.connectionState = ConnectionState.LOGGED_OUT 51 | self.registerLoggedOut() 52 | self.handle_close() 53 | elif msgType == protocol.msgtype.TESTREQUEST: 54 | responses.append(protocol.messages.Messages.heartbeat()) 55 | elif msgType == protocol.msgtype.RESENDREQUEST: 56 | responses.extend(self._handleResendRequest(msg)) 57 | elif msgType == protocol.msgtype.SEQUENCERESET: 58 | newSeqNo = msg[protocol.fixtags.NewSeqNo] 59 | self.session.setRecvSeqNo(int(newSeqNo) - 1) 60 | recvSeqNo = newSeqNo 61 | else: 62 | logging.warning("Can't process message, counterparty is not logged in") 63 | 64 | return (recvSeqNo, responses) 65 | 66 | class FIXServer(FIXEndPoint): 67 | def __init__(self, engine, protocol): 68 | FIXEndPoint.__init__(self, engine, protocol) 69 | 70 | def start(self, host, port): 71 | self.connections = [] 72 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 73 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 74 | self.socket.bind((host, port)) 75 | self.socket.listen(5) 76 | self.serverSocketRegistration = FileDescriptorEventRegistration(self.handle_accept, self.socket, EventType.READ) 77 | 78 | logging.debug("Awaiting Connections " + host + ":" + str(port)) 79 | self.engine.eventManager.registerHandler(self.serverSocketRegistration) 80 | 81 | def stop(self): 82 | logging.info("Stopping server connections") 83 | for connection in self.connections: 84 | connection.disconnect() 85 | self.serverSocketRegistration.fd.close() 86 | self.engine.eventManager.unregisterHandler(self.serverSocketRegistration) 87 | 88 | def handle_accept(self, type, closure): 89 | pair = self.socket.accept() 90 | if pair is not None: 91 | sock, addr = pair 92 | logging.info("Connection from %s" % repr(addr)) 93 | connection = FIXServerConnectionHandler(self.engine, self.protocol, sock, addr, self) 94 | self.connections.append(connection) 95 | for handler in filter(lambda x: x[1] == ConnectionState.CONNECTED, self.connectionHandlers): 96 | handler[0](connection) 97 | -------------------------------------------------------------------------------- /pyfix/session.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | class FIXSession: 4 | def __init__(self, key, targetCompId, senderCompId): 5 | self.key = key 6 | self.senderCompId = senderCompId 7 | self.targetCompId = targetCompId 8 | 9 | self.sndSeqNum = 0 10 | self.nextExpectedMsgSeqNum = 1 11 | 12 | def validateCompIds(self, targetCompId, senderCompId): 13 | return self.senderCompId == senderCompId and self.targetCompId == targetCompId 14 | 15 | def allocateSndSeqNo(self): 16 | self.sndSeqNum += 1 17 | return str(self.sndSeqNum) 18 | 19 | def validateRecvSeqNo(self, seqNo): 20 | if self.nextExpectedMsgSeqNum < int(seqNo): 21 | logging.warning("SeqNum from client unexpected (Rcvd: %s Expected: %s)" % (seqNo, self.nextExpectedMsgSeqNum)) 22 | return (False, self.nextExpectedMsgSeqNum) 23 | else: 24 | return (True, seqNo) 25 | 26 | def setRecvSeqNo(self, seqNo): 27 | # if self.nextExpectedMsgSeqNum != int(seqNo): 28 | # logging.warning("SeqNum from client unexpected (Rcvd: %s Expected: %s)" % (seqNo, self.nextExpectedMsgSeqNum)) 29 | self.nextExpectedMsgSeqNum = int(seqNo) + 1 30 | 31 | -------------------------------------------------------------------------------- /pyfix/transaction.py: -------------------------------------------------------------------------------- 1 | 2 | class TransactionResource(object): 3 | def __init__(self, action): 4 | self.action = action 5 | 6 | def commit(self): 7 | if self.action != None: 8 | self.action() 9 | 10 | class Transaction(TransactionResource): 11 | 12 | def __init__(self): 13 | self.resources = [] 14 | TransactionResource.__init__(self, None) 15 | 16 | def addResource(self, resource): 17 | self.resources.append(resource) 18 | pass 19 | 20 | def commit(self): 21 | for resource in self.resources: 22 | resource.commit() 23 | 24 | class PriorityTransaction(TransactionResource): 25 | def __init__(self): 26 | self.resources = [] 27 | TransactionResource.__init__(self, None) 28 | 29 | def addResource(self, resource, priority): 30 | self.resources.append((priority, resource)) 31 | 32 | def commit(self): 33 | # TODO: sort the resources... 34 | # High --> Low (so you can always make something higher priority) 35 | for resource in self.resources: 36 | resource.commit() 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | config = { 7 | 'description': 'pyfix', 8 | 'author': 'Tom Fewster', 9 | 'url': 'https://github.com/wannabegeek/PyFIX/', 10 | 'download_url': 'https://github.com/wannabegeek/PyFIX/', 11 | 'author_email': 'tom@wanabegeek.com.', 12 | 'version': '0.1', 13 | 'install_requires': [''], 14 | 'packages': ['pyfix', 'pyfix/FIX44'], 15 | 'scripts': [], 16 | 'name': 'pyfix' 17 | } 18 | 19 | setup(**config) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wannabegeek/PyFIX/b903e70ce79cefc2525200f84ee4f90c5bf4c686/tests/__init__.py -------------------------------------------------------------------------------- /tests/codec_tests.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import datetime 3 | import mock as mock 4 | from pyfix.codec import Codec 5 | from pyfix.message import FIXMessage, FIXContext 6 | 7 | __author__ = 'tom' 8 | 9 | import unittest 10 | 11 | class FakeDate(datetime.datetime): 12 | @classmethod 13 | def utcnow(cls): 14 | return cls(2015, 6, 19, 11, 8, 54) 15 | 16 | class FIXCodecTests(unittest.TestCase): 17 | def testDecode(self): 18 | protocol = importlib.import_module("pyfix.FIX44") 19 | codec = Codec(protocol) 20 | inMsg = b'8=FIX.4.4\x019=817\x0135=J\x0134=953\x0149=FIX_ALAUDIT\x0156=BFUT_ALAUDIT\x0143=N\x0152=20150615-09:21:42.459\x0170=00000002664ASLO1001\x01626=2\x0110626=5\x0171=0\x0160=20150615-10:21:42\x01857=1\x0173=1\x0111=00000006321ORLO1\x0138=100.0\x01800=100.0\x01124=1\x0132=100.0\x0117=00000009758TRLO1\x0131=484.50\x0154=2\x0153=100.0\x0155=FTI\x01207=XEUE\x01454=1\x01455=EOM5\x01456=A\x01200=201506\x01541=20150619\x01461=FXXXXX\x016=484.50\x0174=2\x0175=20150615\x0178=2\x0179=TEST123\x0130009=12345\x01467=00000014901CALO1001\x019520=00000014898CALO1\x0180=33.0\x01366=484.50\x0181=0\x01153=484.50\x0110626=5\x0179=TEST124\x0130009=12345\x01467=00000014903CALO1001\x019520=00000014899CALO1\x0180=67.0\x01366=484.50\x0181=0\x01153=484.50\x0110626=5\x01453=3\x01448=TEST1\x01447=D\x01452=3\x01802=2\x01523=12345\x01803=3\x01523=TEST1\x01803=19\x01448=TEST1WA\x01447=D\x01452=38\x01802=4\x01523=Test1 Wait\x01803=10\x01523= \x01803=26\x01523=\x01803=3\x01523=TestWaCRF2\x01803=28\x01448=hagap\x01447=D\x01452=11\x01802=2\x01523=GB\x01803=25\x01523=BarCapFutures.FETService\x01803=24\x0110=033\x01' 21 | msg, remaining = codec.decode(inMsg) 22 | 23 | self.assertEqual("8=FIX.4.4|9=817|35=J|34=953|49=FIX_ALAUDIT|56=BFUT_ALAUDIT|43=N|52=20150615-09:21:42.459|70=00000002664ASLO1001|626=2|10626=5|71=0|60=20150615-10:21:42|857=1|73=1=>[11=00000006321ORLO1|38=100.0|800=100.0]|124=1=>[32=100.0|17=00000009758TRLO1|31=484.50]|54=2|53=100.0|55=FTI|207=XEUE|454=1=>[455=EOM5|456=A]|200=201506|541=20150619|461=FXXXXX|6=484.50|74=2|75=20150615|78=1=>[79=TEST123]|30009=12345|467=00000014903CALO1001|9520=00000014899CALO1|80=67.0|366=484.50|81=0|153=484.50|79=TEST124|453=3=>[448=TEST1|447=D|452=3|802=2=>[523=12345|803=3, 523=TEST1|803=19], 448=TEST1WA|447=D|452=38|802=4=>[523=Test1 Wait|803=10, 523= |803=26, 523=|803=3, 523=TestWaCRF2|803=28], 448=hagap|447=D|452=11|802=2=>[523=GB|803=25, 523=BarCapFutures.FETService|803=24]]|10=033", str(msg)) 24 | 25 | @mock.patch("pyfix.codec.datetime", FakeDate) 26 | def testEncode(self): 27 | from datetime import datetime 28 | 29 | print("%s" % (datetime.utcnow())) 30 | 31 | mock_session = mock.Mock() 32 | mock_session.senderCompId = "sender" 33 | mock_session.targetCompId = "target" 34 | mock_session.allocateSndSeqNo.return_value = 1 35 | 36 | protocol = importlib.import_module("pyfix.FIX44") 37 | codec = Codec(protocol) 38 | 39 | msg = FIXMessage(codec.protocol.msgtype.NEWORDERSINGLE) 40 | msg.setField(codec.protocol.fixtags.Price, "123.45") 41 | msg.setField(codec.protocol.fixtags.OrderQty, 9876) 42 | msg.setField(codec.protocol.fixtags.Symbol, "VOD.L") 43 | msg.setField(codec.protocol.fixtags.SecurityID, "GB00BH4HKS39") 44 | msg.setField(codec.protocol.fixtags.SecurityIDSource, "4") 45 | msg.setField(codec.protocol.fixtags.Symbol, "VOD.L") 46 | msg.setField(codec.protocol.fixtags.Account, "TEST") 47 | msg.setField(codec.protocol.fixtags.HandlInst, "1") 48 | msg.setField(codec.protocol.fixtags.ExDestination, "XLON") 49 | msg.setField(codec.protocol.fixtags.Side, 1) 50 | msg.setField(codec.protocol.fixtags.ClOrdID, "abcdefg") 51 | msg.setField(codec.protocol.fixtags.Currency, "GBP") 52 | 53 | rptgrp1 = FIXContext() 54 | rptgrp1.setField("611", "aaa") 55 | rptgrp1.setField("612", "bbb") 56 | rptgrp1.setField("613", "ccc") 57 | 58 | msg.addRepeatingGroup("444", rptgrp1, 0) 59 | 60 | rptgrp2 = FIXContext() 61 | rptgrp2.setField("611", "zzz") 62 | rptgrp2.setField("612", "yyy") 63 | rptgrp2.setField("613", "xxx") 64 | msg.addRepeatingGroup("444", rptgrp2, 1) 65 | 66 | result = codec.encode(msg, mock_session) 67 | expected = '8=FIX.4.4\x019=201\x0135=D\x0149=sender\x0156=target\x0134=1\x0152=20150619-11:08:54.000\x0144=123.45\x0138=9876\x0155=VOD.L\x0148=GB00BH4HKS39\x0122=4\x011=TEST\x0121=1\x01100=XLON\x0154=1\x0111=abcdefg\x0115=GBP\x01444=2\x01611=aaa\x01612=bbb\x01613=ccc\x01611=zzz\x01612=yyy\x01613=xxx\x0110=255\x01' 68 | self.assertEqual(expected, result) 69 | 70 | 71 | if __name__ == '__main__': 72 | unittest.main() 73 | 74 | -------------------------------------------------------------------------------- /tests/event_tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | from pyfix.event import EventManager, TimerEventRegistration 4 | 5 | 6 | class EventTimerTests(unittest.TestCase): 7 | def testTimerEvent(self): 8 | mgr = EventManager() 9 | endTime = None 10 | t1 = TimerEventRegistration(lambda fire, closure: self.assertEqual(int((datetime.datetime.utcnow() - closure).total_seconds()), 1), 1.0, datetime.datetime.utcnow()) 11 | mgr.registerHandler(t1) 12 | mgr.waitForEventWithTimeout(5.0) 13 | 14 | def testTimerEventReset(self): 15 | mgr = EventManager() 16 | t1 = TimerEventRegistration(lambda fire, closure: self.assertEqual(int((datetime.datetime.utcnow() - closure).total_seconds()), 2), 1.0, datetime.datetime.utcnow()) 17 | mgr.registerHandler(t1) 18 | mgr.registerHandler(TimerEventRegistration(lambda fire, closure: t1.reset(), 0.9)) 19 | 20 | for i in range(0, 3): 21 | mgr.waitForEventWithTimeout(10.0) 22 | -------------------------------------------------------------------------------- /tests/journaler_tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyfix.connection import MessageDirection 3 | from pyfix.journaler import Journaler 4 | from pyfix.message import FIXMessage, FIXContext 5 | from pyfix.session import FIXSession 6 | 7 | 8 | class JournalerTests(unittest.TestCase): 9 | def testAddExtractMsg(self): 10 | journal = Journaler() 11 | 12 | msg = FIXMessage("AB") 13 | msg.setField("45", "dgd") 14 | msg.setField("32", "aaaa") 15 | msg.setField("323", "bbbb") 16 | 17 | rptgrp1 = FIXContext() 18 | rptgrp1.setField("611", "aaa") 19 | rptgrp1.setField("612", "bbb") 20 | rptgrp1.setField("613", "ccc") 21 | 22 | msg.addRepeatingGroup("444", rptgrp1, 0) 23 | session = FIXSession(1, "S1", "T1") 24 | for i in range(0, 5): 25 | msg.setField("34", str(i)) 26 | journal.persistMsg(msg, session, MessageDirection.OUTBOUND) 27 | 28 | msg = journal.recoverMsg(session, MessageDirection.OUTBOUND, 1) 29 | 30 | def testAddExtractMultipleMsgs(self): 31 | journal = Journaler() 32 | 33 | msg = FIXMessage("AB") 34 | msg.setField("45", "dgd") 35 | msg.setField("32", "aaaa") 36 | msg.setField("323", "bbbb") 37 | 38 | rptgrp1 = FIXContext() 39 | rptgrp1.setField("611", "aaa") 40 | rptgrp1.setField("612", "bbb") 41 | rptgrp1.setField("613", "ccc") 42 | 43 | msg.addRepeatingGroup("444", rptgrp1, 0) 44 | session = FIXSession(1, "S1", "T1") 45 | for i in range(0, 5): 46 | msg.setField("34", str(i)) 47 | journal.persistMsg(msg, session, MessageDirection.OUTBOUND) 48 | 49 | msgs = journal.recoverMsgs(session, MessageDirection.OUTBOUND, 0, 4) 50 | for i in range(0, len(msgs)): 51 | msg.setField("34", str(i)) 52 | self.assertEqual(msg, msgs[i]) 53 | -------------------------------------------------------------------------------- /tests/message_tests.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | from pyfix.message import FIXMessage, FIXContext 3 | 4 | __author__ = 'tom' 5 | 6 | import unittest 7 | 8 | 9 | class FIXMessageTests(unittest.TestCase): 10 | def testMsgConstruction(self): 11 | msg = FIXMessage("AB") 12 | msg.setField("45", "dgd") 13 | msg.setField("32", "aaaa") 14 | msg.setField("323", "bbbb") 15 | 16 | rptgrp1 = FIXContext() 17 | rptgrp1.setField("611", "aaa") 18 | rptgrp1.setField("612", "bbb") 19 | rptgrp1.setField("613", "ccc") 20 | 21 | msg.addRepeatingGroup("444", rptgrp1, 0) 22 | 23 | rptgrp2 = FIXContext() 24 | rptgrp2.setField("611", "zzz") 25 | rptgrp2.setField("612", "yyy") 26 | rptgrp2.setField("613", "xxx") 27 | msg.addRepeatingGroup("444", rptgrp2, 1) 28 | 29 | self.assertEqual("45=dgd|32=aaaa|323=bbbb|444=2=>[611=aaa|612=bbb|613=ccc, 611=zzz|612=yyy|613=xxx]", str(msg)) 30 | 31 | msg.removeRepeatingGroupByIndex("444", 1) 32 | self.assertEqual("45=dgd|32=aaaa|323=bbbb|444=1=>[611=aaa|612=bbb|613=ccc]", str(msg)) 33 | 34 | msg.addRepeatingGroup("444", rptgrp2, 1) 35 | 36 | rptgrp3 = FIXContext() 37 | rptgrp3.setField("611", "ggg") 38 | rptgrp3.setField("612", "hhh") 39 | rptgrp3.setField("613", "jjj") 40 | rptgrp2.addRepeatingGroup("445", rptgrp3, 0) 41 | self.assertEqual("45=dgd|32=aaaa|323=bbbb|444=2=>[611=aaa|612=bbb|613=ccc, 611=zzz|612=yyy|613=xxx|445=1=>[611=ggg|612=hhh|613=jjj]]", str(msg)) 42 | 43 | grp = msg.getRepeatingGroupByTag("444", "612", "yyy") 44 | self.assertEqual("611=zzz|612=yyy|613=xxx|445=1=>[611=ggg|612=hhh|613=jjj]", str(grp)) 45 | 46 | def testPickle(self): 47 | msg = FIXMessage("AB") 48 | msg.setField("45", "dgd") 49 | msg.setField("32", "aaaa") 50 | msg.setField("323", "bbbb") 51 | 52 | rptgrp1 = FIXContext() 53 | rptgrp1.setField("611", "aaa") 54 | rptgrp1.setField("612", "bbb") 55 | rptgrp1.setField("613", "ccc") 56 | 57 | msg.addRepeatingGroup("444", rptgrp1, 0) 58 | 59 | str = pickle.dumps(msg) 60 | 61 | msg2 = pickle.loads(str) 62 | self.assertEqual(msg, msg2) 63 | 64 | if __name__ == '__main__': 65 | unittest.main() 66 | --------------------------------------------------------------------------------