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