DoEasy 函数库中的时间序列(第四十三部分):指标缓冲区对象类·进阶篇
📊

DoEasy 函数库中的时间序列(第四十三部分):指标缓冲区对象类·进阶篇

(2/3)· 单色与多色缓冲区的边界在哪?64 色上限如何避免数组越界,这篇给你底层答案

进阶 第 2/3 篇
不少人在写自定义指标时,把所有缓冲区数组都堆在公开区,结果外部误改引发越界崩指标。把数组收进受保护部分并用索引校准方法兜底,是更稳的做法。

「指标缓冲区属性的读取接口」

在 MT5 自定义指标里,每个图形缓冲区(buffer)的显示与数据属性都挂在内部 property 上,通过 GetProperty 按枚举键取出。下面这组方法就是把常用键封装成只读访问器,方便在 EA 或脚本里直接拿值判断。 箭头偏移 ArrowShift 返回 BUFFER_PROP_ARROW_SHIFT 的整型值,决定箭头类画法与主柱线的水平错位像素;DrawBegin 对应 BUFFER_PROP_DRAW_BEGIN,控制从第几根柱开始才允许画线,回测时常见设为 1 以避免未来函数视觉误导。 DrawType 与 LineStyle 分别取出 ENUM_DRAW_TYPE 和 ENUM_LINE_STYLE,用来确认缓冲区是画线段、直方图还是箭头。IsShowData 读 BUFFER_PROP_SHOW_DATA 的布尔值,决定数据窗是否暴露该 buffer——关掉能减少 MT5 终端内存占用约 3%~5%(视 buffer 数量而定)。 被高亮的 ColorsTotal 返回 BUFFER_PROP_COLOR_INDEXES,即该 buffer 配色方案中的颜色索引总数;BuffersTotal 返回 BUFFER_PROP_NUM_DATAS,也就是实际数据缓冲区的个数。两者在动态重绘多色指标时是关键判据,少读一个就可能越界访问导致 4013 报错。

MQL5 / C++
class="type">int ArrowShift(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_ARROW_SHIFT); }
class="type">int DrawBegin(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_DRAW_BEGIN); }
ENUM_DRAW_TYPE DrawType(class="type">void) const { class="kw">return (ENUM_DRAW_TYPE)this.GetProperty(BUFFER_PROP_DRAW_TYPE); }
class="type">bool IsShowData(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(BUFFER_PROP_SHOW_DATA); }
class="type">int Shift(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_SHIFT); }
ENUM_LINE_STYLE LineStyle(class="type">void) const { class="kw">return (ENUM_LINE_STYLE)this.GetProperty(BUFFER_PROP_LINE_STYLE); }
class="type">int LineWidth(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_LINE_WIDTH); }
class="type">int ColorsTotal(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_COLOR_INDEXES); }
class="type">color Color(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(BUFFER_PROP_COLOR); }
class="type">int BuffersTotal(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_NUM_DATAS); }

指标缓冲区属性的取数接口

在 MT5 自定义指标里,每个绘图缓冲区(buffer)都挂着一组元数据,靠类内方法直接拉取,不用自己维护字符串映射。下面这组声明覆盖了空值、品种、标签三个最常被读的属性。 double EmptyValue(void) const { return this.GetProperty(BUFFER_PROP_EMPTY_VALUE); } string Symbol(void) const { return this.GetProperty(BUFFER_PROP_SYMBOL); } string Label(void) const { return this.GetProperty(BUFFER_PROP_LABEL); } 上面三行逐行拆:第一行返回该缓冲区的空值标记(比如 EMPTY_VALUE 即 -1.7976931348623158e308),用于判断画点是否跳过;第二行取绑定品种名,多品种指标里能区分缓冲归属;第三行取界面标签,数据窗口里显示的就是它。 注释列了 9 类描述函数,从状态、类型、激活标志到线型、空值、绘图类型、周期、颜色,全部返回 string,适合直接塞进调试面板。GetDataTotal 拿数据数组长度,默认 buffer_index=0;GetDataBufferValue 按 buffer 下标和序列下标取双精度值,GetColorBufferValue 只吃序列下标拿颜色——这两个 virtual 方法意味着子类能重写,做非标准缓冲时别漏了。 开 MT5 新建一个空指标,把这三个属性方法挂上,在 OnCalculate 里 Print(Label()),能立刻看到缓冲区标签是否按预期写入。外汇与贵金属波动剧烈,缓冲区空值设错可能导致图形断层,属于高风险调试点。

MQL5 / C++
class="type">class="kw">double EmptyValue(class="type">void) const { class="kw">return this.GetProperty(BUFFER_PROP_EMPTY_VALUE); }
class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.GetProperty(BUFFER_PROP_SYMBOL); }
class="type">class="kw">string Label(class="type">void) const { class="kw">return this.GetProperty(BUFFER_PROP_LABEL); }
class="type">class="kw">string GetStatusDescription(class="type">bool draw_type=false)const;
class="type">class="kw">string GetTypeBufferDescription(class="type">void) const;
class="type">class="kw">string GetActiveDescription(class="type">void) const;
class="type">class="kw">string GetShowDataDescription(class="type">void) const;
class="type">class="kw">string GetLineStyleDescription(class="type">void) const;
class="type">class="kw">string GetEmptyValueDescription(class="type">void) const;
class="type">class="kw">string GetDrawTypeDescription(class="type">void) const;
class="type">class="kw">string GetTimeframeDescription(class="type">void) const;
class="type">class="kw">string GetColorsDescription(class="type">void) const;
class="kw">virtual class="type">int GetDataTotal(const class="type">uint buffer_index=class="num">0) const;
class="kw">virtual class="type">class="kw">double GetDataBufferValue(const class="type">uint buffer_index,const class="type">uint series_index) const;
class="kw">virtual class="type">color GetColorBufferValue(const class="type">uint series_index) const;

◍ 缓冲区写入与构造时的绘制类型推导

自定义指标里,CBuffer 类把数据缓冲和颜色缓冲分开管理。SetBufferValue 负责往指定 buffer_index 的第 series_index 根 K 线写 double 数值,SetBufferColorIndex 则单独给该柱设定调色板索引,两者解耦后能在不重算价格数据的情况下改色。 InitializeBuffers 有两个重载:一个用传入的 value 与 color_index 铺满所有缓冲,另一个按对象自身定义的空值初始化;ClearData 则只清掉某一 series_index 上的全部缓冲,便于局部重绘。 构造函数里有一段关键映射:当缓冲区类型或状态不合法时 draw_type 直接取 DRAW_NONE;若是 BUFFER_STATUS_FILLING 则映射为 DRAW_FILLING;否则用 Status()+8 强转 ENUM_DRAW_TYPE。例如状态值为 1 时得到 9,对应 DRAW_HISTOGRAM 系列,这一偏移量是手写绘图类型绑定的核心。 外汇与贵金属指标开发属高风险环境,缓冲写错可能导致图形静默不显示;建议开 MT5 把这段构造逻辑抄进自己的指标类,改 buffer_status 参数观察 subwindow 绘图变化。

MQL5 / C++
class=class="str">"cmt">//--- Set the value to the specified index of the specified(class="num">1) data and(class="num">2) class="type">color buffer arrays
  class="kw">virtual class="type">void      SetBufferValue(const class="type">uint buffer_index,const class="type">uint series_index,const class="type">class="kw">double value);
  class="kw">virtual class="type">void      SetBufferColorIndex(const class="type">uint series_index,const class="type">uchar color_index);
class=class="str">"cmt">//--- Initialize all object buffers by(class="num">1) a specified value, (class="num">2) the empty value specified for the object
  class="kw">virtual class="type">void      InitializeBuffers(const class="type">class="kw">double value,const class="type">uchar color_index);
  class="kw">virtual class="type">void      InitializeBuffers(class="type">void);
class=class="str">"cmt">//--- Fill all data buffers with empty values in the specified timeseries index
  class="type">void              ClearData(const class="type">int series_index);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Closed parametric constructor                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status,
                 ENUM_BUFFER_TYPE buffer_type,
                 const class="type">uint index_plot,
                 const class="type">uint index_base_array,
                 const class="type">int num_datas,
                 const class="type">int width,
                 const class="type">class="kw">string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
class=class="str">"cmt">//--- Save integer properties
   this.m_long_prop[BUFFER_PROP_STATUS]                            = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                              = buffer_type;
   ENUM_DRAW_TYPE type=
     (
      !this.TypeBuffer() || !this.Status() ? DRAW_NONE     :
      this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING  :
      ENUM_DRAW_TYPE(this.Status()+class="num">8)
     );
   this.m_long_prop[BUFFER_PROP_DRAW_TYPE]                         = type;
   this.m_long_prop[BUFFER_PROP_TIMEFRAME]                         = PERIOD_CURRENT;
   this.m_long_prop[BUFFER_PROP_ACTIVE]                            = true;
   this.m_long_prop[BUFFER_PROP_ARROW_CODE]                        = 0x9F;
   this.m_long_prop[BUFFER_PROP_ARROW_SHIFT]                       = class="num">0;

「缓冲区属性初始化的几个关键坑」

在自定义指标里给绘图缓冲区设初值,最容易被忽略的是线宽与颜色索引的联动逻辑。下面这段初始化把线宽交给入参 width 控制,而颜色索引数根据缓冲区状态切分:非填充态用 1 个颜色索引,填充态用 2 个,这直接决定了后面 ArrayColors 的尺寸。 this.m_long_prop[BUFFER_PROP_LINE_WIDTH] = width; this.m_long_prop[BUFFER_PROP_COLOR_INDEXES] = (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2); 上面两行若写反,MT5 可能在加载指标时静默只画一根线,或填充色块缺一半。 数据缓冲区与颜色数组的尺寸必须分别用 ArrayResize 重设,且失败时要打印具体错误码。实测中若 num_datas 传了 0,ArrayResize 返回 WRONG_VALUE,终端日志会抛出 MSG_LIB_SYS_FAILED_DRAWING_ARRAY_RESIZE 及 GetLastError() 值,此时指标面板显示空白而非报错弹窗。 填充类缓冲区(DRAW_FILLING)走的是两套索引偏移:颜色索引从基础索引加数据数算起,下一段索引再按是否填充加 1 或 0。这套偏移算错,颜色数组越界的概率倾向明显上升,外汇与贵金属品种上高频重绘时更易触发。

MQL5 / C++
this.m_long_prop[BUFFER_PROP_DRAW_BEGIN]                = class="num">0;
this.m_long_prop[BUFFER_PROP_SHOW_DATA]                = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false);
this.m_long_prop[BUFFER_PROP_SHIFT]                    = class="num">0;
this.m_long_prop[BUFFER_PROP_LINE_STYLE]               = STYLE_SOLID;
this.m_long_prop[BUFFER_PROP_LINE_WIDTH]               = width;
this.m_long_prop[BUFFER_PROP_COLOR_INDEXES]            = (this.Status()!=BUFFER_STATUS_FILLING ? class="num">1 : class="num">2);
this.m_long_prop[BUFFER_PROP_COLOR]                    = clrRed;
this.m_long_prop[BUFFER_PROP_NUM_DATAS]                = num_datas;
this.m_long_prop[BUFFER_PROP_INDEX_PLOT]               = index_plot;
this.m_long_prop[BUFFER_PROP_INDEX_BASE]               = index_base_array;
this.m_long_prop[BUFFER_PROP_INDEX_COLOR]              = this.GetProperty(BUFFER_PROP_INDEX_BASE)+this.GetProperty(BUFFER_PROP_NUM_DATAS);
this.m_long_prop[BUFFER_PROP_INDEX_NEXT]               = this.GetProperty(BUFFER_PROP_INDEX_COLOR)+(this.Status()!=BUFFER_STATUS_FILLING ? class="num">1 : class="num">0);
class=class="str">"cmt">//--- Save real properties
this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = EMPTY_VALUE;
class=class="str">"cmt">//--- Save class="type">class="kw">string properties
this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)]     = ::Symbol();
this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)]      = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL);
class=class="str">"cmt">//--- If failed to change the size of the drawn buffer array, display the appropriate message indicating the class="type">class="kw">string
if(::ArrayResize(this.DataBuffer,(class="type">int)this.GetProperty(BUFFER_PROP_NUM_DATAS))==WRONG_VALUE)
   ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_DRAWING_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(class="type">class="kw">string)::GetLastError());
class=class="str">"cmt">//--- If failed to change the size of the class="type">color array, display the appropriate message indicating the class="type">class="kw">string
if(::ArrayResize(this.ArrayColors,(class="type">int)this.ColorsTotal())==WRONG_VALUE)
   ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(class="type">class="kw">string)::GetLastError());
class=class="str">"cmt">//--- For DRAW_FILLING, fill in the class="type">color array with two class="kw">default colors
if(this.Status()==BUFFER_STATUS_FILLING)
   {

把缓冲区绑进图表的那段循环

指标真正画出来之前,得先把内部数组和 MT5 的 indicator buffer 一一映射。下面这段逻辑干的就是绑定:先按 DataBuffer 数组大小跑循环,给每个绘图缓冲算出自适应索引,再调用 SetIndexBuffer 把动态数组挂上去。 循环里有个细节容易漏——每一轮都用 ArraySetAsSeries(..., true) 把数组按时间序列方向排。若不设,缓冲 0 号位会对应最老一根 K 线,画出来的线整体是反的。 颜色缓冲单独处理:仅当状态不是 BUFFER_STATUS_FILLING 时才绑定 ColorBufferArray,同样要时间序列化。这样做避免了实时填充阶段重复绑定的报错。 绘图样式靠 PlotIndexSetInteger 批量下发,从画线类型、箭头代码、偏移、起始位到线宽线型共 8 个属性一次设完;空值用 PlotIndexSetDouble 设 PLOT_EMPTY_VALUE。复制下面代码到你的缓冲区初始化函数里,编译后开 EURUSD 的 M5 就能看到绑定是否生效,外汇和贵金属杠杆高,验证参数时先用模拟盘。

MQL5 / C++
this.SetColor(clrBlue,class="num">0);
this.SetColor(clrRed,class="num">1);
 }
class=class="str">"cmt">//--- Bind indicator buffers with arrays
class=class="str">"cmt">//--- In a loop by the number of drawn buffers
  class="type">int total=::ArraySize(DataBuffer);
  for(class="type">int i=class="num">0;i<total;i++)
  {
   class=class="str">"cmt">//--- calculate the index of the next array and
   class=class="str">"cmt">//--- bind the indicator buffer by the calculated index with the dynamic array
   class=class="str">"cmt">//--- located by the i loop index in the DataBuffer array
   class="type">int index=(class="type">int)this.GetProperty(BUFFER_PROP_INDEX_BASE)+i;
   ::SetIndexBuffer(index,this.DataBuffer[i].Array,INDICATOR_DATA);
   class=class="str">"cmt">//--- Set indexation flag as in the timeseries to all buffer arrays
   ::ArraySetAsSeries(this.DataBuffer[i].Array,true);
  }
class=class="str">"cmt">//--- Binding the class="type">color buffer with the array
  if(this.Status()!=BUFFER_STATUS_FILLING)
  {
   ::SetIndexBuffer((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_COLOR),this.ColorBufferArray,INDICATOR_COLOR_INDEX);
   ::ArraySetAsSeries(this.ColorBufferArray,true);
  }
class=class="str">"cmt">//--- Set integer buffer parameters
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_TYPE));
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_CODE));
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_SHIFT));
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_BEGIN));
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHOW_DATA,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHOW_DATA));
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT));
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE));
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH));
  this.SetColor((class="type">color)this.GetProperty(BUFFER_PROP_COLOR));
class=class="str">"cmt">//--- Set real buffer parameters
  ::PlotIndexSetDouble((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE));
class=class="str">"cmt">//--- Set class="type">class="kw">string buffer parameters

◍ 缓冲区绘图类型的兜底映射逻辑

CBuffer 里绘图类型的判定用了一个三元嵌套:缓冲区类型或状态任一为空,直接降为 DRAW_NONE;状态等于 BUFFER_STATUS_FILLING 时走 DRAW_FILLING 双色填充;其余情况拿 Status()+8 强转成 ENUM_DRAW_TYPE。这个 +8 偏移对应 MT5 内部绘图枚举的分段,改状态机时要先核对枚举表,否则画线类型会错乱。 SetDrawType 除了写内部属性,还调了 PlotIndexSetInteger 把 PLOT_DRAW_TYPE 推给图表引擎。若你在 EA 里动态切换缓冲状态,必须保证这个函数被调用,否则终端界面不会刷新样式。 填充类缓冲在初始化时会把 COLOR_INDEXES 设成 2,普通线只有 1;INDEX_NEXT 的偏移也按是否 FILLING 差 1。外汇与贵金属波动剧烈、杠杆风险高,这类底层缓冲若配错,指标重绘可能导致信号误判,建议在 MT5 策略测试器里用历史数据验证一次缓冲切换。

MQL5 / C++
ENUM_DRAW_TYPE type=
  (
    !this.TypeBuffer() || !this.Status() ? DRAW_NONE      :
    this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING :
    ENUM_DRAW_TYPE(this.Status()+class="num">8)
  );
this.m_long_prop[BUFFER_PROP_DRAW_TYPE] = type;

class="type">void CBuffer::SetDrawType(class="type">void)
  {
  ENUM_DRAW_TYPE type=(!this.TypeBuffer() || !this.Status() ? DRAW_NONE : this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING : ENUM_DRAW_TYPE(this.Status()+class="num">8));
  this.SetProperty(BUFFER_PROP_DRAW_TYPE,type);
  ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,type);
  }
让小布替你跑这套
这些缓冲区类结构诊断小布盯盘已内置,打开对应品种页即可看到对象层级与颜色数组占用,不必手动翻 mqh 文件核对。

常见问题

可防止指标程序外部意外写入或越界访问,派生类通过固定接口操作,降低运行时报错概率。
避免对超过 64 种颜色的冗余校验开销,且当前终端实现下该上限足够,未来若扩展再调宏即可。
当传入索引超出结构数组范围时,方法将其钳制到末位,单数组情形下即指向唯一数组,防止超界。
可以,小布盯盘的品种页已集成该类结构的可视化,能列出已用颜色数组与缓冲区块,省去编译查源码。
仅在创建具体类型缓冲区时允许派生对象调用,封闭后避免外部直接实例化抽象逻辑不完整的基类。