Introduction
The article describes how to implement the FIX server and client, it contains a simple sample to demonstrate how the server sends
the MarketDataIncrementalRefresh message to all its client sessions, and how the client parses the MarketDataIncrementalRefresh message and fills the snapshot into a local file.
First of all, we need to know what the FIX protocol is.
What is FIX Protocol?
It is a series of messaging specifications for the electronic communication of trade-related messages. It has been developed through the collaboration of banks, broker-dealers, exchanges, industry utilities and associations, institutional investors, and information technology providers from around the world. These market participants share a vision of a common, global language for the automated trading of financial instruments.
Most of the exchanges use this standard for communication like sending Order, Executions, MarketData, etc. There are many versions of specifications released by
the FIX organization like 4.0, 4.2, 5.0, etc.
You can read more about FIX on http://www.fixprotocol.org.
What is QuickFix?
QuickFIX is a free and open source implementation of the FIX protocol in various languages like C++, Java,
Ruby, .NET, etc.
So let’s start with the implementation of Fix messages. I am going to create two applications, server and client which we call as
BondsProServer
and BondsProClient, respectively.
Implementation with C++
I created a solution which has two projects BondsProServer and BondsProClient. BondsProServer is active as server and BondsProClient is the client app.
BondsProServer
To start BondsProServer, we need to configure and configurations are put into BondsProServer.cfg which should be pretty straightforward configurations like below:
[DEFAULT]
ConnectionType=acceptor
SocketAcceptPort=5001
SocketReuseAddress=Y
StartTime=00:00:00
EndTime=00:00:00
FileLogPath=log
FileStorePath=store
[SESSION]
BeginString=FIX.4.4
SenderCompID=EXECUTOR
TargetCompID=CLIENT1
DataDictionary=spec\FIX44.xml
- Connection type tells this application will run as Acceptor which is server.
- SocketAcceptPort is the listening port.
- Session tag has configuration for creating a session between
the client application (initiator) and acceptor.
- BegingString: This sets the Fix Message specification the
session will work on.
- SenderCompID is the ID of the server which will listen and send messages.
- TargetCompID= is the ID of the client to which the server will send messages.
- DataDictionary: Path of data dictionary file which is in XML format; this file has message specifications according to
the various specification versions.
Source Code
To start any session with Fix, we need to create a class which should implement
the QuickFix.Application
interface. It has the following methods to be implemented:
void Application::onLogon( const FIX::SessionID& sessionID )
{
std::cout << std::endl << "Logon - " << sessionID << std::endl;
sessions_.insert(sessions_.end(), sessionID);
}
void Application::onLogout( const FIX::SessionID& sessionID )
{
std::cout << std::endl << "Logout - " << sessionID << std::endl;
sessions_.remove(sessionID);
}
void Application::toAdmin( FIX::Message& message, const FIX::SessionID& )
{
std::cout << std::endl << "ADMIN OUT: " << message << std::endl;
}
void Application::fromAdmin( const FIX::Message& message, const FIX::SessionID& )
throw( FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon )
{
std::cout << std::endl << "ADMIN IN: " << message << std::endl;
}
void Application::fromApp( const FIX::Message& message,
const FIX::SessionID& sessionID )
throw( FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::UnsupportedMessageType )
{
crack( message, sessionID );
std::cout << std::endl << "IN: " << message << std::endl;
}
void Application::toApp( FIX::Message& message, const FIX::SessionID& sessionID )
throw( FIX::DoNotSend )
{
std::cout << std::endl << "OUT: " << message << std::endl;
}
namespace FIX
{
namespace FIELD
{
const int MinInc = 6350;
const int MinBR = 6351;
const int YTM = 6360;
const int YTW = 6361;
}
DEFINE_QTY(MinInc);
DEFINE_QTY(MinBR);
DEFINE_PERCENTAGE(YTM);
DEFINE_PERCENTAGE(YTW);
}
const char* symbols[]={"bond1","bond2","bond3", "bond4", "bond5"};
void Application::sendXMessages() {
std::ofstream out("d:\\send.txt");
FIX44::MarketDataIncrementalRefresh message = FIX44::MarketDataIncrementalRefresh();
FIX44::MarketDataIncrementalRefresh::NoMDEntries group;
long count = sizeof(symbols)/(sizeof(symbols[0]) );
double rnd = count*rand()/(RAND_MAX+1.0);
FIX::Symbol symbol(symbols[(long)rnd]);
rnd = 3*rand()/(RAND_MAX+1.0);
FIX::MDUpdateAction action(rnd<2.0?(rnd<1?'0':'1'):'2'); rnd = 2*rand()/(RAND_MAX+1.0);
std::ostringstream oss;
oss<<rand();
FIX::MDEntryID entryID(oss.str());
FIX::MDEntryType entryType(rnd<1.0?'0':'1'); FIX::MDEntryPx price(100+10*rand()/(RAND_MAX+1.0));
FIX::MDEntrySize size((long)(1000*rand()/(RAND_MAX+1.0)));
FIX::MinQty qty((long)(100*rand()/(RAND_MAX+1.0)));
FIX::MinInc inc((long)(100*rand()/(RAND_MAX+1.0)));
FIX::MinBR br((long)(1000*rand()/(RAND_MAX+1.0)));
FIX::YTM ytm(0.1*rand()/(RAND_MAX+1.0));
FIX::YTW ytw(0.1*rand()/(RAND_MAX+1.0));
group.set( action );
group.set( entryID );
group.set( entryType );
group.set( symbol );
group.set( price );
group.set( size );
group.set( qty );
group.setField( FIX::MinInc(inc) );
group.setField( FIX::MinBR(br) );
group.setField( FIX::YTM(ytm) );
group.setField( FIX::YTW(ytw) );
message.addGroup(group);
out<<message.toXML().c_str()<<std::endl;
out<<message.toString().c_str()<<std::endl;
std::list<FIX::SessionID>::const_iterator it;
for(it=sessions_.begin(); it!=sessions_.end(); it++)
{
try
{
FIX::Session::sendToTarget( message, *it );
}
catch ( FIX::SessionNotFound& ) {
out<<"error to send the message!"<<std::endl;
}
}
}
Start BondsProServer Application
int main( int argc, char** argv )
{
if ( argc != 2 )
{
std::cout << "usage: " << argv[ 0 ]
<< " FILE." << std::endl;
return 0;
}
std::string file = argv[ 1 ];
try
{
FIX::SessionSettings settings( file );
Application application;
FIX::FileStoreFactory storeFactory( settings );
FIX::FileLogFactory logFactory( settings );
FIX::SocketAcceptor acceptor( application, storeFactory, settings, logFactory );
acceptor.start();
while(true){
FIX::process_sleep(5);
application.sendXMessages();
}
acceptor.stop();
return 0;
}
catch ( std::exception & e )
{
std::cout << e.what() << std::endl;
return 1;
}
}
Steps
- Create a
SessionSettings
object with config file. - Create an object of the
Application
class. - Create an object of the
SocketAcceptor
class by passing SessionSettings
. - Run the
Start
method of the acceptor
object.
Start BondsProClient
To start BondsProClient
, we need to configure and configurations are put into BondsProClient.cfg which should be pretty straightforward configurations like below:
[DEFAULT]
ConnectionType=initiator
HeartBtInt=30
ReconnectInterval=1
FileStorePath=store
FileLogPath=log
StartTime=00:00:00
EndTime=00:00:00
UseDataDictionary=N
SocketConnectHost=localhost
[SESSION]
BeginString=FIX.4.4
SenderCompID=CLIENT1
TargetCompID=FixServer
SocketConnectPort=5001
- Connection type tells this application will run as
Acceptor
which is server. - SocketAcceptPort: listening port.
- Session tag: has configuration for creating a session between
the client application (initiator) and acceptor.
- BeginString: this sets on which Fix Message specification
the session will work.
- SenderCompID: ID of client which will send messages.
- TargetCompID: ID of server to which server will listen messages.
- DataDictionary: Path of data dictionary file which is in
XML format, this file has message specifications according to various specification versions.
Source Code
To start any session with Fix, we need to create a class which should implement
the QuickFix.Application
interface. It has the following methods to be implemented:
void Application::onLogon( const FIX::SessionID& sessionID )
{
std::cout << std::endl << "Logon - " << sessionID << std::endl;
}
void Application::onLogout( const FIX::SessionID& sessionID )
{
std::cout << std::endl << "Logout - " << sessionID << std::endl;
}
void Application::toAdmin( FIX::Message& message, const FIX::SessionID& )
{
}
void Application::fromAdmin( const FIX::Message& message, const FIX::SessionID& )
throw( FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon )
{
}
void Application::fromApp( const FIX::Message& message, const FIX::SessionID& sessionID )
throw( FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::UnsupportedMessageType )
{
std::cout << std::endl << "IN: " << message << std::endl;
crack( message, sessionID );
}
void Application::toApp( FIX::Message& message, const FIX::SessionID& sessionID )
throw( FIX::DoNotSend )
{
try
{
FIX::PossDupFlag possDupFlag;
message.getHeader().getField( possDupFlag );
if ( possDupFlag ) throw FIX::DoNotSend();
}
catch ( FIX::FieldNotFound& ) {}
std::cout << std::endl << "OUT: " << message << std::endl;
}
namespace FIX
{
namespace FIELD
{
const int MinInc = 6350;
const int MinBR = 6351;
const int YTM = 6360;
const int YTW = 6361;
}
DEFINE_QTY(MinInc);
DEFINE_QTY(MinBR);
DEFINE_PERCENTAGE(YTM);
DEFINE_PERCENTAGE(YTW);
}
namespace FIX44
{
class NoMDEntriesBondsPro : public MarketDataIncrementalRefresh::NoMDEntries
{
public:
NoMDEntriesBondsPro() : MarketDataIncrementalRefresh::NoMDEntries() {}
FIELD_SET(*this, FIX::MinInc);
FIELD_SET(*this, FIX::MinBR);
FIELD_SET(*this, FIX::YTM);
FIELD_SET(*this, FIX::YTW);
};
}
void Application::onMessage
( const FIX44::MarketDataIncrementalRefresh& message, const FIX::SessionID& )
{
FIX::NoMDEntries noMDEntries;
message.get(noMDEntries);
if (noMDEntries.getValue()!=1){
std::cout << "NoMDEntries in MarketDataIncrementalRefresh is not 1!" <<std::endl;
return;
}
FIX44::MarketDataIncrementalRefresh::NoMDEntries group;
message.getGroup(1, group);
FIX::MDEntryID entryID; group.get(entryID);
FIX::MDUpdateAction action; group.get(action);
char actionvalue = action.getValue(); if (actionvalue=='2') {
std::map<std::string, SECURITY>::iterator it = securities_.end();
it=securities_.find(entryID);
if (it!=securities_.end())
securities_.erase(it);
return;
}
SECURITY security;
security.MDEntryID = entryID;
security.MDUpdateAction = action;
FIX::Symbol symbol;
if(group.isSet(symbol)){
group.get(symbol);
security.Symbol = symbol;
}
FIX::MDEntryType entryType;
if(group.isSet(entryType)) {
group.get(entryType);
security.MDEntryType = entryType;
}
FIX::MDEntryPx price;
if(group.isSet(price)) {
group.get(price);
security.MDEntryPx = price.getValue();
}
FIX::MDEntrySize size;
if(group.isSet(size)) {
group.get(size);
security.MDEntrySize = size.getValue();
}
FIX::MinQty qty;
if(group.isSet(qty)) {
group.get(qty);
security.MinQty = qty.getValue();
}
FIX::MinInc inc;
if(message.isSetField(inc)) {
message.getField(inc);
security.MinInc = inc.getValue();
}
FIX::MinBR br;
if(message.isSetField(br)) {
message.getField(br);
security.MinBR = br.getValue();
}
FIX::YTM ytm;
if(message.isSetField(ytm)) {
message.getField(ytm);
security.YTM = ytm.getValue();
}
FIX::YTW ytw;
if(message.isSetField(ytw)) {
message.getField(ytw);
security.YTW = ytw.getValue();
}
securities_[entryID] = security;
}
void Application::run()
{
while ( true )
{
try
{
char action = queryAction();
if ( action == '1' ){
fillSnapshot();
}
else if ( action == '2' )
break;
}
catch ( std::exception & e )
{
std::cout << "Message Not Sent: " << e.what();
}
}
}
void Application::fillSnapshot()
{
if (securities_.size()==0){
std::cout << "Empty snapshot, something is wrong when getting the market data from bonds.com! " <<std::endl;
return;
}
std::string file = getHomeFolder()+"\\bonds.com.snapshot.txt";
std::ofstream out(file.c_str());
std::map<std::string, SECURITY>::const_iterator it;
std::vector<SECURITY> securities;
out<<"bonds.com snapshot"<<std::endl;
out<<"cusip,entryid, updateaction, bid/ask, price, size, balance, increment, qty, ytm, ytw"<<std::endl;
for(it=securities_.begin(); it!=securities_.end(); it++){
securities.push_back(it->second);
}
std::sort(securities.begin(), securities.end());
std::vector<SECURITY>::const_iterator itsec;
for(itsec=securities.begin(); itsec!=securities.end(); itsec++){
out<<itsec->Symbol;
out<<","<<itsec->MDEntryID;
out<<","<<(itsec->MDUpdateAction=='0'?"New":"Update");
out<<","<<(itsec->MDEntryType=='0'?"Bid":"Ask");
out<<","<<itsec->MDEntryPx;
out<<","<<itsec->MDEntrySize;
out<<","<<itsec->MinBR;
out<<","<<itsec->MinInc;
out<<","<<itsec->MinQty;
out<<","<<itsec->YTM;
out<<","<<itsec->YTW<<std::endl;
}
std::cout << "The bonds.com snapshot has been filled to " << file.c_str() <<std::endl;
return;
}
Start the BondsProClient application:
int main( int argc, char** argv )
{
if ( argc != 2 )
{
std::cout << "usage: " << argv[ 0 ]
<< " FILE." << std::endl;
return 0;
}
std::string file = argv[ 1 ];
try
{
FIX::SessionSettings settings( file );
Application application;
FIX::FileStoreFactory storeFactory( settings );
FIX::FileLogFactory logFactory( settings );
FIX::SocketInitiator initiator( application, storeFactory, settings, logFactory);
initiator.start();
application.run();
initiator.stop();
return 0;
}
catch ( std::exception & e )
{
std::cout << e.what();
return 1;
}
}
Points of Interest
QuickFix is free and easy to use.
History
This is my first post on CodeProject. Hopefully I will accumulate it to a large amount.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.