Types

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:
"This is a string"
"USD"
  • 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.
42
2017
-1
  • Float number literal must be formatted as (whole part).(fractional part).
3.14159
12.344
-123.44
  • Boolean literals are just two standard boolean constants true and false
true
false

Within IPL there are several given compound datatypes - these are concrete types with arguments. At the time of writing, those that are supported are:

LocalMktDate(int,int,int) 
MonthYear(int,int,?week) 
UTCDateOnly(int,int,int) 
UTCTimeOnly(int,int,int,?int) 
UTCTimestamp(int,int,int,int,int,int,?int)
Duration(int,int,int,int,int,int)

The way one can declare a field of this type is simply by declaring an object such a field as

a : UTCDateOnly

for example. To attribute values and exploit the types of the arguments we can create values, as described in the Values section.

In the IPL model it is possible when defining fields to give types to objects. Fields can exist in records and messages, but their declaration is generally of the form

NAME : TYPE

where TYPE is the name of the type. For example

year   : int
time   : UTCTimeStamp
title  : string
price  : float

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

alias Price : float

With float aliases, a user may wish to explicitly state the precision:

precision (Price, 2)

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

  • UTCTimeStamp
  • UTCTimeOnly

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:

TimeStampPrecisions {
    default : milli
    TransactTime : micro   
}

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:

enum Cardsuit {
 Clubs
 Diamonds
 Hearts
 Spades
}

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:

@encoding: int
enum AccountType {
 @description: Account is carried on customer Side of Books
 CarriedCustomerSide "1"
    
 @description: Account is carried on non-Customer Side of books
 CarriedNonCustomerSide "2"
    
 @description: House Trader
 HouseTrader "3"
    
 @description: Floor Trader
 FloorTrader "4"
    
 @description: Account is carried on non-customer side of books and is cross margined
 CarriedNonCustomerSideCrossMargined "6"
    
 @description: Account is house trader and is cross margined
 HouseTraderCrossMargined "7"
    
 @description: Joint Backoffice Account (JBO)
 JointBackOfficeAccount "8"
}

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:

record date {
 year  : int
 month : int
 day   : int
}

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

record date {
 year  "y" : int
 month "m" : int
 day   "d" : int
 week  "w" :? int
}

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 component block in the fix data dictionary:

@description: The Parties component block is used to identify and convey information on the entities 
@description: both central and peripheral to the financial transaction represented by the FIX message containing 
@description: the Parties Block. The Parties block allows many different types of entites to be expressed through the
@description: use of the PartyRole field and identifies the source of the PartyID through the PartyIDSource.
    
repeating record Parties {
 @description: Number of PartyID (448), PartyIDSource (447), and PartyRole (452) entries
 @description: Repeating group below should contain unique combinations of PartyID, PartyIDSource, and PartyRole
 NoPartyIDs "453" : ? NumInGroup
    
 @description: Party identifier/code. See PartyIDSource (447) and PartyRole (452).
 @description: See "Appendix 6-G – Use of <Parties> Component Block"
 @description: Used to identify source of PartyID. Required if PartyIDSource is specified. Required if NoPartyIDs > 0.
 PartyID "448" : ? String
    
 @description: Identifies class or source of the PartyID (448) value. Required if PartyID is specified. 
 @description: Note: applicable values depend upon PartyRole (452) specified.
 @description: See "Appendix 6-G – Use of <Parties> Component Block"
 @description: Used to identify class source of PartyID value (e.g. BIC). Required if PartyID is specified. Required if NoPartyIDs > 0.
 PartyIDSource "447" : ? PartyIDSource
    
 @description: Identifies the type or role of the PartyID (448) specified.
 @description: See "Appendix 6-G – Use of <Parties> Component Block"
 @description: Identifies the type of PartyID (e.g. Executing Broker). Required if NoPartyIDs > 0.
 PartyRole "452" : ? PartyRole
    
 @description: Repeating group of Party sub-identifiers.
 PtysSubGrp : PtysSubGrp
}

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:

repeatingGroup Parties {
    req NoPartyIDs
    req PartyID
    req PtysSubGrp
}

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:

@encoding: char
enum TimeInForce {
 @description: Day (or session)
 Day "0"
 @description: Good Till Cancel (GTC)
 GoodTillCancel "1"
 @description: At the Opening (OPG)
 AtTheOpening "2"
 @description: Immediate or Cancel (IOC)
 ImmediateOrCancel "3"
 @description: Fill or Kill (FOK)
 FillOrKill "4"
 @description: Good Till Crossing (GTX)
 GoodTillCrossing "5"
 @description: Good Till Date
 GoodTillDate "6"
 @description: At the Close
 AtTheClose "7"
}

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.

extend enum TimeInForce {
 @description: Book Or Cancel
 BookOrCancel "C"
}

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

extend enum TimeInForce {
    @OverrideName "3" ExecOrCancel
}

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.

/* Original declaration that may have derived */
message CreateOrderSingle "D" = {
 @description
 
 ...
}
 
/* Extending the original message declaration above with a custom field */
extend message CreateOrderSingle {
 @description: Routing Instruction
 RoutingInst "9303" : RoutingInst
 ...
}

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:

record Instrument = {
 @description: Specifies when the contract (i.e. MBS/TBA) will settle.
 @description: Must be present for MBS/TBA
 ContractSettlMonth "667" : ? MonthYear
}
 
extend record Instrument = {
 @description: New type contract settle time
 ContractSettlWeek "10103" : ? week
}

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)

repeating record Parties {
 NoPartyIDs "453" : ? NumInGroup
 PartyID "448" : ? String
 PartyIDSource "447" : ? PartyIDSource
 PartyRole "452" : ? PartyRole
 PtysSubGrp : PtysSubGrp
}

A user may wish to ensure that the PartyID always be present. This can be achieved also within a custom extension by writing

extend record Parties {
    req PartyID
}

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

message OrderSingle {
  req ExecInst valid when subset(it,{|Held,NoCross,AllOrNone|})
}

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

alias mySetType: int set

action myAction {
  a : mySetType
}

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

alias OrdIdQtyMap : <string, Qty option> map
alias OrdIdList: string list
internal state {
  a : OrdIdQtyMap = <"a"=(Some 1.0)|default: None>;
  b : OrdIdQtyMap  = <|default: None>;
  c : OrdIdList = [];
}

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:

alias fillIndexMap : <int, Fill option> map

action Fill {
  fill_qty   : Qty
  fill_price : Price
}

internal state {
  fills : fillIndexMap = <|default:None>;
  fill_index : int     = 0;
}

receive(f:Fill){
  insert(state.fills,state.fill_index=Some f)
  state.fill_index = state.fill_index + 1
}

Equally if an outbound message is to be sent and initiated by an action it is possible to write for example:

declare message m "m" {
  a "a" : int
  b "b" : int
}

internal state {
  assignable {
    a:int
  }
}

outbound message m {
  req a 
  req b valid when it >10
  validate{this.a>this.b}
}

action trigger_m {
  msg:m
  validate{
    valid(m)
  }
}

receive(a:action_trigger_m){
  assignFrom(a.msg,state)
  send a.msg
}