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
declare message NewOrderSingle "D" {
@description: Unique customer defined order request identifier
ClOrdID "11" : String
...
@description: Execution and trading restriction parameters supported by Xetra
TimeInForce "59" : ? TimeInForce
...
}
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
extend message NewOrderSingle {
specialExecutionType "2001" : int
}
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
:
message NewOrderSingle {
req Price
req Side
req OrderQtyData.OrderQty
opt OrderQtyData.RoundingDirection
req specialExecutionType
}
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
message NewOrderSingle {
req Price
req Side
req OrdType
req OrderQtyData.OrderQty
opt OrderQtyData.RoundingDirection
req specialExecutionType
req 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:
declare message NewOrderSingle "D" {
ClOrdID "11" : String // required in the FIX specs
Side "54" : Side // required in the FIX specs
MinQty "110" : ? Qty // optional in FIX specs
}
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:
message NewOrderSingle {
require Side
require MinQty // this field is required in our model
}
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:
message NewOrderSingle {
require Side
optional MinQty // we state that we want to use this field as optional
}
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:
message NewOrderSingle {
require Side
optional MinQty default = 0.0
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:
message NewOrderSingle {
require Side
ignore MinQty // this field is now ignored if it exists
}
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:
outbound message ExecutionReport {
req CumQty
req LeavesQty
opt ExecType
}
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:
message NewOrderSingle {
@OverrideName OrderSingle
req ...
}
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:
action ACTIONNAME {
// Field type definitions
...
...
// Validation logic
...
}
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.
action time_change {
newtime : UTCTimeStamp
}
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:
action fill {
fillQty : Qty;
fillPrice : Price;
fillTime : UTCTimeStamp;
}
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:
req Side valid when it in [Side.Buy, Side.Sell]
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:
opt Side valid when it in [Side.Buy, Side.Sell]
For a record field, for example from the OrderQtyData
record, we can write for example
req OrderQtyData.OrderQty valid when it > 0.0
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
req Parties valid with it.PartyID != ""
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:
action fill {
fillQty : Qty;
fillPrice : Price;
fillTime : UTCTimeStamp;
validate { this.fillQty > 0.0 and this.fillQty < state.LeavesQty }
validate { this.fillPrice > 0.0 and this.fillPrice < }
validate { this.fillTime > state.currTime and this.fillTime < state.settings.ClosingTime }
}
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
validate { fillQty > 0.0 and fillQty < state.LeavesQty }
but it is advised to use this qualification for clarity.
It is also possible to write such validation for messages. For example:
message NewOrderSingle {
req Price
opt ClOrdID
req OrdType
req Side
req OrderQtyData.OrderQty
opt OrderQtyData.RoundingDirection
req specialExecutionType
req Parties
validate {present(this.Price) <==> !this.OrdType == OrdType.Market }
validate {this.Parties.PartyID !== this.ClOrdID}
}
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:
receive (msg : NewOrderSingle) {
assignFrom (msg, state)
state.LeavesQty = msg.OrderQtyData.OrderQty
send ExecutionReport {
state with
CumQty = 0.0;
}
}
Receiving Actions
It is possible also to write receive handler for actions:
receive ( act : fill ) {
state.LeavesQty = state.LeavesQty - f.fillQty
state.CumQty = state.CumQty + f.fillQty
state.AvgPx = ( state.AvgPx * state.CumQty + f.fillQty * f.fillPrice ) / ( f.fillQty + state.CumQty )
send ExecutionReport state
}
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:
reject ( msg : NewOrderSingle, rejectReason : string ) {
// A field is missing
missingfield : {
send ExecutionReport {
state with
ExecID = "";
ExecType = Some ExecType.Rejected;
}
}
// an inline validation statement is false
invalidfield : {
send ExecutionReport {
state with
ExecID = "";
ExecType = Some ExecType.Rejected;
}
}
// One or more 'validate' statements is invalid
invalid : {
send ExecutionReport {
state with
ExecID = "";
ExecType = Some ExecType.Rejected;
}
}
}
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
{
text : string
tag : _
field : string option
}
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:
validate 0 {present(this.Price) <==> !this.OrdType == OrdType.Market }
validate 1 {this.Parties.PartyID !== this.ClOrdID}
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.
reject ( msg : NewOrderSingle, rejInfo : rejectInfo ) {
// A field is missing
missingfield : {
send ExecutionReport {
state with
ExecID = "";
ExecType = Some ExecType.Rejected;
}
}
// any validation statement is false
invalidfield, invalid : {
if rejInfo.tag == 0 then
send ExecutionReport {
state with
ExecID = "case1";
ExecType = Some ExecType.Rejected;
}
else
send ExecutionReport {
state with
ExecID = "case2";
ExecType = Some ExecType.Rejected;
}
}
}
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:
req OrderQtyData.OrderQty valid 2 when it > 0.0
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.