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");
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");
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:
Message order(FIX42::Values::MsgType::Order_Single, ProtocolVersion::FIX_42);
order.set(FIX42::Tags::ClOrdID, "ClientOrderID")
.set(FIX42::Tags::OrderQty, 1000);
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:
FlatMessage blankMsg;
FlatMessage msg(ProtocolVersion::FIX_42, FIX42::Values::MsgType::Order_Single);
- 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);
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);
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);
FlatMessage massQuoteFlatMsg(massQuoteMsg);
FlatFieldRef noPartyIDsRef = massQuoteFlatMsg.find(FIX44::Tags::NoPartyIDs);
if(noPartyIDsRef)
{
FlatGroup noPartyIDsGroup = massQuoteFlatMsg.getGroup(noPartyIDsRef);
std::clog << "Found " << noPartyIDsGroup.size() << " party identifiers:" << std::endl;
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:
FlatMessage massQuoteFlatMsg(massQuoteMsg);
std::vector<FlatFieldKey> partyIdFieldKeys;
FlatFieldRef partyIdFieldRef;
while ((partyIdFieldRef = massQuoteFlatMsg.find(FIX44::Tags::PartyID, partyIdFieldRef)))
{
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
Message order(FIX42::Values::MsgType::Order_Single, ProtocolVersion::FIX_42);
order.set(FIX42::Tags::ClOrdID, "ClientOrderID")
.set(FIX42::Tags::OrderQty, 1000);
FlatMessage flatOrder(order);
FlatFieldRef clOrdIdRef = flatOrder.find(FIX42::Tags::ClOrdID);
FlatFieldRef qtyRef = flatOrder.find(FIX42::Tags::OrderQty);
if (!clOrdIdRef || !qtyRef)
{
throw std::domain_error("Cannot find the required fields in the Order.");
}
const FlatFieldKey clOrdIdKey = flatOrder.allocateKey(clOrdIdRef);
const FlatFieldKey qtyKey = flatOrder.allocateKey(qtyRef);
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--)
{
flatOrder.set(clOrdIdKey, Timestamp::utc(), TimestampFormat::YYYYMMDDHHMMSSMsec);
flatOrder.set(qtyKey, qty += 1000);
session.
send(&flatOrder);
const StringRef seqNumberStr = flatOrder[KnownFlatFieldKeys::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.