构建K线趋势约束模型(第五部分):通知系统(第三部分)·综合运用
📲

构建K线趋势约束模型(第五部分):通知系统(第三部分)·综合运用

(3/3)· 前两层只解决信号生成,本篇把约束模型接进27亿用户的聊天框,少写一行都收不到提醒

含代码示例实战向 第 3/3 篇
很多交易者把指标跑通就以为通知系统完成了,结果实盘漏信号才翻日志。WhatsApp集成不是填个token就完事,消息API、脚本权限和安全白名单任一处错配都会静默失败。

◍ 多周期RSI与均线交叉的报警逻辑拆解

这段循环从 limit-1 倒序扫到 0,先跳过过旧的数据以防数组越界或拖慢计算;当 i 超过 MathMin(PLOT_MAXIMUM_BARS_BACK-1, rates_total-1-OMIT_OLDEST_BARS) 时直接 continue。接着校验 barshift_M1 与 barshift_D1 是否落在合法区间,任一越界就跳过该根 K 线。 买入信号(Buffer1)要求 RSI[i] 上穿 Oversold 且前一根还在超卖下方,同时 M1 的 Open 不低于 D1 前一根 Close,并且两组均线 MA>MA2、MA3>MA4 同时成立;命中后在 Low[1+i] 写值,若 i==1 且时间不等于上次报警则弹 Buy 提醒。 卖出信号(Buffer2)对称处理:RSI 下穿 Overbought、M1 Open 不高于 D1 前收、均线空头排列,写 High[1+i] 并可能触发 Sell 弹窗。 第三段缓冲 Buffer3 只看 MA5 与 MA6 的金叉(MA5[i]>MA6[i] 且前根反向),在 Low[i] 标记并可能报警 Buy Reversal。外汇与贵金属波动剧烈,这类多条件共振只提高概率,实盘须自行验证。

MQL5 / C++
for(class="type">int i = limit-class="num">1; i >= class="num">0; i--)
  {
    if (i >= MathMin(PLOT_MAXIMUM_BARS_BACK-class="num">1, rates_total-class="num">1-OMIT_OLDEST_BARS)) class="kw">continue; class=class="str">"cmt">//omit some old rates to prevent "Array out of range" or slow calculation  
    
    if(barshift_M1[i] < class="num">0 || barshift_M1[i] >= rates_total) class="kw">continue;
    if(barshift_D1[i] < class="num">0 || barshift_D1[i] >= rates_total) class="kw">continue;
    
    class=class="str">"cmt">//Indicator Buffer class="num">1
    if(RSI[i] < Oversold
    && RSI[i+class="num">1] > Oversold class=class="str">"cmt">//Relative Strength Index crosses below fixed value
    && Open[barshift_M1[i]] >= Close[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Open >= Candlestick Close
    && MA[i] > MA2[i] class=class="str">"cmt">//Moving Average > Moving Average
    && MA3[i] > MA4[i] class=class="str">"cmt">//Moving Average > Moving Average
    )
      {
       Buffer1[i] = Low[class="num">1+i]; class=class="str">"cmt">//Set indicator value at Candlestick Low
       if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Buy"); class=class="str">"cmt">//Alert on next bar open
       time_alert = Time[class="num">1];
       }
    else
      {
       Buffer1[i] = EMPTY_VALUE;
       }
    class=class="str">"cmt">//Indicator Buffer class="num">2
    if(RSI[i] > Overbought
    && RSI[i+class="num">1] < Overbought class=class="str">"cmt">//Relative Strength Index crosses above fixed value
    && Open[barshift_M1[i]] <= Close[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Open <= Candlestick Close
    && MA[i] < MA2[i] class=class="str">"cmt">//Moving Average < Moving Average
    && MA3[i] < MA4[i] class=class="str">"cmt">//Moving Average < Moving Average
    )
      {
       Buffer2[i] = High[class="num">1+i]; class=class="str">"cmt">//Set indicator value at Candlestick High
       if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Sell"); class=class="str">"cmt">//Alert on next bar open
       time_alert = Time[class="num">1];
       }
    else
      {
       Buffer2[i] = EMPTY_VALUE;
       }
    class=class="str">"cmt">//Indicator Buffer class="num">3
    if(MA5[i] > MA6[i]
    && MA5[i+class="num">1] < MA6[i+class="num">1] class=class="str">"cmt">//Moving Average crosses above Moving Average
    )
      {
       Buffer3[i] = Low[i]; class=class="str">"cmt">//Set indicator value at Candlestick Low
       if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Buy Reversal"); class=class="str">"cmt">//Alert on next bar open
       time_alert = Time[class="num">1];
       }
    else
      {
       Buffer3[i] = EMPTY_VALUE;
       }

「死叉反转动量的缓冲与静音写法」

这段代码把均线下穿的判定直接写进指标缓冲,MA5 在 i 位置低于 MA6、且前一根 i+1 位置 MA5 高于 MA6,即视为一次均线下穿反转。触发时 Buffer4 取该 K 线最高价,并在下一根 Bar 开盘(i==1)且时间戳未重复时弹「Sell Reversal」警报,外汇与贵金属品种在此类信号后反转概率偏高但伴随明显滑点风险。 Buffer5 与 Buffer6 则只做可视化不报警:MA3 上穿或下穿 MA7 时分别把 MA3 值写入对应缓冲,原警报代码整段注释掉。这种「用注释代替删除」的方式,方便你之后随时解开注释恢复 Buy/Sell 提示,而不用重写逻辑。 逐行看更直观: //Indicator Buffer 4 if(MA5[i] < MA6[i] && MA5[i+1] > MA6[i+1] //Moving Average crosses below Moving Average ) { Buffer4[i] = High[i]; //Set indicator value at Candlestick High if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Sell Reversal"); //Alert on next bar open time_alert = Time[1]; } else { Buffer4[i] = EMPTY_VALUE; } //Indicator Buffer 5, Alert muted by turning it into a comment if(MA3[i] > MA7[i] //Moving Average > Moving Average ) { Buffer5[i] = MA3[i]; //Set indicator value at Moving Average //if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Buy"); //Alert on next bar open //time_alert = Time[1]; } else { Buffer5[i] = EMPTY_VALUE; } //Indicator Buffer 6, Alert muted by turning it into a comment if(MA3[i] < MA7[i] //Moving Average < Moving Average ) { Buffer6[i] = MA3[i]; //Set indicator value at Moving Average //if(i == 1 && Time[1] != time_alert) myAlert("indicator", "Sell"); //Alert on next bar open //time_alert = Time[1]; } else { Buffer6[i] = EMPTY_VALUE; } 开 MT5 把这段贴进自定义指标,先只留 Buffer4 报警跑一周,确认信号密度是否适合你的盯盘节奏,再决定是否解开 Buffer5/6 的注释。

MQL5 / C++
class=class="str">"cmt">//Indicator Buffer class="num">4
if(MA5[i] < MA6[i]
&& MA5[i+class="num">1] > MA6[i+class="num">1] class=class="str">"cmt">//Moving Average crosses below Moving Average
)
   {
    Buffer4[i] = High[i]; class=class="str">"cmt">//Set indicator value at Candlestick High
    if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Sell Reversal"); class=class="str">"cmt">//Alert on next bar open
    time_alert = Time[class="num">1];
   }
else
   {
    Buffer4[i] = EMPTY_VALUE;
   }
class=class="str">"cmt">//Indicator Buffer class="num">5, Alert muted by turning it into a comment
if(MA3[i] > MA7[i] class=class="str">"cmt">//Moving Average > Moving Average
)
   {
    Buffer5[i] = MA3[i]; class=class="str">"cmt">//Set indicator value at Moving Average
    class=class="str">"cmt">//if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Buy"); //Alert on next bar open
    class=class="str">"cmt">//time_alert = Time[class="num">1];
   }
else
   {
    Buffer5[i] = EMPTY_VALUE;
   }
class=class="str">"cmt">//Indicator Buffer class="num">6, Alert muted by turning it into a comment
if(MA3[i] < MA7[i] class=class="str">"cmt">//Moving Average < Moving Average
)
   {
    Buffer6[i] = MA3[i]; class=class="str">"cmt">//Set indicator value at Moving Average
    class=class="str">"cmt">//if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Sell"); //Alert on next bar open
    class=class="str">"cmt">//time_alert = Time[class="num">1];
   }
else
   {
    Buffer6[i] = EMPTY_VALUE;
   }

隐藏那个碍事的命令行窗口

前面两节把指标跑通了,Boom 500 指数的图形绘制和社交媒体信号推送都按预期工作。程序启动时会弹出一个命令提示符窗口,用来显示进程并发送启动通知,信号随时间的传输也走同一套逻辑。 但这个弹窗在处理任务时确实碍事。本项目为了观察进程故意没隐藏它;若你想让它在后台静默运行,只改一个参数就行。 ShellExecuteW 的最后一个参数 nShowCmd 控制窗口显示方式。原文传 1(SW_SHOWNORMAL)会正常弹出,改成 0(SW_HIDE)即可彻底不显示窗口。下面这段是可直接抄进 EA 的调用写法,高亮处就是你要动的地方。

MQL5 / C++
class=class="str">"cmt">// execute the command without showing the window
class="type">class="kw">string final_command = "/c " + command + " && timeout class="num">5";
class="type">int result = ShellExecuteW(class="num">0, "open", "cmd.exe", final_command, NULL, class="num">0); class=class="str">"cmt">// Change the last parameter to class="num">0 (SW_HIDE)
if (result <= class="num">32) {
    class="type">int error_code = GetLastError();
    Print("Failed to execute Python script. Error code: ", error_code);
} else {
    Print("Successfully executed Python script. Result code: ", result);
}
逐行拆一下:final_command 拼了 /c 前缀和 timeout 5,让 cmd 执行完命令后等 5 秒再退;ShellExecuteW 第六个实参写 0 就是 SW_HIDE,窗口不出现;返回值 ≤32 代表 shell 调用出错,用 GetLastError 拿系统错误码,>32 则算调起成功。外汇与贵金属信号自动化涉及高风险,静默运行后你失去肉眼监工窗口,建议在日志里保留 Print 输出以便回溯。

MQL5 / C++
class=class="str">"cmt">// execute the command without showing the window
class="type">class="kw">string final_command = "/c " + command + " && timeout class="num">5";
class="type">int result = ShellExecuteW(class="num">0, "open", "cmd.exe", final_command, NULL, class="num">0); class=class="str">"cmt">// Change the last parameter to class="num">0 (SW_HIDE)
if (result <= class="num">32) {
    class="type">int error_code = GetLastError();
    Print("Failed to execute Python script. Error code: ", error_code);
} else {
    Print("Successfully executed Python script. Result code: ", result);
}

◍ DLL 调用下的暗坑与隔离打法

在 MT5 里用 DLL 接外部程序(比如调 Python 发消息)不是点几下就完事。DLL 本身是被标记为潜在风险的加载项,研究下来主要有几类实打实的问题:恶意代码执行、DLL 劫持(攻击者把同名恶意文件丢进加载目录就能跑任意代码,高权限下尤其致命)、参数错乱引发终端崩溃、资源没释放拖慢系统、版本签名不一致直接兼容性报废,还有多 DLL 依赖缠一起的「依赖地狱」。 API 凭证是另一条高压线。脚本路径、Python 路径里若夹带账号 token,一旦外泄等于把门钥匙送人。上面示例里路径用 * 隐去用户名只是底线操作,凭证只该留在本机受信任环境,绝不对公开群聊露底。 代码层能挡掉一部分注入风险:在拼完命令串后插一行 SecureExecuteCommand(command);,它会扫命令里的非法字符,避免拼接出的字符串被拿去执行越权指令。下面这段就是接 Python 发 WhatsApp 消息的落地片段,路径按你本机 Python312 实际位置改。

MQL5 / C++
class=class="str">"cmt">// You can secure the program by adding SecureExecuteCommand(command); below these lines.
class="type">class="kw">string python_path = "C:\\Users\\*****\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
class="type">class="kw">string script_path = "C:\\Users\\*****\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\send_whatsapp_message.py"; class=class="str">"cmt">//***** is your computer user name, the rest of the path txt is correct based on the python version being used.
class="type">class="kw">string message = "Hello, this is a test message";
class="type">class="kw">string command = python_path + " \"" + script_path + "\" \"" + message + "\"";
SecureExecuteCommand(command);
逐行拆:第 1 行注释提醒把安全函数加在下面;2–3 行定义 Python 解释器和脚本绝对路径,* 换成你系统用户名;4 行是待发文本;5 行把三者拼成一条 shell 命令;最后高亮行做字符校验再执行。 运行原则上,只装签名过的 DLL、以最小权限起终端、输入先清洗、出错写日志、定期打补丁,必要时丢沙箱跑——外汇贵金属杠杆高,这类自动通道出事就是实盘连带风险,隔离和最小权限不是可选项。

MQL5 / C++
class=class="str">"cmt">// You can secure the program by adding SecureExecuteCommand(command); below these lines.
class="type">class="kw">string python_path = "C:\Users\*****\AppData\Local\Programs\Python\Python312\python.exe";
class="type">class="kw">string script_path = "C:\Users\*****\AppData\Local\Programs\Python\Python312\Scripts\send_whatsapp_message.py"; class=class="str">"cmt">//***** is your computer user name, the rest of the path txt is correct based on the python version being used.
class="type">class="kw">string message = "Hello, this is a test message";
class="type">class="kw">string command = python_path + " \"" + script_path + "\" \"" + message + "\"";
SecureExecuteCommand(command);

「双平台信号的到达时差」

把信号广播同时接进 Telegram 和 WhatsApp 已经跑通,MetaTrader 5 一侧触发后两边都能收到。实测下来推送通知比社交平台早到几秒,两个社交平台之间也有微小梯度:Telegram 通常比 WhatsApp 快几秒,整体时差大约 7 秒量级,对手动跟单影响有限,但做自动化执行时要留这个缓冲。 附件里 send_whatsapp_message.py 走的是 Twilio 桥接方案,本机需配好凭证和目录;Trend Constraint V1.06.mq5 已内置 WhatsApp 出口,换机器先改路径和 token。Secure Command.mq5 做了隐藏命令行窗口的改动,能挡掉一部分外部注入。 外汇和贵金属本身高杠杆、高风险,信号迟到或漏推都可能让你在错误价位成交,实盘前务必在模拟账户验证整条链路。 下个版本打算在现有广播层上加一层本地队列,避免 MT5 重启时丢信号——这块等你跑通了咱们再聊具体实现。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到约束模型触发与推送状态,你不必自己维护Python脚本和API密钥。

常见问题

WhatsApp用户基数更大、日常打开率高,但API审核更严且需商业账号或第三方网关;Telegram机器人接入更轻量,适合开发者自测。
通常通过本地HTTP服务或消息队列解耦,指标只负责发本地请求,脚本异步外发,避免 tick 事件里直接联网拖慢终端。
先用沙箱号码或只读测试群组跑历史回放,比对指标日志与聊天记录条数,确认延迟和丢失率后再切正式接收人。
小布盯盘已内置品种页信号与AIGC诊断,推送渠道由平台统一接管,你无需自己写脚本即可在终端收到约束触发提醒。
明文落盘风险高,应使用环境变量或加密配置,并限制VPS出入站规则,仅允许访问消息网关域名。