Discord 接 MetaTrader 5:把交易机器人的实时告警推到手机
🔔

Discord 接 MetaTrader 5:把交易机器人的实时告警推到手机

多数 EA 只在本机弹窗,你出门就失联;用 Webhook 把执行与行情告警转推 Discord,任意地点都能盯盘。

偏理论
不少交易者写完 EA 就以为监控到位,其实平台默认禁止对外发请求,WebRequest 没开、webhook 域名还是旧的那套,消息根本出不了本机。等真错过一单才翻日志,已经晚了。

用 Discord 给 MT5 机器人加实时播报

把 Discord 接进 MetaTrader 5,核心是解决「信号发生」与「人不在电脑前」之间的断层。通过 Webhook,MT5 里的 EA 能在订单成交、止损触发、指标背离的瞬间,把消息推送到你私有的 Discord 频道,延迟通常低于 1 秒(取决于 broker 与网络)。 外汇与贵金属本身属于高杠杆、高波动品种,这类实时通知只解决「知道」,不解决「该不该跟」,误报照样会亏。 实现上不依赖第三方付费桥,MT5 自带的 WebRequest 就能 POST 到 Discord 的 incoming webhook URL。下面这段是最小可用骨架,跑通后你再往里塞自己的交易逻辑。

◍ 用 Discord 给 MT5 加一层远程盯盘

把 Discord 接进 MetaTrader 5,本质是用平台的 WebRequest 能力往外推消息,让你在手机端也能实时看到下单、平仓和异动警报。这套思路不挑品种,外汇和贵金属都跑得通,但两者杠杆高、跳空频繁,远程通知只能帮你盯,不能替你控风险。 我们后面会拿一个随机交易机器人做样例,把「平台侧开权限 → Discord 服务端收消息 → MQL5 里发请求」这条链路走通。你照着配完,至少能在不在电脑前时,靠 Discord 推送知道 EA 有没有动手。 前提是你已经写过 MQL5、知道 EA 和脚本怎么挂。如果连 WebRequest 选项卡都没碰过,先去把 MT5 选项里的「允许 WebRequest」名单搞明白,不然代码写了也发不出去。

「给 MT5 放行 Discord 的 Webhook 地址」

在 MT5 里调用 Discord 提醒,先要在「选项 → 智能交易系统」里把两个 https 地址加进允许 Web 请求的列表,否则 EA 发不出消息。实际配置时,除创建并复制 webhook URL,还得准备一台服务器,或者直接用 Discord 里现成的服务器承接推送。 漏掉 URL 白名单这一步,MT5 终端会在日志里报 403 类网络错误,EA 不会崩但提醒全丢,这种隐性失效比报错更难排查。

把 MT5 的报警推到 Discord 频道

MT5 的 EA 要往 Discord 发消息,核心就是 Webhook URL。注意老教程里写的 discordapp.com 域名已经失效,现在必须换成 discord.com 才能被服务器接受,漏掉这一步会一直收不到推送。 EA 里先放几个 input 参数:机器人名、消息颜色、是否发价格更新。这些不写死在代码里,方便你换账号或换品种时直接改面板,不用动源码。 发送函数最怕两件事:MT5 没开 WebRequest 权限,以及 JSON 里混进未转义的特殊字符。权限在 OnInit 里用 TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) 查,没开就直接 INIT_FAILED,别让 EA 挂着却哑火。 实际发请求时,WebRequest 超时设 5000 毫秒,返回码 200 或 204 才算成功。失败就返回 false,调用处可以写重试或兜底通知。字符数组必须用 CP_UTF8 转换,否则中文和 emoji 会乱码。 Webhook URL 等于半把钥匙,谁拿到都能往你频道灌消息。实盘里别明文写死在 EA 顶部,用加密或外部配置存。Discord 自身有速率限制,慢网络下建议加指数退避的重发队列,别硬撞。

MQL5 / C++
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
class=class="str">"cmt">// Discord webhook URL - Replace with your webhook URL
class="type">class="kw">string discord_webhook = "https:class=class="str">"cmt">//discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXXXXXXXX";
class=class="str">"cmt">// Strategy Parameters
class="type">class="kw">string discord_webhook = "https:class=class="str">"cmt">//discord.com/api/webhooks/your-webhook-url";
input group "Discord Settings"
input class="type">class="kw">string DiscordBotName = "MT5 Trading Bot";      
input class="type">color MessageColor = clrBlue;                
input class="type">bool SendPriceUpdates = true;
class="type">int OnInit() {
    Print("Initialization step class="num">1: Checking WebRequest permissions...");
    
    if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) {
        Print("Error: WebRequest is not allowed. Please allow in Tool -> Options -> Expert Advisors");
        class="kw">return INIT_FAILED;
    }
    
    class="type">class="kw">string test_message = "{\"content\":\"Test message from MT5\"}";
    class="type">class="kw">string headers = "Content-Type: application/json\r\n";
    class="type">char data[], result[];
    ArrayResize(data, StringToCharArray(test_message, data, class="num">0, WHOLE_ARRAY, CP_UTF8) - class="num">1);

◍ 把交易信号推到 Discord 的底层写法

EA 想在 MT5 里把开仓信号实时丢进 Discord,核心就是 WebRequest 打 POST 到 webhook。下面这段初始化里的请求若返回 -1,基本是 MT5 的「允许 URL」名单没加,得手动补上 https://discord.com/* 与 https://discordapp.com/*,否则 EA 会直接 INIT_FAILED 起不来。 int res = WebRequest( "POST", discord_webhook, headers, 5000, data, result, headers ); if(res == -1) { Print("Make sure these URLs are allowed: https://discord.com/*, https://discordapp.com/*"); return INIT_FAILED; } return(INIT_SUCCEEDED); 上面代码逐行拆:第1行声明 res 接收 WebRequest 返回值;POST 方法发往 discord_webhook 地址;headers 带请求头;5000 是超时毫秒数;data 为发出去的包体;result 存响应;若 res 为 -1 说明请求层失败,打印提示并返回初始化失败;否则返回成功。 FormatTradeMessage 负责把交易结构体拼成可读文本,价格与止损用 _Digits 精度、手数固定 2 位。EscapeJSON 必须把反斜杠、双引号、换行等转义,不然 JSON 包会断。SendDiscordMessage 里 res==200 或 204 才认发送成功,并记下 lastMessageTime——这套返回码判断比只看大于0更稳,能避掉 400 类假阳性。

MQL5 / C++
class="type">int res = WebRequest(
  "POST",
  discord_webhook,
  headers,
  class="num">5000,
  data,
  result,
  headers
);

if(res == -class="num">1) {
  Print("Make sure these URLs are allowed: https:class=class="str">"cmt">//discord.com/*, https://discordapp.com/*");
  class="kw">return INIT_FAILED;
}

class="kw">return(INIT_SUCCEEDED);
}
class="type">class="kw">string FormatTradeMessage(TradeInfo& tradeInfo) {
  class="type">class="kw">string message = "**New " + tradeInfo.type + " Signal Alert!**\n";
  message += "Symbol: " + tradeInfo.symbol + "\n";
  message += "Type: " + tradeInfo.type + "\n";
  message += "Price: " + DoubleToString(tradeInfo.price, _Digits) + "\n";
  message += "Lots: " + DoubleToString(tradeInfo.lots, class="num">2) + "\n";
  message += "Stop Loss: " + DoubleToString(tradeInfo.sl, _Digits) + "\n";
  message += "Take Profit: " + DoubleToString(tradeInfo.tp, _Digits) + "\n";
  message += "Time: " + TimeToString(TimeCurrent());
  class="kw">return message;
}
class="type">class="kw">string EscapeJSON(class="type">class="kw">string text) {
  class="type">class="kw">string escaped = text;
  StringReplace(escaped, "\\", "\\\\");
  StringReplace(escaped, "\"", "\\\"");
  StringReplace(escaped, "\n", "\\n");
  StringReplace(escaped, "\r", "\\r");
  StringReplace(escaped, "\t", "\\t");
  class="kw">return escaped;
}
class="type">bool SendDiscordMessage(class="type">class="kw">string message, class="type">bool isError = class="kw">false) {
  if(!isWebRequestEnabled) class="kw">return class="kw">false;
  
  message = (isError ? "❌ " : "✅ ") + message;
  class="type">class="kw">string payload = "{\"content\":\"" + EscapeJSON(message) + "\"}";
  class="type">class="kw">string headers = "Content-Type: application/json\r\n";
  
  class="type">char post[], result[];
  ArrayResize(post, StringToCharArray(payload, post, class="num">0, WHOLE_ARRAY, CP_UTF8) - class="num">1);
  
  class="type">int res = WebRequest(
    "POST",
    discord_webhook,
    headers,
    class="num">5000,
    post,
    result,
    headers
  );
  
  if(res == class="num">200 || res == class="num">204) {
    lastMessageTime = TimeCurrent();
    class="kw">return true;
  }

「随机下单与报价推送的实现骨架」

这段逻辑把「随机策略」落到了可执行的 MQL5 函数里:用 MathRand() 对 2 取模决定多空,再用 SymbolInfoDouble 抓当前 ASK/BID 作为开仓价。SL 和 TP 点数各自在 50~149 点之间随机生成(50 + 0~99),通过 _Point 换算成绝对价格,避免硬编码点位。 PlaceRandomTrade 里先拼好 TradeInfo 结构,调用 FormatTradeMessage 发 Discord 预告;只有 SendDiscordMessage 返回 true 才真正 trade.PositionOpen。成功后会回传订单号,失败则静默——这种「先通知后下单」的顺序,能在 MT5 实盘里帮你确认网络通道是否活着。 SendPriceUpdate 受 SendPriceUpdates 开关控制,且用 TimeCurrent() - lastMessageTime < 300 做了 5 分钟节流,只推送 Bid/Ask/Spread。外汇与贵金属杠杆高、滑点随机,这类自动推送仅供监控,不代表任何方向建议。 OnDeinit 在 EA 停止时把 reason 码发出来,方便你区分是手动卸载还是断线重连。把下面代码直接塞进 EA 的同类文件,改 LotSize 和 magicNumber 就能在策略测试器里跑一遍验证。

MQL5 / C++
  class="kw">return class="kw">false;
}
class="type">void PlaceRandomTrade() {
    class="type">bool isBuy = (MathRand() % class="num">2) == class="num">1;
    
    class="type">class="kw">double price = isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                          : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    
    class="type">int slPoints = class="num">50 + (MathRand() % class="num">100);
    class="type">int tpPoints = class="num">50 + (MathRand() % class="num">100);
    
    class="type">class="kw">double sl = isBuy ? price - slPoints * _Point : price + slPoints * _Point;
    class="type">class="kw">double tp = isBuy ? price + tpPoints * _Point : price - tpPoints * _Point;
    
    TradeInfo tradeInfo;
    tradeInfo.symbol = _Symbol;
    tradeInfo.type = isBuy ? "BUY" : "SELL";
    tradeInfo.price = price;
    tradeInfo.lots = LotSize;
    tradeInfo.sl = sl;
    tradeInfo.tp = tp;
    
    class="type">class="kw">string message = FormatTradeMessage(tradeInfo);
    if(SendDiscordMessage(message)) {
        trade.SetExpertMagicNumber(magicNumber);
        
        class="type">bool success = isBuy ?
            trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, LotSize, price, sl, tp, "Random Strategy Trade") :
            trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, LotSize, price, sl, tp, "Random Strategy Trade");
            
        if(success) {
            SendDiscordMessage("✅ Trade executed successfully! Ticket: " + IntegerToString(trade.ResultOrder()));
        }
    }
}
class="type">void SendPriceUpdate() {
    if(!SendPriceUpdates) class="kw">return;
    if(TimeCurrent() - lastMessageTime < class="num">300) class="kw">return;
    
    class="type">class="kw">string message = "```\n";
    message += "Price Update for " + _Symbol + "\n";
    message += "Bid: " + DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits) + "\n";
    message += "Ask: " + DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits) + "\n";
    message += "Spread: " + DoubleToString(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD), class="num">0) + " points\n";
    message += "```";
    
    SendDiscordMessage(message);
}
class="type">void OnDeinit(const class="type">int reason) {
    SendDiscordMessage("```\nEA stopped. Reason code: " +
                        IntegerToString(reason) + "```");
}

把通知链路接进 MT5 才算闭环

Discord 与 MT5 打通后,实时推送、群内沟通和远程盯盘确实能补上手动交易的盲区。但落地不是点几下就完事:Webhook 稳定性、EA 占用资源、密钥泄露风险都得自己压住,外汇和贵金属本身高波动,链路断了可能直接错过信号。 示例文件 Discord_examples.mq5 要丢进 MQL5/Experts/ 才能被 MT5 加载,跑之前先在本机测通网络延迟。后续若加语音播报或 LSTM 过滤警报,也只是把这条通道变得更聪明,根基还是这套自动化对接。 真要验证值不值,开 MT5 挂上 EA,拿模拟账户跑三天,看漏报和误报哪个先让你烦,再决定要不要上实盘。

让小布替你收这些告警
小布盯盘已内置多通道提醒逻辑,打开对应品种页即可看到执行与异动推送,不必自己从头搭 Discord 机器人,你专注策略本身。

常见问题

平台出于安全默认阻断外部请求,只有把目标域名加进允许列表,EA 才能向外发消息,否则 Webhook 调用会直接失败。
旧版域名已逐步弃用,新创建或迁移的 webhook 应使用 discord.com,否则可能出现能复制但发不出消息的情况。
如果你只想要执行与行情告警推送,小布盯盘的内置提醒可覆盖大部分场景,省去维护 EA 与服务器的成本;深度自定义指令仍需自建。
引号与换行等字符会破坏 JSON 结构,Discord 接口收不到合法请求,消息发送静默失败,排查时容易误判为网络问题。
可加粗品种与盈亏、用代码块隔离参数,手机上扫一眼就能抓到重点,比纯文本更适合快节奏盯盘。