Перейти к содержанию

Отправка и получение сообщений

Введение

Эта статья служит введением в использование FIX API для всех, кто заинтересован во взаимодействии с Spotware cServer с использованием FIX.

В этой статье мы используем пример на C#, чтобы подробно описать принципы того, как создать FIX-сообщение, отправить его на сервер и получить ответ. Этот пример ни в коем случае не является безупречным приложением и был сделан максимально простым, чтобы программисты могли легко понять концепцию использования сообщений FIX API.

Для установления и поддержания правильной связи с сервером и правильной обработки ответов требуется дополнительная функциональность, которая была пропущена ради простоты и ясности. Мы рассмотрим эти темы в будущих статьях.

Пример кода

Вы можете найти пример кода, обсуждаемый в этой статье, в нашем репозитории GitHub

Обзор коммуникации FIX

FIX-сообщение - это просто строка, состоящая из наборов числовых тегов и значений, разделенных вертикальной чертой (|). Каждый тег представляет собой различное поле, для которого допустим определенный набор значений. Ниже вы можете увидеть пример FIX-сообщения, которое запрашивает аутентификацию у сервера.

8=FIX.4.4|9=126|35=A|49=theBroker.12345|56=CSERVER|34=1|52=20170117- 08:03:04|57=TRADE|50=any_string|98=0|108=30|141=Y|553=12345|554=passw0rd!|10=131|

Как видите, повторяющийся шаблон, встречающийся в каждом FIX-сообщении:

Tag=Value|Tag=Value|Tag=Value|...

В зависимости от цели каждого сообщения, каждый раз требуется различный набор тегов и значений. Теги и значения, необходимые для каждого сообщения, подробно описаны в Правилах взаимодействия cTrader FIX engine (всегда проверяйте последние правила взаимодействия).

Аналогичным образом ответы отправляются обратно с сервера. Ниже вы можете увидеть ответ сервера на вышеуказанное сообщение.

8=FIX.4.4|9=106|35=A|34=1|49=CSERVER|50=TRADE|52=20170117- 08:03:04.509|56=theBroker.12345|57=any_string|98=0|108=30|141=Y|10=066|

Процесс взаимодействия с FIX-сервером включает следующие этапы:

  1. Создание FIX-сообщения

  2. Передача FIX-сообщения

  3. Получение FIX-сообщения

  4. Разбор FIX-сообщения

Необработанное FIX-сообщение имеет не очень удобочитаемый формат, поскольку оно было разработано с учетом эффективности, а не понятности. Поэтому для каждого программного приложения всегда будет существовать процесс преобразования предоставленной информации в соответствующее FIX-сообщение.

В нашем примере приложения на C# мы создали класс для обработки создания сообщений, а также функции для создания FIX-сообщений на основе соответствующей информации.

После создания сообщения передаются между сервером и клиентом через интернет с помощью сетевых сокетов. Когда сообщения получены, их необходимо разобрать для представления в удобочитаемом формате.

В этой статье мы рассмотрим процесс создания, передачи и получения ответа. Разбором мы займемся в следующей статье.

Создание FIX-сообщения

Структура сообщения

В нашем примере приложения мы создали класс, отвечающий за создание FIX-сообщений. Класс называется MessageConstructor и находится в проекте FIX API Library.

MessageConstructor инициализируется следующими параметрами:

  1. Host(*) — адрес, где расположен наш cServer.

  2. Username(*) — номер счета

  3. Password(*) — пароль

  4. SenderCompID(*) — предоставляется в форме FIX API cTrader. Имеет формат

  5. SenderSubID* — это вторая часть SenderCompID

  6. TargetCompID(*) — предоставляется в форме FIX API cTrader (обычно это cServer)

Эту информацию можно найти в вашей форме cTrader FIX API.

После инициализации MessageConstructor мы готовы создавать сообщения FIX API.

Все сообщения создаются аналогичным образом. Ниже приведен пример кода создания сообщения Logon.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public  string LogonMessage(SessionQualifier qualifier, int messageSequenceNumber,

            int  heartBeatSeconds,  bool  resetSeqNum)

       {

            var  body = new  StringBuilder ();

        //Defines a message encryption scheme.Currently, only transportlevel security

       //is supported.Valid value is "0"(zero) = NONE_OTHER (encryption is not used).

           body.Append( "98=0|");

            //Heartbeat interval in seconds. 

           //Value is set in the 'config.properties' file (client side) as 

         // 'SERVER.POLLING.INTERVAL' . 

           //30 seconds is default interval value. If HeartBtInt is set to 0, 

         no heartbeat message  

           //is required.

           body.Append( "108="  + heartBeatSeconds +  "|");

           // All sides of FIX session should have

           //sequence numbers reset. Valid value

            //is "Y" = Yes(reset). 

            if  (resetSeqNum)

               body.Append( "141=Y|");

            //The numeric User ID.  User is linked to SenderCompID (#49) value (the

            //user's organization). 

           body.Append( "553=" + _username + "|");

           //User Password

           body.Append( "554=" + _password + "|");

            var  header = ConstructHeader(qualifier, 

         SessionMessageCode( SessionMessageType.Logon), messageSequenceNumber,

          body.ToString());

            var  headerAndBody = header + body;            

            var  trailer = ConstructTrailer(headerAndBody);

            var  headerAndMessageAndTrailer = header + body + trailer;

            return  headerAndMessageAndTrailer.Replace("|", "\u0001");

       }

Вы можете видеть, что сначала мы создаем часть тела, затем передаем ее в функцию заголовка и, наконец, передаем обе части в функцию завершения. Эти 3 части подробно описаны ниже.

Процесс создания сообщения — это просто добавление необходимых тегов, значений и разделителей в строку.

Тело

Сначала мы опишем построение тела сообщения, поскольку тело сообщения необходимо создать в первую очередь. Выше можно увидеть пример (создание сообщения Logon).

Мы начинаем с инициализации класса StringBuilder и добавляем теги один за другим на основе входных данных функции. В зависимости от типа сообщения тело должно состоять из различных наборов тегов, некоторые из которых являются обязательными, а другие — необязательными.

Структуру каждого сообщения можно найти в нашем документе Rules of Engagement (/FIX).

Затем мы создаем заголовок для сообщения Logon и добавляем к нему тело сообщения. Наконец, используя строку headerAndBody, мы генерируем трейлер. Далее мы рассмотрим, как создать заголовок и трейлер.

Заголовок

Заголовок — это первая часть сообщения FIX, которая состоит из следующих полей (одинаковых для всех сообщений):

  1. BeginString — начальная строка определяет версию протокола FIX и в нашем случае фиксирована как FIX4.4.
  2. BodyLength — длина тела указывает длину сообщения в символах, исключая поля BeginString, BodyLength и трейлер.
  3. MsgType — в этом поле мы определяем тип сообщения, чтобы получатель знал, как разобрать тело.
  4. SenderCompID — здесь мы устанавливаем SenderCompID.
  5. TargetCompID — это получатель нашего сообщения. В нашем случае это всегда будет cServer.
  6. SenderSubID — логин трейдера.
  7. MsgSeqNum — это порядковый номер сообщения. Он должен увеличиваться для каждого сообщения, отправленного в рамках одной сессии.
  8. Sending Time — время передачи сообщения.

Ниже можно увидеть функцию ConstructHeader, отвечающую за построение заголовков.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
private  string ConstructHeader(SessionQualifier qualifier, string type,

         int  messageSequenceNumber, string bodyMessage)

    {

         var  header = new  StringBuilder ();

        // Protocol version. FIX.4.4 (Always unencrypted, must be first field 

        // in message.

        header.Append( "8=FIX.4.4|");

         var  message = new  StringBuilder ();

        // Message type. Always unencrypted, must be third field in message.

        message.Append( "35=" + type + "|");

        // ID of the trading party in following format: <BrokerUID>.<Trader Login> 

        // where BrokerUID is provided by cTrader and Trader Login is numeric 

        // identifier of the trader account.

        message.Append( "49="  + _senderCompID +  "|");  

        // Message target. Valid value is "CSERVER"

        message.Append( "56="  + _targetCompID +  "|");  

        // Additional session qualifier. Possible values are: "QUOTE", "TRADE".

        message.Append( "57="  + qualifier.ToString() +  "|");  

        // Assigned value used to identify specific message originator.

        message.Append( "50="  + _senderSubID +  "|");

        // Message Sequence Number

        message.Append( "34="  + messageSequenceNumber +  "|");

         // Time of message transmission (always expressed in UTC(Universal Time  

        // Coordinated, also known as 'GMT').

        message.Append("52=" + DateTime.UtcNow.ToString("yyyyMMdd-HH:mm:ss") + "|");

         var  length = message.Length + bodyMessage.Length;

        // Message body length. Always unencrypted, must be second field in message.

        header.Append( "9=" + length + "|");

        header.Append(message);      

         return  header.ToString();

    }

Трейлер

Трейлер — это просто тег, содержащий контрольную сумму остальной части сообщения.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private  string ConstructTrailer(string message)

       {

            //Three byte, simple checksum.  Always last field in message; i.e. serves,

           //with the trailing<SOH>, 

           //as the end - of - message delimiter. Always defined as three characters

           //(and always unencrypted).

            var  trailer = "10=" + CalculateChecksum(message.Replace("|", "\u0001").ToString()).ToString().PadLeft(3, '0') + "|";

            return  trailer;

       }

Системные сообщения

Наш пример содержит функции, которые возвращают следующие системные сообщения:

  • Heartbeat: MessageConstructor.HeartbeatMessage()
  • Test request: MessageConstructor.TestRequestMessage()
  • Logon: MessageConstructor.LogonMessage()
  • Logout: MessageConstructor.LogoutMessage()
  • Resend request: MessageConstructor.ResendMessage()
  • Reject: MessageConstructor.RejectMessage()
  • Sequence reset: MessageConstructor.SequenceResetMessage()

Прикладные сообщения

Наш пример содержит функции, которые возвращают следующие системные сообщения:

  • Market data request: MessageConstructor.HeartbeatMessage()
  • Market data snapshot/full refresh: MessageConstructor.MarketDataSnapshotMessage()
  • Market data incremental refresh: MessageConstructor.MarketDataIncrementalRefreshMessage()
  • New order single: MessageConstructor.NewOrderSingleMessage()
  • Order status request: MessageConstructor.OrderStatusRequest()
  • Execution report: MessageConstructor.ExecutionReport()
  • Business message reject: MessageConstructor.BusinessMessageReject()
  • Request for positions: MessageConstructor.RequestForPositions()
  • Position report: MessageConstructor.PositionReport()

Отправить сообщение и получить ответ

Чтобы отправить сообщение FIX на cServer, сначала необходимо установить соединение с сервером. Это можно сделать, создав TcpClient. В нашем случае мы создаем два клиента, поскольку сообщения с котировками цен и торговые сообщения обрабатываются разными портами на сервере.

Затем нужно получить два потока, по которым будут отправляться сообщения. Этот процесс происходит в конструкторе формы, как показано ниже:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public  frmFIXAPISample()
{

   InitializeComponent();

    _priceClient = new  TcpClient( _host, _pricePort);           

   _priceStream = _ priceClient.GetStream ();            

   _tradeClient =  new  TcpClient ( _host, _tradePort);

   _tradeStream = _ tradeClient.GetStream ();

   _messageConstructor = new  MessageConstructor( _host, _username,

       _password, _senderCompID, _senderSubID, _targetCompID);

}

В конструкторе мы также инициализируем класс MessageConstructor, который будет использоваться для генерации сообщений.

Далее для отправки сообщений мы создали две разные функции: SendPriceMessage() и SendTradeMessage(). Каждая принимает сообщение FIX в качестве входных данных, а затем вызывает функцию SendMessage() с сообщением и соответствующим потоком в качестве входных данных.

Функция SendMessage() работает следующим образом:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private  string SendMessage(string message,  NetworkStream  stream)
{
    var  byteArray =  Encoding.ASCII.GetBytes (message);

    stream.Write(byteArray, 0, byteArray.Length);

    var  buffer = new byte[1024];

    int  i = 0;

    while  (!stream.DataAvailable && i < 100)
    {
         Thread.Sleep ( 100);
         i++;
    }

    if( stream.DataAvailable )
        stream.Read(buffer, 0, 1024);

    _messageSequenceNumber++;

    var  returnMessage = Encoding.ASCII.GetString(buffer);

    return  returnMessage;
}

Подробные шаги следующие:

  1. Кодирование сообщения в массив байтов.
  2. Запись массива байтов в поток.
  3. Чтение ответа из потока.
  4. Увеличение порядкового номера сообщения.
  5. Кодирование сообщения в строку.

Функция должна возвращать сообщение FIX, отправленное сервером.

Как можно предположить, нельзя показывать пользователю необработанное сообщение FIX, поэтому необходимо разработать дополнительный шаг разбора входящего сообщения.

Заключение

Это приложение представляет собой краткую демонстрацию того, как взаимодействовать с cServer с помощью сообщений FIX. Это всего лишь пример, иллюстрирующий концепции протокола FIX, и ни в коем случае не является полноценным движком FIX. Если вы хотите избежать создания собственного движка FIX, можно рассмотреть возможность использования одного из доступных сторонних движков FIX.

Примечание

Эта статья актуальна по состоянию на 03.02.2017 и разработана с учетом движка cTrader FIX, Rules of Engagement v2.9.1.