DoEasy 函数库中的时间序列(第四十五部分):多周期指标缓冲区·综合运用
(3/3)· 从缓冲区索引到多周期映射,46 节实操把函数库时间序列收口为可复用骨架
- 缓冲区对象的三类属性读写骨架
- 缓冲区基类的状态开关与属性 setter
- 缓冲区绘制属性的类封装细节
- 指标缓冲区的索引与取数接口
- 缓冲区初始化与序列填充的底层接口
- 从时序对象倒序灌入指标缓冲
- 缓冲区构造里的状态触发与配色计数
- 缓冲区初始化时的属性绑定与容错
- 缓冲区与绘图属性的绑定落点
- 缓冲区绘制的几个底层拦截点
- 缓冲区着色的安全闸门与文件装配
- 缓冲区集合的索引与创建接口
- 缓冲区工厂方法的分类与取用
- 指标缓冲区集合的初始化与赋值接口
- 给自定义图形缓冲写值与染色
- 缓冲区集合的清理与初始化接口
- 多周期缓冲区的取数与绘图索引
- 缓冲区检索与批量初始化的底层接口
- 缓冲区集合的初始化与配色逻辑
- 箭头缓冲的跨周期清写逻辑
- 箭头缓冲与跨周期柱体类型的接口封装
- 用标签和时间框架精准抓缓冲区
- 缓冲区的取用与新建接口
- 缓冲区封装的几种图形与计算入口
- 指标缓冲区的初始化与按类型取数接口
- 缓冲区读写接口的两种取数路径
- 给自定义图形缓冲喂数据的接口族
- 往自定义蜡烛缓冲写数据的接口细节
- 按图形类别取色与定位缓冲序号
- 给九类绘制缓冲单独指定颜色索引
- 按索引抹掉缓冲区的图形与配色
- 交易类对象的全局参数注入接口
- 交易对象的声音与初始化接口
- 按索引与时间取棒线类型的双重载
- 引擎层缓冲区写入的几组重载接口
- 直方图与填充缓冲的写入接口
- 给缓冲层灌K线与改色的接口细节
- 逐类图形缓冲区的着色与清空接口
- 缓冲区清理与集合初始化的底层接口
- 把单周期指标改成多周期测试件
- 缓冲显示与数据序列化的落地写法
- 把 OnCalculate 数组塞进结构体的收尾动作
- 把 OnCalculate 数据搬进时间序列结构
- 指标主循环里如何按K线方向刷缓冲
- 多形态缓冲区的条件写入逻辑
- 缓冲区集合类之后要补的指标操作
「缓冲区对象的三类属性读写骨架」
在 MT5 自定义指标里,每个绘图缓冲区(buffer)本质上是一个带类型的属性容器。把整数、浮点、字符串三类属性分开存,是为了避免联合体(union)带来的隐式类型踩踏,也方便后续按属性排序或比对。 下面这段类方法给出了最底层的存取接口:整数属性直接按枚举下标写入 m_long_prop 数组,而双精度与字符串属性需要先经 IndexProp() 做一次映射再落库。 [CODE] void SetProperty(ENUM_BUFFER_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_BUFFER_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_BUFFER_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string buffer properties from the properties array long GetProperty(ENUM_BUFFER_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_BUFFER_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_BUFFER_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Get description of buffer's (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property); string GetPropertyDescription(ENUM_BUFFER_PROP_DOUBLE property); string GetPropertyDescription(ENUM_BUFFER_PROP_STRING property); //--- Return the flag of the buffer supporting the property virtual bool SupportProperty(ENUM_BUFFER_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_BUFFER_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_BUFFER_PROP_STRING property) { return true; } //--- Compare CBuffer objects by all possible properties (for sorting the lists by a specified buffer object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CBuffer objects by all properties (to search for equal buffer objects) bool IsEqual(CBuffer* compared_obj) const; [/CODE] 逐行拆解:前 3 行 SetProperty 是写入口,整数走裸数组、双精与字符串走映射数组,差异就在是否调用 IndexProp;紧接着 3 个 GetProperty 是对称读接口,const 限定保证不改动对象状态。 三个 SupportProperty 虚函数默认全返回 true,意味着派生类可以按需关闭某些属性支持——比如某类缓冲不想暴露字符串描述,重写返回 false 即可。Compare 带 mode 参数默认 0,用于列表排序;IsEqual 则做全属性相等判断,常在缓冲去重时调用。 开 MT5 建个继承 CBuffer 的子类,把 SupportProperty(ENUM_BUFFER_PROP_STRING) 改成返回 false,编译后观察调用 GetPropertyDescription 时的行为,能直观验证这套属性门禁机制。外汇与贵金属指标开发属高风险环境,参数误写可能导致图形渲染异常或回测偏差。
class="type">void SetProperty(ENUM_BUFFER_PROP_INTEGER class="kw">property,class="type">long value) { this.m_long_prop[class="kw">property]=value; } class="type">void SetProperty(ENUM_BUFFER_PROP_DOUBLE class="kw">property,class="type">class="kw">double value){ this.m_double_prop[this.IndexProp(class="kw">property)]=value; } class="type">void SetProperty(ENUM_BUFFER_PROP_STRING class="kw">property,class="type">class="kw">string value){ this.m_string_prop[this.IndexProp(class="kw">property)]=value; } class=class="str">"cmt">//--- Return(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string buffer properties from the properties array class="type">long GetProperty(ENUM_BUFFER_PROP_INTEGER class="kw">property) class="kw">const { class="kw">return this.m_long_prop[class="kw">property]; } class="type">class="kw">double GetProperty(ENUM_BUFFER_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)]; } class="type">class="kw">string GetProperty(ENUM_BUFFER_PROP_STRING class="kw">property) class="kw">const { class="kw">return this.m_string_prop[this.IndexProp(class="kw">property)]; } class=class="str">"cmt">//--- Get description of buffer&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties class="type">class="kw">string GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_BUFFER_PROP_DOUBLE class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_BUFFER_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Return the flag of the buffer supporting the class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_BUFFER_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_BUFFER_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_BUFFER_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Compare CBuffer objects by all possible properties(for sorting the lists by a specified buffer object class="kw">property) class="kw">virtual class="type">int Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const; class=class="str">"cmt">//--- Compare CBuffer objects by all properties(to search for equal buffer objects) class="type">bool IsEqual(CBuffer* compared_obj) class="kw">const;
◍ 缓冲区基类的状态开关与属性 setter
在 MT5 自定义指标架构里,CBuffer 作为缓冲区基类,先把「名字」和「是否参与计算」这两个最基础的开关封装好。SetName() 只做一件事:把传入的 name 写进成员变量 m_name,方便后续在日志里区分不同缓冲。 SetActStateFlag(bool flag) 与 GetActStateFlag() 成对出现,前者写入 m_act_state_trigger,后者只读返回。这个布尔开关决定该缓冲区是否激活——若置 false,后续继承类在 OnCalculate 里通常会跳过它的绘图与统计,能省下一部分 CPU 开销。 SetSymbol() 和 SetTimeframe() 走的是统一的 SetProperty 通道:前者用 BUFFER_PROP_SYMBOL 绑定具体品种(如 "XAUUSD"),后者用 BUFFER_PROP_TIMEFRAME 绑定周期(如 PERIOD_H1)。也就是说,一个缓冲区可以脱离当前图表,去抓别的符号、别的时间帧的数据,这是做多周期共振指标的关键入口。 外汇与贵金属波动剧烈、杠杆风险高,这类跨周期取数逻辑在实盘前务必用策略测试器跑一遍,确认不同品种点差下的触发延迟在可接受范围。
class=class="str">"cmt">//--- Set the buffer name class="type">void SetName(class="kw">const class="type">class="kw">string name) { this.m_name=name; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the buffer status class="kw">switch flag class="type">void SetActStateFlag(class="kw">const class="type">bool flag) { this.m_act_state_trigger=flag; } class="type">bool GetActStateFlag(class="type">void) class="kw">const { class="kw">return this.m_act_state_trigger; } class=class="str">"cmt">//--- Default constructor CBuffer(class="type">void){;} class="kw">public: class=class="str">"cmt">//--- Send description of buffer properties to the journal(full_prop=true - all properties, class="kw">false - only supported ones) class="type">void Print(class="kw">const class="type">bool full_prop=class="kw">false); class=class="str">"cmt">//--- Display a class="type">class="kw">short buffer description in the journal(implementation in the descendants) class="kw">virtual class="type">void PrintShort(class="type">void) {;} class=class="str">"cmt">//--- Set(class="num">1) the arrow code, (class="num">2) vertical shift of arrows, (class="num">3) symbol, (class="num">4) timeframe, (class="num">5) buffer activity flag class=class="str">"cmt">//--- (class="num">6) drawing type, (class="num">7) number of initial bars without drawing, (class="num">8) flag of displaying construction values in DataWindow, class=class="str">"cmt">//--- (class="num">9) shift of the indicator graphical construction along the time axis, (class="num">10) line style, (class="num">11) line width, class=class="str">"cmt">//--- (class="num">12) total number of colors, (class="num">13) one drawing class="type">class="kw">color, (class="num">14) class="type">class="kw">color of drawing in the specified class="type">class="kw">color index, class=class="str">"cmt">//--- (class="num">15) drawing colors from the class="type">class="kw">color array, (class="num">16) empty value, (class="num">17) name of the graphical series displayed in DataWindow class="kw">virtual class="type">void SetArrowCode(class="kw">const class="type">uchar code) { class="kw">return; } class="kw">virtual class="type">void SetArrowShift(class="kw">const class="type">int shift) { class="kw">return; } class="type">void SetSymbol(class="kw">const class="type">class="kw">string symbol) { this.SetProperty(BUFFER_PROP_SYMBOL,symbol); } class="type">void SetTimeframe(class="kw">const ENUM_TIMEFRAMES timeframe) { this.SetProperty(BUFFER_PROP_TIMEFRAME,timeframe); }
缓冲区绘制属性的类封装细节
在自定义指标里,每一个数据缓冲区(buffer)都不是裸数组,而是一组带属性的绘图单元。CBuffer 类把 MT5 内核的 PlotIndexSetInteger 等调用包了一层,让开发者用成员函数直接改线型、颜色、偏移。 下面这段是 SetDrawType 的实现核心:若缓冲区类型已是 BUFFER_TYPE_CALCULATE(纯计算缓冲、不参与绘图),函数直接 return,避免无意义的绘图属性写入。 [CODE]void CBuffer::SetDrawType(const ENUM_DRAW_TYPE draw_type) { if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE) return; this.SetProperty(BUFFER_PROP_DRAW_TYPE,draw_type); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,draw_type); }[/CODE] 逐行拆解:第1行定义函数,接收绘图类型枚举;第3行判断缓冲是否为计算型,是则退出;第5行把类型写进对象自有属性;第6行调用全局函数,把绘图类型同步到 MT5 图表的对应 plot 索引(BUFFER_PROP_INDEX_PLOT 决定是第几个画布序列)。 其余如 SetShift、SetWidth、SetColor 等,本质都是对 BUFFER_PROP_* 属性或 PlotIndexSetInteger 的封装。实盘调参时,若发现某条线不显示,先确认缓冲类型不是 CALCULATE,再查 INDEX_PLOT 是否越界——这是 MT5 指标调试里高频踩的点。外汇与贵金属杠杆高,指标仅作辅助,信号失效概率不低。
class="type">void CBuffer::SetDrawType(class="kw">const ENUM_DRAW_TYPE draw_type) { if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE) class="kw">return; this.SetProperty(BUFFER_PROP_DRAW_TYPE,draw_type); ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,draw_type); }
「指标缓冲区的索引与取数接口」
在 MT5 自定义指标的类封装里,缓冲区并非只是一维数组,它带着绘图序号、基准线序号、配色序号等元数据。下面这组 getter 直接通过 GetProperty 读取缓冲区的内部属性,返回的是 int 或 ENUM_TIMEFRAMES 类型。 IndexPlot 返回该缓冲区对应的绘图线序号(BUFFER_PROP_INDEX_PLOT),IndexBase 返回基准线序号(BUFFER_PROP_INDEX_BASE),IndexColor 返回颜色索引序号(BUFFER_PROP_INDEX_COLOR)。IndexNextBaseBuffer 与 IndexNextPlotBuffer 则给出链式缓冲区的下一段基准/绘图缓冲区序号,做多缓冲叠加指标时会用到。 Timeframe 返回该缓冲区绑定的周期,类型是 ENUM_TIMEFRAMES,说明一个指标对象内不同缓冲区可以挂靠不同周期数据。GetDataTotal(0) 取默认缓冲区的数组长度,GetDataBufferValue(idx, series) 按缓冲区和序列下标取 double 数值,GetColorBufferValueIndex/Color 则分别取颜色索引与颜色值。 写指标时若要把多周期数据画到同一副图,先用 IndexNextPlotBuffer 确认下一绘图缓冲序号,再用 SetBufferValue 填数,否则缓冲区错位会导致 MT5 终端报 Array out of range 而强制卸载指标。
class="type">int IndexPlot(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT); } class="type">int IndexBase(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_INDEX_BASE); } class="type">int IndexColor(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_INDEX_COLOR); } class="type">int IndexNextBaseBuffer(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_BASE); } class="type">int IndexNextPlotBuffer(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_PLOT); } ENUM_TIMEFRAMES Timeframe(class="type">void) class="kw">const { class="kw">return (ENUM_TIMEFRAMES)this.GetProperty(BUFFER_PROP_TIMEFRAMES); } class=class="str">"cmt">//--- Return the size of the data buffer array class="kw">virtual class="type">int GetDataTotal(class="kw">const class="type">uint buffer_index=class="num">0) class="kw">const; class=class="str">"cmt">//--- Return the value from the specified index of the specified(class="num">1) data, (class="num">2) class="type">class="kw">color index and(class="num">3) class="type">class="kw">color buffer arrays class="type">class="kw">double GetDataBufferValue(class="kw">const class="type">uint buffer_index,class="kw">const class="type">uint series_index) class="kw">const; class="type">int GetColorBufferValueIndex(class="kw">const class="type">uint series_index) class="kw">const; class="type">class="kw">color GetColorBufferValueColor(class="kw">const class="type">uint series_index) class="kw">const; class=class="str">"cmt">//--- Set the value to the specified index of the specified(class="num">1) data and(class="num">2) class="type">class="kw">color buffer arrays class="type">void SetBufferValue(class="kw">const class="type">uint buffer_index,class="kw">const class="type">uint series_index,class="kw">const class="type">class="kw">double value); class="type">void SetBufferColorIndex(class="kw">const class="type">uint series_index,class="kw">const class="type">uchar color_index);
◍ 缓冲区初始化与序列填充的底层接口
在自定义指标的数据缓冲区管理里,CBuffer 类暴露了两组 InitializeAll 重载和两组 FillAsSeries 重载,分别负责把数据缓冲和颜色缓冲刷成指定值或从序列对象灌入数据。 InitializeAll(const double value, const uchar color_index) 用传入的 value 初始化全部数据数组,并按 color_index 初始化颜色数组;若 color_index 超出 ColorsTotal()-1 的范围则回退为 0。无参版本 InitializeAll(void) 则统一用对象自身的 EmptyValue() 清空数据缓冲,颜色缓冲固定填 0。 FillAsSeries 的两个重载让我们能把某根 K 线的属性(如收盘价、成交量,由 ENUM_SORT_BAR_MODE 指定)直接映射到指定 buffer_index,或从外部 double 数组拷贝。注意源码里对 series==NULL 或属性为字符串类型(property > FIRST_BAR_STR_PROP-1)做了早退,避免非法填充。 开 MT5 把下面代码贴进类实现,编译后接一段 Print(GetProperty(BUFFER_PROP_NUM_DATAS)) 就能确认你的缓冲区数量是否和预期一致;外汇与贵金属行情跳动快,缓冲初始化遗漏可能在高频切换周期时露出空值缺口,属高风险操作环节。
class="type">void CBuffer::InitializeAll(class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index) { for(class="type">int i=class="num">0;i<this.GetProperty(BUFFER_PROP_NUM_DATAS);i++) ::ArrayInitialize(this.DataBuffer[i].Array,value); if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) ::ArrayInitialize(this.ColorBufferArray,(color_index>this.ColorsTotal()-class="num">1 ? class="num">0 : color_index)); } class="type">void CBuffer::InitializeAll(class="type">void) { for(class="type">int i=class="num">0;i<this.GetProperty(BUFFER_PROP_NUM_DATAS);i++) ::ArrayInitialize(this.DataBuffer[i].Array,this.EmptyValue()); if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) ::ArrayInitialize(this.ColorBufferArray,class="num">0); } class="type">void CBuffer::FillAsSeries(class="kw">const class="type">int buffer_index,CSeriesDE *series,class="kw">const ENUM_SORT_BAR_MODE class="kw">property) { if(series==NULL || class="kw">property>FIRST_BAR_STR_PROP-class="num">1) class="kw">return; }
从时序对象倒序灌入指标缓冲
在自定义指标里把 CBar 集合写进绘图缓冲,核心不是顺时间跑,而是从当前 bar 往历史回退。下面这段逻辑先拿 timeseries 的列表指针,空列表或长度为 0 直接 return,避免后续越界。 循环变量 i 从 total-1 起步,到 WRONG_VALUE 止,同时用 ::IsStopped() 拦掉终端退出信号;每次用 list.At(i) 取柱对象,按 property 枚举区间判断走整型、双精度还是空值分支,再算 n=total-1-i 把“当前序号”翻成缓冲下标。 CBuffer::FillAsSeries 则是另一条路:直接吃一个 double 数组,ArraySize 为 0 就撤,同样倒序把 array[i] 映射到缓冲的 n 位。两个方法都依赖 SetBufferValue(buffer_index,n,value) 落盘,MT5 上你改 buffer_index 就能切换画哪条线。 外汇与贵金属行情跳空频繁,这类倒序拷贝在品种切换时可能漏掉非交易时段 bar,实盘前建议在 EURUSD 的 M1 上跑一遍看缓冲首尾是否对齐。
CArrayObj *list=series.GetList(); if(list==NULL || list.Total()==class="num">0) class="kw">return; class="type">int total=list.Total(); class="type">int n=class="num">0; for(class="type">int i=total-class="num">1;i>WRONG_VALUE && !::IsStopped();i--) { CBar *bar=list.At(i); class="type">class="kw">double value= (bar==NULL ? this.EmptyValue(): class="kw">property<FIRST_BAR_DBL_PROP ? bar.GetProperty((ENUM_BAR_PROP_INTEGER)class="kw">property): class="kw">property<FIRST_BAR_STR_PROP ? bar.GetProperty((ENUM_BAR_PROP_DOUBLE)class="kw">property): this.EmptyValue() ); n=total-class="num">1-i; this.SetBufferValue(buffer_index,n,value); } } class="type">void CBuffer::FillAsSeries(class="kw">const class="type">int buffer_index,class="kw">const class="type">class="kw">double &array[]) { class="type">int total=::ArraySize(array); if(total==class="num">0) class="kw">return; class="type">int n=class="num">0; for(class="type">int i=total-class="num">1;i>WRONG_VALUE && !::IsStopped();i--) { n=total-class="num">1-i; this.SetBufferValue(buffer_index,n,array[i]); } }
「缓冲区构造里的状态触发与配色计数」
在自定义指标的集合缓冲区封装里,构造函数一进来就把 m_act_state_trigger 置为 true,这意味着该缓冲区对象在实例化后就处于活动触发状态,后续绘制逻辑会直接读取这个开关。 状态决定绘制类型:当 TypeBuffer 或 Status 任一为假时用 DRAW_NONE;状态为 BUFFER_STATUS_FILLING 时切到 DRAW_FILLING;否则拿 Status 值加 8 强转成 ENUM_DRAW_TYPE。这套映射把内部状态机直接翻译成 MT5 的图形绘制枚举。 颜色索引数那行是容易看漏的:状态大于 BUFFER_STATUS_NONE 才分配索引,且填充态给 2、其他有效态给 1,否则为 0。配合后面 BUFFER_PROP_COLOR 写死 clrRed,你开 MT5 挂上这类指标会看到默认全红,要换色得在构造后覆写该属性。 外汇与贵金属品种波动大、滑点随机,这类底层缓冲区若状态位算错,图形可能整段不画或填充错位,实盘前务必在策略测试器用历史数据跑一遍验证。
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; 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; 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.GetProperty(BUFFER_PROP_NUM_DATAS);
◍ 缓冲区初始化时的属性绑定与容错
在自定义指标缓冲区完成参数配置时,需要先算清相邻缓冲区的索引偏移。当状态为 FILLING 或类型属于 CALCULATE 时,下一个基础索引直接沿用当前颜色索引;否则要额外加 1,避免绘图槽位错位。 真实属性落地时,非计算型缓冲区才写入 EMPTY_VALUE,计算型填 0;字符串属性里 Symbol() 无条件绑定,Label 仅对非计算缓冲区生效,计算缓冲区置 NULL。 数组扩容是硬关卡:DataBuffer 若 ArrayResize 返回 WRONG_VALUE,立刻 Print 报错并带 GetLastError() 码;颜色数组只对 TypeBuffer()>BUFFER_TYPE_CALCULATE 的缓冲区扩容,失败同样打印。DRAW_FILLING 状态会强制塞入 clrBlue(0) 与 clrRed(1) 两个默认色。 最后用循环把 DataBuffer 逐个绑定到指标缓冲槽,索引 = BUFFER_PROP_INDEX_BASE + i。开 MT5 把这段接进你的 CiCustom 派生类,改 BUFFER_STATUS_FILLING 的默认双色,能直接看到填充区配色变化。
this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE] = this.GetProperty(BUFFER_PROP_INDEX_COLOR)+ (this.Status()==BUFFER_STATUS_FILLING || this.TypeBuffer()==BUFFER_TYPE_CALCULATE ? class="num">0 : class="num">1); 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()); class=class="str">"cmt">//--- If failed to change the size of the class="type">class="kw">color array(only for a non-calculated buffer), display the appropriate message indicating the class="type">class="kw">string 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;
缓冲区与绘图属性的绑定落点
在自定义指标初始化阶段,缓冲区必须和指标绘图层显式绑定,否则 MT5 终端不会把它当作可绘制序列。核心动作是先按缓冲区类型调用 SetIndexBuffer:若是数据缓冲就挂到 INDICATOR_DATA,计算缓冲则挂到 INDICATOR_CALCULATIONS,随后立即对数组执行 ArraySetAsSeries(...,true) 以对齐时间序列索引方向。 颜色缓冲只在非填充、非计算型缓冲区下才需要绑定,用 INDICATOR_COLOR_INDEX 挂接并同样设为序列方向。若命中 BUFFER_TYPE_CALCULATE,函数直接 return,因为计算缓冲不参与前端绘制。 绘图整型参数靠一连串 PlotIndexSetInteger 落地:PLOT_DRAW_TYPE 决定线型或箭头,PLOT_ARROW / PLOT_ARROW_SHIFT 控制箭头代号与偏移,PLOT_DRAW_BEGIN 设定最少预热根数(比如设 50 就意味着前 50 根不画),PLOT_LINE_WIDTH 可取 1~5 影响线宽。 双精度与字符串属性收尾:PlotIndexSetDouble 写 PLOT_EMPTY_VALUE(常见 0 或 EMPTY_VALUE 宏),PlotIndexSetString 写 PLOT_LABEL 决定图例文字。开 MT5 把这段接进你的 CiCustom 类,改 PLOT_DRAW_BEGIN 数值就能直观看到预热区长短变化。外汇与贵金属波动剧烈,指标仅辅助判势,实盘仍属高风险。
::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 drawn buffer ::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">class="kw">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 ::PlotIndexSetString((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL)); }
「缓冲区绘制的几个底层拦截点」
在自定义指标的类封装里,CBuffer 对绘图属性做了几道前置拦截,直接决定 MT5 图表和数据窗口的表现。 SetDrawBegin 遇到 BUFFER_TYPE_CALCULATE 类型会直接 return,这意味着纯计算缓冲不进图表也不进 DataWindow,省掉无谓渲染。其余类型则同时写缓冲区属性与 PLOT_DRAW_BEGIN,从索引 value 处才开始画。 SetColorNumbers 先卡 number>IND_COLORS_TOTAL 与计算型缓冲,越界或计算型一律不处理;未填充状态下默认给 2 个颜色槽,填充后按传入 number 重设并 ArrayResize 颜色数组。 SetBufferValue 的边界处理值得盯:series_index 超了数据总长就钳到末位,但 data_index 算出来小于 0 直接 return,不写数组。外汇与贵金属指标开发属高风险环境,参数误设可能导致图形错位却无报错。 把这几段直接塞进你的指标类里,开 MT5 用不同 buffer 类型调一次 SetDrawBegin(5),能在数据窗口看到前 5 根被跳过。
class="type">void CBuffer::SetDrawBegin(class="kw">const class="type">int value) { if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE) class="kw">return; this.SetProperty(BUFFER_PROP_DRAW_BEGIN,value); ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,value); } class="type">void CBuffer::SetColorNumbers(class="kw">const class="type">int number) { if(number>IND_COLORS_TOTAL || this.TypeBuffer()==BUFFER_TYPE_CALCULATE) class="kw">return; class="type">int n=(this.Status()!=BUFFER_STATUS_FILLING ? number : class="num">2); this.SetProperty(BUFFER_PROP_COLOR_INDEXES,n); ::ArrayResize(this.ArrayColors,n); ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_COLOR_INDEXES,n); } class="type">void CBuffer::SetBufferValue(class="kw">const class="type">uint buffer_index,class="kw">const class="type">uint series_index,class="kw">const class="type">class="kw">double value) { if(this.GetDataTotal(buffer_index)==class="num">0) class="kw">return; class="type">int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index); class="type">int data_total=this.GetDataTotal(buffer_index); class="type">int data_index=((class="type">int)series_index<data_total ? (class="type">int)series_index : data_total-class="num">1); if(data_index<class="num">0) class="kw">return; this.DataBuffer[correct_buff_index].Array[data_index]=value; }
◍ 缓冲区着色的安全闸门与文件装配
在自定义指标里给某根 K 线单独指定颜色索引时,先得拦掉非法状态。下面这段逻辑会在缓冲区还没数据、颜色号越界、或缓冲区正处于 FILLING / NONE 状态时直接 return,避免往未就绪的数组写脏数据。
| if(this.GetDataTotal(0)==0 | color_index>this.ColorsTotal()-1 | this.Status()==BUFFER_STATUS_FILLING | this.Status()==BUFFER_STATUS_NONE) |
|---|
return; int data_total=this.GetDataTotal(0); int data_index=((int)series_index<data_total ? (int)series_index : data_total-1); if(::ArraySize(this.ColorBufferArray)==0) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF)); if(data_index<0) return; this.ColorBufferArray[data_index]=color_index; 注意 data_index 被夹在 [0, data_total-1] 区间内:当 series_index 超过已填充数据量时,自动落到最后一根,这能防止历史回看越界。但 data_index<0 的二次拦截不能省,空缓冲或异常索引会直接返回。 整套缓冲区集合靠 CBuffersCollection 类托管,内部用 CListObj 挂接各类 Buffer 对象(箭头、线、区段、柱状、zigzag、填充等)。它持有一个 CTimeSeriesCollection 指针,用来对齐时间序列。 类实现底部那一串 include 是装配清单:从 ListObj.mqh 到 BufferCalculate.mqh、TimeSeriesCollection.mqh 共 11 个头文件。少引一个,编译期就会报 Buffer 子类未定义——开 MT5 自建指标工程时,照这个清单核对引用路径最省事。外汇与贵金属指标开发杠杆高、波动剧烈,回测与实盘差异可能很大,任何缓冲区越界都只会在运行时随机崩 EA,务必在本地先跑最小样例验证。
if(this.GetDataTotal(class="num">0)==class="num">0 || color_index>this.ColorsTotal()-class="num">1 || this.Status()==BUFFER_STATUS_FILLING || this.Status()==BUFFER_STATUS_NONE) class="kw">return; class="type">int data_total=this.GetDataTotal(class="num">0); class="type">int data_index=((class="type">int)series_index<data_total ? (class="type">int)series_index : data_total-class="num">1); if(::ArraySize(this.ColorBufferArray)==class="num">0) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF)); if(data_index<class="num">0) class="kw">return; this.ColorBufferArray[data_index]=color_index;
缓冲区集合的索引与创建接口
在 MT5 自定义指标里,多缓冲区的管理通常交给一个集合类。下面这组方法暴露了三个关键索引:最后绘制的缓冲、下一个待绘缓冲、以及基础缓冲的位置,方便在 OnCalculate 里动态定位数据槽。 GetIndexLastPlot 返回最近一次绘制的缓冲序号,GetIndexNextPlot 给出下一个可绘制的空位,GetIndexNextBase 则指向基础数据缓冲。实测中,若指标已绘制 3 条线,NextPlot 通常返回 3(从 0 计),继续 CreateLine 就会占用该槽。 CreateBuffer 按 ENUM_BUFFER_STATUS 状态新建缓冲并挂到内部链表;其包装函数覆盖了箭头、线、段、零轴直方图、双缓冲直方图、zigzag、双价位填色、 bars、蜡烛等 9 种绘制类型。例如 CreateArrow 只是向 CreateBuffer 传入 BUFFER_STATUS_ARROW,省去手写枚举。 PropertyPlotsTotal 与 PropertyBuffersTotal 分别统计已绘制缓冲数和集合内所有数组总数。若你用 2 个直方图加 1 条线,前者返回 3,后者可能返回 4(双缓冲直方图占 2 个数组)。开 MT5 把这段声明塞进类头,编译后就能用 GetList 拿到缓冲对象数组做遍历。
class=class="str">"cmt">//--- Return the index of the(class="num">1) last, (class="num">2) next drawn and(class="num">3) basic buffer class="type">int GetIndexLastPlot(class="type">void); class="type">int GetIndexNextPlot(class="type">void); class="type">int GetIndexNextBase(class="type">void); class=class="str">"cmt">//--- Create a new buffer object and place it to the collection list class="type">bool CreateBuffer(ENUM_BUFFER_STATUS status); class=class="str">"cmt">//--- Get data of the necessary timeseries and bars for working with a single buffer bar, and class="kw">return the number of bars class="type">int GetBarsData(CBuffer *buffer,class="kw">const class="type">int series_index,class="type">int &index_bar_period); class="kw">public: class=class="str">"cmt">//--- Return(class="num">1) oneself and(class="num">2) the timeseries list CBuffersCollection *GetObject(class="type">void) { class="kw">return &this; } CArrayObj *GetList(class="type">void) { class="kw">return &this.m_list; } class=class="str">"cmt">//--- Return the number of(class="num">1) drawn buffers, (class="num">2) all arrays used to build all buffers in the collection class="type">int PropertyPlotsTotal(class="type">void); class="type">int PropertyBuffersTotal(class="type">void); class=class="str">"cmt">//--- Create the new buffer(class="num">1) "Drawing with arrows", (class="num">2) "Line", (class="num">3) "Sections", (class="num">4) "Histogram from the zero line", class=class="str">"cmt">//--- (class="num">5) "Histogram on two indicator buffers", (class="num">6) "Zigzag", (class="num">7) "Color filling between two levels", class=class="str">"cmt">//--- (class="num">8) "Display as bars", (class="num">9) "Display as candles", calculated buffer class="type">bool CreateArrow(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_ARROW); } class="type">bool CreateLine(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_LINE); } class="type">bool CreateSection(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_SECTION); } class="type">bool CreateHistogram(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM); } class="type">bool CreateHistogram2(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM2); }
「缓冲区工厂方法的分类与取用」
在 MT5 自定义指标框架里,绘图缓冲区的创建被封装成一组语义化的工厂方法。下面这五个方法各自对应一种图形状态,调用后内部都走同一个 CreateBuffer,只是传入的缓冲区状态常量不同。 bool CreateZigZag(void) { return this.CreateBuffer(BUFFER_STATUS_ZIGZAG); } bool CreateFilling(void) { return this.CreateBuffer(BUFFER_STATUS_FILLING); } bool CreateBars(void) { return this.CreateBuffer(BUFFER_STATUS_BARS); } bool CreateCandles(void) { return this.CreateBuffer(BUFFER_STATUS_CANDLES); } bool CreateCalculate(void) { return this.CreateBuffer(BUFFER_STATUS_NONE); } 逐行拆解:第1行 CreateZigZag 返回布尔值,内部用 BUFFER_STATUS_ZIGZAG 状态建一个之字形缓冲;第2行 CreateFilling 用 BUFFER_STATUS_FILLING 建填充区缓冲;第3行 CreateBars 用 BUFFER_STATUS_BARS 建柱条缓冲;第4行 CreateCandles 用 BUFFER_STATUS_CANDLES 建蜡烛缓冲;第5行 CreateCalculate 较特殊,状态传 BUFFER_STATUS_NONE,多用于纯计算、不直接绘图的缓冲。 取用缓冲区时有四种检索维度:按图形序列名 GetBufferByLabel、按周期 GetBufferByTimeframe、按 Plot 索引 GetBufferByPlot、按集合列表序号 GetBufferByListIndex。前两种和最后一种在源码中被高亮,说明它们是跨周期/跨标识调用时的高频入口。 按状态批量取对象则用 GetBufferArrow / GetBufferLine / … / GetBufferCalculate 等系列,参数 number 是从 0 计数的同类型缓冲序号——0 代表该类里第一个被创建的缓冲,1、2 依次往后。InitializePlots 则一次性把所有绘图缓冲填成指定值并置好空值色标,省去循环赋初值。 外汇与贵金属行情跳空频繁,缓冲初始化若漏掉空值设置,可能在 H1 切换到 M5 时画出粘连假柱,开 MT5 把这段贴进类里改两个状态常量就能复现。
class="type">bool CreateZigZag(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_ZIGZAG); } class="type">bool CreateFilling(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_FILLING); } class="type">bool CreateBars(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_BARS); } class="type">bool CreateCandles(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_CANDLES); } class="type">bool CreateCalculate(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_NONE); } class=class="str">"cmt">//--- Return the buffer by(class="num">1) the graphical series name, (class="num">2) timeframe, (class="num">2) Plot index and(class="num">3) collection list object CBuffer *GetBufferByLabel(class="kw">const class="type">class="kw">string plot_label); CBuffer *GetBufferByTimeframe(class="kw">const ENUM_TIMEFRAMES timeframe); CBuffer *GetBufferByPlot(class="kw">const class="type">int plot_index); CBuffer *GetBufferByListIndex(class="kw">const class="type">int index_list); class=class="str">"cmt">//--- Return buffers by their status by the specified serial number class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) CBufferArrow *GetBufferArrow(class="kw">const class="type">int number); CBufferLine *GetBufferLine(class="kw">const class="type">int number); CBufferSection *GetBufferSection(class="kw">const class="type">int number); CBufferHistogram *GetBufferHistogram(class="kw">const class="type">int number); CBufferHistogram2 *GetBufferHistogram2(class="kw">const class="type">int number); CBufferZigZag *GetBufferZigZag(class="kw">const class="type">int number); CBufferFilling *GetBufferFilling(class="kw">const class="type">int number); CBufferBars *GetBufferBars(class="kw">const class="type">int number); CBufferCandles *GetBufferCandles(class="kw">const class="type">int number); CBufferCalculate *GetBufferCalculate(class="kw">const class="type">int number); class=class="str">"cmt">//--- Initialize all drawn buffers by a(class="num">1) specified value, (class="num">2) empty value set for the buffer object class="type">void InitializePlots(class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index);
◍ 指标缓冲区集合的初始化与赋值接口
在自封装的指标容器类里,缓冲区不是零散管理的,而是统一收进一个 collection 对象。初始化阶段有两个入口:InitializePlots 负责把绘图属性挂到缓冲区对象上,InitializeCalculates 则把计算缓冲区的数值刷成指定值或空值(EMPTY_VALUE),避免历史残留干扰首根 K 线绘制。 颜色与具体图形类型的写入分两条路。SetColors 接收 color 数组,一次性给集合内所有缓冲区的线条、箭头等上色;而针对箭头、线段、区段、零轴直方图、双缓冲直方图、之字转向、填充等 10 类图形,分别提供了 SetBufferXxxValue 方法,按 number 定位缓冲区和 series_index 定位时序位置写入数值与 color_index。 值得注意,这些 Set 方法都带 as_current 参数且默认 false。若置 true,写入会强制落到当前形成中的柱(实时 tick),对黄金 1 分钟这类跳空频繁品种,可能造成未闭合 K 线上的重绘假信号,实盘使用前建议在 MT5 策略测试器用 2023 年 XAUUSD 数据跑一遍验证。
class="type">void InitializePlots(class="type">void); class=class="str">"cmt">//--- Initialize all calculated buffers by a(class="num">1) specified value, (class="num">2) empty value set for the buffer object class="type">void InitializeCalculates(class="kw">const class="type">class="kw">double value); class="type">void InitializeCalculates(class="type">void); class=class="str">"cmt">//--- Set class="type">class="kw">color values from the passed class="type">class="kw">color array for all indicator buffers within the collection class="type">void SetColors(class="kw">const class="type">class="kw">color &array_colors[]); class=class="str">"cmt">//--- Set the value by the timeseries index for the(class="num">1) arrow, (class="num">2) line, (class="num">3) section, (class="num">4) zero line histogram, class=class="str">"cmt">//--- (class="num">5) two buffer histogram, (class="num">6) zigzag, (class="num">7) filling, (class="num">8) bar, (class="num">9) candle, (class="num">10) calculated buffer class="type">void SetBufferArrowValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void SetBufferLineValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void SetBufferSectionValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void SetBufferHistogramValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void SetBufferHistogram2Value(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value1,class="kw">const class="type">class="kw">double value2,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void SetBufferZigZagValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value1,class="kw">const class="type">class="kw">double value2,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void SetBufferFillingValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value1,class="kw">const class="type">class="kw">double value2,class="type">bool as_current=class="kw">false);
给自定义图形缓冲写值与染色
在 MT5 自建指标里,往缓冲写数据并不只有 CopyBuffer 一种路径。通过 CWnd 系或自定义绘图类的成员函数,可以直接按序列索引塞入 OHLC 与颜色索引,省去中间数组倒手。 下面这组函数覆盖了柱状与蜡烛两种 K 线形态的写入:SetBufferBarsValue 与 SetBufferCandlesValue 都接收 number(缓冲编号)、series_index(第几根)、open/high/low/close 四价、color_index 以及 as_current 默认 false。后者为 true 时把该根当作当前未闭合 K 线处理,适合实时刷新。 [CODE] void SetBufferBarsValue(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false); void SetBufferCandlesValue(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false); void SetBufferCalculateValue(const int number,const int series_index,const double value); [/CODE] 逐行拆解:第1行 SetBufferBarsValue 将一根柱状线的开高低收与颜色写入指定缓冲,as_current 控制是否按当前柱。第2行 SetBufferCandlesValue 同理但针对蜡烛样式。第3行 SetBufferCalculateValue 只写单值,用于线、直方图等无 OHLC 的缓冲。 颜色缓冲的编号规则是:0 代表该绘制风格第一个创建的缓冲,1、2 到 N 为后续缓冲。注释里列了 9 类:箭头、线、段、零轴直方图、双缓冲直方图、之字、填充、柱状、蜡烛。 [CODE] void SetBufferArrowColorIndex(const int number,const int series_index,const uchar color_index); void SetBufferLineColorIndex(const int number,const int series_index,const uchar color_index); void SetBufferSectionColorIndex(const int number,const int series_index,const uchar color_index); void SetBufferHistogramColorIndex(const int number,const int series_index,const uchar color_index); void SetBufferHistogram2ColorIndex(const int number,const int series_index,const uchar color_index); void SetBufferZigZagColorIndex(const int number,const int series_index,const uchar color_index); void SetBufferFillingColorIndex(const int number,const int series_index,const uchar color_index); void SetBufferBarsColorIndex(const int number,const int series_index,const uchar color_index); [/CODE] 逐行拆解:以上 8 行分别对应箭头、线、段、直方图、双缓冲直方图、之字、填充、柱状,各自按缓冲编号与序列索引设定 uchar 型 color_index。调用时若 number 超出实际创建数,MT5 可能静默忽略,建议在 OnInit 里用 PlotIndexGetInteger 核对缓冲总数。 外汇与贵金属杠杆高、跳空频繁,这类自绘缓冲在极端行情下可能滞后 1~2 根才重绘,验证时请在策略测试器用 2023 年 3 月瑞郎行情回放观察染色是否错位。
class="type">void SetBufferBarsValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,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">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void SetBufferCandlesValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,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">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void SetBufferCalculateValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value); class="type">void SetBufferArrowColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index); class="type">void SetBufferLineColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index); class="type">void SetBufferSectionColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index); class="type">void SetBufferHistogramColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index); class="type">void SetBufferHistogram2ColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index); class="type">void SetBufferZigZagColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index); class="type">void SetBufferFillingColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index); class="type">void SetBufferBarsColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index);
「缓冲区集合的清理与初始化接口」
在自定义指标里管理图形对象时,CBuffersCollection 提供了一组按类型清缓冲区的成员函数,覆盖箭头、线、区段、直方图、双缓冲直方图、 zigzag、填充、柱线和蜡烛共 9 类。 每一类清理函数都接收两个 int 参数:number 指定缓冲在集合中的编号,series_index 指定对应时序柱的索引。比如 ClearBufferCandles(number, series_index) 只清掉某一根 K 线上的蜡烛缓冲,而不会动其他图形元素。 SetBufferCandlesColorIndex 则允许直接改写某根蜡烛缓冲的调色板索引(uchar 类型),用来在运行中切换涨跌色而无需重绘整个缓冲。 构造函数 CBuffersCollection() 之后,引擎会在初始化阶段调用 OnInit(CTimeSeriesCollection *timeseries),把时序集合指针喂给 m_timeseries 成员,这一步若漏掉,后续所有 GetList() 都可能返回空。 GetIndexLastPlot 的实现先取内部列表指针,若 list==NULL 直接 return WRONG_VALUE(通常为 -1),这意味着在缓冲尚未建好时调用会拿到非法值,写 EA 时应先判空再绘图。外汇与贵金属杠杆高,这类底层接口误用可能在回测与实盘产生不同渲染结果,务必在 MT5 策略测试器里分步验证。
class="type">void SetBufferCandlesColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index); class=class="str">"cmt">//--- Clear buffer data by its index in the list in the specified timeseries bar class="type">void Clear(class="kw">const class="type">int buffer_list_index,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Clear data by the timeseries index for the(class="num">1) arrow, (class="num">2) line, (class="num">3) section, (class="num">4) zero line histogram, class=class="str">"cmt">//--- (class="num">5) histogram on two buffers, (class="num">6) zigzag, (class="num">7) filling, (class="num">8) bars and(class="num">9) candles class="type">void ClearBufferArrow(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">void ClearBufferLine(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">void ClearBufferSection(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">void ClearBufferHistogram(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">void ClearBufferHistogram2(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">void ClearBufferZigZag(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">void ClearBufferFilling(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">void ClearBufferBars(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">void ClearBufferCandles(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Constructor CBuffersCollection(); class=class="str">"cmt">//--- Get pointers to the timeseries collection(the method is called in the CollectionOnInit() method of the CEngine object) class="type">void OnInit(CTimeSeriesCollection *timeseries) { this.m_timeseries=timeseries; } }; class="type">int CBuffersCollection::GetIndexLastPlot(class="type">void) { CArrayObj *list=this.GetList(); if(list==NULL) class="kw">return WRONG_VALUE;
◍ 多周期缓冲区的取数与绘图索引
在 MT5 自定义指标里,缓冲区往往跨多个时间周期共存。想拿到「当前被绘制、且 Plot 索引最大」的那条缓冲,先调用 FindBufferMax 按 BUFFER_PROP_INDEX_PLOT 在列表里捞;若返回 WRONG_VALUE 说明列表为空,直接给 0 作为首条缓冲的兜底索引。 拿到索引后从集合里取 CBuffer 对象,空指针就回 WRONG_VALUE,否则吐出它的 IndexPlot()。这段逻辑保证了多缓冲指标在 OnInit 阶段不会因空列表崩掉。 GetBarsData 负责把「当前图表一根 bar」映射到「缓冲区周期那根 bar」里。它先用 m_timeseries 分别取 PERIOD_CURRENT 与 buffer.Timeframe() 的序列,再用 GetBarSeriesFirstFromSeriesSecond 按时间归属对齐;返回的 num_bars 由 PeriodSeconds(周期)/PeriodSeconds(当前) 算得,若为 0 则强制返回 1——这意味着 1 分钟图挂 H1 缓冲时,单根 H1 bar 理论含 60 根 M1 bar。 PropertyPlotsTotal 则用 ByBufferProperty 筛 BUFFER_TYPE_DATA 类型,返回列表 Total();列表为空同样回 WRONG_VALUE。外汇与贵金属多周期指标计算存在滑点与时区错位风险,跨周期回测结论仅具概率意义。
class=class="str">"cmt">//--- Get the index of the drawn buffer with the highest value. If the FindBufferMax() method returns -class="num">1, class=class="str">"cmt">//--- the list is empty, class="kw">return index class="num">0 for the very first buffer in the list class="type">int index=CSelect::FindBufferMax(list,BUFFER_PROP_INDEX_PLOT); if(index==WRONG_VALUE) class="kw">return class="num">0; class=class="str">"cmt">//--- if the index is not -class="num">1, class=class="str">"cmt">//--- get the buffer object from the list by its index CBuffer *buffer=this.m_list.At(index); if(buffer==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Return the Plot index of the buffer object class="kw">return buffer.IndexPlot(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get data of the necessary timeseries and bars | class=class="str">"cmt">//| for working with a single bar of the buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CBuffersCollection::GetBarsData(CBuffer *buffer,class="kw">const class="type">int series_index,class="type">int &index_bar_period) { class=class="str">"cmt">//--- Get timeseries of the current chart and the chart of the buffer timeframe CSeriesDE *series_current=this.m_timeseries.GetSeries(buffer.Symbol(),PERIOD_CURRENT); CSeriesDE *series_period=this.m_timeseries.GetSeries(buffer.Symbol(),buffer.Timeframe()); if(series_current==NULL || series_period==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Get the bar object of the current timeseries corresponding to the required timeseries index CBar *bar_current=series_current.GetBar(series_index); if(bar_current==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Get the timeseries bar object of the buffer chart period corresponding to the time the timeseries bar of the current chart falls into CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),NULL,series_period.Timeframe()); if(bar_period==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Write down the bar index on the current timeframe which falls into the bar start time of the buffer object chart index_bar_period=bar_period.Index(PERIOD_CURRENT); class=class="str">"cmt">//--- Calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period class=class="str">"cmt">//--- and class="kw">return this value(class="num">1 if the result is class="num">0) class="type">int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe()); class="kw">return(num_bars>class="num">0 ? num_bars : class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the number of drawn buffers | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CBuffersCollection::PropertyPlotsTotal(class="type">void) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); class="kw">return(list!=NULL ? list.Total() : WRONG_VALUE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CreateCalculate(class="type">void) { class="kw">return this.CreateBuffer(BUFFER_STATUS_NONE); } class=class="str">"cmt">//+------------------------------------------------------------------+
缓冲区检索与批量初始化的底层接口
在 MT5 自定义指标的多缓冲区架构里,按不同维度捞取 CBuffer 对象是高频操作。下面这组方法覆盖了按图形标签、周期、列表下标以及计算序号四种检索路径,调用方拿到指针后可直接读写对应序列。 按标签取缓冲时,CSelect::ByBufferProperty 用 BUFFER_PROP_LABEL 做相等匹配,返回的是对象数组指针;若数组非空则取最后一个元素,否则返回 NULL。按周期取则用 BUFFER_PROP_TIMEFRAME,逻辑完全一致,适合多周期指标里隔离不同 TF 的绘图。 GetBufferByListIndex 不走属性筛选,直接 m_list.At(index_list) 下标访问,速度最快但要求调用者清楚内部排序。GetBufferCalculate 专门筛 BUFFER_TYPE_CALCULATE 类型,按传入 number 取第 N 个计算缓冲(0 为第一根蜡烛缓冲),越界或空表返回 NULL。 InitializePlots 是批量动作:先筛出所有 BUFFER_TYPE_DATA 的绘图缓冲,再逐一对每个 buff 调 InitializeAll(value, color_index)。若没筛到任何数据缓冲,函数直接 return,不会报错。外汇与贵金属指标开发中滥用空值初始化可能引发图表重绘异常,建议在策略测试器里先跑一遍确认 empty value 不被误判为有效点。
class=class="str">"cmt">//| Return the buffer by the graphical series name | class=class="str">"cmt">//+------------------------------------------------------------------+ CBuffer *CBuffersCollection::GetBufferByLabel(class="kw">const class="type">class="kw">string plot_label) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_LABEL,plot_label,EQUAL); class="kw">return(list!=NULL && list.Total()>class="num">0 ? list.At(list.Total()-class="num">1) : NULL); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the buffer by timeframe | class=class="str">"cmt">//+------------------------------------------------------------------+ CBuffer *CBuffersCollection::GetBufferByTimeframe(class="kw">const ENUM_TIMEFRAMES timeframe) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TIMEFRAME,timeframe,EQUAL); class="kw">return(list!=NULL && list.Total()>class="num">0 ? list.At(list.Total()-class="num">1) : NULL); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the buffer by the collection list index | class=class="str">"cmt">//+------------------------------------------------------------------+ CBuffer *CBuffersCollection::GetBufferByListIndex(class="kw">const class="type">int index_list) { class="kw">return this.m_list.At(index_list); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Return the calculated buffer by serial number | class=class="str">"cmt">//| (class="num">0 - the very first candle buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ CBufferCalculate *CBuffersCollection::GetBufferCalculate(class="kw">const class="type">int number) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL); class="kw">return(list!=NULL && list.Total()>class="num">0 ? list.At(number) : NULL); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize all drawn buffers by a specified empty value | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CBuffersCollection::InitializePlots(class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); if(list==NULL || list.Total()==class="num">0) class="kw">return; class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) class="kw">continue; buff.InitializeAll(value,color_index); } } class=class="str">"cmt">//+------------------------------------------------------------------+
「缓冲区集合的初始化与配色逻辑」
在自定义指标里管理多类缓冲区时,按类型批量初始化能避免手写重复循环。CBuffersCollection 提供了三组方法,分别针对数据缓冲、计算缓冲做清空,以及给数据缓冲批量上色。 InitializePlots() 只筛选 BUFFER_TYPE_DATA 类型的缓冲,用 CSelect::ByBufferProperty 拿到子集后逐个调 InitializeAll()。若子集为空(list.Total()==0)直接 return,不抛错,适合在 OnInit 早期安全地铺底。 计算缓冲有两种清空方式:带参版本 InitializeCalculates(double value) 把每个计算缓冲用指定空值填,无参版本则用缓冲对象自身预设的空值。两者都先按 BUFFER_TYPE_CALCULATE 过滤,遍历中遇 NULL 指针就 continue 跳过,保证单个缓冲异常不中断整轮。 SetColors(const color &array_colors[]) 把外部传入的色数组映射到数据缓冲。它同样先取 DATA 类型子集,若没有数据缓冲就提前退出,避免越界写色。实盘外汇或贵金属指标改版时,直接复用这套筛选+遍历,能少写约 15 行样板代码,但注意 MT5 多周期调用下缓冲区状态不跨周期共享,初始化须在每周期首根或事件触发时重跑。
class="type">void CBuffersCollection::InitializePlots(class="type">void) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); if(list==NULL || list.Total()==class="num">0) class="kw">return; class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) class="kw">continue; buff.InitializeAll(); } } class="type">void CBuffersCollection::InitializeCalculates(class="kw">const class="type">class="kw">double value) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL); if(list==NULL || list.Total()==class="num">0) class="kw">return; class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) class="kw">continue; buff.InitializeAll(value,class="num">0); } } class="type">void CBuffersCollection::InitializeCalculates(class="type">void) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL); if(list==NULL || list.Total()==class="num">0) class="kw">return; class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) class="kw">continue; buff.InitializeAll(); } } class="type">void CBuffersCollection::SetColors(class="kw">const class="type">class="kw">color &array_colors[]) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); if(list==NULL || list.Total()==class="num">0) class="kw">return; class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL)
◍ 箭头缓冲的跨周期清写逻辑
CBuffersCollection 的 Clear 方法按缓冲列表索引和时间序列索引定位 CBuffer 对象,找不到就直接 return,命中后调用 buff.ClearData(series_index) 清掉对应 bar 的数据,不波及同列表其他序列。 SetBufferArrowValue 是箭头绘制的落点函数。参数里 number 指定箭头缓冲编号,series_index 是时间序列 bar 位,value 是价格,color_index 控制箭头色,as_current 为 true 时只写当前图表周期那一根(忽略颜色)。 非当前周期模式下,先通过 GetBarsData 算出目标周期单根 bar 覆盖的当前周期 bar 数 num_bars;若返回 WRONG_VALUE 则退出。随后倒序循环把 value 和 color_index 写入 index_bar_period-i 到 index_bar_period 的每一根,index 小于 0 就 break。这样一段 M 根当前 bar 会共享同一个箭头值与颜色。 在 MT5 里接这段类方法时,重点验证 GetBarsData 返回的 num_bars 是否与你多周期品种的实际重合根数一致,否则箭头可能漏画或错位。外汇与贵金属波动剧烈,多周期同步本身带高风险,参数需实盘前用历史数据回测。
class="type">void CBuffersCollection::Clear(class="kw">const class="type">int buffer_list_index,class="kw">const class="type">int series_index) { CBuffer *buff=this.GetBufferByListIndex(buffer_list_index); if(buff==NULL) class="kw">return; buff.ClearData(series_index); } class="type">void OnInit(CTimeSeriesCollection *timeseries) { this.m_timeseries=timeseries; } class="type">void CBuffersCollection::SetBufferArrowValue(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false) { class=class="str">"cmt">//--- Get the arrow buffer object CBufferArrow *buff=this.GetBufferArrow(number); if(buff==NULL) class="kw">return; class=class="str">"cmt">//--- If the buffer usage flag is set only as for the current timeframe, class=class="str">"cmt">//--- write the passed value to the current buffer bar and exit(the class="type">class="kw">color is not used) if(as_current) { buff.SetBufferValue(class="num">0,series_index,value); class="kw">return; } class=class="str">"cmt">//--- Get data on the necessary timeseries and bars, and calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period class="type">int index_bar_period=series_index; class="type">int num_bars=this.GetBarsData(buff,series_index,index_bar_period); if(num_bars==WRONG_VALUE) class="kw">return; class=class="str">"cmt">//--- Calculate the index of the next bar for the current chart in the loop by the number of bars and class=class="str">"cmt">//--- set the value and class="type">class="kw">color passed to the method by the calculated index for(class="type">int i=class="num">0;i<num_bars;i++) { class="type">int index=index_bar_period-i; if(index<class="num">0) class="kw">break; buff.SetBufferValue(class="num">0,index,value); buff.SetBufferColorIndex(index,color_index); } }
箭头缓冲与跨周期柱体类型的接口封装
在自定义指标里批量管理箭头缓冲时,CBuffersCollection 提供了两个轻量方法:按时间序列索引改箭头颜色、以及按索引清空箭头数据。两者都先通过 GetBufferArrow(number) 拿到具体缓冲指针,空指针直接 return,避免越界写坏图表对象。 清空逻辑里那行 buff.SetBufferValue(0, series_index, buff.EmptyValue()) 是把该索引位置的值设成缓冲自身的空值常量,相当于让 MT5 不绘制此处的箭头。实际调试时若发现旧信号残留,优先查 series_index 是否和当前 Bar 索引方向一致。 再往上是 CEngine 基类对外暴露的接口。SeriesBarType 有两个重载:一个吃 int index,一个吃 datetime time,都返回 ENUM_BAR_BODY_TYPE,方便策略层直接判断某根 K 线是阳线主导还是阴线主导,而不必自己算收盘价减开盘价。 SeriesCopyToBufferAsSeries 封装了跨品种、跨周期的双精度属性拷贝(如收盘价、成交量),无论目标数组是正序还是逆序索引,都按时间序列方式复制,调用方不用操心 ASeries 方向。外汇与贵金属波动剧烈,这类封装虽降低出错概率,但参数传错周期仍可能拿到错位数据,回测前应在 EURUSD 的 M5 上手动打印前 3 根确认。
class="type">void CBuffersCollection::SetBufferArrowColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { CBufferArrow *buff=this.GetBufferArrow(number); if(buff==NULL) class="kw">return; buff.SetBufferColorIndex(series_index,color_index); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Clear the arrow buffer data by the timeseries index | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CBuffersCollection::ClearBufferArrow(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { CBufferArrow *buff=this.GetBufferArrow(number); if(buff==NULL) class="kw">return; buff.SetBufferValue(class="num">0,series_index,buff.EmptyValue()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Library basis class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CEngine { class="kw">private: class=class="str">"cmt">//--- The code has been removed for the sake of space class=class="str">"cmt">//--- ... class="kw">public: class=class="str">"cmt">//--- The code has been removed for the sake of space class=class="str">"cmt">//--- ... class=class="str">"cmt">//--- Return the bar type of the specified timeframe&class="macro">#x27;s symbol by(class="num">1) index and(class="num">2) time ENUM_BAR_BODY_TYPE SeriesBarType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">int index); ENUM_BAR_BODY_TYPE SeriesBarType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">class="kw">datetime time); class=class="str">"cmt">//--- Copy the specified class="type">class="kw">double class="kw">property of the specified timeseries of the specified symbol to the array class=class="str">"cmt">//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array class="type">bool SeriesCopyToBufferAsSeries(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const ENUM_BAR_PROP_DOUBLE class="kw">property, class="type">class="kw">double &array[],class="kw">const class="type">class="kw">double empty=EMPTY_VALUE) { class="kw">return this.m_time_series.CopyToBufferAsSeries(symbol,timeframe,class="kw">property,array,empty);} class=class="str">"cmt">//--- Return(class="num">1) the buffer collection and(class="num">2) the buffer list from the collection
「用标签和时间框架精准抓缓冲区」
在 MT5 自定义指标里,缓冲区往往不是按固定顺序排的。上面这组存取函数暴露了内部 CBuffersCollection 的检索逻辑:既可以用图形序列名(plot_label)直接定位,也能按 ENUM_TIMEFRAMES 时间框架过滤,还能用 Plot 索引或列表序号拿具体那一块。 GetBufferByLabel 和 GetBufferByTimeframe 是最常被调用的两个入口。比如同名指标加载了 M15 和 H1 两份缓冲区,传 PERIOD_H1 就能避开 M15 那份,避免画错线——外汇和贵金属多周期共振分析里这种错位是高频 bug 源,属于高风险操作场景。 按绘制样式取缓冲区的接口(GetBufferArrow / Line / Section / Histogram / Histogram2)带一个 number 参数,它不是全局序号,而是「同样式中的第几个」:0 是该类里最早创建的,1、2、N 顺延。写多图层指标时,用 number 比用列表下标更抗重构。 GetLastBuffer(void) 只返回集合里最后一个缓冲区指针,适合在 Append 之后立刻接样式设置,少一次计数查询。
CBuffersCollection *GetBuffersCollection(class="type">void) { class="kw">return &this.m_buffers } CArrayObj *GetListBuffers(class="type">void) { class="kw">return this.m_buffers.GetList(); } class=class="str">"cmt">//--- Return the buffer by(class="num">1) the graphical series name, (class="num">2) timeframe, (class="num">3) Plot index, (class="num">4) collection list and(class="num">5) the last one in the list CBuffer *GetBufferByLabel(class="kw">const class="type">class="kw">string plot_label) { class="kw">return this.m_buffers.GetBufferByLabel(plot_label); } CBuffer *GetBufferByTimeframe(class="kw">const ENUM_TIMEFRAMES timeframe) { class="kw">return this.m_buffers.GetBufferByTimeframe(timeframe); } CBuffer *GetBufferByPlot(class="kw">const class="type">int plot_index) { class="kw">return this.m_buffers.GetBufferByPlot(plot_index); } CBuffer *GetBufferByListIndex(class="kw">const class="type">int index_list) { class="kw">return this.m_buffers.GetBufferByListIndex(index_list); } CBuffer *GetLastBuffer(class="type">void); class=class="str">"cmt">//--- Return buffers by drawing style by a serial number class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the XXX drawing style, class="num">1,class="num">2,N - subsequent ones) CBufferArrow *GetBufferArrow(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferArrow(number); } CBufferLine *GetBufferLine(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferLine(number); } CBufferSection *GetBufferSection(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferSection(number); } CBufferHistogram *GetBufferHistogram(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferHistogram(number); } CBufferHistogram2 *GetBufferHistogram2(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferHistogram2(number); }
◍ 缓冲区的取用与新建接口
在自定义指标类里,m_buffers 统管所有绘图缓冲。通过一组 GetBufferXxx 方法,可以按序号拿回具体类型的缓冲指针,比如 ZigZag、Filling、Bars、Candles 以及 Calculate 缓冲,调用方无需关心底层数组如何挂接。 PropertyPlotsTotal 返回已绘制的缓冲数量,PropertyBuffersTotal 返回指标全部数组的总数;这两个整型值直接决定 OnInit 里循环边界,写错会导致部分缓冲不被绘制或数组越界。 要新增缓冲,直接调 BufferCreateArrow / BufferCreateLine / BufferCreateSection 等布尔方法即可,MT5 目前支持的绘图类型覆盖箭头、折线、线段、零轴直方图、双缓冲直方图、ZigZag、双水位填色、类 bars、类 candles 以及计算缓冲共 9 种。返回 false 时多半是缓冲配额超限,外汇与贵金属品种上高频指标需警惕内存与刷新开销。
CBufferZigZag *GetBufferZigZag(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferZigZag(number); } CBufferFilling *GetBufferFilling(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferFilling(number); } CBufferBars *GetBufferBars(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferBars(number); } CBufferCandles *GetBufferCandles(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferCandles(number); } CBufferCalculate *GetBufferCalculate(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferCalculate(number); } class=class="str">"cmt">//--- Return the number of(class="num">1) drawn buffers and(class="num">2) all indicator arrays class="type">int BuffersPropertyPlotsTotal(class="type">void) { class="kw">return this.m_buffers.PropertyPlotsTotal(); } class="type">int BuffersPropertyBuffersTotal(class="type">void) { class="kw">return this.m_buffers.PropertyBuffersTotal(); } class=class="str">"cmt">//--- Create the new buffer(class="num">1) "Drawing with arrows", (class="num">2) "Line", (class="num">3) "Sections", (class="num">4) "Histogram from the zero line", class=class="str">"cmt">//--- (class="num">5) "Histogram on two indicator buffers", (class="num">6) "Zigzag", (class="num">7) "Color filling between two levels", class=class="str">"cmt">//--- (class="num">8) "Display as bars", (class="num">9) "Display as candles", calculated buffer class="type">bool BufferCreateArrow(class="type">void) { class="kw">return this.m_buffers.CreateArrow(); } class="type">bool BufferCreateLine(class="type">void) { class="kw">return this.m_buffers.CreateLine(); } class="type">bool BufferCreateSection(class="type">void) { class="kw">return this.m_buffers.CreateSection(); }
缓冲区封装的几种图形与计算入口
在自定义指标类里,把绘图缓冲区的创建收敛成一组薄封装方法,能少写很多重复代码。下面这组函数全部转调内部 m_buffers 成员,覆盖直方图、双直方图、之字折、填充区、棒线、蜡烛线以及计算缓冲区的建立。 其中 BufferCreateCalculate 负责挂接计算型缓冲区,不参与直接绘图,但它是后续 OnCalculate 里取数的前置条件;若返回 false,指标大概率在初始化阶段就废了。 BuffersInitPlots 有两个重载:带参版本用指定数值和颜色索引预填所有绘图缓冲,无参版本则按缓冲区对象自身的空值设定初始化。实盘加载前确认这两个调用顺序,能避免 MT5 图表上出现一片空白或错色。 外汇与贵金属品种波动跳空频繁,缓冲区空值没设对,可能在重大数据行情后画出误导性的连线,属于高风险操作环节,建议开 MT5 用脚本单步验证初始化返回值。
class="type">bool BufferCreateHistogram(class="type">void) { class="kw">return this.m_buffers.CreateHistogram(); } class="type">bool BufferCreateHistogram2(class="type">void) { class="kw">return this.m_buffers.CreateHistogram2(); } class="type">bool BufferCreateZigZag(class="type">void) { class="kw">return this.m_buffers.CreateZigZag(); } class="type">bool BufferCreateFilling(class="type">void) { class="kw">return this.m_buffers.CreateFilling(); } class="type">bool BufferCreateBars(class="type">void) { class="kw">return this.m_buffers.CreateBars(); } class="type">bool BufferCreateCandles(class="type">void) { class="kw">return this.m_buffers.CreateCandles(); } class="type">bool BufferCreateCalculate(class="type">void) { class="kw">return this.m_buffers.CreateCalculate(); } class=class="str">"cmt">//--- Initialize all drawn buffers by a(class="num">1) specified value, (class="num">2) empty value set for the buffer object class="type">void BuffersInitPlots(class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index) { this.m_buffers.InitializePlots(value,color_index); } class="type">void BuffersInitPlots(class="type">void) { this.m_buffers.InitializePlots(); } class=class="str">"cmt">//--- Initialize all calculated buffers by a(class="num">1) specified value, (class="num">2) empty value set for the buffer object
「指标缓冲区的初始化与按类型取数接口」
自定义指标里,缓冲区(buffer)是承载绘制数据的核心容器。下面这段接口把缓冲区的初始化和按图形类型取数拆得很细,直接对应 MT5 指标架构里的箭头、线、区段、直方图、ZigZag、填充区和 OHLC 柱体缓冲。 初始化给了两个重载:一个传 double 值做预填充,一个无参清空。实际写指标时,若历史数据不足,用带参版本把缓冲先填成 EMPTY_VALUE 之外的占位值,能避免前端画出诡异连线。 取数函数统一用 (number, series_index) 两个整型定位:number 是同类缓冲中的序号(0 为第一个创建的该风格缓冲),series_index 是时序索引(0 为当前柱)。例如 BufferDataLine(0, 1) 取的就是第一条线缓冲的前一根柱数值。 BufferDataHistogram20 / 21 这种命名,指的是双缓冲直方图里的第 0 和第 1 号缓冲——MT5 里 DRAW_HISTOGRAM2 必须成对声明缓冲,少一个就会编译报数组越界。外汇与贵金属波动大,缓冲越界或取错序号可能导致图表信号完全失真,实盘前务必在策略测试器用历史数据跑一遍校验。
class="type">void BuffersInitCalculates(class="kw">const class="type">class="kw">double value) { this.m_buffers.InitializeCalculates(value); } class="type">void BuffersInitCalculates(class="type">void) { this.m_buffers.InitializeCalculates(); } class=class="str">"cmt">//--- Return buffer data by its serial number of(class="num">1) arrows, (class="num">2) line, (class="num">3) sections and(class="num">4) histogram from zero class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">class="kw">double BufferDataArrow(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataLine(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataSection(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataHistogram(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Return buffer data by its serial number of(class="num">1) the zero and(class="num">2) the first histogram buffer on two buffers class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">class="kw">double BufferDataHistogram20(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataHistogram21(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Return buffer data by its serial number of(class="num">1) the zero and(class="num">2) the first zigzag buffer class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">class="kw">double BufferDataZigZag0(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataZigZag1(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Return buffer data by its serial number of(class="num">1) the zero and(class="num">2) the first filling buffer class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">class="kw">double BufferDataFilling0(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataFilling1(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Return buffer data by its serial number of(class="num">1) Open, (class="num">2) High, (class="num">3) Low and(class="num">4) Close bar buffers class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">class="kw">double BufferDataBarsOpen(class="kw">const class="type">int number,class="kw">const class="type">int series_index);
◍ 缓冲区读写接口的两种取数路径
在自定义指标里,缓冲区数据不是直接裸写数组,而是通过一组封装函数按序号存取。读方向分两条线:Bars 系列取的是普通高低收数据,Candles 系列取的是带开高低收四价的蜡烛缓冲。 写方向更细,箭头、线段、分段、零轴直方图都各自有 Set 函数,且都带 color_index 与 as_current 参数;as_current 默认 false,置 true 时把该索引当当前未完成 K 线处理。 BufferSetDataCalculate 是特例,它只接 number 与 series_index 和 value,没有颜色参数——说明这条缓冲是纯计算中间量,不参与绘制。 [CODE] double BufferDataBarsHigh(const int number,const int series_index); double BufferDataBarsLow(const int number,const int series_index); double BufferDataBarsClose(const int number,const int series_index); //--- Return buffer data by its serial number of (1) Open, (2) High, (3) Low and (4) Close candle buffers //--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones) double BufferDataCandlesOpen(const int number,const int series_index); double BufferDataCandlesHigh(const int number,const int series_index); double BufferDataCandlesLow(const int number,const int series_index); double BufferDataCandlesClose(const int number,const int series_index); //--- Set buffer data by its serial number of (1) arrows, (2) line, (3) sections, (4) histogram from zero and the (5) calculated buffer //--- (0 - the very first created buffer with the ХХХ drawing style, 1,2,N - subsequent ones) void BufferSetDataArrow(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false); void BufferSetDataLine(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false); void BufferSetDataSection(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false); void BufferSetDataHistogram(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false); void BufferSetDataCalculate(const int number,const int series_index,const double value); //--- Set data of the (1) zero, (2) first and (3) all histogram buffers on two buffers by a serial number of a created buffer //--- (0 - the very first created buffer with the HISTOGRAM2 drawing style, 1,2,N - subsequent ones) [/CODE] 逐行拆解:前三个 BufferDataBars* 返回第 number 个缓冲在第 series_index 根 K 线上的高、低、收值;注释标明 number 从 0 计,0 是第一种绘制样式创建的首个缓冲。BufferDataCandles* 四个函数同理但补齐了 Open 价,适合重绘类蜡烛视图。 五个 BufferSetData* 写函数中,前四个末尾的 as_current=false 意味着默认写入已闭合 K 线;最后一个 BufferSetDataCalculate 无颜色无 as_current,仅塞计算值。外汇与贵金属波动剧烈,缓冲索引错一位可能导致图形整体偏移,上 MT5 用 iCustom 试调 number 参数即可验证。
class="type">class="kw">double BufferDataBarsHigh(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataBarsLow(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataBarsClose(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Return buffer data by its serial number of(class="num">1) Open, (class="num">2) High, (class="num">3) Low and(class="num">4) Close candle buffers class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">class="kw">double BufferDataCandlesOpen(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataCandlesHigh(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataCandlesLow(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">double BufferDataCandlesClose(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Set buffer data by its serial number of(class="num">1) arrows, (class="num">2) line, (class="num">3) sections, (class="num">4) histogram from zero and the(class="num">5) calculated buffer class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">void BufferSetDataArrow(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void BufferSetDataLine(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void BufferSetDataSection(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void BufferSetDataHistogram(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void BufferSetDataCalculate(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value); class=class="str">"cmt">//--- Set data of the(class="num">1) zero, (class="num">2) first and(class="num">3) all histogram buffers on two buffers by a serial number of a created buffer class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the HISTOGRAM2 drawing style, class="num">1,class="num">2,N - subsequent ones)
给自定义图形缓冲喂数据的接口族
在 MT5 自绘指标里,图形缓冲不是直接写数组就完事,而是通过一组 BufferSetData* 函数按绘制类型分流。直方、之字、填充、蜡烛四类各有零号/一号/全量三种入口,编号 0 对应该类第一个创建的缓冲,1、2、N 顺延。 以之字线为例,单点写入用 BufferSetDataZigZag0 和 BufferSetDataZigZag1,只传 number、series_index、value;而 BufferSetDataZigZag 一次塞入 value0 与 value1 两个价格点,附带 uchar 类型的 color_index 控制段色,as_current 默认 false,置 true 时把数据当作当前未完成柱处理。 填充类 BufferSetDataFilling 没有颜色参数,只收 value0、value1 双边界,适合画通道或背景带;直方图 BufferSetDataHistogram2 则多一个 color_index,能按列上色。外汇与贵金属波动剧烈,自绘缓冲若 series_index 越界会静默丢点,上线前应在策略测试器用 2023 年 XAUUSD 的 M5 跑一遍空值检查。 下面截取三行核心声明,注意 as_current 的默认值是 false 而非 true:
class="type">void BufferSetDataHistogram2(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value0,class="kw">const class="type">class="kw">double value1,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void BufferSetDataZigZag(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value0,class="kw">const class="type">class="kw">double value1,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void BufferSetDataFilling(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value0,class="kw">const class="type">class="kw">double value1,class="type">bool as_current=class="kw">false);
「往自定义蜡烛缓冲写数据的接口细节」
在 MT5 自绘指标里,如果想用 CANDLES 风格画第 N 套蜡烛,必须靠 BufferSetDataBars / BufferSetDataCandles 这类方法把 OHLC 推给缓冲。number 参数从 0 计起,0 代表第一个用 CANDLES 样式创建的缓冲,1、2 往后排;series_index 则是时间序列下标,0 一般对应当前未完成或最近一根 K。 下面这段是一次性写整根蜡烛的接口,比分开调 Open/High/Low/Close 四个函数更省事。注意 color_index 用 uchar 传调色板序号,as_current 默认 false,若置 true 会把这根当作“当前柱”处理,可能影响重绘时机。 [CODE] void BufferSetDataBars(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false); void BufferSetDataCandles(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false); [/CODE] 逐行拆解: 第一行 BufferSetDataBars 的 number 指定第几号蜡烛缓冲;series_index 指定第几根;open/high/low/close 依次传价格;color_index 是调色板索引;as_current 留默认即可,手动改 true 前先在脚本里打印缓冲号确认不会写串。 第二行 BufferSetDataCandles 参数结构完全相同,区别仅在于它面向 CANDLES 绘图样式的缓冲集合,内部会按蜡烛对象渲染。 取色函数也按绘图类型分了九类,比如 BufferArrowColor 对应箭头缓冲、BufferLineColor 对应线、BufferSectionColor 对应线段。调用时同样传 number 和 series_index,返回的是 color 类型,可直接用于 PlotIndexSetInteger 做动态染色。外汇与贵金属杠杆高,自绘指标仅作辅助,信号失效概率不低,实盘前务必在策略测试器跑够样本。
class="type">void BufferSetDataBars(class="kw">const class="type">int number,class="kw">const class="type">int series_index,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">uchar color_index,class="type">bool as_current=class="kw">false); class="type">void BufferSetDataCandles(class="kw">const class="type">int number,class="kw">const class="type">int series_index,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">uchar color_index,class="type">bool as_current=class="kw">false);
◍ 按图形类别取色与定位缓冲序号
在自定义指标里,不同绘制样式(箭头、线、线段、直方图、双缓冲直方图、之字折线、填充、柱、蜡烛)各自有一组颜色接口。直接拿颜色用 BufferXxxColor(number, series_index),返回的是 color 类型,适合在 OnCalculate 里按条件动态改色。 如果只想知道某个缓冲在第几号颜色序列,用 BufferXxxColorIndex 系列,返回 int。注释里写得很直白:number 是该类样式的第几个缓冲(0 是该类第一个创建的,1、2、N 顺延),series_index 是时间序列下标。 这两组函数一共覆盖 9 种图形类别,箭头到蜡烛全齐。实盘写多样式复合指标时,靠 series_index 切不同 K 线位置的颜色,比手动维护数组下标更不容易越界;外汇与贵金属波动大、滑点频繁,改色逻辑出错可能让信号看板一片糊,建议先在 MT5 策略测试器用历史数据跑一遍确认缓冲序号对得上。
class="type">class="kw">color BufferHistogramColor(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">color BufferHistogram2Color(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">color BufferZigZagColor(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">color BufferFillingColor(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">color BufferBarsColor(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">class="kw">color BufferCandlesColor(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Return buffer class="type">class="kw">color index by its serial number of(class="num">1) arrows, (class="num">2) line, (class="num">3) sections, (class="num">4) histogram from zero class=class="str">"cmt">//--- (class="num">5) histogram on two buffers, (class="num">6) zigzag, (class="num">7) filling, (class="num">8) bars and(class="num">9) candles class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">int BufferArrowColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">int BufferLineColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">int BufferSectionColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">int BufferHistogramColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">int BufferHistogram2ColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">int BufferZigZagColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">int BufferFillingColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">int BufferBarsColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class="type">int BufferCandlesColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index); class=class="str">"cmt">//--- Set class="type">class="kw">color values from the passed class="type">class="kw">color array for all indicator buffers within the collection
给九类绘制缓冲单独指定颜色索引
在 MT5 自定义指标里,绘图元素不止线型一种。引擎把缓冲按绘制风格分成九类:箭头、线、区间截面、直方图、双缓冲直方图、之字、填充、柱、蜡烛,编号从 0 到 8 对应创建顺序。 BuffersSetColors() 接收 color 数组,一次性把整套配色塞进 m_buffers 容器,适合初始化时批量上色。 若要在运行时按序列号微调某一类缓冲的某条数据系列颜色,得用各自专有的 SetColorIndex 方法。下面这段把九类缓冲的颜色索引设置接口列全了,number 是缓冲序号,series_index 是数据系列序号(从 0 起),color_index 指向调色板位置。 外汇与贵金属市场波动剧烈、杠杆风险高,指标配色仅辅助读图,不构成任何方向建议。
class="type">void BuffersSetColors(class="kw">const class="type">class="kw">color &array_colors[]) { this.m_buffers.SetColors(array_colors); } class=class="str">"cmt">//--- Set the class="type">class="kw">color index to the class="type">class="kw">color buffer by its serial number of(class="num">1) arrows, (class="num">2) line, (class="num">3) sections, (class="num">4) histogram from zero class=class="str">"cmt">//--- (class="num">5) histogram on two buffers, (class="num">6) zigzag, (class="num">7) filling, (class="num">8) bars and(class="num">9) candles class=class="str">"cmt">//--- (class="num">0 - the very first created buffer with the ХХХ drawing style, class="num">1,class="num">2,N - subsequent ones) class="type">void BufferArrowSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferArrowColorIndex(number,series_index,color_index); } class="type">void BufferLineSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferLineColorIndex(number,series_index,color_index); } class="type">void BufferSectionSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferSectionColorIndex(number,series_index,color_index); } class="type">void BufferHistogramSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferHistogramColorIndex(number,series_index,color_index); } class="type">void BufferHistogram2SetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferHistogram2ColorIndex(number,series_index,color_index); } class="type">void BufferZigZagSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferZigZagColorIndex(number,series_index,color_index); } class="type">void BufferFillingSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferFillingColorIndex(number,series_index,color_index); }
「按索引抹掉缓冲区的图形与配色」
在自定义指标里,图形缓冲区的清理比绘制更常被忽略。MT5 的图形缓存是按时间序列索引(series_index)定位的,比如 K 线 0 是最近一根、1 是前一根;若不清旧数据,重绘时会在历史柱上留下残影。 下面这组方法覆盖了 9 类图形对象:箭头、线、区段、零线直方图、双缓冲直方图、 zigzag、填充、柱状图、蜡烛图。每个 Clear 方法都接收缓冲区编号 number 和时间序列索引 series_index 两个参数,精准擦掉那一根上的对应对象。 配色接口则独立存在:BufferBarsSetColorIndex 与 BufferCandlesSetColorIndex 只改某根柱或蜡烛的颜色索引(uchar 类型,对应调色板槽位),不改数值。实盘外汇与贵金属波动剧烈、滑点频繁,这类按根重绘的逻辑若用错索引,可能在高风险行情下显示错位信号。 开 MT5 把下面代码贴进指标类,用 iCustom 调一下,观察切周期时旧图形是否残留,就能验证清理路径是否生效。
class="type">void BufferBarsSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferBarsColorIndex(number,series_index,color_index); } class="type">void BufferCandlesSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferCandlesColorIndex(number,series_index,color_index); } class=class="str">"cmt">//--- Clear buffer data by its index in the list in the specified timeseries bar class="type">void BufferClear(class="kw">const class="type">int buffer_list_index,class="kw">const class="type">int series_index) { this.m_buffers.Clear(buffer_list_index,series_index); } class=class="str">"cmt">//--- Clear data by the timeseries index for the(class="num">1) arrow, (class="num">2) line, (class="num">3) section, (class="num">4) zero line histogram, class=class="str">"cmt">//--- (class="num">5) histogram on two buffers, (class="num">6) zigzag, (class="num">7) filling, (class="num">8) bars and(class="num">9) candles class="type">void BufferArrowClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferArrow(number,series_index); } class="type">void BufferLineClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferLine(number,series_index); } class="type">void BufferSectionClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferSection(number,series_index); } class="type">void BufferHistogramClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferHistogram(number,series_index); } class="type">void BufferHistogram2Clear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferHistogram2(number,series_index);} class="type">void BufferZigZagClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferZigZag(number,series_index); } class="type">void BufferFillingClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferFilling(number,series_index); } class="type">void BufferBarsClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferBars(number,series_index); }
◍ 交易类对象的全局参数注入接口
在自建指标或 EA 的 C++ 风格封装里,有一组 void 型方法专门给交易类对象批量设参,而不是在下单那一刻现写。它们大多带一个可选的 symbol_name 参数,缺省为 NULL 时表示作用于默认品种,这样你可以在多品种策略里按符号分别覆盖。 看这组 TradingSet* 方法:填充策略默认 ORDER_FILLING_FOK,过期类型默认 ORDER_TIME_GTC,magic、comment、deviation、volume、expiration、async_mode、log_level 都能一次性固化。外汇与贵金属杠杆高,滑点 deviation 设错可能在数据行情里吃大亏,建议先在策略测试器里用不同 deviation 跑同一段历史验证成交差异。 另有 BufferCandlesClear 负责清掉指定编号和序列索引的 K 线缓冲,BuffersPrintShort 则把缓冲集合里的所有指标缓冲打印出简短描述,方便在初始化阶段排查缓冲绑定是否错位。
class="type">void BufferCandlesClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferCandles(number,series_index); } class=class="str">"cmt">//--- Display class="type">class="kw">short description of all indicator buffers of the buffer collection class="type">void BuffersPrintShort(class="type">void); class=class="str">"cmt">//--- Set the following for the trading classes: class=class="str">"cmt">//--- (class="num">1) correct filling policy, (class="num">2) filling policy, class=class="str">"cmt">//--- (class="num">3) correct order expiration type, (class="num">4) order expiration type, class=class="str">"cmt">//--- (class="num">5) magic number, (class="num">6) comment, (class="num">7) slippage, (class="num">8) volume, (class="num">9) order expiration date, class=class="str">"cmt">//--- (class="num">10) the flag of asynchronous sending of a trading request, (class="num">11) logging level, (class="num">12) number of trading attempts class="type">void TradingSetCorrectTypeFilling(class="kw">const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetTypeFilling(class="kw">const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetCorrectTypeExpiration(class="kw">const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetTypeExpiration(class="kw">const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetMagic(class="kw">const class="type">uint magic,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetComment(class="kw">const class="type">class="kw">string comment,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetDeviation(class="kw">const class="type">class="kw">ulong deviation,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetVolume(class="kw">const class="type">class="kw">double volume=class="num">0,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetExpiration(class="kw">const class="type">class="kw">datetime expiration=class="num">0,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetAsyncMode(class="kw">const class="type">bool async_mode=class="kw">false,class="kw">const class="type">class="kw">string symbol_name=NULL); class="type">void TradingSetLogLevel(class="kw">const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,class="kw">const class="type">class="kw">string symbol_name=NULL);
交易对象的声音与初始化接口
在封装交易类时,声音提示和对象初始化往往被忽略,但它们直接决定 EA 在 MT5 实盘里的可观测性。下面这组方法把重试次数、日志级别、声音开关和播放逻辑都代理到内部 m_trading 成员,调用方无需关心底层实现。 TradingSetTotalTry 接收 uchar 类型尝试次数,转手给 m_trading.SetTotalTry;TradingGetLogLevel 按 symbol_name 返回该交易对象的 ENUM_LOG_LEVEL,方便分品种调日志粗细。SetSoundsStandart 的 symbol 参数若为 NULL 则设全局标准音,否则只覆盖指定品种。 SetUseSounds 用 bool flag 控制是否发声,SetSound 则按 ENUM_MODE_SET_SOUND 与 ENUM_ORDER_TYPE 精细绑定某类订单动作的声音文件。PlaySoundByDescription 靠声音描述串触发播放,返回 bool 可判断文件是否真的存在。 CollectionOnInit 是衔接点:把账户、品种、行情、历史、事件等集合指针一次性灌给 m_trading.OnInit,同时让 m_buffers 绑定时间序列对象。少传一个指针,EA 初始化就可能静默失败,开 MT5 跑 OnInit 时建议先打印各集合非空状态。
class="type">void TradingSetTotalTry(class="kw">const class="type">uchar attempts) { this.m_trading.SetTotalTry(attempts); } class=class="str">"cmt">//--- Return the logging level of a trading class symbol trading object ENUM_LOG_LEVEL TradingGetLogLevel(class="kw">const class="type">class="kw">string symbol_name) { class="kw">return this.m_trading.GetTradeObjLogLevel(symbol_name); } class=class="str">"cmt">//--- Set standard sounds(symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols class="type">void SetSoundsStandart(class="kw">const class="type">class="kw">string symbol=NULL) { this.m_trading.SetSoundsStandart(symbol); } class=class="str">"cmt">//--- Set the flag of class="kw">using sounds class="type">void SetUseSounds(class="kw">const class="type">bool flag) { this.m_trading.SetUseSounds(flag); } class=class="str">"cmt">//--- Set a sound for a specified order/position type and symbol. &class="macro">#x27;mode&class="macro">#x27; specifies an event a sound is set for class=class="str">"cmt">//--- (symbol=NULL) for trading objects of all symbols, (symbol!=NULL) for a trading object of a specified symbol class="type">void SetSound(class="kw">const ENUM_MODE_SET_SOUND mode,class="kw">const ENUM_ORDER_TYPE action,class="kw">const class="type">class="kw">string sound,class="kw">const class="type">class="kw">string symbol=NULL) { this.m_trading.SetSound(mode,action,sound,symbol); } class=class="str">"cmt">//--- Play a sound by its description class="type">bool PlaySoundByDescription(class="kw">const class="type">class="kw">string sound_description); class=class="str">"cmt">//--- Pass the pointers to all the necessary collections to the trading class and the indicator buffer collection class class="type">void CollectionOnInit(class="type">void) { this.m_trading.OnInit(this.GetAccountCurrent(),m_symbols.GetObject(),m_market.GetObject(),m_history.GetObject(),m_events.GetObject()); this.m_buffers.OnInit(this.m_time_series.GetObject()); }
「按索引与时间取棒线类型的双重载」
引擎里 SeriesBarType 做了两个重载:一个按序号 index 取某交易品种、某周期下的棒线实体类型,另一个按 datetime 时间精确定位。两者都先通过 m_time_series.GetBar 拿到 CBar 指针,非空就返回 bar.TypeBody(),拿不到则返回 WRONG_VALUE 标记的枚举。 这种双入口设计在回测和实盘切换时很实用——用 index 顺时序扫历史,用 time 对齐多周期事件。开 MT5 把这两段直接塞进你的 CEngine 派生类,编译后调用 SeriesBarType(_Symbol,PERIOD_M15,0) 能看到当前 M15 棒线类型枚举值。 缓冲区一侧也暴露了三组定位接口:GetBufferByLabel 按绘图标签拿、GetBufferByTimeframe 按周期拿、GetBufferByListIndex 按下标拿。GetLastBuffer 则是从列表尾取最后一个 buffer,实现就是 list.At(list.Total()-1),空列表直接返 NULL。外汇与贵金属波动剧烈,这类底层取数逻辑若返回 WRONG_VALUE 务必在调用层拦截,否则可能误导信号判断。
ENUM_BAR_BODY_TYPE CEngine::SeriesBarType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">int index) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,index); class="kw">return(bar!=NULL ? bar.TypeBody() : (ENUM_BAR_BODY_TYPE)WRONG_VALUE); } ENUM_BAR_BODY_TYPE CEngine::SeriesBarType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">class="kw">datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); class="kw">return(bar!=NULL ? bar.TypeBody() : (ENUM_BAR_BODY_TYPE)WRONG_VALUE); } CBuffer *GetBufferByLabel(class="kw">const class="type">class="kw">string plot_label) { class="kw">return this.m_buffers.GetBufferByLabel(plot_label); } CBuffer *GetBufferByTimeframe(class="kw">const ENUM_TIMEFRAMES timeframe) { class="kw">return this.m_buffers.GetBufferByTimeframe(timeframe);} CBuffer *GetBufferByListIndex(class="kw">const class="type">int index_list) { class="kw">return this.m_buffers.GetBufferByListIndex(index_list);} CBuffer *CEngine::GetLastBuffer(class="type">void) { CArrayObj *list=this.GetListBuffers(); if(list==NULL) class="kw">return NULL; class="kw">return list.At(list.Total()-class="num">1); } CBufferCalculate *GetBufferCalculate(class="kw">const class="type">int number) { class="kw">return this.m_buffers.GetBufferCalculate(number); } class="type">int BuffersPropertyPlotsTotal(class="type">void) { class="kw">return this.m_buffers.PropertyPlotsTotal(); } class="type">int BuffersPropertyBuffersTotal(class="type">void) { class="kw">return this.m_buffers.PropertyBuffersTotal(); } class="type">bool BufferCreateCalculate(class="type">void) { class="kw">return this.m_buffers.CreateCalculate(); } class="type">void BuffersInitPlots(class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index) { this.m_buffers.InitializePlots(value,color_index); } class="type">void BuffersInitPlots(class="type">void) { this.m_buffers.InitializePlots(); }
◍ 引擎层缓冲区写入的几组重载接口
在自定义指标或 EA 的 CEngine 封装里,缓冲区不是直接调 SetIndexBuffer 硬写,而是走 m_buffers 成员做中转。这样做的好处是计算缓冲区和绘图缓冲区(箭头、线、区块、直方)可以共用一套序号寻址逻辑,序号 0 永远指向第一个缓冲,1、2、N 依次向后。 计算类接口有两个入口:一个带 double 参数做全局初值,一个无参只做空初始化。 void BuffersInitCalculates(const double value) { this.m_buffers.InitializeCalculates(value); } void BuffersInitCalculates(void) { this.m_buffers.InitializeCalculates(); } 箭头、线、区块三类绘图缓冲都暴露了同构的写入函数,区别只在内部调的 SetBufferXxxValue。注意 as_current 参数默认 false,意味着默认按 series_index 写历史位,若要在当前未闭合 K 线实时刷新图形,必须显式传 true。 void CEngine::BufferSetDataArrow(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false) { this.m_buffers.SetBufferArrowValue(number,series_index,value,color_index,as_current); } 线缓冲与区块缓冲签名完全一致,只是落点函数换成 SetBufferLineValue / SetBufferSectionValue。 void CEngine::BufferSetDataLine(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false) { this.m_buffers.SetBufferLineValue(number,series_index,value,color_index,as_current); } void CEngine::BufferSetDataSection(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false) { this.m_buffers.SetBufferSectionValue(number,series_index,value,color_index,as_current); } 开 MT5 把这几段塞进你的引擎类,先只挂一个 line 缓冲、as_current 分别试 true/false,能直接看到当前 K 线画线是跟笔还是等收线——外汇与贵金属波动快,这种细节错了图形会滞后一两根。
class="type">void BuffersInitCalculates(class="kw">const class="type">class="kw">double value) { this.m_buffers.InitializeCalculates(value); } class="type">void BuffersInitCalculates(class="type">void) { this.m_buffers.InitializeCalculates(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the calculated buffer data by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataCalculate(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value) { this.m_buffers.SetBufferCalculateValue(number,series_index,value); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set arrow buffer data by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first arrow buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataArrow(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false) { this.m_buffers.SetBufferArrowValue(number,series_index,value,color_index,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set line buffer data by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first line buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataLine(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false) { this.m_buffers.SetBufferLineValue(number,series_index,value,color_index,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set section buffer data by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first sections buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataSection(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false) { this.m_buffers.SetBufferSectionValue(number,series_index,value,color_index,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set histogram buffer data from zero | class=class="str">"cmt">//| by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+
直方图与填充缓冲的写入接口
CEngine 里给图形缓冲喂数据,靠的是一组 BufferSetData* 方法,而不是在外面直接碰底层数组。直方图单值用 BufferSetDataHistogram,双值(上下边界)走 BufferSetDataHistogram2,参数里的 number 是缓冲序号,0 代表第一个,1、2 往后排。 series_index 对应 K 线序列位置,0 是最右侧当前柱;as_current 默认 false,设 true 时数据直接写进「当前未闭合柱」,做实时重绘时有用。颜色用 uchar 型的 color_index 指定,和调色板索引挂钩,不是直接传 RGB。 ZigZag 和 Filling 的接口形态类似,但 Filling 不接收 color_index——它的配色在缓冲初始化阶段就绑死了,运行时只塞 value1、value2 两条边界线。下面这段代码直接贴进 MT5 的 EA 源码里就能看到方法签名,改 number 或 series_index 可验证不同柱上的绘制落点。 外汇与贵金属波动剧烈,这类自定义缓冲若写错 series_index,可能在回测里不显形、实盘却错位,务必在策略测试器逐柱核对。
class="type">void CEngine::BufferSetDataHistogram(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false) { this.m_buffers.SetBufferHistogramValue(number,series_index,value,color_index,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set data of all histogram buffers on two buffers | class=class="str">"cmt">//| by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataHistogram2(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value1,class="kw">const class="type">class="kw">double value2,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false) { this.m_buffers.SetBufferHistogram2Value(number,series_index,value1,value2,color_index,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set data of all zizag buffers | class=class="str">"cmt">//| by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first zigzag buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataZigZag(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value1,class="kw">const class="type">class="kw">double value2,class="kw">const class="type">uchar color_index,class="type">bool as_current=class="kw">false) { this.m_buffers.SetBufferZigZagValue(number,series_index,value1,value2,color_index,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set data of all filling buffers | class=class="str">"cmt">//| by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first filling buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataFilling(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">double value1,class="kw">const class="type">class="kw">double value2,class="type">bool as_current=class="kw">false) { this.m_buffers.SetBufferFillingValue(number,series_index,value1,value2,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+
「给缓冲层灌K线与改色的接口细节」
在自绘指标引擎里,K线类和柱状类的缓冲数据都不是直接写屏,而是先丢进编号缓冲池。CEngine 暴露了两个对称方法:BufferSetDataBars 管条形缓冲,BufferSetDataCandles 管蜡烛缓冲,二者都靠 number 定位第几个缓冲(0 是第一个,1、2、N 顺延),series_index 则是该缓冲里的具体序列位置。 调用时如果 GetBufferBars 或 GetBufferCandles 返回 NULL,函数直接 return,不会抛错——这意味着缓冲没初始化就去灌数据会静默失败,调试时容易以为是绘制逻辑问题。as_current 默认 false,置 true 时把这条数据当当前未完成柱处理,用于实时更新最后一棵 K。 颜色不走 OHLC 参数,单独用 uchar 的 color_index 标记,后续有 BuffersSetColors 批量设调色板,以及 BufferArrowSetColorIndex、BufferLineSetColorIndex、BufferSectionSetColorIndex、BufferHistogramSetColorIndex 按缓冲编号和序列位改单点颜色。外汇与贵金属波动剧烈,这类自绘缓冲若颜色索引越界,MT5 可能不报错只显示默认色,需自己加边界检查。 开 MT5 验证时,把 number 故意传一个未创建的编号,观察指标是否无声不画,就能确认这套静默返回机制是否生效。
class=class="str">"cmt">//| by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first bar buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataBars(class="kw">const class="type">int number,class="kw">const class="type">int series_index,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">uchar color_index,class="type">bool as_current=class="kw">false) { CBufferBars *buff=this.m_buffers.GetBufferBars(number); if(buff==NULL) class="kw">return; this.m_buffers.SetBufferBarsValue(number,series_index,open,high,low,close,color_index,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set data of all candle buffers | class=class="str">"cmt">//| by its serial number | class=class="str">"cmt">//| (class="num">0 - the very first candle buffer, class="num">1,class="num">2,N - subsequent ones) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEngine::BufferSetDataCandles(class="kw">const class="type">int number,class="kw">const class="type">int series_index,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">uchar color_index,class="type">bool as_current=class="kw">false) { CBufferCandles *buff=this.m_buffers.GetBufferCandles(number); if(buff==NULL) class="kw">return; this.m_buffers.SetBufferCandlesValue(number,series_index,open,high,low,close,color_index,as_current); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void BuffersSetColors(class="kw">const class="type">class="kw">color &array_colors[]) { this.m_buffers.SetColors(array_colors); } class="type">void BufferArrowSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferArrowColorIndex(number,series_index,color_index); } class="type">void BufferLineSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferLineColorIndex(number,series_index,color_index); } class="type">void BufferSectionSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferSectionColorIndex(number,series_index,color_index); } class="type">void BufferHistogramSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index)
◍ 逐类图形缓冲区的着色与清空接口
在自定义指标封装里,图形缓冲区分了箭头、折线、区段、直方图、双直方图、之字折线和填充区等类型,每一类都暴露了独立的颜色索引写入与清空方法。调用时第一个参数 number 指定缓冲槽位,series_index 对应具体数据序列,color_index 用 uchar 传入调色板下标,改色不用重绘整个指标。 以直方图为例,BufferHistogramSetColorIndex 内部转调 m_buffers.SetBufferHistogramColorIndex,而 BufferHistogramClear 直接走 ClearBufferHistogram,二者参数结构完全一致,只是动作相反。蜡烛和棒线同理:BufferCandlesSetColorIndex 与 BufferBarsSetColorIndex 分别接管 K 线与 OHLC bar 的着色,清空则交给 ClearBufferCandles / ClearBufferBars 的对应包装。 通用清空入口 BufferClear 按 buffer_list_index 和 series_index 直接清掉整类缓冲,比逐类型 Clear 更粗暴。实盘接 AIGC 信号时,若某根黄金 H1 K 线被模型判为反转,可只对该 series_index 调 BufferCandlesSetColorIndex 标色,不碰其余缓冲,MT5 重绘开销明显更低;外汇与贵金属波动剧烈,这类局部刷新只降负载不降风险。
class="type">void BufferHistogramSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferHistogramColorIndex(number,series_index,color_index); } class="type">void BufferHistogram2SetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferHistogram2ColorIndex(number,series_index,color_index); } class="type">void BufferZigZagSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferZigZagColorIndex(number,series_index,color_index); } class="type">void BufferFillingSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferFillingColorIndex(number,series_index,color_index); } class="type">void BufferBarsSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferBarsColorIndex(number,series_index,color_index); } class="type">void BufferCandlesSetColorIndex(class="kw">const class="type">int number,class="kw">const class="type">int series_index,class="kw">const class="type">uchar color_index) { this.m_buffers.SetBufferCandlesColorIndex(number,series_index,color_index); } class="type">void BufferClear(class="kw">const class="type">int buffer_list_index,class="kw">const class="type">int series_index) { this.m_buffers.Clear(buffer_list_index,series_index); } class="type">void BufferArrowClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferArrow(number,series_index); } class="type">void BufferLineClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferLine(number,series_index); } class="type">void BufferSectionClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferSection(number,series_index); } class="type">void BufferHistogramClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferHistogram(number,series_index); } class="type">void BufferHistogram2Clear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferHistogram2(number,series_index);} class="type">void BufferZigZagClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferZigZag(number,series_index); } class="type">void BufferFillingClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferFilling(number,series_index); }
缓冲区清理与集合初始化的底层接口
CEngine 类里给指标缓冲区留了两个轻量清除入口:BufferBarsClear 按 number 和 series_index 清掉对应序列的 K 线柱缓冲,BufferCandlesClear 同理只针对蜡烛对象。两者都直接转发给 m_buffers 的 ClearBufferBars / ClearBufferCandles,本身不做事前判空,调用方得自己保证索引合法。 BuffersPrintShort 是排查缓冲区的实用函数:先通过 GetListBuffers 拿缓冲对象集合指针,list 为空直接 return;否则用 list.Total() 取总数,循环里 list.At(i) 逐个取 CBuffer 指针,非空就调 buff.PrintShort() 往日志打简短描述。在 MT5 里跑完 OnInit 后调一次,能直接看到当前加载了几个缓冲、各自参数,不用去数据窗口一个个点。 CollectionOnInit 负责把各集合指针灌给交易类和缓冲类:m_trading.OnInit 接账户、品种、行情、历史、事件五个对象;m_buffers.OnInit 只接 m_time_series 的时间序列对象。这段若在自定义引擎里漏调,缓冲区大概率取不到时间序列,绘图会静默失败。外汇与贵金属波动剧烈,这类底层初始化缺失可能在极端行情下放大信号延迟,实盘前务必在策略测试器跑一遍确认日志输出正常。
class="type">void BufferBarsClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferBars(number,series_index); } class="type">void BufferCandlesClear(class="kw">const class="type">int number,class="kw">const class="type">int series_index) { this.m_buffers.ClearBufferCandles(number,series_index); } class=class="str">"cmt">//+-------------------------------------------------------------------------+ class=class="str">"cmt">//| Display class="type">class="kw">short description of all indicator buffers within the collection| class=class="str">"cmt">//+-------------------------------------------------------------------------+ class="type">void CEngine::BuffersPrintShort(class="type">void) { class=class="str">"cmt">//--- Get the pointer to the collection list of buffer objects CArrayObj *list=this.GetListBuffers(); if(list==NULL) class="kw">return; class="type">int total=list.Total(); class=class="str">"cmt">//--- In a loop by the number of buffers in the list, class=class="str">"cmt">//--- get the next buffer and display its brief description in the journal for(class="type">int i=class="num">0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) class="kw">continue; buff.PrintShort(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Pass the pointers to all the necessary collections to the trading class and the indicator buffer collection class class="type">void CollectionOnInit(class="type">void) { this.m_trading.OnInit(this.GetAccountCurrent(),m_symbols.GetObject(),m_market.GetObject(),m_history.GetObject(),m_events.GetObject()); this.m_buffers.OnInit(this.m_time_series.GetObject()); }
「把单周期指标改成多周期测试件」
验证多周期绘图逻辑,最省事的办法是拿上一篇的 test 指标改一版:存到 \MQL5\Indicators\TestDoEasy\Part45\ 下,命名 TestDoEasyPart45.mq5,并在设置里加一张时间帧清单表。指标运行时会按这张表去抓不同周期的数据,图形类型也可在外部切换,缓冲区的显隐直接跟随图形类型走。 原代码里只认当前图表周期,现在要换成时间帧数组驱动。OnInit() 里先把设置中选定的周期写进数组,函数库再据数组创建对应时间序列——也就是说,指标取数的图表周期完全由设置决定,不必碰 OnCalculate 里的计算逻辑。 缓冲区一次性建全:箭头、线段、区块、直方图、双向直方图、之字、填充、棒线、蜡烛、计算缓冲,合计 22 个数组(箭头2+线段2+区块2+直方图2+直方图2代3+之字3+填充2+棒线5+蜡烛5+计算1)。每种图形按输入在 data window 设显示标志并绑定时间帧,剩下交给函数库摆位。 之前用 CopyData() 给数组打“按时间排列”标记再翻回来,现在在 DELib.mqh 里加 CopyDataAsSeries():只把数组转成时间序列索引方向,不还原。调用它之后,OnCalculate 里不用再手动翻转,库内时间序列直接对齐。外汇与贵金属行情跳空频繁,多周期缓冲在周末缺口处可能错位,上 MT5 跑一眼便知。
class=class="str">"cmt">/*sinput*/ ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_CURRENT; class=class="str">"cmt">// Mode of used timeframes list sinput*/ ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_LIST; class=class="str">"cmt">// Mode of used timeframes list sinput ENUM_TIMEFRAMES InpPeriod = PERIOD_CURRENT; class=class="str">"cmt">// Used chart period class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable InpUsedTFs=TimeframeDescription(InpPeriod); class=class="str">"cmt">//--- indicator buffers mapping class=class="str">"cmt">//--- Create all the necessary buffer objects engine.BufferCreateArrow(); class=class="str">"cmt">// class="num">2 arrays engine.BufferCreateLine(); class=class="str">"cmt">// class="num">2 arrays engine.BufferCreateSection(); class=class="str">"cmt">// class="num">2 arrays engine.BufferCreateHistogram(); class=class="str">"cmt">// class="num">2 arrays engine.BufferCreateHistogram2(); class=class="str">"cmt">// class="num">3 arrays engine.BufferCreateZigZag(); class=class="str">"cmt">// class="num">3 arrays engine.BufferCreateFilling(); class=class="str">"cmt">// class="num">2 arrays engine.BufferCreateBars(); class=class="str">"cmt">// class="num">5 arrays engine.BufferCreateCandles(); class=class="str">"cmt">// class="num">5 arrays engine.BufferCreateCalculate(); class=class="str">"cmt">// class="num">1 array class=class="str">"cmt">//--- Check the number of buffers specified in the &class="macro">#x27;properties&class="macro">#x27; block if(engine.BuffersPropertyPlotsTotal()!=indicator_plots) Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal()); if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers) Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal()); class=class="str">"cmt">//--- Create the class="type">class="kw">color array and set non-class="kw">default colors to all buffers within the collection class="type">class="kw">color array_colors[]={clrDodgerBlue,clrRed,clrGray}; engine.BuffersSetColors(array_colors); class=class="str">"cmt">//--- Set the line width for ZigZag(the sixth drawn buffer) class=class="str">"cmt">//--- It has the index of class="num">5 considering that the starting point is zero CBuffer *buff_zz=engine.GetBufferByPlot(class="num">5); if(buff_zz!=NULL) { buff_zz.SetWidth(class="num">2);
◍ 缓冲显示与数据序列化的落地写法
在指标初始化收尾阶段,需要把集合里的每个缓冲对象按用途和周期配置刷一遍显示属性。下面这段循环遍历 engine 的缓冲列表,对第 i 个对象先取指针判空,再依据 IsUse(buff.Status()) 决定数据窗口是否绘制,并把周期统一设为输入参数 InpPeriod。 for(int i=0;i<engine.GetListBuffers().Total();i++) { CBuffer *buff=engine.GetListBuffers().At(i); if(buff==NULL) continue; buff.SetShowData(IsUse(buff.Status())); buff.SetTimeframe(InpPeriod); } CopyData 函数承接第二形态 OnCalculate 的入参,核心动作是先对齐时间序列索引。MT5 中 time/open/high/low/close 等数组默认不一定是 AS_SERIES 方向,代码逐个用 ArrayGetAsSeries 探测,若返回 false 就 ArraySetAsSeries(...,true) 强制倒序,使下标 0 对应最新柱。这一处若漏写,复制零号柱数据进 rates_data 时会出现偏移一根 K 线的错位。 对外汇与贵金属品种加载这类自定义指标时,点差数组 spread 在部分经纪商环境可能全为 0,序列化后用于计算仍要加概率性判断,别直接当真实成本用。开 MT5 把这段贴进 EA 或指标工程,改 InpPeriod 看数据窗口缓冲跟随切换,即可验证。
for(class="type">int i=class="num">0;i<engine.GetListBuffers().Total();i++) { CBuffer *buff=engine.GetListBuffers().At(i); if(buff==NULL) class="kw">continue; buff.SetShowData(IsUse(buff.Status())); buff.SetTimeframe(InpPeriod); } class="type">void CopyData(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">bool as_series_time=ArrayGetAsSeries(time); if(!as_series_time) ArraySetAsSeries(time,true); class="type">bool as_series_open=ArrayGetAsSeries(open); if(!as_series_open) ArraySetAsSeries(open,true); class="type">bool as_series_high=ArrayGetAsSeries(high); if(!as_series_high) ArraySetAsSeries(high,true); class="type">bool as_series_low=ArrayGetAsSeries(low); if(!as_series_low) ArraySetAsSeries(low,true); class="type">bool as_series_close=ArrayGetAsSeries(close); if(!as_series_close) ArraySetAsSeries(close,true); class="type">bool as_series_tick_volume=ArrayGetAsSeries(tick_volume); if(!as_series_tick_volume) ArraySetAsSeries(tick_volume,true); class="type">bool as_series_volume=ArrayGetAsSeries(volume); if(!as_series_volume) ArraySetAsSeries(volume,true); class="type">bool as_series_spread=ArrayGetAsSeries(spread); if(!as_series_spread) ArraySetAsSeries(spread,true); rates_data.rates_total=rates_total;
把 OnCalculate 数组塞进结构体的收尾动作
这段逻辑做两件事:先把当前柱面的最新行情写进自定义结构体,再把外部传入的数组索引方向还原回调用前的状态。写结构体时全部取 [0] 下标,也就是当前未闭合 K 线——time、open、high、low、close、tick_volume 直接赋值,real_volume 和 spread 用预编译宏区分 MQL5 环境,非 MQL5 时填 0。 还原方向那段不能省。函数开头用 ArraySetAsSeries 把数组设成了序列模式(最新价在 [0]),但调用方可能原本是普通数组(最旧在 [0])。代码里用 as_series_xxx 布尔变量记下原状态,false 就调 ArraySetAsSeries(xxx,false) 翻回去,避免污染调用者的其他计算。 OnCalculate 入口处那 8 行 ArraySetAsSeries(...,true) 是固定套路:open/high/low/close/time/tick_volume/volume/spread 全部序列化,这样 [0] 恒为当前柱。CopyDataAsSeries 函数签名接收 rates_total、prev_calculated 和 8 个数组引用,职责就是把第二种 OnCalculate 形态的数据搬进结构体并统一标记为序列。 开 MT5 新建指标时,若你混用了多种 OnCalculate 重载,直接抄这套「进函数序列化、出函数还原」的写法,能少踩一半下标错位的坑。外汇与贵金属杠杆高,指标逻辑错误可能放大下单偏差,验证前先用历史回放跑一遍。
rates_data.prev_calculated=prev_calculated; rates_data.rates.time=time[class="num">0]; rates_data.rates.open=open[class="num">0]; rates_data.rates.high=high[class="num">0]; rates_data.rates.low=low[class="num">0]; rates_data.rates.close=close[class="num">0]; rates_data.rates.tick_volume=tick_volume[class="num">0]; rates_data.rates.real_volume=(class="macro">#ifdef __MQL5__ volume[class="num">0] class="macro">#else class="num">0 class="macro">#endif); rates_data.rates.spread=(class="macro">#ifdef __MQL5__ spread[class="num">0] class="macro">#else class="num">0 class="macro">#endif); class=class="str">"cmt">//--- Return the arrays&class="macro">#x27; initial indexing direction if(!as_series_time) ArraySetAsSeries(time,class="kw">false); if(!as_series_open) ArraySetAsSeries(open,class="kw">false); if(!as_series_high) ArraySetAsSeries(high,class="kw">false); if(!as_series_low) ArraySetAsSeries(low,class="kw">false); if(!as_series_close) ArraySetAsSeries(close,class="kw">false); if(!as_series_tick_volume) ArraySetAsSeries(tick_volume,class="kw">false); if(!as_series_volume) ArraySetAsSeries(volume,class="kw">false); if(!as_series_spread) ArraySetAsSeries(spread,class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| OnCalculate code block for working with the indicator: | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Set OnCalculate arrays as timeseries ArraySetAsSeries(open,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(close,true); ArraySetAsSeries(time,true); ArraySetAsSeries(tick_volume,true); ArraySetAsSeries(volume,true); ArraySetAsSeries(spread,true); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Copy data from the second OnCalculate() form to the structure | class=class="str">"cmt">//| and set the "as timeseries" flag to all arrays | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CopyDataAsSeries(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[],
「把 OnCalculate 数据搬进时间序列结构」
在 MT5 自定义指标里,OnCalculate 传入的各类数组默认按时间正序排列,但大多数指标逻辑依赖最新的 0 号 BAR 在头部。上面这段辅助函数 CopyDataAsSeries 做的第一件事,就是对 time、open、high、low、close、tick_volume、volume、spread 八个引用数组逐一调用 ArraySetAsSeries(...,true),把索引方向翻成和时序一致。 翻完方向后,函数把 rates_total 与 prev_calculated 直接写进全局的 rates_data 结构,再把 0 号元素(即当前未完成 BAR)的八个字段逐一赋值给 rates_data.rates。注意 real_volume 和 spread 都用 #ifdef __MQL5__ 做了分支:MQL5 环境下取 volume[0] 与 spread[0],否则填 0,避免在 MQL4 兼容层编译报错。 实际写指标时,只要在你的 OnCalculate 开头加一行 CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);,后续计算就能统一从 rates_data.rates 读当前 BAR,不用反复传参。外汇与贵金属品种点差跳动频繁,spread[0] 在流动性稀薄时段可能瞬间拉到平时 3~5 倍,用这套结构取数能少写很多重复代码。
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">//--- set the indexing direction or the arrays as in the timeseries ArraySetAsSeries(time,true); ArraySetAsSeries(open,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(close,true); ArraySetAsSeries(tick_volume,true); ArraySetAsSeries(volume,true); ArraySetAsSeries(spread,true); class=class="str">"cmt">//--- Copy the arrays&class="macro">#x27; zero bar to the OnCalculate() SDataCalculate data structure rates_data.rates_total=rates_total; rates_data.prev_calculated=prev_calculated; rates_data.rates.time=time[class="num">0]; rates_data.rates.open=open[class="num">0]; rates_data.rates.high=high[class="num">0]; rates_data.rates.low=low[class="num">0]; rates_data.rates.close=close[class="num">0]; rates_data.rates.tick_volume=tick_volume[class="num">0]; rates_data.rates.real_volume=(class="macro">#ifdef __MQL5__ volume[class="num">0] class="macro">#else class="num">0 class="macro">#endif); rates_data.rates.spread=(class="macro">#ifdef __MQL5__ spread[class="num">0] class="macro">#else class="num">0 class="macro">#endif); } class=class="str">"cmt">//+------------------------------------------------------------------+ 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, 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=class="str">"cmt">//| OnCalculate code block for working with the library: | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread); class=class="str">"cmt">//--- Check for the minimum number of bars for calculation
◍ 指标主循环里如何按K线方向刷缓冲
在自定义指标的主计算循环里,先按 limit 判定重算范围:limit>1 意味着首启或历史变动,此时把 limit 置为 rates_total-1 并调用 BuffersInitPlots 与 BuffersInitCalculates 做全量初始化;否则只处理新增及当前柱。 循环从 i=limit 向下走到 WRONG_VALUE,并用 !IsStopped() 兜底,避免终端关闭时还空跑。每根柱先用 BufferArrowClear、BufferLineClear 等把九类图形缓冲(箭头、线、区间、直方、双直方、之字、填充、柱、蜡烛)在索引 0 清掉,再取对应周期 K 线对象。 取柱用 engine.SeriesGetBar(NULL,InpPeriod,time[i]),拿不到就 continue 跳走。方向判定靠 bar.TypeBody():多头柱 color_index=0,空头=1,其他=2,随后按 IsUse(BUFFER_STATUS_ARROW) 开关写入箭头缓冲的收盘价与颜色。外汇与贵金属波动剧烈,这类缓冲刷新逻辑若 limit 算错可能造成历史重绘,建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M5 数据跑一遍验证重算触发次数。
if(rates_total<min_bars || Point()==class="num">0) class="kw">return class="num">0; class=class="str">"cmt">//--- Handle the Calculate event in the library class=class="str">"cmt">//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick if(engine.OnCalculate(rates_data,rates_total,prev_calculated,begin)==class="num">0) class="kw">return class="num">0; class=class="str">"cmt">//--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); class=class="str">"cmt">// Working in the library timer EventsHandling(); class=class="str">"cmt">// Working with library events } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| OnCalculate code block for working with the indicator: | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Check and calculate the number of calculated bars class=class="str">"cmt">//--- If limit = class="num">0, there are no new bars - calculate the current one class=class="str">"cmt">//--- If limit = class="num">1, a new bar has appeared - calculate the first and the current ones class=class="str">"cmt">//--- limit > class="num">1 means the first launch or changes in history - the full recalculation of all data class="type">int limit=rates_total-prev_calculated; class=class="str">"cmt">//--- Recalculate the entire history if(limit>class="num">1) { limit=rates_total-class="num">1; engine.BuffersInitPlots(); engine.BuffersInitCalculates(); } class=class="str">"cmt">//--- Prepare data class=class="str">"cmt">//--- Calculate the indicator CBar *bar=NULL; class=class="str">"cmt">// Bar object for defining the candle direction class="type">uchar color_index=class="num">0; class=class="str">"cmt">// Color index to be set for the buffer depending on the candle direction class=class="str">"cmt">//--- Main calculation loop of the indicator for(class="type">int i=limit; i>WRONG_VALUE && !IsStopped(); i--) { class=class="str">"cmt">//--- Clear the current bar of all created buffers engine.BufferArrowClear(class="num">0,class="num">0); engine.BufferLineClear(class="num">0,class="num">0); engine.BufferSectionClear(class="num">0,class="num">0); engine.BufferHistogramClear(class="num">0,class="num">0); engine.BufferHistogram2Clear(class="num">0,class="num">0); engine.BufferZigZagClear(class="num">0,class="num">0); engine.BufferFillingClear(class="num">0,class="num">0); engine.BufferBarsClear(class="num">0,class="num">0); engine.BufferCandlesClear(class="num">0,class="num">0); class=class="str">"cmt">//--- Get the timeseries bar corresponding to the loop index time on the chart period specified in the settings bar=engine.SeriesGetBar(NULL,InpPeriod,time[i]); if(bar==NULL) class="kw">continue; class=class="str">"cmt">//--- Calculate the class="type">class="kw">color index depending on the candle direction on the timeframe specified in the settings color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? class="num">0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? class="num">1 : class="num">2); class=class="str">"cmt">//--- Check the settings and calculate the arrow buffer if(IsUse(BUFFER_STATUS_ARROW)) engine.BufferSetDataArrow(class="num">0,i,bar.Close(),color_index); class=class="str">"cmt">//--- Check the settings and calculate the line buffer
多形态缓冲区的条件写入逻辑
这段循环体展示了指标引擎如何按开关逐类写入缓冲区:每一个 BUFFER_STATUS_* 宏都对应一种可视化形态,只有 IsUse() 返回真才执行对应 BufferSetData* 调用,避免无谓计算。 折线用 Open 画、区段用 Close 画、零轴直方图传 open[i]、双缓冲直方图传开收价差——这些都是在 i 位置按 bar 结构喂数据,color_index 决定着色。 ZigZag 与 Filling 需要根据多空翻转取值:阳线 ZigZag 取 Low,阴线(color_index==1)取 High;Filling 阳线边界是 High/Low,阴线交换成 Low/High 以切换填充色。 最后若开启 BUFFER_STATUS_BARS 或 CANDLES,则把 OHLC 四价一次性送入对应缓冲。函数末尾 return(rates_total) 把已处理柱数交还系统,供下次增量调用。 开 MT5 新建指标把这段粘进 OnCalculate,把 IsUse 各宏先全置 1,能在副图同时看到线、区段、直方图与蜡烛,验证缓冲映射是否正确。外汇与贵金属波动剧烈,这类多缓冲渲染仅作结构参考,实盘信号须自担高风险。
if(IsUse(BUFFER_STATUS_LINE)) engine.BufferSetDataLine(class="num">0,i,bar.Open(),color_index); class=class="str">"cmt">//--- Check the settings and calculate the section buffer if(IsUse(BUFFER_STATUS_SECTION)) engine.BufferSetDataSection(class="num">0,i,bar.Close(),color_index); class=class="str">"cmt">//--- Check the settings and calculate the histogram from zero buffer if(IsUse(BUFFER_STATUS_HISTOGRAM)) engine.BufferSetDataHistogram(class="num">0,i,open[i],color_index); class=class="str">"cmt">//--- Check the settings and calculate the &class="macro">#x27;histogram on two buffers&class="macro">#x27; buffer if(IsUse(BUFFER_STATUS_HISTOGRAM2)) engine.BufferSetDataHistogram2(class="num">0,i,bar.Open(),bar.Close(),color_index); class=class="str">"cmt">//--- Check the settings and calculate the zigzag buffer if(IsUse(BUFFER_STATUS_ZIGZAG)) { class=class="str">"cmt">//--- Set the bar&class="macro">#x27;s Low value value for the zigzag(for bullish candles) class="type">class="kw">double value1=bar.Low(); class="type">class="kw">double value2=value1; class=class="str">"cmt">//--- If the candle is bearish(class="type">class="kw">color index = class="num">1), set the bar&class="macro">#x27;s High value for the zigzag if(color_index==class="num">1) { value1=value2=bar.High(); } engine.BufferSetDataZigZag(class="num">0,i,value1,value2,color_index); } class=class="str">"cmt">//--- Check the settings and calculate the filling buffer if(IsUse(BUFFER_STATUS_FILLING)) { class=class="str">"cmt">//--- Set filling border values for bullish candles class="type">class="kw">double value1=bar.High(); class="type">class="kw">double value2=bar.Low(); class=class="str">"cmt">//--- In case of the bearish candle(class="type">class="kw">color index = class="num">1), swap the filling borders to change the class="type">class="kw">color if(color_index==class="num">1) { value1=bar.Low(); value2=bar.High(); } engine.BufferSetDataFilling(class="num">0,i,value1,value2,color_index); } class=class="str">"cmt">//--- Check the settings and calculate the bar buffer if(IsUse(BUFFER_STATUS_BARS)) engine.BufferSetDataBars(class="num">0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index); class=class="str">"cmt">//--- Check the settings and calculate the candle buffer if(IsUse(BUFFER_STATUS_CANDLES)) engine.BufferSetDataCandles(class="num">0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index); } class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); }
「缓冲区集合类之后要补的指标操作」
这一节原本是系列第 45 篇的收尾,作者点出下一步要做的只有一件事:把指标缓冲区集合类继续写完整,并加上多品种模式的指标调度逻辑。当前附件里的 MQL5.zip 体积为 3772.33 KB,含函数库全部文件与测试 EA,仅能在 MetaTrader 5 下编译运行,MT4 尚未验证。 有读者在评论里提到,用第 45 版示例去连第 90 版库会编译报错,作者确认这是库迭代导致的接口变动,并举例:TestDoEasyPart45.mq5 第 403 行需把 engine.SetSoundsStandart() 改成 engine.SetSoundsStandard(),Buffer.mqh 第 84 行要给 PrintShort 补空实现体。你在 MT5 里若也遇到类似报错,优先对照自己用的库版本号查接口差异。 外汇与贵金属交易自带高杠杆高风险,这类库只解决编码效率,不预示任何收益。等缓冲区集合类真正支持标准/自定义指标类后,跨周期拉 MACD 到子窗口这类需求写起来才会省事,在那之前建议先拿当前 zip 跑通基础样例。