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]; private int _buffer_end=0; private int _buffer_start=0;
private void ReadData()
{
while (true)
{
ReadSocket(25);
int fix_message_buffer_start = _buffer_start;
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;
int length = ReadIntValue(ref _buffer_start);
int fix_message_buffer_length = _buffer_start -
fix_message_buffer_start + length + 7;
ReadSocket(fix_message_buffer_length-25);
int end = _buffer_start + length - 1; 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);
SkipValue(ref _buffer_start);
SkipValue(ref _buffer_start);
SkipValue(ref _buffer_start);
SkipValue(ref _buffer_start);
ProcessFixMessage(fix_message_id, fix_message_buffer_start,
fix_message_buffer_length, _buffer_start, end);
_buffer_start = fix_message_buffer_start + fix_message_buffer_length;
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)
{ 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) {
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;
}
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") { Console.WriteLine("Trade Report");
string sOrderId = null; string sMatchStatus = null; string sSymbol = null; int iLastQty = 0; string sLastPx = null; string sSide = null;
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") {
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()
{
FixSendMessage("A", "98=0/108=1/1137=7/");
}
www.willyhoops.com