创建 MQL5-Telegram 集成 EA 交易(第 5 部分):从 Telegram 向 MQL5 发送命令并接收实时响应·综合运用
(3/3)· 收官篇:把 Telegram 命令流接进 MQL5 解码层,让机器人不再只会单向播报
◍ 聊天节点里的时间与未处理标记
这段 MT5 代码处理的是多聊天的消息写入逻辑:找不到对应节点就新建,找到就覆盖最新一条。新建时把 member_state 置 0、done 置 false,意味着这条消息进入待处理队列,后续轮询才会去消费。 关键在 TimeLocal() 的两次调用——新建和已存在分支都刷新 member_time,相当于用本地时钟给聊天做心跳。若你做跨会话的超时清理,直接比这个字段和当前 TimeLocal() 差值即可,不必另起定时器。 注意 done=false 在两种分支里都出现,说明无论新旧聊天,新消息默认都不算处理完。跑实盘前建议在 OnTimer 里打印 member_new_one.message_text 和 done,确认没有被某处提前置 true 吞掉。外汇与贵金属行情下这类消息遗漏可能造成信号延迟,属高风险环节。
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") 整段移回循环体内。
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,
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,
◍ 正文
<span class="keyword">const</span> <span class="keyword">bool</span> one_time){ <span class="comment">// Construct the JSON string for the custom reply keyboard markup.</span> <span class="comment">// 'keyboard' specifies the layout of the custom keyboard.</span> <span class="comment">// 'resize' determines whether the keyboard should be resized to fit the screen.</span> <span class="comment">// 'one_time' specifies if the keyboard should disappear after being used once.</span> <span class="comment">// 'resize' > true: Resize the keyboard to fit the screen.</span> <span class="comment">// 'one_time' > true: The keyboard will disappear after the user has used it once.</span> <span class="comment">// 'selective' > false: The keyboard will be shown to all users, not just specific ones.</span> <span class="keyword">string</span> result = <span class="string">"{"</span> <span class="string">"\"keyboard\": "</span> + UrlEncode(keyboard) + <span class="string">", "</span> <span class="comment">//--- Encode and set the keyboard layout</span> &nb
正文
<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>; <span class="keyword">string</span> buttons_rows = <span class="string">"[[\"Hello 1\"],[\"Hello 2\"],[\"Hello 3\"]]"</span>; <span class="comment">//--- Send the response message </span> sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(buttons_rows,<span class="keyword">false</span>,<span class="keyword">false</span>)); <span class="keyword">continue</span>; <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>; <span class="keyword">string</span> buttons_rows = <span class="string">"[[\"Hello 1\",\"Hello 2\",\"Hello 3\"]]"</span>; <span class="comment">//--- Send the response message </span> &n
「在 Telegram 里查账户与发单的指令分支」
这段逻辑跑在 EA 的消息轮询循环里,收到文本后按关键字分流。用户发 /info 或 Account Info,脚本就抓账户登录号、服务器名、余额与浮动盈亏,拼成带 emoji 的字符串回传。
货币袋图标用 0xF4B0 这个 Unicode 码位,经 ShortToString 转成字符串再拼进消息;余额和利润走 AccountInfoDouble,分别挂 ACCOUNT_BALANCE 与 ACCOUNT_PROFIT,末尾带账户币种。外汇与贵金属杠杆高,这类一键查账户不代表风险可控,仅作监控用途。
/quotes 或 Quotes 分支只回当前品种的 Ask、Bid:SymbolInfoDouble(_Symbol, SYMBOL_ASK) 取卖价,SYMBOL_BID 取买价,前面拼 \xF170 / \xF171 箭头符号。
/buy 或 Buy 进来时,先 CTrade obj_trade 建交易对象,再取 Ask、Bid 准备下单——具体发单动作在后续片段。下面这段是原文截到的代码边界。
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 点止损在黄金上可能一瞬间被穿透,参数别照抄,按品种波动率重算。
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 只适合做通知与轻量交互,真下单请另走风控通道。
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:";
「正文」
<span class="comment">//--- Send the message with the main keyboard</span> sendMessageToTelegram(chat.member_id,message,customReplyKeyboardMarkup(KEYB_MAIN,<span class="keyword">false</span>,<span class="keyword">false</span>)); <span class="keyword">continue</span>; } <span class="comment">//--- If the message is "next" or "Next"</span>
| <span class="keyword">if</span>(text==<span class="string">"next"</span> | text==<span class="string">"Next"</span>){ |
|---|
chat.member_state=<span class="number">2</span>; <span class="comment">//--- Update chat state to show next options</span> <span class="keyword">string</span> message=<span class="string">"Choose Still More Options Below:"</span>; <span class="comment">//--- Send the next options message with the next options keyboard</span> &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 结果,实盘接指令有延迟和误触风险。
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++){
正文
<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> <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> <span class="macro">ENUM_TIMEFRAMES</span> user_period=periods[k]; <span class="comment">//--- Set user-selected period</span> period = user_period; <span class="comment">//--- Update period</span> found=<span class="macro">true</span>; <span class="keyword">break</span>; } } &nb
「多周期枚举数组的直列写法」
在 MT5 里做跨周期扫描,第一步往往是把要覆盖的周期先固化成一个数组。下面这行声明把 M1、M15、M30、H1、H4、D1 六个常用周期塞进同一个常量数组,后续用循环遍历就能一次性拿到多档时间框架的数据。 这种写法比在代码里反复写 switch 或硬判断更干净,也方便你按需增删周期——比如做贵金属短线策略时,把 PERIOD_M5 加进去就能多盯一个档位。外汇与贵金属杠杆高、滑点跳空频繁,多周期共振也只是提高概率,不等于信号必然有效。
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 跑一遍,若手机端收到带预览和排版的回复,说明通路没问题。外汇与贵金属信号推送涉及实时性和平台风险,任何延迟都可能让策略失效,验证时建议用模拟号先跑。
<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">"&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">"&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(); //--- 获取信息日期 留一个落点:先拿测试号把上面三行跑通,再扩成完整对话树,比一口气抄全工程更稳。
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">//--- 获取信息日期