OnixS C++ FIX Engine  4.12.0
API Documentation
Typed FIX Messages

OnixS::FIX::Message class represents a generic model of any FIX message, in order to manipulate such a message, you need to check the message type and know what fields exist in this message, all of this is not safe and creates some development complexity, so to simplify the FIX message handling and make this more safe you can use strongly typed message classes instead of the generic Message class. Such typed message classes can be generated by the generator tool.

Typed Messages Generator

There are two modes in the typed messages generator.

In the first one, the messages are generated based on the FIX Dictionary file provided. This mode is enabled by specifying the dictionary argument.

In the second one, the generation performed based on the standard FIX Dictionary for the provided FIX versions, which must be specified. This mode is enabled by specifying the versions argument.

It is possible to provide the generator with a filter for messages to be dispatched as incoming ones. It reduces the generated code size and achieves performance benefits for message dispatching. To do so, message-types argument can be used. If no filter provided, all the messages are treated as incoming ones.

Below are the description for the Generator arguments:

Argument Required Description
dictionary Y A path to the FIX dictionary file, the first mode
versions Y A comma-separated list of FIX versions, the second mode
out-name N A name to the files, where typed messages classes are saved
std N The C++ standard version (Cxx03, Cxx11, Cxx14, Cxx17, Cxx20, universal (use macros))
message-typesN A comma-separated list of messages to be dispatched as incoming

Example: --versions=FIX40,FIX50sp1 --out-dir=Generated --std=Cxx17 --message-types=D,8: generate messages based on standard FIX Dictionary for FIX versions 4.0 and 5.0 SP1, use C++17, put generated classes in files named Generated, treat messages with types D and 8 as incoming.

Note
If you generate typed messages from an xml dialect file please make sure that each message node has the "name" attribute, please see the current dialect xml schema.

The typed messages generator is a .NET 7 application. This means that it requires installing the corresponding dotnet-sdk. For example, for Linux, please see the Install .NET on Linux page for details. To run the generator, one can use the following command: dotnet OnixS.Fix.TypedMessagesGenerator.dll.

Establishing Connection

It is very similar to the standard interface, but the listener object must be inherited from TypedMessageListener, instead of OnixS::FIX::ISessionListener.

using namespace OnixS::FIX;
using namespace OnixS::FIX::FIX42;
class InitiatorListener ONIXS_FIXENGINE_FINAL : public TypedMessageListener
{
public:
void onInboundExecutionReport(ExecutionReportMsg&, Session*) ONIXS_FIXENGINE_OVERRIDE
{
// ExecutionReport-specific code to be put here
}
};
InitiatorListener initiatorListener;
Session initiator(targetCompID, senderCompID, ProtocolVersion::FIX_42, &initiatorListener);
initiator.logonAsInitiator(AcceptorHost, AcceptorPort);
// The message exchange..
// Done with messaging.
initiator.logout();
initiator.shutdown();

Sending Typed Messages

It is very similar to the standard interface. The strongly-typed message object is created, and the fields are set with the corresponding setters.

OrderSingleMsg order;
order
.setClOrdId("Unique identifier for Order")
.setSymbol("IBM")
.setSide(Side::Buy)
.setOrderQty(1000)
.setOrdType(OrdType::Market)
.setTransactTime(Timestamp::utc())
.setHandlInst(HandlInst::ManualOrder);
const UInt32 noAllocsSize = 2;
OrderSingleMsg::Allocs noAllocs = order.allocs(noAllocsSize);
noAllocs[0]
.setAllocAccount("AllocAccount1")
.setAllocShares(10)
;
noAllocs[1]
.setAllocAccount("AllocAccount2")
.setAllocShares(20)
;
order.validate();
initiator.send(&order);

Receiving Typed Messages

To receive an incoming messages, it is necessary to override the corresponding method of TypedMessageListener class. The incoming messages are delivered via these callbacks.

The strongly-typed message getters provide a type specified in the FIX Dictionary during the generation; no type conversion is required.

For non-required fields, the getter's return a boolean value that indicates the field's presence, and the value itself is returned by reference.

using namespace OnixS::FIX;
using namespace OnixS::FIX::FIX42;
struct Listener ONIXS_FIXENGINE_FINAL : public TypedMessageListener
{
// Is invoked when 'OrderSingle' message is received.
void onInboundOrderSingle(OrderSingleMsg& order, Session* sn) ONIXS_FIXENGINE_OVERRIDE
{
if(HandlInst::ManualOrder == order.handlInst())
{
ExecutionReportMsg report;
report
.setClOrdId(order.clOrdId())
.setOrderId("Report1")
.setExecTransType(ExecTransType::New)
.setExecType(ExecType::Fill)
.setOrdStatus(OrdStatus::Filled)
.setSymbol(order.symbol())
.setSide(order.side())
.setLeavesQty(0);
// Non-required fields
Decimal price;
if(order.price(price))
{
report.setPrice(price);
}
Decimal qti;
if(order.orderQty(qti))
{
report.setOrderQty(qti);
}
sn->send(&report);
}
}
// Is invoked when 'OrderCancelRequest' message is received.
void onInboundOrderCancelRequest(OrderCancelRequestMsg&, Session*) ONIXS_FIXENGINE_OVERRIDE
{
// Process OrderCancelRequest here
}
};

If a message with an unexpected type is received, it is delivered to the user via TypedMessageListener::onUnrecognizedMessage callback.

Working with Typed Repeating Groups

Every group and its entry are exposed as a nested class of a correspondent owner (message or another group).

To add a new repeating group or modify the number of instances of the existing one, a method named after the group name must be used as in the following example. The method accepts a new number of repeating group entries as a parameter and returns an instance of a newly created or modified repeating group.

using namespace OnixS::FIX;
using namespace OnixS::FIX::FIX42;
MarketDataRequestMsg request;
// Creates a repeating group NoMDEntryTypes with two instances.
MarketDataRequestMsg::EntryTypes entryTypes = request.entryTypes(2);
// Defines fields in the first instance/entry of repeating group.
entryTypes[0].setEntryType(MDEntryType::Offer);
// .. and the same but for the second instance.
entryTypes[1].setEntryType(MDEntryType::Bid);
// Creates a repeating group NoRelatedSym with two instances.
request.relatedSym(2);
MarketDataRequestMsg::RelatedSyms relatedSyms = request.relatedSym(2);
relatedSyms[0].setSymbol(constructStrRef("EURUSD_0"));
relatedSyms[1].setSymbol(constructStrRef("EURUSD_1"));

To allow traversing group entries, every typed group entry class is a descendant of OnixS::FIX::TypedGroup class, which contains correspondent methods: querying groups size, accessing the group's entries, etc. The index operator (or OnixS::FIX::TypedGroup::at member) returns an entry that allows fields access in the very same manner the message does. The indexing entry starts from zero.

Note
If the requested group does not exist, the size query from the correspondent object returns zero.

The following example demonstrates how to traverse over the repeating group entries:

using namespace OnixS::FIX;
using namespace OnixS::FIX::FIX42;
MassQuoteMsg massQuote;
// Get a NoQuoteSets repeating group.
const MassQuoteMsg::QuoteSets& quoteSets = massQuote.quoteSets();
// Obtain the size of a NoQuoteSets repeating group.
const size_t quoteSetsSize = quoteSets.size();
// Iterates over all repeating group entries of the NoQuoteSets group by the index.
for(size_t index = 0; index < quoteSetsSize; ++index)
{
const MassQuoteMsg::QuoteSetsEntry& entry = quoteSets[index];
std::cout << "quoteSets[" << index << "].quoteSetId = " << entry.quoteSetId() << std::endl;
// Gets a nested QuoteEntries repeating group.
const MassQuoteMsg::QuoteSetsEntry::QuoteEntries& nestedGroup = entry.quoteEntries();
print(nestedGroup);
}

A group can also be traversed using an iterator:

using namespace OnixS::FIX;
using namespace OnixS::FIX::FIX42;
// Print helper
struct Print
{
void operator()(const MassQuoteMsg::QuoteSetsEntry& entry) const
{
std::cout << entry.toString('|') << std::endl;
}
};
void iterateStronglyTypedMessageFields()
{
MassQuoteMsg massQuote;
// Get a NoQuoteSets repeating group.
MassQuoteMsg::QuoteSets quoteSets = massQuote.quoteSets();
// Traverse the group using an iterator.
std::for_each(quoteSets.cbegin(), quoteSets.cend(), Print());
}

A group can also be traversed using range-based for loop:

void iterateStronglyTypedMessageFieldsRangeBased()
{
MassQuoteMsg massQuote;
// Get a NoQuoteSets repeating group.
MassQuoteMsg::QuoteSets quoteSets = massQuote.quoteSets();
// Traverse the group using range-based for loop.
for(auto & entry : quoteSets)
{
std::cout << entry.toString('|') << std::endl;
}
}

Overhead

The Strongly Typed Messages are implemented as a very thin wrapper, which incurs almost no additional overhead. Moreover, because of the highly optimized message dispatching, there could be some performance benefits.