diff --git a/doc/conf.py b/doc/conf.py index 8aecf27..cb25a49 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -148,7 +148,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'SimpleFIX.tex', u'SimpleFIX Documentation', + (master_doc, 'SimpleFIX.tex', u'SimpleFIX Programmer\'s Guide', u'David Arnold', 'manual'), ] @@ -158,7 +158,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'simplefix', u'SimpleFIX Documentation', + (master_doc, 'simplefix', u'Programmer\'s Guide', [author], 1) ] diff --git a/doc/creating.rst b/doc/creating.rst index 3de609a..18907b4 100644 --- a/doc/creating.rst +++ b/doc/creating.rst @@ -4,11 +4,13 @@ Creating Messages To create a FIX message, first create an instance of the FixMessage class. - +.. index:: FixMessage .. code-block:: python + :linenos: messsage = simplefix.FixMessage() +.. index:: header You can then add fields to the message as required. You should add the standard header tags 8, 34, 35, 49, 52, and 56 to all messages, unless @@ -17,10 +19,13 @@ you're deliberately creating a malformed message for testing or similar. Simple Fields ............. +.. index:: append_pair + For most tags, using ``append_pair()`` is the easiest way to add a field to the message. .. code-block:: python + :linenos: message.append_pair(1, "MC435967") message.append_pair(54, 1) @@ -29,16 +34,20 @@ to the message. Note that any type of value can be used: it will be explicitly converted to a string before encoding the message. +.. index:: BeginString, BodyLength, MsgType, Checksum + With a few exceptions, the message retains the order in which fields are -added. The exceptions are that fields BeginString (8), Length (9), +added. The exceptions are that fields BeginString (8), BodyLength (9), MsgType (35), and Checksum (10) are encoded in their required locations, regardless of what order they were added to the Message. Header Fields ............. -The Message class does not distinguish header fields from body fields, -with one exception. +The ``FixMessage`` class does not distinguish header fields from body +fields, with one exception. + +.. index:: append_pair, header To enable fields to be added to the FIX header after body fields have already been added, there's an optional keyword parameter to the @@ -47,10 +56,13 @@ already been added, there's an optional keyword parameter to the any previously added header fields, starting at the beginning of the message. +.. index:: MsgSeqNum, SendingTime + This is normally used for setting things like MsgSeqNum (34) and SendingTime (52) immediately prior to encoding and sending the message. .. code-block:: python + :linenos: message.append_pair(8, "FIX.4.4") message.append_pair(35, 0) @@ -65,19 +77,24 @@ the message. After encoding, the order of fields would be: 8, 9, 35, 34, 52, 49, 56, 112, 10. It's not necessary, but field 49 and 56 could also be written with -``header`` set ``True``, in which case, they'd precede 34 ane 52 when +``header`` set ``True``, in which case, they'd precede 34 and 52 when encoded. -See ``append_time()`` below for details of that method. +.. index:: append_utc_timestamp + +See ``append_utc_timestamp()`` below for details of that method. Pre-composed Pairs .................. +.. index:: append_string, append_strings + In some cases, your FIX application might have the message content as pre-composed "tag=value" strings. In this case, as an optimisation, the ``append_string()`` or ``append_strings()`` methods can be used. .. code-block:: python + :linenos: BEGIN_STRING = "8=FIX.4.2" STR_SEQ = ["49=SENDER", "56=TARGET"] @@ -92,23 +109,32 @@ body fields. Timestamps .......... +.. index:: UTCTimestamp, UTCTimeOnly, TZTimestamp, TZTimeOnly + The FIX protocol defines four time types: UTCTimestamp, UTCTimeOnly, TZTimestamp, and TZTimeOnly. Field values of these types can be added using dedicated functions, avoiding the need to translate and format time values in the application code. +.. index:: append_utc_timestamp, append_tz_timestamp +.. index:: append_utc_time_only, append_tz_time_only .. code-block:: python + :linenos: message.append_utc_timestamp(52, precision=6, header=True) message.append_tz_timestamp(1132, my_datetime) message.append_utc_time_only(1495, start_time) message.append_tz_time_only(1079, maturity_time) +.. index:: time, datetime + The first parameter to these functions is the field's tag number. The second parameter is optional: if None or not supplied, it defaults to the current time, otherwise it must be a Unix epoch time (like from ``time.time()``), or a ``datetime`` instance. +.. index:: precision, second, milliseconds, microseconds, header + There are two keyword parameters: ``precision`` which can be 0 for just seconds, 3 for milliseconds, or 6 for microseconds; and ``header`` to insert this field in the header rather than the body. @@ -116,7 +142,9 @@ insert this field in the header rather than the body. In addition, there are a set of methods for creating correctly formatted time only values from their components: +.. index:: append_utc_time_only_parts, append_tz_time_only_parts .. code-block:: python + :linenos: message.append_utc_time_only_parts(1495, 7, 0, 0, 0, 0) message.append_tz_time_only_parts(1079, 20, 0, 0, offset=-300) @@ -125,6 +153,8 @@ As usual, the first parameter to these functions is the field's tag number. The next three parameters are the hour, minute, and seconds of the time value, followed by optional milliseconds and microseconds values. +.. index:: timezone + The timezone for the TZTimeOnly field is set using an offset value, the number of minutes east of UTC. Thus CET will be offset 60 minutes, and New York offset -240 minutes (four hours west). @@ -136,7 +166,9 @@ to manage the formatting itself. Repeating Groups ................ -There is no specific support for creating repeating groups in Messages. +.. index:: repeating group + +There is no specific support for creating repeating groups in FixMessages. The count field must be appended first, followed by the group's member's fields. @@ -146,15 +178,20 @@ but note that the count fields are not added automatically. Data Fields ........... +.. index:: data, raw data + There are numerous defined fields in the FIX protocol that use the *data* type. These fields consist of two parts: a length, which must come first, immediately followed by the value field, whose value may include the ASCII SOH character, the ASCII NUL character, and in fact any 8-bit byte value. +.. index:: append_data + To append a data field to a message, the ``append_data()`` method can be used. It will correctly add both the length field and the value field. .. code-block:: python + :linenos: message.append_data(95, 96, "RAW DATA \x00\x01 VALUE") diff --git a/doc/getting.rst b/doc/getting.rst index 235a95d..1fcd3bf 100644 --- a/doc/getting.rst +++ b/doc/getting.rst @@ -4,8 +4,8 @@ Installation ============ simplefix has a few dependencies. Firstly, it is known to run on -Python_ 2.6, 2.7, 3.3, 3.4, 3.5, 3.6, and 3.7. It will not run on -Python 2.5 or earlier. +Python_ 3.3 through to 3.10. It will not run on Python 2.7 or +earlier versions. You can install it using pip_:: diff --git a/doc/import.rst b/doc/import.rst index 9334c37..cc4b3db 100644 --- a/doc/import.rst +++ b/doc/import.rst @@ -4,6 +4,7 @@ Importing You can import the *simplefix* module maintaining its internal structure, or you can import some or all bindings directly. +.. index:: simplefix, FixMessage .. code-block:: python :linenos: @@ -22,6 +23,8 @@ Or message = FixMessage() +.. index:: import, namespace + Note that the "import \*" form is explicitly supported, with the exposed namespace explicitly managed to contain only the public features of the package. diff --git a/doc/index.rst b/doc/index.rst index 0db3e21..c816d5c 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -13,10 +13,3 @@ SimpleFIX creating encoding parsing - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/doc/parsing.rst b/doc/parsing.rst index 38735de..051e813 100644 --- a/doc/parsing.rst +++ b/doc/parsing.rst @@ -3,28 +3,35 @@ Parsing Messages To parse a FIX message, first create an instance of the FixParser class. +.. index:: FixParser .. code-block:: python + :linenos: parser = simplefix.FixParser() +.. INDEX:: append_buffer, get_message, FixMessage + To extract FIX messages from a byte buffer, such as that received from a socket, you should append it to the internal reassembly buffer using ``append_buffer()`` . At any time, you can call ``get_message()`` : if there's -no complete message in the parser's internal buffer, it'll return None, +no complete message in the parser's internal buffer, it'll return ``None``, otherwise, it'll return a ``FixMessage`` instance. .. code-block:: python + :linenos: parser.append_buffer(response_from_socket) message = parser.get_message() +.. index:: count, get -Once you've received a ``FixMessage`` from ``get_message()`` , you can: check -the number of fields with ``count()`` , retrieve the value of a field using -``get()`` or the built-in "[ ]" syntax, or iterate over all the fields using -"for ... in ...". +Once you've received a ``FixMessage`` from ``get_message()``, you can: check +the number of fields with ``count()``, retrieve the value of a field using +``get()`` or the built-in ``[ ]`` syntax, or iterate over all the fields +using ``for ... in ...``. .. code-block:: python + :linenos: message.count(49) >>> 1 @@ -35,11 +42,201 @@ the number of fields with ``count()`` , retrieve the value of a field using message["35"] >>> 'A' +.. index:: repeating group + Members of repeating groups can be accessed using ``get(tag, nth)``, where the "nth" value is an integer indicating the number of the group to use (note that the first group is number one, not zero). .. code-block:: python + :linenos: messsage = get(9061, 2) >>> 22 + +Parser Options +.............. + +.. index:: options + +By default, the parser is quite forgiving, and will attempt to extract FIX +messages from the supplied buffer ignoring strict adherence to the standard. + +In some cases however, it can be useful to instruct the parser to be more or +less strict in particular ways, depending on the protocol implementation +you're dealing with. + +The parser's constructor accepts several different keyword arguments, +each controlling a specific aspect of the parser's behaviour. These options +can also be configured using individual functions on the parser object. + +Empty Values +~~~~~~~~~~~~ + +The FIX standards explicitly prohibit the use of empty (zero-length) +values. In practice however, these are sometimes seen, and this option +allows them to be parsed. + +For example, a message like + +.. code-block:: + :linenos: + + ...|TAG1=VAL1|TAG2=|TAG3=VAL3|... + +.. index:: EmptyValueError + +would, by default, raise the ``EmptyValueError`` exception. This option +prevents that exception, and returns an empty string value instead. + +.. index:: FixParser, allow_empty_values + +.. code-block:: python + :linenos: + + parser = simplefix.FixParser(allow_empty_values=True) + +or + +.. index:: FixParser, set_allow_empty_values + +.. code-block:: python + :linenos: + + parser = simplefix.FixParser() + parser.set_allow_empty_values(True) + +Missing BeginString +~~~~~~~~~~~~~~~~~~~ +The *BeginString(8)* tag is required by the standard to be the first field +of all messages: always present, and always first. By default, the +parser ensures that this is the case. This option disables that check. + +.. index:: FixParser, allow_missing_begin_string, BeginString + +.. code-block:: python + :linenos: + + parser = simplefix.FixParser(allow_missing_begin_string=True) + +or + +.. index:: FixParser, set_allow_missing_begin_string, BeginString + +.. code-block:: python + :linenos: + + parser = simplefix.FixParser() + parser.set_allow_missing_begin_string(True) + +Note: see Strip Fields Before BeginString below for restrictions on +combining that with this option. + +Strip Fields Before BeginString +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In some cases, message reception timestamps, inbound/outbound +direction flags, or other data might be encoded as "FIX" fields +prior to the *BeginString(8)*. This option instructs the parser to +discard any fields found before the *BeginString(8)* when parsing. + +.. index:: FixParser, strip_fields_before_begin_string, BeginString + +.. code-block:: python + :linenos: + + parser = simplefix.FixParser(strip_fields_before_begin_string=True) + +or + +.. index:: FixParser, set_strip_fields_before_begin_string, BeginString + +.. code-block:: python + :linenos: + + parser = simplefix.FixParser() + parser.set_strip_fields_before_begin_string(True) + +.. index:: allow_missing_begin_string + +Note: this option cannot be combined with +``allow_missing_begin_string`` as it requires a *BeginString(8)* field +to stop stripping. + +Parser Errors +............. + +.. index:: get_message, reassembly buffer + +The ``get_message()`` method on the parser attempts to decode a FIX +message from its internal reassembly buffer. It is not an error for +there to be no message or an incomplete message to be in the +reassembly buffer when it is called. In these cases, ``get_message()`` +will simply return ``None``. + +However, if the parser is unable to successfully decode a message, or +if any configured validation checks fail, the parser will raise an +exception to report the problem. + +Possible exceptions are: + +.. index:: allow_empty_values + +.. py:exception:: EmptyValueError + + The parser read a field where the equals-sign was followed + immediately by the field terminator byte (``SOH``). This is + not permitted by the FIX standard. + + Use the ``allow_empty_values`` parser option override this + prohibition. + +.. index:: BeginString, BodyLength, MsgType + +.. py:exception:: FieldOrderError + + The FIX standard requires messages to contain some tags in + a specific order and position. For instance, *BeginString(8)*, + *BodyLength(9)*, and *MsgType(35)* must occur in that order at + the start of the message. + + This exception indicates that a tag was seen in an unexpected + order or a tag was not seen where it was expected. + +.. index:: stop_byte + +.. py:exception:: IncompleteTagError + + When the parser is configured with ``stop_byte``, this exception + indicates that the stop byte was read part-way through reading + a tag -- that is, following a field terminator (``SOH``), and + one or more tag digits, but before the equals sign. + + This normally indicates a corrupted message. + +.. index:: remove_raw + +.. py:exception:: RawLengthNotNumberError + + Raw data is encoded using two fields: a length field + followed by the value field. This exception indicates that + a field whose tag number is registered as being a raw data + *length* field was parsed, but that its value could not be + decoded as a positive integer as expected. + + Usually, this means that the message being parsed uses a tag + number that the FIX standard reserves as a raw data length + field, but is here being used for another purpose. + + See ``simplefix.FixParser.remove_raw()`` for a way to change + the set of tag numbers expected to be raw data lengths and + values. + +.. py:exception:: TagNotNumberError + + A field was parsed where the tag value, between the previous + field terminator byte (``SOH``) and the equals-sign, could not be + converted to a positive integer. + + This normally results from a corrupted message, often during + development but usually rare in production. It might suggest + problems reassembling the byte stream from the socket layer.