如何采用 MQL5 创建用于 Telegram 的 bots·综合运用
「让交易机器人自动发图与状态提示」
在 MT5 的 Bot 接口里,真正对交易者有用的是把图表截图推给指定聊天对象,而不是去折腾语音、视频或位置共享。SendPhoto 提供了两条路径:第一次发本地图用 local_path 从 \MQL5\Files 读取,服务器会回一个 photo_id;后续给多人或重复发送,直接用 photo_id 即可,省掉重复上传的等待。 SendChatAction 解决的是「处理中要等人」的体验问题。比如你要在回应用户指令后花几秒截 EURUSD 图,先发一个 action 事件告知「发送照片中」,对方不会以为 bot 卡死。 下面这段代码是实打实能贴进 MT5 跑的:用 token 初始化 bot,给 chat_id 198289825 发 EURUSD1.gif 并带「屏幕截图」签名,成功就把 photo_id 打印出来供复用。超时默认 10000 毫秒,若 common_flag 不设为 true,文件只在本机 Files 目录找。 别把 photo_id 当一次性消耗品 同一张图反复 SendPhoto(local_path) 是浪费,服务器返回的那个 photo_id 字符串留好,第二次起只传 chat_id + photo_id + caption,延迟和流量都更低。外汇与贵金属行情波动剧烈,用机器人推送图表须清楚这是高风险辅助手段,截图不代表任何方向建议。
class="type">int SendPhoto(const class="type">long chat_id, const class="type">class="kw">string local_path, class="type">class="kw">string &photo_id, const class="type">class="kw">string caption=NULL, const class="type">bool common_flag=class="kw">false, const class="type">int timeout=class="num">10000) CCustomBot bot; class="type">class="kw">string token = "class="num">208375865:AAFnuOjlZ3Wsdan6PAjeqqUtBybe0Di1or8"; bot.Token(token); class="type">class="kw">string photo_id; class="type">int result=bot.SendPhoto(class="num">198289825,"EURUSD1.gif",photo_id,"屏幕截图"); if(result==class="num">0) Print("照片 ID: ",photo_id); else Print("错误: ",GetErrorDescription(result)); class="type">int SendPhoto(const class="type">long chat_id, const class="type">class="kw">string photo_id, const class="type">class="kw">string caption=NULL) class="type">int SendChatAction(const class="type">long chat_id, const ENUM_CHAT_ACTION actiona)
◍ 把MACD信号推到Telegram频道的三套机器人
实际可落地的做法分三种:第一类EA能回传账户余额、实时报价和图表截图,适合盯盘时不在电脑前的场景;第二类EA把站点内的搜索结果直接发到聊天软件,用来跟踪特定关键词的新帖;第三类EA把标准MACD信号广播到指定频道,代码逻辑很轻,改指标名就能套用到你自己常用的震荡或趋势类指标上。 外汇和贵金属属于高杠杆品种,这类信号推送只解决‘通知’问题,不解决胜率,收到消息后仍需自己判断入场。 下面这段是第三类EA的核心框架(节选初始化与每根K线判断部分),注意 macd_handle 用的参数是 12/26/9 配合收盘价,这是MT5里iMACD的标准快慢线周期。OnTick里用CopyTime拿最新柱时间,靠 time_signal!=time[0] 保证每根柱只判一次信号,避免重复刷屏。 别把令牌写死在公开代码里 示例里的 InpToken 是明文写死的测试令牌,真要用就把 InpToken 改成从加密文件或输入框读取,否则频道会被他人冒用。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Telegram_Signal_EA_v1.mq4 | class=class="str">"cmt">//| 版权所有 class="num">2014, MetaQuotes 软件公司| class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 class="num">2014, MetaQuotes 软件公司" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 包含 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Telegram.mqh> class=class="str">"cmt">//--- 输入参数 input class="type">class="kw">string InpChannelName="@forexsignalchannel";class=class="str">"cmt">//信道名称 input class="type">class="kw">string InpToken="class="num">177791741:AAH0yB3YV7ywm80af_-AGqb7hzTR_Ud9DhQ";class=class="str">"cmt">//令牌 class=class="str">"cmt">//--- 全局变量 CCustomBot bot; class="type">int macd_handle; class="type">class="kw">datetime time_signal=class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能交易程序初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { time_signal=class="num">0; class=class="str">"cmt">//--- 设置令牌 bot.Token(InpToken); class=class="str">"cmt">//--- 获得指标句柄 macd_handle=iMACD(NULL,class="num">0,class="num">12,class="num">26,class="num">9,PRICE_CLOSE); if(macd_handle==INVALID_HANDLE) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 结束 class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能交易程序分时函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- 获得时间 class="type">class="kw">datetime time[class="num">1]; if(CopyTime(NULL,class="num">0,class="num">0,class="num">1,time)!=class="num">1) class="kw">return; class=class="str">"cmt">//--- 在每根柱线上检查信号 if(time_signal!=time[class="num">0]) { class=class="str">"cmt">//--- 第一次计算 if(time_signal==class="num">0) { time_signal=time[class="num">0]; class="kw">return; }
用 MACD 死叉金叉触发 TG 推送
这段逻辑把 MACD 主线与信号线的两期数值拉进数组,靠 CopyBuffer 取最近两根柱。若取数长度不等于 2 就直接 return,避免后续用脏数据发信号。
金叉判定写得很直白:上一根 macd[1] > signal[1] 且当前根 macd[0] <= signal[0],即主线从上方下穿信号线,倾向看作卖出触发;反过来 macd[1] < signal[1] 且 macd[0] >= signal[0] 就是死叉转多,倾向看作买入触发。
信号文本用 StringFormat 拼出品种、周期(截掉 PERIOD_ 前缀)、ask/bid 报价与柱时间,再交给 bot.SendMessage 推到指定频道。返回值非 0 就 Print 错误描述,方便在 MT5 Experts 日志里排查推送失败。
外汇与贵金属波动大、滑点随机,这类交叉信号在历史回测中约有 40%~55% 的胜率区间,实盘仅作提醒而非下单依据,开 MT5 把这段代码挂 EA 里跑一晚就能看到推送格式是否合你频道。
class="type">class="kw">double macd[class="num">2]={class="num">0.0}; class="type">class="kw">double signal[class="num">2]={class="num">0.0}; if(CopyBuffer(macd_handle,class="num">0,class="num">0,class="num">2,macd)!=class="num">2) class="kw">return; if(CopyBuffer(macd_handle,class="num">1,class="num">0,class="num">2,signal)!=class="num">2) class="kw">return; time_signal=time[class="num">0]; class=class="str">"cmt">//--- 发送买入信号 if(macd[class="num">1]>signal[class="num">1] && macd[class="num">0]<=signal[class="num">0]) { class="type">class="kw">string msg=StringFormat("名称: MACD 信号\n品种: %s\n时间帧: %s\n类型: 买入\n价格: %s\n时间: %s", _Symbol, StringSubstr(EnumToString(_Period),class="num">7), DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits), TimeToString(time[class="num">0])); class="type">int res=bot.SendMessage(InpChannelName,msg); if(res!=class="num">0) Print("错误: ",GetErrorDescription(res)); } class=class="str">"cmt">//--- 发送卖出信号 if(macd[class="num">1]<signal[class="num">1] && macd[class="num">0]>=signal[class="num">0]) { class="type">class="kw">string msg=StringFormat("名称: MACD 信号\n品种: %s\n时间帧: %s\n类型: 卖出\n价格: %s\n时间: %s", _Symbol, StringSubstr(EnumToString(_Period),class="num">7), DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits), TimeToString(time[class="num">0])); class="type">int res=bot.SendMessage(InpChannelName,msg); if(res!=class="num">0) Print("错误: ",GetErrorDescription(res)); } } } class=class="str">"cmt">//+------------------------------------------------------------------+
「接 Yandex 分析还得靠 Botan 源」
想把自家跑在 MT4/MT5 上的 bot 接到 Yandex.AppMetrika 做会话分段、队列分析这类指标,原生的 Telegram Bot API 并不直接够用,社区里较可行的路子是挂 Botan 源:消息先回传,统计由挂载在图表上的特殊 bot 推回来,细报告仍走网页端,不用跳出 IM 界面。 随文给的 telegram.zip 约 31.32 KB,里头的 Telegram.mqh 同时覆盖两个平台,但评论区暴露了现实坑:2025 年 6 月起有多人报 ArrayAdd 与 WebRequest 的 overload 匹配失效,编译器直接报「no one of the overloads can be applied」。 遇到上面这种报错,先别急着重写逻辑,去讨论串翻 Lorentzos Roussos 或 Jefferson Judge Metha 发的修正版头文件,社区反馈替换后多数能编译过。外汇与贵金属杠杆高,接 bot 做分析只是辅助,信号延迟或接口挂掉都可能让你漏单,实盘前务必在策略测试器里先跑通。
&class="macro">#x27;ArrayAdd&class="macro">#x27; - no one of the overloads can be applied to the function call Telegram.mqh class="num">149 class="num">10 could be one of class="num">2 function(s) Telegram.mqh class="num">149 class="num">10 class="type">void CCustomBot::ArrayAdd(class="type">uchar&[],const class="type">uchar&[]) Telegram.mqh class="num">120 class="num">22 class="type">void CCustomBot::ArrayAdd(class="type">char&[],const class="type">class="kw">string) Telegram.mqh class="num">132 class="num">22 &class="macro">#x27;WebRequest&class="macro">#x27; - no one of the overloads can be applied to the function call Telegram.mqh class="num">230 class="num">15 could be one of class="num">2 function(s) Telegram.mqh class="num">230 class="num">15 built-in: class="type">int WebRequest(const class="type">class="kw">string,const class="type">class="kw">string,const class="type">class="kw">string,const class="type">class="kw">string,class="type">int,const class="type">char&[],class="type">int,class="type">char&[],class="type">class="kw">string&) Telegram.mqh class="num">230 class="num">15 built-in: class="type">int WebRequest(const class="type">class="kw">string,const class="type">class="kw">string,const class="type">class="kw">string,class="type">int,const class="type">char&[],class="type">char&[],class="type">class="kw">string&) Telegram.mqh class="num">230 class="num">15 &class="macro">#x27;ArrayAdd&class="macro">#x27; - no one of the overloads can be applied to the function call Telegram.mqh class="num">737 class="num">7 could be one of class="num">2 function(s) Telegram.mqh class="num">737 class="num">7 class="type">void CCustomBot::ArrayAdd(class="type">uchar&[],const class="type">uchar&[]) Telegram.mqh class="num">120 class="num">22 class="type">void CCustomBot::ArrayAdd(class="type">char&[],const class="type">class="kw">string) Telegram.mqh class="num">132 class="num">22 &class="macro">#x27;ArrayAdd&class="macro">#x27; - no one of the overloads can be applied to the function call Telegram.mqh class="num">738 class="num">7 could be one of class="num">2 function(s) Telegram.mqh class="num">738 class="num">7
◍ 记住这一条就够了
在 CCustomBot::ArrayAdd 里,Telegram.mqh 第 120 行第 22 列那段逻辑只做了一件事:把消息文本里的双空格压成单空格,再把分号和逗号全替换成空格。 核心就三行——while 循环持续替换 " " 为 " " 直到返回 0,随后两行 StringReplace 分别清掉 ";" 和 ","。 你在 MT5 里接 Telegram 信号时,若发现指令解析错位,先检查这段预处理:没清干净的分隔符会让后续 uchar 数组拼接偏移。外汇与贵金属行情受杠杆影响大,信号延迟或格式错乱都可能放大亏损风险,动手改之前先在策略测试器跑一遍原始文本。
class="type">void CCustomBot::ArrayAdd(class="type">uchar&[],const class="type">uchar&[]) Telegram.mqh class="num">120 class="num">22 while(::StringReplace(text," "," ")>class="num">0); ::StringReplace(text,";"," "); ::StringReplace(text,","," ");