English Deutsch
preview
MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ(最終回)

MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ(最終回)

MetaTrader 5統合 | 24 5月 2024, 10:52
115 0
Jocimar Lopes
Jocimar Lopes
「私たちの目的は、問題とその解決策に対する制約が与えられたときに、常に可能な限り高い抽象度で作業することです。」(Bjarne Stroustrup「Programming Principles and Practice Using C++」)

はじめに

今回がこの連載の最終回なので、簡単にまとめておくと役に立つか、せめて便利でしょう。

この連載の第1回では、MQTTがパブリッシュ/サブスクライブ相互作用モデル(pub/sub)に基づくメッセージ共有プロトコルであり、取引環境において、取引、口座情報、機械学習パイプラインが取り込むための統計データなど、あらゆる種類のデータをリアルタイムで、プレーンテキスト、XML、JSON、画像を含むバイナリデータで共有できるようにすることで有用であることを説明しました。MQTTは軽量で、ネットワークの不安定性や中断に強く、コンテンツにとらわません。その上、プロトコルは成熟し、実戦でテストされ、OASISによって維持されているオープンスタンダードです。1つ前の3.1.1と現行の5.0は最も使用されている2つのバージョンで、いわゆる「モノのインターネット」において、事実上無制限のデバイスを接続するために最も使用されているプロトコルの1つです。MQTTは、切り離されたマシン間でリアルタイムのデータ共有が必要なあらゆるシナリオで使用できます。

開発、テスト、本番環境で利用可能なMQTT証券会社は、オープンソースと商用を問わず多数あり、事実上あらゆるモダンなプログラミング言語用のMQTTクライアントも多数あります。証券会社、ライブラリ、ツールを含むMQTTソフトウェアのリストで確認できます。

本連載第2回では、このクライアント開発のためのコード構成について説明し、オブジェクト指向のパラダイムなど、いくつかの初期設計の選択についてコメントしました。このコードのほとんどは、最初の大規模なリファクタリングで変更されましたが、機能は変わっていません。その記事では、Windows Subsystem for Linux (WSL)上で動作するローカル証券会社との接続をおこなったところ、CONNECTクラスが不正なパケットを生成していることに気づいたことも紹介しました。それを改良し、次の記事で報告しました。

第3回では、プロトコルの操作時動作の部分と、CONNECTフラグとの関係について研究することに専念しました。これらのフラグのセマンティクスとその設定方法について説明し、また、このプロジェクトで使用しているテスト駆動開発の実践についても言及しました。最後に、クラスのprotectedメソッドをテストする方法を説明しました。

第4回では、MQTT 5.0プロパティの重要性について深く掘り下げました。それぞれのデータ型(MQTT用語でいうとデータ表現)についてコメントしました。そこでは、MQTT 5.0のプロパティ、特にユーザプロパティを使用してプロトコルを拡張する方法について、いくつか記録しました。

PUBLISHコントロールパケットとそのユニークな(予約されていない)固定ヘッダーフラグは、第5回のテーマでした。これらのPUBLISH固定ヘッダーフラグがビットレベルでどのように動作するかを示すために、多くのスペースを割きました。異なるMQTTサービス品質レベル(QoS 0、QoS 1、QoS 2)の特徴を、それぞれのQoSにおけるクライアントと証券会社間のパケット交換を示すいくつかの図を用いてマークしました。

第6回は「間奏曲」でした。私たちにとって初めての大規模なコードリファクタリングでした。コントロールパケットクラスの設計図を変更し、重複する関数や時代遅れのテストコードを削除しました。この記事は主に、PUBACKコントロールパケットに関するいくつかの注釈を加えた、これらの変更に関する文書でした。PUBACK理由コードとそれぞれの理由文字列のセマンティクスの注釈が含まれています。

最後に、この第7回では、エキスパートアドバイザー(EA)で使用する指標のシグナルを構築する際に、トレーダーが非常によく直面するニーズ、つまり、取引口座で指標に必要な銘柄がないという問題に対処するためのコードを紹介したいと思います。

カスタム銘柄とMetaTrader 5端末上でサービスとして動作するMQTTクライアントのペアを使用した1つの可能な解決策を提案します。デモコードは単純化されすぎており、単一の端末インスタンス上で実行されていますが、MQTTプロトコル自体の主な特徴(「証券会社」メディエーションによる送信者と受信者の分離)により、この解決策は、任意の数のデバイスインスタンスと銘柄に対応するように拡張することができます。

記事の最後には、ライブラリの現在の状況、開発の優先順位と可能なロードマップ、そしてプロジェクトのフォローアップと貢献ができる場所を示しています。

以下の説明では、OASIS標準で使用されている必要[MUST]と可能[MAY]という用語を使用します。OASIS標準は、IETF RFC 2119で説明されているようにこれらの用語を使用します。

特に断りのない限り、引用はすべてOASIS標準からのものです。


トレーダーのニーズ

あなたがトレーダーとして暗号を専門に扱ってるとしましょう。S&P500とAncapCoinという無名でほとんど知られていないミームコインとの間に一貫した負の相関関係があることを知りました。S&P500が上昇するとAncapCoinは下落し、その逆も同様です。この知識により市場で優位に立つことができ、S&P 500との負の相関関係に従ってAncapCoinを取引して利益を得ることができました。さて、利益を最大化するために、運用を自動化し、最終的にはVPSでEAを24時間365日稼働させたいと考えるでしょう。必要なのは、EAの売買判断をサポートする基本的なS&P500指標だけです。

しかし、AncapBrokerも暗号に専門としているため、S&P500は銘柄に含まれていません。そして、S&P500を提供している証券会社は、あなたの大事なAncapCoinを提供していません。AncapBrokerが提供する取引口座で、S&P500指標をどうやって実行させるでしょうか。

AngcapBrokerとAngcapCoinは単なる架空の名前ですが、この架空のシナリオはまったく架空のものではありません。一般的に、株価指数を運用するトレーダーにとって、株価指数を提供する証券会社が提供していない銘柄や、その逆の銘柄を選んで合成した指標を持てることはありがたいことです。中央集権的な取引所で取引可能な商品とCFD証券会社で取引可能なそれぞれのCFDを考えてみると、トレーダーはデータに合法的にアクセスできるが、データは異なるプロバイダーから提供されているという典型的なミスマッチがあります。手動の裁量取引では、この問題は問題ではありません。別々のモニターを使えば、あるいは同じモニターに別のウィンドウを表示すれば、欠如している銘柄の相場を追うことができます。しかし、自動化となると、両プロバイダーがMetaTrader 5を提供していても、一般の個人トレーダーにとって、取引判断に必要な場所、つまり取引口座にリアルタイムで相場を表示することは、事実上不可能ではないにせよ、非常に難しくなります。

開発者は、このシナリオが顧客の要件ならば、対処するための候補として、Publish/Subscribeメッセージングパターンを取り上げるのがよいでしょう。オープンでよく整備されたpub/sub仕様の中で、MQTTはおそらく最もシンプルで安価かつ堅牢な仕様の1つです。ここで明確にしておきますが、MQTTは、まさに、架空のシナリオで上で概説した種類の要件、つまり地理的に分散している可能性がある複数のソースからリアルタイムで、最小限のネットワークオーバーヘッドを伴ってデータを収集および配布するために設計されました。

以下のセクションでは、MetaTrader 5でこの解決策を実装する方法について、現在の状態でクライアントを使用し、がリアルタイムで相場がデータソース口座から取引口座に流れるところまで見ていきます。そこで、その必要な指標を構築するために利用できるようになります。

取引口座では、以下をおこないます。

  1. S&P500を表すカスタム銘柄を作成する
  2. MQTT証券会社からS&P500の相場を受信するためのSUBSCRIBE MQTTサービス記述する
  3. MQL5関数を使用してS&P500を表すカスタム銘柄を更新する

データソースアカウントでは、以下をおこないます。

  1. S&P500の相場を収集してMQTT証券会社に送信するためのPUBLISH MQTTサービスを記述する

物事を単純かつ簡潔にするために、ここでは1つの取引口座と1つのデータソース口座について話していますが、これらの口座の数は理論的には無制限です。現実的には、双方に接続される口座数は、デバイスの物理的な制限(メモリ、CPU、ネットワーク帯域幅など)とMQTT証券会社による制限によってのみ制限されます。

また、私たちがここで目指しているのは、既製の本番環境に対応した解決策を提供することではないことを心に留めておいてください。その代わりに、私たちの目標は可能性のある実装に必要な主な手順を紹介すること、そしてもちろん、取引業務や顧客向けにカスタマイズされたアプリケーションを提供するためのMQTT pub/subパターンの潜在的な用途に興味を持っていただくことです。私たちは、それがミームコインからの引用をコピーすることをはるかに超えていると確信しています。


カスタム銘柄の作成

カスタム銘柄とは、自分が希望または要求する仕様で作成した銘柄を、自分が提供する見積もりで更新するものです。カスタム銘柄はその仕様と相場をコントロールできるため、複合インデックスを作成したり、EAや指標をテストしたりするのに便利です。

カスタム銘柄は、MetaTrader 5エディターのグラフィカルUI(GUI)を使用してまたは特定のMQL5関数を介してプログラムで作成することができます。ドキュメントには、プログラムまたはGUIを介してカスタム銘柄を作成、カスタマイズ、使用するための詳細な手順が記載されています。ここでは、グラフィカルなUIで十分です。

取引口座をホストしているMetaTrader 5で、[表示]>[銘柄]>[カスタム銘柄を作成する]の順にクリックします。

MetaTrader 5 GUIを使用したカスタム銘柄の作成

図01:MetaTrader 5 GUIを使用したカスタム銘柄の作成

次に表示されるウィンドウで、[コピー元]フィールドでS&P500の銘柄を選択し、実際のインデックスに基づいたカスタム銘柄を作成します。カスタム銘柄の名前と簡単な説明を入力します。

GUIを使ったカスタム銘柄の仕様設定

図02:MetaTrader 5 GUIを使用したカスタム銘柄仕様の設定

警告:注意深い読者なら、取引口座でカスタム銘柄を作成しているが、例によると、この口座にはカスタム銘柄のモデルとして使用するS&P500の銘柄がないことにお気づきかもしれませんデータソース口座にいるべきです。結局のところ、ここでやっていることは、取引口座にS&P500がないからこそできることなのです。その通りです。  実際の状況では、おそらくS&P500の仕様を手作業でコピーし、ニーズに応じてこれらの銘柄の仕様を埋める必要があります。ここではカスタム銘柄の作成に重点を置いているわけではないので、単純化しすぎていますが、その代わりに、興味があるのはMQTT経由で口座を接続することなのです。実際の状況に合わせて銘柄をカスタマイズする必要がある場合は、上記のリンク先の文書を参照してください。

[OK]をクリックすると、新しく作成したカスタム銘柄が左のツリーリストに表示されます。

GUIを使用したカスタム銘柄作成後の銘柄ツリーの確認

図03:MetaTrader 5 GUIを使用したカスタム銘柄作成後の銘柄ツリーの確認

その後、気配値表示に追加し、チャートの視覚化に利用できるようにします。このステップはティックの更新に必要です。 

「CustomTicksAdd関数は、気配値表示ウィンドウで開いたカスタム銘柄に対してのみ機能します。気配値表示で銘柄が選択されていない場合は、CustomTicksReplaceを使用してティックを追加する必要があります。」(MQL5リファレンスドキュメント

MetaTrader GUIを使用したカスタム銘柄作成後の気配値表示ウィンドウの確認

図04:MetaTrader 5 GUIを使用したカスタム銘柄作成後の気配値表示ウィンドウの確認

気配値表示ウインドウには、まだ相場が表示されていません。そこで、新しく作成したMySPX500カスタム銘柄を更新するために、リアルタイムの相場を送信するMQTT証券会社にこの口座をサブスクライブしてみましょう。


SUBSCRIBE MQTTサービスを記述する

現段階では、私たちのクライアントはQoS 0とQoS 1でサブスクライブできますが、相場やティックを更新するにはQoS 0で十分だと思います。この文脈では、最終的に失われたティックは重要ではないからです。500ミリ秒ごとに1ティックを送信しているので、どちらか一方が失われると、その位置はすぐに次のティックに占領されます。

証券会社のホストとポートを入力パラメータとして、subscribe サービスのコードを開始します。これを実際の証券会社のデータに置き換えることを忘れないでください。ローカル証券会社で開発/テスト環境を設定する方法については、次のセクションを参照してください。

#property service
//--- input parameters
input string   host = "172.20.106.92";
input int      port = 80;

次に、ConnectクラスとSubscribeクラスのオブジェクトのグローバルバーを宣言します。停止時やエラーからの復帰時に、これらの変数を削除(クリーンアップ)できるようにしておく必要があります。

//--- global vars
int skt;
CConnect *conn;
CSubscribe *sub;

object_pointer=newクラス名の式で作成されたすべてのオブジェクトは、delete(object_pointer)演算子で削除する必要があります。」(MQL5リファレンスドキュメント

サービスの OnStart() メソッドで、接続オブジェクトをClean Start接続(この接続には証券会社セッションがない)に設定し、開発中に定期的にpingリクエストを送信する必要がないように余裕を持ったKeep Aliveを設定し、クライアントIDを設定し、最後にCONNECTパケットを構築します。パブリッシュサービスで使用するクライアントIDとは異なるクライアントIDを設定してください。

   uchar conn_pkt[];
   conn = new CConnect(host, port);
   conn.SetCleanStart(true);
   conn.SetKeepAlive(3600);
   conn.SetClientIdentifier("MT5_SUB");
   conn.Build(conn_pkt);

同じ OnStart()メソッドで、トピックフィルタを持つサブスクライブパケットも設定し、構築します。分かりやすく直感的に理解できるように、カスタム銘柄に選んだ名前をトピックフィルタとして使用します。もちろん、パブリッシュサービスで同じトピックフィルタを使用する限り、これは任意です。

  uchar sub_pkt[];
  sub = new CSubscribe();
  sub.SetTopicFilter("MySPX500");
  sub.Build(sub_pkt);

最後に、両方のパケットを順番に送信する関数を呼び出し、それらの関数のエラー処理を委譲し、サブスクリプションリクエストに何か問題があれば-1を返します。

if(SendConnect(host, port, conn_pkt) == 0)
     {
      Print("Client connected ", host);
     }
   if(!SendSubscribe(sub_pkt))
     {
      return -1;
     }

SendConnect関数には、MQTT関連のコードは数行しかありません。そのほとんどはネットワーク/ソケット関連なので、ここでは詳しく説明しません。代わりに、ネットワーク関数に関するドキュメントを参照してください。MQL5ネットワーク関数についてより深く理解したい場合は、AlgoBookの関連する章を強くお勧めします。そこでは、MQL5を使用したプレーンネットワーキングとセキュア(TLS)ネットワーキングの両方について、詳細な説明と有用な例が掲載されています。

AlgoBookには、以下の抜粋のような情報が掲載されています。これは、私たちの関数における断続的で非決定的な動作を特定し、回避するのに役立ちました(添付ファイルのコメント付きコードを参照)。

Windows/LinuxのソケットシステムAPIに精通したプログラマーは、ソケットの内部バッファに受信デー タがない場合、0が正常な状態であることを知っています。しかし、この関数はMQL5では異なる動作をします。システムソケットバッファが空の場合、推測的に1を返し、実際にデータが利用可能かどうかの確認は次のread関数の呼び出しまで延期されます。特に、1バイトのダミー結果を伴うこの状況は、原則として、受信側内部バッファがまだ空であるときに、ソケット上で関数が初めて呼び出されたときに発生します。」(AlgoBook

MQTT側では、SendConnectでおこなうことは、証券会社からのCONNACKレスポンスと、関連するReason Codeの値を確認することだけです。

if(rsp[0] >> 4 != CONNACK)
     {
      Print("Not Connect acknowledgment");
      CleanUp();
      return -1;
     }
   if(rsp[3] != MQTT_REASON_CODE_SUCCESS)  // Connect Return code (Connection accepted)
     {
      Print("Connection Refused");
      CleanUp();
      return -1;
     }

おわかりのように、クラスオブジェクトへの動的ポインタをクリーンアップした後、エラーとして-1を返しています。

同じ割合のネットワーク/MQTT関連コードがSendSubscribe関数にも当てはまります。証券会社からのSUBACKレスポンスとそれぞれのReason Codeを確認した後、エラーが発生した場合は、それらのクラスオブジェクトの動的ポインタを削除します。

if(((rsp[0] >> 4) & SUBACK) != SUBACK)
     {
      Print("Not Subscribe acknowledgment");
     }
   else
      Print("Subscribed");
   if(rsp[5] > 2)  // Suback Reason Code (Granted QoS 2)
     {
      Print("Subscription Refused with error code %d ", rsp[4]);
      CleanUp();
      return false;
     }

無限ループの中で、証券会社のメッセージを待ち、Publishクラスの静的メソッドの助けを借りてメッセージを読み取ります。

msg += CPublish().ReadMessageRawBytes(inpkt);
               //printf("New quote arrived for MySPX500: %s", msg);
               //UpdateRates(msg);
               printf("New tick arrived for MySPX500: %s", msg);
               UpdateTicks(msg);

ティックの代わりにレートを更新するための開発コードがコメント付きで残されていることにお気づきでしょう。この方法でテストしたい場合は、これらの行のコメントを外してください。ティックではなくレートのみを更新する場合、気配値表示ウィンドウやグラフに表示されない情報があることに注意してください。ただし、RAM、CPU、帯域幅の消費を抑え、ビジュアルではなく取引自動化のためのデータにもっと関心があるのであれば、これは合理的な代替手段です。

UpdateRates関数は、パブリッシュサービスの対応する関数と連動して動作します。MQTTユーザープロパティを開発し、より信頼性の高いバイナリデータ交換をおこなう間、双方で文字列変換の代償を払っています。これが準ロードマップの最優先事項です。

void UpdateTicks(string new_ticks)
  {
   string new_ticks_arr[];

   StringSplit(new_ticks, 45, new_ticks_arr);

   MqlTick last_tick[1];

   last_tick[0].time          = StringToTime(new_ticks_arr[0]);
   last_tick[0].bid           = StringToDouble(new_ticks_arr[1]);
   last_tick[0].ask           = StringToDouble(new_ticks_arr[2]);
   last_tick[0].last          = StringToDouble(new_ticks_arr[3]);
   last_tick[0].volume        = StringToInteger(new_ticks_arr[4]);
   last_tick[0].time_msc      = StringToInteger(new_ticks_arr[5]);
   last_tick[0].flags         = (uint)StringToInteger(new_ticks_arr[6]);
   last_tick[0].volume_real   = StringToDouble(new_ticks_arr[7]);

   if(CustomTicksAdd("MySPX500", last_tick) < 1)
     {
      Print("Update ticks failed: ", _LastError);
     }
  }

サブスクライブサービスを開始すると、[エキスパート]タブのログにこのようなものが表示されるはずです。

MetaTrader 5[エクスパート]タブの5270エラーを示すログ出力

図05:MetaTrader 5[エクスパート]タブの5270エラーを示すログ出力

私たちのサブスクライブサービスは、砂漠で一人で話しています。MQTT証券会社を実行して、この問題を解決しましょう。


開発とテストのためのローカル証券会社の設定

開発に使用している環境は、Windows Subsystem For Linux (WSL)を使用しています。例を実行したいだけであれば、クライアントと証券会社の両方を同じマシンのループバックで実行することができます。ただし、パブリッシュサービスとサブスクライブサービスに異なるクライアントIDを使用する必要があります。しかし、サンプルを実行するだけでなく、開発環境を設定したい場合は、そのために別のマシンを設定することをお勧めします。ご存知のように、クライアント/サーバーアプリケーションを開発する場合、そしてこの問題のためにpub/subパターンをこのアーキテクチャに含める場合、それぞれの側がそれぞれのホスト上で実行されることが良い習慣と考えられています。こうすることで、接続、認証、その他のネットワークの問題を早期にトラブルシューティングできます。

WSLを使用したこの設定はとても簡単です。WSLのインストール、アクティベーション、構成については、1年前に公開した別の記事で詳しく説明しました。以下は、WSLでMosquitto証券会社を使用する際のヒントです。これらは、私たちの仕事を楽にした些細な点であり、もしかしたら、読者にも役に立つかもしれません。

  • WSLをデフォルトの状態で有効化し、推奨されている簡単な方法でMosquittoをインストールする場合、おそらくパッケージマネージャーを使ってインストールし、Ubuntuサービスとして実行することになるでしょう。つまり、WSLシェルを起動すると自動的にMosquittoが起動します。これは通常の使用には便利で良いのですが、開発にはMosquittoサービスを停止し、コマンドラインからverbose(-v)フラグを付けて手動で再起動することをお勧めします。こうすると、Mosquittoがフォアグラウンドで実行され、すべてのログをSTDOUTにリダイレクトするので、tailコマンドを使ってログを追う必要がなくなります。その上、ログには、verboseフラグを付けて起動すると含まれる情報の一部のみが含まれています。

Windows Subsystem For Linux:Mosquittoサーバーを停止し、Verboseフラグを付けて再起動する

図06:Windows Subsystem For Linux:Mosquittoサーバーを停止し、Verboseフラグを付けて再起動する

  • MetaTrader 5端末のネットワークで許可されたURLに、忘れずにWSLホスト名を含めてください。

Windows Subsystem For Linux:WSLホスト名の取得

図07:Windows Subsystem For Linux:WSLホスト名の取得

MetaTrader 5端末オプションメニューに許可されたURLを含める

図08:MetaTrader 5端末オプションメニューに許可されたURLを含める

  • セキュリティを強化するため、Mosquittoの最新バージョンのデフォルトではローカル接続、つまり同じマシンからの接続しか許可していません。他のマシンから接続するには(この文脈ではWindowsマシンが他のマシンとみなされる)、Mosquitto.confファイルにポート1883(または任意の他のポート)用の1行リスナーを含める必要があります。

Windows Subsystem For Linux:Mosquittoサーバーがポート1883をリッスンするように設定する

図09 :Windows Subsystem For Linux:Mosquittoサーバーがポート1883をリッスンするように設定する

  • 最後に、Mosquittoは非TLS接続の場合、デフォルトでポート1883で実行されることを覚えておいてください。MetaTrader 5端末では、80番ポート(HTTP)と443番ポート(HTTPS)のみ接続が可能です。そこで、80番ポートから1883番ポートにトラフィックをリダイレクトする必要があります。これは、redirというLinuxユーティリティをインストールすれば、1行の短いコマンドでできます。パッケージマネージャーを使ってインストールすることもできます。

Windows Subsystem For Linux:Redirユーティリティを使用したポートリダイレクションの実行

図10:Windows Subsystem For Linux:Redirユーティリティを使用したポートリダイレクションの実行

  • 許可されたURLのリストにWSLのホスト名を含めるかポートをリダイレクトするかしないと、接続が拒否され、おそらくこのようなエラーログが[エキスパート]タブにポップアップ表示されます。

MetaTrader 5エラー5273を示すログ出力

図11: エラー5273(ソケットからのデータ送受信の失敗)を示すMetaTrader 5ログ出力

Linuxに慣れていなくても、すべてがうまくいけば、この設定に10分から15分もかからないはずです。それが完了したら、サブスクライブサービスが意図したとおりに機能しているかどうかを確認することができます。

MetaTrader 5のログ出力がMQTT Subscribeサービスの開始とサブスクライブを示す

図12: MetaTrader 5のログ出力がMQTT Subscribeサービスの開始とサブスクライブを示す



PUBLISH MQTTサービスを記述する

パブリッシュサービスはサブスクライブサービスと同じ構造になっているので、時間を節約するため、ここでは繰り返しません。ただし、パブリッシュサービスとサブスクライブサービスで異なるクライアントIDを使用するよう、再度お伝えしておきます。(クライアントIDについて強調するのは、この些細な点を無視して開発者のいつもながらの怠惰なコピーペーストの繰り返しの結果、Mosquittoがメッセージを適切に配信しないという「バグではない」問題のデバッグに時間を浪費したからです)。

   uchar conn_pkt[];
   conn = new CConnect(host, port);
   conn.SetCleanStart(true);
   conn.SetKeepAlive(3600);
   conn.SetClientIdentifier("MT5_PUB");
   conn.Build(conn_pkt);

パブリッシュは停止するまで連続ループで実行されます。もちろん、パブリッシュサービスで使用しているのと同じトピックフィルタを設定することを忘れないでください。また、ティックの代わりにクォートを更新したい場合は、GetRatesへのペイロード割り当てのコメントを外して、GetLastTickへの割り当てをコメント化するだけで十分です。 

   do
     {
      uchar pub_pkt[];
      pub = new CPublish();
      pub.SetTopicName("MySPX500");
      //string payload = GetRates();
      string payload = GetLastTick();
      pub.SetPayload(payload);
      pub.Build(pub_pkt);
      delete(pub);
     //ArrayPrint(pub_pkt);
      if(!SendPublish(pub_pkt))
        {
         return -1;
         CleanUp();
        }
      ZeroMemory(pub_pkt);
      Sleep(500);
     }
   while(!IsStopped());

サブスクライブサービスにおけるネットワークコードとMQTT固有のコードの割合について述べたことは、ここでも当てはまります。QoS 0を使用しているため、PUBACKを受信することはないからです。つまり、パケットを構築し、接続し、送信するだけの問題なのです。

パブリッシュサービスでは、少なくともユーザープロパティが完全に実装され、自信を持ってバイナリデータを交換できるようになるまでは、文字列変換の代償も支払うことになることは注目に値します。

string GetLastTick()
  {
   MqlTick last_tick;
   if(SymbolInfoTick("#USSPX500", last_tick))
     {
      string format = "%G-%G-%G-%d-%I64d-%d-%G";
      string out;
      out = TimeToString(last_tick.time, TIME_SECONDS);
      out += "-" + StringFormat(format,
                                last_tick.bid, //double
                                last_tick.ask, //double
                                last_tick.last, //double
                                last_tick.volume, //ulong
                                last_tick.time_msc, //long
                                last_tick.flags, //uint
                                last_tick.volume_real);//double
      Print(last_tick.time,
            ": Bid = ", last_tick.bid,
            " Ask = ", last_tick.ask,
            " Last = ", last_tick.last,
            " Volume = ", last_tick.volume,
            " Time msc = ", last_tick.time_msc,
            " Flags = ", last_tick.flags,
            " Vol Real = ", last_tick.volume_real
           );
      Print(out);
      return out;
     }
   else
      Print("Failed to get rates for #USSPX500");
   return "";
  }

MetaTrader 5端末でパブリッシュサービスを開始すると、[エキスパート]タブのログに次のようなものが表示されます。

MQTT Publishサービスの開始と接続を示すMetaTrader 5のログ出力

図13: MQTT Publishサービスの開始と接続を示すMetaTrader 5のログ出力

Mosquitto証券会社のトピックを購読し、証券会社の冗長出力を確認することができます。

Windows Subsystem For Linux:VerboseフラグでMosquittoサーバーのログを表示する

図14:Windows Subsystem For Linux:VerboseフラグでMosquittoサーバーのログを表示する

メッセージが正常に受信されると、サブスクリプションタブに表示されます。

Windows Subsystem For Linux:mosquitto_subユーティリティの出力

図15:Windows Subsystem For Linux:mosquitto_subユーティリティの出力


カスタム銘柄の更新

両サービスを準備し、それぞれを証券会社と照合した後、MetaTrader 5端末で実行し、苦労の成果を確認しましょう。

MetaTrader 5ナビゲータでのMQTT pub/subサービス開始の確認

図16: MetaTrader 5ナビゲータでのMQTT pub/subサービス開始の確認

すべてがうまくいけば、気配値表示ウィンドウのティックタブに以下のようなものが表示されるはずです。

MetaTrader 5気配値表示[ティック]タブでのカスタム銘柄ティック更新

図 17: MetaTrader 5気配値表示[ティック]タブでのカスタム銘柄ティック更新

カスタム銘柄グラフでは、ティックの更新も反映されるはずです。

MetaTrader 5マーケットグラフでのカスタム銘柄ティック更新

図18:MetaTrader 5マーケットグラフでのカスタム銘柄ティック更新

[エキスパート]タブのログには、少し冗長な出力があるはずです。デバッグ情報を残しておいたからです。そこでは、交換されているパケットのPrintArray関数の出力などを見ることができます。この出力によって、Wiresharkのようなパケットアナライザーを適用してパケットの内容を確認する必要がなくなるかもしれません。

MetaTrader 5[エキスパート]タブの開発とデバッグ用MQTTサービスログ出力

図19: MetaTrader 5[エキスパート]タブの開発とデバッグ用MQTTサービスログ出力



結論

この記事では、MQTTを介して証券会社と口座間でリアルタイムの相場を共有するための機能的なコードを紹介しました。デモは同じMetaTrader 5のインスタンスとマシンで実行されていますが、MQTTの柔軟性と堅牢性を示しています。私たちのネイティブMQTTクライアントは、限られた自由時間の中でまだ開発中です。QoS_2の実装やユーザープロパティなどの課題に対処し、6月までに完全準拠版を完成させるのが目標です。4月末までにコードを改良し、GitHubで公開する予定です。

MQL5の専門知識の有無に関わらず、オープンソースプロジェクトへの貢献者は歓迎です。私たちのテスト駆動開発アプローチは、エキスパート以外にもエラーのない開発を保証します。基本的なテストから始まり、MQL5のドキュメントを着実に進めてきました。すべてのMQTT標準を満たすまで、クライアントの改良に取り組んでいます。

初心者の方でも参加できます。読者の協力は貴重です。ご参加ください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14677

添付されたファイル |
mqtt-headers.zip (23.78 KB)
mqtt-services.zip (3.51 KB)
mqtt-tests.zip (19.52 KB)
知っておくべきMQL5ウィザードのテクニック(第14回):STFによる多目的時系列予測 知っておくべきMQL5ウィザードのテクニック(第14回):STFによる多目的時系列予測
データのモデリングに「空間」と「時間」の両方の測定基準を使用する空間的時間的融合は、主にリモートセンシングや、私たちの周囲をよりよく理解するための他の多くの視覚ベースの活動で有用です。発表された論文のおかげで、トレーダーへの可能性を検証することで、その活用に斬新なアプローチを取ります。
MQL5で自己最適化エキスパートアドバイザーを構築する MQL5で自己最適化エキスパートアドバイザーを構築する
どのような市場にも対応できる専門的なエキスパートアドバイザー(EA)を構築します。
どんな市場でも優位性を得る方法 どんな市場でも優位性を得る方法
現在の技術レベルに関係なく、取引したいどのような市場でも先んじることができる方法を学びましょう。
データサイエンスと機械学習(第21回):ニューラルネットワークと最適化アルゴリズムの解明 データサイエンスと機械学習(第21回):ニューラルネットワークと最適化アルゴリズムの解明
ニューラルネットワーク内部で使用される最適化アルゴリズムを解明しながら、ニューラルネットワークの核心に飛び込みます。この記事では、ニューラルネットワークの可能性を最大限に引き出し、モデルを精度と効率の新たな高みへと押し上げる重要なテクニックご紹介します。