Messages and Actions
We have already described how a model ‘state’ is defined and initialised. We now turn to describing how the state will transition in response to external (e.g. FIX message) messages or internal events (e.g. a venue generating a fill for an open order). Our two primary elements are:
- Messages - messages received and sent from/to outside clients.
- Actions - events within the system which may cause the state to change and potentially send out message(s).
The combination of
action constructs within IPL allows us to describe behavior of infinitely many possible combinations of paths through the model.
For messages and actions we show in this section three fundamental elements of IPL syntax and functionality:
- Declaration of the message or action
- Validation statements about the message or action
- Logic handlers for receiving messages or actions, and potentially for rejecting messages.
A message declaration is almost exactly similar to the record declaration – the only difference is that one must specify the message code after the message name.
Here is an example code snippet from the FIX data dictionary, declaring a NewOrderSingle
Here, “11” is the field tag, “ClOrdID” is the name of the field and “String” is the type of the field. The string “Unique Customer defined order request identifier” will be used for documentation. The “TimeInForce” is a different field of “TimeInForce” type, and “?” means that it is declared as an optional field.
Further to the imported message declaration it is possible to extend their definition using custom extensions analogously to the way in which one can define a custom extension for enumeration types and records. For example in this case a user can write
this means that a new field has been declared, with tag “2001” and type int, which is called
The purpose of IPL is to encode interactions of the modeled venue via FIX messages. The user can state that he is going to use some of the messages using the message block. Note that the imported message must be declared first (see above) – this can be done as an entirely new message declaration or we can use a pre-existing definition from the libraries.
The precise fields which a message will import are then chosen from the declaration and declared as
This means that the field
Side are imported non-optionally (required). Further to these, the custom field
specialExecutionType is required, and two fields from within the
OrderQtyData record are imported:
OrderQty is required, but
RoundingDirection is optional.
For repeating groups the repeating group is imported via a message, in contrast to the above example where specific fields from a record. For example we could import the repeating group
Optional types and require/optional importing of fields
Some message fields are documented as required in the FIX specification – other fields are documented as optional. We encode this information in FIX data dictionaries with
If one requires a field that is declared optional ( with
? ) in the FIX data dictionary – that means that in the current model the field is required, even though in general FIX specs it is optional:
If a field is optional in the FIX specs and we want to keep it optional in the model, then we import it with the optional keyword:
For optional fields it is possible to define a default value should the field not be present in the incoming message. For example we could write:
Default values cannot refer to the literal
it but can refer to other incoming fields or state values, or call a function to calculate a value. A defaulted optional field in the model is considered as required for the rest of the model as it is guaranteed to have a value. If a field is imported both as
opt and used in the state then it is an ambiguous value as defined in the Assignable section, and then the optional import (such as above) must have a default value.
It is possible to declare a message field as “ignored” - this means that it may exist within the incoming FIX message, but one cannot state either inline validation or general validation logic for them or reference them elsewhere in the model. These fields can be declared using the following syntax:
Imports of messages which are not inbound but are sent out in the receive logic are specified as outbound messges. For example we can define:
Overriding message names
It is possible in IPL, for example for the purposes of documentation, to give an alternative name to an existing imported message. For example, we may want to call the
NewOrderSingle message simple
OrderSingle as it appears, for example, in the FIX 4.2 dictionary. This can be done by using an
In the model, this message is now referenced (for example in any custom extension or receive logic) with the overridden name, in this case
Actions represent asynchronous ways in how the rest of the system may interact with the model state.
For example, consider the following:
- A customer sends a NewOrderSingle message to Buy 100 shares with a limit price of $55.0
- The model successfully accepts it and responds by sending back ExecutionReport message The syntax for declaring actions is similar to importing messages:
An action is an abstraction of an internal event within the modelled system that may affect the model state. The following are typical examples of an action:
- Time changes leading to an order being expired
- A fill within the system affecting the order’s executed quantity and price
- An internal operator halting the venue
- A primary market halt due to some error causing this instrument to be halted
- Order book changes leading the model to publish market data
Defining and handling time changes
The simplest action describes a time change for the model.
Note that syntax is very similar to that of declaring messages.
Defining and handling fills
A simple fill action has a quantity, price and a timestamp:
Typically, one would add more information to the actions, but in this case we use just these three fields.
When importing messages, it is possible to write expressions which determine the validity of the imported fields. Such validation is not applicable to ignored fields, but applies to both required and optional fields.
A typical example is to ensure that values exist within a certain list of possible values:
for example where here
it refers to the field itself. Note that in the special case of the
in operator, this is possible even if the field is optional:
For a record field, for example from the
OrderQtyData record, we can write for example
which in this case ensures that
OrderQty is positive and non-zero.
For repeating groups one can make validation statements of fields within the group and these validation statements apply to the field in every copy of the repeating group. For example we could write
which states that for every copy of the
Parties repeating group, the field
PartyID must not be the empty string.
It is important to note that inline validation applies to messages only (not actions), and that any validation applied in outbound messages is not processed when executing the model.
In addition to inline validation, one can write general validation statements for both messages and actions. For example in the case of the fill action defined above, we could write:
which places validation constraints on the fields of the action, ensuring that any incoming action is functionally valid. The
this expression refers to the action itself. It is possible in cases where there are no name clashes to remove
this and simply write, for example
but it is advised to use this qualification for clarity.
It is also possible to write such validation for messages. For example:
here the validation ensures that a price is only present for non-market orders, and also that for every copy of the
Parties group, the
PartyID field is not equal to the
Receiving Actions and Messages
In order to describe the functionality of a venue or exchange, IPL provides a mechanism for specifying logic when actions and messages are received. We already described the syntax available in IPL for writing statements in code blocks. Here we describe the situations where this is possible when receiving messages and actions. This is the code which is executed if a message or action is well formed - i.e. does not break any validations. We give two very simple examples of receive handlers here.
One can write receive code when a
NewOrderSingle message is received for example:
It is possible also to write receive handler for actions:
When messages are processed, there are three reasons why they might be invalid:
- Missing Required Field
- Inline field validation statement failed
- General validation statement failed
IPL allows the user to specify rejection behaviour for each instance. For example one can write:
Note that there is no way to define a way to reject an action like there is with messages. This is because actions represent constrained inputs and are sent internally - one does not need to specify a mechanism to inform the creator of an action why it failed to be processed.