创建 MQL5-Telegram 集成 EA 交易 (第二部分):从 MQL5 发送信号到 Telegram·综合运用
📘

创建 MQL5-Telegram 集成 EA 交易 (第二部分):从 MQL5 发送信号到 Telegram·综合运用

第 3/3 篇

◍ 用 emoji 给 EA 消息加辨识度

MT5 里给推送消息或日志加 emoji,本质就是字符串拼接,unicode 直接写进双引号即可。下面这段注释代码列了几类常见场景:初始化提示用 🚀,买卖信号用 📈 / 📉,余额通知用 💰,开单用 🔔,止损止盈调整用 🔄,每日绩效用 📅,平仓用 ❌。 账户状态那组用了十六进制转义 \xF680(火箭)、\xF4CA(钱袋),拼成多行消息后还要过一遍 UrlEncode 才能塞进 WebRequest 的 URL 参数,否则中文和符号会乱码。 实际下单逻辑不需要 emoji 参与。取前一根 K 线的开盘价与收盘价:若 Open<Close 判为看涨信号倾向买,否则倾向卖。买单价取 Ask,止损设在 Bid 下方 1000 点,止盈在 Bid 上方 1000 点,仓位固定 0.01 手。外汇和贵金属杠杆高,1000 点止损在部分品种上可能直接扫掉大部分账户净值,参数须按品种重算。 开 MT5 新建 EA,把 Buy 分支里的 1000*_Point 改成你品种的 ATR(14) 倍数,回测一下 EURUSD 在 H1 上近 3 个月该逻辑触发频率,再决定是否接消息推送。

MQL5 / C++
class=class="str">"cmt">////--- Simple Notification with Emoji:
class=class="str">"cmt">//class="type">class="kw">string msg = "🚀 EA INITIALIZED ON CHART " + _Symbol + " 🚀";
class=class="str">"cmt">////--- Buy/Sell Signal with Emoji:
class=class="str">"cmt">//class="type">class="kw">string msg = "📈 BUY SIGNAL GENERATED ON " + _Symbol + " 📈";
class=class="str">"cmt">//class="type">class="kw">string msg = "📉 SELL SIGNAL GENERATED ON " + _Symbol + " 📉";
class=class="str">"cmt">////--- Account Balance Notification:
class=class="str">"cmt">//class="type">class="kw">double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
class=class="str">"cmt">//class="type">class="kw">string msg = "💰 Account Balance: $" + DoubleToString(accountBalance, class="num">2) + " 💰";
class=class="str">"cmt">////--- Trade Opened Notification:
class=class="str">"cmt">//class="type">class="kw">string orderType = "BUY";   // or "SELL"
class=class="str">"cmt">//class="type">class="kw">double lotSize = class="num">0.1;   // Example lot size
class=class="str">"cmt">//class="type">class="kw">double price = class="num">1.12345;   // Example price
class=class="str">"cmt">//class="type">class="kw">string msg = "🔔 " + orderType + " order opened on " + _Symbol + "; Lot size: " + DoubleToString(lotSize, class="num">2) + "; Price: " + DoubleToString(price, class="num">5) + " 🔔";
class=class="str">"cmt">////--- Stop Loss and Take Profit Update:
class=class="str">"cmt">//class="type">class="kw">double stopLoss = class="num">1.12000;   // Example stop loss
class=class="str">"cmt">//class="type">class="kw">double takeProfit = class="num">1.13000;   // Example take profit
class=class="str">"cmt">//class="type">class="kw">string msg = "🔄 Stop Loss and Take Profit Updated on " + _Symbol + "; Stop Loss: " + DoubleToString(stopLoss, class="num">5) + "; Take Profit: " + DoubleToString(takeProfit, class="num">5) + " 🔄";
class=class="str">"cmt">////--- Daily Performance Summary:
class=class="str">"cmt">//class="type">class="kw">double profitToday = class="num">150.00;   // Example profit for the day
class=class="str">"cmt">//class="type">class="kw">string msg = "📅 Daily Performance Summary 📅; Symbol: " + _Symbol + "; Profit Today: $" + DoubleToString(profitToday, class="num">2);
class=class="str">"cmt">////--- Trade Closed Notification:
class=class="str">"cmt">//class="type">class="kw">string orderType = "BUY";   // or "SELL"
class=class="str">"cmt">//class="type">class="kw">double profit = class="num">50.00;   // Example profit
class=class="str">"cmt">//class="type">class="kw">string msg = "❌ " + orderType + " trade closed on " + _Symbol + "; Profit: $" + DoubleToString(profit, class="num">2) + " ❌";

class=class="str">"cmt">//   ////--- Account Status Update:
class=class="str">"cmt">//   class="type">class="kw">double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
class=class="str">"cmt">//   class="type">class="kw">double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
class=class="str">"cmt">//   class="type">class="kw">string msg = "\xF680 EA INITIALIZED ON CHART " + _Symbol + "\xF680"
class=class="str">"cmt">//               +"\n\xF4CA Account Status \xF4CA"
class=class="str">"cmt">//               +"\nEquity: $"
class=class="str">"cmt">//               +DoubleToString(accountEquity,class="num">2)
class=class="str">"cmt">//               +"\nFree Margin: $"
class=class="str">"cmt">//               +DoubleToString(accountFreeMargin,class="num">2);
class=class="str">"cmt">//   
class=class="str">"cmt">//   class="type">class="kw">string encloded_msg = UrlEncode(msg);
class=class="str">"cmt">//   msg = encloded_msg;
   class="type">class="kw">double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   class="type">class="kw">double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);

   class="type">class="kw">double Price_Open = iOpen(_Symbol,_Period,class="num">1);
   class="type">class="kw">double Price_Close = iClose(_Symbol,_Period,class="num">1);

   class="type">bool isBuySignal = Price_Open < Price_Close;
   class="type">bool isSellSignal = Price_Open >= Price_Close;

   class="type">class="kw">double lotSize = class="num">0, openPrice = class="num">0,stopLoss = class="num">0,takeProfit = class="num">0;

   if (isBuySignal == true){
      lotSize = class="num">0.01;
      openPrice = Ask;
      stopLoss = Bid-class="num">1000*_Point;
      takeProfit = Bid+class="num">1000*_Point;
      obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
   }
   else if (isSellSignal == true){

「把开仓细节推到 Telegram 的写法」

这段逻辑紧接信号判断之后,先把卖单参数钉死:固定 0.01 手,以当前 Bid 入场,止损放在 Ask 上方 1000 点、止盈放在 Ask 下方 1000 点,再调用 CTrade 的 Sell 方法发出市价卖单。外汇与贵金属杠杆高,1000 点止损在 XAUUSD 这类品种上可能对应近百美元浮亏,实盘前务必按账户净值重算手数。 下单后程序拼了一段带 emoji 的状态文本,用 ShortToString(0xF4B0) 把钱袋符号塞进字符串,再附上开仓价、K线时间、服务器时间与本地时间、手数、止损止盈。注意原文里 \xF412\Time 少了个引号,直接编译会报错,要补成 "\n\xF412\Time = "。 文本通过 UrlEncode 编码后,拼进 https://api.telegram.org/bot{token}/sendmessage 的 URL,用 WebRequest 以 POST 发出,超时设 10000 毫秒。若返回 200 代表推送成功;返回 -1 且 GetLastError() 为 4014,说明 MT5 终端没把 Telegram 域名加入允许列表,需要在「选项-EA交易-允许 WebRequest 的 URL」里手动添加。

MQL5 / C++
  lotSize = class="num">0.01;
  openPrice = Bid;
  stopLoss = Ask+class="num">1000*_Point;
  takeProfit = Ask-class="num">1000*_Point;
  obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
 }

 class="type">class="kw">string position_type = isBuySignal ? "Buy" : "Sell";

 class="type">class="kw">ushort MONEYBAG = 0xF4B0;
 class="type">class="kw">string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
 class="type">class="kw">string msg = "\xF680 OPENED "+position_type+" POSITION."
       +"\n===================="
       +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
       +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,class="num">0),TIME_SECONDS)
       +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
       +"\n\xF525 Lotsize = "+DoubleToString(lotSize,class="num">2)
       +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
       +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
       +"\n_________________________"
       +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
       +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
       ;
 class="type">class="kw">string encloded_msg = UrlEncode(msg);
 msg = encloded_msg;

 class=class="str">"cmt">// Construct the URL for the Telegram API request to send a message
 class=class="str">"cmt">// Format: https://api.telegram.org/bot{HTTP_API_TOKEN}/sendmessage?chat_id={CHAT_ID}&text={MESSAGE_TEXT}
 class="kw">const class="type">class="kw">string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
       "&text=" + msg;
 class=class="str">"cmt">// Send the web request to the Telegram API
 class="type">int send_res = WebRequest("POST", url, "", class="num">10000, 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 ", TG_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) {

双均线句柄与缓冲区的初始化回收

EA 启动阶段用 iMA 拉两条均线:快线周期 20、模式 MODE_EMA,慢线周期 50、模式 MODE_SMA,均取收盘价 PRICE_CLOSE。句柄拿不到就直接 INIT_FAILED,避免在后续 OnTick 里用无效句柄崩逻辑。 两个缓冲区 bufferFast、bufferSlow 都用 ArraySetAsSeries(...,true) 改成时间序列,索引 0 即最新柱。magic_no 写死 1234567890 并塞进交易对象,实盘里改这个数就能隔离不同 EA 的订单。 退出时务必 IndicatorRelease 释放快慢线句柄,再 ArrayFree 清缓冲区,否则 MT5 会残留指标实例占用资源。OnTick 里每次先 CopyBuffer 取最近 3 根数据,任一少于 3 就 Print 并返回,防止数据不齐误判。 用 iBars 比对 prevBars 与 currBars,相等就直接 return,等于把同根柱的重复 tick 挡在策略计算之外,回测里这根判断能少跑不少无效分支。

MQL5 / C++
class="type">int handleFast = INVALID_HANDLE; class=class="str">"cmt">// -class="num">1
class="type">int handleSlow = INVALID_HANDLE; class=class="str">"cmt">// -class="num">1
class="type">class="kw">double bufferFast[];
class="type">class="kw">double bufferSlow[];
class="type">long magic_no = class="num">1234567890;
  handleFast = iMA(Symbol(),Period(),class="num">20,class="num">0,MODE_EMA,PRICE_CLOSE);
  if (handleFast == INVALID_HANDLE){
    Print("UNABLE TO CREATE FAST MA INDICATOR HANDLE. REVERTING NOW!");
    class="kw">return (INIT_FAILED);
  }
  handleSlow = iMA(Symbol(),Period(),class="num">50,class="num">0,MODE_SMA,PRICE_CLOSE);
  if (handleSlow == INVALID_HANDLE){
    Print("UNABLE TO CREATE FAST MA INDICATOR HANDLE. REVERTING NOW!");
    class="kw">return (INIT_FAILED);
  }
  Print("HANDLE FAST MA = ",handleFast);
  Print("HANDLE SLOW MA = ",handleSlow);
  ArraySetAsSeries(bufferFast,true);
  ArraySetAsSeries(bufferSlow,true);
  obj_Trade.SetExpertMagicNumber(magic_no);
class="type">void OnDeinit(class="kw">const class="type">int reason) {
  IndicatorRelease(handleFast);
  IndicatorRelease(handleSlow);
  ArrayFree(bufferFast);
  ArrayFree(bufferSlow);
}
class="type">void OnTick() {
  ...
}
  if (CopyBuffer(handleFast,class="num">0,class="num">0,class="num">3,bufferFast) < class="num">3){
    Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING");
    class="kw">return;
  }
  if (CopyBuffer(handleSlow,class="num">0,class="num">0,class="num">3,bufferSlow) < class="num">3){
    Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING");
    class="kw">return;
  }
  class="type">int currBars = iBars(_Symbol,_Period);
  class="kw">static class="type">int prevBars = currBars;
  if (prevBars == currBars) class="kw">return;
  prevBars = currBars;

◍ 快线金叉慢线时的平仓与开多逻辑

当快速均线前一根高于慢线、再前一根低于或等于慢线时,判定为金叉信号。此时先倒序遍历所有持仓,把当前符号且 magic 匹配的卖单平掉,再以市价 Ask 开 0.01 手多单,止损放在 Bid 下方 1000 点、止盈放在 Bid 上方 1000 点。 平仓循环从 PositionsTotal()-1 走到 0,用 PositionGetTicket 取ticket后再 PositionSelectByTicket 选中,避免漏掉中途变动的持仓序号。外汇与贵金属杠杆高,1000 点止损在 XAUUSD 上约等于 10 美元/0.01 手,实盘前请在 MT5 策略测试器按品种点值核对。 开仓后代码没有真发 Telegram,只是 Print 一句提示并拼了一段带 emoji 的字符串(如 0xF4B0 转成的钱袋符号、0xF680 等)。想接真实推送,把 Print 换成 WebRequest 调自己 bot 的 sendMessage 即可,本地时间用 TimeLocal 拼进消息体。

MQL5 / C++
  class="type">class="kw">double fastMA1 = bufferFast[class="num">1];
  class="type">class="kw">double fastMA2 = bufferFast[class="num">2];
  
  class="type">class="kw">double slowMA1 = bufferSlow[class="num">1];
  class="type">class="kw">double slowMA2 = bufferSlow[class="num">2];
  if (fastMA1 > slowMA1 && fastMA2 <= slowMA2){
      for (class="type">int i = PositionsTotal()-class="num">1; i>= class="num">0; i--){
          class="type">class="kw">ulong ticket = PositionGetTicket(i);
          if (ticket > class="num">0){
              if (PositionSelectByTicket(ticket)){
                  if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
                      PositionGetInteger(POSITION_MAGIC) == magic_no){
                      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
                       obj_Trade.PositionClose(ticket);
                      }
                  }
              }
          }
      }
      class="type">class="kw">double lotSize = class="num">0.01;
      class="type">class="kw">double openPrice = Ask;
      class="type">class="kw">double stopLoss = Bid-class="num">1000*_Point;
      class="type">class="kw">double takeProfit = Bid+class="num">1000*_Point;
      obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
   }
      class=class="str">"cmt">// BUY POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("BUY POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
      class="type">class="kw">ushort MONEYBAG = 0xF4B0;
      class="type">class="kw">string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
      class="type">class="kw">string msg =  "\xF680 Opened Buy Position."
          +"\n===================="
          +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
          +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,class="num">0),TIME_SECONDS)
          +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
          +"\n\xF525 Lotsize = "+DoubleToString(lotSize,class="num">2)
          +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
          +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
          +"\n_________________________"
          +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)

「死叉触发后的平仓与反手卖单逻辑」

当快线跌破慢线(fastMA1 < slowMA1)且另一组快线仍不低于慢线(fastMA2 >= slowMA2)时,EA 进入卖单分支。它先倒序遍历所有持仓,用 PositionSelectByTicket 锁定当前符号且 magic 匹配的买单,并调用 PositionClose 平掉,避免双向持仓。 平仓后直接以 0.01 手开空:openPrice 取 Bid,stopLoss 设为 Ask+1000*_Point,takeProfit 设为 Ask-1000*_Point。在 XAUUSD 这类 _Point=0.01 的品种上,止损距入场约 10 美元,止盈同距,属对称盈亏比结构,外汇与贵金属杠杆高,实盘前需在 MT5 策略测试器按品种点值重算。 开仓后 Print 提示向 Telegram 发信,并用 ChartIndicatorAdd(0,0,handleFast/Slow) 把两条均线自动加载到主图。OnTick 里每次Tick先 CopyBuffer 取快慢线各 3 根缓冲,若任一返回值小于 3 就 Print 报错并 return,防止数据不足时误判交叉。

MQL5 / C++
      +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
      ;
      class="type">class="kw">string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;
   else if (fastMA1 < slowMA1 && fastMA2 >= slowMA2){
      for (class="type">int i = PositionsTotal()-class="num">1; i>= class="num">0; i--){
         class="type">class="kw">ulong ticket = PositionGetTicket(i);
         if (ticket > class="num">0){
            if (PositionSelectByTicket(ticket)){
               if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
                  PositionGetInteger(POSITION_MAGIC) == magic_no){
                  if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
                     obj_Trade.PositionClose(ticket);
                  }
               }
            }
         }
      }
      class="type">class="kw">double lotSize = class="num">0.01;
      class="type">class="kw">double openPrice = Bid;
      class="type">class="kw">double stopLoss = Ask+class="num">1000*_Point;
      class="type">class="kw">double takeProfit = Ask-class="num">1000*_Point;
      obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit);
      
      class=class="str">"cmt">// SELL POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
      Print("SELL POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
  class=class="str">"cmt">//--- Add indicators to the chart automatically
  ChartIndicatorAdd(class="num">0,class="num">0,handleFast);
  ChartIndicatorAdd(class="num">0,class="num">0,handleSlow);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick() {
  class=class="str">"cmt">// Code to execute on every tick event
  
  if (CopyBuffer(handleFast,class="num">0,class="num">0,class="num">3,bufferFast) < class="num">3){
      Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING");
      class="kw">return;
  }
  if (CopyBuffer(handleSlow,class="num">0,class="num">0,class="num">3,bufferSlow) < class="num">3){
      Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING");
      class="kw">return;
  }
  
  class="type">class="kw">double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
  class="type">class="kw">double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);

金叉触发时平空反手多的代码骨架

下面这段逻辑跑在 MT5 的 OnTick 里,核心是先判定 K 线数量是否变化,避免同一根 bar 内重复触发。currBars 取当前品种周期的 bars 总数,prevBars 用 static 保存上一次的值,两者相等就直接 return,只有新 bar 进来才继续往下走。 新 bar 确认后,取快线和慢线前两根(索引 1、2)的缓冲区值。若 fastMA1 > slowMA1 且 fastMA2 <= slowMA2,即判定为快线从下方上穿慢线(金叉)。此时先倒序遍历 PositionsTotal,用 PositionSelectByTicket 匹配同品种同 magic 的空单,命中就 PositionClose 平掉。 平仓后直接以 Ask 市价开 0.01 手多单,止损设在 Bid - 1000*_Point,止盈 Bid + 1000*_Point(对 XAUUSD 这类点值约 0.1 美元的点差环境,就是 1000 点宽幅,实战需按品种重算)。开仓后拼一段带 emoji 的字符串准备发 Telegram,其中 MONEYBAG 用 0xF4B0 转码,时间取自 iTime 和 TimeCurrent。外汇与贵金属杠杆高,这段代码未做风控过滤,直接上实盘可能触发巨额浮亏。 别把 1000 点止损当通用值 XAUUSD 的 _Point 是 0.01,1000*_Point = 10 美元;EURUSD 的 _Point 是 0.00001,同样写法变成 1000 点 = 0.1 美元。复制前先 Print(_Point) 确认品种再调倍数。

MQL5 / C++
class="type">int currBars = iBars(_Symbol,_Period);
class="kw">static class="type">int prevBars = currBars;
if (prevBars == currBars) class="kw">return;
prevBars = currBars;

class="type">class="kw">double fastMA1 = bufferFast[class="num">1];
class="type">class="kw">double fastMA2 = bufferFast[class="num">2];

class="type">class="kw">double slowMA1 = bufferSlow[class="num">1];
class="type">class="kw">double slowMA2 = bufferSlow[class="num">2];

if (fastMA1 > slowMA1 && fastMA2 <= slowMA2){
    for (class="type">int i = PositionsTotal()-class="num">1; i>= class="num">0; i--){
        class="type">class="kw">ulong ticket = PositionGetTicket(i);
        if (ticket > class="num">0){
            if (PositionSelectByTicket(ticket)){
                if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
                    PositionGetInteger(POSITION_MAGIC) == magic_no){
                    if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
                        obj_Trade.PositionClose(ticket);
                    }
                }
            }
        }
    }
    class="type">class="kw">double lotSize = class="num">0.01;
    class="type">class="kw">double openPrice = Ask;
    class="type">class="kw">double stopLoss = Bid-class="num">1000*_Point;
    class="type">class="kw">double takeProfit = Bid+class="num">1000*_Point;
    obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit);

    class=class="str">"cmt">// BUY POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
    Print("BUY POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");

    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">ushort MONEYBAG = 0xF4B0;
    class="type">class="kw">string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
    class="type">class="kw">string msg =  "\xF680 Opened Buy Position."
                +"\n===================="
                +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
                +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,class="num">0),TIME_SECONDS)
                +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)

◍ 把信号推到 Telegram 的发送与容错

上面那段字符串拼接把手数、止损、止盈和本地时间都塞进 msg,再用 UrlEncode 处理成 URL 安全格式,这一步不能省,否则中文符号或空格会让 WebRequest 直接废掉。 实际发送走的是 POST 到 TG_API_URL + /bot + token + /sendmessage?chat_id= + chatID + &text= 的拼接地址,超时设了 10000 毫秒。注意 MT5 终端默认禁止外部 URL,必须在「选项 → 智能交易系统 → 允许 WebRequest 的 URL 列表」里手动加入 Telegram API 域名,否则返回 -1 且 GetLastError() 报 4014。 返回码判断很直接:200 打印成功;-1 且错误 4014 提示加白名单,其他情况打印 UNABLE TO SEND;既非 200 也非 -1 则把意外响应码和错误码一并打出,方便排查。 下方 else if 分支处理 FastMA1 < SlowMA1 且 FastMA2 >= SlowMA2 的反向条件,用 PositionsTotal()-1 倒序遍历持仓,按 ticket 选中后比对 _Symbol 与 magic_no,为后续平仓或反手留好入口。外汇与贵金属波动剧烈,这类自动通知只是辅助,信号失效时仍可能快速回撤,需自行评估风险。

MQL5 / C++
      +"\n\xF525 Lotsize = "+DoubleToString(lotSize,class="num">2)
      +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
      +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
      +"\n_________________________ "
      +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
      +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
      ;
      class="type">class="kw">string encloded_msg = UrlEncode(msg);
      msg = encloded_msg;

      class="kw">const class="type">class="kw">string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
         "&text=" + msg;

      class=class="str">"cmt">// Send the web request to the Telegram API
      class="type">int send_res = WebRequest("POST", url, "", class="num">10000, 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 ", TG_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());
      }
      
   }
   else if (fastMA1 < slowMA1 && fastMA2 >= slowMA2){
      for (class="type">int i = PositionsTotal()-class="num">1; i>= class="num">0; i--){
         class="type">class="kw">ulong ticket = PositionGetTicket(i);
         if (ticket > class="num">0){
            if (PositionSelectByTicket(ticket)){
               if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
                   PositionGetInteger(POSITION_MAGIC) == magic_no){

「开空后把成交细节推到 Telegram」

当持仓遍历发现已有多单时,这段代码会先平掉多单,紧接着以市价 Bid 开一手 0.01 的空单,止损放在 Ask 上方 1000 点、止盈放在 Ask 下方 1000 点。外汇与贵金属杠杆高,1000 点止损在 XAUUSD 上约对应 10 美元/0.01 手,实际滑点可能扩大亏损,下单前要在 MT5 里核对品种 _Point 与 _Digits。 开仓成功后,程序用 char 数组 data/res 和 string resHeaders 承接 WebRequest 的返回,并把成交信息拼成带 emoji 的字符串:价格取 openPrice、时间取 iTime(_Symbol,_Period,0) 与 TimeCurrent()、手数固定 0.01、止损止盈按 _Digits 格式化。 msg 先经 UrlEncode 编码,再拼进 TG_API_URL/bot{token}/sendmessage?chat_id={id}&text= 的完整 URL,最后用 WebRequest("POST", url, "", 10000, data, res, resHeaders) 发出。把 botTkn、chatID、TG_API_URL 换成你自己的常量,编译后开 EA 测试,手机大概率能在 1 秒内收到那条带钱袋 emoji 的空单通知。

MQL5 / C++
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
      obj_Trade.PositionClose(ticket);
      }
    }
  }
}
class="type">class="kw">double lotSize = class="num">0.01;
class="type">class="kw">double openPrice = Bid;
class="type">class="kw">double stopLoss = Ask+class="num">1000*_Point;
class="type">class="kw">double takeProfit = Ask-class="num">1000*_Point;
obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit);

class=class="str">"cmt">// SELL POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM
Print("SELL POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");

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">ushort MONEYBAG = 0xF4B0;
class="type">class="kw">string MONEYBAG_Emoji_code = ShortToString(MONEYBAG);
class="type">class="kw">string msg =  "\xF680 Opened Sell Position."
      +"\n===================="
      +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits)
      +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,class="num">0),TIME_SECONDS)
      +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS)
      +"\n\xF525 Lotsize = "+DoubleToString(lotSize,class="num">2)
      +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits)
      +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits)
      +"\n_________________________"
      +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE)
      +" @ "+TimeToString(TimeLocal(),TIME_SECONDS)
      ;
class="type">class="kw">string encloded_msg = UrlEncode(msg);
msg = encloded_msg;

class="kw">const class="type">class="kw">string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
   "&text=" + msg;

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

WebRequest 发完 Telegram 后的三种回执处理

EA 调 Telegram 接口不能只管把请求甩出去,回执状态必须分情况接住,否则线上跑的时候你根本不知道消息到底发出去没。 下面这段是发送函数末尾的状态判断,覆盖了正常、终端拦截、异常三类回执。HTTP 200 代表推送成功,终端会打印 SUCCESSFULLY;若返回 -1 且 GetLastError() 等于 4014,说明 MT5 没把 Telegram 的 API 域名加进允许列表,必须去终端选项里补白名单。 其余非 200 且非 -1 的情况属于意外响应,直接把 send_res 和错误码一起打印出来,方便你对照接口文档排错。外汇与贵金属行情波动剧烈,这类通知失败可能让你漏掉关键信号,上线前务必在策略测试器里实跑一遍确认回执逻辑通畅。

MQL5 / C++
  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 ", TG_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());
  }

◍ 用 1 分钟图验证信号推送链路

要确认交易终端到 Telegram 的集成是否真的通了,最省事的办法是把初始化时的测试逻辑注释掉,避免一启动就刷出一堆假信号。然后把图表切到 M1 周期,把指标参数改成 5 和 10,短周期组合出信号的速度明显更快,方便当场观察整条链路。 我们在 M1 上跑出来的里程碑很直接:终端侧先后弹出卖出、买入确认,Telegram 群聊里也同步收到了对应的卖、买信号文本。外汇与贵金属品种在此类短周期下假信号概率偏高,仅为验证集成可用,不构成任何方向建议。 从终端和聊天群的四条确认记录能看出,集成已经跑通——一次扫描确认后,信号细节被打包成单条消息从终端发到 Telegram。至此目标达成,接下来就是拿真实品种去压测稳定性。

「把这条线请下神坛」

把移动平均线交叉从信号神坛上请下来,它只是集成 EA 里的一层触发器:侦测到金叉或死叉,经指标句柄校验后由 MQL5 推到 Telegram,离开终端也能收到提醒。本次附带的 TELEGRAM_MQL5_SIGNALS_PART2.mq5 为 41.63 KB、编译后 ex5 为 47.14 KB,读者可直接拖进 MT5 覆盖原 EA 验证推送链路。 下一篇转向图表截图直发,文本信号叠视觉背景能让盘感更完整,但外汇与贵金属自带高杠杆风险,任何自动推送都不替代人工复核。 线可以接好,神坛不必留;你点开 MT5 跑一遍,比读十次结论更接近真相。

常见问题

在发送文本前拼接 emoji 前缀(如 📈 开多、📉 开空、⚠️ 风控),按消息类型分类加标识,手机通知里扫一眼就能分辨。
用 StringFormat 拼好包含 _Symbol、开仓手数、止盈止损的字符串,再走 WebRequest 推送到 bot 接口即可,别只发“已开仓”四个字。
可以。小布能聚合多品种 EA 推送的 Telegram 消息并按 emoji 和品种归类,异动时直接给你摘要,省去手动翻聊天记录。
句柄在 OnInit 用 iMA 创建、OnDeinit 用 IndicatorRelease 回收,缓冲区用 CopyBuffer 取数;不释放会漏内存、重复调用还容易报句柄错误。
先遍历持仓用 OrderSelect+OrderClose 平掉空单,确认无反向仓后再用 OrderSend 开多;金叉死叉都套同一套“先平后开”骨架,加个持仓方向判断就不会反复发单。