构建K线图趋势约束模型(第六部分):一体化集成·进阶篇
(2/3)·双通道通知代码各自为战,合并时隐蔽执行难确认送达,本篇拆解连贯函数整合逻辑
「用 ShellExecuteW 把 MT5 警报推到外部聊天软件」
想在 MT5 触发信号时顺手发到 WhatsApp,核心不是 MQL5 自己联网,而是借 Windows 的 shell32.dll 起一个进程跑 Python 脚本。下面这段通过 #import 把 ShellExecuteW 引进来,参数里 hwnd 填 0、lpOperation 用 "open"、nShowCmd 用 1(普通窗口),返回码 ≤32 代表系统层调用失败。 警报本身套了一层冷却逻辑:alert_cooldown_seconds 输入项默认 60 秒,myAlert 每次进来先用 TimeCurrent() 减 last_alert_time,差值不够就直接 return,避免同一根 K 线里被刷屏。 真正发消息只在 type 为 indicator 或 info 时走分支:若 Audible_Alerts 开就 Alert() 弹窗响铃,Push_Notifications 开就 SendNotification() 推 MT5 移动端;同时拼出 whatsapp_command,把 python.exe 路径和脚本路径写死(注意原文是 C:\\Users\\Your_Computer_Name\\…\\Python312,需改成你本机实际路径),再用 cmd.exe /c 执行并 timeout 5 留 5 秒让脚本发完。 调试时 Print 会打出整条命令串,方便你直接粘到命令行手动复跑;若 whatsapp_result<=32 就说明 ShellExecuteW 没拉起 cmd,优先查路径空格转义与 Python 是否装在该目录。外汇与贵金属波动剧烈,这类外推仅作辅助提醒,信号误发或延迟都可能发生,实盘前务必在模拟盘验证通路。
class="macro">#class="kw">import "shell32.dll" class="type">int ShellExecuteW(class="type">int hwnd, class="type">class="kw">string lpOperation, class="type">class="kw">string lpFile, class="type">class="kw">string lpParameters, class="type">class="kw">string lpDirectory, class="type">int nShowCmd); class="macro">#class="kw">import class="type">class="kw">datetime last_alert_time; input class="type">int alert_cooldown_seconds = class="num">60; class=class="str">"cmt">// Cooldown period in seconds class="type">void myAlert(class="type">class="kw">string type, class="type">class="kw">string message) { class="type">class="kw">datetime current_time = TimeCurrent(); if (current_time - last_alert_time < alert_cooldown_seconds) { class=class="str">"cmt">// Skip alert if within cooldown period class="kw">return; } last_alert_time = current_time; class="type">class="kw">string full_message = type + " | Trend Constraint V1.class="num">06 @ " + Symbol() + "," + IntegerToString(Period()) + " | " + message; if (type == "print") { Print(message); } else if (type == "error") { Print(type + " | Trend Constraint V1.class="num">06 @ " + Symbol() + "," + IntegerToString(Period()) + " | " + message); } else if (type == "order") { class=class="str">"cmt">// Add order alert handling if needed } else if (type == "modify") { class=class="str">"cmt">// Add modify alert handling if needed } else if (type == "indicator" || type == "info") { if (Audible_Alerts) { Alert(full_message); } if (Push_Notifications) { SendNotification(full_message); } class=class="str">"cmt">// Send to WhatsApp class="type">class="kw">string python_path = "C:\\Users\\Your_Computer_Name\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"; class="type">class="kw">string whatsapp_script_path = "C:\\Users\\Your_Computer_Name\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\send_whatsapp_message.py"; class="type">class="kw">string whatsapp_command = python_path + " \"" + whatsapp_script_path + "\" \"" + full_message + "\""; class=class="str">"cmt">// Debugging: Print the command being executed for WhatsApp Print("Executing command to send WhatsApp message: ", whatsapp_command); class=class="str">"cmt">// Use cmd.exe to execute the command and then wait for class="num">5 seconds class="type">class="kw">string final_whatsapp_command = "/c " + whatsapp_command + " && timeout class="num">5"; class="type">int whatsapp_result = ShellExecuteW(class="num">0, "open", "cmd.exe", final_whatsapp_command, NULL, class="num">1); if (whatsapp_result <= class="num">32) {
◍ 把信号推到 WhatsApp 与 Telegram 的桥接细节
在 MT5 的 EA 或脚本里调用外部 Python 发消息,核心是用 ShellExecuteW 拉起 cmd.exe 再跑 python 脚本。下面这段片段先处理 WhatsApp 失败分支:拿 GetLastError 码打印出来,成功则回显 result code,方便你当场判断是权限问题还是路径错。
Telegram 部分把 python 完整路径和脚本路径拼进命令字符串,注意 C:\\Users\\Your_Computer_Name\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\ 这类路径必须换成你本机真实用户名,否则 ShellExecuteW 返回 ≤32 直接进失败分支。
调试时在日志里打印最终 command 非常关键——曾有人在 cmd 里手动跑 python send_telegram_message.py "Trend Constraint V1.07 testing" 得到 Message sent successfully!,但 EA 里静默失败,差异就在转义引号与 && timeout 5 的有无。
冷却逻辑用 TimeCurrent 减 last_alert_time 判断是否小于 alert_cooldown_seconds,未过冷却直接 return,避免一秒触发几十条消息把号限流。外汇与贵金属信号推送本身不杠杆风险,但策略误报会带来高频干扰,实盘前先在 demo 账户验证推送链路。
class="type">int error_code = GetLastError(); Print("Failed to execute WhatsApp Python script. Error code: ", error_code); } else { Print("Successfully executed WhatsApp Python script. Result code: ", whatsapp_result); } class=class="str">"cmt">// Send to Telegram class="type">class="kw">string telegram_script_path = "C:\\Users\\Your_Computer_Name\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\send_telegram_message.py"; class="type">class="kw">string telegram_command = python_path + " \"" + telegram_script_path + "\" \"" + full_message + "\""; class=class="str">"cmt">// Debugging: Print the command being executed for Telegram Print("Executing command to send Telegram message: ", telegram_command); class=class="str">"cmt">// Use cmd.exe to execute the command and then wait for class="num">5 seconds class="type">class="kw">string final_telegram_command = "/c " + telegram_command + " && timeout class="num">5"; class="type">int telegram_result = ShellExecuteW(class="num">0, "open", "cmd.exe", final_telegram_command, NULL, class="num">1); if (telegram_result <= class="num">32) { class="type">int error_code = GetLastError(); Print("Failed to execute Telegram Python script. Error code: ", error_code); } else { Print("Successfully executed Telegram Python script. Result code: ", telegram_result); } } } class="type">class="kw">datetime current_time = TimeCurrent(); if (current_time - last_alert_time < alert_cooldown_seconds) { class=class="str">"cmt">// Skip alert if within cooldown period class="kw">return; } last_alert_time = current_time;
指标缓冲区与参数声明的底层结构
这套自定义指标一口气挂了 6 个绘图缓冲区和 6 个 indicator_plots,意味着 MT5 子窗口里能同时渲染箭头与线段两类信号。前 4 个 plot 是 DRAW_ARROW:买/卖主信号线宽 5、反转信号线宽 2,颜色分别用 0xFF3C00(橙红买)、0x0000FF(蓝卖)、0xE8351A(深红买反转)、0x1A1AE8(深蓝卖反转)做了区分。 后两个 plot 是 DRAW_LINE,宽度均为 2,橙黄 0xFFAA00 标买区、纯蓝 0x0000FF 标卖区,和箭头形成双层视觉确认。宏 PLOT_MAXIMUM_BARS_BACK 设 5000、OMIT_OLDEST_BARS 设 50,实际回看深度控制在最近 5000 根、计算时跳过最老 50 根,避免历史边界噪声。 输入参数里 RSI 超卖 30 / 超买 70 是经典阈值,慢 MA 200、快 MA 100 两线金叉死叉作为趋势过滤。Audible_Alerts 与 Push_Notifications 默认开,新信号会触发终端响铃和手机推送——外汇与贵金属波动剧烈,信号仅作概率参考,实盘须自担高风险。 代码里声明了 RSI_handle 及 7 组 MA_handle(MA~MA7),说明指标内部叠加了多周期均线阵列,并非单线判断。ShellExecuteW 从 shell32.dll 导入,预留了调外部程序的能力,开 MT5 把这段贴进 MQEditor 可直接编译看缓冲分配是否报错。
class="macro">#class="kw">property indicator_buffers class="num">6 class="macro">#class="kw">property indicator_plots class="num">6 class="macro">#class="kw">property indicator_type1 DRAW_ARROW class="macro">#class="kw">property indicator_width1 class="num">5 class="macro">#class="kw">property indicator_color1 0xFF3C00 class="macro">#class="kw">property indicator_label1 "Buy" class="macro">#class="kw">property indicator_type2 DRAW_ARROW class="macro">#class="kw">property indicator_width2 class="num">5 class="macro">#class="kw">property indicator_color2 0x0000FF class="macro">#class="kw">property indicator_label2 "Sell" class="macro">#class="kw">property indicator_type3 DRAW_ARROW class="macro">#class="kw">property indicator_width3 class="num">2 class="macro">#class="kw">property indicator_color3 0xE8351A class="macro">#class="kw">property indicator_label3 "Buy Reversal" class="macro">#class="kw">property indicator_type4 DRAW_ARROW class="macro">#class="kw">property indicator_width4 class="num">2 class="macro">#class="kw">property indicator_color4 0x1A1AE8 class="macro">#class="kw">property indicator_label4 "Sell Reversal" class="macro">#class="kw">property indicator_type5 DRAW_LINE class="macro">#class="kw">property indicator_style5 STYLE_SOLID class="macro">#class="kw">property indicator_width5 class="num">2 class="macro">#class="kw">property indicator_color5 0xFFAA00 class="macro">#class="kw">property indicator_label5 "Buy" class="macro">#class="kw">property indicator_type6 DRAW_LINE class="macro">#class="kw">property indicator_style6 STYLE_SOLID class="macro">#class="kw">property indicator_width6 class="num">2 class="macro">#class="kw">property indicator_color6 0x0000FF class="macro">#class="kw">property indicator_label6 "Sell" class="macro">#define PLOT_MAXIMUM_BARS_BACK class="num">5000 class="macro">#define OMIT_OLDEST_BARS class="num">50 class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double Buffer1[]; class="type">class="kw">double Buffer2[]; class="type">class="kw">double Buffer3[]; class="type">class="kw">double Buffer4[]; class="type">class="kw">double Buffer5[]; class="type">class="kw">double Buffer6[]; input class="type">class="kw">double Oversold = class="num">30; input class="type">class="kw">double Overbought = class="num">70; input class="type">int Slow_MA_period = class="num">200; input class="type">int Fast_MA_period = class="num">100; class="type">class="kw">datetime time_alert; class=class="str">"cmt">//used when sending alert input class="type">bool Audible_Alerts = true; input class="type">bool Push_Notifications = true; class="type">class="kw">double myPoint; class=class="str">"cmt">//initialized in OnInit class="type">int RSI_handle; class="type">class="kw">double RSI[]; class="type">class="kw">double Open[]; class="type">class="kw">double Close[]; class="type">int MA_handle; class="type">class="kw">double MA[]; class="type">int MA_handle2; class="type">class="kw">double MA2[]; class="type">int MA_handle3; class="type">class="kw">double MA3[]; class="type">int MA_handle4; class="type">class="kw">double MA4[]; class="type">class="kw">double Low[]; class="type">class="kw">double High[]; class="type">int MA_handle5; class="type">class="kw">double MA5[]; class="type">int MA_handle6; class="type">class="kw">double MA6[]; class="type">int MA_handle7; class="type">class="kw">double MA7[]; class=class="str">"cmt">//--- ShellExecuteW declaration ---------------------------------------------- class="macro">#class="kw">import "shell32.dll"
「给 EA 警报加上冷却与 WhatsApp 转发」
在 MT5 里跑趋势约束类 EA,最烦的是同一条信号在几秒内刷出十几条报警。下面这段代码用全局变量 last_alert_time 配合输入参数 alert_cooldown_seconds(默认 60 秒),把重复报警压住:距上次报警不足 60 秒的直接 return,读者可以把 60 改成 5 或 300 来适配自己的盯盘节奏。
myAlert() 函数按 type 分流:print 只写日志,error 带前缀写日志,indicator/info 类才触发 Alert() 弹窗和 SendNotification() 推送。若想让小布类工具替你跑这套,可把 Audible_Alerts、Push_Notifications 两个开关接进输入变量,避免硬改代码。
真正有意思的是 WhatsApp 分支:它拼出 python.exe 调用本地 send_whatsapp_message.py 的命令,再用 ShellExecuteW 拉起 cmd.exe 执行,并追加 && timeout 5 留 5 秒让脚本发完。ShellExecuteW 返回值 ≤32 表示 shell 调用失败(微软约定),EA 里应补一行 Print 报这个码,方便查路径错。路径里的 Your_Computer 必须换成你机子的实际用户名,否则 cmd 根本起不来。
外汇与贵金属杠杆高、滑点大,任何报警转发都只是辅助,信号错判可能造成实亏,上线前务必在策略测试器用历史数据跑通整条链路。
class="type">int ShellExecuteW(class="type">int hwnd, class="type">class="kw">string lpOperation, class="type">class="kw">string lpFile, class="type">class="kw">string lpParameters, class="type">class="kw">string lpDirectory, class="type">int nShowCmd); class="macro">#class="kw">import class=class="str">"cmt">//--- global variables ------------------------------------------------------ class="type">class="kw">datetime last_alert_time; input class="type">int alert_cooldown_seconds = class="num">60; class=class="str">"cmt">// Cooldown period in seconds class=class="str">"cmt">//--- myAlert function ------------------------------------------------------ class="type">void myAlert(class="type">class="kw">string type, class="type">class="kw">string message) { class="type">class="kw">datetime current_time = TimeCurrent(); if (current_time - last_alert_time < alert_cooldown_seconds) { class=class="str">"cmt">// Skip alert if within cooldown period class="kw">return; } last_alert_time = current_time; class="type">class="kw">string full_message = type + " | Trend Constraint V1.class="num">07 @ " + Symbol() + "," + IntegerToString(Period()) + " | " + message; if (type == "print") { Print(message); } else if (type == "error") { Print(type + " | Trend Constraint V1.class="num">07 @ " + Symbol() + "," + IntegerToString(Period()) + " | " + message); } else if (type == "order") { class=class="str">"cmt">// Add order alert handling if needed } else if (type == "modify") { class=class="str">"cmt">// Add modify alert handling if needed } else if (type == "indicator" || type == "info") { if (Audible_Alerts) { Alert(full_message); } if (Push_Notifications) { SendNotification(full_message); } class=class="str">"cmt">// Send to WhatsApp //Replace your_computer_name with the your actual computer name. //Make sure the path to your python and scripts is correct. class="type">class="kw">string python_path = "C:\\Users\\Your_Computer\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"; class="type">class="kw">string whatsapp_script_path = "C:\\Users\\Your_computer_name\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\send_whatsapp_message.py"; class="type">class="kw">string whatsapp_command = python_path + " \"" + whatsapp_script_path + "\" \"" + full_message + "\""; class=class="str">"cmt">// Debugging: Print the command being executed for WhatsApp Print("Executing command to send WhatsApp message: ", whatsapp_command); class=class="str">"cmt">// Use cmd.exe to execute the command and then wait for class="num">5 seconds class="type">class="kw">string final_whatsapp_command = "/c " + whatsapp_command + " && timeout class="num">5"; class="type">int whatsapp_result = ShellExecuteW(class="num">0, "open", "cmd.exe", final_whatsapp_command, NULL, class="num">1); if (whatsapp_result <= class="num">32) {
◍ 用 ShellExecuteW 外发信号并初始化箭头缓冲
这段逻辑把 MT5 指标算出的信号通过系统命令行甩给外部 Python 脚本,分别推到 WhatsApp 和 Telegram。ShellExecuteW 返回值若 ≤32 代表调用失败,此时用 GetLastError 拿系统错误码打印,便于在 MT5 专家日志里定位是路径问题还是权限问题。
Telegram 部分拼出的命令会先走 cmd.exe 的 /c 参数,末尾加 && timeout 5,意思是脚本跑完强制等 5 秒。这个等待在频繁触发的行情里可能拖慢主线程,若你用更高频率的 M1 周期,建议把 5 秒改短或改成异步。
OnInit 里给三个缓冲分别绑定箭头样式:Buffer1 用 241(向上箭头),Buffer2 用 242(向下箭头),Buffer3 留作第三状态。PLOT_DRAW_BEGIN 用 MathMax 卡住老 K 线,避免历史太长的回测把图形画到不可信区域。外汇与贵金属波动剧烈,这类外部通讯若失败可能漏发信号,实盘前务必在模拟盘验证脚本路径与返回值。
class="type">int error_code = GetLastError(); Print("Failed to execute WhatsApp Python script. Error code: ", error_code); } else { Print("Successfully executed WhatsApp Python script. Result code: ", whatsapp_result); } class=class="str">"cmt">// Send to Telegram class="type">class="kw">string telegram_script_path = "C:\\Users\\protech\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\send_telegram_message.py"; class="type">class="kw">string telegram_command = python_path + " \"" + telegram_script_path + "\" \"" + full_message + "\""; class=class="str">"cmt">// Debugging: Print the command being executed for Telegram Print("Executing command to send Telegram message: ", telegram_command); class=class="str">"cmt">// Use cmd.exe to execute the command and then wait for class="num">5 seconds class="type">class="kw">string final_telegram_command = "/c " + telegram_command + " && timeout class="num">5"; class="type">int telegram_result = ShellExecuteW(class="num">0, "open", "cmd.exe", final_telegram_command, NULL, class="num">1); if (telegram_result <= class="num">32) { class="type">int error_code = GetLastError(); Print("Failed to execute Telegram Python script. Error code: ", error_code); } else { Print("Successfully executed Telegram Python script. Result code: ", telegram_result); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { SetIndexBuffer(class="num">0, Buffer1); PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">0, PLOT_ARROW, class="num">241); SetIndexBuffer(class="num">1, Buffer2); PlotIndexSetDouble(class="num">1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">1, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">1, PLOT_ARROW, class="num">242); SetIndexBuffer(class="num">2, Buffer3); PlotIndexSetDouble(class="num">2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
信号箭头与指标句柄的初始化落点
指标绘图缓冲的收尾绑定里,第 2、3、4、5 号绘图索引分别用 PlotIndexSetInteger 设了 PLOT_DRAW_BEGIN,取值是 Bars(Symbol(),PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1 与 OMIT_OLDEST_BARS+1 的较大者,箭头代码 236、238 分别对应两套买卖标记;空值统一交给 EMPTY_VALUE,避免旧数据在图表上留噪点。 启动阶段直接调 myAlert 发一条订阅确认文案,并顺手算 myPoint:当 Digits() 是 5 或 3 时,Point() 乘 10 修正点位精度,否则用原始 Point(),这一步决定了后面挂单距离和箭头偏移是否画歪。 RSI 用 iRSI(NULL,PERIOD_CURRENT,14,PRICE_CLOSE) 拿句柄,7 周期 SMMA 与 400 周期 SMA 分别由 iMA 生成;任一 handle 小于 0 就 Print 出错信息并 return(INIT_FAILED),MT5 终端里能看到具体 GetLastError 码,外汇与贵金属品种波动大,句柄创建失败往往和离线历史不足有关,开图前先加载够 K 线再拖指标更稳。
PlotIndexSetInteger(class="num">2, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(),PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">2, PLOT_ARROW, class="num">236); SetIndexBuffer(class="num">3, Buffer4); PlotIndexSetDouble(class="num">3, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">3, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(),PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">3, PLOT_ARROW, class="num">238); SetIndexBuffer(class="num">4, Buffer5); PlotIndexSetDouble(class="num">4, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">4, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(),PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); SetIndexBuffer(class="num">5, Buffer6); PlotIndexSetDouble(class="num">5, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">5, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(),PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); class=class="str">"cmt">// Send test message on launch myAlert("info", "Thank you for subscribing. You shall be receiving Trend Constraint signal alerts via Whatsapp."); class=class="str">"cmt">//initialize myPoint myPoint = Point(); if(Digits() == class="num">5 || Digits() == class="num">3) { myPoint *= class="num">10; } RSI_handle = iRSI(NULL, PERIOD_CURRENT, class="num">14, PRICE_CLOSE); if(RSI_handle < class="num">0) { Print("The creation of iRSI has failed: RSI_handle=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle = iMA(NULL, PERIOD_CURRENT, class="num">7, class="num">0, MODE_SMMA, PRICE_CLOSE); if(MA_handle < class="num">0) { Print("The creation of iMA has failed: MA_handle=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle2 = iMA(NULL, PERIOD_CURRENT, class="num">400, class="num">0, MODE_SMA, PRICE_CLOSE); if(MA_handle2 < class="num">0) { Print("The creation of iMA has failed: MA_handle2=", INVALID_HANDLE); Print("Runtime error = ", GetLastError());