Zum Inhalt

Nachrichten senden und empfangen

Einführung

Dieser Artikel dient als Einführung in die Verwendung von FIX API für alle, die an der Interaktion mit Spotware cServer über FIX interessiert sind.

In diesem Artikel werden wir anhand eines C#-Beispiels detailliert die Prinzipien beschreiben, wie man eine FIX-Nachricht konstruiert, an den Server sendet und die Antwort empfängt. Dieses Beispiel ist keineswegs eine fehlerfreie Anwendung und wurde so einfach wie möglich gehalten, damit Programmierer das Konzept der Verwendung von FIX API-Nachrichten leicht verstehen können.

Um eine ordnungsgemäße Kommunikation mit dem Server und eine korrekte Behandlung der Antworten zu gewährleisten und aufrechtzuerhalten, sind zusätzliche Funktionen erforderlich, die der Einfachheit und Klarheit halber übersprungen wurden. Wir werden uns in zukünftigen Artikeln mit diesen Themen befassen.

Codebeispiel

Sie finden das in diesem Artikel besprochene Codebeispiel in unserem GitHub-Repository

Überblick über die FIX-Kommunikation

Eine FIX-Nachricht ist nur eine Zeichenfolge, die aus Sätzen von numerischen Tags und Werten besteht, die durch einen senkrechten Strich (|) getrennt sind. Jeder Tag repräsentiert ein anderes Feld, für das eine bestimmte Menge von Werten zulässig ist. Unten sehen Sie eine Beispiel-FIX-Nachricht, die eine Authentifizierung vom Server anfordert.

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|

Wie Sie sehen können, ist das sich wiederholende Muster, das in jeder FIX-Nachricht zu finden ist:

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

Je nach Zweck jeder Nachricht ist jedes Mal ein anderer Satz von Tags und Werten erforderlich. Die für jede Nachricht erforderlichen Tags und Werte sind in den Einsatzregeln der cTrader FIX-Engine detailliert beschrieben (überprüfen Sie immer die neuesten Einsatzregeln).

In ähnlicher Weise werden Antworten vom Server zurückgesendet. Unten sehen Sie die Antwort des Servers auf die obige Nachricht.

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|

Die Schritte, die am Prozess der Kommunikation mit einem FIX-Server beteiligt sind, sind die folgenden:

  1. Erstellen einer FIX-Nachricht

  2. Übertragen einer FIX-Nachricht

  3. Empfangen einer FIX-Nachricht

  4. Parsen einer FIX-Nachricht

Eine rohe FIX-Nachricht ist kein sehr lesbares Format, da sie eher auf Effizienz als auf Verständlichkeit ausgelegt wurde. Daher wird es für jede Softwareanwendung immer einen Prozess der Übersetzung der bereitgestellten Informationen in die jeweilige FIX-Nachricht geben.

In unserer C#-Beispielanwendung haben wir eine Klasse für die Handhabung der Nachrichtenerstellung sowie Funktionen zum Erstellen von FIX-Nachrichten basierend auf den relevanten Informationen erstellt.

Nachdem die Nachrichten erstellt wurden, werden sie zwischen einem Server und einem Client über das Internet durch Netzwerk-Sockets übertragen. Wenn die Nachrichten empfangen werden, müssen sie geparst werden, um in einem lesbaren Format dargestellt zu werden.

In diesem Artikel werden wir den Prozess der Erstellung, Übertragung und des Empfangs der Antwort behandeln. Das Parsen werden wir in einem zukünftigen Artikel behandeln.

Erstellen einer FIX-Nachricht

Nachrichtenstruktur

In unserer Beispielanwendung haben wir eine Klasse erstellt, die für die Erstellung von FIX-Nachrichten verantwortlich ist. Die Klasse heißt MessageConstructor und befindet sich im FIX API Library-Projekt.

Der MessageConstructor wird mit den folgenden Parametern initialisiert:

  1. Host(*) – die Adresse, an der sich unser cServer befindet.

  2. Username(*) – die Kontonummer

  3. Password(*) – das Passwort

  4. SenderCompID(*) – wird im FIX API-Formular von cTrader bereitgestellt. Es hat das Format

  5. SenderSubID* – ist der zweite Teil von SenderCompID

  6. TargetCompID(*) – wird im FIX API-Formular von cTrader bereitgestellt (normalerweise ist es cServer)

Sie finden diese Informationen in Ihrem cTrader FIX API-Formular.

Nachdem wir einen MessageConstructor initialisiert haben, sind wir bereit, FIX API-Nachrichten zu erstellen.

Alle Nachrichten werden auf ähnliche Weise erstellt. Unten finden Sie ein Codebeispiel für die Erstellung einer Logon-Nachricht.

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

       }

Sie können sehen, dass wir zuerst den Textteil erstellen, ihn dann an die Header-Funktion übergeben und schließlich beide Teile an die Trailer-Funktion übergeben. Diese 3 Teile werden unten detailliert beschrieben.

Der Nachrichtenerstellungsprozess ist lediglich das Hinzufügen der erforderlichen Tags, Werte und Trennzeichen zu einer Zeichenfolge.

Text

Zunächst beschreiben wir die Konstruktion des Bodys, da der Body der Nachricht zuerst erstellt werden muss. Wir können oben ein Beispiel sehen (Erstellen der Logon-Nachricht).

Wir beginnen mit der Initialisierung einer StringBuilder-Klasse und fügen die Tags einzeln basierend auf den Funktionseingaben hinzu. Basierend auf dem Nachrichtentyp muss der Body aus verschiedenen Sätzen von Tags bestehen, von denen einige obligatorisch und andere optional sind.

Sie finden die Struktur jeder Nachricht in unseren Rules of Engagement (/FIX).

Dann erstellen wir einen Header für eine Logon-Nachricht und fügen ihm den Body der Nachricht hinzu. Schließlich generieren wir mit der headerAndBody-Zeichenfolge den Trailer. Im Folgenden werden wir sehen, wie wir einen Header und einen Trailer konstruieren.

Der Header ist der erste Teil der FIX-Nachricht und besteht aus den folgenden Feldern (bei allen Nachrichten gleich):

  1. BeginString – die Begin-Zeichenfolge definiert die FIX-Protokollversion und ist in unserem Fall auf FIX4.4 festgelegt.
  2. BodyLength – die Body-Länge gibt die Länge der Nachricht in Zeichen an, ausgenommen die Felder BeginString, BodyLength und Trailer.
  3. MsgType – in diesem Feld definieren wir den Nachrichtentyp, damit der Empfänger weiß, wie der Body zu parsen ist.
  4. SenderCompID – hier legen wir die SenderCompID fest.
  5. TargetCompID – dies ist das Ziel unserer Nachricht. In unserem Fall wird dies immer cServer sein.
  6. SenderSubID – das Händler-Login.
  7. MsgSeqNum – dies ist die Sequenznummer der Nachricht. Sie muss für jede in derselben Sitzung gesendete Nachricht erhöht werden.
  8. Sending Time – die Zeit der Nachrichtenübertragung.

Unten sehen Sie die ConstructHeader-Funktion, die für die Konstruktion der Header verantwortlich ist.

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

    }

Trailer

Der Trailer ist nur ein Tag, das die Prüfsumme des restlichen Teils der Nachricht enthält.

 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;

       }

Systemnachrichten

Unser Beispiel enthält Funktionen, die die folgenden Systemnachrichten zurückgeben:

  • Heartbeat: MessageConstructor.HeartbeatMessage()
  • Testanforderung: MessageConstructor.TestRequestMessage()
  • Logon: MessageConstructor.LogonMessage()
  • Logout: MessageConstructor.LogoutMessage()
  • Resend-Anforderung: MessageConstructor.ResendMessage()
  • Ablehnung: MessageConstructor.RejectMessage()
  • Sequenz-Reset: MessageConstructor.SequenceResetMessage()

Anwendungsnachrichten

Unser Beispiel enthält Funktionen, die die folgenden Systemnachrichten zurückgeben:

  • Marktdatenanforderung: MessageConstructor.HeartbeatMessage()
  • Marktdaten-Momentaufnahme/vollständige Aktualisierung: MessageConstructor.MarketDataSnapshotMessage()
  • Inkrementelle Marktdatenaktualisierung: MessageConstructor.MarketDataIncrementalRefreshMessage()
  • Neue Einzelorder: MessageConstructor.NewOrderSingleMessage()
  • Orderstatusanforderung: MessageConstructor.OrderStatusRequest()
  • Ausführungsbericht: MessageConstructor.ExecutionReport()
  • Ablehnung von Geschäftsnachrichten: MessageConstructor.BusinessMessageReject()
  • Anforderung für Positionen: MessageConstructor.RequestForPositions()
  • Positionsbericht: MessageConstructor.PositionReport()

Eine Nachricht senden und eine Antwort empfangen

Um eine FIX-Nachricht an cServer zu senden, müssen Sie zunächst eine Verbindung mit dem Server herstellen. Sie können dies tun, indem Sie einen TcpClient erstellen. In unserem Fall erstellen wir zwei Clients, da Preisnotierungsnachrichten und Handelsnachrichten von verschiedenen Ports auf dem Server verarbeitet werden.

Dann müssen wir die beiden Streams abrufen, über die die Nachrichten gesendet werden. Dieser Prozess findet im Konstruktor des Formulars statt, wie unten gezeigt:

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

}

Im Konstruktor initialisieren wir auch eine MessageConstructor-Klasse, die zum Generieren der Nachrichten verwendet wird.

Als Nächstes haben wir zum Senden der Nachrichten zwei verschiedene Funktionen erstellt: SendPriceMessage() und SendTradeMessage(). Jede nimmt die FIX-Nachricht als Eingabe und ruft dann die SendMessage()-Funktion mit der Nachricht und dem jeweiligen Stream als Eingabe auf.

Die SendMessage()-Funktion funktioniert wie folgt:

 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;
}

Die detaillierten Schritte sind wie folgt:

  1. Kodieren Sie die Nachricht in ein Byte-Array.
  2. Schreiben Sie das Byte-Array auf den Stream.
  3. Lesen Sie die Antwort vom Stream.
  4. Erhöhen Sie die Nachrichtensequenznummer.
  5. Kodieren Sie die Nachricht in eine Zeichenfolge.

Die Funktion sollte die vom Server gesendete FIX-Nachricht zurückgeben.

Wie Sie vermuten würden, können Sie dem Benutzer keine rohe FIX-Nachricht anzeigen, daher sollte ein zusätzlicher Schritt zum Parsen der eingehenden Nachricht entwickelt werden.

Fazit

Diese Anwendung ist eine kurze Demonstration, wie man mit cServer unter Verwendung von FIX-Nachrichten kommuniziert. Es ist nur ein Beispiel, das die Konzepte des FIX-Protokolls veranschaulicht, und keinesfalls eine vollständige FIX-Engine. Wenn Sie es vermeiden möchten, Ihre eigene FIX-Engine zu erstellen, könnten Sie die Verwendung einer der verfügbaren FIX-Engines von Drittanbietern in Betracht ziehen.

Hinweis

Dieser Artikel ist auf dem Stand vom 03.02.2017 und wurde unter Berücksichtigung der cTrader FIX-Engine, Rules of Engagement v2.9.1, entwickelt.