Repeating Groups

We now add to our a model functionality for Repeating Groups which allow multiple copies of the same record of information to be passed in to the system. For example the Parties of FIX version 4.4 is defined as

declare repeating record Parties {
    @description: Repeating group below should contain unique combinations of PartyID, PartyIDSource, and PartyRole
    NoPartyIDs "453" :? NumInGroup
    @description: Used to identify source of PartyID. Required if PartyIDSource is specified. Required if NoPartyIDs > 0.
    PartyID "448" :? string
    @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 of PartyID (e.g. Executing Broker). Required if NoPartyIDs > 0.
    PartyRole "452" :? PartyRole
    @description: Repeating group of Party sub-identifiers.
    PtysSubGrp  : PtysSubGrp
}

The NoPartyIDs is an indicator of the number of copies of this group that exist. All repeating groups much have to have a field with type NumInGroup or type NoSides which is restricted to a maximum length of 2.

In this repeating group, the field PtysSubGrp}is also a repeating group, meaning there are multiple copies of this group within each member of the Parties repeating group.

Customising Repeating Groups

With repeating groups, as with non-repeating records and messages, it is possible to add custom fields. Also it is possible to override a defined optionality of an existing repeating group to ensure certain elements be present within each copy of the group.

Add custom fields

As shown in the section on custom fields and cases it is possible to add a custom field to a repeating group. We do so here, by adding a new field of type int called PartyIndex:

extend record Parties {
    PartyIndex "pi": int
}

It is important to note that any modification to a repeating group is true for all instances of that repeating group within the model. This means any field which is of type Parties in the model will have the same internal fields and optionality.

Changing field optionality

It is possible to change the optionality of a field with a repeating group. For example we could impose a condition that the PartyID field be required in all copies of the Parties field of message NewOrderSingle. We do this as follows:

extend record Parties {
    req PartyID
    PartyIndex "pi": int
}

Incorporating Repeating Groups

In order to incorporate repeating groups we first add a field to the NewOrderSingle message as follows:

message NewOrderSingle {
    req ClOrdID
    req Side
    req TransactTime
    req OrdType valid when it in [ OrdType.Limit, OrdType.Market, StopSpread ]
    req OrderQtyData.OrderQty
    opt SpreadProportion valid when case(it){None:true}{Some x: x>0.0 && x<=1.0}
    opt Price 
    ign Account
    req Parties
}

We can add a validation statement to this field - for example:

req Parties valid when it.PartyIndex > 0 && it.PartyIndex < 100
            valid when it.PartyID != ""

which ensures that the new field PartyIndex is within a certain range, and that the PartyID field is not the empty string.

We can also add the Parties field to ExecutionReport:

outbound message ExecutionReport {
    req OrderID
    req ExecID
    req ExecType
    req OrdStatus
    req Side
    req OrderQtyData.OrderQty
    req LeavesQty
    req CumQty
    opt Text
    req Parties
}

More complex validation statements

It is possible to write more complex validation statements about repeating group elements. For example we could write the following:

 validate {
        case(this.Parties.PtysSubGrp.PartySubID)
            {None:true}
            {Some x : this.Parties.PartyID != x}
    }

which states that for each copy of the repeating group Parties, the PartyID field should not be the same as any of the PartySubID fields of the contained sub-repeating group PtysSubGrp if they are present.

Modification of State

We can modify state and introduce repeating groups in the same as for any other field. For example by adding to the assignable section:

internal state {
    assignable{
        Side:Side = Side.Buy;
        Price:?Price
        OrderQtyData.OrderQty : float = 0.0;
        OrdStatus:OrdStatus = OrdStatus.New;
        OrdType:OrdType = OrdType.Market;
        LeavesQty:Qty = 0.0;
        CumQty:Qty = 0.0;
        SpreadProportion:?float
        Parties:Parties
    }
    live_order : bool = false;
    AvgPx : float = 0.0;
    bestBid: Price = 0.0;
    bestAsk: Price = 0.0;
}

Now the Parties element of the state will be automatically assigned to from the incoming message, and can be assigned to the Parties field of the outgoing ExecutionReport of a receive or reject block.

Errors and Warnings

Tag consistency errors apply to the fields of repeating groups as they do for any other field. Further to this it is only possible to assign entire repeating groups e.g.

state.Parties = msg.Parties

It is not possible to assign to a particular field. Particular fields can only be referenced in boolean expressions such asd

if (state.Parties.PartyID == "acct") then return

which implicitly checks that all PartyID fields are not equal to "acct".

If you try to write, for example

state.Parties.PartyID = "acct"

you will receive the error message

Repeating Group fields can only be referenced in a boolean expression.

Full Model – Imandra Analysis

Click on the image below for an interactive analysis of this model performed by imandra.

Full Model - IPL Code

import FIX_4_4
@title: "Repeating Groups Tutorial Example"

scenario NewOrderFill {
	name "NewOrderFill"
	description "Initialise Book State, send NewOrderSingle Message then receive a fill action"
	events[bookState, NewOrderSingle, fill]
}

internal state {
    assignable{
        Side:Side
        Price:?Price
        OrderQtyData.OrderQty : Qty
        OrdStatus:OrdStatus = OrdStatus.New;
        OrdType:OrdType 
        LeavesQty:Qty 
        CumQty:Qty 
        SpreadProportion:?float
        Parties:Parties
    }
    live_order : bool = false;
    AvgPx : float 
    bestBid: Price 
    bestAsk: Price 
}

extend enum OrdType {
    StopSpread "s"
}

extend message NewOrderSingle {
    SpreadProportion "sp" :? float
}

extend record Parties {
    req PartyID
    PartyIndex "pi" : int
}



message NewOrderSingle {
    req ClOrdID
    req Side
    req TransactTime
    req OrdType valid when it in [ OrdType.Limit, OrdType.Market, StopSpread ]
    req OrderQtyData.OrderQty
    opt SpreadProportion valid when case(it){None:true}{Some x: x>0.0 &&  x<=1.0}
    opt Price 
    ign Account
    req Parties valid when it.PartyIndex > 0 && it.PartyIndex < 100
                valid when it.PartyID != ""
     validate {
         (this.OrdType == OrdType.Market <==> !present(this.Price)) &&
         (this.OrdType == OrdType.Limit ==> present(this.Price)) &&
         (this.OrdType == OrdType.StopSpread ==> present(this.Price))
    }

    validate {
        this.OrdType == StopSpread <==>
            present(this.SpreadProportion)
    }

    validate {
         this.OrdType != OrdType.Market ==>
                (case this.Price
                    {Some price: price > 0.0}
                    {None: false}
                 )
    }

    validate {
        case(this.Parties.PtysSubGrp.PartySubID)
            {None:true}
            {Some x : this.Parties.PartyID != x}
    }

}

outbound message ExecutionReport {
    req OrderID
    req ExecID
    req ExecType
    req OrdStatus
    req Side
    req OrderQtyData.OrderQty
    req LeavesQty
    req CumQty
    opt Text
    req Parties
}

action fill {
    fill_price : Price
    fill_qty : Qty

    validate {state.OrdStatus != OrdStatus.PendingNew}
    validate { this.fill_qty > 0.0 }
    validate { this.fill_qty <= state.LeavesQty}
    validate { this.fill_price > 0.0 }
    validate {
        (state.OrdType != OrdType.Market) ==>
        ( case state.Price
              { Some p:
                    if ( state.Side == Side.Buy ) then
                        ( this.fill_price <= p )
                    else ( this.fill_price >= p )
              }
              { None: true }
        )
    }
}

action bookState {
    bestBid : Price
    bestAsk : Price
    
     validate{
        this.bestAsk > this.bestBid &&
        this.bestBid > 0.0 &&
        this.bestAsk > 0.0
    }
}


receive (f:fill) {
    state.LeavesQty = state.LeavesQty - f.fill_qty
    state.AvgPx = ( state.AvgPx * state.CumQty + f.fill_qty * f.fill_price ) / ( f.fill_qty + state.CumQty )
    state.CumQty = state.CumQty + f.fill_qty

    if state.LeavesQty == 0.0 then
        state.OrdStatus = OrdStatus.Filled
    else
        state.OrdStatus = OrdStatus.PartiallyFilled

    send ExecutionReport { state with
        OrderID = "";
        ExecID = "";
        ExecType = ExecType.New;
    }
}

receive (ba:bookState){
    state.bestBid = ba.bestBid
    state.bestAsk = ba.bestAsk

    let spread = (state.bestAsk - state.bestBid)/state.bestAsk
    if 
    (case(state.SpreadProportion){None:false}{Some x: x >= spread}) &&
        state.OrdStatus == OrdStatus.PendingNew
        then state.OrdStatus = OrdStatus.New
}

receive (msg:NewOrderSingle) {
    state.live_order = true
    state.LeavesQty = msg.OrderQtyData.OrderQty
    assignFrom(msg,state)

    if msg.OrdType == StopSpread
    then case(msg.SpreadProportion){Some x:
        if state.bestAsk != 0.0 then
        if x >= (state.bestAsk - state.bestBid)/state.bestAsk
            then state.OrdStatus = OrdStatus.New 
            else state.OrdStatus = OrdStatus.PendingNew
        else state.OrdStatus = OrdStatus.PendingNew
    }

     send ExecutionReport {state with 
        OrderID = msg.ClOrdID;
        ExecID = "";
        ExecType = ExecType.New;
    }
}

reject (msg:NewOrderSingle, text:string){
    missingfield:{
        send ExecutionReport {state with
            OrderID = "";
            ExecID = "";
            ExecType = ExecType.New;
            Text = Some text;
        }
    }
    invalidfield:{
         send ExecutionReport {state with
            OrderID = msg.ClOrdID;
            ExecID = "";
            ExecType = ExecType.New;
            Text = Some text;
        }
    }
    invalid:{
    }
}

Generated Documentation

Click here to view the generated documentation for this model.