Types are the fundamental way in which data in the model is categorised. In this section we explain the types of data which can be defined and show how the declare types and define new ones. There is a link between an encoding and a type which corresponds to the correlation of the string accepted in a FIX message, for example, and the data representing that string in the model. We will explain this correlation in the sections that follow.
Literals are basic values that one can encode in IPL. There are four types of such values: strings, integers, floating point numbers and boolean constants.
- Strings are character sequences enclosed in double quotes:
- Integer literals are basic integer numbers. No special suffixes/prefixes are allowed – the negative numbers are application of unary negation operator to an integer literal.
- Float number literal must be formatted as (whole part).(fractional part).
- Boolean literals are just two standard boolean constants
Within IPL there are several given compound datatypes - these are concrete types with arguments. At the time of writing, those that are supported are:
The way one can declare a field of this type is simply by declaring an object such a field as
for example. To attribute values and exploit the types of the arguments we can create values, as described in the Values section.
TYPE is the name of the type. For example
Specifications may indicate precision of float types it uses. This requires additional care when performing arithmetic operations. In order to allow this control we introduce two notions:
- Type aliases
- Float precision
In order to declare an alias one can write for example
With float aliases, a user may wish to explicitly state the precision:
Note that precision may be set for any alias of a floating point types Qty, Amount, etc. It cannot be set for the float type itself. When analysing the code, this precision is taken into account and the correct multiplcation and addition precision is used for the necessary types.
Similarly to float precision it is possible to specify the precision of fields which have timestamp related types. The two types to which this applies are
It is possible to specify for these whether the type should have millisecond or microsecond precision. By default all time related fields have millisecond precision. The way to define specific precision per field is to use the following syntax, for example:
This states the default time precision for all fields is millisecond but that the field
TransactTime is univerally given microsecond time precision.
A simple enum declaration contains a type name and a list of allowed cases:
Optionally, it is possible to add a FIX tag value, used to represent the given case on the wire – the value should be in the double quotes, following the case declaration.
Descriptions for each case (and for the type itself) can be given using the
@description: annotation before the declaration. Also, an
@encoding: annotation can be used to state which encoding type is used on the wire for that enum. If this encoding is not present, the encoding is assumed to be of type
string. If the provided FIX value does not match the encoding, IPL will throw an error. Here is an example of the enum declaration, used for for the AccountType field (tag 581) in the FIX data dictionary:
Here in the first entry, the FIX value corresponding to the CarriedCustomSide case is “1”, the encoding type for the AccountType enum is int. Note that the ‘@description’ annotation used in the example above is entirely optional.
The encoding type char can be given, which denotes that the FIX tag value for the case is a string of fixed length 1. It is possible to encode generally fixed length strings and multiple value chars and strings. Both functionalities for multiple value strings and fixed length strings are forthcoming.
A standard practice is to make certain aspects of the exchanged data optional. The logic may still refer to them, but we need to be careful as to not depend on a calculation that refers to a field that may not exist. For example, consider the following example:
- An incoming message is declared with an optional type ‘Price’
- Within our processing logic, we assign this value to our internal state so that all further analytics are done with it
The problem is that this value may not be assigned after all, so then if we were to merely execute the model, we would get something similar to a NullPointerException that Java developers are well familiar with.
Supporting exceptions would lead to a much more complex syntax and user experience - not to mention the consequences on the mathematical analysis that this would entail. As a result we introduce the concept of optional types. We give examples of how fields in IPL can be declared as optional in the Records section, and discuss how we can reason using optional types in the Optional Types section of the Expressions section.
A record data type is useful for representing internal structures and provide a way of grouping information together. A simple record declaration contains a type name and a list of the record’s fields with their corresponding types:
Optionally, it is possible to add:
- a FIX tag, used to represent the field on the wire – it should be in double quotes, following the field name
- descriptions for each field (and for the type itself), using
@description:annotations before the declaration
- a ‘?’ question mark before the type name: that declares the filed as optional – it will have an option type and will be treated differently in the message processing logic
It is necessary to include a fix tag to a field if it will be imported via a message. For more on importing see the Messages section which describes this process. We can for example include a new record
This record now adds an optional week field. This means that within the record it may not be present or it may have a value. All of the fields now have FIX tab values which means elements from this record can be imported via messages.
Here is an example of a record declaration, used for the
This is an example of a Repeating Group. This means that many copies of the information held within the group may be present in an incoming message. The field of type
NumInGroup indicates the number of copies of this record that are present. Any repeating group must have an element with type either called
NumInGroup or of type
NoSides. Within the group above there is a field of type
PtysSubGrp. This is a repeating group within a repeating group. If there were 3 copies of the information within the
Parties group and 3 copies of each
PtysSubGrp within each of those, there would in total be 9 copies of the
PtysSubGrp repeating group. We explain how we can reason about the structure of Reasoning Groups in the General Validation section. The structure and typing of repeating groups is assumed to be the same for every copy that appears in the model - that is to say that should a repeating group be imported by two messages, its optionality and type structure is the same in both copies.
In order for a repeating group to be incorporated into the model, it must be explicitly imported in a similar way to Messages. For every copy of a repeating group in the file, the optionality of each field can be determined:
IPL allows the addition of custom tags for standard messages, records or enums. For example in the FIX protocol, as described in the secion on enumeration types, an enumeration type receives a tag value in the FIX message which may have an encoding. This defines all the possible string values that can be received in a FIX message. We can extend the values that can be received using a custom extension to the enumeration type. This is also the case for field entries in records and messages as we will demonstrate in this section.
Custom Enum Entries
FIX data dictionaries are large collections of message, record and enum declaration that reflect the FIX protocol specification. Sometimes users of the FIX protocol are adding their own custom fields or enum cases. They have their own tag/value codes and exhibit some venue-specific meanings and behaviors. To allow an ability to encode in IPL such customizations to the basic FIX protocol, we introduce the “extend” statements.
Consider a TimeInForce enum declaration from the FIX data dictionary:
Using “extend enum” one can add their own entries to existing enums. For example, in the code section below, the extra element “BookOrCancel” is added to the enum “TimeInForce” - note that the given tag or name cannot already exist in the enum.
Within a custom enum extension it is also possible to override the name of an existing element with a given tag, should a user wish to attribute a different descriptive name or behaviour to that tag. This can be done for example by writing
Custom Record Field Entries
Some venues may deviate from the standard protocol specification by requiring or making optional new fields for a given message. To allow users to extend the baseline definition, we’ve incorporated syntax to ‘customise’ an already declared message.
This is a custom entry in the BATS definition of a FIX message field. It has tag 9303, a user-defined description for documentation “Routing Instruction (Bats Only orders)”, and has values defined by the enum RoutingInst (see next section).
Analogously, users may wish to extend fields in existing declared records. This is done using the following code:
Custom Entries in Repeating Group Definitions
It is possible to change the optionality of fields globally for a repeating group. Recall from the Repeating Groups that in the FIX dictionaries the
Parties repeating group is given as (with the documenation comments removed for clarity)
A user may wish to ensure that the
PartyID always be present. This can be achieved also within a custom extension by writing
In addition if the special element which determines the number of repeating groups in the message is non-optional, then this means that the repeating group must be present in the FIX message. The order of elements is important in repeating groups - the list of fields must be consistent with the order in which the field entries can appear in a FIX message.
IPL contains support for sets, maps lists and associated operations. Some fields can be associated with an enumeration type which has an encoding which is for example
MultipleValueChar. The type of this field is then implicitly a set type. For example, if a message requires the field
ExecInst to be present we can write
this means that the values acceptable for the
ExecInst field in the incoming message must be a strict subset of the stated values.
Set types can either be introduced via set encodings or by declaring an alias type which is a set, for example
This is also true of list and map types. In order to declare a map or list type it is necessary on fields to declare an alias. For example
Action and Message Types
It is possible in actions and internal declarations to define elements with type referring to actions or messages. For example if it was needed to keep a track of all of the fills for a particular order one could write:
Equally if an outbound message is to be sent and initiated by an action it is possible to write for example: