MQL5 细则手册:在 MQL5 中开发多交易品种波动指标·进阶篇
📊

MQL5 细则手册:在 MQL5 中开发多交易品种波动指标·进阶篇

(2/3)·跨品种 ATR 同步卡在柱起点?六个交易品种一键诊断的 MQL5 进阶写法

含代码示例实战向 第 2/3 篇
写多交易品种指标时,常以为 CopyBuffer 直接拉别的品种数据就能用,结果柱对齐错位、句柄返回空值。先想清楚“真实柱起点”和品种同步,再动手填代码,能省掉大半调试时间。

◍ 指标子窗口与画布的初始变量怎么定

在 MT5 写自定义指标时,先把子窗口和画布相关的全局变量一次性声明好,后面绘图逻辑才不会到处找坐标。下面这段声明来自一个 ATR 可视化指标的骨架,可以直接抄进你的工程改前缀。

MQL5 / C++
class="type">class="kw">string empty_symbol="EMPTY";
class=class="str">"cmt">//--- Indicator subwindow properties
class="type">int subwindow_number=WRONG_VALUE; class=class="str">"cmt">// Subwindow number
class="type">int chart_width=class="num">0; class=class="str">"cmt">// Chart width
class="type">int subwindow_height=class="num">0; class=class="str">"cmt">// Subwindow height
class="type">int last_chart_width=class="num">0; class=class="str">"cmt">// Last saved chart width
class="type">int last_subwindow_height=class="num">0; class=class="str">"cmt">// Last saved subwindow height
class="type">int subwindow_center_x=class="num">0; class=class="str">"cmt">// Horizontal center of the subwindow
class="type">int subwindow_center_y=class="num">0; class=class="str">"cmt">// Vertical center of the subwindow
class="type">class="kw">string subwindow_shortname="MS_ATR"; class=class="str">"cmt">// Short name of the indicator
class="type">class="kw">string prefix=subwindow_shortname+"_"; class=class="str">"cmt">// Prefix for objects
class=class="str">"cmt">//--- Canvas properties
class="type">class="kw">string canvas_name=prefix+"canvas"; class=class="str">"cmt">// Canvas name
class="type">class="kw">color canvas_background=clrBlack; class=class="str">"cmt">// Canvas background class="type">class="kw">color
class="type">uchar canvas_opacity=class="num">190; class=class="str">"cmt">// Opacity
class="type">int font_size=class="num">16; class=class="str">"cmt">// Font size
class="type">class="kw">string font_name="Calibri"; class=class="str">"cmt">// Font
ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_RAW; class=class="str">"cmt">// Color components should be correctly set by the user
class=class="str">"cmt">//--- Canvas messages
逐行拆一下关键点:empty_symbol 存个 "EMPTY" 字符串,常用来标记未绑定品种;subwindow_number 初始化成 WRONG_VALUE(-1),意味着还没挂到具体子窗口,得等 OnInit 里用 ChartWindowFind 之类补上。chart_width、subwindow_height 及 last_* 那组全置 0,是用来在 OnChartEvent 里比对尺寸变化、决定要不要重绘画布的。 subwindow_shortname 写成 "MS_ATR",prefix 自动拼成 "MS_ATR_",所有图形对象名都带这个前缀,避免和图表上别的指标对象重名。canvas_name 即 "MS_ATR_canvas",canvas_background 用 clrBlack,canvas_opacity 设 190(0~255 区间,越接近 255 越不透明),font_size 16、font_name "Calibri" 管文字渲染。clr_format 选 COLOR_FORMAT_ARGB_RAW,说明颜色分量要你自己按 ARGB 字节序填,别指望系统帮你转换。 外汇和贵金属图表加载这类自定义指标属高风险环境,参数没配对可能导致子窗口不显示或频繁重绘吃 CPU。开 MT5 新建一个指标工程,把上面变量原样贴进全局区,把 MS_ATR 改成你自己的缩写,就能跑通第一层初始化。

MQL5 / C++
class="type">class="kw">string empty_symbol="EMPTY";
class=class="str">"cmt">//--- Indicator subwindow properties
class="type">int subwindow_number=WRONG_VALUE; class=class="str">"cmt">// Subwindow number
class="type">int chart_width=class="num">0; class=class="str">"cmt">// Chart width
class="type">int subwindow_height=class="num">0; class=class="str">"cmt">// Subwindow height
class="type">int last_chart_width=class="num">0; class=class="str">"cmt">// Last saved chart width
class="type">int last_subwindow_height=class="num">0; class=class="str">"cmt">// Last saved subwindow height
class="type">int subwindow_center_x=class="num">0; class=class="str">"cmt">// Horizontal center of the subwindow
class="type">int subwindow_center_y=class="num">0; class=class="str">"cmt">// Vertical center of the subwindow
class="type">class="kw">string subwindow_shortname="MS_ATR"; class=class="str">"cmt">// Short name of the indicator
class="type">class="kw">string prefix=subwindow_shortname+"_"; class=class="str">"cmt">// Prefix for objects
class=class="str">"cmt">//--- Canvas properties
class="type">class="kw">string canvas_name=prefix+"canvas"; class=class="str">"cmt">// Canvas name
class="type">class="kw">color canvas_background=clrBlack; class=class="str">"cmt">// Canvas background class="type">class="kw">color
class="type">uchar canvas_opacity=class="num">190; class=class="str">"cmt">// Opacity
class="type">int font_size=class="num">16; class=class="str">"cmt">// Font size
class="type">class="kw">string font_name="Calibri"; class=class="str">"cmt">// Font
ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_RAW; class=class="str">"cmt">// Color components should be correctly set by the user
class=class="str">"cmt">//--- Canvas messages

初始化阶段的状态字符串与守卫逻辑

在 MT5 自定义指标里,把用户可能看到的等待/报错提示先固化成字符串变量,是避免运行时拼字符串卡顿的常见做法。下面这组声明把句柄无效、数据准备中、未同步等状态都预存下来,其中 msg_load_data、msg_sync_update、msg_last 初始留空,意味着后续由同步流程动态填充。

MQL5 / C++
class="type">class="kw">string msg_invalid_handle ="Invalid indicator handle! Please wait...";
class="type">class="kw">string msg_prepare_data ="Preparing data! Please wait...";
class="type">class="kw">string msg_not_synchronized ="Unsynchronized data! Please wait...";
class="type">class="kw">string msg_load_data ="";
class="type">class="kw">string msg_sync_update ="";
class="type">class="kw">string msg_last ="";
class=class="str">"cmt">//--- Maximum number of bars specified in the terminal settings
class="type">int terminal_max_bars=class="num">0;
OnInit 里先调 CheckInputParameters 做输入守卫:若 IndicatorPeriod 大于 500 直接返回 INIT_PARAMETERS_INCORRECT,并在图表上 Comment 提示「Decrease the indicator period!」,这条硬上限能拦掉多数历史深度爆内存的写法。
MQL5 / C++
class="type">bool CheckInputParameters()
  {
   if(IndicatorPeriod>class="num">500)
     {
      Comment("Decrease the indicator period! Indicator Period: ",IndicatorPeriod,"; Limit: class="num">500;");
      printf("Decrease the indicator period! Indicator Period: %d; Limit: %d;",IndicatorPeriod,class="num">500);
      class="kw">return(class="kw">false);
     }
   class="kw">return(true);
  }
初始化主体用 EventSetTimer(1) 挂了 1 秒定时器,随后依次做字体、符号名、水平位、句柄等初始化,最后读 TerminalInfoInteger(TERMINAL_MAXBARS) 存进 terminal_max_bars。开 MT5 把 IndicatorPeriod 设成 501 编译加载,能立刻看到图表注释报错而非常规指标线——这就是守卫生效的可验证现象。外汇与贵金属品种波动大、点差跳变频繁,这类初始化失败若不打日志,实盘排查成本偏高,建议保留 printf 输出。

MQL5 / C++
class="type">class="kw">string msg_invalid_handle ="Invalid indicator handle! Please wait...";
class="type">class="kw">string msg_prepare_data ="Preparing data! Please wait...";
class="type">class="kw">string msg_not_synchronized ="Unsynchronized data! Please wait...";
class="type">class="kw">string msg_load_data ="";
class="type">class="kw">string msg_sync_update ="";
class="type">class="kw">string msg_last ="";
class=class="str">"cmt">//--- Maximum number of bars specified in the terminal settings
class="type">int terminal_max_bars=class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Check class="kw">input parameters for correctness
   if(!CheckInputParameters())
      class="kw">return(INIT_PARAMETERS_INCORRECT);
class=class="str">"cmt">//--- Set the timer at class="num">1-second intervals
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Set the font to be displayed on the canvas
   canvas.FontSet(font_name,font_size,FW_NORMAL);
class=class="str">"cmt">//--- Initialization of arrays
   InitArrays();
class=class="str">"cmt">//--- Initialize the array of symbols 
   InitSymbolNames();
class=class="str">"cmt">//--- Initialize the array of levels
   InitLevels();
class=class="str">"cmt">//--- Get indicator handles
   GetIndicatorHandles();
class=class="str">"cmt">//--- Set indicator properties
   SetIndicatorProperties();
class=class="str">"cmt">//--- Get the number of bars specified in the terminal settings
   terminal_max_bars=TerminalInfoInteger(TERMINAL_MAXBARS);
class=class="str">"cmt">//--- Clear the comment
   Comment("");
class=class="str">"cmt">//--- Refresh the chart
   ChartRedraw();
class=class="str">"cmt">//--- Initialization completed successfully
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checking class="kw">input parameters for correctness                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CheckInputParameters()
  {
   if(IndicatorPeriod>class="num">500)
     {
      Comment("Decrease the indicator period! Indicator Period: ",IndicatorPeriod,"; Limit: class="num">500;");
      printf("Decrease the indicator period! Indicator Period: %d; Limit: %d;",IndicatorPeriod,class="num">500);
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//---
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| First initialization of arrays                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InitArrays()
  {
   ArrayInitialize(limit_time,NULL);
   ArrayInitialize(series_first_date,NULL);
   ArrayInitialize(series_first_date_last,NULL);

「多品种指标句柄的初始化套路」

多符号监控面板在 MT5 里第一道关,是把句柄和缓冲数组先置脏。ArrayInitialize(symbol_handles,INVALID_HANDLE) 把 6 个品种的指标句柄全填成 INVALID_HANDLE,随后 for 循环里对 atr_buffers[s].data 逐个填 EMPTY_VALUE,避免首根 K 线前读到野指针。 品种名与阈值都走独立初始化函数。InitSymbolNames 把 _Symbol 及 Symbol02~Symbol06 共 6 个字符串塞进 symbol_names 数组,InitLevels 则把 Level01~Level06 这 6 个外部参数写进 indicator_levels,数量硬编码为 6,改品种数得同步动这三处。 AddSymbolToMarketWatch 是真正落盘的逻辑:先 SymbolsTotal(false) 拿服务端全部品种数,再 for 遍历 SymbolName(i,false) 比对,命中后 SymbolSelect(name,true) 把品种加进市场报价窗,返回名;未命中返回 empty_symbol。这样即便某个品种券商没提供,面板也不会崩,只是留空位。 别把 6 这个数写死在脑里 若你接的手稿 SYMBOLS_COUNT 不是 6,InitSymbolNames 和 InitLevels 的数组下标必须跟着改,否则编译期越界或运行期漏初始化都可能发生。开 MT5 把 Symbol02~06 换成你盯的 XAUUSD、EURUSD 之类,跑一遍看市场报价窗是否多出对应行。

MQL5 / C++
  ArrayInitialize(symbol_handles,INVALID_HANDLE);
class=class="str">"cmt">//---
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
      ArrayInitialize(atr_buffers[s].data,EMPTY_VALUE);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initializing array of symbols                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InitSymbolNames()
  {
  symbol_names[class="num">0]=AddSymbolToMarketWatch(_Symbol);
  symbol_names[class="num">1]=AddSymbolToMarketWatch(Symbol02);
  symbol_names[class="num">2]=AddSymbolToMarketWatch(Symbol03);
  symbol_names[class="num">3]=AddSymbolToMarketWatch(Symbol04);
  symbol_names[class="num">4]=AddSymbolToMarketWatch(Symbol05);
  symbol_names[class="num">5]=AddSymbolToMarketWatch(Symbol06);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initializing array of levels                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InitLevels()
  {
  indicator_levels[class="num">0]=Level01;
  indicator_levels[class="num">1]=Level02;
  indicator_levels[class="num">2]=Level03;
  indicator_levels[class="num">3]=Level04;
  indicator_levels[class="num">4]=Level05;
  indicator_levels[class="num">5]=Level06;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Adding the specified symbol to the Market Watch window                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string AddSymbolToMarketWatch(class="type">class="kw">string symbol)
  {
  class="type">int      total=class="num">0; class=class="str">"cmt">// Number of symbols
  class="type">class="kw">string   name=""; class=class="str">"cmt">// Symbol name
class=class="str">"cmt">//--- If an empty class="type">class="kw">string is passed, class="kw">return the empty class="type">class="kw">string
  if(symbol=="")
     class="kw">return(empty_symbol);
class=class="str">"cmt">//--- Total symbols on the server
  total=SymbolsTotal(class="kw">false);
class=class="str">"cmt">//--- Iterate over the entire list of symbols
  for(class="type">int i=class="num">0;i<total;i++)
     {
     class=class="str">"cmt">//--- Symbol name on the server
     name=SymbolName(i,class="kw">false);
     class=class="str">"cmt">//--- If this symbol is available,
     if(name==symbol)
        {
        class=class="str">"cmt">//--- add it to the Market Watch window and
        SymbolSelect(name,true);
        class=class="str">"cmt">//--- class="kw">return its name
        class="kw">return(name);
        }
     }
class=class="str">"cmt">//--- If this symbol is not available, class="kw">return the class="type">class="kw">string representing the lack of the symbol
  class="kw">return(empty_symbol);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Getting indicator handles                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetIndicatorHandles()
  {
class=class="str">"cmt">//--- An indication of all handles being valid
  class="type">bool valid_handles=true;
class=class="str">"cmt">//--- Iterate over all symbols in a loop and ...

◍ 多品种 ATR 句柄获取与绘图属性初始化

在 MT5 里做跨品种监控,第一步是把每个交易品种的 ATR 指标句柄拿到手。下面这段循环会遍历 symbol_names 数组,跳过空品种名,只在对应句柄还是 INVALID_HANDLE 时才调用 iATR 重新获取;若某个品种连续拿不到句柄,就把 valid_handles 置为 false,并返回让上层在画布上提示「句柄无效」。 for(int s=0; s<SYMBOLS_COUNT; s++) { //--- 若品种名非空(可用) if(symbol_names[s]!=empty_symbol) { //--- 且当前品种句柄无效时 if(symbol_handles[s]==INVALID_HANDLE) { //--- 获取该品种 ATR 句柄,周期为当前图表周期 Period(),周期为 IndicatorPeriod symbol_handles[s]=iATR(symbol_names[s],Period(),IndicatorPeriod); //--- 若仍拿不到,下次再试,并标记整体句柄无效 if(symbol_handles[s]==INVALID_HANDLE) valid_handles=false; } } } //--- 若有任一品种句柄获取失败,打印消息并画布提示 if(!valid_handles) { msg_last=msg_invalid_handle; ShowCanvasMessage(msg_invalid_handle); } //--- 返回句柄是否有效 return(valid_handles); } 拿到句柄后,SetIndicatorProperties 负责把绘图层收拾干净。它先用 IndicatorSetString 设短名、IndicatorSetInteger 对齐 _Digits 小数位;随后对 0 到 SYMBOLS_COUNT-1 的每个索引,分别绑定 atr_buffers[s].data 为数据缓冲、设标签为「ATR (序号, 品种名)」、画线类型 DRAW_LINE、线宽固定 1、颜色取 line_colors[s]、空值用 EMPTY_VALUE 占位。 void SetIndicatorProperties() { //--- 设短名 IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname); //--- 设小数位 IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- 绑定各品种数据缓冲 for(int s=0; s<SYMBOLS_COUNT; s++) SetIndexBuffer(s,atr_buffers[s].data,INDICATOR_DATA); //--- 设当前品种标签 for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetString(s,PLOT_LABEL,"ATR ("+IntegerToString(s)+", "+symbol_names[s]+")"); //--- 画线类型 for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE); //--- 线宽 1 for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_LINE_WIDTH,1); //--- 线色 for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]); //--- 空值不画 for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE); } 实盘里若 SYMBOLS_COUNT 设到 10 以上,初始化时反复 iATR 失败往往是因为品种不在市场报价窗口;开 MT5 前先确认「市场观察」已显示这些品种,否则 valid_handles 会卡在 false,子窗口什么都不画。外汇与贵金属波动剧烈,多品种 ATR 仅作波动参考,实际仓位仍须自担高风险。

MQL5 / C++
for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
  {
  class=class="str">"cmt">//--- If the symbol is available
  if(symbol_names[s]!=empty_symbol)
    {
    class=class="str">"cmt">// And if the handle of the current symbol is invalid
    if(symbol_handles[s]==INVALID_HANDLE)
      {
      class=class="str">"cmt">//--- Get it
      symbol_handles[s]=iATR(symbol_names[s],Period(),IndicatorPeriod);
      class=class="str">"cmt">//--- If the handle could not be obtained, try again next time
      if(symbol_handles[s]==INVALID_HANDLE)
        valid_handles=class="kw">false;
      }
    }
  }
class=class="str">"cmt">//--- Print the relevant message if the handle for one of the symbols could not be obtained
if(!valid_handles)
  {
  msg_last=msg_invalid_handle;
  ShowCanvasMessage(msg_invalid_handle);
  }
class=class="str">"cmt">//---
class="kw">return(valid_handles);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Setting indicator properties                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SetIndicatorProperties()
  {
class=class="str">"cmt">//--- Set the class="type">class="kw">short name
  IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname);
class=class="str">"cmt">//--- Set the number of decimal places
  IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
class=class="str">"cmt">//--- Define buffers for drawing
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    SetIndexBuffer(s,atr_buffers[s].data,INDICATOR_DATA);
class=class="str">"cmt">//--- Set labels for the current symbol
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    PlotIndexSetString(s,PLOT_LABEL,"ATR("+IntegerToString(s)+", "+symbol_names[s]+")");
class=class="str">"cmt">//--- Set the plotting type: lines
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE);
class=class="str">"cmt">//--- Set the line width
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    PlotIndexSetInteger(s,PLOT_LINE_WIDTH,class="num">1);
class=class="str">"cmt">//--- Set the line class="type">class="kw">color
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]);
class=class="str">"cmt">//--- Empty value for plotting where nothing will be drawn
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }

把多品种行情灌进自定义指标缓冲区

做跨品种 ATR 监控时,第一步是把主图 OnCalculate 传进来的行情数组原样接住。下面这段把 rates_total、prev_calculated 以及 time/open/high/low/close 等九类数组用 ArrayCopy 拷到全局容器,后续子函数才能按索引取用,不必反复调 CopyRates。 OC_rates_total=rates_total 记下总柱数,OC_prev_calculated 保留已计算偏移,其余八个 ArrayCopy 逐一对齐。注意参数里的 spread[] 也是可拷贝项,在贵金属点差跳变时它能单独留痕。 真正跑多品种前还要给临时数组定尺寸。ResizeCalculatedArrays 按 SYMBOLS_COUNT 循环,对每个符号的 time 与 atr 值数组做 ArrayResize 到 OC_rates_total,否则跨符号写入会越界报错。 每次重算前必须清场。ZeroCalculatedArrays 把临时 time 置 NULL、atr 值置 EMPTY_VALUE;ZeroIndicatorBuffers 只清 atr_buffers 的 data。外汇与贵金属波动剧烈,若不清 EMPTY_VALUE,上一根残留 ATR 可能让信号出现伪连续,实盘验证时务必确认这一步被执行。

MQL5 / C++
class="kw">const class="type">int     prev_calculated,
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[])
  {
  OC_rates_total=rates_total;
  OC_prev_calculated=prev_calculated;
  ArrayCopy(OC_time,time);
  ArrayCopy(OC_open,open);
  ArrayCopy(OC_high,high);
  ArrayCopy(OC_low,low);
  ArrayCopy(OC_close,close);
  ArrayCopy(OC_tick_volume,tick_volume);
  ArrayCopy(OC_volume,volume);
  ArrayCopy(OC_spread,spread);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Resizing the size of arrays to the size of the main array        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ResizeCalculatedArrays()
  {
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    {
     ArrayResize(tmp_symbol_time[s].time,OC_rates_total);
     ArrayResize(tmp_atr_values[s].value,OC_rates_total);
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Zeroing out arrays for data preparation                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ZeroCalculatedArrays()
  {
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    {
     ArrayInitialize(tmp_symbol_time[s].time,NULL);
     ArrayInitialize(tmp_atr_values[s].value,EMPTY_VALUE);
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Zeroing out indicator buffers                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ZeroIndicatorBuffers()
  {
  for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
    ArrayInitialize(atr_buffers[s].data,EMPTY_VALUE);
  }

「OnCalculate 里的首次与增量计算分支」

自定义指标跑在 MT5 上,核心入口是 OnCalculate。它每次被终端调用都会带进 rates_total(当前 K 线总数)和 prev_calculated(上一次已处理柱数),这两个值决定了你是全量重算还是只补最后几根。 下面这段是典型骨架:先定义 limit 控制起点,再把入参整体拷进类内数组,然后按 prev_calculated==0 走首算分支——清零缓冲、记下 OC_prev_calculated=rates_total;否则 limit=prev_calculated-1,只从倒数第二根往后推。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, 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=class="str">"cmt">//--- 定义计算起点
class="type">int limit=class="num">0;
class=class="str">"cmt">//--- 拷贝 OnCalculate 入参到内部数组
CopyDataOnCalculate(rates_total,prev_calculated, time,open,high,low,close, tick_volume,volume,spread);
class=class="str">"cmt">//--- 调整预备数组尺寸
ResizeCalculatedArrays();
class=class="str">"cmt">//--- 首算或补历史时
if(prev_calculated==class="num">0) {
  ZeroCalculatedArrays();
  ZeroIndicatorBuffers();
  OC_prev_calculated=rates_total;
}
class=class="str">"cmt">//--- 仅重算末值时
else
  limit=prev_calculated-class="num">1;
class=class="str">"cmt">//--- 准备绘图数据(略)
class=class="str">"cmt">//--- 填充绘图数组(略)
class="kw">return(rates_total);
}
逐行看:第 1 行函数签名里的 spread[] 是点差序列,做贵金属回测时别忽略它;prev_calculated==0 不仅发生在加载时,终端补历史缺口也会触发,所以清零必须彻底。 首算把 OC_prev_calculated 设成 rates_total,下次进来 else 分支就从 rates_total-1 接着算,避免重复跑全样本。外汇与贵金属杠杆高、滑点跳空频繁,这种增量逻辑能显著降低 CPU 占用,但首次加载若历史过深仍可能卡顿一两秒,属正常现象。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, 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=class="num">0;
CopyDataOnCalculate(rates_total,prev_calculated, time,open,high,low,close, tick_volume,volume,spread);
ResizeCalculatedArrays();
if(prev_calculated==class="num">0) {
  ZeroCalculatedArrays();
  ZeroIndicatorBuffers();
  OC_prev_calculated=rates_total;
}
else
  limit=prev_calculated-class="num">1;
class="kw">return(rates_total);
}
把跨品种诊断交给小布
这些品种同步与句柄状态的排查,小布盯盘背后的 AIGC 已内置到对应品种页,打开即可看到实时波幅与缺失提示,你只管判断信号。

常见问题

多数情况是目标品种未加入市场报价窗口或名称拼写与总表不符,OnInit 里应先添加并检查再获取句柄。
可能导致不同品种缓冲区按错误索引对齐,ATR 值错位比较,信号失真概率上升。
检查 CheckInputParameters 之后的品种存在性判断,总表没有的名称会被跳过且不参与计算。
可以,对应品种页已内置跨品种波幅与缺失标注,不必自己写指标也能快速扫盘。
定时器便于分离进程提示与数据拉取节奏,避免主计算阻塞,也方便在画布上更新进度。