Cómo crear un bot para Telegram en el lenguaje MQL5

Andriy Voitenko | 31 mayo, 2016

Introducción

El 12 de abril de 2016 en la conferencia F8 en San Francisco, la compañía Facebook anunció la implementación de una API para bots en su messenger. El mismo día salió una gran actualización para la plataforma Telegram Bot Plataform. La versión 2.0 alegró a los usuarios con nuevas características. Al parecer, el público ha recobrado el interés por los bots, que fueron populares en la época de ICQ. En un nuevo giro de la espiral del desarrollo de los bots se les ha dado una funcionalidad más meditada, una interfaz abierta para la programación y soporte multimedia. En general, ahora tienen todo lo necesario para con vertirse en algo imprescindible cuando el usuario quiere buscar, ver y comprar.

Este artículo pretende ser una guía paso a paso para crear bots para Telegrama en MQL5. ¿Qué es un bot? Un bot (abreviatura de "robot") es una cuenta especial en Telegram que proporciona la oportunidad de intercambiar mensajes de forma programática. El bot se ejecuta en su lado (cliente) e interactúa con el servidor de Telegram a través de un conjunto especial de comandos incluido en la Bot API. Antes de proceder a la creación del bot, por favor, instale Telegram y autorícese en este servicio. El registro está ligado a un número telefónico, pero, además, usted podrá tener un @username, con ayuda del cual será posible localizarle a través de una búsqueda. Ahora es el momento de tener un primer contacto con la carpeta de todos los bots.

Registro de un nuevo bot

El bot especial @BotFather es el responsable del registro y configuración de bots. Lo encontraremos a través de la búsqueda. Después de añadirlo a la lista de contactos, comenzaremos el contacto con el comando /start. Como respuesta, nos enviará una lista con todos los comandos disponibles, como se muestra en la figura 1.


Lista de comandos de @BotFather.

Fig. 1. Lista de comandos de @BotFather

Con el comando /newbot comenzaremos el registro de un nuevo bot. Ahora es necesario pensar dos nombres. El primer nombre (name) del bot podrá pensarlo en su lengua nativa. El segundo es el nombre del usuario del bot (username) en alfabeto latino, y termina con el sufijo “bot”. Como resultado, obtendremos un token: la llave de acceso para trabajar con el bot a través de una API. Mostramos un ejemplo de registro en la figura 2.


Registro de un nuevo bot

Fig.2.  Registro de un nuevo bot.

Si lo desea, es posible cambiar ciertos parámetros. Los ajustes del modo inline es mejor no tocarlos. Nuestros bots no van a trabajar con ellos. Recomiendo configurar exclusivamente los ajustes cosméticos:
  • /setcommands sirve para establecer la lista de comandos con soporte. Esta lista se mostrarán los usuarios en forma de pista emergente al introducir el símbolo "/" en la ventana del chat.
  • /setuserpic sirve para establecer la fotografíca del perfil. Sin foto, el bot no tendrá un aspecto serio.
  • /setdescription es el texto que se representará como saludo al añadir el bot al messenger. Aquí normalmente se indican las tareas del bot en unas cunetas frases.

Bien, ya tenemos al bot registrado. Hablemos un poco de las formas en las que se puede usar.

Modos de funcionamiento de los bots

En Telegram existen tres esquemas de interacción de los bots con los usuarios. El primero son los chats privados. Cada usuario se relaciona con un bot en el modo solicitud-respuesta independientemente el uno del otro, como se muestra en la figura 3.


bot y chats privados

Fig.3.  Bot y chats privados.


Los usuarios envían mensajes al bot. Estos mensajes se guardan en el servidor no más de un día, y después se eliminan. El bot dispone de un tiempo determinado para solicitar estos mensajes y reponder a ellos. Este es el modo principal en el que trabajarán nuestros bots.

El segundo modo es el chat en grupo. Aquí el mensaje que haya enviado cualquiera de los integrantes del grupo será visto por todos (figura 4).

bot y chat de grupo

Fig.4. Bot en el chat de grupo.

En lo que respecta a los bots, se les puede permitir entrar en el grupo a través del comando /setjoingroups. Si el bot ha sido añadido a un grupo, con la ayuda del comando /setprivacy se puede configurar la posibilidad de recibir todos los mensajes, o bien aquellos que comiencen con el distintivo de comando del símbolo “/”. Para ser sinceros, he logrado inventar solo un valor para el bot en este modo: se trata de la recopilación de mensajes estadísticos para su posterior análisis.

El tercer modo es el trabajo en el canal. Los canales en Telegram son cuentas para transmitir mensajes a un auditorio amplio, y que son compatibles con una cantidad ilimitada de suscriptores. Una peculiaridad especial de los canales es la posibilidad de los usaurios de dejar comentarios y likes en la línea de mensajes (la conexión es unilateral). Solo los administradores del canal pueden crear un mensaje en la línea (figura 5).


Bot como administrador del canal

Fig.5. Bot como administrador del canal.

A la lista de administradores también se pueden añadir bots. Esto convertirá al canal en un instrumento ideal para la distribución de señales. Un poco más tarde escribiremos un bot sencillo que publica señales del indicador estándar MACD. Es posible crear un nuevo canal público a través del menú “New Channel” del messenger. No se olvide de introducir su bot en la lista de administradores del canal. Esto se hace a través de la ventana de propiedades del canal. Con esto ya hemos terminado los trabajos preparativos y podemos proceder a la programación.


Procesamiento del flujo de mensajes

Mientras escribía este artículo, surgió la necesidad de crear una clase que asuma la rutina de procesar los mensajes y que permita concentrarse en la lógica de funcionamiento del propio bot. Los resultados han sido escritos en la clase CCustomBot, que implementa la funcionalidad mínima imprescindible para trabajar.

El contacto con el servidor tiene lugar mediante solicitudes POST, usando la función WebRequst. Para cada comando hay un URL propio:

https://api.telegram.org/bot< TOKEN >/ METHOD_NAME

donde, TOKEN es el token del bot registrado; METHOD_NAME es la lista de los métodos soportados.

Las respuestas al servidor llegan en formato JSON, por eso necesitaremos un buen parser JSON. Yo he aplicado el parser nativo JSON Serialization and Deserialization. Quiero agradecer a Alexey (sergeev) por el trabajo que ha realizado. También he utilizado un panel para representar ciertos parámetros. La clase CComment, tomada de Codebase, ha encajado muy bien con esta tarea. Para que tenga un carácter más universal, las denominaciones de los métodos se han tomado de la documentación sobre Bot API. La lista de métodos que se ha logrado implementar en la clase se muestra más abajo:

Vamos a profundizar en la programación, para comprobar cómo se usan estas funciones.


GetMe

Puesto que el token se envía en cada solicitud, ante todo se implementa la función GetMe, que comprueba su autenticidad. Es mejor realizar esta comprobación al iniciar el experto y en caso de que no se tenga éxito, informar al usuario sobre ello.

int GetMe()
 Valor devuelto  código de error

En caso de éxito, GetMe retorna 0, y a través del método Name() se puede conocer el nombre (username) del bot. Este nombre no participa en el funcionamiento. Sin embargo, se destacará en el panel como información. Una dirección como telegram.me/<botname> permite usar la versión Web del messenger y servirá como enlace para los anuncios de su bot. El experto con comprobación de token OnInit puede tener el aspecto siguiente:

//+------------------------------------------------------------------+
//|                                               Telegram_GetMe.mq5 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Telegram.mqh>

input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//Token

CCustomBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set token
   bot.Token(InpToken);
//--- check token
   getme_result=bot.GetMe();
//--- run timer
   EventSetTimer(3);
   OnTimer();
//--- done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- show error message end exit
   if(getme_result!=0)
     {
      Comment("Error: ",GetErrorDescription(getme_result));
      return;
     }
//--- show bot name
   Comment("Bot name: ",bot.Name());

//---{ insert your code here }
  }
//+------------------------------------------------------------------+

GetUpdates

La función principal GetUpdates lee la matriz de los mensajes guardados en el servidor. Es imprescindible llamarla con un temporizador. Lo mejor será poner como periodo de actualización del temporizador 1 segundo, para no sobrecargar el servidor.

int GetUpdate()
 Valor devuelto  código de error

Veamos con más detalle el contenido de esta función. Al llamarla, tiene lugar la lectura y el parseo de todos los mensajes no leídos de los usuarios. Más abajo se muestra un ejemplo de un mensaje así:

{ 
   "ok":true,
   "result":[ 
      { 
         "update_id":349778698,
         "message":{ 
            "message_id":2,
            "from":{ 
               "id":198289825,
               "first_name":"Andriy",
               "last_name":"Voitenko",
               "username":"avaticks"
            },
            "chat":{ 
               "id":198289825,
               "first_name":"Andriy",
               "last_name":"Voitenko",
               "username":"avaticks",
               "type":"private"
            },
            "date":1459775817,
            "text":"\/start"
         }
      }
   ]
}

El usuario con el nick avaticks ha enviado al bot el comando /start. La tarea consiste en guardar estos mensajes para contestar posteriormente a ellos. En este caso, el identificador único es el número de chat chat[id]. Un mismo usuario que se relacione con un bot a través de diferentes dispositivos, tendrá diferentes identificadores de chats. Este parámetro sirve como clave única para construir la lista de chats. Durante su funcionamiento, el bot acumulará datos en la matriz de chats y actualizará en cada uno de ellos el último mensaje enviado. Si hemos respondido al mensaje, entonces este mensaje se considerará procesado y se le pondrá la bandera done. El tipo de chat también es conocido. Puede ser o bien privado (private), o bien en grupo (group).

Para escribir nuestro propio bot necesitamos solo heredar de CCustomBot y redefinir en nuestra clase la función virtual ProcessMessage, que está diseñada para implementar en ella nuestra lógica de funcionamiento. De acuerdo con la documentación de Telegram, un bot plenamente funcional debe saber responder a dos comandos "/start" y "/help". Vamos a escribir el primer bot, que contestará a estos comandos.

//+------------------------------------------------------------------+
//|                                          Telegram_GetUpdates.mq5 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Telegram.mqh>
//+------------------------------------------------------------------+
//|   CMyBot                                                         |
//+------------------------------------------------------------------+
class CMyBot: public CCustomBot
  {
public:
   void ProcessMessages(void)
     {
      for(int i=0; i<m_chats.Total(); i++)
        {
         CCustomChat *chat=m_chats.GetNodeAtIndex(i);
         //--- if the message is not processed
         if(!chat.m_new_one.done)
           {
            chat.m_new_one.done=true;
            string text=chat.m_new_one.message_text;

            //--- start
            if(text=="/start")
               SendMessage(chat.m_id,"Hello, world! I am bot. \xF680");

            //--- help
            if(text=="/help")
               SendMessage(chat.m_id,"My commands list: \n/start-start chatting with me \n/help-get help");
           }
        }
     }
  };

//---
input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//Token
//---
CMyBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set token
   bot.Token(InpToken);
//--- check token
   getme_result=bot.GetMe();
//--- run timer
   EventSetTimer(3);
   OnTimer();
//--- done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- show error message end exit
   if(getme_result!=0)
     {
      Comment("Error: ",GetErrorDescription(getme_result));
      return;
     }
//--- show bot name
   Comment("Bot name: ",bot.Name());
//--- reading messages
   bot.GetUpdates();
//--- processing messages
   bot.ProcessMessages();
  }
//+------------------------------------------------------------------+

Los resultados de su funcionamiento se muestran en la figura 6.


Bot con un conjunto mínimo de comandos

Fig.6. Bot con un conjunto mínimo de comandos.

Trabajando con el teclado

Para la interacción con el usuario, hemos pensado en “teclados” para los bots. Al enviar mensajes, para cada chat se puede representar un “teclado” con un conjunto de teclas preestablecido. Pulsando una tecla, el usuario envía un mensaje con el texto indicado en ella. De esta forma, se simplifica notoriamente la interacción del bot con el usuario.

En la clase hay tres funciones para trabajar con el teclado. La primera de ellas crea el objeto del teclado.

string ReplyKeyboardMarkup(const string keyboard,
                           const bool resize,
                           const bool one_time)
 keyboard
 línea que define la ubicación de las teclas
 resize  permiso para modificar las dimensiones del teclado
 one_time  mostrar el teclado solo una vez. Después de pulsar la tecla, el teclado desaparece.
 Valor devuelto línea (objeto JSON), que se debe transmitir como parámetro reply_markup al enviar un mensaje con ayuda de SendMessage

La segunda función oculta el teclado.

string ReplyKeyboardHide()
 Valor devuelto línea (objeto JSON), que se debe transmitir como parámetro reply_markup al enviar un mensaje con ayuda de SendMessage

La tercera función permite enviar un pequeño panel cuyo aspecto indica que el bot espera respuesta de usted en forma de texto (el teclado no se muestra).

string ForceReply()
 Valor devuelto línea (objeto JSON), que se debe transmitir como parámetro reply_markup al enviar un mensaje con ayuda de SendMessage

Ahora veremos cómo se usan estas funciones.

SendMessage

El teclado no puede mostrarse o ocultarse por sí mismo. Las acciones se envían junto con el mensaje. La función SendMessage para el envío de mensajes en el chat tiene el aspecto siguiente:

int SendMessage(const long chat_id,
                const string text,
                const string reply_markup=NULL)
 chat_id
 número de chat
 text  texto del mensaje
 reply markup  teclado (objeto JSON)
 Valor devuelto  código de error

En este caso el teclado es opcional. Podemos enviar sencillos mensajes de texto desde nuestros programas MQL. A mi entender, esta función es más interesante que la nativa SendNotification. En primer lugar, podemos enviar mensajes con mayor frecuencia (aproximadamente una vez por segundo). En segundo lugar, es compatible con el formato HTML. Un plus bastante importante es la posibilidad de enviar emoticonos.

Тelegram da soporte a una gran cantidad de emoticonos (Emoji), cuyo recuadro se puede ver aquí. Como habrá notado, la inmensa mayoría de los códigos de los emoticonos se encuentra en el diapasón (1F300 – 1F700). Su número de secuencias supera los límites de la codificación de línea de dos bytes habitual en MQL5. Si quitamos las secuencias antiguas para que quede un número de dos bytes, entonces el diapasón obtenido (F300 – F700) entrará en la zona de (E000— F8FF), que en el recuadro Unicode está reservada para el uso privado. De esta forma, nadie nos impedirá usar dos bytes menores enviar un emoticono. La línea del mensaje con un emoticono sonriente clásico con código U+1F642 puede tener el aspecto siguiente:

string text="Have a nice day.\xF642";//texto del mensaje con el emoticono U+1F642

Esto es legítimo también para las teclas, ya que ellas, en esencia, son texto. Nada nos impide usar emoticonos en las teclas. Vamos a escribir un ejemplo para la representación de tres teclas con procesador de eventos.

//+------------------------------------------------------------------+
//|                                         Telegram_SendMessage.mq5 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Telegram.mqh>
//+------------------------------------------------------------------+
//|   CMyBot                                                         |
//+------------------------------------------------------------------+
class CMyBot: public CCustomBot
  {
private:
   string            m_button[3];
public:
   //+------------------------------------------------------------------+
   void CMyBot::CMyBot(void)
     {
      m_button[0]="Button #1";
      m_button[1]="Button #2";
      m_button[2]="Button #3";
     }

   //+------------------------------------------------------------------+
   string GetKeyboard()
     {
      return("[[\""+m_button[0]+"\"],[\""+m_button[1]+"\"],[\""+m_button[2]+"\"]]");
     }

   //+------------------------------------------------------------------+
   void ProcessMessages(void)
     {
      for(int i=0;i<m_chats.Total();i++)
        {
         CCustomChat *chat=m_chats.GetNodeAtIndex(i);
         if(!chat.m_new_one.done)
           {
            chat.m_new_one.done=true;
            string text=chat.m_new_one.message_text;

            //--- start or help commands
            if(text=="/start" || text=="/help")
               bot.SendMessage(chat.m_id,"Click on the buttons",bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));

            //--- on click event
            int total=ArraySize(m_button);
            for(int k=0;k<total;k++)
              {
               if(text==m_button[k])
                  bot.SendMessage(chat.m_id,m_button[k],bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }
           }
        }
     }
  };

input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//Token

CMyBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set token
   bot.Token(InpToken);
//--- check token
   getme_result=bot.GetMe();
//--- run timer
   EventSetTimer(1);
   OnTimer();
//--- done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- show error message end exit
   if(getme_result!=0)
     {
      Comment("Error: ",GetErrorDescription(getme_result));
      return;
     }
//--- show bot name
   Comment("Bot name: ",bot.Name());
//--- reading messages
   bot.GetUpdates();
//--- processing messages
   bot.ProcessMessages();
  }
//+------------------------------------------------------------------+

Como resultado, obtenemos el mensaje con un teclado mostrado en la figura 7.

mensaje con un teclado

Fig.7. mensaje con un teclado.

Ahora vamos a probar a implementar un análogo de los elementos de control RadioButton y CheckBox. Por ejemplo, necesitamos elegir una de las tres opciones y también activar o desactivar una opción determinada. Los cambios están relacionados solo con nuestra clase, por eso el resto del código del experto permanece intacto del ejemplo anterior.
//+------------------------------------------------------------------+
//|                                         Telegram_SendMessage.mq5 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Telegram.mqh>

#define MUTE_TEXT       "Mute"
#define UNMUTE_TEXT     "Unmute"

#define LOCK_TEXT       "Lock"
#define UNLOCK_TEXT     "Unlock"

#define RADIO_SELECT    "\xF518"
#define RADIO_EMPTY     "\x26AA"

#define MUTE_CODE       "\xF515"
#define UNMUTE_CODE     "\xF514"

#define LOCK_CODE       "\xF512"
#define UNLOCK_CODE     "\xF513"
//+------------------------------------------------------------------+
//|   CMyBot                                                         |
//+------------------------------------------------------------------+
class CMyBot: public CCustomBot
  {
private:
   string            m_radio_button[3];
   int               m_radio_index;
   bool              m_lock_state;
   bool              m_mute_state;

public:
   //+------------------------------------------------------------------+
   void CMyBot::CMyBot(void)
     {
      m_radio_button[0]="Radio Button #1";
      m_radio_button[1]="Radio Button #2";
      m_radio_button[2]="Radio Button #3";
      m_radio_index=0;
      m_lock_state=false;
      m_mute_state=true;
     }

   //+------------------------------------------------------------------+
   string GetKeyboard()
     {
      //---
      string radio_code[3]={RADIO_EMPTY,RADIO_EMPTY,RADIO_EMPTY};
      if(m_radio_index>=0 && m_radio_index<=2)
         radio_code[m_radio_index]=RADIO_SELECT;
      //---
      string mute_text=UNMUTE_TEXT;
      string mute_code=UNMUTE_CODE;
      if(m_mute_state)
        {
         mute_text=MUTE_TEXT;
         mute_code=MUTE_CODE;
        }
      //---
      string lock_text=UNLOCK_TEXT;
      string lock_code=UNLOCK_CODE;
      if(m_lock_state)
        {
         lock_text=LOCK_TEXT;
         lock_code=LOCK_CODE;
        }
      //---
      //Print(m_lock.GetKey());
      return(StringFormat("[[\"%s %s\"],[\"%s %s\"],[\"%s %s\"],[\"%s %s\",\"%s %s\"]]",
             radio_code[0],m_radio_button[0],
             radio_code[1],m_radio_button[1],
             radio_code[2],m_radio_button[2],
             lock_code,lock_text,
             mute_code,mute_text));
     }

   //+------------------------------------------------------------------+
   void ProcessMessages(void)
     {
      for(int i=0;i<m_chats.Total();i++)
        {
         CCustomChat *chat=m_chats.GetNodeAtIndex(i);
         if(!chat.m_new_one.done)
           {
            chat.m_new_one.done=true;
            string text=chat.m_new_one.message_text;

            //--- start
            if(text=="/start" || text=="/help")
              {
               bot.SendMessage(chat.m_id,"Click on the buttons",bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- Click on a RadioButton
            int total=ArraySize(m_radio_button);
            for(int k=0;k<total;k++)
              {
               if(text==RADIO_EMPTY+" "+m_radio_button[k])
                 {
                  m_radio_index=k;
                  bot.SendMessage(chat.m_id,m_radio_button[k],bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
                 }
              }

            //--- Unlock
            if(text==LOCK_CODE+" "+LOCK_TEXT)
              {
               m_lock_state=false;
               bot.SendMessage(chat.m_id,UNLOCK_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- Lock
            if(text==UNLOCK_CODE+" "+UNLOCK_TEXT)
              {
               m_lock_state=true;
               bot.SendMessage(chat.m_id,LOCK_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- Unmute
            if(text==MUTE_CODE+" "+MUTE_TEXT)
              {
               m_mute_state=false;
               bot.SendMessage(chat.m_id,UNMUTE_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- Mute
            if(text==UNMUTE_CODE+" "+UNMUTE_TEXT)
              {
               m_mute_state=true;
               bot.SendMessage(chat.m_id,MUTE_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }
           }
        }
     }
  };

Como resultado, obtenemos una ventana con el aspecto siguiente (figura 8).

Fig.8. Los elementos de control RadioButton y CheckBox

Como puede ver, aquí los emoticonos transmiten a los ajustes una gran claridad. Aparte de estos elementos de control, podemos implementar de forma igualmente sencilla un menú jerarquizado de navegación en cada submenú. En general, todo dependerá de la funcionalidad que usted haya pensado y quiera implementar.

Si queremos publicar mensajes en el canal, para ello tenemos una segunda variante, SendMessage.

int SendMessage(const string channel_name,
                const string text)
 channel_name
 nombre del canal en forma @name
 text  texto del mensaje. Se da soporte a tags de HTML.
 Valor devuelto  código de error

El resultado del trabajo de esta función se puede ver más abajo, en la figura 9.

Trabajando con multimedia

Los bots saben intercambiar fotografías, archivos de audio y de vídeo, así como mensajes de voz, stickers y coordenadas de ubicación. En la época en que escribí el artículo, salió la versión Bot API 2.0, a la que se ha añadido la posibilidad de intercambiar datos de contacto e invitaciones para un encuentro. De todo lo enumerado, para nuestros bots es relevante solo el intercambio de fotografías.

SendPhoto

En la clase se ha implementado el envío de fotos con dos variantes de uso.

int SendPhoto(const long   chat_id,
              const string local_path,
              string       &photo_id,
              const string caption=NULL,
              const bool   common_flag=false,
              const int    timeout=10000)
 chat_id  número de chat
 local_path  ruta local al archivo en la carpeta <catálogo de datos>\MQL5\Files
 photo_id  identificador de la foto cargada en el servidor
 caption  texto de la foto
 common_flag  bandera de ubicación del archivo en la carpeta general de todos los terminales de cliente \Terminal\Common\Files
 timeout  timeout de la operación en milisegundos

Ejemplo del código que envía una foto:

CCustomBot bot;

string token = "208375865:AAFnuOjlZ3Wsdan6PAjeqqUtBybe0Di1or8";

bot.Token(token);

string photo_id;
int result=bot.SendPhoto(198289825,"EURUSD1.gif",photo_id,"screenshot");
if(result==0)
   Print("Photo ID: ",photo_id);
else
   Print("Error: ",GetErrorDescription(result));

Pienso que habrá casos en los que tendrá la necesidad de enviar una foto a varios usuarios o bien enviar una foto repetidas veces. En este caso, lo razonable será descargar las fotos una a una, y para el envío repetido, usar el identificador photo_id junto con la segunda variante de la función SendPhoto:

int SendPhoto(const long chat_id,
              const string photo_id,
              const string caption=NULL)
 chat_id  número de chat
 photo_id  identificador de la foto cargada en el servidor
 caption  texto de la foto

SendChartAction

Imagine que está procesando la respuesta de un usuario y ya está listo para darle el resultado. Pero tardará varios segundos en crear la respuesta. Estaría bien notificar al usuario que está usted en ello. Para ello existen los eventos. Por ejemplo, mientras se formatea una captura de pantalla del gráfico para enviarla a un usuario, usted puede enviar el evento "envío de foto". Esto se hace con la ayuda de SendChatAction.

int SendChatAction(const long chat_id,
                   const ENUM_CHAT_ACTION actiona)
 chat_id  número de chat
 action  identificador del evento
Todas la funciones descritas aquí han sido implementadas en tres bots de demostración, sobre los que hablaremos más tarde.

Ejemplos de bots

El primer bot Telegram_Bot_EA permite obtener información sobre el estado de la cuenta, las cotizaciones y las capturas de pantalla. Su funcionamiento se muestra en este vídeo.


El segundo bot Telegram_Search_EA envía los resultados de la búsqueda a la página MQL5.com. Seguro que le interesará ver cómo funciona en el siguiente vídeo.


El tercer bot Telegram_Signal_EA publica en el canal las señales del indicador estándar MACD. Creo que podrá sustituir fácilmente MACD por su indicador favorito y poner su código en funcionamiento para usted.

//+------------------------------------------------------------------+
//|                                        Telegram_Signal_EA_v1.mq4 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//|   Includes                                                       |
//+------------------------------------------------------------------+
#include <Telegram.mqh>

//--- Input parameters
input string InpChannelName="@forexsignalchannel";//Channel Name
input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//Token

//--- Global variables
CCustomBot bot;
int macd_handle;
datetime time_signal=0;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   time_signal=0;

//--- set token
   bot.Token(InpToken);

//--- get an indicator handle
   macd_handle=iMACD(NULL,0,12,26,9,PRICE_CLOSE);
   if(macd_handle==INVALID_HANDLE)
      return(INIT_FAILED);

//--- done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

//--- get time
   datetime time[1];
   if(CopyTime(NULL,0,0,1,time)!=1)
      return;

//--- check the signal on each bar
   if(time_signal!=time[0])
     {
      //--- first calc
      if(time_signal==0)
        {
         time_signal=time[0];
         return;
        }

      double macd[2]={0.0};
      double signal[2]={0.0};

      if(CopyBuffer(macd_handle,0,0,2,macd)!=2)
         return;
      if(CopyBuffer(macd_handle,1,0,2,signal)!=2)
         return;

      time_signal=time[0];

      //--- Send signal BUY
      if(macd[1]>signal[1] && 
         macd[0]<=signal[0])
        {
         string msg=StringFormat("Name: MACD Signal\nSymbol: %s\nTimeframe: %s\nType: Buy\nPrice: %s\nTime: %s",
                                 _Symbol,
                                 StringSubstr(EnumToString(_Period),7),
                                 DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits),
                                 TimeToString(time[0]));
         int res=bot.SendMessage(InpChannelName,msg);
         if(res!=0)
            Print("Error: ",GetErrorDescription(res));
        }

      //--- Send signal SELL
      if(macd[1]<signal[1] && 
         macd[0]>=signal[0])
        {
         string msg=StringFormat("Name: MACD Signal\nSymbol: %s\nTimeframe: %s\nType: Sell\nPrice: %s\nTime: %s",
                                 _Symbol,
                                 StringSubstr(EnumToString(_Period),7),
                                 DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits),
                                 TimeToString(time[0]));
         int res=bot.SendMessage(InpChannelName,msg);
         if(res!=0)
            Print("Error: ",GetErrorDescription(res));
        }
     }
  }
//+------------------------------------------------------------------+

Como resultado, recibirá mensajes como los mostrados en la figura 9.

Señales del indicador MACD

Fig.9. Señales del indicador MACD.

Conclusión

Quien desee conectar su análisis con la base de Yandex.AppMetrika para su bot, puede usar el recurso Botan. Con este servicio usted envía los mensajes recibidos de los usuarios y puede solicitar índices como la segmentación, el seguimiento, el análisis de cohortes, etc. Además, no hace falta abandonar el ecosistema de messenger, puesto que la estadística se enviará con un bot especial en forma de gráficos. En la página podrá acceder a un informe más detallado.

Espero que este artículo haya despertado su interés por el uso de Telegram en el trading. Mi objetivo no ha sido extenderme en detalles, dado que estos están muy bien explicados en la documentación de Bot API. Los códigos adjuntos al artículo han sido adaptados a ambas plataformas: tanto para MetaTrader 4, como para MetaTrader 5.