Click here to Skip to main content
Click here to Skip to main content

Fix Protocol C# Fix Engine Source Code

, 16 Apr 2009
Rate this:
Please Sign up or sign in to vote.
Fix Protocol C# Fix Engine Source Code

Introduction

I needed some C# fix protocol engine code and was disappointed that there is almost nothing on the internet, so here is a small project in VS 2005. It actually connects to EBS AI v4 fix which is a major foreign exchange trading platform. Fix servers are usually huge projects but there is a simple way to cut them down to nothing - do not support message retransmission. In fact EBS, along with many others, does not support message retransmission either because it is pointless for today's 100% reliable socket communication. Using this technique, you can avoid paying for a fix engine or fighting with QuickFix and it will be much faster as well. This code is for connecting to a big reliable platform, not for connecting to customers who might send you junk.

Points of Interest

Some people process fix messages by splitting the received bytes into a vector of string pairs or even a vector of maps - but this is pointlessly slow! Instead I pass simply the start and end location of the fix message in the buffer and provide a few functions such as ReadKey(), ReadStringValue(), ReadIntegerValue(), SkipValue(), etc. The ProcessMessage code can then more quickly process the fields as desired. Usually the order of the data is constant, but if it is not use a loop and case. Repeating blocks will always have some field marking the start of a new block. When I am reading the integer key, I use a loop that subtracts 48 from the char value and multiples by 10 etc. rather than just calling convert to string and Convert.Int32(..). In other words, I have taken care to keep it all mostly as fast as possible.

Normally a class like this would have an interface with various events but again this is just sample code so I am not including this stuff. I am using a synchronous or blocking socket read instead of asynchronous events. This is slightly faster for a single connection example like this which only consumes one thread. With debug on, I log to a simple StreamWriter. Of course any logging should actually take place in a proper logging class with a queue and intermittent writing thread (always to the local drive, keep network traffic to a minimum), but I have not bothered including code for that in here. Note you don't need to waste time logging fix except in debug mode - in the event of a problem you can always get the monster log file from your vendor anyway.

Processing the fix messages in this code takes just a few microseconds but how fast are sockets in .NET? I wrote some test code that sends messages between a server and a client server running on the same machine in C#, C++ and Linux C++. I used RTDSC to time the server send to client receive interval. For messages up to about 1.5k bytes in size, I got a pretty steady time of 70 microseconds in C#. In Windows C++, this came down to 45 microseconds. Rewriting all this in Linux C++ gave 25 microseconds. This performance hit compared to Linux C++ is probably OK for most people.

Some Code Examples

private byte[] _buffer_data = new byte[655360]; // buffer
private int _buffer_end=0; // this is the number of bytes of data in the buffer 
private int _buffer_start=0; // this is posn of current message

private void ReadData()
    {
        while (true)
        {
            // we are using a blocking read.. so at this point 
	   // we will always be exactly at the start of a new fix message
            // read at least enough data to get the message length
            ReadSocket(25);
            // Stre the location of the start of the fix message in 
	   // fix_message_buffer_start
            int fix_message_buffer_start = _buffer_start; 
            // message starts "8=FIXT.1.1/9=" which has length 13
            if (_buffer_data[_buffer_start] != '8' || 
			_buffer_data[_buffer_start+1] != '=') 
                ErrorHandler("message did not start with 8=", 
			fix_message_buffer_start, 25);
            _buffer_start+=13;
            // read the message length
            int length = ReadIntValue(ref _buffer_start);
            // Store the total length of the message in fix_message_buffer_length
            int fix_message_buffer_length = _buffer_start - 
				fix_message_buffer_start + length + 7;
            // we are only sure we have read the first 25 bytes of the message - 
	   // now read the rest
            ReadSocket(fix_message_buffer_length-25);
            // now parse the message
            int end = _buffer_start + length - 1; // _buffer_data[end] is the 01 
			// end field terminator just before the checksum starts
            // Message must start with 35=
            if (_buffer_data[_buffer_start] != 51 || 
		_buffer_data[++_buffer_start] != 53 || 
		_buffer_data[++_buffer_start] != 61)
                ErrorHandler("35= Was not at the start of the message", 
		fix_message_buffer_start, fix_message_buffer_length);
            _buffer_start++;
            string fix_message_id = ReadStringValue(ref _buffer_start);
            // Now skip the next four fields which are SenderCompId 
	   // and MsgSeqNum and TargetCompId and SendingTime
            SkipValue(ref _buffer_start);
            SkipValue(ref _buffer_start);
            SkipValue(ref _buffer_start);
            SkipValue(ref _buffer_start);
            // Process the fix message
            ProcessFixMessage(fix_message_id, fix_message_buffer_start, 
			fix_message_buffer_length, _buffer_start, end);
            // Move to end of message
            _buffer_start = fix_message_buffer_start + fix_message_buffer_length;
            // Clean the buffer out if there are no more messages inside it 
	   // (normally the case)
            if (_buffer_start == _buffer_end) _buffer_start = _buffer_end = 0;
        }
    }

    private int ReadKey(ref int posn)
    {
        int val=_buffer_data[posn] - 48;
        while (_buffer_data[++posn] != 61) val = val * 10 + _buffer_data[posn] - 48;
        posn++;
        return val;
    }

    private string ReadStringValue(ref int posn)
    { // we assume the value is at least one character long
        string val;
        int posn_temp=posn;
        while (_buffer_data[++posn] != 1) {  }
        val = Encoding.ASCII.GetString(_buffer_data, posn_temp, posn - posn_temp );
        posn++;
        return val;
    }

    private void ReadSocket(int numbytes) 	// Block until we have at least 
				     	// numbytes of data in the buffer
    {
        int bytes_available = _buffer_end - _buffer_start;
        while (bytes_available < numbytes)
        {
            try
            {
                int size = _socket_networkstream.Read
			(_buffer_data, _buffer_end, 655360 - _buffer_end);
                if (size==0) ErrorHandler("Socket Dead",0,0);
                _buffer_end += size;
                // record the time now - this is the time before message processing -
                //  if the message arrived in parts this is the 
	       // first time all the data we need is in the buffer            
	   }
            catch
            {
                ErrorHandler("Socket Dead",0,0);
            }
            bytes_available = _buffer_end - _buffer_start;
        }
    }

    private void ProcessFixMessage(string message_id, 
	int message_buffer_start, int message_buffer_length, int start, int end)
    { 
        if (message_id == "AE") // Trade Report or Deal mesage
        { // this message does not come in a reliable order - 
	 // use a case to extract the values we need
            Console.WriteLine("Trade Report");
            string sOrderId = null; // 37 This is our order id
            string sMatchStatus = null; 	// 573 - "Z" for Pending, "2" 
					// for Confirmed, "0" for Done
            string sSymbol = null; // 55
            int iLastQty = 0; // 32
            string sLastPx = null; // 31
            string sSide = null; // 54

            while (start < end)
            {
                int key = ReadKey(ref start);
                switch (key)
                {
                    case 573:
                        sMatchStatus = ReadStringValue(ref start);
                        break;
                    case 55:
                        sSymbol = ReadStringValue(ref start);
                        break;
                    case 32:
                        iLastQty = ReadIntValue(ref start)/1000000;
                        break;
                    case 31:
                        sLastPx = ReadStringValue(ref start);
                        break;
                    case 54:
                        sSide = ReadStringValue(ref start);
                        break;
                    case 37:
                        sOrderId = ReadStringValue(ref start);
                        break;
                    default:
                        SkipValue(ref start);
                        break;
                }
            }

            _fixlog.WriteLine("TRADE_MESSAGE: MatchStatus=" + sMatchStatus + "," + 
                              "Order ID="+sOrderId + "," +
                              "Side=" + sSide + "," +
                                sSymbol + "," + sLastPx + "," + iLastQty );
            
            return;
        }

        if (message_id == "X") // MarketUpdateEventMsg
        {
           etc etc...

    private void FixSendMessage(string type, string body)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("35=");
        sb.Append(type);
        sb.Append("/49=ME/56=ICAP_AI_Server/34=");
        sb.Append((++_message_seq_num).ToString());
        sb.Append("/52=");
        sb.Append(DateTime.UtcNow.ToString("yyyyMMdd-HH:mm:ss"));
        sb.Append('/');
        sb.Append(body);
        string t = sb.ToString();
        sb.Length = 0;
        sb.Append("8=FIXT.1.1/9=");
        sb.Append((t.Length).ToString());
        sb.Append("/");
        sb.Append(t);
        t = sb.ToString();
        int sum = 0;
        int len = t.Length;
        for (int i = 0; i < len; i++) sum += Convert.ToChar(t.Substring(i, 1));
        sum = sum % 256;
        sb.Append("10=");
        sb.Append(sum.ToString("000"));
        sb.Append('/');
        byte[] buffer = Encoding.ASCII.GetBytes(sb.ToString());
        _socket_networkstream.Write(buffer, 0, buffer.Length);
    }

    private void SendLogon1()
    {
        // 8=FIXT.1.1/9=84/35=A/49=ME/56=ICAP_AI_Server/34=1/
        // 52=20090402-12:57:29/98=0/108=1/1137=7/10=153/
        FixSendMessage("A", "98=0/108=1/1137=7/");
    }

www.willyhoops.com

License

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

About the Author

w.hooper

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralGreat job PinmemberDavid Jiboye31-Jul-13 4:46 
QuestionWhat Fix version is it done for? FIXT.1.1? Pinmemberdarch229-Jun-13 9:58 
QuestionVery very awesome! PinmemberMember 199004718-Apr-13 16:59 
GeneralMy vote of 5 Pinmemberqtonchr17-Apr-11 22:32 
GeneralMy vote of 3 Pinmembermatthias Weiser22-Sep-10 1:20 
General[My vote of 1] this is very slowly looking codes. Pinmembervishram sharma15-Aug-10 4:14 
GeneralRe: [My vote of 1] this is very slowly looking codes. Pinmemberw.hooper15-Aug-10 18:37 
GeneralRe: [My vote of 1] this is very slowly looking codes. Pinmembervishram sharma15-Aug-10 21:19 
GeneralRe: [My vote of 1] this is very slowly looking codes. Pinmemberw.hooper16-Aug-10 22:53 
GeneralRe: [My vote of 1] this is very slowly looking codes. Pinmembervishram sharma19-Aug-10 2:49 
GeneralRe: [My vote of 1] this is very slowly looking codes. PinmemberDevAlgoTrade12-Jul-12 5:53 
GeneralMy vote of 5 PinmemberFXMC10-Aug-10 8:23 
GeneralGreat stuff. PinmemberFXMC10-Aug-10 8:23 
GeneralMy vote of 1 PinmemberMember 1841428-Jul-10 5:34 
GeneralGood starting point PinmemberMember 41797953-Jun-10 4:11 
Generalfix server Pinmemberbachebahal_13638-Dec-09 0:43 
GeneralWas helpful Pinmemberritzy pal29-Sep-09 21:17 
GeneralTCPIP Reconstruction Pinmemberprgtrdr20-Jul-09 1:29 
GeneralGood Job Pinmemberblack198130-May-09 3:06 
GeneralMy vote of 2 PinmemberZTransform16-Apr-09 2:42 
GeneralRe: My vote of 2 Pinmemberw.hooper20-Apr-09 0:43 
Generalunsafe code Pinmemberxliqz14-Apr-09 7:42 
GeneralRe: unsafe code Pinmemberwilliammorrishooper14-Apr-09 7:58 
GeneralMy vote of 1 PinmvpDave Kreskowiak14-Apr-09 4:46 
GeneralRe: My vote of 1 Pinmemberwilliammorrishooper14-Apr-09 6:42 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 16 Apr 2009
Article Copyright 2009 by w.hooper
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid