Click here to Skip to main content
15,867,921 members
Articles / Programming Languages / C++

The FIX client and server implementation using QuickFix

Rate me:
Please Sign up or sign in to vote.
4.30/5 (6 votes)
26 Jul 2012CPOL3 min read 69K   5.2K   15   4
The FIX client and server implementation using QuickFix.

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:

C++
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() //send the X messages to all active sessions.
{
    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');//0=new,1=update,2=delete
    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');//0=bid, 1=offer
    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

C++
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();
    //wait();
    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

  1. Create a SessionSettings object with config file.
  2. Create an object of the Application class.
  3. Create an object of the SocketAcceptor class by passing SessionSettings.
  4. 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:

C++
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& )
{
    //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 )
{
  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
    //add custom fields to the MarketDataIncrementalRefresh message
    {
    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();//0=New, 1=Update, 2=Delete)
    if (actionvalue=='2') //ignore the delete
    {
        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:

C++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSource Code Pin
Mohit Singla8-Jul-21 3:11
Mohit Singla8-Jul-21 3:11 
QuestionHelp with small proyect Pin
Member 1053289518-Jan-14 10:02
Member 1053289518-Jan-14 10:02 
QuestionNot clear on why NoMDEntriesBondsPro is required? Pin
arunnair.w2w13-Nov-12 20:41
arunnair.w2w13-Nov-12 20:41 
QuestionLook more a Code Dump Pin
ThatsAlok30-Jul-12 2:11
ThatsAlok30-Jul-12 2:11 
please explain how everything is working? though idea is quite good. if it include explanation, they it deserve 5points+

"Opinions are neither right nor wrong. I cannot change your opinion. I can, however, change what influences your opinion." - David Crow
Never mind - my own stupidity is the source of every "problem" - Mixture


cheers,
Alok Gupta
VC Forum Q&A :- I/IV
Support CRY- Child Relief and You

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.