English Русский 中文 Español Deutsch Português
preview
MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ

MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ

MetaTrader 5統合 | 8 8月 2023, 10:11
316 0
Jocimar Lopes
Jocimar Lopes

はじめに

「...戦略は間違いなく、まず動作させること、次に正しく動作させること、そして最後に高速化することです。スティーヴン・カーティス・ジョンソンとブライアン・カーニハンの「The C Language and Models for Systems Programming」(バイトマガジン掲載)(1983年8月)

MetaTraderの2つ以上のインスタンス間でリアルタイムデータを共有することは、トレーダーと口座マネージャーの間で共通のニーズです。おそらく、最も需要の高いデータ共有は、いわゆる「取引コピー機」によるコピー取引に関連したものですが、口座情報の共有、銘柄のスクリーニング、機械学習のための統計データなどのリクエストを簡単に見つけることができます。この機能は、ネットワークソケット名前付きパイプを使用したプロセス間通信、Webサービス、ローカルファイル共有、および既にテストおよび/または開発されているその他のソリューションを使用することによって取得できます。

ソフトウェア開発では一般的なことですが、これらのソリューションにはそれぞれ、使いやすさ、安定性、信頼性、開発とメンテナンスに必要なリソースの点で長所と短所があります。つまり、ユーザーの要件と予算に応じて、それぞれが異なる費用対効果の関係を示します。

この記事では、MQTTプロトコルのクライアントサイドの実装の最初のステップについて報告します。これは、マシン間のリアルタイムデータ共有というニーズを正確に満たすために設計されたテクノロジーであり、高いパフォーマンス、低帯域幅消費、低リソース要件、低価格を備えています。


MQTTとは

「MQTTは、クライアントサーバーのパブリッシュ/サブスクライブ・メッセージングトランスポート・プロトコルです。MQTTは軽量、オープン、シンプルで、簡単に実装できるように設計されています。これらの特性により、小さなコードフットプリントが必要な場合やネットワーク帯域幅が重要視されるマシンツーマシン(M2M)やモノのインターネット(IoT)コンテキストでの通信などの制約のある環境を含む、多くの状況での使用に最適です。」

上記の定義は、2013年以来オープンスタンダードとしてプロトコルの所有者および開発者であるOASISからのものです。

「2013年に、IBMは仕様への軽微な変更のみが受け入れられることを保証する憲章とともにMQTTv3.1をOASIS仕様団体に提出しました。IBMから標準のメンテナンスを引き継いだ後、OASISは2014年10月29日にバージョン3.1.1をリリースしました。いくつかの新機能を追加したMQTTバージョン5へのより実質的なアップグレードが2019年3月7日にリリースされました。」(Wikipedia)

IBMは、センサーで石油パイプラインを監視し、衛星経由でデータを遠隔制御センターに送信するという産業界のニーズに対応するために、1999年にプロトコルの開発を開始しました。Andy Stanford-Clark博士とプロトコルを共同作成したArlen Nipper氏によると、目標は、これらのコントロールセンターにリアルタイムのデータストリームを提供することでした。

「当時、私は通信業界で1,200ボーのダイヤルアップ回線と300ボーのダイヤルアップ回線、および帯域幅が非常に制約されたVSATで通信していました。私たちがやろうとしたのは、当時IBMのミドルウェアであったMQ Integratorを採用して、この2つを結びつけることでした。」

技術スタックの制限と高価なネットワークコストにより、堅牢、高速、低コストになるように設計されているにもかかわらず、継続的なセッション認識を備えたサービス品質のデータ配信を提供する必要があり、これにより、信頼性の低い、または断続的なインターネット接続にも対応できます。

MQTTはバイナリプロトコルとして、メモリと処理要件の点で非常に効率的です。最小のMQTTパケットが2バイトしかないことはさらに興味深いことです。

MQTTはリクエスト/レスポンスではなくパブリッシュ/サブスクライブモデル(pub/sub)に基づいているため、双方向です。つまり、クライアント/サーバー接続が確立されると、HTTP WebRequestの場合のように、事前のリクエストを必要とせずに、いつでもデータがクライアントからサーバー、およびサーバーからクライアントに流れることができます。データが到着すると、サーバーはそれをすぐに受信側に転送します。この機能により、エンドポイント間の遅延を最小限に抑えることができるため、リアルタイムデータ交換の基礎となります。一部のベンダーは、ミリ秒単位の遅延を宣伝しています。

データのタイプ、形式、コーデックなどは関係ありません。MQTTはデータに依存しません。ユーザーは生のバイトからテキスト形式(XML、JSONオブジェクト)、プロトコルバッファ、写真、ビデオフラグメントなどを送受信できます。

クライアントとサーバー間の対話のほとんどは非同期でおこなうことができます。つまり、MQTTはスケーラブルです。IoT業界では、数千、さらには数百万のデバイスが接続され、リアルタイムでデータを交換するという話は珍しくありません。 

このプロトコルはTLSと互換性があり認証および認可メカニズムが組み込まれているため、そのメッセージはエンドポイント間で暗号化することができ、通常は暗号化されます。

当然のことながら、MQTTは高水準の仕様セットであるだけでなく、いくつかの業界で広く採用されているテクノロジーです。

「MQTTは現在、自動車、製造、通信、石油・ガスなどの幅広い業界で使用されています。」(mqtt.org)


メインコンポーネント

pub/subは非常によく知られたメッセージ交換モデルです。クライアントはサーバーに接続し、トピックに関するメッセージ公開します。その後、そのトピックにサブスクライブしているすべてのクライアントがメッセージを受信します。これがこのモデルの基本的な仕組みです。 

サーバーはブローカーとして機能し、クライアントの間に立ってサブスクリプションとパブリケーションの両方を受け取ります。TCP/IPは基礎となるトランスポートプロトコルであり、クライアントはTCP/IPとMQTTを理解する任意のデバイスです。メッセージは通常、JSONまたはXMLペイロードですが、生のバイトシーケンスを含む任意のペイロードにすることができます。

トピックは、名前空間のような階層構造を記述するために使用されるUTF-8でエンコードされた文字列です。

  • office/machine01/account123456

  • office/machine02/account789012

  • home/machine01/account345678

ハッシュ(#)をワイルドカード文字として使用してトピックをサブスクライブすることもできます。たとえば、homeからmachine01のすべての口座をサブスクライブするには、次のようにします。
  • home/machine01/# 
または、officeからすべてのマシンをサブスクライブするには、次のようにします。
  • office/# 

MQTTはマシン間の会話のために開発されたもので、IoTのコンテキストで広く使用されており、堅牢で高速、そして安価です。しかし、これが取引環境にどのようなメリットや改善をもたらすのか、と疑問に思われるかもしれません。MetaTraderでのMQTTの使用例にはどのようなものがあるのでしょうか。

上で述べたように、「取引コピー機」は、取引環境におけるMQTTの最も明白な使用例です。しかし、機械学習パイプラインにリアルタイムデータを供給したり、Webサービスから取得したリアルタイムデータに応じてEAの動作を変更したり、任意のデバイスからMetaTraderアプリケーションをリモート制御したりすることを考える人もいるかもしれません。

マシン間のデータのリアルタイムストリームが必要なシナリオでは、MQTTを考慮することがあります。


MetaTraderでMQTTを使用する方法

モバイルおよび組み込みデバイス用のそれぞれのバリアントを含む、最も一般的な汎用言語用の無料のオープンソースMQTTクライアントライブラリがあります。したがって、MQL5からMQTTを使用するには、C、C++、またはC#からそれぞれのDLLを生成してインポートできます。 

共有されるデータが取引取引/口座情報に限定されており、比較的長い遅延が許容される場合、別のオプションは、PythonMQTTクライアントライブラリとMQL5Pythonモジュールを「ブリッジ」として使用することです。 

しかし、ご存知のとおり、DLLの使用はMQL5エコシステムにいくつかのマイナスの影響を及ぼします。最も注目すべきは、マーケットがDLLに依存するEAを受け入れないことです。また、DLLに依存するEAは、MQL5クラウド上でバックテスト最適化を実行することはできません。DLLの依存関係とPythonブリッジを回避するための理想的なソリューションは、MetaTrader用のクライアントサイドのネイティブMQTTライブラリを開発することです。

これは今後数週間でおこなう予定です。MetaTrader5のクライアントサイドでMQTT-v5.0プロトコルを実装します。

MQTTクライアントの実装は、他のネットワークプロトコルと比較すると「比較的簡単」であると考えられます。しかし、比較的簡単であることが必ずしも簡単であることであるとは限りません。したがって、ボトムアップのアプローチで、テスト駆動開発(TDD)から始め、できればコミュニティのテストとフィードバックを取り入れていきます。

TDDは、ほとんどあらゆるものに対して「誇大広告」または「バズワード」として使用される可能性があります(そして頻繁に使用されています)が、一連の正式な仕様がある場合、つまり標準化されたネットワークプロトコルの場合に非常によく適合します。

ボトムアップのアプローチを採用する、いわばベイビーステップで侵入することで、巨大な仕様に立ち向かうことができます。MQTTの仕様はそれほど大規模なものではなく、ブローカーサイドと比較するとクライアントサイドが最も簡単です。しかし、特にいくつかの追加機能が含まれているバージョン5.0以降では、独自の複雑さがあります。

私たちには有給の時間もスキルを組み合わせるためのチームもありませんので、ここではベイビーステップが最善の方法のように思えます。メッセージを送信するにはどうすればよいでしょうか。一体どんなことを書けばいいのでしょうか。まず動作させ、高速に動作させることを考える前に、うまく動作するように改善するにはどうすればよいでしょうか。


巨大な仕様、ベイビーステップ:MQTTプロトコルの理解と分析

すべてではないにしても多くのネットワークプロトコルの標準と同様、MQTTプロトコルは、いわゆるパケットで送信されるデータを分割することによって機能します。したがって、受信側が各種類のパケットの意味を知っていれば、受信したパケットの種類に応じて適切な運用動作を採用できます。MQTT用語では、パケットの種類は制御パケットタイプと呼ばれ、それぞれに最大3つの部分があります。

  • すべてのパケットに存在する固定ヘッダ

  • 一部のパケットに存在する可変ヘッダ

  • ペイロード(一部のパケットにのみ)

MQTT-v5.0には15の制御パケットタイプがあります。

表1:MQTT制御パケットのタイプ(OASIS仕様の表)

名前 流れの方向 詳細
Reserved
0 禁止
予約
CONNECT 1 クライアント->サーバー 接続要求
CONNACK 2 サーバー->クライアント 接続確認
PUBLISH 3 クライアント<->サーバー メッセージ公開
PUBREC 5 クライアント<->サーバー

公開を受信(QoS2配信パート1)
PUBREL 6 クライアント<->サーバー
公開をリリース(QoS2配信パート2)
PUBCOMP 7 クライアント<->サーバー

公開完了(QoS2配信パート3)
SUBSCRIBE 8 クライアント->サーバー サブスクライブ要求
SUBACK 9 サーバー->クライアント サブスクライブ承認
UNSUBSCRIBE 10 クライアント->サーバー サブスクライブ解除要求
UNSUBACK 11 サーバー->クライアント
サブスクライブ解除承認
PINGREQ 12 クライアント->サーバー PING要求
PINGRESP 13 サーバー->クライアント PING応答
DISCONNECT 14 クライアント<->サーバー 切断通知
AUTH 15 クライアント<->サーバー 認証交換

すべての制御パケットの固定ヘッダは同じ形式です。

図1:MQTT固定ヘッダ形式

MQTT固定ヘッダ形式

クライアントとサーバー間の接続が確立されるまでは何もすることができません。標準に次のような明確な記述があります。

「クライアントによってサーバーへのネットワーク接続が確立された後、クライアントからサーバーに送信される最初のパケットはCONNECTパケットでなければなりません。」 

CONNECTパケットの固定ヘッダをどのように形式設定する必要があるかを見てみましょう。

図2:CONNECTパケットのMQTT固定ヘッダ形式

CONNECTパケットのMQTT固定ヘッダ形式

これを2バイトで埋める必要があります。最初のバイトはバイナリ値00010000でなければならず、2番目のバイトはいわゆる「残りの長さ」の値を持つ必要があります。

標準では残りの長さを次のように定義しています。

「可変ヘッダとペイロードのデータを含む、現在の制御パケット内に残っているバイト数を表すVariable Byte Integer。残りの長さには、残りの長さをエンコードするために使用されるバイトは含まれません。パケットサイズはMQTT制御パケットの合計バイト数であり、これは固定ヘッダの長さに残りの長さを加えたものに等しくなります。」(強調は私たちのものです)

標準には、このVariable Byte Integerのエンコードスキームも定義されています。

「Variable Byte Integerは、最大127の値に単一バイトを使用するエンコードスキームを使用してエンコードされます。より大きな値は次のように処理されます。各バイトの下位7ビットはデータをエンコードし、最上位ビットは表現内に後続のバイトがあるかどうかを示すために使用されます。したがって、各バイトは128個の値と「継続ビット」をエンコードします。Variable Byte Integerフィールドの最大バイト数は4です。エンコードされた値は、値を表すために必要な最小バイト数を使用しなければなりません。」

まったく、一度に多くの情報を吸収する必要があるようです。まだ、2番目のバイトを埋めようとしているところです。

幸いなことに、この標準では、「非負の整数(X)をVariable Byte Integerエンコードスキームにエンコードするためのアルゴリズム」が提供されています。

do
   encodedByte = X MOD 128
   X = X DIV 128
   // if there are more data to encode, set the top bit of this byte
   if (X > 0)
      encodedByte = encodedByte OR 128
   endif
   'output' encodedByte
while (X > 0)

「MODはモジュロ演算子(C言語の「%」)、DIVは整数の除算(C言語の「/」)、ORはビット単位の論理和(C言語の「|」)です。」

さて、次が揃いました。

  • すべての制御パケットタイプのリスト 

  • 2バイトのCONNECTパケットの固定ヘッダの形式

  • 最初のバイトの値

  • 2番目のバイトを埋めるVariable Byte Integerをエンコードするアルゴリズム

最初のテストの作成を開始します。 

注:ボトムアップのTDDアプローチを採用しているため、実装前にテストを作成します。最初から、1)合格しないテストを作成し、2)テストに合格するために必要なコードのみを実装し、3)必要に応じてコードをリファクタリングすることを想定できます。最初の実装が単純であっても、醜くても、あるいはパフォーマンスが悪いように見えても、問題ではありません。機能するコードができたら、これらの問題に対処します。パフォーマンスは私たちのタスクリストの最後にあります。

早速、MetaEditorを開いて、次の内容を含むTestFixedHeaderという名前のスクリプトを作成してみましょう。

#include <MQTT\mqtt.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print(TestFixedHeader_Connect());
  }
//---
bool TestFixedHeader_Connect()
  {
   uchar content_buffer[]; //empty
//---
   uchar expected[2];
   expected[0] = 1; //pkt type
   expected[1] = 0; //remaining length
//---
   uchar fixed_header[];
//---
   GenFixedHeader(CONNECT, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return false;
     }
   return true;
  }

また、関数の開発を開始するmqtt.mqhヘッダを作成し、以下のコードを書き入れます。
void GenFixedHeader(uint pkt_type, uchar& buf[], uchar& head[])
  {
   ArrayFree(head);
   ArrayResize(head, 2);
//---
   head[0] = uchar(pkt_type);
//---
//Remaining Length
   uint x;
   x = ArraySize(buf);
   do
     {
      uint encodedByte = x % 128;
      x = (uint)(x / 128);
      if(x > 0)
        {
         encodedByte = encodedByte | 128;
        }
      head[1] = uchar(encodedByte);
     }
   while(x > 0);
  }
//+------------------------------------------------------------------+
enum ENUM_PKT_TYPE
  {
   CONNECT      =  1, // Connection request
   CONNACK      =  2, // Connect acknowledgment
   PUBLISH      =  3, // Publish message
   PUBACK       =  4, // Publish acknowledgment (QoS 1)
   PUBREC       =  5, // Publish received (QoS 2 delivery part 1)
   PUBREL       =  6, // Publish release (QoS 2 delivery part 2)
   PUBCOMP      =  7, // Publish complete (QoS 2 delivery part 3)
   SUBSCRIBE    =  8, // Subscribe request
   SUBACK       =  9, // Subscribe acknowledgment
   UNSUBSCRIBE 	=  10, // Unsubscribe request
   UNSUBACK     =  11, // Unsubscribe acknowledgment
   PINGREQ      =  12, // PING request
   PINGRESP     =  13, // PING response
   DISCONNECT  	=  14, // Disconnect notification
   AUTH         =  15, // Authentication exchange
  };

スクリプトを実行すると、[エキスパート]タブに次の内容が表示されます。

図3:出力テスト固定ヘッダ-テスト合格

出力テストの固定ヘッダ-True

テストが機能していることを確認するには、テストが不合格であることを確認する必要があります。したがって、期待される変数は変更しないまま、content_buffer変数で表される入力を変更することを強くお勧めします。[エキスパート]タブに次のようなものが出力されるはずです。

図4:出力テスト固定ヘッダ-テスト不合格

出力テスト固定ヘッダ-テスト不合格

とにかく、mqtt.mqhヘッダのコードと同様に、この時点でのテストは脆弱であると想定できます。問題ありません。まだ始まったばかりですが、前進するにつれて、それらを改善し、間違いから学び、その結果としてスキルを向上させる機会が得られます。

これで、TestFixedHeader_Connect関数を他のパケットタイプに複製できるようになりました。サーバーからクライアントへのフローのみの方向を持つCONNACK、PUBACK、SUBACK、UNSUBACK、PINGRESPは無視します。これらのACK(S)およびping応答パケットヘッダはサーバーによって生成され、後で処理します。

テストが期待どおりに動作していることを確認するには、不合格になると予想されるテストを含める必要があります。これらのテストは不合格になるとtrueを返します
#include <MQTT\mqtt.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print(TestFixedHeader_Connect());
   Print(TestFixedHeader_Connect_RemainingLength1_Fail());
   Print(TestFixedHeader_Publish());
   Print(TestFixedHeader_Publish_RemainingLength1_Fail());
   Print(TestFixedHeader_Puback());
   Print(TestFixedHeader_Puback_RemainingLength1_Fail());
   Print(TestFixedHeader_Pubrec());
   Print(TestFixedHeader_Pubrec_RemainingLength1_Fail());
   Print(TestFixedHeader_Pubrel());
   Print(TestFixedHeader_Pubrel_RemainingLength1_Fail());
   Print(TestFixedHeader_Pubcomp());
   Print(TestFixedHeader_Pubcomp_RemainingLength1_Fail());
   Print(TestFixedHeader_Subscribe());
   Print(TestFixedHeader_Subscribe_RemainingLength1_Fail());
   Print(TestFixedHeader_Puback());
   Print(TestFixedHeader_Puback_RemainingLength1_Fail());
   Print(TestFixedHeader_Unsubscribe());
   Print(TestFixedHeader_Unsubscribe_RemainingLength1_Fail());
   Print(TestFixedHeader_Pingreq());
   Print(TestFixedHeader_Pingreq_RemainingLength1_Fail());
   Print(TestFixedHeader_Disconnect());
   Print(TestFixedHeader_Disconnect_RemainingLength1_Fail());
   Print(TestFixedHeader_Auth());
   Print(TestFixedHeader_Auth_RemainingLength1_Fail());
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestFixedHeader_Connect()
  {
   uchar content_buffer[]; //empty
//---
   uchar expected[2];
   expected[0] = 1; //pkt type
   expected[1] = 0; //remaining length
//---
   uchar fixed_header[];
//---
   GenFixedHeader(CONNECT, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return false;
     }
   Print(__FUNCTION__);
   return true;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestFixedHeader_Connect_RemainingLength1_Fail()
  {
   uchar content_buffer[]; //empty
   ArrayResize(content_buffer, 1);
   content_buffer[0] = 1;
//---
   uchar expected[2];
   expected[0] = 1; //pkt type
   expected[1] = 0; //remaining length should be 1
//---
   uchar fixed_header[];
//---
   GenFixedHeader(CONNECT, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return true;
     }
   Print(__FUNCTION__);
   return false;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestFixedHeader_Publish()
  {
   uchar content_buffer[]; //empty
//---
   uchar expected[2];
   expected[0] = 3; //pkt type
   expected[1] = 0; //remaining length
//---
   uchar fixed_header[];
//---
   GenFixedHeader(PUBLISH, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return false;
     }
   Print(__FUNCTION__);
   return true;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool TestFixedHeader_Publish_RemainingLength1_Fail()
  {
   uchar content_buffer[]; //empty
   ArrayResize(content_buffer, 1);
   content_buffer[0] = 1;
//---
   uchar expected[2];
   expected[0] = 3; //pkt type
   expected[1] = 0; //remaining length should be 1
//---
   uchar fixed_header[];
//---
   GenFixedHeader(PUBLISH, content_buffer, fixed_header);
//---
   if(!ArrayCompare(expected, fixed_header) == 0)
     {
      Print(__FUNCTION__);
      for(uint i = 0; i < expected.Size(); i++)
        {
         Print("expected: ", expected[i], " result: ", fixed_header[i]);
        }
      return true;
     }
   Print(__FUNCTION__);
   return false;
  }
.
.
.
(omitted for brevity)

さて、これは、大量の定型コード、数十回の反復入力かコピー/ペーストです。 

確かにそうですが、長期的には良い見返りが得られるでしょう。これらの単純な(単純すぎる)テストを実施することで、開発のための一種のセーフティネットを構築しているのです。それらは私たちが次をできるようにしてくれます。 

  • 目の前の仕事に集中し続ける

  • オーバーエンジニアリングを避ける

  • リグレッションバグを発見する

メモ:添付ファイルをそのまま使用するのではなく、自分で作成することを強くお勧めします。最初からどれだけの小さな「無害な」エラーを検出できるかがわかります。クライアントの運用動作を進めるにつれて、これらのテスト(およびその他のより具体的なテスト)がその価値を証明します。それに加えて、よくある技術的負債、つまりテストを最後に作成したままにすることも避けています。通常、最後まで放置しておくと、テストは決して書かれません

図5:出力テストの固定ヘッダ-すべて合格

出力テスト固定ヘッダ-すべて合格

2バイトのCONNECTヘッダがMQTTブローカーによって有効なヘッダとして認識されるかどうかを確認してみましょう。


開発とテストのためにMQTTブローカー(およびクライアント)をインストールする方法

オンラインで利用できる実稼働MQTTブローカーは多数あり、そのほとんどは開発とテストの目的で、ある種の「サンドボックス」URLを提供します。お気に入りの検索エンジンで「MQTTブローカー」を簡単に検索するだけで、いくつかのブローカーを見つけることができます。

ただし、この時点ではクライアントは初期段階にあります。パケットアナライザーを使用してネットワークトラフィックを捕捉しないと、応答を受信して​​読み取ることはできません。このツールは後で役に立ちますが、現時点では仕様に準拠したMQTTブローカーを開発マシンにインストールするだけで十分です。そのため、ログをチェックして対話の結果を確認できます。理想的には、クライアントのIP以外のIPを取得するには、仮想マシンにインストールする必要があります。開発とテストに異なるIPを持つブローカーを使用することで、接続と認証の問題に早期に対処できます。

繰り返しになりますが、Windows、Linux、Macにはいくつかのオプションがあります。私は、Windows Subsystem For Linux (WSL)にMosquittoをインストールしました。Mosquittoは無料でオープンソースであることに加えて、MQTTトピックをパブリッシュおよびサブスクライブするための、開発に非常に便利な2つのコマンドラインアプリケーションmosquitto_pubmosquitto_subが付属しているため、非常に便利です。いくつかのエラーをクロスチェックできるように、Windows開発マシンにもインストールしました。

MetaTraderでは、[ツール]>[オプション]メニューの[エキスパートアドバイザー]タブに外部URLをリストする必要があります。MetaTraderからはポート80または443のみにアクセスできることに注意してください。したがって、このWSLにブローカーをインストールする手順に従う場合は、そのホストIPを含めることを忘れずに、またポート80に到着するネットワークトラフィックをデフォルトのMQTT(およびMosquitto)ポートである1883にリダイレクトすることも忘れないでください。このようなポートリダイレクトを簡単かつ安定した方法で実行するredirと呼ばれるツールがあります。

図6:MetaTrader5ダイアログ-WebリクエストURLを許可

MetaTrader5ダイアログ-WebリクエストURLを許可


WSLIPを取得するには、以下のコマンドを実行します。

図7:WSLのホスト名取得コマンド

図7:WSLのホスト名取得コマンド


Mosquittoをインストールすると、起動時に「サービス」として開始するように自動設定されます。したがって、WSLを再起動するだけで、デフォルトのポート1883でMosquittoが起動します。

redirを使用してネットワークトラフィックをポート80から1883にリダイレクトするには、以下のコマンドを実行します。

図8:「redir」によるネットワークトラフィックのリダイレクト

「redir」コマンドラインを使用したポートリダイレクト


そして最後に、2バイトのCONNECT固定ヘッダが、仕様に準拠したMQTTブローカーによって有効なMQTTヘッダとして認識されるかどうかを確認できます。「スクラッチ」スクリプトを作成し、次のコードを貼り付けるだけです。(gethostname -Iコマンドの出力に従って、broker_ip変数のIPアドレスを変更することを忘れないでください。)

#include <MQTT\mqtt.mqh>

string broker_ip = "172.20.155.236";
int broker_port = 80;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   int socket = SocketCreate();
   if(socket != INVALID_HANDLE)
     {
      if(SocketConnect(socket, broker_ip, broker_port, 1000))
        {
         Print("Connected ", broker_ip);
         //---
         uchar fixed_header[];
         uchar content_buffer[]; //empty
         //---
         GenFixedHeader(CONNECT, content_buffer, fixed_header);
         //---
         if(SocketSend(socket, fixed_header, ArraySize(fixed_header)) < 0)
           {
            Print("Failed sending fixed header ", GetLastError());
           }
        }
     }
  }

[エキスパート]タブの出力には次のように表示されるはずです...

図9:ローカルブローカー接続の出力

出力ローカルブローカー接続

…そして、Mosquittoログに次の出力が記録されます。

図10:ローカルブローカー接続の出力-Mosquittoログ

ローカルブローカー接続の出力-Mosquittoログ

CONNECT固定ヘッダはMosquitoによって認識されましたが、<不明>クライアントは「プロトコルエラーのため」すぐに切断されました。このエラーは、プロトコル名、プロトコルレベル、およびその他の関連メタデータを含む可変ヘッダをまだ含めていないために発生しました。次のステップでこれを修正します。

注:上記のコマンドの最初でわかるように、tail-f {pathToLogFile}コマンドを使用しています。開発中にこれを使用すると、ファイルを開いて再読み込みすることなく、Mosquitoログの更新を追跡できます。

次のステップでは、ブローカーとの安定した接続を維持するために、CONNECT可変ヘッダなどを実装します。また、メッセージをPUBLISHし、ブローカーから返されたCONNACKパケットとそれに関連する理由コードも処理します。この次のステップでは、接続フラグを埋めるための興味深いビット単位の演算がおこなわれます。この次のステップでは、クライアントとブローカーの会話の結果として現れる複雑さに対処するためにテストを大幅に改善することも必要になります。


結論

この記事では、MQTTpub/subリアルタイムメッセージ共有プロトコル、その起源、および主要コンポーネントの概要を説明しました。また、取引コンテキストでのリアルタイムメッセージングに対するMQTTの使用例と、C、C++、C#から生成されたDLLをインポートするか、MQTT Pythonライブラリを使用してMetaTrader 5で自動操作にMetaTrader 5 Pythonモジュールを介してMQTTを使用する方法についても指摘しました。

MetaQuotesマーケットおよびMetaQuotes Cloud TesterでのDLLの使用に課せられた制限を考慮して、テスト駆動開発(TDD)アプローチを利用したネイティブMQL5 MQTTクライアントの実装に関する最初のステップも提案し、説明しました。


役に立つかもしれない参考文献

すべての車輪を再発明する必要はありません。他の言語用のMQTTクライアントを作成する際に開発者が直面する最も一般的な課題に対するソリューションの多くは、オープンソースライブラリ/SDKとして利用できます。

  • ソフトウェア(ブローカー、ライブラリ、ツールを含む)のリスト
  • GitHub上のMQTTに関連するいくつかのリソースのリスト

経験豊富なMQL5開発者で提案がある方は、以下にコメントを残してくださればとても有難いです。


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

添付されたファイル |
TestFixedHeader.mq5 (19.48 KB)
mqtt.mqh (2.19 KB)
Goertzelアルゴリズムによるサイクル分析 Goertzelアルゴリズムによるサイクル分析
この記事では、MQL5でGoertzel(ゲルツェル)アルゴリズムを実装するコードユーティリティを紹介し、このテクニックを価格相場の分析に利用し、可能な戦略を開発するための2つの方法を探ります。
MQL5オブジェクト指向プログラミング(OOP)について MQL5オブジェクト指向プログラミング(OOP)について
開発者として、私たちは、特に異なる動作をするオブジェクトがある場合に、コードを重複せずに再利用可能で柔軟なソフトウェアを作成し開発する方法を学ぶ必要があります。これは、オブジェクト指向プログラミングのテクニックと原則を使うことでスムーズにおこなうことができます。この記事では、MQL5オブジェクト指向プログラミングの基本を紹介し、この重要なトピックの原則とプラクティスをソフトウェアでどのように使用できるかを説明します。
MQL5のインタラクティブGUIで取引チャートを改善する(第2回):移動可能なGUI (II) MQL5のインタラクティブGUIで取引チャートを改善する(第2回):移動可能なGUI (II)
MQL5で移動可能なGUIを作成するための詳細なガイドで、取引戦略やユーティリティでの動的なデータ表現の可能性を引き出しましょう。オブジェクト指向プログラミングの基本原理を理解し、同じチャート上に単一または複数の移動可能なGUIを簡単かつ効率的に設計実装する方法を発見してください。
シンプルな平均回帰取引戦略 シンプルな平均回帰取引戦略
平均回帰とは、トレーダーが価格が何らかの形の均衡に戻ることを期待する逆張り取引の一種で、通常は平均値または別の中心的傾向の統計によって測定されます。