创建 MQL5-Telegram 集成 EA 交易(第 5 部分):从 Telegram 向 MQL5 发送命令并接收实时响应·综合运用
📡

创建 MQL5-Telegram 集成 EA 交易(第 5 部分):从 Telegram 向 MQL5 发送命令并接收实时响应·综合运用

(3/3)· 收官篇:把 Telegram 命令流接进 MQL5 解码层,让机器人不再只会单向播报

进阶 第 3/3 篇
不少交易者把 Telegram 机器人只当成信号喇叭,EA 发什么你看什么。真要隔着聊天框下指令,多数人卡在 JSON 更新抓取和命令解析这一步,机器人收了消息却回不了话。

◍ 聊天节点里的时间与未处理标记

这段 MT5 代码处理的是多聊天的消息写入逻辑:找不到对应节点就新建,找到就覆盖最新一条。新建时把 member_state 置 0、done 置 false,意味着这条消息进入待处理队列,后续轮询才会去消费。 关键在 TimeLocal() 的两次调用——新建和已存在分支都刷新 member_time,相当于用本地时钟给聊天做心跳。若你做跨会话的超时清理,直接比这个字段和当前 TimeLocal() 差值即可,不必另起定时器。 注意 done=false 在两种分支里都出现,说明无论新旧聊天,新消息默认都不算处理完。跑实盘前建议在 OnTimer 里打印 member_new_one.message_text 和 done,确认没有被某处提前置 true 吞掉。外汇与贵金属行情下这类消息遗漏可能造成信号延迟,属高风险环节。

MQL5 / C++
   chat.member_time=TimeLocal(); class=class="str">"cmt">//--- Set the current time for the chat
   chat.member_state=class="num">0; class=class="str">"cmt">//--- Initialize the chat state
   chat.member_new_one.message_text=obj_msg.message_text; class=class="str">"cmt">//--- Set the new message text
   chat.member_new_one.done=false; class=class="str">"cmt">//--- Mark the new message as not processed
   }
   class=class="str">"cmt">//--- If the chat is found, update the chat message
   else{
    Class_Chat *chat=member_chats.GetNodeAtIndex(index);
    chat.member_time=TimeLocal(); class=class="str">"cmt">//--- Update the chat time
    chat.member_new_one.message_text=obj_msg.message_text; class=class="str">"cmt">//--- Update the message text
    chat.member_new_one.done=false; class=class="str">"cmt">//--- Mark the new message as not processed
   }
     }
   class=class="str">"cmt">//--- After the first update, set the flag to false
   member_first_remove=false;
  class=class="str">"cmt">//--- Return the result of the POST request
  class="kw">return(res);

让 EA 在 Telegram 里听懂人话并回话

拿到聊天更新后,核心动作是遍历 member_chats,用索引 i 取出每个聊天对象,检查 member_new_one 里的完成标志。标志为 false 就置 true 并提取文本,避免同一条消息被重复处理——这是实盘机器人最容易漏写的防护。 文本到手后先匹配 "Hello" 做冒烟测试:命中就调 sendMessageToTelegram 回确认,证明 MQL5 端到 Telegram 的回路通了。该函数拼 base URL + getTrimmedToken() + "/sendMessage",参数带 chat_id、UrlEncode 后的 text、parse_mode=HTML、disable_web_page_preview=true,返回响应码判断成败。 想脱离命令行就上自定义键盘。customReplyKeyboardMarkup(keyboard, resize, one_time) 把 JSON 布局用 UrlEncode 编码,布尔值经 convertBoolToString 转字符串;hideCustomReplyKeyboard 回 {"hide_keyboard": true},forceReplyCustomKeyboard 回 {"force_reply": true}。用 "[["Hello 1"],["Hello 2"],["Hello 3"]]" 是竖排三键,换 "[["Hello 1","Hello 2","Hello 3"]]" 即横排。 命令路由很直接:/start、/help 发欢迎信带主键盘;/name 用 __FILE__ 宏回传 EA 文件名;/buy 建 CTrade 对象,SymbolInfoDouble 取买卖价,开 0.01 手、SL=买价-300点、TP=买价+300点,再 ResultOrder 取单号、PositionGetInteger 拉持仓数据回给用户。外汇与贵金属杠杆高,实盘前务必在模拟盘验这套下单逻辑。 /html 格式化用 <s> 删除线、<pre> 代码块、<u><i> 下划斜体、<b> 粗体,/join 命令就靠它做带超链的 MQL5 社区邀请。状态机靠聊天状态数字切换:"more" 推新键盘,上箭头重置为 0 回主菜单,手枪表情在 2 与 1 间切,取消表情清键盘回 /start。 截图命令把状态置 10,先问品种再问周期,品种或周期非法就回提示并重发键盘。品种数组与周期数组驱动自定义键盘,跑起来后 MT5 日志里能看到用户每步点击都被正确路由。

「消息轮询与回传的骨架代码」

截图发送流程在 EA 里已经跑通,但命令合法性校验只是第一道关。要让用户只能发出有效指令,得在消息处理层把脏输入挡在门外,否则 Telegram bot 可能把异常文本原样丢回 API 造成超时或限流。 下面这段 Class_Bot_EA::ProcessMessages 的骨架,核心是遍历 member_chats 里所有会话,用 done 标记避免同一条消息重复处理。注意 chat.member_new_one.done 置 true 后立即取 message_text,但命令分支(如 text=="Hello")实际被放在循环外,编译逻辑有错位,开 MT5 前需把 if(text=="Hello") 整段移回循环体内。

MQL5 / C++
class="type">void Class_Bot_EA::ProcessMessages(class="type">void){
class=class="str">"cmt">//---
}
  class=class="str">"cmt">//--- Loop through all chats
  for(class="type">int i=class="num">0; i<member_chats.Total(); i++){
      Class_Chat *chat=member_chats.GetNodeAtIndex(i); class=class="str">"cmt">//--- Get the current chat
      if(!chat.member_new_one.done){ class=class="str">"cmt">//--- Check if the message has not been processed yet
          chat.member_new_one.done=true; class=class="str">"cmt">//--- Mark the message as processed
          class="type">class="kw">string text=chat.member_new_one.message_text; class=class="str">"cmt">//--- Get the message text
          class=class="str">"cmt">//---
      }
  }
          class=class="str">"cmt">//--- Process the command based on the message text

          class=class="str">"cmt">//--- If the message is "Hello"
          if(text=="Hello"){
              class="type">class="kw">string message="Hello world! You just sent a &class="macro">#x27;Hello&class="macro">#x27; text to MQL5 and has been processed successfully.";

          }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Send a message to Telegram                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int sendMessageToTelegram(const class="type">long chat_id,const class="type">class="kw">string text,
                const class="type">class="kw">string reply_markup=NULL){
    class="type">class="kw">string output; class=class="str">"cmt">//--- Variable to store the response from the request
    class="type">class="kw">string url=TELEGRAM_BASE_URL+"/bot"+getTrimmedToken(InpToken)+"/sendMessage"; class=class="str">"cmt">//--- Construct the URL for the Telegram API request
    class=class="str">"cmt">//--- Construct parameters for the API request
    class="type">class="kw">string params="chat_id="+IntegerToString(chat_id)+"&text="+UrlEncode(text); class=class="str">"cmt">//--- Set chat ID and message text
    if(reply_markup!=NULL){ class=class="str">"cmt">//--- If a reply markup is provided
        params+="&reply_markup="+reply_markup; class=class="str">"cmt">//--- Add reply markup to parameters
    }
    params+="&parse_mode=HTML"; class=class="str">"cmt">//--- Set parse mode to HTML(can also be Markdown)
    params+="&disable_web_page_preview=true"; class=class="str">"cmt">//--- Disable web page preview in the message
    class=class="str">"cmt">//--- Send a POST request to the Telegram API
    class="type">int res=postRequest(output,url,params,WEB_TIMEOUT); class=class="str">"cmt">//--- Call postRequest to send the message
    class="kw">return(res); class=class="str">"cmt">//--- Return the response code from the request
}
              class=class="str">"cmt">//--- Send the response message 
              sendMessageToTelegram(chat.member_id,message,NULL);
              class="kw">continue;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a custom reply keyboard markup for Telegram                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string customReplyKeyboardMarkup(const class="type">class="kw">string keyboard, const class="type">bool resize,
sendMessageToTelegram 这个函数拼 URL 时用 TELEGRAM_BASE_URL 加 token 和 /sendMessage,params 里写死 parse_mode=HTML 且 disable_web_page_preview=true,意味着回传图表截图链接时不会在客户端展开预览,减少带宽占用。WEB_TIMEOUT 是 postRequest 的硬上限,外汇与贵金属行情跳动快,bot 通知延迟若超过该值可能返回非 0 错误码,实盘前建议在策略测试器里用不同超时值压测。 下一节会专门做完整回测与局限性排查,眼下先把这段代码的循环作用域修顺,否则多聊天会话下只有最后一个 chat 的 text 能被匹配。

MQL5 / C++
class="type">void Class_Bot_EA::ProcessMessages(class="type">void){
class=class="str">"cmt">//---
}
  class=class="str">"cmt">//--- Loop through all chats
  for(class="type">int i=class="num">0; i<member_chats.Total(); i++){
      Class_Chat *chat=member_chats.GetNodeAtIndex(i); class=class="str">"cmt">//--- Get the current chat
      if(!chat.member_new_one.done){ class=class="str">"cmt">//--- Check if the message has not been processed yet
          chat.member_new_one.done=true; class=class="str">"cmt">//--- Mark the message as processed
          class="type">class="kw">string text=chat.member_new_one.message_text; class=class="str">"cmt">//--- Get the message text
          class=class="str">"cmt">//---
      }
  }
          class=class="str">"cmt">//--- Process the command based on the message text

          class=class="str">"cmt">//--- If the message is "Hello"
          if(text=="Hello"){
              class="type">class="kw">string message="Hello world! You just sent a &class="macro">#x27;Hello&class="macro">#x27; text to MQL5 and has been processed successfully.";

          }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Send a message to Telegram                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int sendMessageToTelegram(const class="type">long chat_id,const class="type">class="kw">string text,
                const class="type">class="kw">string reply_markup=NULL){
    class="type">class="kw">string output; class=class="str">"cmt">//--- Variable to store the response from the request
    class="type">class="kw">string url=TELEGRAM_BASE_URL+"/bot"+getTrimmedToken(InpToken)+"/sendMessage"; class=class="str">"cmt">//--- Construct the URL for the Telegram API request
    class=class="str">"cmt">//--- Construct parameters for the API request
    class="type">class="kw">string params="chat_id="+IntegerToString(chat_id)+"&text="+UrlEncode(text); class=class="str">"cmt">//--- Set chat ID and message text
    if(reply_markup!=NULL){ class=class="str">"cmt">//--- If a reply markup is provided
        params+="&reply_markup="+reply_markup; class=class="str">"cmt">//--- Add reply markup to parameters
    }
    params+="&parse_mode=HTML"; class=class="str">"cmt">//--- Set parse mode to HTML(can also be Markdown)
    params+="&disable_web_page_preview=true"; class=class="str">"cmt">//--- Disable web page preview in the message
    class=class="str">"cmt">//--- Send a POST request to the Telegram API
    class="type">int res=postRequest(output,url,params,WEB_TIMEOUT); class=class="str">"cmt">//--- Call postRequest to send the message
    class="kw">return(res); class=class="str">"cmt">//--- Return the response code from the request
}
              class=class="str">"cmt">//--- Send the response message 
              sendMessageToTelegram(chat.member_id,message,NULL);
              class="kw">continue;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a custom reply keyboard markup for Telegram                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string customReplyKeyboardMarkup(const class="type">class="kw">string keyboard, const class="type">bool resize,

◍ 正文

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">const</span> <span class="keyword">bool</span> one_time){ &nbsp;&nbsp; <span class="comment">// Construct the JSON string for the custom reply keyboard markup.</span> &nbsp;&nbsp; <span class="comment">// 'keyboard' specifies the layout of the custom keyboard.</span> &nbsp;&nbsp; <span class="comment">// 'resize' determines whether the keyboard should be resized to fit the screen.</span> &nbsp;&nbsp; <span class="comment">// 'one_time' specifies if the keyboard should disappear after being used once.</span> &nbsp;&nbsp; &nbsp;&nbsp; <span class="comment">// 'resize' &gt; true: Resize the keyboard to fit the screen.</span> &nbsp;&nbsp; <span class="comment">// 'one_time' &gt; true: The keyboard will disappear after the user has used it once.</span> &nbsp;&nbsp; <span class="comment">// 'selective' &gt; false: The keyboard will be shown to all users, not just specific ones.</span> &nbsp;&nbsp; &nbsp;&nbsp; <span class="keyword">string</span> result = <span class="string">"{"</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="string">"\"keyboard\": "</span> + UrlEncode(keyboard) + <span class="string">", "</span> <span class="comment">//--- Encode and set the keyboard layout</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb

正文

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">string</span> message=<span class="string">"Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully."</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">string</span> buttons_rows = <span class="string">"[[\"Hello 1\"],[\"Hello 2\"],[\"Hello 3\"]]"</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- Send the response message </span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(buttons_rows,<span class="keyword">false</span>,<span class="keyword">false</span>)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">continue</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">string</span> message=<span class="string">"Hello world! You just sent a 'Hello' text to MQL5 and has been processed successfully."</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">string</span> buttons_rows = <span class="string">"[[\"Hello 1\",\"Hello 2\",\"Hello 3\"]]"</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- Send the response message </span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n

「在 Telegram 里查账户与发单的指令分支」

这段逻辑跑在 EA 的消息轮询循环里,收到文本后按关键字分流。用户发 /infoAccount Info,脚本就抓账户登录号、服务器名、余额与浮动盈亏,拼成带 emoji 的字符串回传。 货币袋图标用 0xF4B0 这个 Unicode 码位,经 ShortToString 转成字符串再拼进消息;余额和利润走 AccountInfoDouble,分别挂 ACCOUNT_BALANCEACCOUNT_PROFIT,末尾带账户币种。外汇与贵金属杠杆高,这类一键查账户不代表风险可控,仅作监控用途。 /quotesQuotes 分支只回当前品种的 Ask、Bid:SymbolInfoDouble(_Symbol, SYMBOL_ASK) 取卖价,SYMBOL_BID 取买价,前面拼 \xF170 / \xF171 箭头符号。 /buyBuy 进来时,先 CTrade obj_trade 建交易对象,再取 Ask、Bid 准备下单——具体发单动作在后续片段。下面这段是原文截到的代码边界。

MQL5 / C++
sendMessageToTelegram(chat.member_id,message,NULL);
     }
     class=class="str">"cmt">//--- If the message is "/info" or "Account Info"
     class="type">class="kw">ushort MONEYBAG = 0xF4B0; class=class="str">"cmt">//--- Define money bag emoji
     class="type">class="kw">string MONEYBAGcode = ShortToString(MONEYBAG); class=class="str">"cmt">//--- Convert emoji to class="type">class="kw">string
     if(text=="/info" || text=="Account Info"){
       class="type">class="kw">string currency=AccountInfoString(ACCOUNT_CURRENCY); class=class="str">"cmt">//--- Get the account currency
       class="type">class="kw">string message="\x2733\Account No: "+(class="type">class="kw">string)AccountInfoInteger(ACCOUNT_LOGIN)+"\n";
       message+="\x23F0\Account Server: "+AccountInfoString(ACCOUNT_SERVER)+"\n";
       message+=MONEYBAGcode+"Balance: "+(class="type">class="kw">string)AccountInfoDouble(ACCOUNT_BALANCE)+" "+currency+"\n";
       message+="\x2705\Profit: "+(class="type">class="kw">string)AccountInfoDouble(ACCOUNT_PROFIT)+" "+currency+"\n";
       
       class=class="str">"cmt">//--- Send the response message
       sendMessageToTelegram(chat.member_id,message,NULL);
       class="kw">continue;
     }
     class=class="str">"cmt">//--- If the message is "/quotes" or "Quotes"
     if(text=="/quotes" || text=="Quotes"){
       class="type">class="kw">double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class=class="str">"cmt">//--- Get the current ask price
       class="type">class="kw">double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); class=class="str">"cmt">//--- Get the current bid price
       class="type">class="kw">string message="\xF170 Ask: "+(class="type">class="kw">string)Ask+"\n";
       message+="\xF171 Bid: "+(class="type">class="kw">string)Bid+"\n";
       
       class=class="str">"cmt">//--- Send the response message
       sendMessageToTelegram(chat.member_id,message,NULL);
       class="kw">continue;
     }
     class=class="str">"cmt">//--- If the message is "/buy" or "Buy"
     if (text=="/buy" || text=="Buy"){
       CTrade obj_trade; class=class="str">"cmt">//--- Create a trade object
       class="type">class="kw">double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class=class="str">"cmt">//--- Get the current ask price
       class="type">class="kw">double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); class=class="str">"cmt">//--- Get the current bid price

◍ 用 Telegram 指令撬动 MT5 下单与平仓

在 EA 里接住聊天指令后,真正产生账户动作的是 CTrade 对象。下面这段逻辑演示了收到 /buy 时以 0.01 手开多单,并把挂单距离压到正负 300 点:SL 设在 Bid-300*_Point,TP 设在 Bid+300*_Point,属于典型的高杠杆外汇/贵金属场景,点值波动可能迅速吃掉本金,务必先在模拟盘验证。 开单后不能只信 ResultOrder() 返回的 ticket,要用 PositionSelectByTicket 反查真实持仓。代码里逐字段捞出了开仓价、SL、TP 和手数,再拼成带表情前缀的字符串发回 Telegram,这样手机端能直接看到成交明细而不是盲猜。 平仓分支更直白:先记 PositionsTotal() 得到平仓前持仓数,调 PositionClose(_Symbol) 平掉当前品种,再取一次 PositionsTotal() 做前后对比。两个数值差若为 1,说明平仓指令实际生效;若没差,大概率是品种名不匹配或订单状态锁死,得查交易环境而非代码本身。 把这套接进你的信号机器人,等于给 MT5 装了个远程扳机。外汇和贵金属的高风险在于滑点与休市跳空,300 点止损在黄金上可能一瞬间被穿透,参数别照抄,按品种波动率重算。

MQL5 / C++
obj_trade.Buy(class="num">0.01,NULL,class="num">0,Bid-class="num">300*_Point,Bid+class="num">300*_Point); class=class="str">"cmt">//--- Open a buy position
class="type">class="kw">double entry=class="num">0,sl=class="num">0,tp=class="num">0,vol=class="num">0;
class="type">ulong ticket = obj_trade.ResultOrder(); class=class="str">"cmt">//--- Get the ticket number of the new order
if (ticket > class="num">0){
   if (PositionSelectByTicket(ticket)){ class=class="str">"cmt">//--- Select the position by ticket
      entry=PositionGetDouble(POSITION_PRICE_OPEN); class=class="str">"cmt">//--- Get the entry price
      sl=PositionGetDouble(POSITION_SL); class=class="str">"cmt">//--- Get the stop loss price
      tp=PositionGetDouble(POSITION_TP); class=class="str">"cmt">//--- Get the take profit price
      vol=PositionGetDouble(POSITION_VOLUME); class=class="str">"cmt">//--- Get the volume
   }
}
class="type">class="kw">string message="\xF340\Opened BUY Position:\n";
message+="Ticket: "+(class="type">class="kw">string)ticket+"\n";
message+="Open Price: "+(class="type">class="kw">string)entry+"\n";
message+="Lots: "+(class="type">class="kw">string)vol+"\n";
message+="SL: "+(class="type">class="kw">string)sl+"\n";
message+="TP: "+(class="type">class="kw">string)tp+"\n";

class=class="str">"cmt">//--- Send the response message
sendMessageToTelegram(chat.member_id,message,NULL);
class="kw">continue;
   }

   class=class="str">"cmt">//--- If the message is "/close" or "Close"
   if (text=="/close" || text=="Close"){
      CTrade obj_trade; class=class="str">"cmt">//--- Create a trade object
      class="type">int totalOpenBefore = PositionsTotal(); class=class="str">"cmt">//--- Get the total number of open positions before closing
      obj_trade.PositionClose(_Symbol); class=class="str">"cmt">//--- Close the position for the symbol
      class="type">int totalOpenAfter = PositionsTotal(); class=class="str">"cmt">//--- Get the total number of open positions after closing
      class="type">class="kw">string message="\xF62F\Closed Position:\n";
      message+="Total Positions(Before): "+(class="type">class="kw">string)totalOpenBefore+"\n";
      message+="Total Positions(After): "+(class="type">class="kw">string)totalOpenAfter+"\n";

用指令串起 Telegram bot 的菜单状态

在 MT5 的 EA 里接 Telegram 指令,核心是把用户文本映射成不同分支。下面这段逻辑处理了 /contact、/join、more 以及上箭头表情四类输入,每命中一个就发对应消息并 continue 跳过后续判断。 /join 分支最有看头:它用 HTML 标签拼出带删除线、预格式、斜体下划线和粗体的多行文本,说明 Telegram API 支持在 message 里直接塞标签。注意 message 里写的是 t.me/forexalgo_trading 这个频道链接,实测发到客户端会渲染成可点击样式。 more 指令把 chat.member_state 置为 1,并调用 customReplyKeyboardMarkup(KEYB_MORE,false,true) 弹出自定义键盘;KEYB_MORE 宏定义是 [["⬆"],["Buy","Close","Next"]],也就是上箭头独占一行,下面三个交易动作占一行。上箭头被按下时状态复位成 0,回到主菜单。 外汇与贵金属自动化信号推送波动剧烈、滑点不可控,这类 bot 只适合做通知与轻量交互,真下单请另走风控通道。

MQL5 / C++
   class=class="str">"cmt">//--- Send the response message
   sendMessageToTelegram(chat.member_id,message,NULL);
   class="kw">continue;
   }
   class=class="str">"cmt">//--- If the message is "/contact" or "Contact"
   if (text=="/contact" || text=="Contact"){
      class="type">class="kw">string message="Contact the developer via link below:\n";
      message+="https:class=class="str">"cmt">//t.me/Forex_Algo_Trader";
      
      class=class="str">"cmt">//--- Send the contact message
      sendMessageToTelegram(chat.member_id,message,NULL);
      class="kw">continue;
   }
   class=class="str">"cmt">//--- If the message is "/join" or "Join"
   if (text=="/join" || text=="Join"){
      class="type">class="kw">string message="You want to be part of our MQL5 Community?\n";
      message+="Welcome! <a href=\"https:class=class="str">"cmt">//t.me/forexalgo_trading\">Click me</a> to join.\n";
      message+="<s>Civil Engineering</s> Forex AlgoTrading\n";class=class="str">"cmt">//strikethrough
      message+="<pre>This is a sample of our MQL5 code</pre>\n";class=class="str">"cmt">//preformat
      message+="<u><i>Remember to follow community guidelines!\xF64F\</i></u>\n";class=class="str">"cmt">//italic, underline
      message+="<b>Happy Trading!</b>\n";class=class="str">"cmt">//bold
      
      class=class="str">"cmt">//--- Send the join message
      sendMessageToTelegram(chat.member_id,message,NULL);
      class="kw">continue;
   }
   class=class="str">"cmt">//--- If the message is "more" or "More"
   if (text=="more" || text=="More"){
      chat.member_state=class="num">1; class=class="str">"cmt">//--- Update chat state to show more options
      class="type">class="kw">string message="Choose More Options Below:";
      
      class=class="str">"cmt">//--- Send the more options message with the more options keyboard
      sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MORE,false,true));
      class="kw">continue;
   }
 class="macro">#define EMOJI_UP    "\x2B06" class=class="str">"cmt">//--- Upwards arrow emoji
 class="macro">#define KEYB_MORE "[[""+EMOJI_UP+"","],["Buy","Close","Next"]]" class=class="str">"cmt">//--- More options keyboard layout
   class=class="str">"cmt">//--- If the message is the up emoji
   if(text==EMOJI_UP){
      chat.member_state=class="num">0; class=class="str">"cmt">//--- Reset chat state
      class="type">class="kw">string message="Choose a menu item:";

「正文」

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- Send the message with the main keyboard</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MAIN,<span class="keyword">false</span>,<span class="keyword">false</span>)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">continue</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">//--- If the message is "next" or "Next"</span>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(text==<span class="string">"next"</span>text==<span class="string">"Next"</span>){

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;chat.member_state=<span class="number">2</span>; <span class="comment">//--- Update chat state to show next options</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">string</span> message=<span class="string">"Choose Still More Options Below:"</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//--- Send the next options message with the next options keyboard</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n

◍ 用聊天指令抓取指定品种截图的状态机

在 MT5 的 EA 里接 Telegram 指令做截图,核心是一套 member_state 状态机。用户发 /screenshot 或 Screenshot,程序把状态置为 10,并弹出品种键盘让对方选 symbol,比如 AUDUSDm。 状态 10 下收到文本先调 SymbolSelect(user_symbol,true) 验证品种是否存在。通过就切到状态 11,回一句 CORRECT 并弹出周期键盘(H1 之类);不通过则回 WRONG 并重新发品种键盘,不会往下走。 状态 11 负责收周期,代码里用 ArraySize(periods) 拿到预定义周期总数,再跑 for 循环逐个比对用户输入。整套交互完全靠 chat.member_state 的 10/11 跳转驱动,你可以在 MT5 策略测试器外挂一个信号类 EA 照此改,验证指令流是否卡在某状态。外汇与贵金属品种名带 m 后缀与否会影响 SymbolSelect 结果,实盘接指令有延迟和误触风险。

MQL5 / C++
  class=class="str">"cmt">//--- If the message is "/screenshot" or "Screenshot"
  class="kw">static class="type">class="kw">string symbol = _Symbol; class=class="str">"cmt">//--- Default symbol
  class="kw">static ENUM_TIMEFRAMES period = _Period; class=class="str">"cmt">//--- Default period
  if (text=="/screenshot" || text=="Screenshot"){
    chat.member_state = class="num">10; class=class="str">"cmt">//--- Set state to screenshot request
    class="type">class="kw">string message="Provide a symbol like &class="macro">#x27;AUDUSDm&class="macro">#x27;";
    
    class=class="str">"cmt">//--- Send the message with the symbols keyboard
    sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_SYMBOLS,false,false));
    class="kw">continue;
  }
  class=class="str">"cmt">//--- Handle state class="num">10 (symbol selection for screenshot)
  if (chat.member_state==class="num">10){
    class="type">class="kw">string user_symbol = text; class=class="str">"cmt">//--- Get the user-provided symbol
    if (SymbolSelect(user_symbol,true)){ class=class="str">"cmt">//--- Check if the symbol is valid
      chat.member_state = class="num">11; class=class="str">"cmt">//--- Update state to period request
      class="type">class="kw">string message = "CORRECT: Symbol is found\n";
      message += "Now provide a Period like &class="macro">#x27;H1&class="macro">#x27;";
      symbol = user_symbol; class=class="str">"cmt">//--- Update symbol
      
      class=class="str">"cmt">//--- Send the message with the periods keyboard
      sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_PERIODS,false,false));
    }
    else {
      class="type">class="kw">string message = "WRONG: Symbol is invalid\n";
      message += "Provide a correct symbol name like &class="macro">#x27;AUDUSDm&class="macro">#x27; to proceed.";
      
      class=class="str">"cmt">//--- Send the invalid symbol message with the symbols keyboard
      sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_SYMBOLS,false,false));
    }
    class="kw">continue;
  }
  class=class="str">"cmt">//--- Handle state class="num">11 (period selection for screenshot)
  if (chat.member_state==class="num">11){
    class="type">bool found=false; class=class="str">"cmt">//--- Flag to check if period is valid
    class="type">int total=ArraySize(periods); class=class="str">"cmt">//--- Get the number of defined periods
    for(class="type">int k=class="num">0; k<total; k++){

正文

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">string</span> str_tf=<span class="functions">StringSubstr</span>(<span class="functions">EnumToString</span>(periods[k]),<span class="number">7</span>); <span class="comment">//--- Convert period enum to string</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(<span class="functions">StringCompare</span>(str_tf,text,<span class="macro">false</span>)==<span class="number">0</span>){ <span class="comment">//--- Check if period matches</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="macro">ENUM_TIMEFRAMES</span> user_period=periods[k]; <span class="comment">//--- Set user-selected period</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;period = user_period; <span class="comment">//--- Update period</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;found=<span class="macro">true</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">break</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nb

「多周期枚举数组的直列写法」

在 MT5 里做跨周期扫描,第一步往往是把要覆盖的周期先固化成一个数组。下面这行声明把 M1、M15、M30、H1、H4、D1 六个常用周期塞进同一个常量数组,后续用循环遍历就能一次性拿到多档时间框架的数据。 这种写法比在代码里反复写 switch 或硬判断更干净,也方便你按需增删周期——比如做贵金属短线策略时,把 PERIOD_M5 加进去就能多盯一个档位。外汇与贵金属杠杆高、滑点跳空频繁,多周期共振也只是提高概率,不等于信号必然有效。

MQL5 / C++
const ENUM_TIMEFRAMES periods[] = {PERIOD_M1,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H4,PERIOD_D1};

◍ 在 MT5 里跑通 Telegram 消息推送的验证点

把 EA 接到 Telegram 做信号推送时,先确认链接预览是否开启。默认关掉预览会让用户只看到裸链接,很难判断频道质量;把 disable_web_page_preview 设为 false 后,消息下方能直接露出标题和缩略图,点击前就大概知道落地页内容。 切换解析模式是第二个验证动作。从 HTML 改到 Markdown 后,整段消息的排版语法要重写:链接用 [text](URL),删除线用 ~text~,预格式化文本用三重反引号包裹,粗体用 text。Markdown 本身没有下划线标签,最接近的效果是 *text* 或 _text_ 的斜体,真要下划线只能塞进占位符里凑合。 下面这段 MQL5 片段就是实测可用的结构。第一段在函数里加预览参数,第二段切到 Markdown 解析,第三段是收到 /join 指令后拼出的多格式消息体。在 MT5 策略测试器挂上机器人 token 跑一遍,若手机端收到带预览和排版的回复,说明通路没问题。外汇与贵金属信号推送涉及实时性和平台风险,任何延迟都可能让策略失效,验证时建议用模拟号先跑。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Send a message to Telegram                                                                |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">int</span> sendMessageToTelegram( ... ){
  
  <span class="comment">class=class="str">"cmt">//--- ...</span>
  <span class="keyword">params</span>+=<span class="class="type">class="kw">string">"&amp;disable_web_page_preview=false"</span>; <span class="comment">class=class="str">"cmt">//--- Enable web page preview in the message</span>
  <span class="comment">class=class="str">"cmt">//--- ...</span>
  
}
<span>
</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Send a message to Telegram                                                                |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">int</span> sendMessageToTelegram( ... ){
  <span class="comment">class=class="str">"cmt">//--- ...</span>
  <span class="keyword">params</span>+=<span class="class="type">class="kw">string">"&amp;parse_mode=Markdown"</span>; <span class="comment">class=class="str">"cmt">//--- Set parse mode to Markdown(can also be HTML)</span>
  <span class="comment">class=class="str">"cmt">//--- ...</span>
}
<span>
</span>
        <span class="comment">class=class="str">"cmt">//--- If the message is "/join" or "Join"</span>
        <span class="keyword">if</span> (text==<span class="class="type">class="kw">string">"/join"</span> || text==<span class="class="type">class="kw">string">"Join"</span>){
          <span class="keyword">class="type">class="kw">string</span> message = <span class="class="type">class="kw">string">"You want to be part of our MQL5 Community?\n"</span>;
          message += <span class="class="type">class="kw">string">"Welcome! [Click me](https:class=class="str">"cmt">//t.me/forexalgo_trading) to join.\n"</span>; <span class="comment">// Link</span>
          message += <span class="class="type">class="kw">string">"~Civil Engineering~ Forex AlgoTrading\n"</span>; <span class="comment">class=class="str">"cmt">// Strikethrough</span>
          message += <span class="class="type">class="kw">string">"```\nThis is a sample of our MQL5 code\n```"</span>; <span class="comment">class=class="str">"cmt">// Preformatted text</span>
          message += <span class="class="type">class="kw">string">"*_Remember to follow community guidelines! \xF64F_*"</span>; <span class="comment">class=class="str">"cmt">// Italic and underline</span>
          message += <span class="class="type">class="kw">string">"**Happy Trading!**\n"</span>; <span class="comment">class=class="str">"cmt">// Bold</span>
          
          <span class="comment">class=class="str">"cmt">//--- Send the join message</span>
          sendMessageToTelegram(chat.member_id, message, <span class="macro">NULL</span>);
          <span class="keyword">class="kw">continue</span>;
        }

画得少,看得清

把 EA 与 Telegram 双向机器人直连后,用户不再等信号触发才发指令,随时能下 MQL5 命令并由端侧正确解析回传。实测中这套链路在 MT5 _build 4410 以上环境跑通,绕开了等待中继 bot 的延迟,响应倾向更跟手。 有读者在评论区提到 PART5 的 mq5 在 1151、1223 行报 ArrayAdd 重载错误,社区解法是把 destinationArr 形参从 char 改成 uchar,编译即过。外汇与贵金属自动对话控制属于高风险操作,误指令可能直接市价开仓。 下面这段取更新体的代码,是解析 JSON 回传的最小骨架,复制进你的监听函数就能验证字段是否到位: obj_msg.update_id=obj_item["update_id"].ToInt(); //--- 获取更新 ID obj_msg.message_id=obj_item["message"]["message_id"].ToInt(); //--- 获取报文 ID obj_msg.message_date=(datetime)obj_item["message"]["date"].ToInt(); //--- 获取信息日期 留一个落点:先拿测试号把上面三行跑通,再扩成完整对话树,比一口气抄全工程更稳。

MQL5 / C++
obj_msg.update_id=obj_item["update_id"].ToInt(); class=class="str">"cmt">//--- 获取更新 ID
obj_msg.message_id=obj_item["message"]["message_id"].ToInt(); class=class="str">"cmt">//--- 获取报文 ID
obj_msg.message_date=(class="type">class="kw">datetime)obj_item["message"]["date"].ToInt(); class=class="str">"cmt">//--- 获取信息日期
把指令回环交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到消息回路状态,你专注决策而非自己搭轮询。

常见问题

MT5 环境对外部依赖限制严,自带 WebRequest 拿到的原始响应需自己按结构解析,独立类便于在 MQL5 侧控制字段映射与超时。
多为转义符处理不当、字段名大小写不一致或 API 返回里混了 edited_message 等非预期类型,需在解析层做类型分流。
先用私人聊天发固定格式文本,在 EA 日志打印解码后的结构体,确认往返时延与回执内容符合预期再接复杂指令。
若你只想要品种异动提醒与指令可视化,小布盯盘已封装相关能力;深度自定义下单逻辑仍建议参考本篇自己接 EA。
网络延迟或聊天 API 限流可能导致指令迟到,杠杆品种波动剧烈,远程发单可能错价,仅适合小仓位验证策略。