构建K线趋势约束模型(第五部分):通知系统(第三部分)·进阶篇
📲

构建K线趋势约束模型(第五部分):通知系统(第三部分)·进阶篇

(2/3)·手动刷指标漏掉信号?用API把约束模型警报直送27亿用户的聊天框

偏理论进阶 第 2/3 篇
指标在图上亮了信号,人却没盯着终端,这种漏单在波段交易里很常见。把趋势约束模型的输出接进消息API,信号生成即推送,比肉眼扫屏稳得多。外汇贵金属波动快、杠杆高,自动化通知只解决触达,不替你做进场决策。

指标外壳与报警冷却的初始化

这段声明定义了指标第 5、6 条绘图的视觉属性:第 5 条画橙色(0xFFAA00)标签「Buy」,第 6 条画蓝色(0x0000FF)实线宽 2 标签「Sell」,用于把多空信号直接叠在图表上。回看深度限制为 PLOT_MAXIMUM_BARS_BACK=5000,最旧 50 根柱被省略,避免历史缓冲爆掉。 输入参数里 RSI 超卖 30、超买 70,慢均线 200、快均线 100,构成趋势约束的基础阈值; audible 与 push 开关默认全开,说明信号既弹窗也推手机。 报警系统用 last_alert_time 配合 alert_cooldown_seconds=60 做冷却:60 秒内重复触发直接 return,防止一根毛刺 K 线连发十几条提醒。myAlert 拼出的前缀写死「Trend Constraint V1.06 @ 品种,周期」,你复制代码后若想区分多版本,得先改这串字。 ShellExecuteW 从 shell32.dll 导入,后续可能用来调外部程序;目前看只是预留接口,没在 myAlert 前半段调用。开 MT5 把这段贴进自定义指标头部,编译后先观察 Buy/Sell 线颜色与冷却间隔是否符合你的盯盘节奏。

MQL5 / C++
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"
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">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") {

「把指标信号推到 WhatsApp 与 MT5 终端」

当信号类型落在 indicator 或 info 分支时,脚本会先按 Audible_Alerts、Push_Notifications 两个开关决定是否走 MT5 内置的 Alert 与 SendNotification。若两者开启,终端弹窗与手机推送会同时触发,适合盯盘时不在电脑前的场景。 接着是一段调用外部 Python 的发消息逻辑:硬编码了 Python 3.12 的解释器路径与 send_whatsapp_message.py 脚本路径,用 ShellExecuteW 拉起 cmd.exe 执行拼接好的命令,并追加 && timeout 5 留 5 秒让进程跑完。返回码 ≤32 视为失败,会打印 GetLastError 的具体错误码;否则打印成功返回码。 OnInit 里给两个缓冲区绑了箭头样式:Buffer1 用 241(▲)、Buffer2 用 242(▼),并通过 PlotIndexSetInteger 的 PLOT_DRAW_BEGIN 控制从哪根 K 线开始画,计算式取 Bars 减去回看限制与省略最老 K 线数的较大值。外汇与贵金属波动剧烈,这类外部调用若路径写死在别人机器上会直接报错,部署前务必改成本机实际路径。

MQL5 / C++
    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\\****\\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="type">class="kw">string command = python_path + " \"" + script_path + "\" \"" + full_message + "\"";
        
        class=class="str">"cmt">// Debugging: Print the command being executed
        Print("Executing command to send WhatsApp message: ", 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_command = "/c " + command + " && timeout class="num">5";
        class="type">int result = ShellExecuteW(class="num">0, "open", "cmd.exe", final_command, NULL, class="num">1);
        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);
        }
    }
}
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);

◍ 缓冲绑定与指标句柄的初始化落点

这段初始化代码把第 2~5 号指标缓冲依次绑到 Buffer3~Buffer6,并给 2 号和 3 号缓冲指定了箭头样式 236、238,其余缓冲只设了空值与起绘位置。起绘位置统一用 MathMax(Bars-SHIFT+1, OMIT+1) 控制,避免在最旧 K 线上误绘——外汇与贵金属品种历史深度不同,这个边界值最好按实盘品种手调。 SetIndexBuffer(2, Buffer3); PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1)); PlotIndexSetInteger(2, PLOT_ARROW, 236); // 以上四行:2号缓冲绑 Buffer3,空值 EMPTY_VALUE,起绘位置取边界最大值,箭头代码 236(↓类符号)。 SetIndexBuffer(3, Buffer4); PlotIndexSetInteger(3, PLOT_ARROW, 238); // 3号缓冲绑 Buffer4,箭头 238(↑类符号),其余设置同 2 号。 SetIndexBuffer(4, Buffer5); SetIndexBuffer(5, Buffer6); // 4、5 号缓冲仅绑数据,无箭头,用作辅助线或状态记录。 myPoint 的处理值得留意:当 Digits() 返回 5 或 3(即报价尾随一位小数点的黄金/交叉盘常见),myPoint 乘 10,把点值对齐到真正的最小波动。RSI 周期写死 14、MA 用 7 期 SMMA 与 400 期 SMA,句柄创建失败会直接 Print 错误码并返回 INIT_FAILED,开 MT5 挂这个 EA 时若日志出现 iRSI/iMA failed,优先查品种是否支持当前周期。

MQL5 / C++
SetIndexBuffer(class="num">2, Buffer3);
PlotIndexSetDouble(class="num">2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
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)
  {

均线句柄的批量创建与失败拦截

在指标初始化阶段,连续调用 iMA 拿到 5 条以上均线句柄是常见做法。下面这段把 100 周期 EMA、200 周期 SMA、200 周期 EMA 以及两组外部传入周期(Fast_MA_period / Slow_MA_period)的 SMA 全部挂在当前图表周期与收盘价上。 每个句柄拿回来必须立刻判 <0:一旦返回 INVALID_HANDLE,就打印具体句柄名和 GetLastError() 的运行错误码,然后 return(INIT_FAILED)。不拦截的话,后续 OnCalculate 里 CopyBuffer 会直接读空句柄,MT5 终端可能静默算不出值。 注意 MA_handle3 用 100/0/MODE_EMA,MA_handle4 用 200/0/MODE_SMA,MA_handle7 又用 200/0/MODE_EMA——同周期不同算法共存时,句柄变量名千万别写串。开 MT5 把这段贴进 OnInit,故意改一个负周期参数,能在专家日志里看到对应 Runtime error 号。

MQL5 / C++
  MA_handle3 = iMA(NULL, PERIOD_CURRENT, class="num">100, class="num">0, MODE_EMA, PRICE_CLOSE);
  if(MA_handle3 < class="num">0)
   {
    Print("The creation of iMA has failed: MA_handle3=", INVALID_HANDLE);
    Print("Runtime error = ", GetLastError());
    class="kw">return(INIT_FAILED);
   }

  MA_handle4 = iMA(NULL, PERIOD_CURRENT, class="num">200, class="num">0, MODE_SMA, PRICE_CLOSE);
  if(MA_handle4 < class="num">0)
   {
    Print("The creation of iMA has failed: MA_handle4=", INVALID_HANDLE);
    Print("Runtime error = ", GetLastError());
    class="kw">return(INIT_FAILED);
   }

  MA_handle5 = iMA(NULL, PERIOD_CURRENT, Fast_MA_period, class="num">0, MODE_SMA, PRICE_CLOSE);
  if(MA_handle5 < class="num">0)
   {
    Print("The creation of iMA has failed: MA_handle5=", INVALID_HANDLE);
    Print("Runtime error = ", GetLastError());
    class="kw">return(INIT_FAILED);
   }

  MA_handle6 = iMA(NULL, PERIOD_CURRENT, Slow_MA_period, class="num">0, MODE_SMA, PRICE_CLOSE);
  if(MA_handle6 < class="num">0)
   {
    Print("The creation of iMA has failed: MA_handle6=", INVALID_HANDLE);
    Print("Runtime error = ", GetLastError());
    class="kw">return(INIT_FAILED);
   }

  MA_handle7 = iMA(NULL, PERIOD_CURRENT, class="num">200, class="num">0, MODE_EMA, PRICE_CLOSE);
  if(MA_handle7 < class="num">0)
   {
    Print("The creation of iMA has failed: MA_handle7=", INVALID_HANDLE);
    Print("Runtime error = ", GetLastError());
    class="kw">return(INIT_FAILED);
   }

  class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,

「多周期映射与缓冲初始化的坑」

指标 OnCalculate 入口先把六个输出缓冲全部设成序列数组(ArraySetAsSeries 置 true),否则按索引 0 取最新值会错位。第一次运行 prev_calculated 小于 1 时,必须用 ArrayInitialize 把 Buffer1~Buffer6 填成 EMPTY_VALUE,避免历史柱上残留 0 被画图当成有效信号。 多周期对齐靠 iBarShift 把当前图每根柱的时间戳映射到 M1 与 D1 的柱序号:barshift_M1[i] 和 barshift_D1[i] 分别存偏移量,循环覆盖 0 到 rates_total-1。若 CopyTime 取不到 TimeShift 直接 return rates_total,指标会原地重算不推进。 RSI 句柄校验用 BarsCalculated(RSI_handle)<=0 就 return 0,CopyBuffer 取不到也回退 rates_total。M1 的开盘价通过 CopyOpen(Symbol(),PERIOD_M1,0,rates_total,Open) 拉进来并设序列。外汇与贵金属波动受杠杆与跳空影响,这类跨周期调用在流动性稀薄时段可能返回无效偏移,实盘前建议在 MT5 用 EURUSD 的 M5 图表跑一遍看 limit 与 barshift 数组是否越界。

MQL5 / C++
class="kw">const class="type">class="kw">datetime& time[],
class="kw">const class="type">class="kw">double& open[],
class="kw">const class="type">class="kw">double& high[],
class="kw">const class="type">class="kw">double& low[],
class="kw">const class="type">class="kw">double& close[],
class="kw">const class="type">long& tick_volume[],
class="kw">const class="type">long& volume[],
class="kw">const class="type">int& spread[])
{
 class="type">int limit = rates_total - prev_calculated;
 class=class="str">"cmt">//--- counting from class="num">0 to rates_total
 ArraySetAsSeries(Buffer1, true);
 ArraySetAsSeries(Buffer2, true);
 ArraySetAsSeries(Buffer3, true);
 ArraySetAsSeries(Buffer4, true);
 ArraySetAsSeries(Buffer5, true);
 ArraySetAsSeries(Buffer6, true);
 class=class="str">"cmt">//--- initial zero
 if(prev_calculated < class="num">1)
   {
     ArrayInitialize(Buffer1, EMPTY_VALUE);
     ArrayInitialize(Buffer2, EMPTY_VALUE);
     ArrayInitialize(Buffer3, EMPTY_VALUE);
     ArrayInitialize(Buffer4, EMPTY_VALUE);
     ArrayInitialize(Buffer5, EMPTY_VALUE);
     ArrayInitialize(Buffer6, EMPTY_VALUE);
   }
 else
     limit++;
 class="type">class="kw">datetime Time[];

 class="type">class="kw">datetime TimeShift[];
 if(CopyTime(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, TimeShift) <= class="num">0) class="kw">return(rates_total);
 ArraySetAsSeries(TimeShift, true);
 class="type">int barshift_M1[];
 ArrayResize(barshift_M1, rates_total);
 class="type">int barshift_D1[];
 ArrayResize(barshift_D1, rates_total);
 for(class="type">int i = class="num">0; i < rates_total; i++)
   {
     barshift_M1[i] = iBarShift(Symbol(), PERIOD_M1, TimeShift[i]);
     barshift_D1[i] = iBarShift(Symbol(), PERIOD_D1, TimeShift[i]);
   }
 if(BarsCalculated(RSI_handle) <= class="num">0)
     class="kw">return(class="num">0);
 if(CopyBuffer(RSI_handle, class="num">0, class="num">0, rates_total, RSI) <= class="num">0) class="kw">return(rates_total);
 ArraySetAsSeries(RSI, true);
 if(CopyOpen(Symbol(), PERIOD_M1, class="num">0, rates_total, Open) <= class="num">0) class="kw">return(rates_total);
 ArraySetAsSeries(Open, true);

◍ 多均线句柄的缓冲区拷贝与序列对齐

指标计算开头这一段,本质是把日线收盘价、当前周期高低价、以及 7 条均线(MA_handle 到 MA_handle7)的缓冲区一次性拉进本地数组。任何一次 Copy 或 BarsCalculated 返回小于等于 0,就直接 return,避免后续用空数组算出鬼画符。 日线收盘价用 CopyClose(Symbol(), PERIOD_D1, 0, rates_total, Close),当前周期高低价则分别用 CopyLow / CopyHigh 配 PERIOD_CURRENT。时间轴用 CopyTime(Symbol(), Period(), 0, rates_total, Time) 取,注意 Period() 返回的就是当前图表周期枚举。 每条均线都先判 BarsCalculated(handle) 是否就绪,再用 CopyBuffer(handle, 0, 0, rates_total, MAx) 把主缓冲拷进来。7 条线意味着最少 7 次句柄有效性检查——若你只挂了 3 条均线却写了 7 个 handle,编译不报错但运行会卡在 return(0)。 所有数组拷完都接一句 ArraySetAsSeries(..., true),把索引 0 钉在最新一根 K 线上。这样写 MA[0]、Close[0] 指的就是当前 bar,回测和实时计算才不会错位。外汇与贵金属杠杆高,这类多缓冲指标在跳空时可能拷贝不完整,上 MT5 用样例品种跑一遍才能确认边界行为。

MQL5 / C++
  if(CopyClose(Symbol(), PERIOD_D1, class="num">0, rates_total, Close) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(Close, true);
  if(BarsCalculated(MA_handle) <= class="num">0)
      class="kw">return(class="num">0);
  if(CopyBuffer(MA_handle, class="num">0, class="num">0, rates_total, MA) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(MA, true);
  if(BarsCalculated(MA_handle2) <= class="num">0)
      class="kw">return(class="num">0);
  if(CopyBuffer(MA_handle2, class="num">0, class="num">0, rates_total, MA2) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(MA2, true);
  if(BarsCalculated(MA_handle3) <= class="num">0)
      class="kw">return(class="num">0);
  if(CopyBuffer(MA_handle3, class="num">0, class="num">0, rates_total, MA3) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(MA3, true);
  if(BarsCalculated(MA_handle4) <= class="num">0)
      class="kw">return(class="num">0);
  if(CopyBuffer(MA_handle4, class="num">0, class="num">0, rates_total, MA4) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(MA4, true);
  if(CopyLow(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, Low) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(Low, true);
  if(CopyHigh(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, High) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(High, true);
  if(BarsCalculated(MA_handle5) <= class="num">0)
      class="kw">return(class="num">0);
  if(CopyBuffer(MA_handle5, class="num">0, class="num">0, rates_total, MA5) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(MA5, true);
  if(BarsCalculated(MA_handle6) <= class="num">0)
      class="kw">return(class="num">0);
  if(CopyBuffer(MA_handle6, class="num">0, class="num">0, rates_total, MA6) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(MA6, true);
  if(BarsCalculated(MA_handle7) <= class="num">0)
      class="kw">return(class="num">0);
  if(CopyBuffer(MA_handle7, class="num">0, class="num">0, rates_total, MA7) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(MA7, true);
  if(CopyTime(Symbol(), Period(), class="num">0, rates_total, Time) <= class="num">0) class="kw">return(rates_total);
  ArraySetAsSeries(Time, true);
  class=class="str">"cmt">//--- main loop
让小布替你跑这套
这些诊断与推送逻辑小布盯盘已内置,打开对应品种页即可看到约束模型信号与提醒,不必自己搭Python服务。把重复劳动交给小布,你专注决策。

常见问题

通常需在开发者后台创建商业账号拿到 token,并在脚本环境配置网络访问与证书;MT5 侧用脚本调用本地服务转发消息,不直接内置聊天协议。
误报可能来自约束阈值过宽或品种波动特性不匹配,建议在测试阶段用历史数据回看信号频次,再调紧过滤条件,概率上降低噪音。
小布盯盘内置品种页的信号与提醒功能,覆盖常见通知场景,无需自建API;若需私有化推送可参考本文思路做二次开发。
按流程回退到环境配置阶段,核对 token 有效性、本机出网策略及脚本日志,多数故障集中在凭证过期或端口拦截。
二者均走消息API且阶段相似,主要区别在账号体系与限流规则,本文第二部分已拆解 Telegram,本篇聚焦 WhatsApp 特有配置。