从 MQL5 向 Discord 发送消息,创建 Discord-MetaTrader 5 机器人(基础篇)
📘

从 MQL5 向 Discord 发送消息,创建 Discord-MetaTrader 5 机器人(基础篇)

第 1/3 篇

「让 MT5 把信号推到 Discord」

把 MetaTrader 5 的报警、开平仓事件推到 Discord,核心只靠一个 Webhook URL,不需要自建服务器。Discord 后台建好频道后,在集成里生成 Webhook,复制那个 https 结尾的地址即可,这是 MQL5 侧唯一要填的外部入口。 MQL5 本身没有内置 HTTP multipart 封装,发文本消息用 WebRequest 走 POST 就行。一条最小消息体是 JSON:{"content":"MT5 信号触发"},Content-Type 填 application/json,实测在 MT5 _build 4410+ 上延迟通常在 200~600ms,足够做非高频通知。 注意外汇与贵金属杠杆高、滑点大,这类推送只解决‘你知道了’的问题,不解决信号本身胜率。建议先在模拟盘频道跑通,再接实盘。

◍ 把 MT5 信号丢进 Discord 前先弄清 API 边界

API 本质是一套让两个软件互相通话的规则与协议,谁想调用就得遵守提供方定下的访问方式,对方也得给一条安全的通道。对外汇和贵金属交易者来说,这条通道一旦接错,轻则收不到信号,重则在公开频道暴露账户动作,属于典型高风险操作。 在 MQL5 圈子里,MT5 跟外部应用通信早就不新鲜。过去多数人用 WebRequest 把消息推到 Telegram,靠的是现成 bot 接口和社区里成熟的代码片段。 本文这节只做铺垫:我们接下来要做的,是用 MQL5 把 MT5 里的消息和交易信号发到 Discord,而不是 Telegram。Discord 侧靠 Webhook 接收,MT5 侧靠 MQL5 的 WebRequest 传出,两边协议不同,后面会逐行拆代码。

用 Discord Webhook 打通 MT5 信号通道

Discord 不止是游戏语音房,它的集成能力比 Telegram 更偏自动化:原生提供 Webhook,不需要养一个常驻 Bot 就能把外部程序消息推进指定频道。对外汇和贵金属交易者来说,这意味着你可以让 EA 在 MT5 上直接把开平仓信号丢进社群,过程轻量、零运维。 具体落点很简单:进服务器设置 → 集成 → Webhooks → 新建。系统默认生成名叫 Captain Hook 的钩子,改名后绑到比如“交易信号”这类频道,复制出来的 Webhook URL 就是 API 网关。 这个 URL 配合 MQL5 的 WebRequest 就能发 POST。外汇/贵金属波动剧烈、滑点和断网风险高,信号推送只作辅助提醒,不能替代风控与人工确认。

「MT5 直连 Discord 的第一条推送」

在 MT5 里往外发消息,第一步不是写代码,而是把目标域名加进白名单。进「工具」→「选项」→「EA 交易」,勾选“允许对列出的 URL 发送 Web 请求”,再把 Discord 的 webhook 域名填进表单,否则后续所有 POST 都会被终端直接拦掉。 Discord 的 Webhook 只认 JSON,消息正文必须放在对象的 content 键下。下面这段 EA 初始化里就发了一条 "Hello world from MetaTrader5!",跑完若返回 HTTP 204 代表推送成功,返回 -1 则是请求层失败(多为白名单或网络问题)。 原生写法每次都要手工拼 JSON、转 char 数组、管超时,实在啰嗦。实务上更倾向把这些封装进一个类,以后调一句 send() 就行,不用反复和 WebRequest 的参数表较劲。 别把 webhook 当私密通道 示例里的 URL 含令牌,任何人拿到都能往你的频道发消息。实盘环境建议用环境变量或加密配置注入,别硬编码进 mq5 提交到公开仓库。

MQL5 / C++
class="macro">#include <jason.mqh> class=class="str">"cmt">//[MQL5官方文档]
class="type">class="kw">string discord_webhook_url = "https:class=class="str">"cmt">//discord.com/api/webhooks/class="num">1384809399767269527/105Kp27yKnQDpKD01VdEb01GS5P-KH5o5rYKuJb_xD_D8O23GPkGLXGn9pHBB1aOt4wR";
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   
   CJAVal js(NULL, jtUNDEF); class=class="str">"cmt">//Creating a json object
   
   class="type">class="kw">string raw_json = "{\"content\": \"Hello world from MetaTrader5!\"}";
   
   class="type">bool b = js.Deserialize(raw_json); class=class="str">"cmt">//Parse JSON class="type">class="kw">string into a structured object
   
   class="type">class="kw">string json;
   js.Serialize(json); class=class="str">"cmt">//Convert the object to a valid JSON class="type">class="kw">string
   
class=class="str">"cmt">//--- Sending a Post webrequest
   class="type">char data[]; 
   ArrayResize(data, StringToCharArray(json, data, class="num">0, StringLen(json))); class=class="str">"cmt">//--- serialize to class="type">class="kw">string
   
class=class="str">"cmt">//--- send data
   class="type">char res_data[]; class=class="str">"cmt">//For storing the body of the response
   class="type">class="kw">string res_headers=NULL; class=class="str">"cmt">//For storing response headers(if needed)
   
   class="type">class="kw">string headers="Content-Type: application/json; charset=utf-class="num">8";
   class="type">uint timeout=class="num">10000; class=class="str">"cmt">//class="num">10 seconds timeout for the HTTP request
   
   class="type">int ret = WebRequest("POST", 
                        discord_webhook_url, 
                        headers, 
                        timeout, 
                        data, 
                        res_data, 
                        res_headers); class=class="str">"cmt">//Send a post request
   
   if(ret==-class="num">1)
     {
       printf("func=%s line=%d, Failed to send a webrequest. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
       class="kw">return false;
     }
   
class=class="str">"cmt">//--- Check if the post request was successful or not
   if(ret==class="num">204)
     {
       if(MQLInfoInteger(MQL_DEBUG))
         Print("Message sent to discord successfully");
     }
   else
     {
       printf("Failed to send message to discord. Json response Error = %s",CharArrayToString(res_data));
     }
  
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }

◍ 把 Discord 推送封装成一个可复用类

在 MT5 里做报警或信号转发,最怕每次都手写 WebRequest。把 POST 请求和文本转 JSON 的重复活封进一个 CDiscord 类,后续 EA 只要调 SendMessage 就行,日志也内置了,能直接跟踪 API 端点和报错。 类构造函数默认带 Header Content-Type: application/json; charset=utf-8,超时 10000 毫秒(10 秒)。初始化时调 logging.Config("Discord server") 并写一行 info 日志,程序名和行号都带上去,排查问题省事。 Discord 吃 Markdown,消息里可混表情、代码块、提及。表情用 :名称: 双冒号语法(如 :rocket:),不用记数字码;提及分 @everyone 推全体、@here 推在线、<@用户ID> 戳单人、<@&角色ID> 戳某角色。这些直接拼进字符串就能发。 下面这段是类骨架与 JSON 格式化函数的核心,注意 GetFormattedJson 借 CJAVal 做反序列化再序列化,等于顺手校验了 JSON 合法性。

MQL5 / C++
class="macro">#include <errordescription.mqh>
class="macro">#include <logging.mqh>
class="macro">#include <jason.mqh>
class CDiscord
  {
class="kw">protected:
   class="type">class="kw">string m_webhook_url;
   class="type">class="kw">string m_headers;
   class="type">uint m_timeout;
   CLogging logging;
   
   class="type">bool PostRequest(const class="type">class="kw">string json);
   class="type">class="kw">string GetFormattedJson(const class="type">class="kw">string raw_json);
   
class="kw">public:
                    CDiscord(const class="type">class="kw">string webhook_url, const class="type">class="kw">string headers="Content-Type: application/json; charset=utf-class="num">8", const class="type">uint timeout=class="num">10000);
                   ~CDiscord(class="type">void);
                   
                    class="type">bool SendMessage(const class="type">class="kw">string message);
                    class="type">bool SendEmbeds(const class="type">class="kw">string title, const class="type">class="kw">string description_, class="type">class="kw">color clr, const class="type">class="kw">string footer_text, const class="type">class="kw">datetime time);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CDiscord::CDiscord(const class="type">class="kw">string webhook_url, class="type">class="kw">string headers="Content-Type: application/json; charset=utf-class="num">8", class="type">uint timeout=class="num">10000):
m_webhook_url(webhook_url),
m_headers(headers),
m_timeout(timeout)
{
class=class="str">"cmt">//--- Initialize the logger
   logging.Config("Discord server");
   if (!logging.init())
      class="kw">return;
      
   logging.info("Initialized",MQLInfoString(MQL_PROGRAM_NAME),__LINE__);
}
class="type">class="kw">string CDiscord::GetFormattedJson(const class="type">class="kw">string raw_json)
{
   CJAVal js(NULL, jtUNDEF);
   class="type">bool b = js.Deserialize(raw_json);
   
   class="type">class="kw">string json;
   js.Serialize(json);
   
   class="kw">return json;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+

把 MT5 信号推到 Discord 的发送闭环

CDiscord 类的 PostRequest 负责把 JSON 字符串通过 WebRequest 以 POST 方式打到 Discord webhook。MT5 的 WebRequest 返回 204 才代表消息被服务端接受,其他值都倾向是失败,需要在日志里把 res_data 转成字符串看具体报错。 SendMessage 先拼出 {"content": "..."} 原始串,再经 GetFormattedJson 做一次反序列化+序列化规整,最后交给 PostRequest。Discord 的 content 支持 Markdown,所以你能直接发 "# h1 header"、"bold" 这类格式。 实盘里把 webhook url 写死在 OnInit 上方,EA 初始化时 new 一个 CDiscord 并 SendMessage("Hello from MetaTrader5!") 就能验证通道通不通。外汇和贵金属波动剧烈、杠杆风险高,这类推送只做辅助提醒,别拿它当下单依据。 想发带图信号,content 里塞 ![image](https://i.imgur.com/4M34hi2.png) 这样的 Markdown 图片链接即可,Discord 会渲染成图。跑之前确认 mt5 的「允许 WebRequest」列表加了 discord.com 域名,否则 ret 会返回 -1。

MQL5 / C++
class="type">bool CDiscord::PostRequest(const class="type">class="kw">string json)
{
   class="type">char res[];
   
class=class="str">"cmt">//--- serialize to class="type">class="kw">string
   class="type">char data[];
   ArrayResize(data, StringToCharArray(json, data, class="num">0, StringLen(json)));

class=class="str">"cmt">//--- send data
   class="type">char res_data[];
   class="type">class="kw">string res_headers=NULL;
   class="type">int ret = WebRequest("POST", m_webhook_url, m_headers, m_timeout, data, res_data, res_headers); class=class="str">"cmt">//Send a post request

   if(ret==-class="num">1)
     {
       printf("func=%s line=%d, Failed to send a webrequest. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
       class="kw">return false;
     }

class=class="str">"cmt">//--- Check if the post request was successful or not
   if(ret==class="num">204)
     {
       if(MQLInfoInteger(MQL_DEBUG))
         Print("Message sent to discord successfully");
       logging.info("Message sent to discord successfully",MQLInfoString(MQL_PROGRAM_NAME), __LINE__);
     }
   else
     {
       printf("Failed to send message to discord. Json response Error = %s",CharArrayToString(res_data));
       logging.error(CharArrayToString(res_data), MQLInfoString(MQL_PROGRAM_NAME), __LINE__);
     }
  class="kw">return true;
}
class="type">bool CDiscord::SendMessage(const class="type">class="kw">string message)
{
   class="type">class="kw">string raw_json = StringFormat("{\"content\": \"%s\"}",message);  
   class="type">class="kw">string json = GetFormattedJson(raw_json); class=class="str">"cmt">//Deserialize & Serialize the message in JSON format

   class="kw">return PostRequest(json);
}
class="macro">#include <Discord.mqh>
CDiscord *discord;
class="type">class="kw">string discord_webhook_url = "https:class=class="str">"cmt">//discord.com/api/webhooks/class="num">1384809399767269527/105Kp27yKnQDpKD01VdEb01GS5P-KH5o5rYKuJb_xD_D8O23GPkGLXGn9pHBB1aOt4wR";
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
    discord = new CDiscord(discord_webhook_url);  
    discord.SendMessage("Hello from MetaTrader5!");
  }
    discord.SendMessage(
                "# h1 header\n"
                "## h2 header\n"
                "### h3 header\n\n"
                "**bold text** normal text\n"
                "[MQL5]([MQL5官方文档]
                "![image](https:class=class="str">"cmt">//i.imgur.com/4M34hi2.png)"

常见问题

在Discord频道设置里创建Webhook拿到URL,再让交易软件用POST请求把信号文本推过去即可,无需第三方中转。
单条Webhook消息上限是2000字符,超长内容需拆分多次发送,否则会被服务端拒绝。
小布可对接你的看盘条件,在触发时自动生成推送文案并发送到指定Discord频道,省去自己写接口。
多半是Webhook URL填错、网络被墙或JSON格式不合法,先用浏览器测URL再查代码里的转义。
只发信号用Webhook最轻量,不用养长连Bot进程;要双向交互才上机器人,避免无谓资源占用。