如何采用 MQL5 创建用于 Telegram 的 bots·进阶篇
用 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 名称;外汇与贵金属波动剧烈,机器人仅作信号中转,任何自动下单都需自担高风险。
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,适合把信号广播进频道。外汇与贵金属波动剧烈、杠杆风险高,这类推送仅作辅助提醒,不构成任何交易建议。
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()
「正文」
{ <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>); } <span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">void</span> ProcessMessages(<span class="keyword">void</span>) { <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i<m_chats.Total();i++) { CCustomChat *chat=m_chats.GetNodeAtIndex(i); <span class="keyword">if</span>(!chat.m_new_one.done) { chat.m_new_one.done=<span class="macro">true</span>; <span class="keyword">string</span> text=chat.m_new_one.message_text; <span class="comment">//--- start 或 help 命令</span> &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 字符串,就能让手机端收到带「锁定 / 解锁 / 静音」图标的自定义键盘。外汇和贵金属行情波动剧烈,这类远程控制只建议用于模拟盘或非自动下单的提醒,实盘遥控存在滑点和误触高风险。
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 就能看到三选一加锁/静音的键盘长相。
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 跑起来发一句就能验证状态翻转。外汇与贵金属波动剧烈,自动化锁单只是降低误操作概率,不消除爆仓风险。
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)