English Русский Español Deutsch Português
preview
MQL5-Telegram統合エキスパートアドバイザーの作成(第4回):関数コードのモジュール化による再利用性の向上

MQL5-Telegram統合エキスパートアドバイザーの作成(第4回):関数コードのモジュール化による再利用性の向上

MetaTrader 5トレーディングシステム | 30 10月 2024, 09:54
210 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

本連載の前回の記事では、MetaTrader 5からTelegramにキャプション付きチャートスナップショットを送信するプロセスを詳述しました。既存の方法は効果的であったものの、やや単純で柔軟性に欠けており、スクリーンショットのキャプチャやメッセージ対応の形式への変換・エンコード、そしてTelegramへの送信に必要なコンポーネントが多く結合されていました。こうした設定で一応の成果は得られましたが、コードの冗長化と管理の煩雑さが問題となっていました。この実装を改善するためにはどうすればいいのでしょうか。よりモジュール化されたコードベースに移行します。これは、より柔軟で保守性の高いシステムへの第一歩です。

本連載第4部では、コードのモジュール化(英語)によりプログラムの再利用性を高めることに焦点を当て、コードモジュール化の原則を具体的に解説するとともに、それが本プロジェクトにどのように適用されるかを議論します。次に、既存のMQL5スクリプトを明確に定義された関数群に再編成する手順を紹介します。最終的には、旧来の一体型プログラムを継続使用するか、新しいモジュール型エキスパートアドバイザー(EA)に移行するかを選択することになります。

ここからは、現在のコードを計画的に変更し、プログラム構造に新しい整理方法を適用していきます。コードは各タスクごとに個別の関数に分割され、各関数が特定の役割(メッセージ送信、スクリーンショット撮影、送信用のデータ形式エンコードなど)を担います。新しい構造により、コード内で各機能がどのように組み合わさるか、また各関数が重複を避けつつ簡便に更新・拡張される方法についても解説します。

最後に、モジュール化された戦略コードのテストおよび検証手順について触れます。各関数の動作確認、システム全体のパフォーマンス評価、従来のコードとの結果比較などをおこないます。モジュール化(英語)という言葉を使っていますが、これはEAをどれほど理解しやすく保守しやすくするかということです。この記事の終わりまでには、この変更が開発における重要なステップである理由、具体的な実施方法、および期待される利点について明確に理解いただけるでしょう。以下に、EA作成におけるトピックを挙げます。

  1. モジュール化の必要性を理解する
  2. メッセージ送信コードのリファクタリング
  3. スクリーンショット機能のモジュール化
  4. モジュール関数のテストと実装
  5. 結論

最後には、整然とした効率的なMetaQuotes Language 5(MQL5)-Telegram EAが完成します。このモジュール化された構造化コードは、統合が容易で、柔軟性に富み、更新や新機能追加の労力が従来の手法よりもはるかに少なくなります。メッセージ送信、スクリーンショットキャプチャ、データ保持を効率的かつスケーラブルな方法で管理し、将来の機能拡張にも対応できる強固な基盤が築かれます。それでは、始めていきましょう。


モジュール化の必要性を理解する

ここでは、ソフトウェア工学における基本手法の1つであるコードモジュール化の考え方について検証します。モジュール化されたプログラムは、より小さく、管理しやすい部分に分割され、それぞれがほぼ自己完結します。これらの部分(またはモジュール)は、特定の機能を実行し、明確に定義された方法で他のモジュールと相互作用します。本記事では、プログラムの大部分はまだ書く必要があるので、プログラムをモジュール化するための基本的な原則や概念についても見ていきます。

開発段階とメンテナンス段階の両方で、モジュール化の効果は大きく表れます。コードを書く時には、開発者は一度に1つのモジュールに集中できるため、各モジュールのコードは次のモジュールに移る前に実装され、テストされ、デバッグされます。システム稼動後に変更が必要になった場合も、通常は1つのモジュールのみで変更が完了します。あるモジュールの変更は他のモジュールにほとんど影響を与えないため、全体的な結果として、より安定したシステムが実現し、変更や修正にかかるコストも低く抑えられます。例えば、私たちのMQL5 EAでは、特定のイベントが発生したときにメッセージを送信することができるモジュールと、スクリーンショットを撮影し保存するモジュールがあります。もしスクリーンショット機能を拡張する場合も、メッセージ送信を行うモジュールに影響を与えることなく実施できるでしょう。

プログラムの保守性を確保することは見かけより単純で、実は非常に重要です。プログラムを機能させるためのすべての要素をきれいに整理し秩序ある状態に保つことが求められます。ここまで述べてきたことのほとんどは、この本質的な段落への前提です。関係者を満足させるために必要なすべての要素(あなた用、私用、あちらの機能用など)を含むモジュールを作成することほど、保守性を高めるものはありません。なぜなら、各モジュールが期待どおりに機能することにより、保守性を助長するだけでなく、再利用性も確保されるからです。モジュールが意図通りに機能すれば、それで十分です。

MQL5 EAを明確に定義された関数とクラスに分割することで、コードのモジュール化を実証します。テキストメッセージの送信、スクリーンショットの撮影、システムに供給するデータの再構築など、きちんと指定された仕事を達成するために、これらのモジュールをどのように組み合わせるかを見ていきます。最終的には、これらの変更がEAの効率性、保守性、スケーラビリティをどのように向上させるかを理解していただければと思います。


メッセージ送信コードのリファクタリング

まず初めにおこなうことは、ロジックを入力できるカスタム関数またはモジュールを作成し、それをカプセル化して整理し、最大限に再利用できるようにすることです。最初の関数は、単純なメッセージの送信を担当します。

//+------------------------------------------------------------------+
//|    FUNCTION TO SEND SIMPLE MESSAGE                               |
//+------------------------------------------------------------------+

void sendSimpleMessage(){

//...

}

ここでは、Telegramメッセージの送信処理を「sendSimpleMessage」という関数内にカプセル化しています。このモジュール構造により、EA内のコードの保守、管理、および再利用が容易になります。この関数はvoid型として値を返さない構造にしており、Telegramへのメッセージ送信を専任とするものです。これにより、操作の成否を関数内部で処理できるため、コードが複数のif文で複雑化することを防げます。このカプセル化の利点は、メインプログラムがメッセージ送信を要する際、Telegram APIの操作方法に煩わされることなく、ただこの関数を呼び出すだけで済む点にあります。

さらに、メッセージ送信操作の柔軟性を確保するため、この関数は必要に応じて異なるテキストを送信できるよう自動的に再利用が可能な構造にします。そのために、APIのURL、ボットのトークン、チャットIDなどのパラメータを含める必要があります。

void sendSimpleMessage(string custom_message,const string api_url,
                       const string bot_token,const string chat_id,
                       int timeout=10000){

//...

}

ここでは、複雑な添付ファイルやデータ処理なしで、プレーンなメッセージをTelegramに送信するという目的を反映した、「sendSimpleMessage」という名前のvoid関数を定義します。この関数は、custom_message、api_url、bot_token、chat_idの4つの必須入力パラメータと、timeoutの1つのオプション入力パラメータで満たされます。理解しやすくするために、パラメータを構造的に分解してみましょう。

  • custom_message:これは文字列パラメータで、Telegramに送信したい実際のテキストメッセージを保持します。
  • api_url:Telegram Bot APIのベースURLを含む文字列パラメータです。このURLは、正しいAPIエンドポイントをリクエストするために使用されます。
  • bot_token:ボットの認証とメッセージの送信の承認に必要な、ボットの一意のトークンを保持する別の文字列パラメータです。
  • chat_id:この文字列パラメータは、メッセージを送信するTelegramチャットまたはチャンネルの一意の識別子を指定します。
  • timeout:これはオプションの整数パラメータで、 リクエストがタイムアウトしたとみなす前に、関数がTelegram APIからの応答を待つ時間をミリ秒単位で設定します。デフォルト値は10,000ミリ秒(10秒)に設定されていますが、必要に応じてカスタムタイムアウト値を指定することができます。

入力引数のいくつかにキーワードconstが使われていることにお気づきでしょうか。これは、渡された値が最終的なものであり、関数の本体内で変更・修正・置換されることがないことを意味します。このため、関数内部で誤った上書きが発生しないことが保証されます。次に、単純なメッセージを送信するコードスニペットをデフォルトのフォームから関数に転送するだけです。

   char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request

   string message = custom_message;
   
   const string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
      "&text=" + message;
   
   // Send the web request to the Telegram API
   int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);

まず、dataとresという2つの配列を宣言します。data配列は空の状態で初期化されますが、これはWebリクエストにおいて特にデータを送信しないためです。送信されるのはURLパラメータとしてのメッセージのみです。r一方、res配列にはリクエスト後にサーバーから返される応答データが格納されます。さらに、resHeadersという文字列変数も宣言しており、これはTelegram APIから返送される応答ヘッダーの内容を保持するためのものです。

次に、入力パラメータからcustom_messageを取り出し、message変数に代入します。これによって、もし必要であれば、関数内でメッセージを扱ったり、伝えたりすることができるようになります。

ベース「api_url」、「/bot」エンドポイント、認証の「bot_token」、受信者チャットの「chat_id」など、いくつかのコンポーネントを連結してAPIリクエストURLを構築します。そして、送信するメッセージテキストをURLパラメータとして追加します。その結果、API呼び出しに必要なすべてのデータが含まれた完全なURLが完成します。

最後に、WebリクエストロジックをWebRequest関数に渡します。この関数は、Telegram APIに対してHTTP POSTリクエストを送信します。先ほど構築したAPI用のURLを使用し、リクエストのタイムアウト値はデフォルトで10秒(またはユーザーが指定した別の値)に設定され、これはリクエストが諦めて処理を続行するまでの応答待機時間を決定します。リクエストは空のデータ配列(JSON: JavaScript Object Notation形式の空のオブジェクトでもよい)で送信され、APIから返される応答は結果配列と結果ヘッダー文字列に格納されます。

最後に、Webリクエストの応答ステータスのチェックロジックを追加します。

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
   }

MetaTrader 5からTelegramにシンプルなメッセージを送信するための最終的な関数コードは以下の通りです。

//+------------------------------------------------------------------+
//|    FUNCTION TO SEND SIMPLE MESSAGE                               |
//+------------------------------------------------------------------+

void sendSimpleMessage(string custom_message,const string api_url,
                       const string bot_token,const string chat_id,
                       int timeout=10000){
   
   char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request

   string message = custom_message;
   
   const string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
      "&text=" + message;
   
   // Send the web request to the Telegram API
   int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);
   
   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
   }
   
}

これが完璧に機能することを確認するために、OnInitイベントハンドラに移り、不要なコードスニペットをコメントアウトし、関数を呼び出してみましょう。関数を呼び出すには、その関数名を入力し、必要なパラメータを指定します。

   string msg = "EA INITIALIZED ON CHART " + _Symbol;  // Message to send, including the chart symbol
   sendSimpleMessage(msg,TG_API_URL,botTkn,chatID,10000);

ここでは、sendSimpleMessage関数を呼び出して、Telegram上のチャットにメッセージを配信します。この文字列は、「EA INITIALIZED ON CHART」と現在のチャートの銘柄(_Symbol)を単に連結したものです。このメッセージの受信者は、EAが特定のチャート上で初期化されたことを通知されます。

送信したいテキストメッセージを定義したら、関数sendSimpleMessageを呼び出します。関数に4つの引数を渡します。最初の引数は送信したいテキストメッセージだけなので、msgという名前になっています。2番目の引数はTG_API_URLという定数で、これはTelegram Bot APIのベースURLです。3番目の引数はボットのアクセストークン(botTkn)で、4番目の引数(chatID)はボットがメッセージを送信するTelegram内のチャットまたはチャンネルのIDです。最後に、タイムアウト値として10秒(10000ミリ秒)を指定します。Telegramサーバーがそれ以上経っても応答しない場合は、失敗とみなし、エラーコードを返します。テストでは、次のようなメッセージが表示されました。

シンプルなTelegramメッセージ

うまくいきました。同じようなアクションを実行するのに、それほど長いコードスニペットは必要ないことがお分かりいただけたでしょう。必要なのは、担当する関数を呼び出し、それぞれの引数を渡すだけです。チャートの時間枠や期間を知らせるシンプルなメッセージを送りましょう。

   string new_msg = "THE CURRENT TIMEFRAME IS "+EnumToString(_Period);
   sendSimpleMessage(new_msg,TG_API_URL,botTkn,chatID,10000);

ここでは、new_msgという新しい文字列変数を定義します。この新しい変数は、「THE CURRENT TIMEFRAME IS」というテキストと、_Period(現在のチャートの時間枠)の値の文字列バージョンを結合することによって確立されます。これはEnumToString関数を使っておこなわれ、_Periodの値を人間が読める形に変換します。例えば、チャートが1時間の時間枠に設定されている場合、new_msgには「THE CURRENT TIMEFRAME IS PERIOD_H1」というテキストが含まれます。その後、単純なメッセージを送信する同じ関数が呼び出され、それで終わりです。これがいかに簡単かわかるでしょう。テストを実行すると、次のように出力されます。

新しいシンプルメッセージ

コードの送信がいかに簡単であったかがわかります。そのために使ったコードはわずか2行です。次に、複雑な符号化されたメッセージの送信に移ります。関数に大きな変化はないでしょう。同じロジックを継承しています。しかし、複雑なメッセージを送信するため、その結果生じるかもしれないエラーに対処します。したがって、単にvoid関数を宣言するのではなく、整数データ型の関数を用意し、呼び出されると失敗か成功を示す特定のコードを返すようにします。このためには、グローバルスコープでエラーコードを定義しなければなりません。

#define FAILED_CODE -1
#define SUCCEEDED_CODE +1

ここでは、#defineプリプロセッサダイレクティブを使って、2つの定数FAILED_CODEとSUCCEEDED_CODEを定義します。これらの定数には、演算の結果を表すために特定の整数値が割り当てられています。「FAILED_CODE」は -1 に設定され、失敗を表し、「SUCCEEDED_CODE」は +1 に設定され、成功を表します。これらの定数は、自分が適切と考えるものであれば何でも構いません。これらの宣言の後、関数を構築します。

int sendEncodedMessage(string custom_message,const string api_url,
                       const string bot_token,const string chat_id,
                       int timeout=10000){

//...

}

ここでは、sendEncodedMessageという名前の整数型関数を定義しています。Webリクエストデータは変わりません。しかし、応答ステータスの成否を確認し、必要な措置を講じる必要があります。

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
      return (SUCCEEDED_CODE);
   }

ここで、応答ステータスが成功で、メッセージの送信に成功した場合、「SUCCEEDED_CODE」を返します。一方、応答ステータスが失敗の場合は、FAILED_CODEを返します。

   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
      return (FAILED_CODE);
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      return (FAILED_CODE);
   }

最後に、ここまでがすべて成功した場合に、成功したコードを以下のように返す必要があります。

   return (SUCCEEDED_CODE);

サブ関数がいずれもチェックされず、関数が整数を返す必要がある可能性があるため、最後に成功を返すことは非常に重要です。リターンコードなしでプログラムをコンパイルしようとすると、以下のようなエラーが発生します。

戻り関数のエラーメッセージ

したがって、複雑なメッセージの送信を担当する完全な関数コードは以下のようになります。

//+------------------------------------------------------------------+
//|    FUNCTION TO SEND ENCODED MESSAGE                              |
//+------------------------------------------------------------------+

//#define FAILED_CODE -1
//#define SUCCEEDED_CODE +1

int sendEncodedMessage(string custom_message,const string api_url,
                       const string bot_token,const string chat_id,
                       int timeout=10000){
   char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request

   string message = custom_message;
   
   const string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
      "&text=" + message;
   
   // Send the web request to the Telegram API
   int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);
   
   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
      return (SUCCEEDED_CODE);
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
      return (FAILED_CODE);
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      return (FAILED_CODE);
   }
   
   return (SUCCEEDED_CODE);
}

それでは、絵文字を連結したセグメントで口座情報をTelegramに送信し、その応答を見てみましょう。これについても同じコード構造を採用するが、具体的に何をするのかを簡単に説明します。

   ////--- Account Status Update:
   double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   string complex_msg = "\xF680 EA INITIALIZED ON CHART " + _Symbol + "\xF680"
                +"\n\xF4CA Account Status \xF4CA"
                +"\nEquity: $"
                +DoubleToString(accountEquity,2)
                +"\nFree Margin: $"
                +DoubleToString(accountFreeMargin,2);
   
   string encloded_msg = UrlEncode(complex_msg);
   complex_msg = encloded_msg;
   sendEncodedMessage(complex_msg,TG_API_URL,botTkn,chatID,10000);


まず、AccountInfoDoubleを使用し、引数ACCOUNT_EQUITYACCOUNT_MARGIN_FREEをそれぞれ渡して、口座のエクイティと余剰証拠金にアクセスします。これらは、リアルタイムで口座の資本と余剰証拠金を取得します。complex_msgという詳細なメッセージを作成し、その中でチャートの銘柄、口座のエクイティと余剰証拠金を使用し、絵文字でフォーマットします。Telegram APIを使ってメッセージを送信しますが、その前にHTTPで送信しても安全であることを確認する必要があります。これは、UrlEncode関数でメッセージをエンコードすることでおこないます。メッセージを送信すると、Telegramアプリにはこのように表示されます。

複雑なメッセージ

Telegramで複雑なメッセージが正常に受信されていることがわかります。これでメッセージ送信関数は完成です。先行する機能を関数内にカプセル化することで、コードをすっきりさせ、メッセージ送信処理を複数の場所で簡単に再利用できるようにすることは、ある程度明らかです。これは、画像送信や誤差処理用の関数を追加するなど、コードのモジュール化を進める際に特に役立つでしょう。これは次のセクションで説明します。


スクリーンショット関数のモジュール化

ここでは、必要なパラメータを受け取り、チャート画像をTelegramに送信する関数を構築する必要があります。そのコード構造は、エンコードされたメッセージの送信に使ったものと同じになります。

int sendScreenShot(string screenshot_name,const string telegram_url,
                   const string bot_token,const string chat_id,
                   string caption=""){

//...

}

sendScreenShotという名前の整数型関数を宣言します。この関数は複数のパラメータを取り込み、柔軟性とモジュール性を確保します。

  • screenshot_nameパラメータは、送信されるスクリーンショットファイルの名前を指し、異なるスクリーンショットを指定することができます。
  • telegram_url、bot_token、chat_idは、Telegram APIとの通信に必要なコア入力であり、この関数はさまざまなボット構成やTelegramアカウントに適応できます。
  • オプションのパラメータcaptionを使用すると、スクリーンショットに説明テキストを添付することができ、送信前にスクリーンショットに注釈を付けることができるため、機能性が向上します。

同じコード構造が維持されているので、ここではリターンロジックだけに集中します。最初のインスタンスは、画像コンテンツを開いて読み込もうとするインスタンスです。

   int screenshot_Handle = INVALID_HANDLE;
   screenshot_Handle = FileOpen(screenshot_name,FILE_READ|FILE_BIN);
   if(screenshot_Handle == INVALID_HANDLE){
      Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!");
      return(FAILED_CODE);
   }

ここで、Telegramに送信するスクリーンショットデータを開くことができない場合、関数からFAILED_CODEを返し、ファイルアクセスの問題でスクリーンショットを送信する操作が続行できないことを示します。次に、応答ステータスを確認するための同じロジックを継承し、それぞれのメッセージログとステータスコードを以下に示します。

   // Send the web request to the Telegram API
   int send_res = WebRequest("POST",URL,HEADERS,10000, DATA, res, resHeaders);

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM SCREENSHOT FILE SENT SUCCESSFULLY");
      return (SUCCEEDED_CODE);
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", telegram_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM SCREENSHOT FILE");
      return (FAILED_CODE);
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      return (FAILED_CODE);
   }
   return (SUCCEEDED_CODE);

チャートのスクリーンショットファイルを送信する関数は以下の通りです。

//+------------------------------------------------------------------+
//|    FUNCTION TO SEND CHART SCREENSHOT FILES                       |
//+------------------------------------------------------------------+

int sendScreenShot(string screenshot_name,const string telegram_url,
                   const string bot_token,const string chat_id,
                   string caption=""){
   
   int screenshot_Handle = INVALID_HANDLE;
   screenshot_Handle = FileOpen(screenshot_name,FILE_READ|FILE_BIN);
   if(screenshot_Handle == INVALID_HANDLE){
      Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!");
      return(FAILED_CODE);
   }
   
   else if (screenshot_Handle != INVALID_HANDLE){
      Print("SCREENSHOT WAS SAVED & OPENED SUCCESSFULLY FOR READING.");
      Print("HANDLE ID = ",screenshot_Handle,". IT IS NOW READY FOR ENCODING.");
   }
   
   int screenshot_Handle_Size = (int)FileSize(screenshot_Handle);
   if (screenshot_Handle_Size > 0){
      Print("CHART SCREENSHOT FILE SIZE = ",screenshot_Handle_Size);
   }
   uchar photoArr_Data[];
   ArrayResize(photoArr_Data,screenshot_Handle_Size);
   FileReadArray(screenshot_Handle,photoArr_Data,0,screenshot_Handle_Size);
   if (ArraySize(photoArr_Data) > 0){
      Print("READ SCREENSHOT FILE DATA SIZE = ",ArraySize(photoArr_Data));
   }
   FileClose(screenshot_Handle);
   
   //ArrayPrint(photoArr_Data);
   
   //--- create boundary: (data -> base64 -> 1024 bytes -> md5)
   //Encodes the photo data into base64 format
   //This is part of preparing the data for transmission over HTTP.
   uchar base64[];
   uchar key[];
   CryptEncode(CRYPT_BASE64,photoArr_Data,key,base64);
   if (ArraySize(base64) > 0){
      Print("Transformed BASE-64 data = ",ArraySize(base64));
      //Print("The whole data is as below:");
      //ArrayPrint(base64);
   }
   
   //Copy the first 1024 bytes of the base64-encoded data into a temporary array
   uchar temporaryArr[1024]= {0};
   //Print("FILLED TEMPORARY ARRAY WITH ZERO (0) IS AS BELOW:");
   //ArrayPrint(temporaryArr);
   ArrayCopy(temporaryArr,base64,0,0,1024);
   //Print("FIRST 1024 BYTES OF THE ENCODED DATA IS AS FOLLOWS:");
   //ArrayPrint(temporaryArr);
      
   //Create an MD5 hash of the temporary array
   //This hash will be used as part of the boundary in the multipart/form-data
   uchar md5[];
   CryptEncode(CRYPT_HASH_MD5,temporaryArr,key,md5);
   if (ArraySize(md5) > 0){
      Print("SIZE OF MD5 HASH OF TEMPORARY ARRAY = ",ArraySize(md5));
      Print("MD5 HASH boundary in multipart/form-data is as follows:");
      ArrayPrint(md5);
   }

   //Format MD5 hash as a hexadecimal string &
   //truncate it to 16 characters to create the boundary.
   string HexaDecimal_Hash=NULL;//Used to store the hexadecimal representation of MD5 hash
   int total=ArraySize(md5);
   for(int i=0; i<total; i++){
      HexaDecimal_Hash+=StringFormat("%02X",md5[i]);
   }
   Print("Formatted MD5 Hash String is: \n",HexaDecimal_Hash);
   HexaDecimal_Hash=StringSubstr(HexaDecimal_Hash,0,16);//truncate HexaDecimal_Hash string to its first 16 characters
   //done to comply with a specific length requirement for the boundary
   //in the multipart/form-data of the HTTP request.
   Print("Final Truncated (16 characters) MD5 Hash String is: \n",HexaDecimal_Hash);
   
   //--- WebRequest
   char DATA[];
   string URL = NULL;
   URL = telegram_url+"/bot"+bot_token+"/sendPhoto";
   //--- add chart_id
   //Append a carriage return and newline character sequence to the DATA array.
   //In the context of HTTP, \r\n is used to denote the end of a line
   //and is often required to separate different parts of an HTTP request.
   ArrayAdd(DATA,"\r\n");
   //Append a boundary marker to the DATA array.
   //Typically, the boundary marker is composed of two hyphens (--)
   //followed by a unique hash string and then a newline sequence.
   //In multipart/form-data requests, boundaries are used to separate
   //different pieces of data.
   ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
   //Add a Content-Disposition header for a form-data part named chat_id.
   //The Content-Disposition header is used to indicate that the following data
   //is a form field with the name chat_id.
   ArrayAdd(DATA,"Content-Disposition: form-data; name=\"chat_id\"\r\n");
   //Again, append a newline sequence to the DATA array to end the header section
   //before the value of the chat_id is added.
   ArrayAdd(DATA,"\r\n");
   //Append the actual chat ID value to the DATA array.
   ArrayAdd(DATA,chat_id);
   //Finally, Append another newline sequence to the DATA array to signify
   //the end of the chat_id form-data part.
   ArrayAdd(DATA,"\r\n");

   // EXAMPLE OF USING CONVERSIONS
   //uchar array[] = { 72, 101, 108, 108, 111, 0 }; // "Hello" in ASCII
   //string output = CharArrayToString(array,0,WHOLE_ARRAY,CP_ACP);
   //Print("EXAMPLE OUTPUT OF CONVERSION = ",output); // Hello
   
   Print("CHAT ID DATA:");
   ArrayPrint(DATA);
   string chatID_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_UTF8);
   Print("SIMPLE CHAT ID DATA IS AS FOLLOWS:",chatID_Data);   


   //--- Caption
   string CAPTION_STRING = NULL;
   CAPTION_STRING = caption;
   if(StringLen(CAPTION_STRING) > 0){
      ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
      ArrayAdd(DATA,"Content-Disposition: form-data; name=\"caption\"\r\n");
      ArrayAdd(DATA,"\r\n");
      ArrayAdd(DATA,CAPTION_STRING);
      ArrayAdd(DATA,"\r\n");
   }
   //---
   
   ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
   ArrayAdd(DATA,"Content-Disposition: form-data; name=\"photo\"; filename=\"Upload_ScreenShot.jpg\"\r\n");
   ArrayAdd(DATA,"\r\n");
   ArrayAdd(DATA,photoArr_Data);
   ArrayAdd(DATA,"\r\n");
   ArrayAdd(DATA,"--"+HexaDecimal_Hash+"--\r\n");
   
   Print("FINAL FULL PHOTO DATA BEING SENT:");
   ArrayPrint(DATA);
   string final_Simple_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_ACP);
   Print("FINAL FULL SIMPLE PHOTO DATA BEING SENT:",final_Simple_Data);

   string HEADERS = NULL;
   HEADERS = "Content-Type: multipart/form-data; boundary="+HexaDecimal_Hash+"\r\n";
   
   Print("SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.");
   
   //char data[];  // Array to hold data to be sent in the web request (empty in this case)
   char res[];  // Array to hold the response data from the web request
   string resHeaders;  // String to hold the response headers from the web request
   //string msg = "EA INITIALIZED ON CHART " + _Symbol;  // Message to send, including the chart symbol
   
   //const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
   //   "&text=" + msg;
      
   // Send the web request to the Telegram API
   int send_res = WebRequest("POST",URL,HEADERS,10000, DATA, res, resHeaders);

   // Check the response status of the web request
   if (send_res == 200) {
      // If the response status is 200 (OK), print a success message
      Print("TELEGRAM SCREENSHOT FILE SENT SUCCESSFULLY");
      return (SUCCEEDED_CODE);
   }
   else if (send_res == -1) {
      // If the response status is -1 (error), check the specific error code
      if (GetLastError() == 4014) {
         // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", telegram_url, " TO THE TERMINAL");
      }
      // Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM SCREENSHOT FILE");
      return (FAILED_CODE);
   }
   else if (send_res != 200) {
      // If the response status is not 200 or -1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      return (FAILED_CODE);
   }
   return (SUCCEEDED_CODE);
}

スクリーンショットを送信する関数はありますが、スクリーンショットのファイルを取得する関数はありません。まず、プログラムが添付されているチャートの画像ファイルを取得する関数を作成します。

//+------------------------------------------------------------------+
//|    FUNCTION TO GET SCREENSHOT OF CURRENT CHART                   |
//+------------------------------------------------------------------+

int getScreenshot_of_Current_Chart(string screenshot_name){
   
   //--- First delete an instance of the screenshot file if it already exists
   if(FileIsExist(screenshot_name)){
      FileDelete(screenshot_name);
      Print("Chart Screenshot was found and deleted.");
      ChartRedraw(0);
   }
   
   ChartScreenShot(0,screenshot_name,1366,768,ALIGN_RIGHT);
   
   // Wait for 30 secs to save screenshot if not yet saved
   int wait_loops = 60;
   while(!FileIsExist(screenshot_name) && --wait_loops > 0){
      Sleep(500);
   }
   
   if(!FileIsExist(screenshot_name)){
      Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!");
      return (FAILED_CODE);
   }
   else if(FileIsExist(screenshot_name)){
      Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE.");
      return (SUCCEEDED_CODE);
   }
   return (SUCCEEDED_CODE);
}

ここでは、MetaTrader 5で現在のチャートのスクリーンショットをキャプチャして保存する処理を実行する整数型関数getScreenshot_of_Current_Chartを宣言します。この関数は、screenshot_nameという1つのパラメータを取り、このパラメータにはスクリーンショットを保存するファイル名を指定します。まず、screenshot_nameという名前のファイルがすでに存在するかどうかを確認します。存在する場合、そのファイルを削除します。なぜなら、もし既存のファイルを削除しなければ、保存されたスクリーンショットがつい最近削除されたファイルと同じ名前で保存されてしまうという上書き問題が避けられないからです。

また、スクリーンショットを撮る前にChartRedraw関数を呼び出してチャート表示を更新します。次に、現在の状態のチャートのスクリーンショットを取得するために、ChartScreenShot関数を呼び出し、ファイルに付ける名前、希望する寸法、希望する配置を伝えます。続いて、ファイルの存在を確認するためにwhileループを使用する必要がありました。次のステップに進む前に、ファイルが表示されるまで最大30秒間待機しますが、スクリーンショットが表示されるためにプロセスを遅くすることは絶対に望ましくありませんでした。

この間隔が過ぎてもファイルが存在しない場合は、スクリーンショットが保存されなかったことを伝えるエラーメッセージを生成し、明確なFAILED_CODEを返します。しかし、ファイルが見つかれば、成功メッセージを発し、明確なSUCCEEDED_CODEを返します。要するに、2つの可能性のある結果を許容し、それらに明確なラベルを付けるのです。カスタムチャートを開いてスナップショットを撮る機能も同様のロジックを継承しています。

//+------------------------------------------------------------------+
//|    FUNCTION TO GET SCREENSHOT OF A NEW CHART                     |
//+------------------------------------------------------------------+

int getScreenshot_of_New_Chart(string screenshot_name,string symbol_name,
                               ENUM_TIMEFRAMES period_name){
   
   //--- First delete an instance of the screenshot file if it already exists
   if(FileIsExist(screenshot_name)){
      FileDelete(screenshot_name);
      Print("Chart Screenshot was found and deleted.");
      ChartRedraw(0);
   }
   
   long chart_id=ChartOpen(symbol_name,period_name);
   ChartSetInteger(chart_id,CHART_BRING_TO_TOP,true);
   // update chart
   int wait=60;
   while(--wait>0){//decrease the value of wait by 1 before loop condition check
      if(SeriesInfoInteger(symbol_name,period_name,SERIES_SYNCHRONIZED)){
         break; // if prices up to date, terminate the loop and proceed
      }
   }

   ChartRedraw(chart_id);
   ChartSetInteger(chart_id,CHART_SHOW_GRID,false);
   ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false);
   ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BEAR,clrRed);
   ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BULL,clrBlue);
   ChartSetInteger(chart_id,CHART_COLOR_BACKGROUND,clrLightSalmon);

   ChartScreenShot(chart_id,screenshot_name,1366,768,ALIGN_RIGHT);
   Print("OPENED CHART PAUSED FOR 10 SECONDS TO TAKE SCREENSHOT.");
   Sleep(10000); // sleep for 10 secs to see the opened chart
   ChartClose(chart_id);
   
   // Wait for 30 secs to save screenshot if not yet saved
   int wait_loops = 60;
   while(!FileIsExist(screenshot_name) && --wait_loops > 0){
      Sleep(500);
   }
   
   if(!FileIsExist(screenshot_name)){
      Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!");
      return (FAILED_CODE);
   }
   else if(FileIsExist(screenshot_name)){
      Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE.");
      return (SUCCEEDED_CODE);
   }
   return (SUCCEEDED_CODE);
}

ここでは、画像名、銘柄名、開くチャートの期間の3つのパラメータをとる整数型関数を宣言します。ここまでで、スクリーンショットを取得してTelegramに送信するのに必要な関数は揃いました。現在のチャートのスクリーンショットを取得して送信し、何が得られるか見てみましょう。そのためには、以下のコードスニペットが適用されます。

   getScreenshot_of_Current_Chart(SCREENSHOT_FILE_NAME);
   sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,NULL);

ここでは、保存されたスクリーンショットの取得と送信をそれぞれ担当する2つの関数を呼び出し、必要な入力パラメータを渡すだけです。ロジックがいかに小さくなったかわかるでしょう。たった2行のコードで十分な効果があります。コンパイルすると、次のような出力が得られます。

現在のチャートの最初の画像

現在のチャートのスクリーンショットファイルをTelegramチャットに受信できることがわかります。ここまでで、必要なことはほぼすべて網羅されたので、関数の失敗や成功に対抗するためにリターンコードがどのように使われるかを見ることができます。この練習では、新鮮なチャートロジックを使用します。

   int get_screenshot_new_chart_result = getScreenshot_of_New_Chart(SCREENSHOT_FILE_NAME,_Symbol,_Period);
   if (get_screenshot_new_chart_result == FAILED_CODE){
      string result_msg = "NEW CHART SCREENSHOT COULDN'T BE SAVED. REVERT NOW.";
      Print(result_msg);
      sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
      return (INIT_FAILED);
   }
   else if (get_screenshot_new_chart_result == SUCCEEDED_CODE){
      string result_msg = "SUCCESS. NEW CHART SCREENSHOT WAS SAVED. CONTINUE NOW.";
      Print(result_msg);
      sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
      string sending_msg = "\x2705\SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.";
      sending_msg += "\n\x270C\YOU SHOULD RECEIVE THE IMAGE FILE WITHIN 10 SECONDS";
      string encoded_sending_msg = UrlEncode(sending_msg);
      Print(encoded_sending_msg);
      sendEncodedMessage(encoded_sending_msg,TG_API_URL,botTkn,chatID,10000);
      
   }


ここでは、関数getScreenshot_of_New_Chartからの出力を管理します。この関数は、新しいチャートのスクリーンショットを撮って保存する作業をおこないます。スクリーンショットファイルの名前、チャートの現在の銘柄、時間枠の明確なパラメータを使用して関数を呼び出します。この関数の結果は、get_screenshot_new_chart_resultと呼ぶ変数に格納されます。この結果が成功なら、プログラムの次の部分に進みます。もし失敗したら、賢明と思われる方法で失敗を処理します。

結果としてFAILED_CODEを受け取ると、スクリーンショットの保存に失敗したことを示します。このような状況では、スクリーンショットの保存プロセスで何か問題が発生したことをユーザーに明確に示すエラーメッセージを生成します。このメッセージは端末に出力され、sendSimpleMessage関数を使ってTelegramチャットにも送信されます。そして、戻りコードとして INIT_FAILEDを返し、操作が成功しなかったこと、そして次の操作を試みるべきではないことをユーザーに知らせます。これで初期化プロセスが終了します。

一方、結果が「SUCCEEDED_CODE」であれば、スクリーンショットが正常に保存されたことを意味します。スクリーンショットを撮った後、端末が成功コマンドを送信したというメッセージを用意して出力します。次に、sendSimpleMessage関数を利用して、スクリーンショットが保存されたこと、そしてファイルを近日中に受け取る予定であることをユーザーに通知します。ユーザーにメッセージを送るプロセスは明確で、簡潔で、適切に実行されています。スクリーンショットをユーザーに送信するコマンドは成功し、ユーザーは約10秒後にファイルを受け取るはずです。操作ログでは、次のような出力があります。

操作ログログ

Telegramのチャットでは、次のような出力があります。

受信画像ファイル

今すぐ、Telegramに複数のメッセージのインスタンスを送信して簡単にチャットできることがお分かりいただけるでしょう。あとは、スクリーンショット送信ロジックの関数を呼び出して画像ファイルを転送するだけです。これは以下のロジックで実現されます。

   sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,NULL);

コードを実行すると、次のような結果が得られます。

新チャートのスクリーンショット

うまくいきました。受け取った画像にキャプションがないことにお気づきでしょうか。これは、キャプションフィールドをNULLにしたためです。キャプションを含めるには、キャプションフィールドを定義して関数に渡すだけです。デフォルトのキャプションは以下のように使用されます。

   //--- Caption
   string CAPTION = NULL;
   CAPTION = "Screenshot of Symbol: "+Symbol()+
             " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+
             ") @ Time: "+TimeToString(TimeCurrent());
   
   sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,CAPTION);

コンパイルすると、説明どおりのデフォルトパラメータで新しく開いたカスタムチャートのスクリーンショットが表示されますが、さらに重要なのは、スクリーンショットの銘柄、時間枠、およびキャプチャされてTelegramに送信された時刻を示すキャプションが付いていることです。

キャプション付きスクリーンショット

うまくいきました。Telegramチャットと通信するための関数を利用した、完全に機能するプログラムを手に入れました。MQL5とTelegramのブリッジとして機能するという点では、この実装は信頼性が高く柔軟性があり、後でより複雑な統合を構築できる優れたモジュール設計です。次のセクションでは、これについて明示します。


モジュール関数のテストと実装

このセクションでは、個々の関数の構築から、シグナル確認が特定の応答を引き出すような実際の取引状況における関数の応用に焦点を移します。今の目的は、スクリーンショットのキャプチャやメッセージの送信など、モジュール化した関数が、これから構築する大きなフレームワークの中で一緒に動くかどうかを検証することです。EAのロジックを再利用可能な関数に組み込むことで、特定の条件が満たされたときに特定の関数をトリガーするという原則に基づいて動作するEAのロジックを維持しながら、チャートのスクリーンショットを送信したり、アカウントのステータスを更新したりする目的をより適切に果たすことができます。

このテーマでは、取引シグナルが確認されたときにこれらのモジュール関数を呼び出し、すべてのコンポーネントが実際のシナリオで効率的に機能することを保証する方法を説明します。これらの関数の信頼性を厳しく確認し、繰り返し実行するブレッドボードを通し、シグナル確認メッセージを正確かつタイムリーにTelegramに送信するという主要なタスクを達成しつつ、誤差管理の厳しさに耐えられるかどうかを確認します。こうすることで、コードの機能を検証するだけでなく、ほぼ完全に堅牢で、誤差条件を優雅に処理する売買シグナル管理システムを作るためのステップとして利用することができます。あとは初期化コードスニペットをシグナル生成セクションに移すだけです。買いシグナルの場合、コードスニペットは以下のようになります。

      // BUY POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("BUY POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
      
      //char data[];  // Array to hold data to be sent in the web request (empty in this case)
      //char res[];  // Array to hold the response data from the web request
      //string resHeaders;  // String to hold the response headers from the web request
      
      
      ushort MONEYBAG = 0xF4B0;
      string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
      string msg =  "\xF680 Opened Buy Position."
             +"\n===================="
             +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
             +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
             +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
             +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
             +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
             +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
             +"\n_________________________"
             +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
             +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
             ;
      string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;
      
      sendEncodedMessage(msg,TG_API_URL,botTkn,chatID,10000);
      
      int get_screenshot_current_chart_result = getScreenshot_of_Current_Chart(SCREENSHOT_FILE_NAME);
      if (get_screenshot_current_chart_result == FAILED_CODE){
         string result_msg = "CURRENT CHART SCREENSHOT COULDN'T BE SAVED. REVERT NOW.";
         Print(result_msg);
         sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
         return;
      }
      else if (get_screenshot_current_chart_result == SUCCEEDED_CODE){
         string result_msg = "SUCCESS. CURRENT CHART SCREENSHOT WAS SAVED. CONTINUE NOW.";
         Print(result_msg);
         sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
         string sending_msg = "\x2705\SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.";
         sending_msg += "\n\x270C\YOU SHOULD RECEIVE THE IMAGE FILE WITHIN 10 SECONDS";
         string encoded_sending_msg = UrlEncode(sending_msg);
         Print(encoded_sending_msg);
         sendEncodedMessage(encoded_sending_msg,TG_API_URL,botTkn,chatID,10000);
         
      }
      
      //--- Caption
      string CAPTION = NULL;
      CAPTION = "Screenshot of Symbol: "+Symbol()+
                " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+
                ") @ Time: "+TimeToString(TimeCurrent());
      
      sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,CAPTION);

ここでは、発生したシグナルの通知と、取引レベルを示す現在のチャートのスクリーンショットを送信することだけに集中します。売りシグナルの確認では、同様のロジックが以下のように適応されます。

      // SELL POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("SELL POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
      
      //char data[];  // Array to hold data to be sent in the web request (empty in this case)
      //char res[];  // Array to hold the response data from the web request
      //string resHeaders;  // String to hold the response headers from the web request
   
      ushort MONEYBAG = 0xF4B0;
      string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
      string msg =  "\xF680 Opened Sell Position."
             +"\n===================="
             +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
             +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS)
             +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
             +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2)
             +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
             +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
             +"\n_________________________"
             +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
             +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
             ;
      string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;
      
      sendEncodedMessage(msg,TG_API_URL,botTkn,chatID,10000);
      
      int get_screenshot_current_chart_result = getScreenshot_of_Current_Chart(SCREENSHOT_FILE_NAME);
      if (get_screenshot_current_chart_result == FAILED_CODE){
         string result_msg = "CURRENT CHART SCREENSHOT COULDN'T BE SAVED. REVERT NOW.";
         Print(result_msg);
         sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
         return;
      }
      else if (get_screenshot_current_chart_result == SUCCEEDED_CODE){
         string result_msg = "SUCCESS. CURRENT CHART SCREENSHOT WAS SAVED. CONTINUE NOW.";
         Print(result_msg);
         sendSimpleMessage(result_msg,TG_API_URL,botTkn,chatID,10000);
         string sending_msg = "\x2705\SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.";
         sending_msg += "\n\x270C\YOU SHOULD RECEIVE THE IMAGE FILE WITHIN 10 SECONDS";
         string encoded_sending_msg = UrlEncode(sending_msg);
         Print(encoded_sending_msg);
         sendEncodedMessage(encoded_sending_msg,TG_API_URL,botTkn,chatID,10000);
         
      }
      
      //--- Caption
      string CAPTION = NULL;
      CAPTION = "Screenshot of Symbol: "+Symbol()+
                " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+
                ") @ Time: "+TimeToString(TimeCurrent());
      
      sendScreenShot(SCREENSHOT_FILE_NAME,TG_API_URL,botTkn,chatID,CAPTION);

シグナルが発生していることを確認するために、1分足に切り替えてシグナルの応答を待ってみましょう。最初のシグナルは売り仕掛けです。取引端末では以下のように生成されます。

MT5売り設定

以下は、MetaTrader 5の売りシグナルログです。

売りログ

以下は、Telegramのチャットフィールドで受信した売りメッセージです。

Telegram売り確認

上記の画像から、売りシグナルの設定が取引端末で確認され、重要なメッセージが操作ログに記録され、それぞれのポジションデータとスクリーンショットがTelegramのチャットグループに送信されていることがわかります。強気のクロスオーバーがあったら、既存の売りポジションを決済し、買いポジションを建て、その情報をTelegramグループにも送信します。取引端末の確認設定は以下の通りです。

MT5買い設定

以下は、MetaTrader 5の買いシグナルのログです。

買いのログ

以下は、Telegramのチャットフィールドで受信した買いメッセージです。

Telegram買い確認

マイルストーンをより効率的に可視化するために、EAのコンパイルから、初期化、生成されたシグナルに基づく取引開始、そしてメタデータがMQL5からTelegramにどのように送信されるかに基づく詳細なビデオをご覧ください。


ここまでで、この関数のコードモジュール化が成功していることがわかります。MQL5とTelegramの統合は問題なく機能するようになり、シグナルを確認するために呼び出したモジュール機能を使って、メッセージングとスクリーンショットの撮影を自動化しました。これにより、重要なイベントやアップデートがあるとすぐにダナ・ホワイトがTelegramのチャットに打ち込むようにしました。十分にテストしたので、うまくいくと確信しています。MQL5 とTelegram 間のブリッジとして機能するという点では、この実装は信頼性が高く柔軟性があり、後でより複雑な統合を構築できる優れたモジュール設計です。


結論

この記事では、MetaQuotes Language 5 (MQL5)とTelegramの統合について、メッセージ送信とチャートのスクリーンショット取得を行うためのモジュール関数の作成に焦点を当てて概説しました。モジュール設計により、EAの効率性、拡張性、保守性が高まり、不必要な複雑さが排除されています。

次の記事では、TelegramがMetaTrader 5にコマンドを送信し、EAのアクションを制御することを可能にする、双方向通信システムを探ります。この統合により、Telegramから直接ライブ取引データやスクリーンショットをリクエストするなど、より高度なインタラクションが可能になり、MQL5とTelegramの統合の限界を押し広げることが期待されます。今後の展開にご注目ください。

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

市場イベント予測のための因果ネットワーク分析(CNA)とベクトル自己回帰モデルの例 市場イベント予測のための因果ネットワーク分析(CNA)とベクトル自己回帰モデルの例
この記事では、MQL5で因果ネットワーク分析(CNA: Causal Network Analysis)とベクトル自己回帰(VAR: Vector Autoregression)デルを使用した高度な取引システムを実装するための包括的なガイドを紹介します。これらの手法の理論的背景をカバーし、取引アルゴリズムにおける主要な機能を詳細に説明し、実装のためのサンプルコードも含んでいます。
知っておくべきMQL5ウィザードのテクニック(第35回):サポートベクトル回帰 知っておくべきMQL5ウィザードのテクニック(第35回):サポートベクトル回帰
サポートベクトル回帰(SVR)は、2つのデータセット間の関係を最も適切に表現する関数または「超平面」を見つけるための理想的な手法です。本稿では、MQL5ウィザードのカスタムクラス内での時系列予測において、この手法を活用することを試みます。
リプレイシステムの開発(第47回):Chart Tradeプロジェクト(VI) リプレイシステムの開発(第47回):Chart Tradeプロジェクト(VI)
ついに、Chart Trade指標はEAと相互作用を開始し、情報をインタラクティブに転送できるようにします。そこで今回は、この指標を改良し、どのEAでも使えるような機能的なものにします。これにより、Chart Trade指標にアクセスし、実際にEAに接続されているかのように操作できるようになります。しかし、以前よりもずっと興味深い方法でそれをおこなうつもりです。
知っておくべきMQL5ウィザードのテクニック(第34回):非従来型RBMによる価格の埋め込み 知っておくべきMQL5ウィザードのテクニック(第34回):非従来型RBMによる価格の埋め込み
制限ボルツマンマシンは、1980年代半ば、計算資源が非常に高価だった時代に開発されたニューラルネットワークの一種です。当初は、入力された訓練データセットの次元を削減し、隠れた確率や特性を捉えるために、ギブスサンプリングとコントラストダイバージェンス(Contrastive Divergence)に依存していました。RBMが予測用の多層パーセプトロンに価格を「埋め込む」場合、バックプロパゲーションがどのように同様の性能を発揮できるかを検証します。