创建 MQL5-Telegram 集成 EA 交易 (第二部分):从 MQL5 发送信号到 Telegram·综合运用
◍ 用 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 个月该逻辑触发频率,再决定是否接消息推送。
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」里手动添加。
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 挡在策略计算之外,回测里这根判断能少跑不少无效分支。
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 拼进消息体。
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,防止数据不足时误判交叉。
+" @ "+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) 确认品种再调倍数。
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,为后续平仓或反手留好入口。外汇与贵金属波动剧烈,这类自动通知只是辅助,信号失效时仍可能快速回撤,需自行评估风险。
+"\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 的空单通知。
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 和错误码一起打印出来,方便你对照接口文档排错。外汇与贵金属行情波动剧烈,这类通知失败可能让你漏掉关键信号,上线前务必在策略测试器里实跑一遍确认回执逻辑通畅。
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 跑一遍,比读十次结论更接近真相。