Przejdź do treści

Wysyłanie i odbieranie wiadomości

Wprowadzenie

Ten artykuł służy jako wprowadzenie do korzystania z FIX API dla każdego zainteresowanego interakcją z Spotware cServer za pomocą FIX.

W tym artykule użyjemy przykładu w C#, aby szczegółowo opisać zasady konstruowania wiadomości FIX, wysyłania jej do serwera i otrzymywania odpowiedzi. Ten przykład w żadnym wypadku nie jest aplikacją odporną na błędy i został utrzymany w jak najprostszej formie, aby programiści mogli łatwo zrozumieć koncepcję korzystania z wiadomości FIX API.

Aby ustanowić i utrzymać prawidłową komunikację z serwerem oraz właściwe obsługiwanie odpowiedzi, wymagana jest dodatkowa funkcjonalność, która została pominięta dla uproszczenia i przejrzystości. Zajmiemy się tymi tematami w przyszłych artykułach.

Przykład kodu

Przykład kodu omówiony w tym artykule można znaleźć w naszym repozytorium GitHub

Przegląd komunikacji FIX

Wiadomość FIX to po prostu ciąg znaków składający się z zestawów numerycznych tagów i wartości oddzielonych pionową kreską (|). Każdy tag reprezentuje inne pole, dla którego dozwolony jest określony zestaw wartości. Poniżej możesz zobaczyć przykładową wiadomość FIX, która żąda uwierzytelnienia od serwera.

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|

Jak widać, powtarzalny wzorzec występujący w każdej wiadomości FIX to:

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

W zależności od celu każdej wiadomości, za każdym razem wymagany jest inny zestaw tagów i wartości. Tagi i wartości wymagane dla każdej wiadomości są szczegółowo opisane w Zasadach Zaangażowania silnika FIX cTrader (zawsze sprawdzaj najnowsze zasady zaangażowania).

W podobny sposób odpowiedzi są wysyłane z powrotem z serwera. Poniżej możesz zobaczyć odpowiedź serwera na powyższą wiadomość.

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|

Kroki związane z procesem komunikacji z serwerem FIX są następujące:

  1. Skonstruuj wiadomość FIX

  2. Prześlij wiadomość FIX

  3. Odbierz wiadomość FIX

  4. Przeanalizuj wiadomość FIX

Surowa wiadomość FIX nie jest formatem łatwym do odczytania, ponieważ została zaprojektowana z myślą o wydajności, a nie zrozumiałości. Dlatego dla każdej aplikacji zawsze będzie istniał proces tłumaczenia dostarczonych informacji na odpowiednią wiadomość FIX.

W naszej przykładowej aplikacji C# stworzyliśmy klasę do obsługi konstrukcji wiadomości, a także funkcje do tworzenia wiadomości FIX na podstawie odpowiednich informacji.

Po skonstruowaniu wiadomości są one przesyłane między serwerem a klientem przez internet za pomocą gniazd sieciowych. Gdy wiadomości są odbierane, muszą zostać przeanalizowane, aby można je było przedstawić w czytelnym formacie.

W tym artykule omówimy proces konstrukcji, transmisji i odbioru odpowiedzi. Analizowaniem zajmiemy się w przyszłym artykule.

Skonstruuj wiadomość FIX

Struktura wiadomości

W naszej przykładowej aplikacji stworzyliśmy klasę odpowiedzialną za tworzenie wiadomości FIX. Klasa nazywa się MessageConstructor i można ją znaleźć w projekcie FIX API Library.

MessageConstructor jest inicjalizowany następującymi parametrami:

  1. Host(*) – adres, pod którym znajduje się nasz cServer.

  2. Username(*) – numer konta

  3. Password(*) – hasło

  4. SenderCompID(*) – jest podany w formularzu FIX API platformy cTrader. Ma format

  5. SenderSubID* – jest to druga część SenderCompID

  6. TargetCompID(*) – jest podany w formularzu FIX API platformy cTrader (zazwyczaj jest to cServer)

Informacje te można znaleźć w formularzu cTrader FIX API.

Po zainicjalizowaniu MessageConstructor jesteśmy gotowi do konstruowania wiadomości FIX API.

Wszystkie wiadomości są konstruowane w podobny sposób. Poniżej znajduje się przykład kodu konstruującego wiadomość 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");

       }

Widać, że najpierw konstruujemy część body, następnie przekazujemy ją do funkcji header, a na końcu przekazujemy obie części do funkcji trailer. Te 3 części są szczegółowo opisane poniżej.

Proces konstrukcji wiadomości to po prostu dodanie wymaganych tagów, wartości i separatorów do ciągu znaków.

Treść

Najpierw opiszemy konstrukcję body, ponieważ treść wiadomości musi zostać utworzona w pierwszej kolejności. Powyżej możemy zobaczyć przykład (tworzenie wiadomości Logon).

Zaczynamy od zainicjowania klasy StringBuilder i dołączamy tagi jeden po drugim na podstawie danych wejściowych funkcji. W zależności od typu wiadomości, body musi składać się z różnych zestawów tagów, z których niektóre są obowiązkowe, a inne opcjonalne.

Strukturę każdej wiadomości można znaleźć w naszym dokumencie Rules of Engagement (/FIX).

Następnie tworzymy nagłówek dla wiadomości Logon i dołączamy do niego treść wiadomości. Na koniec, używając ciągu headerAndBody, generujemy trailer. Dalej zobaczymy, jak konstruujemy nagłówek i trailer.

Nagłówek

Nagłówek jest pierwszą częścią wiadomości FIX i składa się z następujących pól (takich samych dla wszystkich wiadomości):

  1. BeginString – ciąg początkowy definiuje wersję protokołu FIX i w naszym przypadku jest ustalony na FIX4.4.
  2. BodyLength – długość treści określa długość wiadomości w znakach, z wyłączeniem pól BeginString, BodyLength i trailer.
  3. MsgType – w tym polu definiujemy typ wiadomości, aby odbiorca wiedział, jak parsować treść.
  4. SenderCompID – tutaj ustawiamy SenderCompID.
  5. TargetCompID – to jest cel naszej wiadomości. W naszym przypadku zawsze będzie to cServer.
  6. SenderSubID – login tradera.
  7. MsgSeqNum – to jest numer sekwencyjny wiadomości. Musi być zwiększany dla każdej wiadomości wysłanej w tej samej sesji.
  8. Sending Time – czas transmisji wiadomości.

Poniżej można zobaczyć funkcję ConstructHeader, odpowiedzialną za konstruowanie nagłówków.

 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

Trailer to po prostu tag zawierający sumę kontrolną reszty wiadomości.

 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;

       }

Komunikaty systemowe

Nasz przykład zawiera funkcje, które zwracają następujące wiadomości systemowe:

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

Komunikaty aplikacyjne

Nasz przykład zawiera funkcje, które zwracają następujące wiadomości systemowe:

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

Wyślij wiadomość i otrzymaj odpowiedź

Aby wysłać wiadomość FIX do cServer, najpierw musisz nawiązać połączenie z serwerem. Możesz to zrobić, tworząc TcpClient. W naszym przypadku tworzymy dwóch klientów, ponieważ wiadomości z notowaniami cen i wiadomości handlowe są obsługiwane przez różne porty na serwerze.

Następnie musimy uzyskać dwa strumienie, na których będą wysyłane wiadomości. Ten proces odbywa się w konstruktorze formularza, jak pokazano poniżej:

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

}

W konstruktorze inicjujemy również klasę MessageConstructor, która będzie używana do generowania wiadomości.

Następnie, aby wysłać wiadomości, utworzyliśmy dwie różne funkcje, SendPriceMessage() i SendTradeMessage(). Każda przyjmuje wiadomość FIX jako dane wejściowe, a następnie wywołuje funkcję SendMessage() z wiadomością i odpowiednim strumieniem jako danymi wejściowymi.

Funkcja SendMessage() działa w następujący sposób:

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

Szczegółowe kroki są następujące:

  1. Zakoduj wiadomość do tablicy bajtów.
  2. Zapisz tablicę bajtów w strumieniu.
  3. Odczytaj odpowiedź ze strumienia.
  4. Zwiększ numer sekwencyjny wiadomości.
  5. Zakoduj wiadomość do ciągu.

Funkcja powinna zwrócić wiadomość FIX wysłaną przez serwer.

Jak można się domyślić, nie można pokazać użytkownikowi surowej wiadomości FIX, więc należy opracować dodatkowy krok parsowania wiadomości przychodzącej.

Podsumowanie

Ta aplikacja jest krótką demonstracją komunikacji z cServer przy użyciu wiadomości FIX. To tylko przykład ilustrujący koncepcje protokołu FIX i w żadnym wypadku nie jest pełnym silnikiem FIX. Jeśli chcesz uniknąć budowania własnego silnika FIX, możesz rozważyć użycie jednego z dostępnych silników FIX firm trzecich.

Uwaga

Ten artykuł jest aktualny na dzień 03/02/2017 i został opracowany z uwzględnieniem silnika FIX cTrader, Rules of Engagement v2.9.1.