Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.


Table of Contents

Overview

CME ILink3 support is implemented as a kind of session inside Fix Antenna HFT engine scope. It allows to work with ILink3 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 ILink3 documentation that describes ILink3 protocol in details. Please refer to it for ILink3 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 ILink3 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 ILink3 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 ILink3 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 ILink3 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 ILink3 order-entry gateway. 1-10 symbols.


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

Engine::SessionExtraParameters members. All parameters are mandatory!


ParameterTypeDescriptionCan be set with properties file?
1iLink3AccessKey_std::stringILink3 session access key. This parameter is provided by CME and unique for each ILink3 session.Session.<Sender>/<Target>.ILink3.AccessKey
2iLink3Firm_std::stringILink3 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 ILink3 session.No
Note
titlePlease 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::stringILink3 session ID. This parameter is provided by CME and unique for each ILink3 session.
2targetCompIDstd::stringCan be any value(usually "ILink3"). This parameter is not used by ILink3 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 ILink3 handler must be used for the newly created session.

Engine initialization.

See the sample code below.

Code Block
languagecpp
titleEngine 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.

Code Block
languagecpp
titleSession 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:

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

Session connection.

ILink3 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.

Code Block
languagecpp
titleSession 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.

ILink3 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.

Code Block
languagecpp
titleMessage 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.

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

There is no special callback to receive incoming messages from ILink3 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 ILink3 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.

Code Block
languagecpp
titleMessage 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;
}


Info

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 ILink3 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 ILink3 gateway, See CME message specification for details. One should use CME ILink3 documentation in order to populate outgoing and processing incoming messages data correctly. See samples below and inline comments.

Sending.

Code Block
languagecpp
titleSending 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.

Code Block
languagecpp
titleProcessing received message with built-in API
void processData( const char* binaryData, size_t dataSize )
{
    // Cast data to base ILink3 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 ILink3 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.

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

	// 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.

Code Block
languagecpp
titleHandle 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 ILink3 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;
}

Multiply segments connection

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 ILink3 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 ILink3 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.

Code Block
languagecpp
titleMulti 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 ILink3 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 ILink3 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://kb.b2bits.com/pages/viewpage.action?pageId=25886757