Introducing State

We now extend our basic model with the concept of a state. The way to conceptualize the model as a whole is a state transition system. This means that for any event (see Messages and Actions) the state can be updated. Remember that the state here represents information pertaining to one order. There is no current requirement to provide initial values in the state, but it should be noted that those fields which have no explicit initial value are provided with a generated value. It is recommended that values with no initial value are either those which are initialised via an incoming message, or are initialised via an action as discussed for example in the Adding Actions section.

State initialisation

Let us first construct a state in our basic model (here we use the canonical name state but there can be many such global states with any name):

internal state {
    Side : Side
    Price :? Price
    live_order : bool = false;
}

Here we have introduced two fields: side which is of type Side, which is not optional, and price of type Price which is optional. It is important to note that initial values on fields are not required and if are not present, a candidate is generated upon analysis. In this example, live_order has been given an initial value of false, where are Side and Price don’t have initial values specified.

Assignment to and from state

In the basic model we can now use the declared state variable as a way of storing information. For example in the message receive block we can write

receive (msg:NewOrderSingle){
    state.live_order = true
    state.Side = msg.Side
    state.Price = msg.Price

     send ExecutionReport {
        OrderID = fresh();
        ExecID = fresh();
        ExecType = ExecType.New;
        OrdStatus = OrdStatus.New;
        Side = state.Side;
        LeavesQty = 0.0;
        CumQty = 0.0;
        OrderQtyData.OrderQty = msg.OrderQtyData.OrderQty;
    }
}

Note here how state has been updated with values from the incoming message, and state can be used to populated values of the outbound ExecutionReport.

Automatic assignment using state

It is possible to define a special subset of state variables which can be used to automatically update to and from the state. In order to do this we create an assignable section which contains fields which have been imported via messages. For example we can create the following state:

internal state {
    assignable{
        Side                  :  Side
        Price                 :? Price
        OrderQtyData.OrderQty :  Qty
        OrdType               :  OrdType
        OrdStatus             :  OrdStatus = OrdStatus.PendingNew;
        ExecType              :  ExecType  = ExecType.PendingNew;
        LeavesQty             :  Qty
        CumQty                :  Qty
        OrderID               :  string
    }
    live_order : bool = false;
    AvgPx : float
    bestBid : float
    bestAsk : float
}

where we declare fields common to both outgoing and incoming messages in the assignable section of the state. It is now possible to exploit this when processing messages. For example we could rewrite the message receive to be:

receive (msg:NewOrderSingle){
    state.live_order = true
    state.LeavesQty = msg.OrderQtyData.OrderQty
    state.OrderID = fresh()
    state.OrdStatus = OrdStatus.New
    assignFrom(msg,state)
    send ExecutionReport {
        state with
        ExecID = fresh();
        ExecType = ExecType.New;
    }
}

Note here using this syntax the fields declared in the assignable section of state are automatically updated using the assignFrom directive. In the send command, the insertion of the {state with syntax uses the fields from the assignable section of state to automatically set the fields of ExecutionReport. In this case the fields Side and OrderQtyData.OrderQty are set automatically.

Errors and Warnings

As before it is possible to encounter errors and warnings in this model. Those that are most pertinent here to the creation of an internal state are

  • Assignable field errors

The only fields that can exist in the assignable section of state are fields that have been imported in a message. One cannot place an arbitrary field such as live_order in the assignable section and the following error will appear if it is placed there:

Couldn't resolve reference to MessageRequire 'live_order'
  • Assignable field optionality errors

If the optionality of an assignable field does not match the corresponding message field, then an error will occur. For example if the assignable field

 Price:Price = 0.0;

Then the following error will be given:

This field should be defined as optional.

It is possible to deal with the situation where a field is imported both non-optionally and optionally in messages. This is discussed in the advanced section Ambiguous optionality

Full Model – Imandra Analysis

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

Full Model - IPL Code

//
//  Imandra Inc.
//  Copyright (c) 2024
//
//  Code for 'Tutorial State Model'
//   
//  For further info see https://docs.imandra.ai
// 
// 
//

import FIX_4_4

@title: "Tutorial State Model"

messageFlows {
  NewOrderOnly {
    name "NewOrderOnly"
    description "Send NewOrderSingle Message"
    template[NewOrderSingle]
  }
}

internal state {
  assignable{
    Side                  :  Side
    Price                 :? Price
    OrderQtyData.OrderQty :  Qty
    OrdType               :  OrdType
    OrdStatus             :  OrdStatus = OrdStatus.PendingNew;
    ExecType              :  ExecType  = ExecType.PendingNew;
    LeavesQty             :  Qty
    CumQty                :  Qty
    OrderID               :  string
  }
  live_order : bool = false;
  AvgPx : float
  bestBid : float
  bestAsk : float
}

message NewOrderSingle {
  req ClOrdID
  req Side
  req TransactTime
  req OrdType valid when it in [ OrdType.Limit, OrdType.Market ]
  req OrderQtyData.OrderQty
  opt Price
  ign Account
  validate {
    (this.OrdType == OrdType.Market <==> !present(this.Price)) &&
         (this.OrdType == OrdType.Limit ==> present(this.Price))
  }
  validate {
    this.OrdType == OrdType.Limit ==>
                (case this.Price
                    {Some price: price > 0.0}
                    {None: false}
                 )
  }
}

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

receive (msg:NewOrderSingle){
  state.live_order = true
  state.LeavesQty = msg.OrderQtyData.OrderQty
  state.OrderID = fresh()
  state.OrdStatus = OrdStatus.New
  assignFrom(msg,state)
  send ExecutionReport {
    state with
    ExecID = fresh();
    ExecType = ExecType.New;
  }
}

reject (msg:NewOrderSingle, text:string)
{
  missingfield:{
    send ExecutionReport {
      state with
      ExecID = fresh();
      Text = Some text;
    }
  }
    invalidfield, invalid:{
        state.ExecType = ExecType.Rejected
        state.OrdStatus = OrdStatus.Rejected
        send ExecutionReport {
            state with
            ExecID = fresh();
            Text = Some text;
        }
    }
}

Generated Documentation

Click here to view the generated documentation for this model.