콘텐츠로 이동

메시지 송수신

소개

이 글은 FIX API를 사용하여 Spotware 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 엔진 Rules of Engagement에 자세히 설명되어 있습니다(항상 최신 규칙을 확인하세요).

비슷한 방식으로 서버에서 응답이 다시 전송됩니다. 아래에서 위 메시지에 대한 서버의 응답을 볼 수 있습니다.

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 메시지를 구성할 준비가 되었습니다.

모든 메시지는 비슷한 방식으로 구성됩니다. 아래에 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;

       }

시스템 메시지

우리의 샘플에는 다음과 같은 시스템 메시지를 반환하는 함수가 포함되어 있습니다:

  • 하트비트: 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 엔진, Rules of Engagement v2.9.1을 고려하여 개발되었습니다.