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 state
, message
and 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.
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 specialExecutionType
.
Importing messages
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 req
, opt
or ign
:
This means that the field Price
and 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 Parties
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 ?
syntax:
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:
Default values
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.
It is important to note that 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 req
and 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.
Ignored imports
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:
Outbound messages
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 @OverrideName
directive:
In the model, this message is now referenced (for example in any custom extension or receive logic) with the overridden name, in this case OrderSingle
.
Actions
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
An action is a framework for modelling such events in a way that is compatible with the syntax already deacribed for declaring messages and state.
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.
Inline Validation
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:
where 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.
General Validation
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 ClOrdID
field.
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.
Receiving messages
One can write receive code when a NewOrderSingle
message is received for example:
Receiving Actions
It is possible also to write receive handler for actions:
Rejecting Messages
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
String based reject reasons
IPL allows the user to specify rejection behaviour for each instance. For example one can write, using an argument to indicate a string based reason for rejection:
Note that there is no way to define rejecting 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.
Structured reject reasons
It is also possible to pass a more structured argument to reject clauses, not simply of string as above giving a string representation of the failure. There exists a builtin type called rejectInfo
this has a record type
The text element of this record is the same as the text if you use the simple string argument type above.
This record allows a user a “tag” to identify a validation statement using an expression. For example from the snippet given here, we can use any consistent type to identify the validation statements:
in this case we are using an integer to identify the validation statements, but it could be any type as long as it is consistent across all validations.
In this case the ExecutionReport message sent out on rejection would be different depending on the tag written in the message. It is also possible to write such tags on inline field validations such as those shown here:
in this case in the reject block the rejInfo
populates the field
element with the name of the field - here the value would be Some "OrderQtyData.OrderQty"
.
The type of the field
in the rejectInfo
record can be any inferable type as long as it is consistent across the message in which it is used. It can, for example, be given an enumerable type - e.g. of type OrdRejectReason which can then be used to mirror rejection reasons sent out in execution reports for example.