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 cause 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.

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 message:

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 ezample 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 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 = Some 0.0

Default values can only be concrete values and cannot be the literal None. 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 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 am 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.

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]

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:

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.

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) <==> !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.

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
}

When messages are processed, there are three reasons why they might be invalid:

  • Missing Reqiured 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:

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 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.