概要
本記事では、C# の例を使用して、FIX メッセージを作成し、サーバーに送信し、応答を受信する方法を詳しく説明します。
この例は、複雑なアプリケーションではなく、プログラマーが FIX API メッセージを使用する概念を簡単に理解できるように、できるだけシンプルに記述してあります。
サーバーとの適切な通信を確立・維持し、レスポンスを適切に処理するためには、追加機能が必要ですが、それらは単純明快にするために省略されています。レスポンスの適切な処理については、今後の記事で扱う予定です。
コードサンプル
この記事で取り上げたコードサンプルは、こちらのGitHubリポジトリでご覧いただけます: https://github.com/spotware/FIX-API-Sample
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メッセージに見られる繰り返しパターンは次のとおりです:
タグ=値|タグ=値|タグ=値|…
各メッセージの目的に応じて、毎回異なるタグと値のセットが必要になります。各メッセージに必要なタグと値は、cTrader FIX Engine 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サーバーと通信する手順
- FIXメッセージの構築
- FIXメッセージの送信
- FIXメッセージの受信
- FIXメッセージの解析
生のFIXメッセージは、理解しやすさよりも効率性を念頭に置いて設計されているため、あまり読みやすいフォーマットではありません。
そのため、各ソフトウェア・アプリケーションでは、提供された情報をそれぞれの FIX メッセージに変換するプロセスが常に発生します。
C# サンプル・アプリケーションでは、メッセージ作成を処理するクラスと、関連情報に基づいて FIX メッセージを作成する関数を作成しました。
メッセージが作成された後、ネットワーク・ソケットを通じてインターネット経由でサーバーとクライアントの間で送信されます。メッセージが受信されると、可読形式で表示されるように解析する必要があります。
この記事では、メッセージの作成、送信、返信の受信のプロセスについて説明する。構文解析については今後の記事で扱う予定である。
FIXメッセージの作成
メッセージの構造
サンプル・アプリケーションでは、FIXメッセージの作成を担当するクラスを作成しました。このクラスは MessageConstructor と呼ばれ、FIX API Library プロジェクトの中にあります。
MessageConstructor は以下のパラメータで初期化されます:
- ホスト(*): cServerが置かれているアドレス。
- ユーザー名(*): アカウント番号
- パスワード(*): パスワード
- SenderCompID(*): cTrader の FIX API 形式で提供されます。
- SenderSubID: SenderCompID の 2 番目の部分。
- TargetCompID(*): cTrader の FIX API 形式で提供される。通常は cServer です。
この情報は、cTrader FIX API フォームに記載されています。
MessageConstructor を初期化したら、FIX API メッセージを作成する準備は完了です。
すべてのメッセージは同様の方法で構築されます。以下に、ログオンメッセージを作成するコードサンプルを示します。
| 1 | public string LogonMessage(SessionQualifier qualifier, int messageSequenceNumber, |
| 2 | |
| 3 | int heartBeatSeconds, bool resetSeqNum) |
| 4 | |
| 5 | { |
| 6 | |
| 7 | var body = new StringBuilder (); |
| 8 | |
| 9 | //Defines a message encryption scheme.Currently, only transportlevel security |
| 10 | |
| 11 | //is supported.Valid value is “0”(zero) = NONE_OTHER (encryption is not used). |
| 12 | |
| 13 | body.Append( “98=0|”); |
| 14 | |
| 15 | //Heartbeat interval in seconds. |
| 16 | |
| 17 | //Value is set in the ‘config.properties’ file (client side) as |
| 18 | |
| 19 | // ‘SERVER.POLLING.INTERVAL’ . |
| 20 | |
| 21 | //30 seconds is default interval value. If HeartBtInt is set to 0, |
| 22 | |
| 23 | no heartbeat message |
| 24 | |
| 25 | //is required. |
| 26 | |
| 27 | body.Append( “108=” + heartBeatSeconds + “|”); |
| 28 | |
| 29 | // All sides of FIX session should have |
| 30 | |
| 31 | //sequence numbers reset. Valid value |
| 32 | |
| 33 | //is “Y” = Yes(reset). |
| 34 | |
| 35 | if (resetSeqNum) |
| 36 | |
| 37 | body.Append( “141=Y|”); |
| 38 | |
| 39 | //The numeric User ID. User is linked to SenderCompID (#49) value (the |
| 40 | |
| 41 | //user’s organization). |
| 42 | |
| 43 | body.Append( “553=” + _username + “|”); |
| 44 | |
| 45 | //USer Password |
| 46 | |
| 47 | body.Append( “554=” + _password + “|”); |
| 48 | |
| 49 | var header = ConstructHeader(qualifier, |
| 50 | |
| 51 | SessionMessageCode( SessionMessageType.Logon), messageSequenceNumber, |
| 52 | |
| 53 | body.ToString()); |
| 54 | |
| 55 | var headerAndBody = header + body; |
| 56 | |
| 57 | var trailer = ConstructTrailer(headerAndBody); |
| 58 | |
| 59 | var headerAndMessageAndTrailer = header + body + trailer; |
| 60 | |
| 61 | return headerAndMessageAndTrailer.Replace(“|”, “\u0001”); |
| 62 | |
| 63 | } |
まずボディ部分を作成し、次にそれをヘッダー関数に渡し、最後に両方の部分をトレーラー関数に渡しているのがお分かりいただけると思います。この3つの部分については、次の段落で詳しく説明します。
メッセージの作成プロセスは、必要なタグ、値、セパレータを文字列に追加するだけです。
ボディ
まず、メッセージの本文を作る必要があるので、本文の作り方から説明します。
上の例(ログオン・メッセージの作成)を見てください。
まず、StringBuilder クラスを初期化し、関数の入力に基づいてタグを 1 つずつ追加していきます。メッセージ・タイプに応じて、本文は異なるタグ・セットで構成されなければなりません。
各メッセージの構造は、私たちの規約(/FIX)に記載されています。
次に、ログオン・メッセージのヘッダーを作成し、それにメッセージ本文を追加する。最後に、headerAndBody文字列を使って、トレーラを生成します。
ヘッダー
ヘッダーはFIXメッセージの最初の部分で、以下のフィールドで構成されます。(すべてのメッセージに共通です)
- BeginString:: 開始文字列は FIX プロトコルのバージョンを定義します。
- BodyLength: 本文の長さは、BeginString、BodyLength、Trailer フィールドを除いたメッセージの長さを文字数で表す。
- MsgType: このフィールドでは、メッセージ・タイプを定義する。
- SenderCompID: ここでは SenderCompID を設定する。
- TargetCompID: これはメッセージのターゲットである。この場合、常に CSERVER となる。
- SenderSubID: トレーダーのログイン名。
- MsgSeqNum: メッセージのシーケンス番号: メッセージのシーケンス番号。同じセッションでメッセージを送信するたびに増加させる必要がある。
- Sending Time: メッセージ送信時刻。
以下に、ヘッダーを構築するConstructHeader関数を示します。
| 1 | private string ConstructHeader(SessionQualifier qualifier, string type, |
| 2 | |
| 3 | int messageSequenceNumber, string bodyMessage) |
| 4 | |
| 5 | { |
| 6 | |
| 7 | var header = new StringBuilder (); |
| 8 | |
| 9 | // Protocol version. FIX.4.4 (Always unencrypted, must be first field |
| 10 | |
| 11 | // in message. |
| 12 | |
| 13 | header.Append( “8=FIX.4.4|”); |
| 14 | |
| 15 | var message = new StringBuilder (); |
| 16 | |
| 17 | // Message type. Always unencrypted, must be third field in message. |
| 18 | |
| 19 | message.Append( “35=” + type + “|”); |
| 20 | |
| 21 | // ID of the trading party in following format: <BrokerUID>.<Trader Login> |
| 22 | |
| 23 | // where BrokerUID is provided by cTrader and Trader Login is numeric |
| 24 | |
| 25 | // identifier of the trader account. |
| 26 | |
| 27 | message.Append( “49=” + _senderCompID + “|”); |
| 28 | |
| 29 | // Message target. Valid value is “CSERVER” |
| 30 | |
| 31 | message.Append( “56=” + _targetCompID + “|”); |
| 32 | |
| 33 | // Additional session qualifier. Possible values are: “QUOTE”, “TRADE”. |
| 34 | |
| 35 | message.Append( “57=” + qualifier.ToString() + “|”); |
| 36 | |
| 37 | // Assigned value used to identify specific message originator. |
| 38 | |
| 39 | message.Append( “50=” + _senderSubID + “|”); |
| 40 | |
| 41 | // Message Sequence Number |
| 42 | |
| 43 | message.Append( “34=” + messageSequenceNumber + “|”); |
| 44 | |
| 45 | // Time of message transmission (always expressed in UTC(Universal Time |
| 46 | |
| 47 | // Coordinated, also known as ‘GMT’). |
| 48 | |
| 49 | message.Append(“52=” + DateTime.UtcNow.ToString(“yyyyMMdd-HH:mm:ss”) + “|”); |
| 50 | |
| 51 | var length = message.Length + bodyMessage.Length; |
| 52 | |
| 53 | // Message body length. Always unencrypted, must be second field in message. |
| 54 | |
| 55 | header.Append( “9=” + length + “|”); |
| 56 | |
| 57 | header.Append(message); |
| 58 | |
| 59 | return header.ToString(); |
| 60 | |
| 61 | } |
トレーラー
トレーラーは、メッセージの残りの部分のチェックサムを含むタグです。
| 1 | private string ConstructTrailer(string message) |
| 2 | |
| 3 | { |
| 4 | |
| 5 | //Three byte, simple checksum. Always last field in message; i.e. serves, |
| 6 | |
| 7 | //with the trailing<SOH>, |
| 8 | |
| 9 | //as the end – of – message delimiter. Always defined as three characters |
| 10 | |
| 11 | //(and always unencrypted). |
| 12 | |
| 13 | var trailer = “10=” + CalculateChecksum(message.Replace(“|”, “\u0001”).ToString()).ToString().PadLeft(3, ‘0’) + “|”; |
| 14 | |
| 15 | return trailer; |
| 16 | |
| 17 | } |
システムメッセージ
このサンプルには、以下のシステム・メッセージを返す関数が含まれています
- ハートビート 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()
- RequestForPositions 位置の要求: MessageConstructor.RequestForPositions()
- ポジションレポート MessageConstructor.PositionReport()
メッセージの送信と応答の受信
cServerにFIXメッセージを送信するには、まずサーバーとの接続を確立する必要があります。そのためには TcpClient を作成します。
なお、価格提示メッセージと取引メッセージはサーバー上の異なるポートで処理されます。
次に、メッセージが送信される 2 つのストリームを取得する必要があります。この処理は、以下のようにフォームのコンストラクタで行われます:
| 1 | public frmFIXAPISample() |
| 2 | { |
| 3 | |
| 4 | InitializeComponent(); |
| 5 | |
| 6 | _priceClient = new TcpClient( _host, _pricePort); |
| 7 | |
| 8 | _priceStream = _ priceClient.GetStream (); |
| 9 | |
| 10 | _tradeClient = new TcpClient ( _host, _tradePort); |
| 11 | |
| 12 | _tradeStream = _ tradeClient.GetStream (); |
| 13 | |
| 14 | _messageConstructor = new MessageConstructor( _host, _username, |
| 15 | |
| 16 | _password, _senderCompID, _senderSubID, _targetCompID); |
| 17 | |
| 18 | } |
コンストラクタでは、メッセージ生成に使用する MessageConstructor クラスも初期化しています。
次にメッセージを送信するために、SendPriceMessage() と SendTradeMessage() という 2 つの関数を作成しました。それぞれ FIX メッセージを入力として受け取り、メッセージとそれぞれのストリームを入力として SendMessage() 関数を呼び出します。
SendMessage() 関数の動作は以下のとおりです:
| 1 | private string SendMessage(string message, NetworkStream stream) |
| 2 | { |
| 3 | var byteArray = Encoding.ASCII.GetBytes (message); |
| 4 | |
| 5 | stream.Write(byteArray, 0, byteArray.Length); |
| 6 | |
| 7 | var buffer = new byte[1024]; |
| 8 | |
| 9 | int i = 0; |
| 10 | |
| 11 | while (!stream.DataAvailable && i < 100) |
| 12 | { |
| 13 | Thread.Sleep ( 100); |
| 14 | i++; |
| 15 | } |
| 16 | |
| 17 | if( stream.DataAvailable ) |
| 18 | stream.Read(buffer, 0, 1024); |
| 19 | |
| 20 | _messageSequenceNumber++; |
| 21 | |
| 22 | var returnMessage = Encoding.ASCII.GetString(buffer); |
| 23 | |
| 24 | return returnMessage; |
| 25 | } |
具体的な手順は以下のようになります
- メッセージをバイト配列にエンコードする。
- バイト配列をストリームに書き込む。
- ストリームから応答を読み出す。
- メッセージのシーケンス番号を増やす。
- メッセージを文字列にエンコードする。
この関数は、サーバーから送信されたFIXメッセージを返します。
生のFIXメッセージをユーザに見せることはできないので、受信メッセージを解析する追加のステップを開発する必要があります。
まとめ
このアプリケーションは、FIXメッセージを使用してcServerと通信する方法についての簡単なデモンストレーションです。本記事での解説は FIX プロトコルの概念を示す単なる例であり、決して完全な FIX エンジンではありません。
独自開発を効率化するには、サードパーティの FIX エンジンの使用をご検討ください。
本記事はcTrader FIX Engine, Rules of Engagement v2.9.1を念頭に置いて作成されています。
