构建K线趋势约束模型(第五部分):通知系统(第三部分)·综合运用
(3/3)· 前两层只解决信号生成,本篇把约束模型接进27亿用户的聊天框,少写一行都收不到提醒
◍ 多周期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。外汇与贵金属波动剧烈,这类多条件共振只提高概率,实盘须自行验证。
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 的注释。
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 的调用写法,高亮处就是你要动的地方。
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); }
/c 前缀和 timeout 5,让 cmd 执行完命令后等 5 秒再退;ShellExecuteW 第六个实参写 0 就是 SW_HIDE,窗口不出现;返回值 ≤32 代表 shell 调用出错,用 GetLastError 拿系统错误码,>32 则算调起成功。外汇与贵金属信号自动化涉及高风险,静默运行后你失去肉眼监工窗口,建议在日志里保留 Print 输出以便回溯。
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 实际位置改。
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);
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 重启时丢信号——这块等你跑通了咱们再聊具体实现。