跳转至

发送和接收消息

介绍

本文作为使用FIX API与Spotware cServer交互的入门指南,适用于所有对使用FIX与cServer交互感兴趣的人。

在本文中,我们将使用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引擎交互规则中有详细描述(请务必查看最新的交互规则)。

同样,服务器也会以类似的方式发送回响应。下面您可以看到服务器对上述消息的响应。

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(*) – 在cTrader的FIX API表单中提供。格式为

  5. SenderSubID* – 是SenderCompID的第二部分

  6. TargetCompID(*) – 在cTrader的FIX API表单中提供(通常是cServer)

您可以在cTrader FIX API表单中找到这些信息。

初始化MessageConstructor后,我们就可以构建FIX API消息了。

所有消息的构建方式都类似。下面是构建登录消息的代码示例。

 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");

       }

您可以看到,我们首先构建主体部分,然后将其传递给头部函数,最后将两部分都传递给尾部函数。这三个部分将在下面详细介绍。

消息构建过程只是将所需的标签、值和分隔符添加到字符串中。

主体

我们首先从描述主体构建开始,因为需要先创建消息的主体。我们可以在上面看到一个示例(创建登录消息)。

我们首先初始化一个StringBuilder类,然后根据函数输入逐个附加标签。根据消息类型,主体必须由不同的标签集组成,其中一些是必需的,另一些是可选的。

您可以在我们的交互规则(/FIX)中找到每条消息的结构。

然后我们为登录消息创建一个头部,并将消息的主体附加到其中。最后,使用headerAndBody字符串生成尾部。接下来,我们将看到如何构建头部和尾部。

头部

头部是FIX消息的第一部分,由以下字段组成(所有消息都相同):

  1. BeginString – 开始字符串定义FIX协议版本,在我们的情况下固定为FIX4.4。
  2. BodyLength – 主体长度表示消息的字符长度,不包括BeginStringBodyLength和尾部字段。
  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;

       }

系统消息

我们的示例包含返回以下系统消息的函数:

  • 心跳: MessageConstructor.HeartbeatMessage()
  • 测试请求: MessageConstructor.TestRequestMessage()
  • 登录: MessageConstructor.LogonMessage()
  • 登出: MessageConstructor.LogoutMessage()
  • 重发请求: MessageConstructor.ResendMessage()
  • 拒绝: MessageConstructor.RejectMessage()
  • 序列重置: MessageConstructor.SequenceResetMessage()

应用程序消息

我们的示例包含返回以下系统消息的函数:

  • 市场数据请求: MessageConstructor.HeartbeatMessage()
  • 市场数据快照/完整刷新: MessageConstructor.MarketDataSnapshotMessage()
  • 市场数据增量刷新: MessageConstructor.MarketDataIncrementalRefreshMessage()
  • 新单: MessageConstructor.NewOrderSingleMessage()
  • 订单状态请求: MessageConstructor.OrderStatusRequest()
  • 执行报告: MessageConstructor.ExecutionReport()
  • 业务消息拒绝: MessageConstructor.BusinessMessageReject()
  • 持仓请求: MessageConstructor.RequestForPositions()
  • 持仓报告: MessageConstructor.PositionReport()

发送消息并接收响应

为了向cServer发送FIX消息,您首先需要与服务器建立连接。您可以通过创建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消息,因此还应开发一个额外的步骤来解析传入的消息。

结论

这个应用程序简要演示了如何使用FIX消息与cServer通信。它只是一个说明FIX协议概念的示例,绝不是一个完整的FIX引擎。如果您想避免构建自己的FIX引擎,可以考虑使用可用的第三方FIX引擎之一。

注意

本文截至2017年2月3日是最新的,并考虑了cTrader FIX引擎交互规则v2.9.1。