English Русский Español Deutsch 日本語 Português
如何采用 MQL5 创建用于 Telegram 的 bots

如何采用 MQL5 创建用于 Telegram 的 bots

MetaTrader 5积分 | 30 六月 2016, 15:04
4 907 0
Andriy Voitenko
Andriy Voitenko

介绍

2016 年 4 月 12 日, 在旧金山举办的 F8 会议期间, Facebook 宣布将 Bot API 集成到即时通信软件里。同日发布了 Telegram Bot 平台的重大更新。版本 2.0 有令人惊喜的功能。看样子, 曾经在 ICQ 时代流行的 bots 正在经历卷土重来。开发 bots 进入了新阶段, 完善的功能, 开放编程的接口, 以及多媒体支持。基本上, 当您查找、查看或购买物品时, 它们拥有令人难以割舍的所有条件。

本文是采用 MQL5 逐步创建用于 Telegram 的 bots 手册。那么, 什么是 "bot"?bot ("robot" 的缩写) 是 Telegram 内的特殊帐户, 用于交换消息。Bots 的操作位于您的客户端, 并且通过部分 Bot API 的特殊命令与 Telegram 服务器进行交互。在我们着手创建 bot 之前, 请下载 Telegram 并登录。注册会与电话号码连接, 但您也可以通过 @username 搜索。现在是时候来认识所有 bots 的文件夹。

注册新的 bot

一个特殊的 bot @BotFather 负责注册并设置 bots。我们将通过搜索引擎来搜索它。将它添加到联系人列表后, 我们将使用 /start 命令开始与之通信。作为响应, 它会发给您所有可用命令的列表, 如图例. 1 所示。


命令 @BotFather 的列表.

图例.1. 命令 @BotFather 的列表

利用 /newbot 命令我们开始注册新的 bot。我们需要紧跟两个名字。第一个是 bot 的名字, 可用您的母语设定。第二个是拉丁语的用户名, 结尾是 "bot" 后缀。作为结果, 我们得到一个令牌 – bot 通过 API 进行操作的识别符。注册的例子如图例. 2 所示。


注册新的 bot

图例.2. 新 bot 注册

若您愿意, 一些参数可以省略。我建议保持设置用于内联模式。否则, 我们的 bots 将不能与之工作。我建议仅进行一些装饰性设置:
  • /setcommands – 支持的设置命令列表。当在聊天窗口键入 "/" 符号时, 此列表将会作为工具提示显示给用户。
  • /setuserpic – 设置资料图片。若没有它的话, bot 的表现力就不够。
  • /setdescription – 当机器人添加到即时通信软件时的致辞。通常情况下, 用几句话来描述 bot 的目的。

于是, 新 bot 注册完成。现在让我们来讨论它可以使用的模式。

bots 的操作模式

Telegram 有三种方案用于 bots 和用户之间的交互。第一种 - 私聊。每位用户利用 bot 单独与他人通信, 如图例. 3 所示, 通过产生请求和接收响应。


bot 和私聊

图例.3.  bot 和私聊。


用户发送消息至 bot。它们保存在服务器上不超过 24 小时, 然后即被删除。bot 有足够的时间发送这些消息并响应它们。这是我们的 bots 将要操作的主要模式。

第二种模式涉及群聊。在此情况下, 发自群内任意成员的消息可以被全群所见 (图例. 4)。

bot 和群聊

图例.4. 群聊模式的 bot。

利用相关 bots, 您可以使用 /setjoingroups 命令让它们加入群。如果 bot 已经被加入群, 则使用 /setprivacy 命令您可以设置选项, 即可接收所有消息, 或开始字符为 "/" 标记的那些。说实话, 我只设想到一个这种模式下 bot 的作用 - 用于后续分析的消息统计数据。

第三种模式重点在于信道操作。Telegram 信道是为大量听众传输消息的帐户, 它可支持无限数量的订阅者。信道的重要特征是它不支持用户留言, 就像新闻提要 (单向连接)。只有信道的管理员才能创建消息 (图例. 5)。


Bot 作为信道管理员

图例.5. Bot 作为通道管理员。

Bots 也可以添加到管理员列表。这可令信道作为理想的提供交易信号的工具。稍后我们将编写一个简单的 bot 来发布标准 MACD 指标的信号。新的公共信道可以通过即时通信软件的 "新信道" 菜单创建。不要忘记将您的 bot 添加到信道的管理员列表。它是通过信道的属性窗口来实现。所有的准备工作已经完成, 让我们继续进行编程。


处理消息流

当撰写本文时, 我有一个目标就是创建一个类来承担例行消息处理, 并可以集中精力于 bot 的逻辑。作为结果, 类 CCustomBot 实现了所写操作的最小功能。

使用 WebRequst 函数 POST 请求可与服务器进行通信。每个命令有其自己的 URL:

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

此处 TOKEN 是注册 bot 的令牌; METHOD_NAME — 支持的方法列表。

自服务器的响应抵达时是 JSON 格式, 所以需要一款优秀的 JSON 解析器。我应用了一款本地化的解析器 JSON 序列化和逆序列化。我十分感谢 Alexey (sergeev) 为此所做的贡献。此外, 还应用了显示参数的面板。类 CComment 取自代码库, 它十分适合这个任务。类的公共方法名称借助 API 的文档来达到普适性。在类中我们已管理的方法列表如下:

为了理解如何使用这些方法, 我们将进一步深入编程。


GetMe

由于以上所有函数每次请求期间要发送令牌, 则 GetMe 函数检查其可信度。最好是在 EA 开始时执行检查, 并且在出故障的情况下通知用户。

int GetMe()
 返回值  错误代码

如果成功, GetMe 返回 0, 并且您可以通过 Name() 方法找出 bot 的用户名。此名字并非用于操作。不过, 出于提示目的, 它将显示在面板上。像是 telegram.me/<botname> 的地址可以使用即时通信软件的网页版, 且作为您的 bot 的广告链接。EA 在 OnInit 当中检查令牌的代码如下所示:

//+------------------------------------------------------------------+
//|                                               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";//令牌

CCustomBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 设置令牌
   bot.Token(InpToken);
//--- 检查令牌
   getme_result=bot.GetMe();
//--- 返回定时器
   EventSetTimer(3);
   OnTimer();
//--- 结束
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- 显示错误消息并退出
   if(getme_result!=0)
     {
      Comment("错误: ",GetErrorDescription(getme_result));
      return;
     }
//--- 显示机器人名字
   Comment("机器人名字: ",bot.Name());

//---{ 在此插入您的代码 }
  }
//+------------------------------------------------------------------+

GetUpdates

主函数 GetUpdates 从服务器上读取一个消息数组。它需要通过定时器来调用。定时器的更新周期不应低于一秒钟, 以避免服务器过载。

int GetUpdate()
 返回值  错误代码

让我们来看看这个函数的内部。当它被调用时, 将会执行读取并解析用户接收的所有未读消息。以下提供这些消息之一的例子:

{ 
   "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"
         }
      }
   ]
}

一名用户以 avaticks 为用户名发送 /start 命令至 bot。要点是保存这种信息, 并在以后作出回应。聊天号码 chat[id] 是独有的标识符。同一用户使用各种不同设备与 bot 通信会有不同的聊天标识符。此参数适合作为构建聊天列表的独有关键字。当操作时, bot 将会累加聊天数组, 并依次更新最后收到的消息。如果我们已经做出了回应, 则此消息已被处理, 我们可以为它设置结束标志。聊天类型也是已知的。它可以是私聊, 或群聊。

为了编写您自己的机器人, 只需要简单地继承 CCustomBot 类, 并重写类中的 ProcessMessage 虚函数, 实现操作逻辑。一个成熟的 bot, 依据 Telegram 文档, 需要知道如何响应两个命令: "/start" 和 "/help"。让我们编写第一个 bot 来响应它们。

//+------------------------------------------------------------------+
//|                                          Telegram_GetUpdates.mq5 |
//|                                 版权所有 2014, MetaQuotes 软件公司|
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "版权所有 2014, MetaQuotes 软件公司"
#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(!chat.m_new_one.done)
           {
            chat.m_new_one.done=true;
            string text=chat.m_new_one.message_text;

            //--- 开始
            if(text=="/start")
               SendMessage(chat.m_id,"你好, 世界!我是 bot。\xF680");

            //--- 帮助
            if(text=="/help")
               SendMessage(chat.m_id,"我的命令列表: \n/start-开始和我聊天 \n/help-获取帮助");
           }
        }
     }
  };

//---
input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//令牌
//---
CMyBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 设置令牌
   bot.Token(InpToken);
//--- 检查令牌
   getme_result=bot.GetMe();
//--- 运行定时器
   EventSetTimer(3);
   OnTimer();
//--- 结束
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- 显示错误消息并退出
   if(getme_result!=0)
     {
      Comment("错误: ",GetErrorDescription(getme_result));
      return;
     }
//--- 显示 bot 名称
   Comment("Bot 名称: ",bot.Name());
//--- 读取消息
   bot.GetUpdates();
//--- 处理消息
   bot.ProcessMessages();
  }
//+------------------------------------------------------------------+

获得的结果示于图例 6。


拥有最小命令集的 bot

图例.6. 拥有最小命令集的 bot。

利用键盘操作

为了便于用户与 bot 之间的交互通信, 开发者产生的想法是使用 "键盘"。当每次聊天发送消息时, 可以显示一个具有预选按键集合的 "键盘"。当按下按键y, 用户发送一个其上指示的消息。此种方式, 令 bot 和一名用户之间的交互显著简化。

这个类有三个函数用于键盘操作。第一个函数创建键盘的对象。

string ReplyKeyboardMarkup(const string keyboard,
                           const bool resize,
                           const bool one_time)
 keyboard
 本地按键集合的字符串
 resize  改变键盘大小的许可
 one_time  仅显示一次键盘在按键之后键盘消失。
 返回值 当 SendMessage 发送消息时, 字符串 (JSON 对象) 需要作为 reply_markup 参数传递。

第二个函数隐藏键盘。

string ReplyKeyboardHide()
 返回值 当 SendMessage 发送消息时, 字符串 (JSON 对象) 需要作为 reply_markup 参数传递。

第三个函数可以发送一条小面板, 其类型表示 bot 期望能从您这里得到文本表单的应答 (不显示键盘)。

string ForceReply()
 返回值 当 SendMessage 发送消息时, 字符串 (JSON 对象) 需要作为 reply_markup 参数传递。

现在我们来分析这些函数如何使用。

SendMessage

键盘不能显示或自行隐身。这个动作伴随一条消息发送。函数 SendMessage 发送聊天消息如下所示:

int SendMessage(const long chat_id,
                const string text,
                const string reply_markup=NULL)
 chat_id
 聊天号码
 text  消息文本
 reply markup  键盘 (JSON 对象)
 返回值  错误代码

在此情况下键盘是选项。我们可以从我们的 MQL 程序里发送简单的文本消息。我的看法是, 此函数比原生的 SendNotification 更有趣。首先, 我们可以更频繁地发送消息 (大概每秒钟一条)。第二, 它支持 HTML 格式。此外, 发送表情的能力是一桩红利。

Тelegram 所支持的表情符号可 在此 查看。如您所见, 主要的表情代码范围在 1F300 – 1F700。它们的位数已经超出了 MQL5 可接受的双字节字符串范围。如果您删除高位仅保留两位, 则所得范围 (F300 – F700) 落入 (E000— F8FF) 区域, 而此范围在 Unicode 表里当前保留。如此一来, 没有什么可以阻止我们使用低两位发送表情符号。带有经典 U+1F642 代码表情的消息看上去如下:

string text="Have a nice day.\xF642";//message text with Emoji U+1F642

这也是公平的, 事实上文本才是关键。没有什么可以阻止我们从按键上使用表情符号。让我们来编写事件处理器显示三个按键的例程。

//+------------------------------------------------------------------+
//|                                         Telegram_SendMessage.mq5 |
//|                                 版权所有 2014, MetaQuotes 软件公司|
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "版权所有 2014, MetaQuotes 软件公司"
#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]="按钮 #1";
      m_button[1]="按钮 #2";
      m_button[2]="按钮 #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 或 help 命令
            if(text=="/start" || text=="/help")
               bot.SendMessage(chat.m_id,"点击按钮",bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));

            //--- 点击事件
            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";//令牌

CMyBot bot;
int getme_result;
//+------------------------------------------------------------------+
//|   OnInit                                                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 设置令牌
   bot.Token(InpToken);
//--- 检查令牌
   getme_result=bot.GetMe();
//--- 运行定时器
   EventSetTimer(1);
   OnTimer();
//--- 结束
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|   OnDeinit                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//|   OnTimer                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- 显示错误消息并退出
   if(getme_result!=0)
     {
      Comment("错误: ",GetErrorDescription(getme_result));
      return;
     }
//--- 显示 bot 名称
   Comment("Bot 名称: ",bot.Name());
//--- 读取消息
   bot.GetUpdates();
//--- 处理消息
   bot.ProcessMessages();
  }
//+------------------------------------------------------------------+

结果就是我们得到一条带键盘的消息, 如图例. 7。

带键盘的消息

图例.7. 带键盘的消息。

现在, 我们将尝试实现 RadioButton 和 CheckBox 控件的模拟。例如, 我们不得不从三个选项中选出其一, 并启用或禁用确定的选项。变化仅会影响我们的类, 所以从前例中保留的 EA 代码将保持不变。
//+------------------------------------------------------------------+
//|                                         Telegram_SendMessage.mq5 |
//|                                 版权所有 2014, MetaQuotes 软件公司|
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "版权所有 2014, MetaQuotes 软件公司"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <Telegram.mqh>

#define MUTE_TEXT       "静音"
#define UNMUTE_TEXT     "非静音"

#define LOCK_TEXT       "锁定"
#define UNLOCK_TEXT     "解锁"

#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]="单选按钮 #1";
      m_radio_button[1]="单选按钮 #2";
      m_radio_button[2]="单选按钮 #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;

            //--- 开始
            if(text=="/start" || text=="/help")
              {
               bot.SendMessage(chat.m_id,"点击按钮",bot.ReplyKeyboardMarkup(GetKeyboard(),false,false));
              }

            //--- 点击单选按钮
            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));
                 }
              }

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

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

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

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

结果是我们得到以下窗口 (图例 8)。

图例.8. 单选按钮和多选按钮控件

我们可以从这里看出, 在此使用的表情符号提供一个更好的视觉设置。除了这些控件, 我们将能够轻松实现每个子菜单导航的层叠菜单。一切都取决于您将要提出并决定实现的功能。

当我们决定在信道里发布消息的情况下, 此时有第二个选项 - SendMessage。

int SendMessage(const string channel_name,
                const string text)
 channel_name
 信道名称如 @name
 text  消息文本。支持 HTML 标签。
 返回值  错误代码

此函数的结果显示如下图例 9。

多媒体操作

Bots 可以交换照片, 声频和视频文件, 还有语音消息, 贴纸和位置坐标。在撰写本文之时, 具有交换联系人数据并邀请他人参加会议功能的 Bot API 2.0 已经发布。从已提供的完整清单中, 只有交换照片选项与我们相关。

SendPhoto

该类已经实现了两种发送照片的应用方式。

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  聊天号码
 local_path  在 <数据文件夹>\MQL5\Files 里的本地路径
 photo_id  上载到服务器的照片标识符
 caption  照片下面的签名文本
 common_flag  文件在所有客户终端的共享文件夹 \Terminal\Common\Files 中的位置标记
 timeout  以毫秒为单位的操作超时

发送照片的例程代码:

CCustomBot bot;

string token = "208375865:AAFnuOjlZ3Wsdan6PAjeqqUtBybe0Di1or8";

bot.Token(token);

string photo_id;
int result=bot.SendPhoto(198289825,"EURUSD1.gif",photo_id,"屏幕截图");
if(result==0)
   Print("照片 ID: ",photo_id);
else
   Print("错误: ",GetErrorDescription(result));

我相信您会遇到这种情况: 需要将一张照片发送给多个用户, 或将同一照片发送多次。在此情况下, 更合理的情形是载照片只上一次, 当重发照片时, 利用 SendPhoto 函数的第二种选项, 并应用 photo_id 识别符:

int SendPhoto(const long chat_id,
              const string photo_id,
              const string caption=NULL)
 chat_id  聊天号码
 photo_id  上载到服务器的照片标识符
 caption  照片下面的签名文本

SendChartAction

想象一下, 您处理一条用户的回应, 并几乎准备好提供结果。由于创建回应需要花费几秒钟, 此时应当客气地通知用户您已经在处理。这就是使用事件的目的。例如, 在将产生的图表截图发送给用户期间, 您可以提示 "发送照片" 事件。这是通过 SendChatAction 实现的。

int SendChatAction(const long chat_id,
                   const ENUM_CHAT_ACTION actiona)
 chat_id  聊天号码
 action  事件标识符
在三个展示的 bots 里实现的所有上述功能, 我们会稍后讨论。

Bots 例程

第一个 bot 是 Telegram_Bot_EA, 可以获取关于账户余额、报价和图表截图的信息。在这部视频里演示了它是如何运作的。


第二个 bot 是 Telegram_Search_EA, 发送搜索结果至 MQL5.com。您也许会好奇地观看下面的视频来看看它是如何实际工作的。


第三个 bot 是 Telegram_Signal_EA, 在信道里发布来自 MACD 标准指标的信号。我认为很容易就能将 MACD 改为您喜爱的指标, 并用此代码为您的目的服务。

//+------------------------------------------------------------------+
//|                                        Telegram_Signal_EA_v1.mq4 |
//|                                 版权所有 2014, MetaQuotes 软件公司|
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "版权所有 2014, MetaQuotes 软件公司"
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//|   包含                                                           |
//+------------------------------------------------------------------+
#include <Telegram.mqh>

//--- 输入参数
input string InpChannelName="@forexsignalchannel";//信道名称
input string InpToken="177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";//令牌

//--- 全局变量
CCustomBot bot;
int macd_handle;
datetime time_signal=0;
//+------------------------------------------------------------------+
//| 智能交易程序初始化函数                                             |
//+------------------------------------------------------------------+
int OnInit()
  {
   time_signal=0;

//--- 设置令牌
   bot.Token(InpToken);

//--- 获得指标句柄
   macd_handle=iMACD(NULL,0,12,26,9,PRICE_CLOSE);
   if(macd_handle==INVALID_HANDLE)
      return(INIT_FAILED);

//--- 结束
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 智能交易程序分时函数                                               |
//+------------------------------------------------------------------+
void OnTick()
  {

//--- 获得时间
   datetime time[1];
   if(CopyTime(NULL,0,0,1,time)!=1)
      return;

//--- 在每根柱线上检查信号
   if(time_signal!=time[0])
     {
      //--- 第一次计算
      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];

      //--- 发送买入信号
      if(macd[1]>signal[1] && 
         macd[0]<=signal[0])
        {
         string msg=StringFormat("名称: MACD 信号\n品种: %s\n时间帧: %s\n类型: 买入\n价格: %s\n时间: %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("错误: ",GetErrorDescription(res));
        }

      //--- 发送卖出信号
      if(macd[1]<signal[1] && 
         macd[0]>=signal[0])
        {
         string msg=StringFormat("名称: MACD 信号\n品种: %s\n时间帧: %s\n类型: 卖出\n价格: %s\n时间: %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("错误: ",GetErrorDescription(res));
        }
     }
  }
//+------------------------------------------------------------------+

结果就是您将收到一条消息, 如图例. 9。

MACD 指标信号

图例.9. MACD 指标信号。

结论

那些希望将它们的 bot 连接到 Yandex.AppMetrika 基地进行分析的用户, 也许可以使用 Botan 源。该服务的思路是: 用户接收的消息发送给它们, 并请求诸如分段、跟踪、队列分析等指标。没有必要退出即时通信软件, 因为统计数据将通过挂载到图表上的特殊 bot 发送, 更详细的报告将在网站上提供。

我希望这篇文章能启发您在交易中应用 Telegram。我的目标并非要覆盖所有的细节, 因为它们已经在 Bot API 的文档里提供。本文所附的代码适用于这两个交易平台 — MetaTrader 4 和 MetaTrader 5。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/2355

附加的文件 |
telegram.zip (31.06 KB)
图形界面 VI: 滑动条与双重滑动条控件(第二章) 图形界面 VI: 滑动条与双重滑动条控件(第二章)
在前一篇文章中,我们已经使用四个常用图形界面控件加强我们的开发库:复选框,编辑框,带有复选框的编辑框,以及复选组合框。第六部分的第二章将致力于滑动条与双重滑动条控件的开发。
图形界面 VI: 复选框控件,编辑框控件以及它们的组合类型 (第一章) 图形界面 VI: 复选框控件,编辑框控件以及它们的组合类型 (第一章)
本文是在MetaTrader终端图形界面开发库系列中第六部分的开端,在第一章中,我们将讨论复选框控件,编辑框控件以及它们的组合类型。
通用智能交易系统:自定义追踪止损(第六章) 通用智能交易系统:自定义追踪止损(第六章)
通用智能交易系统的第六章介绍追踪止损功能的用法。本文将指导你如何使用通用规则创建一个自己的追踪止损模型,以及如何将其添加到交易引擎中来实现自动管理持仓头寸的功能。
图形界面 V: 组合框控件 (第三章) 图形界面 V: 组合框控件 (第三章)
在本系列第五部分的前两章中,我们开发了用于创建滚动条和列表视图的类,在本章中,我们将讨论创建组合框(combobox)控件的类,这也是一个组合控件,包含了第五部分前面章节中讨论的一些元件。