ข้ามไปที่เนื้อหา

ส่งและรับข้อความ

บทนำ

บทความนี้เป็นการแนะนำการใช้ FIX API สำหรับผู้ที่สนใจในการโต้ตอบกับ Spotware cServer โดยใช้ FIX

ในบทความนี้ เราจะใช้ตัวอย่าง C# เพื่ออธิบายหลักการในการสร้างข้อความ FIX ส่งไปยังเซิร์ฟเวอร์ และรับการตอบกลับอย่างละเอียด ตัวอย่างนี้ไม่ใช่แอปพลิเคชันที่สมบูรณ์แบบและถูกทำให้ง่ายที่สุดเท่าที่จะเป็นไปได้เพื่อให้โปรแกรมเมอร์เข้าใจแนวคิดของการใช้ข้อความ FIX API ได้ง่าย

เพื่อสร้างและรักษาการสื่อสารที่เหมาะสมกับเซิร์ฟเวอร์และการจัดการการตอบกลับที่เหมาะสม จำเป็นต้องมีฟังก์ชันการทำงานเพิ่มเติม ซึ่งถูกข้ามไปเพื่อความเรียบง่ายและชัดเจน เราจะจัดการกับหัวข้อเหล่านี้ในบทความในอนาคต

ตัวอย่างโค้ด

คุณสามารถหาตัวอย่างโค้ดที่กล่าวถึงในบทความนี้ได้ที่ GitHub repository ของเรา

ภาพรวมของการสื่อสาร 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 Rules of Engagement (ตรวจสอบ 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(*) – มีให้ในแบบฟอร์ม 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 – begin string กำหนดเวอร์ชันโปรโตคอล FIX และในกรณีของเราคือ FIX4.4
  2. BodyLength – body length ระบุความยาวของข้อความเป็นตัวอักษร ไม่รวม 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.SequenceReset

ข้อความของแอปพลิเคชัน

ตัวอย่างของเราประกอบด้วยฟังก์ชันที่ส่งคืนข้อความระบบต่อไปนี้:

  • คำขอข้อมูลตลาด: MessageConstructor.HeartbeatMessage()
  • สแนปช็อตตลาด/รีเฟรชเต็มรูปแบบ: MessageConstructor.MarketDataSnapshotMessage()
  • รีเฟรชข้อมูลตลาดแบบเพิ่มขึ้น: MessageConstructor.MarketDataIncrementalRefreshMessage()
  • คำสั่งซื้อขายใหม่เดี่ยว: MessageConstructor.NewOrderSingleMessage()
  • คำขอสถานะคำสั่งซื้อขาย: MessageConstructor.OrderStatusRequest()
  • รายงานการดำเนินการ: MessageConstructor.ExecutionReport()
  • การปฏิเสธข้อความทางธุรกิจ: MessageConstructor.BusinessMessageReject()
  • คำขอสำหรับตำแหน่ง: MessageConstructor.RequestForPositions()
  • รายงานตำแหน่ง: 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 และพัฒนาขึ้นโดยคำนึงถึงเอ็นจิน FIX ของ cTrader, Rules of Engagement v2.9.1