English Русский Deutsch 日本語
preview
Creación de un asesor experto integrado de MQL5 y Telegram (Parte 7): Análisis de comandos para la automatización de indicadores en los gráficos

Creación de un asesor experto integrado de MQL5 y Telegram (Parte 7): Análisis de comandos para la automatización de indicadores en los gráficos

MetaTrader 5Sistemas comerciales | 21 mayo 2025, 08:14
124 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En este artículo, nos basaremos en los avances realizados en la parte anterior (Parte 6), donde integramos botones en línea responsivos para mejorar la interacción con los bots. Ahora, nuestro enfoque se desplaza a la automatización de la adición de indicadores en MetaTrader 5 gráficos utilizando comandos enviados desde Telegram. Crearemos un sistema en el que el Asesor Experto captura los parámetros de los indicadores definidos por el usuario a través de Telegram, analiza los datos y aplica los indicadores especificados a los gráficos de negociación en tiempo real. 

Los siguientes temas nos guiarán paso a paso en la aplicación de este proceso de automatización de indicadores:

  1. Visión general del comercio basado en indicadores en Telegram: Vamos a explorar cómo los comerciantes pueden utilizar Telegram comandos para controlar los indicadores en MetaTrader 5.
  2. Análisis y procesamiento de comandos indicadores en Telegram: esta sección detallará cómo extraer y procesar correctamente los parámetros indicadores de los mensajes de Telegram.
  3. Ejecución de Indicadores en MQL5: Demostraremos cómo utilizar comandos analizados para añadir y automatizar indicadores directamente dentro de MetaTrader 5.
  4. Prueba del sistema de negociación de indicadores: un proceso de prueba exhaustivo garantizará el buen funcionamiento del sistema para una automatización precisa de los indicadores.
  5. Conclusión: Finalmente, recapitularemos todo el proceso y analizaremos los puntos clave.

Al final de este artículo, tendrás un sistema de automatización de indicadores Telegram-to-MetaTrader 5 completamente funcional, capaz de recibir y procesar órdenes desde Telegram para aplicar indicadores técnicos sin problemas en MQL5. ¡Comencemos!


Descripción general del trading basado en indicadores en Telegram

En esta sección, profundizamos en el uso de Telegram para el envío de comandos de indicadores que pueden automatizar el análisis de gráficos. Muchos traders están aprovechando Telegram para interactuar con bots y Expert Advisors (EAs) que les permiten añadir, modificar o eliminar indicadores técnicos directamente en MetaTrader 5. Estos comandos suelen incluir información clave como el tipo de indicador, el marco temporal, el periodo y la aplicación del precio, esencial para el análisis de gráficos. Sin embargo, cuando se manejan manualmente, la aplicación de estos indicadores puede ser propensa a retrasos o errores, especialmente en mercados que se mueven con rapidez.

Al automatizar el proceso de aplicación de indicadores mediante comandos en Telegram, los operadores pueden mejorar su análisis técnico sin las molestias de la gestión manual de los gráficos. Cuando se integran correctamente, estos comandos en Telegram se pueden analizar y convertir en instrucciones ejecutables en MetaTrader 5, lo que permite añadir indicadores a los gráficos en tiempo real. Esto garantiza no sólo la precisión, sino también un flujo de trabajo de negociación más eficaz, que permite a los operadores centrarse en la interpretación de los resultados en lugar de gestionar la configuración. Una visualización típica de los comandos del indicador es la que se muestra a continuación:

FORMATO DE COMANDO DEL INDICADOR EN TELEGRAM

El resultado es un sistema que tiende un puente entre Telegram y MetaTrader 5, permitiendo a los operadores agilizar su análisis gráfico, minimizar los errores y aprovechar al máximo las oportunidades del mercado en tiempo real mediante la gestión automatizada de indicadores.


Análisis sintáctico y tratamiento de los comandos de indicadores en Telegram

Lo primero que tenemos que hacer es capturar los comandos de los indicadores proporcionados por Telegram y, a continuación, codificarlos, analizarlos y procesarlos en MetaQuotes Language 5 (MQL5) para poder interpretarlos y aplicar los indicadores correspondientes a los gráficos de MetaTrader 5. Para la parte de codificación y análisis sintáctico, ya hemos introducido las clases necesarias en la parte 5 de esta serie. Sin embargo, en esta parte, revisaremos estas clases para asegurar la claridad, especialmente desde que la parte 6 se centró en diferentes funcionalidades como los botones en línea. A continuación se proporciona el fragmento de código responsable de analizar y procesar los comandos de indicadores en 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);
}

Aquí, introducimos la «Class_Bot_EA» e implementamos su función «getChatUpdates» para manejar las actualizaciones entrantes de Telegram. En el constructor, inicializamos el token, el nombre y otras variables pertinentes del bot. También establecemos una bandera para determinar si debemos eliminar el primer mensaje y borrar algunos datos antiguos, incluida la lista de chat y cualquier filtro de usuario.

La función «getChatUpdates» construye una URL para la API de Telegram que nos consigue actualizaciones para el bot especificado. El ID de la última actualización procesada se utiliza como desplazamiento en la URL, lo que significa que no obtendremos ninguna actualización que ya esté procesada. Después de construir la URL, enviamos una solicitud POST a la API y gestionamos la respuesta del servidor. Dado que esperamos datos JavaScript Object Notation (JSON) de vuelta del servidor, comprobamos si hay errores intentando analizar los datos. Si falla el análisis o si el campo «ok» de la respuesta JSON es falso, imprimimos un mensaje de error y devolvemos un código de error.

Después de responder correctamente, extraemos la información relevante del mensaje: el ID de la actualización, el ID del mensaje, los datos del remitente y los detalles del chat, lo que nos permite entender lo que ocurrió en la conversación. A continuación, miramos la lista de chats que tenemos hasta ahora y vemos dónde encaja este nuevo dato. Si el chat conectado a este nuevo mensaje no está en la lista, lo agregamos. Si está en la lista, actualizamos su información con el nuevo mensaje.

Por último, nos encargamos del filtrado de mensajes definidos por el usuario y del manejo del estado de cada chat. Una vez completadas las actualizaciones necesarias, nos aseguramos de que el último mensaje procesado de cada chat esté actualizado. Finalmente, devolvemos el resultado de nuestra solicitud POST, indicando éxito o un error descrito correspondientemente.

Esto es todo lo que necesitamos para procesar los comandos recibidos. Luego necesitamos interpretar los comandos del indicador recibidos, identificar el indicador solicitado y agregarlos al gráfico automáticamente para un análisis posterior. Esto se hace en la siguiente sección.



Ejecución de indicadores en MQL5

Para procesar los comandos indicadores recibidos, llamaremos a la función responsable del procesamiento de mensajes para que procesemos los mensajes como un todo y luego interpretemos los detalles del mensaje en segmentos. Se aplica la siguiente función:

void Class_Bot_EA::ProcessMessages(void){

//...

}

Es ahora, en esta función, cuando comienza el verdadero procesamiento. Lo primero que tenemos que hacer es recorrer todos los mensajes recibidos y procesarlos individualmente. Esto es importante porque el proveedor podría haber enviado las señales simultáneamente y a granel para varios símbolos comerciales, digamos «AUDUSD, EURUSD, GBPUSD, XAUUSD, XRPUSD, USDKES, USDJPY, EURCHF» y muchos más. Lo conseguimos mediante la siguiente lógica.

   //--- 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

         //...

      }
   }

En primer lugar, recorremos los chats almacenados en la lista «member_chats». Cada objeto de chat se recupera con la función «GetNodeAtIndex». Comprobamos si el mensaje asociado a ese chat ha sido tratado evaluando una bandera en la estructura «member_new_one». Si aún no se ha actuado sobre el mensaje, la bandera «done» se establece en «true», lo que significa que el mismo mensaje no se tratará varias veces.

A continuación, extraemos el contenido del mensaje. Se almacenan en el campo "message_text" de la estructura "member_new_one". De esta forma podemos trabajar directamente con el texto sin preocuparnos de lo que ya ha sido procesado.

Lo primero que debemos hacer ahora es obtener los detalles del comando para su análisis. Aquí está la lógica.

         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);

Aquí, primero almacenamos el texto del mensaje entrante de la variable "text" en una nueva variable llamada "user_text". Esto nos permite trabajar con el contenido del mensaje sin modificar la variable original. A continuación, imprimimos el texto del mensaje sin formato del usuario utilizando la función «Print», que emite el mensaje al terminal MetaTrader 5 para fines de registro.

A continuación, convertimos toda la cadena «user_text» a mayúsculas utilizando la función StringToUpper. Esto transforma todos los caracteres del mensaje a sus equivalentes en mayúsculas. Esto es necesario porque igualaremos los caracteres del mensaje y será más fácil trabajar con eso. Después de la transformación, imprimimos nuevamente el mensaje actualizado en la terminal, mostrando la versión en mayúsculas transformada de la entrada del usuario. Este proceso nos permite ver tanto la versión original como la modificada del mensaje para su posterior procesamiento o respuesta. Cuando ejecutamos nuevamente el programa, obtenemos la siguiente salida en la sección de registro.

TEXTO EN MAYÚSCULAS TRANSFORMADO

Después de transformar el mensaje de señal a mayúsculas, necesitamos inicializar las variables que contendrán nuestros datos como se muestra a continuación:

         // 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;

Primero comenzamos con el indicador de media móvil. Inicializamos varias variables que contendrán los datos extraídos para configurar un indicador de media móvil (Moving Average , MA) en MetaTrader 5 según la entrada del usuario de Telegram. Esto es lo que representa cada variable:

  • indicator_type: El tipo de indicador, en este caso, es una Media Móvil.
  • indicator_symbol: El símbolo (par de divisas o activo) sobre el que se aplicará el indicador.
  • indicator_timeframe: El marco temporal del gráfico (por ejemplo, M1, H1, D1) en el que se trazará el indicador.
  • indicator_period: El número de periodos (o barras) que la Media Móvil considerará en su cálculo.
  • indicator_shift: El valor de desplazamiento para mover el indicador hacia adelante o hacia atrás en el gráfico.
  • indicator_method: El método de cálculo de la MA (por ejemplo, SMA, EMA).
  • indicator_app_price: El precio aplicado utilizado para el cálculo de la MA (por ejemplo, precio de cierre, precio de apertura).

Para extraer los elementos de datos relacionados con un indicador, tendremos que dividir el mensaje por líneas y recorrer cada línea en busca de los detalles. Esta aplicación se consigue mediante la lógica que se expone a continuación.

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

Aquí, dividimos el mensaje transformado del usuario en líneas individuales para facilitar la extracción de información indicadora relevante. Primero declaramos un array llamado «lines» para contener cada línea del mensaje una vez dividido. A continuación, aplicamos la función StringSplit, utilizando el carácter de nueva línea ('\n') como delimitador para dividir el mensaje en líneas separadas. Esta función rellena para siempre la matriz «líneas» con cada porción del texto que fue separada por una nueva línea.

Una vez dividido el mensaje, imprimimos los segmentos resultantes utilizando la función ArrayPrint, que muestra cada línea como un elemento individual. Este paso es obligatorio para visualizar la estructura del mensaje y asegurarse de que el proceso de división ha funcionado correctamente. Al organizar el mensaje de esta manera, podemos procesar más fácilmente cada línea para extraer elementos críticos como el símbolo de negociación, el tipo de indicador y otros detalles. Para obtener los detalles, tenemos que recorrer cada línea.

         //--- 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);

            //...

         }

Iteramos sobre la matriz «lines» para extraer información específica del indicador de cada línea del mensaje dividido. Utilizamos un bucle for para recorrer cada elemento de la matriz, asegurándonos de que cada línea se procesa individualmente. Al principio del bucle, aplicamos las funciones StringTrimLeft y StringTrimRight a cada línea para eliminar cualquier carácter de espacio en blanco inicial o final. De este modo se garantiza que ningún espacio adicional interfiera en el proceso de análisis.

A continuación, asignamos cada línea recortada a la variable «selected_line», que contiene la línea actual que se está procesando. Al tener cada línea bien recortada y almacenada en la variable «selected_line», podemos realizar otras operaciones, como comprobar si la línea contiene señales o comandos de negociación específicos. Para confirmar que tenemos la información correcta, imprimimos cada línea, y a continuación se muestra el resultado.

ITERACIONES DE COMANDO

Eso fue un éxito. Podemos proceder a buscar detalles específicos en la línea seleccionada. Comencemos buscando el tipo de indicador técnico comercial. Primero buscaremos el texto del tipo de indicador de media móvil, es decir, «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
            }

Aquí, procesamos el mensaje de Telegram del usuario para extraer el tipo de indicador. Utilizamos la función StringFind para buscar en la línea actual («selected_line») el texto «INDICATOR TYPE». Si se encuentra este texto, la función devuelve la posición inicial de la coincidencia, que es un valor mayor o igual que cero. Una vez detectada una coincidencia, extraemos el tipo de indicador utilizando la función StringSubstr, que recupera una subcadena desde la posición después de «INDICATOR TYPE» (comenzando en el índice 16) hasta el final de la línea. El valor extraído se almacena en la variable «indicator_type». Por último, imprimimos el índice de la línea y el «indicator_type» extraído utilizando la función Print para confirmar que los datos se han recuperado correctamente. Cuando ejecutamos esto, obtenemos la siguiente salida.

CONFIRMACIÓN DEL INDICADOR

Podemos ver que hemos recorrido con éxito todos los segmentos de comando e identificado el nombre del indicador. A continuación, necesitamos identificar el símbolo. Se utilizará una lógica similar pero un poco más compleja. 

            //--- 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
               }
            }            

En este bloque de código, comprobamos la entrada del usuario con la lista de símbolos de negociación disponibles y asignamos el correcto a la variable «indicator_symbol». En primer lugar, utilizamos la función SymbolsTotal, que devuelve el número total de símbolos disponibles actualmente en la plataforma. El argumento «true» especifica que queremos el número de símbolos visibles. A continuación, recorremos todos los símbolos disponibles mediante un bucle for con la variable «k» como índice.

Dentro del bucle, utilizamos la función SymbolName para obtener el nombre del símbolo en el índice «k». El segundo argumento, «true», indica que queremos el nombre del símbolo en su forma abreviada. Tras recuperar el nombre del símbolo y almacenarlo en la variable «selected_symbol», utilizamos la función StringCompare para comparar este «selected_symbol» con la entrada del usuario («selected_line»). El argumento "false" indica que la comparación no debe distinguir entre mayúsculas y minúsculas.

Si la función devuelve cero, significa que las dos cadenas coinciden, y asignamos «selected_symbol» a la variable «indicator_symbol». Por último, imprimimos el índice de la línea y el «indicator_symbol» coincidente utilizando la función Print para confirmar que hemos identificado y asignado correctamente el símbolo a partir de la entrada del usuario. Esto no contiene ningún texto adicional, por lo que buscamos directamente. Al ejecutarlo, no obtendremos ningún resultado con el fragmento de código actual porque el texto extraído y los símbolos predeterminados no son similares en el sentido de que distinguen entre mayúsculas y minúsculas, es decir, «XAUUSDM» no es igual a «XAUUSDm». Aquí está el nombre del símbolo que tenemos:

ESTRUCTURA PREDETERMINADA

Por lo tanto, necesitamos transformar el símbolo del sistema predeterminado a mayúsculas para hacer la comparación. El nuevo fragmento de código actualizado es el siguiente:

            //--- 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
               }
            }            

Con la nueva transformación podemos proceder a realizar la comparación y los resultados que obtenemos son los siguientes:

SÍMBOLO SELECCIONADO

Eso fue un éxito. Para obtener los demás detalles, empleamos una lógica similar.

            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);
            }
         }

Cuando ejecutamos esto, obtenemos la siguiente salida.

DETALLES DEL INDICADOR

Para ver los datos de una manera más estructurada, podemos imprimir toda la información extraída adquirida de la siguiente manera:

         //--- 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);

Imprimimos los datos finales extraídos para confirmar que las variables se han completado correctamente. En primer lugar, utilizamos la función «Print» para mostrar un mensaje de cabecera: «\nFINAL EXTRACTED DATA:». Esto sirve como una señal visual en los registros, marcando dónde se mostrarán los datos procesados.

A continuación, imprimimos secuencialmente los valores de cada una de las variables clave: «indicator_type», «indicator_symbol», «indicator_timeframe», «indicator_period», «indicator_shift», «indicator_method» y «indicator_app_price». Cada llamada a Print muestra el nombre de la variable y su valor actual. Esto es importante para la depuración y verificación, ya que asegura que los datos analizados de la entrada del usuario (por ejemplo, el tipo de indicador, el símbolo, el marco de tiempo, etc) ha sido capturado con precisión antes de que el sistema procede a añadir el indicador en el gráfico en MetaTrader 5. La salida que obtenemos se visualiza a continuación:

REGISTRO ORGANIZADO

¡Perfecto! Ahora que tenemos todos los detalles necesarios requeridos en un indicador de media móvil, podemos proceder a agregarlo al gráfico. Sin embargo, antes de agregarlo, podemos verificar que tengamos el indicador correcto según las instrucciones de Telegram. Esto se consigue mediante una sentencia if.

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

Una vez confirmado el indicador, podemos proceder a convertir las entradas extraídas en sus respectivas estructuras de tipos de datos. Tenga en cuenta que los valores actuales que tenemos son de tipo cadena o entero. El sistema no entenderá, por ejemplo, que la entrada del usuario "SMA" como método de media móvil se refiere a "SIMPLE MOVING AVERAGE" dentro de la enumeración ENUM\_MA\_METHOD, como se muestra a continuación.

ENUM_MA_METHOD

Por lo tanto, tenemos que explicárselo mejor al programa. Para ir sistemáticamente, empezaremos por lo más obvio, que es el marco temporal.

            //--- 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);
            }

En esta sección, convertimos el marco temporal proporcionado por el usuario a partir de la cadena «indicator_timeframe» extraída en la enumeración «ENUM_TIMEFRAMES» correspondiente de MetaTrader 5. Este paso es crucial porque MetaTrader 5 utiliza marcos temporales predefinidos como «PERIOD_M1» para gráficos de 1 minuto, «PERIOD_H1» para gráficos de 1 hora, etc. Estos plazos se definen como parte del tipo «ENUM_TIMEFRAMES».

Comenzamos inicializando la variable «timeframe_enum» al timeframe del gráfico actual, que está representado por _Period. Sirve como alternativa por defecto en caso de que el plazo proporcionado por el usuario no sea válido.

A continuación, utilizamos una serie de sentencias condicionales if else para comprobar el valor de la cadena «indicator_timeframe». Cada condición compara la cadena extraída con identificadores temporales conocidos, como «M1» (1 minuto), «H1» (1 hora), etc. Si se encuentra una coincidencia, el valor «ENUM_TIMEFRAMES» correspondiente (como «PERIOD_M1» o «PERIOD_H1») se asigna a la variable «timeframe_enum».

Si ninguna de las condiciones coincide, se activa el bloque final else, imprimiendo un mensaje de error en el registro que indica un «Invalid timeframe» junto con el valor de «indicator_timeframe.» Esto ayuda a asegurar que sólo los plazos válidos se pasan a MetaTrader 5 durante el proceso de creación del indicador. Del mismo modo, transformamos las demás variables en sus respectivas formas.

            //--- 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);
            }

Una vez realizado el proceso de transformación, ya podemos proceder a crear el manejador del indicador que utilizaremos para añadir el indicador al gráfico.

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

Aquí, creamos un manejador para el indicador de Media Móvil (Moving Average, MA) utilizando la función iMA. Esta función genera un indicador basado en los parámetros que hemos extraído y procesado anteriormente. El controlador nos permitirá referenciar y manipular el indicador en el gráfico más adelante en el código. Pasamos varios argumentos a la función "iMA", cada uno correspondiente a un parámetro que hemos recopilado del comando Telegram:

  • "indicator_symbol": Especifica el instrumento financiero (por ejemplo, EURUSD) al que se aplicará el indicador.
  • "timeframe_enum": Este argumento se refiere al marco temporal (por ejemplo, M1 para 1 minuto, H1 para 1 hora) que se convirtió previamente a partir de la entrada del usuario.
  • "(int)indicator_period": Esto convierte el «indicator_period» extraído de un tipo de datos long a un entero, representando el número de periodos para la MA.
  • "(int)indicator_shift": De forma similar, esto convierte «indicator_shift» en un número entero, definiendo cuántas barras desplazar el indicador en el gráfico.
  • "ma_method": Especifica el método de cálculo de la MA, como la media móvil simple (Simple Moving Average, SMA) o la media móvil exponencial (Exponential Moving Average, EMA), en función de los datos introducidos por el usuario.
  • "app_price_enum": Indica el tipo de precio aplicado (por ejemplo, precio de cierre o precio de apertura) sobre el que se calculará la MA.

El resultado de la función «iMA» se almacena en la variable «handle_ma». Si la función crea con éxito un indicador «handle», «handle_ma» contendrá una referencia válida. Si la creación falla, devolverá INVALID_HANDLE, indicando que hubo un problema con uno o más parámetros. Puesto que sabemos que INVALID HANDLE es una representación de fallo, podemos proceder a utilizarla para el procesamiento posterior.

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

                //...

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

Aquí comprobamos si se ha creado correctamente el handle del indicador Moving Average (MA) verificando el valor de «handle_ma.» Si el handle es válido, la función iMA devolverá un valor que no es igual a INVALID_HANDLE. En ese caso, imprimimos un mensaje indicando éxito: «Successfully created the indicator handle!». Esto significa que todos los parámetros (símbolo, marco temporal, período, método, etc.) se han interpretado correctamente y que el indicador MA está listo para utilizarse en el gráfico.

Si la creación del handle falla, es decir, «handle_ma» es igual a INVALID_HANDLE, imprimimos un mensaje de error: «Failed to create the indicator handle!». Esta condición indica que algo salió mal durante el proceso de creación del indicador, como un símbolo no válido, un período de tiempo incorrecto o cualquier otro parámetro incorrecto. Este manejo de errores nos ayuda a garantizar que el sistema pueda detectar problemas y proporcionar información durante el proceso de configuración del indicador. Luego podemos proceder a abrir un gráfico con el símbolo y el período de tiempo especificados, asegurarnos de que esté sincronizado con los datos más recientes y ajustar su configuración para mayor claridad.

               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);

En primer lugar, utilizamos la función ChartOpen para abrir un nuevo gráfico basado en el «indicator_symbol» y el «timeframe_enum», que previamente hemos extraído del comando Telegram. El ID del gráfico es devuelto y almacenado en la variable «chart_id». Para traer este gráfico al frente en MetaTrader 5, usamos la función ChartSetInteger, pasando el ID del gráfico junto con la constante «CHART_BRING_TO_TOP» para asegurar que el gráfico sea visible para la interacción.

A continuación, implementamos una verificación de sincronización para asegurarnos de que los datos de precios del gráfico estén completamente actualizados. Para ello, se realiza un bucle de hasta 60 veces utilizando la función SeriesInfoInteger para comprobar si la serie de datos de precios está sincronizada. Si la sincronización se produce antes de que se complete el bucle, salimos antes. Después de confirmar que los datos están actualizados, pasamos a personalizar la apariencia del gráfico. La rejilla y los separadores de periodo se ocultan usando la función ChartSetInteger, donde pasamos «CHART_SHOW_GRID» y «CHART_SHOW_PERIOD_SEP» como falso, creando una vista de gráfico más limpia.

Tras estos ajustes, se fuerza la actualización visual del gráfico mediante la función ChartRedraw. Por último, se añade una pausa de 7 segundos con la función Sleep para dar tiempo a que el gráfico y los datos se carguen completamente antes de proceder con cualquier otra operación. Todo este proceso garantiza que el gráfico esté listo para la interacción y la visualización, con datos actualizados y una configuración visual limpia. Después de todo esto, podemos continuar agregando el indicador al gráfico.

               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.");
               }   

Aquí, añadimos el indicador Moving Average (MA) creado previamente al gráfico que hemos abierto antes. Esto lo conseguimos utilizando la función ChartIndicatorAdd, donde pasamos el «chart_id», un cero (que indica que estamos añadiendo el indicador al gráfico principal), y el «handle_ma», que representa el handle del indicador MA que hemos creado. Si la suma se realiza correctamente, se imprime un mensaje de éxito que dice: «SUCCESS.». Indicador añadido al gráfico», y establece la variable booleana “isIndicatorAddedToChart” en true, indicando que el indicador está ahora activo en el gráfico. Definimos la variable booleana y la inicializamos a false fuera de la sentencia «if» como se muestra.

         bool isIndicatorAddedToChart = false;

Por el contrario, si la adición falla, imprimimos un mensaje de error que dice: «FAIL: Indicator not added to the chart». Esta comprobación es crucial porque garantiza que podamos confirmar si el indicador se aplicó correctamente al gráfico, lo que es esencial para las operaciones comerciales posteriores y el análisis visual. Al gestionar ambos resultados, mantenemos la transparencia en nuestro proceso y estamos informados sobre el estado del indicador en el gráfico. Si creamos y agregamos el indicador al gráfico, podemos informar al usuario del éxito en la misma estructura de código.

         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
         }
 

Aquí comprobamos si el indicador se ha añadido correctamente al gráfico evaluando la variable booleana «isIndicatorAddedToChart». Si esta condición es verdadera, procedemos a preparar un mensaje de éxito que detalla la configuración del indicador. Comenzamos inicializando una variable de cadena llamada «mensaje» con una cabecera de mensaje de éxito: «\nSUCCESS! THE INDICATOR WAS ADDED TO THE CHART WITH THE FOLLOWING DETAILS:\n". Luego concatenamos varias piezas de información sobre el indicador, incluido su tipo, símbolo, marco temporal, período, cambio y precio aplicado. Para los valores numéricos, utilizamos la función IntegerToString para asegurarnos de que se convierten a formato de cadena para una concatenación adecuada.

Después de recopilar toda esta información, añadimos una línea final al mensaje, que dice «\nHAPPY TRADING!» para transmitir un sentimiento positivo. A continuación, utilizamos la función Print para dar salida al mensaje completo en el terminal, proporcionando una confirmación clara de la adición del indicador y sus detalles. Finalmente, llamamos a la función «sendMessageToTelegram» para enviar el mismo mensaje de éxito a Telegram, asegurándonos de que el chat relevante, identificado por «chat.member_id», es notificado sobre la operación exitosa. Cuando ejecutamos esto obtenemos el siguiente resultado.

SALIDA DE ERROR

Podemos ver que a pesar de tener el símbolo en el reloj del mercado, todavía devolvemos un mensaje de error. Esto se debe a que interferimos con la estructura correcta del nombre del símbolo al transformar todo a mayúsculas. Para recuperar la estructura correcta manteniendo al mismo tiempo la interpretación y comparación correctas de los símbolos, podemos usar una gran cantidad de opciones, pero la más sencilla es agregar directamente el nombre del símbolo inicial a la variable contenedora como se muestra.

            //--- 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
               }
            }            

El único cambio que ocurrió en el fragmento se muestra y resalta en color amarillo. Cuando volvemos a ejecutarlo, obtenemos el resultado correcto como se muestra a continuación:

SALIDA CORRECTA

Hasta este punto, hemos agregado con éxito el indicador al gráfico. En Telegram, obtenemos la respuesta de éxito como se visualiza a continuación:

CONFIRMACIÓN DE TELEGRAM

En la terminal comercial, se abre un nuevo gráfico y se agrega el indicador. A continuación se muestra una confirmación visual.

CONFIRMACIÓN MT5

Hasta este punto, podemos decir que hemos logrado nuestro objetivo de agregar automáticamente indicadores técnicos de Media Móvil a los gráficos. Una metodología similar se puede aplicar a otros indicadores, como el Awesome Oscillator. Al seguir el mismo formato para identificar el comando, extraer parámetros relevantes y ejecutar la adición del indicador, podemos integrar sin problemas varios indicadores en nuestro sistema comercial, manteniendo la consistencia y la eficiencia durante toda la implementación. Sin embargo, necesitamos probar la implementación y confirmar que todo funciona bien. Esto se hace en la siguiente sección.


Prueba del sistema de trading de indicadores

En esta sección nos centraremos en validar la funcionalidad de nuestro sistema de trading de indicadores. La prueba implica verificar que los indicadores estén configurados correctamente y respondan adecuadamente a los comandos recibidos de Telegram. Examinaremos el proceso de agregar indicadores al gráfico, asegurándonos de que todos los parámetros estén configurados con precisión y de que los indicadores se muestren correctamente en los gráficos.

Para proporcionar una visión clara de este proceso, hemos incluido un vídeo que demuestra la configuración de los indicadores en MetaTrader 5, más particularmente la media móvil, destacando cómo funciona el sistema y confirmando su preparación para escenarios comerciales reales, como se muestra a continuación.



Conclusión

En conclusión, en este artículo hemos demostrado cómo analizar y procesar comandos de indicadores recibidos a través de Telegram para automatizar la adición de indicadores a sus gráficos de MetaTrader 5. Al implementar estas técnicas, hemos simplificado el proceso, mejorando la eficiencia comercial y reduciendo la posibilidad de errores humanos.

Con el conocimiento adquirido con esta implementación, usted estará preparado para desarrollar sistemas más sofisticados que incorporen indicadores y comandos adicionales. Esta base le permitirá perfeccionar sus estrategias comerciales, adaptarse a las condiciones cambiantes del mercado y, en última instancia, mejorar su desempeño comercial. Esperamos que el artículo le haya resultado fácil de entender y que le haya proporcionado los conocimientos necesarios para mejorar sus sistemas de trading. Si tiene alguna pregunta o necesita más aclaraciones, no dude en explorar recursos adicionales o experimentar con los conceptos compartidos a través de esta serie. ¡Feliz trading!

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/15962

Archivos adjuntos |
Métodos de optimización de la biblioteca ALGLIB (Parte II) Métodos de optimización de la biblioteca ALGLIB (Parte II)
En este artículo seguiremos analizando los métodos restantes de optimización de la biblioteca ALGLIB, prestando especial atención a su comprobación con funciones multivariantes complejas. Esto nos permitirá no solo evaluar el rendimiento de cada algoritmo, sino también identificar sus puntos fuertes y débiles en diferentes condiciones.
Redes neuronales en el trading: Representación adaptativa de grafos (NAFS) Redes neuronales en el trading: Representación adaptativa de grafos (NAFS)
Hoy le proponemos familiarizarse con el método Node-Adaptive Feature Smoothing (NAFS), que supone un enfoque no paramétrico para crear representaciones de nodos que no requiere entrenamiento de parámetros. El NAFS extrae las características de cada nodo considerando sus vecinos y luego combina adaptativamente dichas características para formar la representación final.
Aprendizaje automático y Data Science (Parte 30): La pareja ideal para predecir el mercado bursátil: redes neuronales convolucionales (CNN) y recurrentes (RNN) Aprendizaje automático y Data Science (Parte 30): La pareja ideal para predecir el mercado bursátil: redes neuronales convolucionales (CNN) y recurrentes (RNN)
En este artículo exploramos la integración dinámica de redes neuronales convolucionales (CNN) y redes neuronales recurrentes (RNN) en la predicción bursátil. Aprovechando la capacidad de las CNN para extraer patrones y la destreza de las RNN para manejar datos secuenciales. Veamos cómo esta potente combinación puede mejorar la precisión y la eficacia de los algoritmos de negociación.
Creación de un modelo de restricción de tendencia de velas (Parte 9): Asesor Experto de múltiples estrategias (I) Creación de un modelo de restricción de tendencia de velas (Parte 9): Asesor Experto de múltiples estrategias (I)
Hoy, exploraremos las posibilidades de incorporar múltiples estrategias en un Asesor Experto (Expert Advisor, EA) utilizando MQL5. Los asesores expertos ofrecen capacidades más amplias que solo indicadores y scripts, lo que permite enfoques comerciales más sofisticados que pueden adaptarse a las condiciones cambiantes del mercado. Encuentre más información en este artículo de discusión.