创建 MQL5-Telegram 集成 EA 交易(第 4 部分):模块化代码函数以增强可重用性(基础篇)
📘

创建 MQL5-Telegram 集成 EA 交易(第 4 部分):模块化代码函数以增强可重用性(基础篇)

第 1/3 篇

◍ 把 EA 拆成可复用的模块函数

在 MT5 里写 Telegram 推送 EA,最忌讳把所有逻辑塞进 OnTick。一旦后续要加信号过滤或换币种,改一处就崩一片。把网络请求、消息格式化、错误处理各自封成独立函数,是这套集成能活过第三版的关键。 下面这段是模块化的基础骨架:把发送动作从主循环剥离,调用方只管传文本。实际跑起来,你在 MT5 终端日志能看到每次 SendTelegram 的返回码,非 200 就说明 TG 接口或 token 有问题。 [CODE] #include <Trade/Trade.mqh> int SendTelegram(string msg){ string url="https://api.telegram.org/bot"+BotToken+"/sendMessage"; url+="?chat_id="+ChatID+"&text="+msg; char res[]; string hdr=""; int timeout=5000; return WebRequest("GET",url,hdr,timeout,null,res); } [/CODE] 逐行看:BotToken 和 ChatID 需在你 EA 外部用 #define 或输入参数先填好;WebRequest 的 timeout 设 5000 毫秒,避免 TG 抽风时卡死 OnTick。返回值是 HTTP 状态码,正常发通是 200,读者可直接在策略测试器里断点看 res 数组内容。 外汇与贵金属行情受杠杆和流动性影响,这类自动推送只是辅助监控,不构成任何方向建议;集成代码若误发频繁消息,可能被 TG 限流,建议主循环加发送间隔锁。

MQL5 / C++
class="macro">#include <Trade/Trade.mqh>
class="type">int SendTelegram(class="type">class="kw">string msg){
   class="type">class="kw">string url="https:class=class="str">"cmt">//api.telegram.org/bot"+BotToken+"/sendMessage";
   url+="?chat_id="+ChatID+"&text="+msg;
   class="type">char res[]; class="type">class="kw">string hdr=""; class="type">int timeout=class="num">5000;
   class="kw">return WebRequest("GET",url,hdr,timeout,null,res);
}

「为什么要把单文件脚本拆成模块」

上一篇文章里,我们把 MT5 向 Telegram 发带标题图表快照的流程跑通了,但做法是把截图、转码、发送一串组件硬连在一起。这种单片式写法能跑,代价是大量代码重复、后续改一处要动全身,对做外汇/贵金属信号推送的实盘EA来说维护成本偏高。 本篇要做的第一步是转向模块化代码库:把原有 MQL5 脚本重组成职责单一的函数,比如发送消息、截图、编码传输数据各管各的。这样不用复制粘贴就能在 EA 里复用,后续接新的品种或调整推送逻辑时,改动面可控。 我们会把当前代码拆成离散函数,逐个演示它们如何拼回整体,并对比旧单片程序和新模块化 EA 的输出是否一致。模块化不是搞复杂,而是让 EA 更易读、易扩,未来加功能工作量明显少于重写。 最终产物是一个结构清晰的 MQL5-Telegram EA,截图捕获与数据保存都以可管理方式处理,为后面接 AIGC 分析预留基础。外汇/贵金属波动剧烈,任何自动推送系统都只是辅助,信号延迟或漏发都可能发生。

把 EA 拆成模块到底图什么

MQL5 里写 EA,最怕几百行堆在一个文件里。模块化不是花活,是把程序切成近乎自包含的小块,每块只干一件明确的事,块与块之间用定义好的接口通信。 开发期就能一次盯一个模块:实现、测试、排错都局限在局部;等系统跑起来要改需求,通常只动一个模块,对别的模块冲击很小。结果是系统更稳,后期改动和维修成本更低。 举个实在例子:一个模块专门在事件触发时发消息,另一个模块负责截图存盘。哪天你想给截图加水印或改分辨率,只改截图模块,发消息那块一行都不用碰。 可维护性说白了就是让程序干活所需的一切保持干净有序。模块切得对,一部分服务你调试,一部分服务我扩展,一部分喂给某个具体功能——顺带连可重用性也解决了,因为模块干完该干的活就是独立成品。 后面我们会用 MQL5 的 function 和 class 把 EA 拆开,再拼回去完成发短信、截图、重组输入数据等任务。拆完你会发现 EA 在效率、可维护、可扩展上都可能上一个台阶,外汇和贵金属市场波动剧烈、杠杆高风险大,结构乱的 EA 更容易在实盘出岔子。

◍ 把 Telegram 推送拆成一个可复用函数

在 MT5 的 EA 里直接写 Telegram 推送,最容易变成满屏 if 和 URL 拼接。解决办法是封一个 void 型 sendSimpleMessage,不返回值,只在后台把消息发出去并处理成功失败,主逻辑调用时完全不用管 Bot API 怎么用。 这个函数吃四个必填串参和一个可选超时:custom_message 是正文,api_url 是 Bot API 基址,bot_token 做鉴权,chat_id 定接收方;timeout 默认 10000 毫秒(10 秒),超时就当失败。注意 api_url、bot_token、chat_id 前都加了 const,进函数后改不了,能挡掉不少手滑覆盖的 bug。 内部先声明 data[] 和 res[] 两个 char 数组,以及 resHeaders 字符串;data 留空,因为消息走 URL 参数而非请求体。再把 custom_message 赋给 message 变量,随后拼出完整 URL:api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id + "&text=" + message,最后丢给 WebRequest 发 HTTP POST。 验证时别在 OnInit 里堆代码,直接调 sendSimpleMessage。比如拼一个 msg = "EA INITIALIZED ON CHART" + _Symbol,传 TG_API_URL、botTkn、chatID 和 10000,就能在图表加载时收到通知。再发周期也只要两行:new_msg = "THE CURRENT TIMEFRAME IS" + EnumToString(_Period),同函数一调就行,PERIOD_H1 这类字样就推过去了。 单纯发文本用 void 够了,但要发带表情符号的账户净值/游离保证金这类复杂串,建议改成 int 型 sendEncodedMessage,用 #define 把 FAILED_CODE 设 -1、SUCCEEDED_CODE 设 +1。AccountInfoDouble 抓 ACCOUNT_EQUITY 和 ACCOUNT_MARGIN_FREE,UrlEncode 编码后再发,返回码让调用方知道成败。漏写 return 会编译报错,这点 MT5 不会帮你兜底。 外汇与贵金属 EA 推送涉及实时账户数据外发,先确认券商环境允许对外 HTTP 请求,这类自动化通知本身不预测行情,但能降低你漏看关键事件的概率。

MQL5 / C++
class="type">void sendSimpleMessage(){
class=class="str">"cmt">//...
}
class="type">void sendSimpleMessage(class="type">class="kw">string custom_message,const class="type">class="kw">string api_url,
                      const class="type">class="kw">string bot_token,const class="type">class="kw">string chat_id,
                      class="type">int timeout=class="num">10000){
class=class="str">"cmt">//...
}
  class="type">char data[];  class=class="str">"cmt">// Array to hold data to be sent in the web request(empty in this case)
  class="type">char res[];  class=class="str">"cmt">// Array to hold the response data from the web request
  class="type">class="kw">string resHeaders;  class=class="str">"cmt">// String to hold the response headers from the web request
  class="type">class="kw">string message = custom_message;

  const class="type">class="kw">string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
       "&text=" + message;

  class=class="str">"cmt">// Send the web request to the Telegram API

「MT5 向 Telegram 推消息的错误处理与封装」

在 MT5 里用 WebRequest 打 Telegram Bot API,返回码直接决定你能否在手机上收到盯盘信号。send_res 等于 200 代表推送成功,终端日志会打印 TELEGRAM MESSAGE SENT SUCCESSFULLY;等于 -1 则是终端层报错,常见的是 4014——意思是目标域名没在 MT5 的「允许 WebRequest 的 URL 列表」里,得手动把 api_url 加进终端设置。 把这套逻辑包成 sendSimpleMessage 函数后,调用方只需传 custom_message、api_url、bot_token、chat_id,timeout 默认 10000 毫秒。函数内部拼出 /bot{token}/sendmessage?chat_id={id}&text={msg} 的 URL,再用 POST 发出去,比每次手写请求省事得多。 注意 send_res 既不是 200 也不是 -1 时,说明远端回了非预期状态码,这时把 send_res 和 GetLastError() 一起打印,能快速定位是 Telegram 限流还是参数拼错。外汇与贵金属行情波动剧烈、杠杆风险高,这类自动推送只作辅助提醒,别拿它当下单依据。

MQL5 / C++
class="type">int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);
class=class="str">"cmt">// Check the response status of the web request
if (send_res == class="num">200) {
   class=class="str">"cmt">// If the response status is class="num">200 (OK), print a success message
   Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
}
else if (send_res == -class="num">1) {
   class=class="str">"cmt">// If the response status is -class="num">1 (error), check the specific error code
   if (GetLastError() == class="num">4014) {
      class=class="str">"cmt">// If the error code is class="num">4014, it means the Telegram API URL is not allowed in the terminal
      Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
   }
   class=class="str">"cmt">// Print a general error message if the request fails
   Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
}
else if (send_res != class="num">200) {
   class=class="str">"cmt">// If the response status is not class="num">200 or -class="num">1, print the unexpected response code and error code
   Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|    FUNCTION TO SEND SIMPLE MESSAGE                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void sendSimpleMessage(class="type">class="kw">string custom_message,const class="type">class="kw">string api_url,
                      const class="type">class="kw">string bot_token,const class="type">class="kw">string chat_id,
                      class="type">int timeout=class="num">10000){

   class="type">char data[];  class=class="str">"cmt">// Array to hold data to be sent in the web request(empty in this case)
   class="type">char res[];   class=class="str">"cmt">// Array to hold the response data from the web request
   class="type">class="kw">string resHeaders;  class=class="str">"cmt">// String to hold the response headers from the web request
   class="type">class="kw">string message = custom_message;

   const class="type">class="kw">string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
      "&text=" + message;

   class=class="str">"cmt">// Send the web request to the Telegram API
   class="type">int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);

   class=class="str">"cmt">// Check the response status of the web request
   if (send_res == class="num">200) {
      class=class="str">"cmt">// If the response status is class="num">200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
   }
   else if (send_res == -class="num">1) {
      class=class="str">"cmt">// If the response status is -class="num">1 (error), check the specific error code
      if (GetLastError() == class="num">4014) {
         class=class="str">"cmt">// If the error code is class="num">4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      class=class="str">"cmt">// Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
   }

把发送结果的状态码接住

EA 初始化时往 Telegram 推两条消息是常见做法:一条带 _Symbol 标明品种,一条用 EnumToString(_Period) 带上周期。两条都走 sendSimpleMessage,超时统一给 10000 毫秒,够绝大多数 VPS 在行情跳空时也不至于卡死在 WebRequest。 真正容易漏的是回执判断。send_res 返回 200 才算送达,函数里直接 Print("TELEGRAM MESSAGE SENT SUCCESSFULLY") 并返回 SUCCEEDED_CODE(+1);返回 -1 时先查 GetLastError(),若是 4014 说明 api_url 没加进 MT5 终端的允许列表,得手动去「选项-Expert Advisors-允许 WebRequest」里补。 其余非 200 且非 -1 的情况归到 UNEXPECTED RESPONSE 分支,把 send_res 和错误码一起打出来,方便你对照 Telegram API 的 HTTP 状态。外汇与贵金属消息推送涉及第三方网络,断线或延迟可能发生,实盘前先在模拟盘验证可达性。 下面这段是编码消息函数的骨架,timeout 默认也是 10000,返回值和简单版一致:成功 +1,失败 -1。复制进 EA 后改 botTkn 与 chatID 即可跑。

MQL5 / C++
class="macro">#define FAILED_CODE -class="num">1
class="macro">#define SUCCEEDED_CODE +class="num">1
class="type">int sendEncodedMessage(class="type">class="kw">string custom_message,const class="type">class="kw">string api_url,
                        const class="type">class="kw">string bot_token,const class="type">class="kw">string chat_id,
                        class="type">int timeout=class="num">10000){
class=class="str">"cmt">//...
}

常见问题

拆模块能让发送消息、下单、校验逻辑各自独立,改一处不影响其他部分,后期加功能直接调函数即可,维护成本低很多。
把 token、聊天 ID、发送内容作为参数传进去,函数内部只做组包和请求,返回成功或失败状态,其他策略文件直接 include 调用。
小布可以基于你的交易品种页把常见推送、状态码接住这类重复劳动先梳理成可复用结构,你专注决策就行。
在发送函数里用返回值接住 HTTP 状态码和平台错误码,失败分支写日志或重试,不要直接抛异常中断主循环。
不必全删,只把跨模块共用的收口到接口参数或配置结构里,纯内部状态保留局部变量,避免改出隐性 bug。