OnixS C++ FIX Engine  4.12.0
API Documentation
Flat FIX Message

Although OnixS::FIX::Message class is designed for high-performance duty, each concept has its bounds and bottlenecks. The serialization/deserialization operations, that are done by the OnixS::FIX::Session each time when an instance of OnixS::FIX::Message is sent/received to/from counterparty, is quite a heavy-weight operation, thus it has negative influence on a general performance.

To satisfy the needs of ultra-low latency trading, OnixS FIX Engine offers a new concept of an editable flat FIX message, exposed as the OnixS::FIX::FlatMessage class. This class provides functionality that is similar to the OnixS::FIX::Message. However, being pre-serialized, it eliminates the necessity of converting between structured memory presentation and a 'tag=value' form when the message is actually sent or received.

Attention
All outbound session callbacks are called for the regular OnixS::FIX::Message class objects only. Therefore, when OnixS::FIX::FlatMessage object is sent by the OnixS::FIX::Session::send method, then outbound session callbacks are not called.

Constructing Flat FIX Message

OnixS::FIX::FlatMessage instance can be constructed either from a 'tag=value' form or a regular OnixS::FIX::Message class instance. The following code shows the creation of a flat order (MsgType=D) from a 'tag=value' form:

const std::string RawOrder(
"8=FIX.4.2\0019=00\00135=D\00149=Sender\00156=Target\001"
"34=0\00152=99990909-17:17:17.777\00154=1\00155=A001\001"
"11=BUY000000001\00138=1000\00140=2\00144=1001.000\001"
"59=3\001117=A001\00146=A001\00110=000\001");
// Constructs the flat message from the given 'tag=value' form
FlatMessage order(RawOrder.data(), RawOrder.size());

Also, OnixS::FIX::FlatMessage instance can be constructed from a 'tag=value' form without session-level fields, in this case, the required session-level fields will be added during the construction:

const std::string RawOrderWithoutSessionDetails(
"54=1\00155=A001\00111=BUY000000001\00138=1000\00140=2\00144=1001.000\00159=3\001117=A001\00146=A001\001");
// Constructs the flat message from a 'tag=value' form without session-level fields
FlatMessage serializedOrder(ProtocolVersion::FIX_42, "D", "Sender", "Target", RawOrderWithoutSessionDetails.data(), RawOrderWithoutSessionDetails.size());

The following code shows the creation of a flat order (MsgType=D) from the previously filled OnixS::FIX::Message instance:

// Order as a FIX Message object.
Message order(FIX42::Values::MsgType::Order_Single, ProtocolVersion::FIX_42);
// Fills the order in a regular way.
order.set(FIX42::Tags::ClOrdID, "ClientOrderID")
.set(FIX42::Tags::OrderQty, 1000);
// ...
// Constructs a FlatMessage from the given Message instance.
// Since this moment the source Message instance is not needed any more.
FlatMessage serializedOrder(order);

Also, the OnixS::FIX::FlatMessage instance can be constructed as a blank instance or with required message header fields only. In this case, one can add all additional fields by OnixS::FIX::FlatMessage::add methods:

// Create the blank message.
FlatMessage blankMsg;
// You can add all additional fields by `FlatMessage::add` methods.
// ...
// Create a message with empty required message header fields.
FlatMessage msg(ProtocolVersion::FIX_42, FIX42::Values::MsgType::Order_Single);
// You can add all additional fields by `FlatMessage::add` methods.
// ...
Attention
If the OnixS::FIX::FlatMessage instance is constructed as a blank instance or with required message header fields only, you cannot use the zero-copy feature with this message instance. The zero-copy feature can be used only when the OnixS::FIX::FlatMessage instance is created from the fully defined 'tag=value' form or OnixS::FIX::Message instance.

Accessing FIX Fields

Once the instance of OnixS::FIX::FlatMessage class is constructed, its fields can be manipulated in a similar way, as in the case of OnixS::FIX::Message.

OnixS::FIX::FlatMessage class interface exposes two ways of accessing fields: using temporary references and using permanent keys. Temporary references are represented by OnixS::FIX::FlatFieldRef class and permanent keys are of the OnixS::FIX::FlatFieldKey type.

Whatever way is used to access a field, a temporary reference to the field must be obtained first. The OnixS::FIX::FlatMessage class exposes the OnixS::FIX::FlatMessage::find method for that purposes. If the lookup succeeds, it returns a valid reference instance for further use:

FlatFieldRef clOrdIdRef = flatOrder.find(FIX42::Tags::ClOrdID);
// Make sure that the found instance refers to a field.
if (!clOrdIdRef)
{
throw std::domain_error("Cannot find the ClOrdID field in the given Order.");
}

Once a reference is obtained, it can be used to update the field value:

flatOrder.set(clOrdIdRef, StringRef("NewClientOrderID"));
Attention
An important aspect of a reference is that it remains valid only when a manipulating value of the field reference was obtained. If another field is updated the reference may become invalid. References also invalidate when the instance of OnixS::FIX::FlatMessage class is sent to counterparty via OnixS::FIX::Session::send. For this reason, do not use multiple references to the same type.

For a continuous and non-destructive modification of a message field value, the concept of a flat field key is exposed. In contrast to a temporary field references, keys remain constant for the entire life of the particular OnixS::FIX::FlatMessage instance. Changing field values does not invalidate keys and, thus multiple fields can be updated without affecting keys. Keys are unique in bounds of a single flat message.

The following code demonstrates how a key can be obtained from a temporary reference:

FlatFieldRef clOrdIdRef = flatOrder.find(FIX42::Tags::ClOrdID);
assert(static_cast<bool>(clOrdIdRef));
const FlatFieldKey clOrdIdKey = flatOrder.allocateKey(clOrdIdRef);

Once a key is obtained, the field value can be accessed and modified, using the same way as using temporary references:

flatOrder.set(clOrdIdKey, StringRef("YetAnotherCliendOrderID"));
Attention
Keys are similar to the field tags with the only difference. Keys are constant in bounds of a single instance of the OnixS::FIX::FlatMessage class. However, keys may differ for the same field for different instances of the OnixS::FIX::FlatMessage class. Also, tags are statically defined, whereas keys are dynamically allocated by each particular instance of the OnixS::FIX::FlatMessage class.

Iterating over Fields of FlatMessage

To iterate over all fields of a OnixS::FIX::FlatMessage you can use the OnixS::FIX::FlatMessage::ConstIterator class:

class FlatFieldOutputFunctor
{
public:
void operator()(const FlatField & field)
{
std::clog << field.tag() << "=" << field.value() << std::endl;
}
};
void iterateFlatMessageFields()
{
Message order(FIX42::Values::MsgType::Order_Single, ProtocolVersion::FIX_42);
FlatMessage flatOrder(order);
//Iterates over all fields of the FlatMessage.
std::for_each(flatOrder.begin(), flatOrder.end(), FlatFieldOutputFunctor());
}

Accessing FIX groups

To simplify access to FIX groups, the OnixS::FIX::FlatGroup class is implemented. One can use the OnixS::FIX::FlatMessage::getGroup() method to get the OnixS::FIX::FlatGroup instance. After that, one can use the OnixS::FIX::FlatGroup::ConstIterator class to iterate over all group instances in the repeating group. Each group instance iterator returns the OnixS::FIX::FlatGroupInstance object, which one can use to get access to group instance fields. To access a nested repeating group, one can use the OnixS::FIX::FlatGroupInstance::getGroup() method. The following code demonstrates the access to fields of FIX group instances by iterators:

class FlatGroupOutputFunctor
{
size_t partyIdCount_;
public:
FlatGroupOutputFunctor() :partyIdCount_(0) {}
void operator()(const FlatGroupInstance & instance)
{
FlatFieldRef ref = instance.find(FIX44::Tags::PartyID);
if(ref)
std::clog << "Entry #" << partyIdCount_++ << " has PartyID=" << instance[ref] << std::endl;
}
};
void iterateFlatGroupEntries()
{
Message massQuoteMsg(FIX42::Values::MsgType::Mass_Quote, ProtocolVersion::FIX_42);
// Constructs the flat message from the structured instance which has the repeating group.
FlatMessage massQuoteFlatMsg(massQuoteMsg);
// Find the NoPartyIDs group field reference.
FlatFieldRef noPartyIDsRef = massQuoteFlatMsg.find(FIX44::Tags::NoPartyIDs);
if(noPartyIDsRef)
{
// Gets the NoPartyIDs repeating group.
FlatGroup noPartyIDsGroup = massQuoteFlatMsg.getGroup(noPartyIDsRef);
std::clog << "Found " << noPartyIDsGroup.size() << " party identifiers:" << std::endl;
//Iterates over all group instances of the NoPartyIDs group.
std::for_each(noPartyIDsGroup.begin(), noPartyIDsGroup.end(), FlatGroupOutputFunctor());
}
}

Also, the OnixS::FIX::FlatMessage class provides an additional ability to access fields with the same tag from different entries of a flat repeating group. It can be performed with the overloaded OnixS::FIX::FlatMessage::find(OnixS::FIX::Tag, const OnixS::FIX::FlatFieldRef&) const method. The second parameter indicates the OnixS::FIX::FlatFieldRef value after which the search will be performed. Accordingly, you can find all field values with the same tag and allocate keys for further access. The following code demonstrates the access to fields with the same tag:

// Constructs the flat message from the structured instance which has the repeating group.
FlatMessage massQuoteFlatMsg(massQuoteMsg);
std::vector<FlatFieldKey> partyIdFieldKeys;
FlatFieldRef partyIdFieldRef;
//Finds all PartyID fields
while ((partyIdFieldRef = massQuoteFlatMsg.find(FIX44::Tags::PartyID, partyIdFieldRef)))
{
//Allocates and saves the key for the found PartyID field
partyIdFieldKeys.push_back(massQuoteFlatMsg.allocateKey(partyIdFieldRef));
}
std::clog << "Found " << partyIdFieldKeys.size() << " party identifiers:" << std::endl;
for (size_t partyIdCount = 0; partyIdCount < partyIdFieldKeys.size(); ++partyIdCount)
{
std::clog << "Entry #" << partyIdCount << " has PartyID=" << massQuoteFlatMsg[partyIdFieldKeys[partyIdCount]] << std::endl;
}

Adding Field

To add a field, you can use OnixS::FIX::FlatMessage::add methods. These methods add the given tag/value pair at the end of the message instance:

message.add(FIX42::Tags::ClOrdID, "ClOrdID value");

Inserting Field

To insert a field, you can use OnixS::FIX::FlatMessage::insert methods. These methods insert the given tag/value pair to the message instance. The position is specified by the posTag parameter. The OnixS::FIX::InsertMode::Enum is used to specify where the new field will be inserted - before or after the position tag:

message.insert(FIX42::Tags::PossDupFlag, "Y", FIX42::Tags::MsgSeqNum, InsertMode::After);

Removing Field

To remove a field, use the OnixS::FIX::FlatMessage::remove(OnixS::FIX::Tag) method.

Example

// Order as a Message object.
Message order(FIX42::Values::MsgType::Order_Single, ProtocolVersion::FIX_42);
// Fills the order in a regular way.
order.set(FIX42::Tags::ClOrdID, "ClientOrderID")
.set(FIX42::Tags::OrderQty, 1000);
// ...
// Constructs a flat message from the structured instance.
// Since this moment, the structured instance is not used any more.
FlatMessage flatOrder(order);
FlatFieldRef clOrdIdRef = flatOrder.find(FIX42::Tags::ClOrdID);
FlatFieldRef qtyRef = flatOrder.find(FIX42::Tags::OrderQty);
// Ensures whether both fields are found.
if (!clOrdIdRef || !qtyRef)
{
throw std::domain_error("Cannot find the required fields in the Order.");
}
// Once a reference is obtained, it's time to allocate the field key
// to have fast access. The key remains constant for the message
// life-time. However, it refers to a particular instance ONLY.
const FlatFieldKey clOrdIdKey = flatOrder.allocateKey(clOrdIdRef);
const FlatFieldKey qtyKey = flatOrder.allocateKey(qtyRef);
// Retrieves initial order qty value for further incremental update.
UInt32 qty;
if (!Number::tryParse(flatOrder[qtyKey].data(), flatOrder[qtyKey].size(), qty))
{
throw std::domain_error("Cannot transform order qty to integer value. ");
}
size_t iteration = 1000;
while (iteration--)
{
// Assigns a new value to the ClOrdID field.
flatOrder.set(clOrdIdKey, Timestamp::utc(), TimestampFormat::YYYYMMDDHHMMSSMsec);
// Assigns the new order qty.
flatOrder.set(qtyKey, qty += 1000);
// Sends the updated order to the counterparty.
session.send(&flatOrder);
// Extracts the sequence number assigned to the sent message.
const StringRef seqNumberStr = flatOrder[KnownFlatFieldKeys::seqNumber()];
SequenceNumber seqNumber;
if (Number::tryParse(seqNumberStr.data(), seqNumberStr.size(), seqNumber))
{
std::cout << "Order sent under #" << seqNumber << '.' << std::endl;
}
else
{
throw std::domain_error("Cannot cast sequence number to an integer number. ");
}
}

Associating User Data

The OnixS::FIX::FlatMessage::userData() method can attach a user data pointer to the message object. This way can be used to associate some data with the given message object or pass user data with a message object through sending or receiving call stack. The user data is available in OnixS::FIX::ISessionListener, and OnixS::FIX::ISessionStorage callbacks are called for this particular message object.

Note
The message object does not manage the lifetime of user data. This is the responsibility of the user's code.