FIX Antenna HFT CME iLink 3 support


Overview

CME iLink 3 support is implemented as a kind of session inside Fix Antenna HFT engine scope. It allows to work with iLink 3 protocol transparently like with another sessions. The configuration is quite the same. The implementation allows end-user to get direct binary data and utilize any binary message handling API on his choice. Initiator(client) sessions are supported only. Further information about basic concepts and initial Fix Antenna HFT engine configuration can be found here. All basic concepts regarding errors handling and general session management are the same among any session type. So, they can be found using the link provided. Here is the link to CME iLink 3 documentation that describes iLink 3 protocol in details. Please refer to it for iLink 3 details not described in this document.

Quick start.

Quick start guide can be found here. This guide describes briefly how to start working with the engine, initialize it and do basic operations. Further described differences are mostly connected to iLink 3 specifically but general concept of Quick start guide remains the same and is valid. The difference is in details only.

Configuration

There are engine parameters that have to be configured on engine start. These parameters are CME related and applicable for CME iLink 3 sessions only. Please see the table below for details. To obtain these parameters please contact CME.

Engine::FixEngine::InitParameters members. All parameters are mandatory!


ParameterTypeDescription
1cmeTradingSystemNamestd::stringTrading system name. This parameter is registered with CME for end-user business application that connects to CME iLink 3 order-entry gateway. 1-30 symbols.
2cmeTradingSystemVersionstd::stringTrading system version. This parameter is registered with CME for end-user business application that connects to CME iLink 3 order-entry gateway. 1-10 symbols.
3cmeTradingSystemVendorstd::stringTrading system vendor. This parameter is registered with CME for end-user business application that connects to CME iLink 3 order-entry gateway. 1-10 symbols.


Session related parameters. These parameters are configured on per-session basis. So every iLink 3 session can have these parameters different.

Engine::SessionExtraParameters members. All parameters are mandatory!


ParameterTypeDescriptionCan be set with properties file?
1iLink3AccessKey_std::stringiLink 3 session access key. This parameter is provided by CME and unique for each iLink 3 session.Session.<Sender>/<Target>.ILink3.AccessKey
2iLink3Firm_std::stringiLink 3 Globex firm. Usually this parameters corresponds to "Primary Globex Firm".Session.<Sender>/<Target>.ILink3.Firm
3iLink3GetSigKeyCallback_Engine::SessionExtraParameters::ILink3GetSigKeyCallbackCallback routine used by the engine to get key used to create CME logon signature. Provided by CME and unique for each iLink 3 session.No

Please pay attention

To achieve the best performance  LiteFixMessage should be used. So configure your SessionExtraParameters with useLiteMessage_ = true.


Arguments for Engine::FixEngine::createSession() routine.


ArgumentTypeDescription
1senderCompIDstd::stringiLink 3 session ID. This parameter is provided by CME and unique for each iLink 3 session.
2targetCompIDstd::stringCan be any value(usually "iLink 3"). This parameter is not used by iLink 3 handler in specific manner and is used for session logs naming and utility only.
3scpProtocolNamestd::stringMust be Exactly "ILINK3". The value tells the engine that iLink 3 handler must be used for the newly created session.

Engine initialization.

See the sample code below.

Engine initialization
    // Initialize engine
    Engine::FixEngine::InitParameters params;
    params.propertiesFileName_ = "myapp.properties";

	...
    
    params.cmeTradingSystemName = "MyTradeSystem";// CME system name
    params.cmeTradingSystemVersion = "1.0";       // CME system version     
    params.cmeTradingSystemVendor = "MyFirmName"; // CME system vendor

    FixEngine::init( params );

Session creation.

The session is created in the same manner like any other(FIX) session within the engine except minor details connected with newly added configuration parameters. See the sample code below.

Session creation
    Engine::SessionExtraParameters extraParams;

	...

	extraParams.useLiteMessage_ = true;
    extraParams.iLink3AccessKey_ = "XXXXX"; // Session Access Key
    extraParams.iLink3Firm_ = "000"; // Session Globex Firm
    extraParams.iLink3GetSigKeyCallback_ = GetILink3Key;

	std::string sessionID = "AAA"; // Session ID

    RefCounter<Session> pSn(FixEngine::singleton()->createSession( pMyILink3App, sessionID, "ILink3", "ILINK3", &extraParams), false);

In the simplest case GetILink3Key routine can be defined as following:

Simple get signature key callback routine
static std::string GetILink3Key ( const std::string& /*accessKey*/ )
{
    return "xxxxxxxxxxxxxxxxx"; // Session signature key
}

Session connection.

iLink 3 session connects the same way like a FIX session does. If fail over mechanism is used secondary ip/port should be set as backup connection for the session. See the sample code below.

Session connection
  	std::string backupHost;
	int backupPort = 0;
	Engine::SessionExtraParameters extraParams;	

	... // parameters setup here
	
	bool hasBackup = !backupHost.empty() && backupPort != 0;
    if(hasBackup)
    {
        extraParams.enableAutoSwitchToBackupConnection_ = true; // Auto switch to backup
        extraParams.cyclicSwitchBackupConnection_ = true; // Auto switch back to primary
    }
    
	... // Session creation here

	Engine::SessionBackupParameters backupParams;
    backupParams.host_ = backupHost;
    backupParams.port_ = backupPort;
    backupParams.hbi_ = hbi;

    pSn->connect(hbi, host, port, Engine::NONE, hasBackup ? &backupParams : NULL);

Send message.

iLink 3 session works binary data only.  The engine automatically updates outgoing sequence number of the message being sent, so no sequence handling from business application is required. Below is send message sample.

Message sending
static void sendMessage( RefCounter<Session> pILink3Session, const char* pMessageData, size_t messageDataSize )
{
    pILink3Session->put( pMessageData, messageDataSize ); // Sequence number is updated automatically before message is sent.
}

Receive message.

iLink 3 session delivers binary data only. So, the business application gets binary data to process.

There is no special callback to receive incoming messages from iLink 3 session. The same callback as for FIX sessions is used but there is a new function inside Engine::FIXMessage interface introduced that should be used to get binary data from a message. It is named getBinaryData and returns true if the message contains binary data(always true for iLink 3 sessions) and false otherwise (always false for ordinary FIX sessions).

The function has two arguments.

  1. The first is a reference to a pointer that receives a pointer to binary data
  2. The second is a reference to a variable that receives binary data size. The binary data got has the same scope as Engine::FIXMessage it was gotten from, so one has to copy it if one needs a wider scope. 

See the sample below.

Message receiving
bool TraderApp::process( const Engine::FIXMessage& msg, const Engine::Session& aSn )
{
    const char* buffer;
    size_t bufferSize;

    if(!msg.getBinaryData( buffer, bufferSize ))
        throw Utils::Exception( "Message without binary data received! Unable to process!" );
	// buffer points to binary data now, bufferSize contains binary data size.
    // Please pay attention that binary data is valid in the scope of process routine only. So one must copy it if one needs it for wider scope!
	// See iLink3_certification sample on possible solution.
    return true;
}


iLink3_certification sample in the package demonstrates the process callback usage


Working with binary data.

It is up to end-user how to handle binary data. One can use built-in CME iLink 3 binary messages handling API or any other API on his choice, for example Real logic SBE. Built-in API is contained within iLink3BinaryMessages.h. This is a generated file that contains all(session and application layer) in/out messages from/to CME iLink 3 gateway. See CME message specification for details. One should use CME iLink 3 documentation in order to populate outgoing and processing incoming messages data correctly. See samples below and inline comments.

Sending.

Sending with built-in API
static const size_t cMaxILink3MessageSize = 65535;
unsigned int sendPartiesDefinition( RefCounter<Session> pILink3Session, unsigned int id )
{
	// Allocate buffer on stack
    char buf[cMaxILink3MessageSize];
    memset(& buf, 0, sizeof( buf )); // buffer for messages with repeating groups should be cleared before use
    ILink3::PartyDetailsDefinitionRequest518* pPartyDetails = new (&buf) ILink3::PartyDetailsDefinitionRequest518(true); // Use placement new to avoid memory allocation.

    // Populate fields.
	pPartyDetails->setPartyDetailsListReqID( id );
    pPartyDetails->setSendingTimeEpoch( System::Time::currentTimeUsec1970() * 1000 );
    pPartyDetails->setListUpdateAction( ILink3::ListUpdAct::ListUpdAct_Add );
    pPartyDetails->setCustOrderCapacity( ILink3::CustOrderCapacity::CustOrderCapacity_Membertradingfortheirownaccount );
    pPartyDetails->setClearingAccountType( ILink3::ClearingAcctType::ClearingAcctType_Customer );
    pPartyDetails->setCustOrderHandlingInst( ILink3::CustOrdHandlInst::CustOrdHandlInst_ClientElectronic );
        
	// Default values are set within constructor if its boolean argument is 'true'.
	/*
    pPartyDetails->setMemo( "" );
    pPartyDetails->setAvgPxGroupID( "" );
    pPartyDetails->setSelfMatchPreventionID( ILink3::getuInt64NULLNullValue() );
    pPartyDetails->setCmtaGiveupCD( ILink3::CmtaGiveUpCD::CmtaGiveUpCD_Null );
    pPartyDetails->setSelfMatchPreventionInstruction( ILink3::SMPI::SMPI_Null );
    pPartyDetails->setAvgPxIndicator( ILink3::AvgPxInd::AvgPxInd_Null );
    pPartyDetails->setClearingTradePriceType( ILink3::SLEDS::SLEDS_Null );
    pPartyDetails->setExecutor( ILink3::getuInt64NULLNullValue() );
    pPartyDetails->setIDMShortCode( ILink3::getuInt64NULLNullValue() );
    */

    // Populate repeating group
	{
		// Get reference to repeating group of interest.
        ILink3::PartyDetailsDefinitionRequest518::NoPartyDetails& partyDetail = pPartyDetails->getNoPartyDetails();
		// Set required number of the group elements.
        partyDetail.setElementsCount( 3 );
		// Get pointer to the group elements array.
        ILink3::PartyDetailsDefinitionRequest518::NoPartyDetails::NoPartyDetailsElement* elements = partyDetail.getElements();

        elements[0].setPartyDetailRole( ILink3::PartyDetailRole::PartyDetailRole_CustomerAccount );
        elements[0].setPartyDetailID( "acc" );

        elements[1].setPartyDetailRole( ILink3::PartyDetailRole::PartyDetailRole_ExecutingFirm );
        elements[1].setPartyDetailID( "004" );

        elements[2].setPartyDetailRole( ILink3::PartyDetailRole::PartyDetailRole_Operator );
        elements[2].setPartyDetailID( "operator" );
    }

	// Update message size taking into account all nesting repeating groups.
    pPartyDetails->updateMessageLength();

	// It is OK to pass buffer pointing to stack location since the engine doesn't use it after return.        
	pILink3Session->put( pPartyDetails, pPartyDetails->getMessageLength() );

    return id;
}

#define SET_PRICE9(a, b) ((a).setMantissa(static_cast<System::i64>((b)*1000000000)))
static void sendLimit( RefCounter<Session> pILink3Session, int instrumentID, int qty )
{
	// Allocate buffer on stack
    char buf[cMaxILink3MessageSize];
    ILink3::NewOrderSingle514* pNewOrder = new (&buf) ILink3::NewOrderSingle514( true );

    // Populate fields.
    pNewOrder->setOrdType( ILink3::OrderTypeReq::OrderTypeReq_Limit );

    SET_PRICE9( pNewOrder->price, 1 );
    pNewOrder->setOrderQty( qty );
    pNewOrder->setSecurityID( instrumentID );
    pNewOrder->setSide( ILink3::SideReq::SideReq_Sell );
    pNewOrder->setSenderID( "user1" );
    std::ostringstream ss;
    ss << "TEST_" << pILink3Session->getOutSeqNum();
    pNewOrder->setClOrdID( ss.str() );
    pNewOrder->setPartyDetailsListReqID( 0 );
    //pNewOrder->setOrderRequestID( 0 );
    pNewOrder->setSendingTimeEpoch( System::Time::currentTimeUsec1970() * 1000 );
    pNewOrder->setLocation( "IL" );
    pNewOrder->setTimeInForce( ILink3::TimeInForce::TimeInForce_Day );
    pNewOrder->setManualOrderIndicator( ILink3::ManualOrdIndReq::ManualOrdIndReq_Manual );

	// Update message size.
    pNewOrder->updateMessageLength();

	// It is OK to pass buffer pointing to stack location since the engine doesn't use it after return.        
    pILink3Session->put( pNewOrder, pNewOrder->getMessageLength() );
}

Receiving.

Processing received message with built-in API
void processData( const char* binaryData, size_t dataSize )
{
    // Cast data to base iLink 3 type
	const ILink3::StandardHeader* hdr = reinterpret_cast<const ILink3::StandardHeader*>(binaryData);

	// Check whatever the message is valid	
    if(!ILink3::isValidILink3Message( hdr ))
        throw std::exception( "Invalid iLink 3 message received!" );
    if(hdr->getSbeHeader().getTemplateId() == ILink3::ExecutionReportNew522::TemplateID)
    {
	    const ILink3::ExecutionReportNew522& msg = reinterpret_cast<const ILink3::ExecutionReportNew522&>(*hdr);
        std::cout << "SplitMsg:" << static_cast<unsigned int>(msg.splitMsg.value) << std::endl;
        std::cout << "DelayToTime:" << msg.delayToTime << std::endl;
        std::cout << "\t\t Qty:" << msg.orderQty << std::endl;
    }else if(...)
	{
		...
	}else
	{
		// Unexpected message type processing here.
	}
}

Session termination.

Session termination can be made in simple manner. One just needs to call session's disconnect() method. This method accepts logout text that is sent to counter-party and terminates the session correctly. See the sample below.

Terminate session
void terminate( RefCounter<Session> pILink3Session, bool bReleaseSession )
{
    // Send logout and disconnect
	pILink3Session->disconnect( "The session was closed by application" );

    // Unregister our application from the session to avoid any callback be called after this point. Application object can be destroyed after this call.
    pILink3Session->registerApplication(NULL);

	// It is safe to release reference to the session. The engine keeps its our reference and releases it after session's termination is finished but it is safe to keep this reference if needed and release it later.
	if( bReleaseSession )
		pILink3Session.reset();

}

Errors handling.

Errors handling can be done utilizing Engine::Application callbacks. The names are quite descriptive. The general idea behind is that any important action has particular callback that corresponds to it. So, one can define callbacks required inside Engine::Application interface to react on some erroneous events and act as required. The engine handles most error conditions related to session level errors internally. The only event of interest is the case when NotApplied message from CME is received. The engine handles this condition the way CME recommends. in particular, send Sequences message to gap-fill. So, if there is a need to know what sequence range is to be gap-filled Engine::Application::onResendRequestEvent() callback should be implemented. One can force engine to ignore NotApplied from CME setting processResendRequest_ member of Engine::ResendRequestEvent to false in order to process NotApplied manually. Its default value is true. More details can be found at https://corp-web.b2bits.com/fixahft/doc/html/classEngine_1_1Application.html#a54fde5f18f9f63c4aadcbb5b6f10b1c8. See the sample below.

Handle resend request event
class MyApp: public Engine::Application
{
public:
	virtual void onResendRequestEvent( const Engine::ResendRequestEvent& event, const Engine::Session& /*sn*/ );
}
...
void MyApp::onResendRequestEvent( const Engine::ResendRequestEvent& event, const Engine::Session& /*sn*/ ) 
{
	// Getting begin and end sequence numbers of the gap
	int gapBegin = event.getBeginSeqNo();
	int gapEnd = event.getEndSeqNo();
	
	if(gapEnd - gapBegin < 100) // Do custom NotApplied processing if the gap is more than 100 messages. It is just a sample not related to any actual business needs!
		return;
	// Tell the engine to ignore NotApplied message.
	event.processResendRequest_ = false;

	// Getting original binary message if required.
    const char* buffer;
    size_t bufferSize;

    if(!event.resendRequestMsg_->getBinaryData( buffer, bufferSize ))
        throw Utils::Exception( "Message without binary data received! Unable to process!" );
	
    // Cast data to base iLink 3 type
	const ILink3::NotApplied513* pNotApplied = reinterpret_cast<const ILink3::NotApplied513*>(buffer);

	// Check whatever the message is valid	
    if(!ILink3::isValidILink3Message( pNotApplied ) ||  pNotApplied ->getSbeHeader().getTemplateId() != ILink3::NotApplied513::TemplateID)
        throw std::exception( "Invalid NotApplied message received!" );

	// Do custom NotApplied processing here

	...
	
    return;
}

Multiple segments connection.

CME has the market splitted into several segments each of which has it's separate IP and port defined. So one can connect to several or even all segments simulteneously. To do this  one needs to create separate iLink 3 session to each segment needed the same way as describe above in the samples. So, every segment looks like separate session. The only thing to take into account here is that every session has to have unique combination of senderCompID/targetCompID. Since senderCompID is used as sessionID for iLink 3 sessions and is the same for all segments targetCompID should be different for every segment. So the combinations migth be like following: AAA/iLink3-seg99, AAA/iLink3-seg21 and so on. Please see the sample below.

Multi segments sample
    Engine::SessionExtraParameters extraParams;

	...

	extraParams.useLiteMessage_ = true;
    extraParams.iLink3AccessKey_ = "XXXXX"; // Session Access Key
    extraParams.iLink3Firm_ = "000"; // Session Globex Firm
    extraParams.iLink3GetSigKeyCallback_ = GetILink3Key;

	std::string sessionID = "AAA"; // Session ID

	// pMyILink3AppForSeg99 and pMyILink3AppForSeg21 can be the same if no separate processing for different segments is required.
	// Create session for segment 99
    RefCounter<Session> pSn99(FixEngine::singleton()->createSession( pMyILink3AppForSeg99, sessionID, "ILink3-seg99", "ILINK3", &extraParams), false);

	// Connect to segment 99
    pSn99->connect(hbi, seg99Host, seg99Port);

	// Create session for segment 21
    RefCounter<Session> pSn21(FixEngine::singleton()->createSession( pMyILink3AppForSeg21, sessionID, "ILink3-seg21", "ILINK3", &extraParams), false);

	// Connect to segment 21
    pSn21->connect(hbi, seg21Host, seg21Port);

Sample.

There is project named iLink3_certification that is provided within the package. This project demonstrates possible solution how to work with CME iLink 3 using the engine. It is real operating application that can be used as a base to build on end-user business project or to build application to pass CME certification. This application, after setting correct configuration values in engine.properties file, see configuration section above, can be used to connect to CME iLink 3 gateway and do some basic operations like send a test order and similar. It utilizes object pool model to queue messages for processing incoming messages. 

How-Tos.

Here is the link where How-To links regarding latency configuration and other useful topics are located. https://b2bits.atlassian.net/wiki/pages/viewpage.action?pageId=6129580