DoEasy 函数库中的时间序列(第四十六部分):多周期、多品种指标缓冲区(基础篇)
「让缓冲区对象接管多品种多周期指标」
DoEasy 函数库把指标缓冲区抽象成对象后,最大的变化是:一个缓冲区实例不再绑定当前图表品种和周期。你可以显式指定任意品种(如 XAUUSD)和任意周期(如 PERIOD_H1),缓冲区自己去拉取对应数据。 在 2020-11-03 发布的这一版里,作者用 2469 字描述了缓冲区对象类的重构思路,核心目标是让同一套代码能同时处理多品种、多周期的指标计算,而不必为每个组合写重复逻辑。 验证方法很直接:打开 MT5,新建一个 EA 把移动均线和 MACD 的缓冲区分别指向 EURUSD 的 M15 与 XAUUSD 的 H1,观察两个缓冲区是否各自独立更新。外汇与贵金属杠杆高、跳空频繁,跨品种取数要留意点差和休市导致的空洞。
让库支持跨品种跨周期取数
目前这套函数库已经能创建并跟踪多周期指标的缓冲区,但只局限在单一品种上下文里跑。要把它推向多品种模式,核心是把之前缓冲区对象类里已有的跨品种能力做一轮精炼,而不是重写。 具体落点是改进计算缓冲区对象类:让它直接依据标准指标句柄,去接收该句柄对应数组所需的数据。库本身早具备按句柄取数的骨架,这次只是补几处小幅附加逻辑,代价很小,但能直接在当前图表上把任意品种、任意周期的标准指标数据拉出来显示。 验证方式很直接:开 MT5 建个 EA 草稿,给 iMA 跨品种句柄喂进改造后的缓冲区类,看副图能否画出非当前品种均线。外汇与贵金属市场高杠杆、跨周期信号易失效,跨品种引用还需警惕点差与流动性错位。
◍ 给缓冲区基类加一个数组计数变量
抽象缓冲区基类原本只记录当前指标缓冲区之后的后续缓冲区数组索引,但遇到“两级别之间填充颜色”这种不挂颜色缓冲区的绘图样式时,索引推算容易乱。与其在每个子类里反复判断类型,不如在基类私密段加一个 uchar 型成员 m_total_arrays,专门存构造该缓冲区对象所占用的数组总数。 做法是在受保护参数构造函数里多收一个 uchar 参数,创建箭头、线条、直方图等子类对象时,把固定数组数(比如计算缓冲区是 1,绘制蜡烛可能是 4)硬传进去。基类构造函数里把它赋给 m_total_arrays,再据此算出下一个缓冲区的基本数组索引;算颜色数组索引时仍要判类型——绘制类加 m_total_arrays,计算类没颜色数组就加 0。 改完之后,十个子类文件(BufferArrow / BufferLine / BufferSection / BufferHistogram / BufferHistogram2 / BufferZigZag / BufferFilling / BufferBars / BufferCandles / BufferCalculate)都在初始化列表里补传了数组数量。以后新增缓冲区不用再写类型分支,索引逻辑清爽很多。 计算缓冲区类还加了三个方法,把标准指标句柄数据写进自己的数组,底层都调 CopyBuffer() 的不同重载。配合时间序列集合类里新增的跨品种柱线索引映射方法,就能在 Current 图表用指定品种/周期数据画图——这是多品种模式跑通的关键一步,外汇与贵金属品种切换时价差跳空可能导致映射偏移,实盘前建议在 MT5 用 EURUSD 与 XAUUSD 不同周期对一遍。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Abstract indicator buffer class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBuffer : class="kw">public CBaseObj { class="kw">private: class="type">long m_long_prop[BUFFER_PROP_INTEGER_TOTAL]; class=class="str">"cmt">// Integer properties class="type">class="kw">double m_double_prop[BUFFER_PROP_DOUBLE_TOTAL]; class=class="str">"cmt">// Real properties class="type">class="kw">string m_string_prop[BUFFER_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties class="type">bool m_act_state_trigger; class=class="str">"cmt">// Auxiliary buffer status class="kw">switch flag class="type">uchar m_total_arrays; class=class="str">"cmt">// Total number of buffer arrays class=class="str">"cmt">//--- Default constructor CBuffer(class="type">void){;} class="kw">protected: class=class="str">"cmt">//--- Protected parametric constructor CBuffer(ENUM_BUFFER_STATUS status_buffer, ENUM_BUFFER_TYPE buffer_type,
「缓冲区对象的构造与绘制类型推导」
在 MT5 自定义指标里,图形缓冲区的初始化往往被忽视,但它直接决定后续 Plot 能否正确上屏。下面这段 CBuffer 的闭参构造函数,把状态、类型、数组数等一次性固化进对象内部属性。 构造时先写死集合标识 COLLECTION_BUFFERS_ID 与触发开关 m_act_state_trigger=true,并把外部传入的 total_arrays 存进 m_total_arrays(背景高亮处即这一行)。这两个值后续不会随行情变动,属于对象级别的静态配置。 绘制类型靠一段三元嵌套推导:若类型或状态为空则 DRAW_NONE;状态是 BUFFER_STATUS_FILLING 走 DRAW_FILLING;否则用状态值 +8 强转 ENUM_DRAW_TYPE。这意味着状态枚举和绘图枚举在底层是偏移对齐的,+8 这个硬偏移写死在代码里,改枚举时极易踩坑。 其余 long 属性里,TIMEFRAME 默认 PERIOD_CURRENT、ACTIVE 默认 true、ARROW_CODE 写死 0x9F(即 Wingdings 里的箭头符号)、ARROW_SHIFT 为 0。开 MT5 新建指标时,若箭头不显示,优先核对 0x9F 与字体映射,而不是盲目调 plot 索引。
CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status, ENUM_BUFFER_TYPE buffer_type, class="kw">const class="type">uint index_plot, class="kw">const class="type">uint index_base_array, class="kw">const class="type">int num_datas, class="kw">const class="type">uchar total_arrays, class="kw">const class="type">int width, class="kw">const class="type">class="kw">string label) { this.m_type=COLLECTION_BUFFERS_ID; this.m_act_state_trigger=true; this.m_total_arrays=total_arrays; 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; }
指标缓冲区属性的初始化落点
在自定义指标封装里,缓冲区对象构造时要把一长串绘图与数据属性写进 m_long_prop 映射。DRAW_BEGIN 与 SHIFT 都置 0,线型锁死 STYLE_SOLID,线宽由入参 width 决定,颜色先给 clrRed——这些决定了 MT5 图表上这根线从哪根 K 线起画、偏移多少、胖瘦如何。 SHOW_DATA 的判断很直接:buffer_type 大于 BUFFER_TYPE_CALCULATE 才允许在 DataWindow 显示数值,否则藏起来只参与计算。COLOR_INDEXES 则跟着 Status() 走,非 NONE 状态下填充态给 2、其他给 1,没状态就是 0,这关系到多色绘制时调色板怎么分配。 INDEX_COLOR 与 INDEX_NEXT_BASE 的递推值得盯一眼:前者在 CALCULATE 类型时只取 BASE 索引,否则要再叠 NUM_DATAS 长度;后者直接拿 index_base_array 加 m_total_arrays 算出下一组基址。改完属性后若 ArrayResize 返回 WRONG_VALUE,会打印带错误码的提示,说明缓冲区数组扩容失败——这在加载大周期历史数据时可能触发,属外汇/贵金属高频重算场景下的常见坑。 字符串属性里 SYMBOL 绑定当前品种,LABEL 仅对非计算缓冲生效,计算缓冲留 NULL 不占图例。开 MT5 把这段塞进你的 CiCustom 子类构造函数,调 width 和 label 两个参数就能直观看到线宽与图例变化。
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 : class="kw">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_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? class="num">1 : class="num">2) : class="num">0); 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.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : class="num">0); this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE] = index_base_array+this.m_total_arrays; this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+class="num">1 : index_plot); class=class="str">"cmt">//--- Save real properties this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : class="num">0); 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 indicator 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());
◍ 缓冲区绑定收尾时顺手把画图属性定死
在自定义指标初始化阶段,缓冲区数组真正挂到图表前还得处理几件细活。若缓冲区类型大于 BUFFER_TYPE_CALCULATE,先按 ColorsTotal() 扩容颜色数组,失败就打印错误码——这一步漏了,后面 DRAW_FILLING 以外的多色绘制会直接崩。 对于状态为 BUFFER_STATUS_FILLING 的填充带,代码里硬编码了 clrBlue 和 clrRed 两个默认色,分别落在索引 0 和 1。你在 MT5 里改这两个宏,填充区域的上下边界配色就会立刻变,不用动循环逻辑。 随后用 ArraySize(DataBuffer) 拿到缓冲区块数,循环里靠 GetProperty(BUFFER_PROP_INDEX_BASE)+i 推算 plot 索引,调 SetIndexBuffer 把动态数组绑上去;TYPE 为 DATA 时标 INDICATOR_DATA,否则标 INDICATOR_CALCULATIONS。每轮都跑一次 ArraySetAsSeries(...,true),保证和分时序列同向索引,否则老数据会倒着画。 非填充且非计算型缓冲才会绑颜色索引数组,计算型直接 return 跳过。最后那串 PlotIndexSetInteger 把 DRAW_TYPE、ARROW、SHIFT、DRAW_BEGIN、SHOW_DATA 一次性写死到 plot 属性——这些整数参数若不在绑定阶段设好,运行时改往往不生效,只能重加载指标。
if(this.TypeBuffer()>BUFFER_TYPE_CALCULATE) 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">class="kw">color array with two class="kw">default colors if(this.Status()==BUFFER_STATUS_FILLING) { 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 indicator 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,(this.TypeBuffer()==BUFFER_TYPE_DATA ? INDICATOR_DATA : INDICATOR_CALCULATIONS)); 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">//--- Bind the class="type">class="kw">color buffer with the array(only for a non-calculated buffer and not for the filling buffer) if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) { ::SetIndexBuffer((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_COLOR),this.ColorBufferArray,INDICATOR_COLOR_INDEX); ::ArraySetAsSeries(this.ColorBufferArray,true); } class=class="str">"cmt">//--- If this is a calculated buffer, all is done if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE) class="kw">return; class=class="str">"cmt">//--- Set integer parameters of the graphical series ::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));