如何采用 MQL5 创建用于 Telegram 的 bots·进阶篇
📘

如何采用 MQL5 创建用于 Telegram 的 bots·进阶篇

第 2/3 篇

用 MT5 定时器轮询 Telegram 消息

想在 MT5 里接一个 Telegram 机器人,核心不是复杂框架,而是用 EventSetTimer 起一个周期任务,把收消息和处理逻辑挂上去。下面这段把令牌校验、3 秒轮询、未读消息遍历做成了一个最小可跑骨架。 初始化阶段先塞入 input 里的令牌字符串,马上调 GetMe 验证令牌有效性;若返回非 0,后续定时器里会直接把错误描述打到图表上并退出,避免带着废令牌空跑。EventSetTimer(3) 设定每 3 秒触发一次 OnTimer,首次还手动调了一次 OnTimer 做即时拉取。 OnTimer 里先判 getme_result,非零就 Comment 报错并返回;正常则显示 bot 名称,调 GetUpdates 拉新消息,再走 ProcessMessages 遍历聊天节点。ProcessMessages 中对每条未处理消息看文本:命中 /start 回「你好, 世界!我是 bot。」,命中 /help 回命令列表,靠 chat.m_new_one.done 置 true 防重复处理。 实盘接这类 EA 前,先在策略测试器外挂真实令牌跑几分钟,看 Comment 能否打出 Bot 名称;外汇与贵金属波动剧烈,机器人仅作信号中转,任何自动下单都需自担高风险。

MQL5 / C++
for(class="type">int i=class="num">0; i<m_chats.Total(); i++)
  {
   CCustomChat *chat=m_chats.GetNodeAtIndex(i);
   class=class="str">"cmt">//--- 如果消息尚未处理
   if(!chat.m_new_one.done)
     {
      chat.m_new_one.done=true;
      class="type">class="kw">string text=chat.m_new_one.message_text;
      class=class="str">"cmt">//--- 开始
      if(text=="/start")
         SendMessage(chat.m_id,"你好, 世界!我是 bot。\xF680");
      class=class="str">"cmt">//--- 帮助
      if(text=="/help")
         SendMessage(chat.m_id,"我的命令列表: \n/start-开始和我聊天 \n/help-获取帮助");
     }
   }
  };
class=class="str">"cmt">//---
input class="type">class="kw">string InpToken="class="num">177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";class=class="str">"cmt">//令牌
class=class="str">"cmt">//---
CMyBot bot;
class="type">int getme_result;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  OnInit                                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 设置令牌
   bot.Token(InpToken);
class=class="str">"cmt">//--- 检查令牌
   getme_result=bot.GetMe();
class=class="str">"cmt">//--- 运行定时器
   EventSetTimer(class="num">3);
   OnTimer();
class=class="str">"cmt">//--- 结束
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  OnDeinit                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  OnTimer                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- 显示错误消息并退出
   if(getme_result!=class="num">0)
     {
      Comment("错误: ",GetErrorDescription(getme_result));
      class="kw">return;
     }
class=class="str">"cmt">//--- 显示 bot 名称
   Comment("Bot 名称: ",bot.Name());
class=class="str">"cmt">//--- 读取消息
   bot.GetUpdates();
class=class="str">"cmt">//--- 处理消息
   bot.ProcessMessages();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 在 MT5 里给 Telegram bot 挂交互键盘

把 EA 和聊天 bot 打通时,最顺手的做法不是让程序自说自话,而是用「键盘」把可选项直接推到对话界面。每次发消息都能带一组预置按键,用户点一下就回传对应指令,比手动敲文本省事,也少出错。 这个类暴露三个键盘相关函数:ReplyKeyboardMarkup 负责造键盘,参数 keyboard 是按键集合字符串,resize 允许改尺寸,one_time 设 true 时按完即焚;ReplyKeyboardHide 用来藏键盘;ForceReply 发一条不显示键盘、但要求对方以文本回复的面板。三者返回值都是要塞进 SendMessage 的 reply_markup 里的 JSON 字符串。 键盘自己不会跳出来,必须搭着 SendMessage 走。SendMessage(chat_id, text, reply_markup) 里 reply_markup 可选,不传就是纯文本。相比 MQL5 原生的 SendNotification,它大概能每秒发一条,支持 HTML,还能带表情——这点很实用。 Telegram 表情主区间在 1F300–1F700,超出 MQL5 双字节串范围;砍掉高位留低两位落到 F300–F700(属 Unicode 保留区 E000–F8FF),就能直接发。比如 U+1F642 写成 "\xF642" 拼进文本即可。下面这段代码演示了三按键键盘的构建与发送。 键盘不仅能做菜单,用表情模拟 RadioButton / CheckBox 也行:从三个里选一、或开关某些项,只改类内部、EA 主体不动。层叠子菜单同理,上限看你打算实现多少。 除了私聊,SendMessage 还有个按 channel_name(如 @name)发的重载,文本同样支持 HTML,适合把信号广播进频道。外汇与贵金属波动剧烈、杠杆风险高,这类推送仅作辅助提醒,不构成任何交易建议。

MQL5 / C++
class="type">class="kw">string ReplyKeyboardMarkup(const class="type">class="kw">string keyboard,
                                 const class="type">bool resize,
                                 const class="type">bool one_time)
class="type">class="kw">string ReplyKeyboardHide()
class="type">class="kw">string ForceReply()
class="type">int SendMessage(const class="type">long chat_id,
                const class="type">class="kw">string text,
                const class="type">class="kw">string reply_markup=NULL)
class="type">class="kw">string text="Have a nice day.\xF642";class=class="str">"cmt">//message text with Emoji U+1F642
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                Telegram_SendMessage.mq5 |
class=class="str">"cmt">//|            版权所有 class="num">2014, MetaQuotes 软件公司|
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2014, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict
class="macro">#include <Telegram.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  CMyBot                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CMyBot: class="kw">public CCustomBot
  {
class="kw">private:
  class="type">class="kw">string            m_button[class="num">3];
class="kw">public:
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">void CMyBot::CMyBot(class="type">void)
    {
     m_button[class="num">0]="按钮 #class="num">1";
     m_button[class="num">1]="按钮 #class="num">2";
     m_button[class="num">2]="按钮 #class="num">3";
    }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">class="kw">string GetKeyboard()

「正文」

&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span>(<span class="string">"[[\""</span>+m_button[<span class="number">0</span>]+<span class="string">"\"],[\""</span>+m_button[<span class="number">1</span>]+<span class="string">"\"],[\""</span>+m_button[<span class="number">2</span>]+<span class="string">"\"]]"</span>); &nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp; <span class="comment">//+------------------------------------------------------------------+</span> &nbsp;&nbsp; <span class="keyword">void</span> ProcessMessages(<span class="keyword">void</span>) &nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;m_chats.Total();i++) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCustomChat *chat=m_chats.GetNodeAtIndex(i); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(!chat.m_new_one.done) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;chat.m_new_one.done=<span class="macro">true</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">string</span> text=chat.m_new_one.message_text; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- start 或 help 命令</span> &nbsp;&n

用 Telegram 机器人回传 MT5 状态与交互

在 MT5 里接 Telegram 推送,不只是发个报价截图。上面这段把错误显示、bot 名称打印、消息拉取与处理串成一段标准流程:若 getme_result 不为 0,就在图表上 Comment 出错误描述并直接 return,否则继续显示 bot 名称、调用 GetUpdates 和 ProcessMessages。 真正能做交互的是 CMyBot 这个类。它继承了 CCustomBot,内部用 m_radio_button[3] 存三个单选按钮文案,m_radio_index 记录当前选中项(初始化为 0),m_lock_state 和 m_mute_state 分别管锁态和静音态,构造函数里把静音默认设成 true、锁默认 false。 宏定义那块值得注意:MUTE_CODE 是 \xF515、UNMUTE_CODE 是 \xF514,LOCK_CODE 和 UNLOCK_CODE 分别对应 \xF512 / \xF513,这些十六进制是 Telegram 行内按钮的 Unicode 图标码。你在 MT5 里 include <Telegram.mqh> 后,改这些宏和 m_radio_button 字符串,就能让手机端收到带「锁定 / 解锁 / 静音」图标的自定义键盘。外汇和贵金属行情波动剧烈,这类远程控制只建议用于模拟盘或非自动下单的提醒,实盘遥控存在滑点和误触高风险。

MQL5 / C++
class=class="str">"cmt">//--- 显示错误消息并退出
   if(getme_result!=class="num">0)
     {
       Comment("错误: ",GetErrorDescription(getme_result));
       class="kw">return;
     }
class=class="str">"cmt">//--- 显示 bot 名称
   Comment("Bot 名称: ",bot.Name());
class=class="str">"cmt">//--- 读取消息
   bot.GetUpdates();
class=class="str">"cmt">//--- 处理消息
   bot.ProcessMessages();
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//class="macro">#class="kw">property copyright "版权所有 class="num">2014, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict
class="macro">#include <Telegram.mqh>
class="macro">#define MUTE_TEXT      "静音"
class="macro">#define UNMUTE_TEXT    "非静音"
class="macro">#define LOCK_TEXT      "锁定"
class="macro">#define UNLOCK_TEXT    "解锁"
class="macro">#define RADIO_SELECT   "\xF518"
class="macro">#define RADIO_EMPTY    "\x26AA"
class="macro">#define MUTE_CODE      "\xF515"
class="macro">#define UNMUTE_CODE    "\xF514"
class="macro">#define LOCK_CODE      "\xF512"
class="macro">#define UNLOCK_CODE    "\xF513"
class CMyBot: class="kw">public CCustomBot
  {
class="kw">private:
   class="type">class="kw">string            m_radio_button[class="num">3];
   class="type">int               m_radio_index;
   class="type">bool              m_lock_state;
   class="type">bool              m_mute_state;
class="kw">public:
   class="type">void CMyBot::CMyBot(class="type">void)
     {
      m_radio_button[class="num">0]="单选按钮 #class="num">1";
      m_radio_button[class="num">1]="单选按钮 #class="num">2";
      m_radio_button[class="num">2]="单选按钮 #class="num">3";
      m_radio_index=class="num">0;
      m_lock_state=class="kw">false;
      m_mute_state=true;
     }
   class="type">class="kw">string GetKeyboard()
     {

◍ 键盘状态拼装与消息响应

这段逻辑负责把当前单选、静音、锁定三类控件状态拼成 Telegram 自定义键盘的 JSON 串。radio_code 数组长度固定为 3,初始化全是 RADIO_EMPTY,只有当 m_radio_index 落在 0~2 闭区间时才把对应下标换成 RADIO_SELECT,越界则保持全空。 静音与锁定走的是同样的二态切换:m_mute_state 为真就换成 MUTE_TEXT/MUTE_CODE,否则用 UNMUTE 那组;m_lock_state 同理决定 LOCK 或 UNLOCK。拼串用 StringFormat,格式固定为 5 个按钮组,前 3 个是单选,后 2 个是锁与静音。 ProcessMessages 里用 m_chats.Total() 遍历会话,靠 m_new_one.done 标记避免重复处理。收到 /start 或 /help 就推一句“点击按钮”并带上 GetKeyboard 生成的键盘;遍历 m_radio_button 数组,若文本等于“RADIO_EMPTY 按钮名”就记下 m_radio_index 并回发该按钮名。开 MT5 把这段塞进 EA,发 /start 给 bot 就能看到三选一加锁/静音的键盘长相。

MQL5 / C++
class="type">class="kw">string radio_code[class="num">3]={RADIO_EMPTY,RADIO_EMPTY,RADIO_EMPTY};
if(m_radio_index>=class="num">0 && m_radio_index<=class="num">2)
   radio_code[m_radio_index]=RADIO_SELECT;
class="type">class="kw">string mute_text=UNMUTE_TEXT;
class="type">class="kw">string mute_code=UNMUTE_CODE;
if(m_mute_state)
  {
   mute_text=MUTE_TEXT;
   mute_code=MUTE_CODE;
  }
class="type">class="kw">string lock_text=UNLOCK_TEXT;
class="type">class="kw">string lock_code=UNLOCK_CODE;
if(m_lock_state)
  {
   lock_text=LOCK_TEXT;
   lock_code=LOCK_CODE;
  }
class=class="str">"cmt">//Print(m_lock.GetKey());
class="kw">return(StringFormat("[["%s %s"],["%s %s"],["%s %s"],["%s %s","%s %s"]]",
     radio_code[class="num">0],m_radio_button[class="num">0],
     radio_code[class="num">1],m_radio_button[class="num">1],
     radio_code[class="num">2],m_radio_button[class="num">2],
     lock_code,lock_text,
     mute_code,mute_text));
}
class="type">void ProcessMessages(class="type">void)
  {
   for(class="type">int i=class="num">0;i<m_chats.Total();i++)
     {
      CCustomChat *chat=m_chats.GetNodeAtIndex(i);
      if(!chat.m_new_one.done)
        {
         chat.m_new_one.done=true;
         class="type">class="kw">string text=chat.m_new_one.message_text;
         if(text=="/start" || text=="/help")
           {
            bot.SendMessage(chat.m_id,"点击按钮",bot.ReplyKeyboardMarkup(GetKeyboard(),class="kw">false,class="kw">false));
           }
         class="type">int total=ArraySize(m_radio_button);
         for(class="type">int k=class="num">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(),class="kw">false,class="kw">false));
              }
           }
         class=class="str">"cmt">//--- 解锁

「用指令串切换锁单与静音状态」

Telegram bot 接收文本指令后,靠拼接码字判断该翻哪个开关。LOCK_CODE 加空格加 LOCK_TEXT 命中就解琐并把键盘发回去;UNLOCK_CODE 加 UNLOCK_TEXT 命中则反向锁住。 静音逻辑同构:MUTE_CODE 配 MUTE_TEXT 关掉静音,UNMUTE_CODE 配 UNMUTE_TEXT 打开静音,每次都回发 ReplyKeyboardMarkup 刷新按钮。四个 if 彼此独立,谁先匹配谁改对应的 m_lock_state / m_mute_state。 SendMessage 这个重载只接频道名和文本,说明指令回执走的是轻量通道,不夹图表。复制这段代码进 EA 的 OnMessage 分支,把那几个 *_CODE / *_TEXT 宏换成你自己的暗语,MT5 跑起来发一句就能验证状态翻转。外汇与贵金属波动剧烈,自动化锁单只是降低误操作概率,不消除爆仓风险。

MQL5 / C++
if(text==LOCK_CODE+" "+LOCK_TEXT)
  {
   m_lock_state=class="kw">false;
   bot.SendMessage(chat.m_id,UNLOCK_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),class="kw">false,class="kw">false));
  }
class=class="str">"cmt">//--- 锁定
if(text==UNLOCK_CODE+" "+UNLOCK_TEXT)
  {
   m_lock_state=true;
   bot.SendMessage(chat.m_id,LOCK_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),class="kw">false,class="kw">false));
  }
class=class="str">"cmt">//--- 非静音
if(text==MUTE_CODE+" "+MUTE_TEXT)
  {
   m_mute_state=class="kw">false;
   bot.SendMessage(chat.m_id,UNMUTE_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),class="kw">false,class="kw">false));
  }
class=class="str">"cmt">//--- 静音
if(text==UNMUTE_CODE+" "+UNMUTE_TEXT)
  {
   m_mute_state=true;
   bot.SendMessage(chat.m_id,MUTE_TEXT,bot.ReplyKeyboardMarkup(GetKeyboard(),class="kw">false,class="kw">false));
  }
   }
  }
 }
};
class="type">int SendMessage(const class="type">class="kw">string channel_name,
                const class="type">class="kw">string text)

常见问题

在 EA 初始化时通过 bot API 发送带 inline_keyboard 的消息即可,把按钮回调数据写成指令串,后续点击直接回传指令。
用定时器轮询账户与报错标志,拼装状态文本后调用 bot 发送接口,建议加静音开关避免刷屏。
可以,小布能汇总 bot 回传的锁单、静音与报错状态并做异常提醒,你只需要在关键事件时介入。
多是回调指令串格式不对或 EA 端消息响应没匹配到前缀,检查拼装逻辑和响应分支是否覆盖所有按钮。
轮询间隔建议 1~3 秒,过短会占资源,过长则指令延迟;用事件标记减少空轮询可降负载。