English Русский Español Deutsch
preview
MQL5-Telegram統合エキスパートアドバイザーの作成(第7回):チャート上のインジケーター自動化のためのコマンド解析

MQL5-Telegram統合エキスパートアドバイザーの作成(第7回):チャート上のインジケーター自動化のためのコマンド解析

MetaTrader 5トレーディングシステム | 10 12月 2024, 10:53
252 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

この記事では、前回(第6回)での進捗状況を基に、ボットのインタラクションを強化するためにレスポンシブなインラインボタンを統合しました。現在、私たちの焦点は、Telegramから送信されたコマンドを使用してMetaTrader 5チャートへのインジケーターの追加を自動化することに移っています。エキスパートアドバイザー(EA)がTelegram経由でユーザー定義のインジケーターパラメータを取得し、データを解析して、指定されたインジケーターをリアルタイムで取引チャートに適用するシステムを作成します。 

次のトピックでは、このインジケーター自動化プロセスの実装を段階的に説明します。

  1. Telegramインジケーターベースの取引の概要:トレーダーがTelegramコマンドを使用してMetaTrader 5上のインジケーターを制御する方法について説明します。
  2. Telegramインジケーターコマンドの解析と処理:このセクションでは、Telegramメッセージからインジケーターパラメーターを適切に抽出して処理する方法について詳しく説明します。
  3. MQL5でのインジケーターの実行:解析されたコマンドを使用して、MetaTrader 5内で直接インジケーターを追加および自動化する方法を説明します。
  4. インジケーター取引システムのテスト:徹底したテストプロセスにより、正確なインジケーターの自動化のためのシステムがスムーズに動作することが保証されます。
  5. 結論最後に、プロセス全体を要約し、重要なポイントについて説明します。

この記事を読み終える頃には、Telegramからコマンドを受信して処理し、MQL5でテクニカルインジケーターをシームレスに適用できる、完全に機能するTelegramからMetaTrader 5へのインジケーター自動化システムが完成しているはずです。それでは始めましょう。


Telegramインジケーターベースの取引の概要

このセクションでは、チャート分析を自動化できるインジケーターコマンドを送信するためのTelegramの使用について詳しく解説します。多くのトレーダーは、Telegramを活用してボットやEAを操作し、MetaTrader 5でテクニカルインジケーターを直接追加、変更、削除できるようにしています。これらのコマンドには通常、チャート分析に不可欠なインジケーターの種類、時間枠、期間、価格の適用などの重要な情報が含まれています。しかし、これらのインジケーターを手動で適用すると、特に動きの速い市場では遅延やエラーが発生しやすくなります。

Telegramコマンドを通じてインジケーターを適用するプロセスを自動化することで、トレーダーは手動でのチャート管理の手間をかけずにテクニカル分析を強化できます。適切に統合されると、これらのTelegramコマンドは解析され、MetaTrader 5で実行可能な命令に変換され、インジケーターをリアルタイムでチャートに追加できるようになります。これにより、正確性だけでなく、より効率的な取引ワークフローが保証され、トレーダーはセットアップの管理ではなく結果の解釈に集中できるようになります。インジケーターコマンドの典型的な視覚化を以下に示します。

テレグラムインジケーターコマンドフォーマット

その結果、TelegramMetaTrader 5の間のギャップを埋めるシステムが誕生し、トレーダーは自動インジケーター管理を通じてチャート分析を合理化し、ミスを最小限に抑え、リアルタイムの市場機会を最大限に活用できるようになります。


Telegramインジケーターコマンドの解析と処理

最初におこなう必要があるのは、Telegramが提供するインジケーターコマンドをキャプチャし、次にそれらをMetaQuotes Language 5(MQL5)でエンコード、解析、処理して、解釈し、対応するインジケーターをMetaTrader 5チャートに適用できるようにすることです。エンコードと解析の部分については、本連載の第5回で必要なクラスをすでに紹介しました。ただし、このパートでは、特に第6回ではインラインボタンなどのさまざまな機能に焦点を当てたため、明確さを確保するためにこれらのクラスを再度検討します。Telegramインジケーターコマンドの解析と処理を担当するコードスニペットを以下に示します。

//+------------------------------------------------------------------+
//|   Class_Bot_EA                                                   |
//+------------------------------------------------------------------+
class Class_Bot_EA{
   private:
      string            member_token;         //--- Stores the bot’s token.
      string            member_name;          //--- Stores the bot’s name.
      long              member_update_id;     //--- Stores the last update ID processed by the bot.
      CArrayString      member_users_filter;  //--- An array to filter users.
      bool              member_first_remove;  //--- A boolean to indicate if the first message should be removed.
   
   protected:
      CList             member_chats;         //--- A list to store chat objects.

   public:
      void Class_Bot_EA();   //--- Declares the constructor.
      ~Class_Bot_EA(){};    //--- Declares the destructor.
      int getChatUpdates(); //--- Declares a function to get updates from Telegram.
      void ProcessMessages(); //--- Declares a function to process incoming messages.
};


void Class_Bot_EA::Class_Bot_EA(void){ //--- Constructor
   member_token=NULL; //--- Initialize the bot's token as NULL.
   member_token=getTrimmedToken(InpToken); //--- Assign the trimmed bot token from InpToken.
   member_name=NULL; //--- Initialize the bot's name as NULL.
   member_update_id=0; //--- Initialize the last update ID to 0.
   member_first_remove=true; //--- Set the flag to remove the first message to true.
   member_chats.Clear(); //--- Clear the list of chat objects.
   member_users_filter.Clear(); //--- Clear the user filter array.
}
//+------------------------------------------------------------------+
int Class_Bot_EA::getChatUpdates(void){
   //--- Check if the bot token is NULL
   if(member_token==NULL){
      Print("ERR: TOKEN EMPTY"); //--- Print an error message if the token is empty
      return(-1); //--- Return with an error code
   }
   
   string out; //--- Variable to store the response from the request
   string url=TELEGRAM_BASE_URL+"/bot"+member_token+"/getUpdates"; //--- Construct the URL for the Telegram API request
   string params="offset="+IntegerToString(member_update_id); //--- Set the offset parameter to get updates after the last processed ID
   
   //--- Send a POST request to get updates from Telegram
   int res=postRequest(out, url, params, WEB_TIMEOUT);
   // THIS IS THE STRING RESPONSE WE GET // "ok":true,"result":[]}
   
   //--- If the request was successful
   if(res==0){
      //Print(out); //--- Optionally print the response
      
      //--- Create a JSON object to parse the response
      CJSONValue obj_json(NULL, jv_UNDEF);
      //--- Deserialize the JSON response
      bool done=obj_json.Deserialize(out);
      //--- If JSON parsing failed
      // Print(done);
      if(!done){
         Print("ERR: JSON PARSING"); //--- Print an error message if parsing fails
         return(-1); //--- Return with an error code
      }
      
      //--- Check if the 'ok' field in the JSON is true
      bool ok=obj_json["ok"].ToBool();
      //--- If 'ok' is false, there was an error in the response
      if(!ok){
         Print("ERR: JSON NOT OK"); //--- Print an error message if 'ok' is false
         return(-1); //--- Return with an error code
      }
      
      //--- Create a message object to store message details
      Class_Message obj_msg;
      
      //--- Get the total number of updates in the JSON array 'result'
      int total=ArraySize(obj_json["result"].m_elements);
      //--- Loop through each update
      for(int i=0; i<total; i++){
         //--- Get the individual update item as a JSON object
         CJSONValue obj_item=obj_json["result"].m_elements[i];
         
         //--- Extract message details from the JSON object
         obj_msg.update_id=obj_item["update_id"].ToInt(); //--- Get the update ID
         obj_msg.message_id=obj_item["message"]["message_id"].ToInt(); //--- Get the message ID
         obj_msg.message_date=(datetime)obj_item["message"]["date"].ToInt(); //--- Get the message date
         
         obj_msg.message_text=obj_item["message"]["text"].ToStr(); //--- Get the message text
         obj_msg.message_text=decodeStringCharacters(obj_msg.message_text); //--- Decode any HTML entities in the message text
         
         //--- Extract sender details from the JSON object
         obj_msg.from_id=obj_item["message"]["from"]["id"].ToInt(); //--- Get the sender's ID
         obj_msg.from_first_name=obj_item["message"]["from"]["first_name"].ToStr(); //--- Get the sender's first name
         obj_msg.from_first_name=decodeStringCharacters(obj_msg.from_first_name); //--- Decode the first name
         obj_msg.from_last_name=obj_item["message"]["from"]["last_name"].ToStr(); //--- Get the sender's last name
         obj_msg.from_last_name=decodeStringCharacters(obj_msg.from_last_name); //--- Decode the last name
         obj_msg.from_username=obj_item["message"]["from"]["username"].ToStr(); //--- Get the sender's username
         obj_msg.from_username=decodeStringCharacters(obj_msg.from_username); //--- Decode the username
         
         //--- Extract chat details from the JSON object
         obj_msg.chat_id=obj_item["message"]["chat"]["id"].ToInt(); //--- Get the chat ID
         obj_msg.chat_first_name=obj_item["message"]["chat"]["first_name"].ToStr(); //--- Get the chat's first name
         obj_msg.chat_first_name=decodeStringCharacters(obj_msg.chat_first_name); //--- Decode the first name
         obj_msg.chat_last_name=obj_item["message"]["chat"]["last_name"].ToStr(); //--- Get the chat's last name
         obj_msg.chat_last_name=decodeStringCharacters(obj_msg.chat_last_name); //--- Decode the last name
         obj_msg.chat_username=obj_item["message"]["chat"]["username"].ToStr(); //--- Get the chat's username
         obj_msg.chat_username=decodeStringCharacters(obj_msg.chat_username); //--- Decode the username
         obj_msg.chat_type=obj_item["message"]["chat"]["type"].ToStr(); //--- Get the chat type
         
         //--- Update the ID for the next request
         member_update_id=obj_msg.update_id+1;
         
         //--- If it's the first update, skip processing
         if(member_first_remove){
            continue;
         }

         //--- Filter messages based on username
         if(member_users_filter.Total()==0 || //--- If no filter is applied, process all messages
            (member_users_filter.Total()>0 && //--- If a filter is applied, check if the username is in the filter
            member_users_filter.SearchLinear(obj_msg.from_username)>=0)){

            //--- Find the chat in the list of chats
            int index=-1;
            for(int j=0; j<member_chats.Total(); j++){
               Class_Chat *chat=member_chats.GetNodeAtIndex(j);
               if(chat.member_id==obj_msg.chat_id){ //--- Check if the chat ID matches
                  index=j;
                  break;
               }
            }

            //--- If the chat is not found, add a new chat to the list
            if(index==-1){
               member_chats.Add(new Class_Chat); //--- Add a new chat to the list
               Class_Chat *chat=member_chats.GetLastNode();
               chat.member_id=obj_msg.chat_id; //--- Set the chat ID
               chat.member_time=TimeLocal(); //--- Set the current time for the chat
               chat.member_state=0; //--- Initialize the chat state
               chat.member_new_one.message_text=obj_msg.message_text; //--- Set the new message text
               chat.member_new_one.done=false; //--- Mark the new message as not processed
            }
            //--- If the chat is found, update the chat message
            else{
               Class_Chat *chat=member_chats.GetNodeAtIndex(index);
               chat.member_time=TimeLocal(); //--- Update the chat time
               chat.member_new_one.message_text=obj_msg.message_text; //--- Update the message text
               chat.member_new_one.done=false; //--- Mark the new message as not processed
            }
         }
         
      }
      //--- After the first update, set the flag to false
      member_first_remove=false;
   }
   //--- Return the result of the POST request
   return(res);
}

ここでは、Class_Bot_EAを紹介し、Telegramからの受信更新を処理するためのgetChatUpdates関数を実装します。コンストラクタでは、ボットのトークン、名前、その他の関連変数を初期化します。また、最初のメッセージを削除し、チャットリストやユーザーフィルタなどの古いデータを消去するかどうかを決定するフラグも設定します。

getChatUpdates関数は、指定されたボットの更新情報を取得するTelegram APIのURLを構築します。最後に処理された更新IDはURL内のオフセットとして使用されます。つまり、すでに処理された更新は取得されません。URLを構築した後、APIにPOSTリクエストを送信し、サーバーからの応答を処理します。サーバーからJavaScript Object Notation(JSON)データが返されることを期待しているため、データを解析してエラーをチェックします。解析が失敗した場合、またはJSON応答の「ok」フィールドがfalseの場合、エラーメッセージを出力し、エラーコードを返します。

正常に応答すると、更新ID、メッセージID、送信者情報、チャットの詳細など、会話の内容がわかる関連情報がメッセージから取得されます。次に、これまでのチャットのリストを確認し、この新しい情報がどこに当てはまるかを確認します。この新しいメッセージに接続されているチャットがリストにない場合は、追加されます。リスト内にある場合は、新しいメッセージでその情報を更新します。

最後に、ユーザー定義のメッセージのフィルタリングと各チャットの状態の処理をおこないます。必要な更新が完了したら、各チャットの最後に処理されたメッセージが最新であることを確認します。最後に、POSTリクエストの結果を返します。これは、成功か、それに応じて記述されたエラーのいずれかを示します。

受信したコマンドを処理するために必要なのはこれだけです。次に、受信したインジケーターコマンドを解釈し、要求されたインジケーターを識別し、さらに分析するためにそれらをチャートに自動的に追加する必要があります。これは次のセクションで説明します。



MQL5でのインジケーターの実行

受信したインジケーターコマンドを処理するには、メッセージ処理を担当する関数を呼び出して、メッセージを全体として処理し、メッセージの詳細をセグメントごとに解釈します。以下の関数が適用可能です。

void Class_Bot_EA::ProcessMessages(void){

//...

}

この関数で実際の処理が開始されます。最初におこなう必要があるのは、受信したすべてのメッセージをループして個別に処理することです。これは、プロバイダーがAUDUSD、EURUSD、GBPUSD、XAUUSD、XRPUSD、USDKES、USDJPY、EURCHFなど、複数の取引銘柄に対して同時に一括してシグナルを送信できる可能性があるため重要です。これは次のロジックによって実現されます。

   //--- Loop through all chats
   for(int i=0; i<member_chats.Total(); i++){
      Class_Chat *chat=member_chats.GetNodeAtIndex(i); //--- Get the current chat
      if(!chat.member_new_one.done){ //--- Check if the message has not been processed yet
         chat.member_new_one.done=true; //--- Mark the message as processed
         string text=chat.member_new_one.message_text; //--- Get the message text

         //...

      }
   }

まず、member_chatsリスト内に保存されているチャットをループします。各チャットオブジェクトは、GetNodeAtIndex関数を使用して取得されます。member_new_one構造体のフラグを評価することによって、そのチャットに関連付けられたメッセージが処理されたかどうかを確認します。メッセージがまだ処理されていない場合、doneフラグはtrueに設定され、同じメッセージが複数回処理されないことを意味します。

次に、メッセージの内容を抽出します。これらは、member_new_one構造体のmessage_textフィールドに保存されます。したがって、すでに処理されている内容を気にすることなく、テキストを直接操作できます。

最初におこなう必要があるのは、分析のためにコマンドの詳細を取得することです。ロジックはこうです。

         string user_text = text;
         Print("USER'S PLAIN TEXT IS AS BELOW:\n",user_text);
         
         StringToUpper(user_text);
         Print("USER'S TRANSFORMED UPPERCASE TEXT IS AS BELOW:\n",user_text);

ここでは、まず、変数textから受信したメッセージのテキストをuser_textという新しい変数に保存します。これにより、元の変数を変更せずにメッセージの内容を操作できるようになります。次に、Print関数を使用してユーザーのプレーンメッセージテキストを印刷します。この関数は、ログ記録の目的で、メッセージをMetaTrader 5端末に出力します。

次に、StringToUpper関数を使用して、user_text文字列全体を大文字に変換します。これにより、メッセージ内のすべての文字が大文字に変換されます。これは、メッセージ文字を均等化して作業を容易にするために必要です。変換後、更新されたメッセージを再度端末に出力し、ユーザーの入力を大文字に変換したバージョンを表示します。このプロセスにより、メッセージの元のバージョンと変更されたバージョンの両方を確認して、さらに処理または対応することができます。プログラムを再度実行すると、ログセクションに次の出力が表示されます。

大文字に変換されたテキスト

シグナルメッセージを大文字に変換した後、以下のようにデータを保持する変数を初期化する必要があります。

         // MOVING AVERAGE
         //--- Initialize variables to hold extracted data
         string indicator_type = NULL;
         string indicator_symbol = NULL;
         string indicator_timeframe = NULL;
         long indicator_period = 0;
         long indicator_shift = 0;
         string indicator_method = NULL;
         string indicator_app_price = NULL;

まず、移動平均インジケーターから始めます。Telegramからのユーザー入力に基づいて、MetaTrader 5で移動平均(MA)インジケーターを構成するために抽出されたデータを保持するいくつかの変数を初期化します。各変数が表す内容は次のとおりです。

  • indicator_type:この場合のインジケーターの種類は移動平均です
  • indicator_symbol:インジケーターが適用される銘柄(通貨ペアまたは資産)
  • indicator_timeframe:インジケーターがプロットされるチャートの時間枠(例:M1、H1、D1)
  • indicator_period:移動平均の計算で考慮される期間(またはバー)の数
  • indicator_shift:チャート上でインジケーターを前方または後方に移動するためのオフセットまたはシフト値
  • indicator_method:MAの計算方法(例:SMA、EMA)
  • indicator_app_price:MA計算に使用される適用価格(例:終値、始値)

インジケーターに関連するデータ要素を抽出するには、メッセージを行ごとに分割し、各行をループして詳細を探す必要があります。この実装は、以下のロジックによって実現されます。

         //--- Split the message by lines
         string lines[];
         StringSplit(user_text,'\n',lines);
         Print("SPLIT TEXT SEGMENTS IS AS BELOW:");
         ArrayPrint(lines,0,",");

ここでは、ユーザーの変換されたメッセージを個別の行に分割して、関連するインジケーター情報を簡単に抽出できるようにします。まず、分割されたメッセージの各行を保持するためのlinesという配列を宣言します。次に、改行文字(\n)を区切り文字として使用してStringSplit関数を適用し、メッセージを個別の行に分割します。この関数は、新しい行で区切られたテキストの各部分をlines配列に永続的に入力します。

メッセージが分割されると、ArrayPrint関数を使用して結果のセグメントを印刷します。この関数は、各行を個別の要素として出力します。この手順は、メッセージの構造を視覚化し、分割プロセスが正しく機能したことを確認するために必須です。このようにメッセージを整理することで、各行をより簡単に処理して、取引銘柄、インジケーターの種類、その他の詳細などの重要な要素を抽出できます。詳細を取得するには、各行をループする必要があります。

         //--- Iterate over each line to extract information
         for (int i=0; i<ArraySize(lines); i++){
            StringTrimLeft(lines[i]);
            StringTrimRight(lines[i]);
            
            string selected_line = lines[i];
            Print(i,". ",selected_line);

            //...

         }

lines配列を反復処理して、分割メッセージの各行から特定のインジケーター情報を抽出します。forループを使用して配列内のすべての要素を調べ、各行が個別に処理されるようにします。ループの開始時に、各行にStringTrimLeft関数とStringTrimRight関数を適用して、先頭または末尾の空白文字を削除します。これにより、余分なスペースが解析プロセスに干渉することがなくなります。

次に、トリミングされた各行を、現在処理中の行を保持するselected_line変数に割り当てます。各行をきちんとトリミングしてselected_line変数に保存することで、行に特定の取引シグナルやコマンドが含まれているかどうかを確認するなどの追加の操作を実行できます。正しい情報があることを確認するために、各行を出力します。以下は出力です。

コマンドの繰り返し

うまくいきました。選択した行の特定の詳細を検索することができます。まず、取引テクニカルインジケーターの種類を調べてみましょう。まず、移動平均インジケーターのタイプのテキスト、つまり「INDICATOR TYPE」を探します。 

            if (StringFind(selected_line,"INDICATOR TYPE") >= 0){
               indicator_type = StringSubstr(selected_line,16);
               Print("Line @ index ",i," Indicator Type = ",indicator_type); //--- Print the extracted details
            }

ここでは、ユーザーのTelegramメッセージを処理してインジケーターの種類を抽出します。StringFind関数を使用して、現在の行(selected_line)でテキスト「INDICATOR TYPE」を検索します。このテキストが見つかった場合、関数は一致の開始位置を返します。これは0以上の値です。一致が検出されると、StringSubstr関数を使用してインジケータータイプを抽出します。この関数は、「INDICATOR TYPE」の後の位置(インデックス16から開始)から行の末尾までの部分文字列を取得します。抽出された値はindicator_type変数に格納されます。最後に、Print関数を使用して行のインデックスと抽出されたindicator_typeを出力し、データが正常に取得されたことを確認します。これを実行すると、次の出力が得られます。

インジケーターの確認

すべてのコマンドセグメントを正常にループし、インジケーター名を識別できたことがわかります。次に、銘柄を識別する必要があります。同様ですが、もう少し複雑なロジックが使用されます。 

            //--- Check for symbol in the list of available symbols and assign it
            for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols
               string selected_symbol = SymbolName(k, true); //--- Get the symbol name
               if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name
                  indicator_symbol = selected_symbol; //--- Assign the symbol if a match is found
                  Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol
               }
            }            

このコードブロックでは、ユーザーの入力を利用可能な取引銘柄のリストと照合し、正しいものをindicator_symbol変数に割り当てます。まず、プラットフォームで現在利用可能な銘柄の合計数を返すSymbolsTotal関数を使用します。引数trueは、表示される銘柄の数が必要であることを指定します。次に、変数kをインデックスとしてforループを使用して、使用可能なすべての銘柄をループします。

ループ内では、SymbolName関数を使用して、インデックス「k」の銘柄の名前を取得します。2番目の引数trueは、銘柄の名前を短縮形式で取得することを示します。銘柄名を取得してselected_symbol変数に保存した後、StringCompare関数を使用してこのselected_symbolをユーザーの入力(selected_line)と比較します。false引数は、比較で大文字と小文字を区別しないことを示します。

関数がゼロを返す場合、2つの文字列が一致することを意味し、selected_symbolをindicator_symbol変数に割り当てます。最後に、Print関数を使用して行のインデックスと一致したindicator_symbolを出力し、ユーザーの入力から銘柄を正しく識別して割り当てたことを確認します。これには余分なテキストが含まれていないため、直接検索します。実行すると、抽出されたテキストとデフォルトの銘柄は大文字と小文字が区別される点で類似していないため、現在のコードスニペットでは結果が得られません。つまり、XAUUSDMはXAUUSDmと等しくありません。銘柄名は次のとおりです。

デフォルト構造

したがって、比較をおこなうには、デフォルトのシステム銘柄を大文字に変換する必要があります。新しく更新されたコードスニペットは次のとおりです。

            //--- Check for symbol in the list of available symbols and assign it
            for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols
               string selected_symbol = SymbolName(k, true); //--- Get the symbol name
               StringToUpper(selected_symbol);
               if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name
                  indicator_symbol = selected_symbol; //--- Assign the symbol if a match is found
                  Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol
               }
            }            

新しい変換を使用して比較を進めると、次の結果が得られます。

選択された銘柄

うまくいきました。他の詳細を取得するには、同様のロジックを使用します。

            if (StringFind(selected_line,"TIMEFRAME") >= 0){
               indicator_timeframe = StringSubstr(selected_line,12);
               Print("Line @ index ",i," Indicator Timeframe = ",indicator_timeframe); //--- Print the extracted details
            }
            if (StringFind(selected_line,"PERIOD") >= 0){
               indicator_period = StringToInteger(StringSubstr(selected_line,9));
               Print("Line @ index ",i," Indicator Period = ",indicator_period);
            }
            if (StringFind(selected_line,"SHIFT") >= 0){
               indicator_shift = StringToInteger(StringSubstr(selected_line,8));
               Print("Line @ index ",i," Indicator Shift = ",indicator_shift);
            }
            if (StringFind(selected_line,"METHOD") >= 0){
               indicator_method = StringSubstr(selected_line,9);
               Print("Line @ index ",i," Indicator Method = ",indicator_method);
            }
            if (StringFind(selected_line,"APPLIED PRICE") >= 0){
               indicator_app_price = StringSubstr(selected_line,16);
               Print("Line @ index ",i," Indicator Applied Price = ",indicator_app_price);
            }
         }

これを実行すると、次の出力が得られます。

インジケーターの詳細

データをより構造化された方法で表示するには、取得した抽出情報をすべて以下のように印刷します。

         //--- Final data
         Print("\nFINAL EXTRACTED DATA:"); //--- Print the final data for confirmation
         
         Print("Type = ",indicator_type);
         Print("Symbol = ",indicator_symbol);
         Print("Timeframe = ",indicator_timeframe);
         Print("Period = ",indicator_period);
         Print("Shift = ",indicator_shift);
         Print("Method = ",indicator_method);
         Print("Applied Price = ",indicator_app_price);

変数が正しく入力されていることを確認するために、最終的に抽出されたデータを出力します。まず、Print関数を使用して、ヘッダーメッセージ「\nFINAL EXTRACTED DATA:」を表示します。これはログ内で視覚的な手がかりとして機能し、処理されたデータが表示される場所をマークします。

その後、各主要変数indicator_type、indicator_symbol、indicator_timeframe、indicator_period、indicator_shift、indicator_method、indicator_app_priceの値を順番に出力します。Printを呼び出すたびに、変数の名前と現在の値が出力されます。これは、システムがMetaTrader 5のチャートにインジケーターを追加する前に、ユーザーの入力から解析されたデータ(インジケーターの種類、銘柄、時間枠など)が正確にキャプチャされていることを保証するため、デバッグと検証にとって重要です。得られる出力は以下のように視覚化されます。

整理されたログ

完璧です。これで、移動平均インジケーターに必要な詳細がすべて揃ったので、チャートへの追加に進むことができます。ただし、追加する前に、Telegramの指示どおりに正しいインジケーターがあるかどうかを再確認できます。これはifステートメントを使用して実現します。

         if (indicator_type=="MOVING AVERAGE"){
                //...
         }

インジケータを確認した後、抽出された入力をそれぞれのデータ型構造に変換することができます。現在の値は文字列または整数データ型のいずれかであることに注意してください。たとえば、システムは、ユーザーの移動平均法「SMA」入力が、以下のようにENUM_MA_METHOD列挙体の「SIMPLE MOVING AVERAGE」を意味することを理解しません。

ENUM_MA_METHOD

したがって、プログラムに対してそれをさらに説明する必要があります。体系的に進めるために、まずは最も明白な時間枠から始めます。

            //--- Convert timeframe to ENUM_TIMEFRAMES
            ENUM_TIMEFRAMES timeframe_enum = _Period;
            if (indicator_timeframe == "M1") {
               timeframe_enum = PERIOD_M1;
            } else if (indicator_timeframe == "M5") {
               timeframe_enum = PERIOD_M5;
            } else if (indicator_timeframe == "M15") {
               timeframe_enum = PERIOD_M15;
            } else if (indicator_timeframe == "M30") {
               timeframe_enum = PERIOD_M30;
            } else if (indicator_timeframe == "H1") {
               timeframe_enum = PERIOD_H1;
            } else if (indicator_timeframe == "H4") {
               timeframe_enum = PERIOD_H4;
            } else if (indicator_timeframe == "D1") {
               timeframe_enum = PERIOD_D1;
            } else if (indicator_timeframe == "W1") {
               timeframe_enum = PERIOD_W1;
            } else if (indicator_timeframe == "MN1") {
               timeframe_enum = PERIOD_MN1;
            } else {
               Print("Invalid timeframe: ", indicator_timeframe);
            }

このセクションでは、抽出されたindicator_timeframe文字列からユーザー提供の時間枠を、対応するMetaTrader 5列挙体ENUM_TIMEFRAMESに変換します。MetaTrader 5では、1分チャートの場合はPERIOD_M1、1時間チャートの場合はPERIOD_H1などの定義済みの時間枠が使用されるため、この手順は非常に重要です。これらの時間枠は、ENUM_TIMEFRAMES型の一部として定義されます。

まず、変数timeframe_enumを、_Periodで表される現在のチャートの時間枠に初期化します。これは、ユーザーが提供する時間枠が無効な場合のフォールバックデフォルトとして機能します。

次に、一連の条件付きif-elseステートメントを使用して、indicator_timeframe文字列の値を確認します。各条件では、抽出された文字列をM1(1分)、H1(1時間)などの既知の時間枠識別子と比較します。一致が見つかった場合、対応するENUM_TIMEFRAMES値(PERIOD_M1やPERIOD_H1など)がtimeframe_enum変数に割り当てられます。

いずれの条件も一致しない場合は、最後のelseブロックがトリガーされ、indicator_timeframeの値とともに、時間枠が無効であることを示すエラーメッセージがログに出力されます。これにより、インジケーターの作成プロセス中に有効な時間枠のみがMetaTrader 5に渡されるようになります。同様に、他の変数もそれぞれの形式に変換します。

            //--- Convert MA method to ENUM_MA_METHOD
            ENUM_MA_METHOD ma_method = MODE_SMA;
            if (indicator_method == "SMA") {
               ma_method = MODE_SMA;
            } else if (indicator_method == "EMA") {
               ma_method = MODE_EMA;
            } else if (indicator_method == "SMMA") {
               ma_method = MODE_SMMA;
            } else if (indicator_method == "LWMA") {
               ma_method = MODE_LWMA;
            } else {
               Print("Invalid MA method: ", indicator_method);
            }
            
            //--- Convert applied price to ENUM_APPLIED_PRICE
            ENUM_APPLIED_PRICE app_price_enum = PRICE_CLOSE;
            
            if (indicator_app_price == "CLOSE") {
               app_price_enum = PRICE_CLOSE;
            } else if (indicator_app_price == "OPEN") {
               app_price_enum = PRICE_OPEN;
            } else if (indicator_app_price == "HIGH") {
               app_price_enum = PRICE_HIGH;
            } else if (indicator_app_price == "LOW") {
               app_price_enum = PRICE_LOW;
            } else if (indicator_app_price == "MEDIAN") {
               app_price_enum = PRICE_MEDIAN;
            } else if (indicator_app_price == "TYPICAL") {
               app_price_enum = PRICE_TYPICAL;
            } else if (indicator_app_price == "WEIGHTED") {
               app_price_enum = PRICE_WEIGHTED;
            } else {
               Print("Invalid applied price: ", indicator_app_price);
            }

変換プロセスが完了したら、インジケーターをチャートに追加するために使用するインジケーターハンドルの作成に進むことができます。

            int handle_ma = iMA(indicator_symbol,timeframe_enum,(int)indicator_period,(int)indicator_shift,ma_method,app_price_enum);

ここでは、iMA関数を使用して移動平均(MA)インジケーターのハンドルを作成します。この関数は、以前に抽出して処理したパラメータに基づいてインジケーターハンドルを生成します。ハンドルを使用すると、コードの後半でチャート上のインジケーターを参照および操作できるようになります。iMA関数にいくつかの引数を渡します。各引数は、Telegramコマンドから収集したパラメータに対応しています。

  • indicator_symbol:インジケーターが適用される金融商品(例:EURUSD)を指定します
  • timeframe_enum:ユーザーの入力から以前に変換された時間枠(例:1分の場合はM1、1時間の場合はH1)を参照します
  • (int)indicator_period:抽出されたindicator_periodがlongデータ型からMAの期間数を表す整数に変換されます
  • (int)indicator_shift:同様に、ndicator_shiftを整数に変換し、チャート上でインジケーターをシフトするバーの数を定義します
  • ma_method:ユーザーの入力に基づいて、単純移動平均(SMA)や指数移動平均(EMA)などのMAの計算方法を指定します
  • app_price_enum:MAの計算に使用される価格タイプ(終値または始値など)を示します

iMA関数の結果は変数handle_maに格納されます。関数がインジケーターハンドルを正常に作成すると、handle_maに有効な参照が含まれます。作成に失敗した場合は、1つ以上のパラメータに問題があったことを示すINVALID_HANDLEが返されます。INVALID HANDLEは失敗の表現であることがわかっているので、それを使用してさらに処理を進めることができます。

            if (handle_ma != INVALID_HANDLE){
               Print("Successfully created the indicator handle!");

                //...

            }
            else if (handle_ma == INVALID_HANDLE){
               Print("Failed to create the indicator handle!");
            }

ここでは、handle_maの値を確認して、移動平均(MA)インジケーターハンドルが正常に作成されたかどうかを確認します。ハンドルが有効な場合、iMA関数はINVALID_HANDLEと等しくない値を返します。 その場合、成功を示すメッセージ「Successfully created the indicator handle!」を出力します。これは、すべてのパラメータ(銘柄、時間枠、期間、方法など)が正しく解釈され、MAインジケーターをチャートで使用できる状態であることを意味します。

ハンドルの作成に失敗した場合、つまりhandle_maがINVALID_HANDLEに等しい場合は、エラーメッセージ「Failed to create the indicator handle!」を出力します。この状態は、インジケーターの作成プロセス中に、無効な銘柄、不正な時間枠、その他の不正なパラメーターなど、何らかの問題が発生したことを示します。このエラー処理により、インジケーターのセットアッププロセス中にシステムが問題を検出し、有益なフィードバックを提供できるようになります。次に、指定した銘柄と時間枠でチャートを開き、最新のデータと同期していることを確認し、わかりやすくするために設定を調整します。

               long chart_id=ChartOpen(indicator_symbol,timeframe_enum);
               ChartSetInteger(ChartID(),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(indicator_symbol,timeframe_enum,SERIES_SYNCHRONIZED)){
                     break; // if prices up to date, terminate the loop and proceed
                  }
               }
               
               ChartSetInteger(chart_id,CHART_SHOW_GRID,false);
               ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false);
               ChartRedraw(chart_id);
               
               Sleep(7000);

まず、ChartOpen関数を使用して、以前にTelegramコマンドから抽出したindicator_symbolとtimeframe_enumに基づいて新しいチャートを開きます。チャートIDが返され、変数chart_idに格納されます。MetaTrader 5でこのチャートを最前面に表示するには、ChartSetInteger関数を使用し、チャートIDと定数CHART_BRING_TO_TOPを渡して、チャートが操作可能に表示されるようにします。

次に、チャートの価格データが完全に更新されていることを確認するために同期チェックを実装します。これは、SeriesInfoInteger関数を使用して最大60回ループし、価格データシリーズが同期されているかどうかを確認することによって実行されます。ループが完了する前に同期が発生した場合は、早期に抜け出します。データが最新であることを確認したら、グラフの外観をカスタマイズします。グリッドと期間の区切りはChartSetInteger関数を使用して非表示になります。この関数では、CHART_SHOW_GRIDとCHART_SHOW_PERIOD_SEPをfalseとして渡し、よりクリーンなチャート表示を作成します。

これらの調整後、ChartRedraw関数を使用してチャートが視覚的に強制的に更新されます。最後に、Sleep関数により7秒間の一時停止が追加され、チャートとデータが完全に読み込まれてから、次の操作に進むことができます。このプロセス全体により、データが更新され、視覚的にきれいな設定がされたチャートが、操作と表示の準備が整っていることが保証されます。その後、インジケーターをチャートに追加し続けることができます。

               if (ChartIndicatorAdd(chart_id,0,handle_ma)) {
                  Print("SUCCESS. Indicator added to the chart.");
                  isIndicatorAddedToChart = true;
               }
               else {
                  Print("FAIL: Indicator not added to the chart.");
               }   

ここでは、以前に開いたチャートに、以前に作成した移動平均(MA)インジケーターを追加します。これを実現するには、ChartIndicatorAdd関数を使用します。この関数では、chart_id、ゼロ(インジケーターをメインチャートに追加していることを示します)、および作成したMAインジケーターのハンドルを表すhandle_maを渡します。追加が成功した場合は、成功メッセージ「SUCCESS.Indicator added to the chart」が表示さrせ、ブール変数isIndicatorAddedToChartがtrueに設定され、インジケーターがチャート上でアクティブになったことが示されます。示されているように、ブール変数を定義し、「if」ステートメントの外側でそれをfalseに初期化しました。

         bool isIndicatorAddedToChart = false;

逆に、追加が失敗した場合は、失敗メッセージ「FAIL:Indicator not added to the chart」を表示します。このチェックは、インジケーターがチャートに正常に適用されているかどうかを確認できるため、非常に重要です。これは、その後の取引操作と視覚的な分析に不可欠です。両方の結果を処理することで、プロセスの透明性が維持され、チャート上のインジケーターの状態に関する情報が得られます。インジケーターを作成してチャートに追加すると、同じコード構造でユーザーに成功を通知できます。

         if (isIndicatorAddedToChart){
            string message = "\nSUCCESS! THE INDICATOR WAS ADDED TO THE CHART WITH THE FOLLOWING DETAILS:\n"; //--- Prepare success message
            message += "\nType = "+indicator_type+"\n";
            message += "Symbol = "+indicator_symbol+"\n";
            message += "Timeframe = "+indicator_timeframe+"\n";
            message += "Period = "+IntegerToString(indicator_period)+"\n";
            message += "Shift = "+IntegerToString(indicator_shift)+"\n";
            message += "Applied Price = "+indicator_app_price+"\n";
            message+="\nHAPPY TRADING!"; //--- Add final message line
            Print(message); //--- Print the message in the terminal
            sendMessageToTelegram(chat.member_id,message,NULL); //--- Send the success message to Telegram
         }
 

ここでは、ブール変数isIndicatorAddedToChartを評価して、インジケーターがチャートに正常に追加されたかどうかを確認します。この条件が真である場合、インジケーターの構成の詳細を示す成功メッセージの準備に進みます。まず、messageという名前の文字列変数を成功メッセージヘッダー「\nSUCCESS!THE INDICATOR WAS ADDED TO THE CHART WITH THE FOLLOWING DETAILS:\n」で初期化します。次に、インジケーターの種類、銘柄、時間枠、期間、シフト、適用価格など、インジケーターに関するさまざまな情報を連結します。数値については、IntegerToString関数を使用して、適切に連結されるように文字列形式に変換します。

このすべての情報をまとめた後、前向きな気持ちを伝えるために、メッセージの最後の行に「\nHAPPY TRADING!」を追加します。次に、Print関数を使用して完全なメッセージを端末に出力し、インジケーターの追加とその詳細を明確に確認できるようにします。最後に、sendMessageToTelegram関数を呼び出して、同じ成功メッセージをTelegramに送信し、chat.member_idで識別される関連チャットに操作の成功が通知されるようにします。これを実行すると、次の出力が得られます。

エラー出力

気配値表示に銘柄があるにもかかわらず、エラーメッセージが返されていることがわかります。これは、すべてを大文字に変換することで、銘柄名の正しい構造を妨げたためです。正しい銘柄の解釈と比較を維持しながら正しい構造に戻すには、さまざまなオプションを使用できますが、最も簡単な方法は、図に示すように、最初の銘柄名をホルダー変数に直接追加することです。

            //--- Check for symbol in the list of available symbols and assign it
            for(int k = 0; k < SymbolsTotal(true); k++) { //--- Loop through all available symbols
               string selected_symbol = SymbolName(k, true); //--- Get the symbol name
               StringToUpper(selected_symbol);
               if (StringCompare(selected_line,selected_symbol,false) == 0){ //--- Compare the line with the symbol name
                  indicator_symbol = SymbolName(k, true); //--- Assign the symbol if a match is found
                  Print("Line @ index ",i," SYMBOL = ",indicator_symbol); //--- Print the found symbol
               }
            }            

スニペット内で発生した変更のみが黄色で強調表示され表示されます。再実行すると、以下のように正しい出力が得られます。

正しい出力

ここまでで、インジケーターをチャートに追加できました。Telegramでは、以下に示すように成功の応答が返されます。

TELEGRAM確認

取引端末で新しいチャートが開かれ、インジケーターが追加されます。以下は視覚的な確認です。

MT5確認

ここまでで、移動平均テクニカルインジケーターをチャートに自動的に追加するという目的は達成されたと言えます。同様の方法論は、Awesome Oscillatorなどの他のインジケーターにも適用できます。コマンドの識別、関連パラメータの抽出、インジケーターの追加の実行に同じ形式に従うことで、実装全体にわたって一貫性と効率性を維持しながら、さまざまなインジケーターを取引システムにシームレスに統合できます。ただし、実装をテストし、すべてが正常に動作することを確認する必要があります。これは次のセクションで説明します。


インジケーター取引システムのテスト

このセクションでは、インジケーター取引システムの機能の検証に焦点を当てます。テストでは、インジケーターが正しく構成され、Telegramから受信したコマンドに適切に応答することを確認します。すべてのパラメータが正確に設定され、インジケーターがチャート上に正しく表示されることを確認しながら、チャートにインジケーターを追加するプロセスを検討します。

このプロセスを明確に理解していただくために、MetaTrader 5のインジケーター、特に移動平均のセットアップと構成を紹介するビデオを掲載しました。このビデオでは、システムの機能を強調し、実際の取引シナリオに対応できるかどうかを確認しています。



結論

結論として、この記事では、Telegram経由で受信したインジケーターコマンドを解析および処理して、MetaTrader 5チャートへのインジケーターの追加を自動化する方法を説明しました。これらの技術を実装することで、プロセスが合理化され、取引の効率が向上し、人為的エラーの可能性が減りました。

この実装から得られた知識により、追加のインジケーターとコマンドを組み込んだ、より洗練されたシステムを開発できるようになります。この基礎により、取引戦略を洗練させ、変化する市場状況に適応し、最終的に取引パフォーマンスを向上させることができます。この記事が皆様にとって分かりやすく、取引システムを強化するために必要な洞察を提供できたことを願っています。ご質問がある場合やさらに詳しい説明が必要な場合は、お気軽に追加のリソースを調べたり、このシリーズで共有されている概念を試してみてください。取引をお楽しみください。

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

添付されたファイル |
古典的な戦略を再構築する(第9回):多時間枠分析(II) 古典的な戦略を再構築する(第9回):多時間枠分析(II)
本日のディスカッションでは、AIモデルがどの時間枠で最高のパフォーマンスを発揮するかを明らかにするため、多時間枠分析の戦略を検討します。この分析により、EURUSDペアにおいて月次および時間足の時間枠が比較的誤差の少ないモデルを生成することが分かりました。この結果を活用し、月次時間枠でAIによる予測を行い、時間枠で取引を実行するアルゴリズムを作成しました。
新しい指標と条件付きLSTMの例 新しい指標と条件付きLSTMの例
本記事は、テクニカル分析とディープラーニング(深層学習)予測を融合した自動取引用エキスパートアドバイザー(EA)の開発に焦点を当てます。
知っておくべきMQL5ウィザードのテクニック(第41回):DQN (Deep-Q-Network) 知っておくべきMQL5ウィザードのテクニック(第41回):DQN (Deep-Q-Network)
DQN (Deep-Q-Network)は強化学習アルゴリズムであり、機械学習モジュールの学習プロセスにおいて、次のQ値と理想的な行動を予測する際にニューラルネットワークを関与させます。別の強化学習アルゴリズムであるQ学習についてはすでに検討しました。そこでこの記事では、強化学習で訓練されたMLPが、カスタムシグナルクラス内でどのように使用できるかを示すもう1つの例を紹介します。
どんな市場でも優位性を得る方法(第5回):FRED EURUSD代替データ どんな市場でも優位性を得る方法(第5回):FRED EURUSD代替データ
本日の議論では、セントルイス連邦準備銀行の広義のドル指数に関する代替日次データとその他のマクロ経済指標の集合を使用して、EURUSDの将来の為替レートを予測しました。残念ながら、データはほぼ完璧な相関関係にあるように見えますが、モデルの精度において際立った向上は実現できず、投資家は代わりに通常の市場相場を使用した方がよい可能性があることを示唆している可能性があります。