DoEasy 函数库中的时间序列(第四十三部分):指标缓冲区对象类·进阶篇
(2/3)· 单色与多色缓冲区的边界在哪?64 色上限如何避免数组越界,这篇给你底层答案
「指标缓冲区属性的读取接口」
在 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 报错。
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()),能立刻看到缓冲区标签是否按预期写入。外汇与贵金属波动剧烈,缓冲区空值设错可能导致图形断层,属于高风险调试点。
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 绘图变化。
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。这套偏移算错,颜色数组越界的概率倾向明显上升,外汇与贵金属品种上高频重绘时更易触发。
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 就能看到绑定是否生效,外汇和贵金属杠杆高,验证参数时先用模拟盘。
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 策略测试器里用历史数据验证一次缓冲切换。
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);
}