Click here to Skip to main content
15,881,852 members
Articles / Programming Languages / C#

Fix Protocol C# Fix Engine Source Code

Rate me:
Please Sign up or sign in to vote.
3.00/5 (15 votes)
16 Apr 2009CPOL3 min read 98.3K   2.5K   29   28
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

C++
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.19=" 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=ME56=ICAP_AI_Server34=");
        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.19=");
        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.19=8435=A49=ME56=ICAP_AI_Server34=1
        // 52=20090402-12:57:2998=0108=11137=710=153
        FixSendMessage("A", "98=0108=11137=7");
    }

www.willyhoops.com

License

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



Comments and Discussions

 
QuestionFix Message Implementation using QuickFix Pin
Member 1156015816-Nov-17 18:52
Member 1156015816-Nov-17 18:52 
QuestionRating Limitations Pin
Tomaž Štih3-Nov-15 4:18
Tomaž Štih3-Nov-15 4:18 
AnswerRe: Rating Limitations Pin
AcrobatX23-Nov-15 9:19
AcrobatX23-Nov-15 9:19 
GeneralGreat job Pin
David Jiboye31-Jul-13 4:46
David Jiboye31-Jul-13 4:46 
QuestionWhat Fix version is it done for? FIXT.1.1? Pin
darch229-Jun-13 9:58
darch229-Jun-13 9:58 
QuestionVery very awesome! Pin
Member 199004718-Apr-13 16:59
Member 199004718-Apr-13 16:59 
GeneralMy vote of 5 Pin
qtonchr17-Apr-11 22:32
qtonchr17-Apr-11 22:32 
GeneralMy vote of 3 Pin
matthias Weiser22-Sep-10 1:20
matthias Weiser22-Sep-10 1:20 
General[My vote of 1] this is very slowly looking codes. Pin
vishram sharma15-Aug-10 4:14
vishram sharma15-Aug-10 4:14 
GeneralMessage Closed Pin
15-Aug-10 18:37
w.hooper15-Aug-10 18:37 
GeneralRe: [My vote of 1] this is very slowly looking codes. Pin
vishram sharma15-Aug-10 21:19
vishram sharma15-Aug-10 21:19 
GeneralMessage Closed Pin
16-Aug-10 22:53
w.hooper16-Aug-10 22:53 
GeneralRe: [My vote of 1] this is very slowly looking codes. PinPopular
vishram sharma19-Aug-10 2:49
vishram sharma19-Aug-10 2:49 
GeneralMessage Closed Pin
12-Jul-12 5:53
DevAlgoTrade12-Jul-12 5:53 
GeneralMy vote of 5 Pin
FXMC10-Aug-10 8:23
FXMC10-Aug-10 8:23 
GeneralGreat stuff. Pin
FXMC10-Aug-10 8:23
FXMC10-Aug-10 8:23 
GeneralMy vote of 1 Pin
Member 1841428-Jul-10 5:34
Member 1841428-Jul-10 5:34 
GeneralGood starting point Pin
Member 41797953-Jun-10 4:11
Member 41797953-Jun-10 4:11 
Generalfix server Pin
bachebahal_13638-Dec-09 0:43
bachebahal_13638-Dec-09 0:43 
GeneralWas helpful Pin
ritzy pal29-Sep-09 21:17
ritzy pal29-Sep-09 21:17 
GeneralTCPIP Reconstruction Pin
prgtrdr20-Jul-09 1:29
prgtrdr20-Jul-09 1:29 
GeneralGood Job Pin
black198130-May-09 3:06
black198130-May-09 3:06 
GeneralMy vote of 2 Pin
ZTransform16-Apr-09 2:42
ZTransform16-Apr-09 2:42 
GeneralRe: My vote of 2 Pin
w.hooper20-Apr-09 0:43
w.hooper20-Apr-09 0:43 
Generalunsafe code Pin
xliqz14-Apr-09 7:42
xliqz14-Apr-09 7:42 
ReadKey will cause unhandled exception with erroneus/corrupted fix information.

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.