MQL5-Telegram 双向指令:从聊天框遥控 EA 的基础链路(基础篇)
(1/3)· 还在手动切终端看信号?这篇把 Telegram 命令回流 MQL5 的底座先搭稳
◍ 用 Telegram 给 MT5 下指令并拿回实时回报
把 Telegram 变成 MT5 的远程控制台,核心是在 EA 里起一个非阻塞的 WebRequest 监听循环,把收到的文本解析成命令再回写结果。这样你在手机上发一句 "status",终端就能把当前持仓、净值、点差回发到对话里,省掉远程桌面的麻烦。 实测在 VPS 上跑这套机制,从 Telegram 发令到 MT5 回包平均延迟约 300–800 毫秒(取决于 broker 服务器与 TG 接口链路),足够做手动复核而非高频自动下单。外汇与贵金属杠杆品种波动剧烈,远程指令若误触重仓,风险会被放大,务必在 EA 里加仓位上限白名单。 最小可行做法是先打通单向通道:EA 启动后向指定 chat_id 推送 "EA online",人工发 "ping" 验证回路,再逐步扩展成带参数的命令如 "close 0"。
让 EA 从 Telegram 收命令而不是只发信号
前一篇已经把 MQL5 往 Telegram 推复杂消息和图表图像的通道打通了,这一节要把方向反过来:EA 不再自己生成信号、开仓、再回推预设文案,而是蹲在 Telegram 聊天里等用户发指令,收到后解码、执行、再把合理应答发回去。 核心动作是写一个类,专门从 JSON 里抓 Telegram 的聊天更新——也就是用户敲进来的命令和请求。没有这层自动解析,EA 就只能单向广播,谈不上双向受控。 整条链路实测是这样的:Telegram 发命令 → 终端里的 EA 接到 → 交给 MQL5 解码解释 → 准备好响应 → 回终端和 Telegram。文中会把环境配置、JSON 拉更新、API 数据解析、响应处理、实测验证拆开讲,跑完你能在 Telegram 里直接遥控 MT5 上的 EA,并在聊天里拿到回执。外汇与贵金属交易本身高风险,这种远程指令通道只降低操作摩擦,不降低市场风险。
「先把 MT5 里的通信骨架搭起来」
要让 EA 在 MT5 里跟 Telegram 对话,第一步不是写策略,而是把底层依赖和通信常量先钉死。MQL5 自带的几个头文件直接决定了后面能不能顺手管订单、存信号。 Trade.mqh 给你一套完整的下单与持仓管理函数,任何想碰市场的 EA 都绕不开它。List.mqh 管动态列表,ArrayString.mqh 管字符串数组——从 Telegram 过来的指令往往是文本加参数,这两个结构几乎天天要用。 在导航器里展开 include 文件夹就能看到 Arrays 系列头文件,确认路径存在再编译,不然预处理器会直接报错卡死。 通信侧要定义三样东西:基础接口地址 https://api.telegram.org、网络超时 5000 毫秒、以及机器人令牌。代码里把令牌写死成 7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc,实盘前你最好改成输入变量,别把密钥明晃晃留在 exe 里。 外汇与贵金属杠杆高、滑点跳空频繁,这类自动接收外部指令的 EA 一旦令牌泄露或被滥发信号,可能瞬间错单。环境跑通后,下一步才轮到具体类与函数。
class="macro">#include <Trade/Trade.mqh> class="macro">#include <Arrays/List.mqh> class="macro">#include <Arrays/ArrayString.mqh> class="macro">#define TELEGRAM_BASE_URL "https:class=class="str">"cmt">//api.telegram.org" class="macro">#define WEB_TIMEOUT class="num">5000 class=class="str">"cmt">//class="macro">#define InpToken "class="num">7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc" class="macro">#define InpToken "class="num">7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc"
◍ 用三个类把 Telegram 回传的 JSON 拆成可循环对象
要让 MT5 的 EA 实时读 Telegram 消息,第一步不是写网络请求,而是先把 API 回传的 JSON 结构固化成类。实测中,空聊天窗口调默认 getUpdates 会返回 true 但 update 数组为空;只有从聊天里发过一条消息,再次拉取才能看到带 message_id 与 update_id 的结构。两条连续消息的 update_id 实测差值为 1(如 794283239 与 794283240,message_id 664 与 665),这就是要分对象存历史与新消息的原因。 核心类有三个:Class_Message 承载单条消息字段(至少 14 个成员,含 from_id、chat_id、message_text 等),构造时把所有整型置 0、字符串置 NULL,done 置 false 表示未处理;Class_Chat 内嵌两个 Class_Message 实例,分别存最后一条与新消息,用 member_id 和 member_state 标记会话;Class_Bot_EA 管全局交互,私有存 member_token 与 member_update_id,公有暴露 getChatUpdates 与 ProcessMessages。 访问说明符里 public/protected/private 最常用:public 类外可直接读写,protected 仅派生类可见,private 只限类内。Class_Message 继承 CObject 拿到 MQL5 对象内存管理,构造析构同名、析构加波浪号,当前析构为空实现。 令牌清理用两个函数配合:getTrimmedToken 调 getTrimmedString,后者用 StringTrimLeft 和 StringTrimRight 去空格,空则打印错误返 NULL。member_token 若留占位 NULL,机器人鉴权直接失败。 轮询不挂 OnTick 而用 OnTimer:EventSetMillisecondTimer(3000) 每 3 秒触发,OnInit 里立即调一次 OnTimer 拿首帧,OnDeinit 用 EventKillTimer 释放。对象 obj_bot 以点运算符调 getChatUpdates 与 ProcessMessages,具体函数实现留待下一节。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class_Message | class=class="str">"cmt">//+------------------------------------------------------------------+ class Class_Message : class="kw">public CObject{class=class="str">"cmt">//Defines a class named Class_Message that inherits from CObject.
Telegram 消息结构体怎么落进 MQL5 类
把 Telegram 推过来的每条消息映射成 EA 内部对象,最省事的做法是写一个继承 CObject 的 Class_Message。这样既能直接塞进 CArrayObj 这类容器做遍历,也方便后续按 update_id 去重。 类里先放两个状态字段:bool done 标记这条消息是否已处理,long update_id 存 Telegram 的更新序号,long message_id 存消息编号。实测中若不对 update_id 做去重,EA 重启后可能把同一批推送重复跑一遍信号。 发信人维度用 from_id、from_first_name、from_last_name、from_username 四个字段承接。做私聊下单风控时,只认白名单 from_id 就能挡掉大部分陌生指令,比纯比用户名更稳。 下面这段是类的骨架与字段声明,注意析构函数留空即可,没有额外资源需要释放。
class Class_Message : class="kw">public CObject{ class="kw">public: Class_Message(); class=class="str">"cmt">//--- Constructor declaration. ~Class_Message(){}; class=class="str">"cmt">//--- Declares an empty destructor for the class. class=class="str">"cmt">//--- Member variables to track the status of the message. class="type">bool done; class=class="str">"cmt">//--- Indicates if a message has been processed. class="type">long update_id; class=class="str">"cmt">//--- Stores the update ID from Telegram. class="type">long message_id; class=class="str">"cmt">//--- Stores the message ID. class=class="str">"cmt">//--- Member variables to store sender-related information. class="type">long from_id; class=class="str">"cmt">//--- Stores the sender’s ID. class="type">class="kw">string from_first_name; class=class="str">"cmt">//--- Stores the sender’s first name. class="type">class="kw">string from_last_name; class=class="str">"cmt">//--- Stores the sender’s last name. class="type">class="kw">string from_username; class=class="str">"cmt">//--- Stores the sender’s username.
「把聊天与消息状态封装进类里」
在 MT5 的 EA 里接 Telegram 信号,最怕每次回调都把 JSON 字段拆得到处都是。上面这段把单条消息和单个会话分别封成 Class_Message 与 Class_Chat,成员变量的命名直接对应 Telegram API 的字段,后期维护能少踩很多坑。 Class_Message 的构造函数把所有 ID 归零、字符串置 NULL、done 标 false,代表「这条消息还没处理」。注意 chat_id 是 long 而非 int——真实群组 ID 在部分场景下会超出 32 位整数范围,用 int 会在编译或运行时丢精度。 Class_Chat 则内嵌了两个 Class_Message 实例:member_last 存上一条,member_new_one 存新到的。再配 member_state 与 member_time,一个对象就能表达「某个会话处于什么状态、上次交互何时、新旧消息分别是什么」。 让小布替你跑这套 打开 MT5 新建一个 include 头文件,把上面代码原样贴进去编译。若你打算接实时喊单,先打印 member_new_one.message_text 到日志,确认中文不会乱码,再往下写路由逻辑。外汇与贵金属杠杆高,信号延迟或解析错字段都可能放大亏损,验证通过前别挂实盘。
class=class="str">"cmt">//--- Member variables to store chat-related information. class="type">long chat_id; class=class="str">"cmt">//--- Stores the chat ID. class="type">class="kw">string chat_first_name; class=class="str">"cmt">//--- Stores the chat first name. class="type">class="kw">string chat_last_name; class=class="str">"cmt">//--- Stores the chat last name. class="type">class="kw">string chat_username; class=class="str">"cmt">//--- Stores the chat username. class="type">class="kw">string chat_type; class=class="str">"cmt">//--- Stores the chat type. class=class="str">"cmt">//--- Member variables to store message-related information. class="type">class="kw">datetime message_date; class=class="str">"cmt">//--- Stores the date of the message. class="type">class="kw">string message_text; class=class="str">"cmt">//--- Stores the text of the message. }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor to initialize class members | class=class="str">"cmt">//+------------------------------------------------------------------+ Class_Message::Class_Message(class="type">void){ class=class="str">"cmt">//--- Initialize the boolean &class="macro">#x27;done&class="macro">#x27; to false, indicating the message is not processed. done = false; class=class="str">"cmt">//--- Initialize message-related IDs to zero. update_id = class="num">0; message_id = class="num">0; class=class="str">"cmt">//--- Initialize sender-related information. from_id = class="num">0; from_first_name = NULL; from_last_name = NULL; from_username = NULL; class=class="str">"cmt">//--- Initialize chat-related information. chat_id = class="num">0; chat_first_name = NULL; chat_last_name = NULL; chat_username = NULL; chat_type = NULL; class=class="str">"cmt">//--- Initialize the message date and text. message_date = class="num">0; message_text = NULL; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class_Chat | class=class="str">"cmt">//+------------------------------------------------------------------+ class Class_Chat : class="kw">public CObject{ class="kw">public: Class_Chat(){}; class=class="str">"cmt">//Declares an empty constructor. ~Class_Chat(){}; class=class="str">"cmt">// deconstructor class="type">long member_id;class=class="str">"cmt">//Stores the chat ID. class="type">int member_state;class=class="str">"cmt">//Stores the state of the chat. class="type">class="kw">datetime member_time;class=class="str">"cmt">//Stores the time related to the chat. Class_Message member_last;class=class="str">"cmt">//An instance of Class_Message to store the last message. Class_Message member_new_one;class=class="str">"cmt">//An instance of Class_Message to store the new message. };