DoEasy 函数库中的时间序列(第四十九部分):多周期、多品种、多缓冲区标准指标·综合运用
📊

DoEasy 函数库中的时间序列(第四十九部分):多周期、多品种、多缓冲区标准指标·综合运用

(3/3)· 从单缓冲区到多线标准指标,解决跨品种周期引用时缓冲区归属混乱的最后一环

含代码示例实战向 第 3/3 篇
在 current 图表叠加别的产品周期指标时,很多人卡在多条指标线分不清哪条属于哪个实例。给缓冲区和线型补上标识参数,引用就不再是碰运气。
本章目录
  1. 指标缓冲区的只读属性接口
  2. 缓冲区对象的属性读取接口
  3. 缓冲区对象的构造与默认属性落位
  4. 正文
  5. 把缓冲区绑进指标绘图层的细节
  6. 缓冲区整型属性的可读化输出
  7. 指标缓冲区属性的可读化拼装
  8. 缓冲区字符串属性的描述拼装逻辑
  9. 缓冲区属性支持的判定逻辑
  10. 在缓冲集合里挂一条均线通道
  11. 多周期 ADX 的缓冲区分发逻辑
  12. 给 ADX 三条线分别挂上缓冲区参数
  13. 给 -DI 线挂上 ADX 缓冲对象
  14. 单缓冲与多缓冲指标的取数分支
  15. 按缓冲属性抓取三线数据
  16. 多指标缓冲区的按线取数逻辑
  17. 单缓冲与多缓冲指标的清空差异
  18. 按指标类型清空缓冲区的写法
  19. 清空 MACD 与 stochastic 缓冲区的写法
  20. 单缓冲指标如何取出绘制与计算对象
  21. 跨周期缓冲对齐时的柱数换算
  22. ADX 多缓冲区的取色与判级逻辑
  23. 跨周期缓冲对齐与染色逻辑
  24. 跨周期取上下轨缓冲区的代码落点
  25. 跨周期缓冲区的染色与回填逻辑
  26. 跨周期指标缓冲对齐的坑
  27. MACD类指标的跨周期取色逻辑
  28. 跨周期缓冲对齐时的取色与填充逻辑
  29. 缓冲集合里按类型取指标标签与短名
  30. 按类型与ID取缓冲标签的实现
  31. 用两个指标把多周期标准信号搬上图表
  32. 动量类指标的一行式注册
  33. 子窗口多缓冲指标的创建分支
  34. 按指标类型动态定小数位与水平线
  35. 按指标类型切分水平线与小数位
  36. 初始化里按类型建缓冲的写法
  37. 子窗口指标与缓冲区自检的落地写法
  38. 指标精度与警戒线的分支设定
  39. 摆动类指标的临界线怎么落进图表
  40. 收束

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

在 MT5 自定义指标类里,缓冲区(buffer)的元数据大多通过 GetProperty 以只读方法暴露,调用方只能取不能改。下面这组方法覆盖了显示开关、偏移、线型线宽、颜色与数据量等常见属性,返回类型与枚举严格对应。 IsShowData 返回布尔值,对应 BUFFER_PROP_SHOW_DATA,决定该缓冲数据是否绘制在图表上;Shift 取 BUFFER_PROP_SHIFT,常见默认值为 0,部分指标设为 1 以实现领先/滞后平移。LineStyle 与 LineWidth 分别映射线型枚举与像素宽度,前者为 ENUM_LINE_STYLE 而非整型。 ColorsTotal 读 BUFFER_PROP_COLOR_INDEXES,告诉你该缓冲支持几套配色;Color 直接给主色。BuffersTotal 返回 BUFFER_PROP_NUM_DATAS,即实际数据缓冲条数;EmptyValue 是 double 型缺失值标记,Symbol 则锁定该缓冲绑定的交易品种字符串。 开 MT5 新建指标,在 OnCalculate 里调一句 buffer.LineWidth() 就能确认当前线宽,不用翻代码头部宏定义。外汇与贵金属行情波动剧烈,这类属性读取仅用于显示控制,不构成任何方向判断。

MQL5 / C++
class="type">bool IsShowData(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(BUFFER_PROP_SHOW_DATA); }
class="type">int Shift(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_SHIFT); }
ENUM_LINE_STYLE LineStyle(class="type">void) class="kw">const { class="kw">return (ENUM_LINE_STYLE)this.GetProperty(BUFFER_PROP_LINE_STYLE); }
class="type">int LineWidth(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_LINE_WIDTH); }
class="type">int ColorsTotal(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_COLOR_INDEXES); }
class="type">color Color(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(BUFFER_PROP_COLOR); }
class="type">int BuffersTotal(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_NUM_DATAS); }
class="type">class="kw">double EmptyValue(class="type">void) class="kw">const { class="kw">return this.GetProperty(BUFFER_PROP_EMPTY_VALUE); }
class="type">class="kw">string Symbol(class="type">void) class="kw">const { class="kw">return this.GetProperty(BUFFER_PROP_SYMBOL); }

◍ 缓冲区对象的属性读取接口

在 MT5 自定义指标架构里,CBuffer 类把每个绘图缓冲区的元信息封装成一组只读访问器,避免外部直接碰底层属性表。下面这段声明展示了从标签到指标句柄的映射方式。 [CODE] string Label(void) const { return this.GetProperty(BUFFER_PROP_LABEL); } int ID(void) const { return (int)this.GetProperty(BUFFER_PROP_ID); } int IndicatorHandle(void) const { return (int)this.GetProperty(BUFFER_PROP_IND_HANDLE); } ENUM_INDICATOR IndicatorType(void) const { return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE); } string IndicatorName(void) const { return this.GetProperty(BUFFER_PROP_IND_NAME); } string IndicatorShortName(void) const { return this.GetProperty(BUFFER_PROP_IND_NAME_SHORT); } int IndicatorBarsCalculated(void) const { return ::BarsCalculated((int)this.GetProperty(BUFFER_PROP_IND_HANDLE)); } ENUM_INDICATOR_LINE_MODE LineMode(void) const { return (ENUM_INDICATOR_LINE_MODE)this.GetProperty(BUFFER_PROP_IND_LINE_MODE); } [/CODE] 逐行看:Label 取缓冲区文字标签,ID 强转取整数编号;IndicatorHandle 拿指标句柄,IndicatorType 回吐指标枚举类型。IndicatorShortName 与 IndicatorName 分别取完整名和短名,前者常用于子窗口标题。 IndicatorBarsCalculated 直接调全局 BarsCalculated 并传入句柄,返回已计算柱数——这是判断指标数据是否就绪的关键返回值,若小于当前图表 Bars 总数,读取缓冲区可能拿到空值。LineMode 暴露画线模式(如线/柱/点),决定渲染层怎么画。 CBuffer 的闭参构造接收状态、类型、plot 索引、基数组索引、数据量、数组总数与线宽,把这些在实例化时一次性固化。实盘加载指标时若 ShortName 显示异常,优先查 BUFFER_PROP_IND_NAME_SHORT 有没有在构造后正确写入。外汇与贵金属波动大、杠杆高,缓冲区未就绪就下单信号可能失真,务必在 EA 里先判 BarsCalculated 再取数。

MQL5 / C++
class="type">class="kw">string Label(class="type">void) class="kw">const { class="kw">return this.GetProperty(BUFFER_PROP_LABEL); }
class="type">int ID(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_ID); }
class="type">int IndicatorHandle(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(BUFFER_PROP_IND_HANDLE); }
ENUM_INDICATOR IndicatorType(class="type">void) class="kw">const { class="kw">return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE); }
class="type">class="kw">string IndicatorName(class="type">void) class="kw">const { class="kw">return this.GetProperty(BUFFER_PROP_IND_NAME); }
class="type">class="kw">string IndicatorShortName(class="type">void) class="kw">const { class="kw">return this.GetProperty(BUFFER_PROP_IND_NAME_SHORT); }
class="type">int IndicatorBarsCalculated(class="type">void) class="kw">const { class="kw">return ::BarsCalculated((class="type">int)this.GetProperty(BUFFER_PROP_IND_HANDLE)); }
ENUM_INDICATOR_LINE_MODE LineMode(class="type">void) class="kw">const { class="kw">return (ENUM_INDICATOR_LINE_MODE)this.GetProperty(BUFFER_PROP_IND_LINE_MODE); }

缓冲区对象的构造与默认属性落位

在自定义指标架构里,缓冲区(buffer)对象初始化时先把内部类型标记为 COLLECTION_BUFFERS_ID,并打开激活触发开关 m_act_state_trigger,同时记录总数组数 m_total_arrays。这一步决定了后续绘图与数据绑定的容器规模。 整数属性集中,BUFFER_PROP_ID 先填 WRONG_VALUE、INDICATOR_LINE_MODE_MAIN 写死为主线模式,句柄与指标类型也暂置无效值。注意被高亮的那行:默认线模式强制为主图线,若你要做副图独立线,这里必须改。 绘制类型由类型与状态推导:状态或类型为假则 DRAW_NONE;填充状态走 DRAW_FILLING;其余用状态值 +8 映射 ENUM_DRAW_TYPE。箭头默认码 0x9F、偏移 0、线宽取入参 width、颜色默认 clrRed,而 SHOW_DATA 仅在 buffer_type 大于 BUFFER_TYPE_CALCULATE 时才为真。 开 MT5 把这段贴进类构造,改 INDICATOR_LINE_MODE_MAIN 为副图枚举,看导航树里指标线归属变化,就能验证默认落位逻辑。外汇与贵金属指标开发属高风险调试,参数误设可能导致图表无输出。

MQL5 / C++
 class="kw">const class="type">class="kw">string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
   this.m_total_arrays=total_arrays;
class=class="str">"cmt">//--- Save integer properties
   this.m_long_prop[BUFFER_PROP_STATUS]                       = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                         = buffer_type;
   this.m_long_prop[BUFFER_PROP_ID]                           = WRONG_VALUE;
   this.m_long_prop[BUFFER_PROP_IND_LINE_MODE]                = INDICATOR_LINE_MODE_MAIN;
   this.m_long_prop[BUFFER_PROP_IND_HANDLE]                  = INVALID_HANDLE;
   this.m_long_prop[BUFFER_PROP_IND_TYPE]                    = WRONG_VALUE;
   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 : 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;

「正文」

&nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_INDEX_PLOT]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= index_plot; &nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_INDEX_BASE]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= index_base_array; &nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_INDEX_COLOR]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="keyword">this</span>.GetProperty(BUFFER_PROP_INDEX_BASE)+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (<span class="keyword">this</span>.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? <span class="keyword">this</span>.GetProperty(BUFFER_PROP_NUM_DATAS) : <span class="number">0</span>); &nbsp;&nbsp; <span class="keyword">this</span>.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = index_base_array+<span class="keyword">this</span>

◍ 把缓冲区绑进指标绘图层的细节

在自定义指标里,缓冲区不是声明完就能画的,必须逐一绑定到指标句柄并设成时间序列索引。下面这段逻辑通常在初始化阶段跑,决定了 MT5 图表上最终看到的是线、箭头还是填充区。 循环里先按基准索引偏移算出当前 buffer 的全局序号,再用 SetIndexBuffer 把动态数组挂上去;类型走 INDICATOR_DATA 还是 INDICATOR_CALCULATIONS,取决于该缓冲区的用途。紧接着 ArraySetAsSeries(..., true) 把数组按时间序排列,否则新K线会写反位置。 颜色缓冲区只在非填充、非计算型 buffer 上绑定,用 INDICATOR_COLOR_INDEX 标识;纯计算缓冲一旦识别就直接 return,不再往下设绘图属性。 绘图外观全靠 PlotIndexSetInteger 一堆调用:画线类型、箭头代码、偏移、起始忽略根数、是否显示数据、整体平移、线型与线宽,共 9 个整型属性。双精度层只设 PLOT_EMPTY_VALUE,字符串层只设 PLOT_LABEL。开 MT5 新建指标时,若图表不显示或错位,优先核对这几行绑定是否漏了 ArraySetAsSeries。

MQL5 / C++
class=class="str">"cmt">//--- bind the indicator buffer by the calculated index with the dynamic array
class=class="str">"cmt">//--- located by the i loop index in the DataBuffer array
class="type">int index=(class="type">int)this.GetProperty(BUFFER_PROP_INDEX_BASE)+i;
::SetIndexBuffer(index,this.DataBuffer[i].Array,(this.TypeBuffer()==BUFFER_TYPE_DATA ? INDICATOR_DATA : INDICATOR_CALCULATIONS));
class=class="str">"cmt">//--- Set indexation flag as in the timeseries to all buffer arrays
::ArraySetAsSeries(this.DataBuffer[i].Array,true);
 }
class=class="str">"cmt">//--- Bind the class="type">color buffer with the array(only for a non-calculated buffer and not for the filling buffer)
 if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
 {
 ::SetIndexBuffer((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_COLOR),this.ColorBufferArray,INDICATOR_COLOR_INDEX);
 ::ArraySetAsSeries(this.ColorBufferArray,true);
 }
class=class="str">"cmt">//--- If this is a calculated buffer, all is done
 if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
 class="kw">return;
class=class="str">"cmt">//--- Set integer parameters of the graphical series
 ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_TYPE));
 ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_CODE));
 ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_SHIFT));
 ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_BEGIN));
 ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHOW_DATA,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHOW_DATA));
 ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT));
 ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE));
 ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH));
 this.SetColor((class="type">color)this.GetProperty(BUFFER_PROP_COLOR));
class=class="str">"cmt">//--- Set real parameters of the graphical series
 ::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 parameters of the graphical series
 ::PlotIndexSetString((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL));
}

缓冲区整型属性的可读化输出

在自研指标类里,直接把 ENUM_BUFFER_PROP_INTEGER 的枚举值打印出来基本没人看得懂。下面这段 CBuffer::GetPropertyDescription 做的就是「枚举 → 人类语言」的映射,把绘图索引、状态、类型、周期等 9 类整型属性翻成界面文字。 代码用嵌套三元运算符按 property 逐一匹配:先取多语言文本前缀(如 MSG_LIB_TEXT_BUFFER_TEXT_STATUS),再判断当前缓冲区是否 SupportProperty,不支持就补「: 不支持」,支持则调用对应的 GetXxxDescription 或强转 GetProperty 出值。 实际排错时,若某属性返回「不支持」,先确认该 buffer 是否注册了对应特性,而非以为数值异常。外汇与贵金属指标在高波动时段重绘可能让描述与实际绘图短暂偏离,属高风险环境下的正常现象。 把这套描述函数接进专家日志,能在 MT5 回放里快速定位是哪个 buffer 的配置没生效,省掉反复翻源码的时间。

MQL5 / C++
class="type">class="kw">string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER class="kw">property)
  {
   class="kw">return
     (
       class="kw">property==BUFFER_PROP_INDEX_PLOT       ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT)+
         (!this.SupportProperty(class="kw">property) ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
         )  :
       class="kw">property==BUFFER_PROP_STATUS           ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_STATUS)+
         (!this.SupportProperty(class="kw">property) ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+this.GetStatusDescription()
         )  :
       class="kw">property==BUFFER_PROP_TYPE             ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TYPE)+
         (!this.SupportProperty(class="kw">property) ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+this.GetTypeBufferDescription()
         )  :
       class="kw">property==BUFFER_PROP_TIMEFRAME        ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME)+
         (!this.SupportProperty(class="kw">property) ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+this.GetTimeframeDescription()
         )  :
       class="kw">property==BUFFER_PROP_ACTIVE           ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE)+
         (!this.SupportProperty(class="kw">property) ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+this.GetActiveDescription()
         )  :
       class="kw">property==BUFFER_PROP_DRAW_TYPE        ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE)+
         (!this.SupportProperty(class="kw">property) ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+this.GetDrawTypeDescription()
         )  :
       class="kw">property==BUFFER_PROP_ARROW_CODE       ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE)+
         (!this.SupportProperty(class="kw">property) ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
         )  :
       class="kw">property==BUFFER_PROP_ARROW_SHIFT      ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT)+
         (!this.SupportProperty(class="kw">property)     ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
         )  :
       class="kw">property==BUFFER_PROP_LINE_STYLE       ?   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE)+
         (!this.SupportProperty(class="kw">property) ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+this.GetLineStyleDescription()
         )  :
       class="kw">property==BUFFER_PROP_LINE_WIDTH       ?   

「指标缓冲区属性的可读化拼装」

在自研指标或面板里,想把某个缓冲区的配置直接打印成日志,就得把内部枚举翻译成人话。下面这段分支逻辑专门处理箭头/线宽、绘起始位、是否显数据等十余个属性,不支持的属性统一回写“不支持”提示,支持的则取实际值。 外汇与贵金属行情跳变频繁,缓冲区若设了 BUFFER_PROP_DRAW_BEGIN=10,前 10 根 K 线可能留空,回测时容易误判信号缺失,属高风险下的显示陷阱。 把这段逻辑原样丢进 MT5 的 CS 类方法里,编译后调用 GetPropertyDescription(BUFFER_PROP_SHIFT) 之类,能立刻看到当前偏移量数值,方便核对与调参。

MQL5 / C++
 (this.Status()==BUFFER_STATUS_ARROW ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE) :
   CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH))+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 )  :
class="kw">property==BUFFER_PROP_DRAW_BEGIN      ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 )  :
class="kw">property==BUFFER_PROP_SHOW_DATA       ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+this.GetShowDataDescription()
 )  :
class="kw">property==BUFFER_PROP_SHIFT           ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHIFT)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 )  :
class="kw">property==BUFFER_PROP_COLOR_INDEXES ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 )  :
class="kw">property==BUFFER_PROP_INDEX_COLOR     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 )  :
class="kw">property==BUFFER_PROP_INDEX_BASE      ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 )  :
class="kw">property==BUFFER_PROP_INDEX_NEXT_BASE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 )  :
class="kw">property==BUFFER_PROP_INDEX_NEXT_PLOT ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
   ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 )  :
class="kw">property==BUFFER_PROP_ID ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ID)+
 (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :

◍ 缓冲区字符串属性的描述拼装逻辑

CBuffer 类里 GetPropertyDescription 针对字符串类属性(ENUM_BUFFER_PROP_STRING)做统一描述输出,靠三元运算符链按 property 值分流。 当 property 是 BUFFER_PROP_SYMBOL,先取语言文本 MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL 作前缀;若 SupportProperty 返回 false,拼 ': ' 加“不支持”提示,否则接 this.Symbol() 的实际值。 BUFFER_PROP_LABEL 分支里多了空值判断:Label() 为 NULL 或空串时显示“未设置”,否则用双引号包裹标签内容,避免界面上标签缺失时显示异常。 BUFFER_PROP_IND_NAME 分支结构相同,不支持时回退到 MSG_LIB_PROP_NOT_SUPPORTED 文案,支持时取指标名文本。 在 MT5 里新建 CBuffer 派生类后,直接调用 GetPropertyDescription(BUFFER_PROP_LABEL) 就能看到标签描述是否符合预期,省去自己拼字符串的麻烦。

MQL5 / C++
class="type">class="kw">string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_STRING class="kw">property)
  {
   class="kw">return
     (
      class="kw">property==BUFFER_PROP_SYMBOL     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL)+
        (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+this.Symbol()
        )  :
      class="kw">property==BUFFER_PROP_LABEL     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LABEL)+
        (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
           ": "+(this.Label()==NULL || this.Label()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Label()+"\"")
        )  :
      class="kw">property==BUFFER_PROP_IND_NAME  ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME)+
        (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :

缓冲区属性支持的判定逻辑

在 MQL5 自定义指标框架里,CBufferLine 用三组重载的 SupportProperty() 来拦截「计算型缓冲区」不能碰的属性。BUFFER_TYPE_CALCULATE 这类缓冲本身不持有绘图数据,若强行读写箭头偏移、线宽等整数属性会直接返回 false。 整数属性那一版最苛刻:箭头代码、箭头偏移一律不支持;若是计算型缓冲,除类型、基础索引、线模式、句柄、指标类型、ID 这 6 个标识类属性外,其余整数属性全被挡掉。 字符串属性相对宽松——计算型缓冲仅放开 BUFFER_PROP_IND_NAME_SHORT(短名称),其余字符串属性同样返回 false。双精度属性则最简单:只要不是计算型缓冲就通吃。 开 MT5 把这段塞进你的指标类,给计算型缓冲乱设 ArrowShift 会立刻走 false 分支,省掉运行时报错的麻烦。外汇与贵金属指标开发属高风险调试,参数误用可能导致图形错位。

MQL5 / C++
class="type">bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_INTEGER class="kw">property)
  {
   if((class="kw">property==BUFFER_PROP_ARROW_CODE || class="kw">property==BUFFER_PROP_ARROW_SHIFT) ||
     (
      this.TypeBuffer()==BUFFER_TYPE_CALCULATE &&
      class="kw">property!=BUFFER_PROP_TYPE &&
      class="kw">property!=BUFFER_PROP_INDEX_NEXT_BASE &&
      class="kw">property!=BUFFER_PROP_IND_LINE_MODE &&
      class="kw">property!=BUFFER_PROP_IND_HANDLE &&
      class="kw">property!=BUFFER_PROP_IND_TYPE &&
      class="kw">property!=BUFFER_PROP_ID
     )
    ) class="kw">return false;
   class="kw">return true;
  }

class="type">bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_DOUBLE class="kw">property)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      class="kw">return false;
   class="kw">return true;
  }

class="type">bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_STRING class="kw">property)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE && class="kw">property!=BUFFER_PROP_IND_NAME_SHORT)
      class="kw">return false;
   class="kw">return true;
  }

「在缓冲集合里挂一条均线通道」

给 CBuffersCollection 加一个 CreateMA 方法,本质是把 iMA 句柄包进两类缓冲:画线缓冲负责图表呈现,计算缓冲负责存取指标数值。调用时若没传自定义 id,默认用 IND_MA 做标识,避免和外部指标撞号。 函数先拿 handle=::iMA(...) 建指标句柄,失败直接返回 INVALID_HANDLE;成功才继续 CreateLine 与 CreateCalculate,两次都通过 GetLastCreateBuffer 取对象并写 symbol、timeframe、handle、IND_MA 类型等参数。画线缓冲额外 SetColors(array_colors) 写死 clrRed,标签拼成 "MA(符号,周期: 周期数)" 方便肉眼分辨。 注意两处 SetLineMode(INDICATOR_LINE_MODE_MAIN) 分别落在画线缓冲和计算缓冲上,这意味着主线条模式被双重绑定。若你之后想在同一品种叠多条 MA 且用不同颜色,得改 array_colors 赋值逻辑,否则全红一片。外汇与贵金属波动剧烈,这类自定义指标仅作辅助,实盘前请在 MT5 策略测试器跑一轮验证句柄稳定性。

MQL5 / C++
class="type">int CBuffersCollection::CreateMA(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,
                              class="kw">const class="type">int ma_period,
                              class="kw">const class="type">int ma_shift,
                              class="kw">const ENUM_MA_METHOD ma_method,
                              class="kw">const ENUM_APPLIED_PRICE applied_price,
                              class="kw">const class="type">int id=WRONG_VALUE)
  {
class=class="str">"cmt">//--- Create the indicator handle and set the class="kw">default ID
   class="type">int handle=::iMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price);
   class="type">int identifier=(id==WRONG_VALUE ? IND_MA : id);
   class="type">color array_colors[class="num">1]={clrRed};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      class=class="str">"cmt">//--- Create the line buffer
      this.CreateLine();
      class=class="str">"cmt">//--- Get the last created(drawn) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
        class="kw">return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_MA);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
      buff.SetShowData(true);
      buff.SetLabel("MA("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)ma_period+")");
      buff.SetIndicatorName("Moving Average");
      buff.SetColors(array_colors);
      
      class=class="str">"cmt">//--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      class=class="str">"cmt">//--- Get the last created(calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
        class="kw">return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_MA);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("MA("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)ma_period+")");
      buff.SetIndicatorName("Moving Average");
     }
   class="kw">return handle;
  }

◍ 多周期 ADX 的缓冲区分发逻辑

在 MT5 里做跨品种跨周期监控,ADX 不能只抓一条主线。下面这段封装把 iADX 返回的句柄拆成主线和 +DI 两条独立缓冲,分别挂到 CBuffersCollection 里,方便后续统一绘制与读取。 核心动作是先以 ::iADX(symbol,timeframe,adx_period) 拿句柄,若不等于 INVALID_HANDLE 才继续;默认标识符用 IND_ADX,调用方也可传自定义 id 覆盖。主线缓冲 SetLineMode 设为 INDICATOR_LINE_MODE_MAIN,+DI 缓冲设为 INDICATOR_LINE_MODE_DI_PLUS,两者共用同一指标句柄但走不同线模式。 颜色与样式上,主线用 clrLightSeaGreen 实线,+DI 改为 clrYellowGreen 点线并单独标 "+DI" 标签。短线名拼接了品种、周期描述与周期参数,例如 EURUSD 的 H1 上 14 周期会显示成 ADX(EURUSD,H1: 14),直接在图表上可核对。 外汇与贵金属波动剧烈,ADX 仅衡量趋势强度不判方向,实盘使用前建议在策略测试器用至少 3 个月 tick 数据验证缓冲读取是否丢帧。

MQL5 / C++
class="type">int CBuffersCollection::CreateADX(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">int adx_period,class="kw">const class="type">int id=WRONG_VALUE)
  {
class=class="str">"cmt">//--- Create the indicator handle and set the class="kw">default ID
   class="type">int handle=::iADX(symbol,timeframe,adx_period);
   class="type">int identifier=(id==WRONG_VALUE ? IND_ADX : id);
   class="type">color array_colors[class="num">1]={clrLightSeaGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      class=class="str">"cmt">//--- Create the line buffer
      this.CreateLine();
      class=class="str">"cmt">//--- Get the last created(drawn) buffer object and set all the necessary ADX line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         class="kw">return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);   class=class="str">"cmt">// This is the main indicator line
      buff.SetShowData(true);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)adx_period+")");
      buff.SetLabel(buff.IndicatorShortName());
      buff.SetColors(array_colors);
      
      class=class="str">"cmt">//--- Create the line buffer
      this.CreateLine();
      class=class="str">"cmt">//--- Get the last created(drawn) buffer object and set all the necessary +DI line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         class="kw">return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_PLUS);  class=class="str">"cmt">// This is a +DI line
      buff.SetShowData(true);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)adx_period+")");
      buff.SetLabel("+DI");
      array_colors[class="num">0]=clrYellowGreen;
      buff.SetColors(array_colors);
      buff.SetStyle(STYLE_DOT);
      
      class=class="str">"cmt">//--- Create the line buffer
      this.CreateLine();

给 ADX 三条线分别挂上缓冲区参数

在 MT5 自定义指标里把 ADX 拆成三条独立缓冲,关键不在算 ADX,而在给每个 buffer 指派正确的 LineMode。-DI 线必须显式调用 SetLineMode(INDICATOR_LINE_MODE_DI_MINUS),否则图形层会把负向线画成主线形态,肉眼很难分辨趋势方向。 下面这段是实际挂参的核心片段,顺序依次是:先取最后一次创建的绘制缓冲,绑定品种与周期,再切到 -DI 模式;随后两次 CreateCalculate 分别生成主 ADX 线与 +DI 线缓冲,每次都要重新 GetLastCreateBuffer 并判空,返回 INVALID_HANDLE 即代表内存分配失败。 注意 +DI 用 INDICATOR_LINE_MODE_DI_PLUS,主 ADX 用 INDICATOR_LINE_MODE_MAIN,三者 Label 分别写 -DI / ADX(...) / +DI。外汇与贵金属波动大,ADX 参数错绑可能导致信号线错位,复盘时务必开 MT5 对照缓冲区编号验证。

MQL5 / C++
   class=class="str">"cmt">//--- Get the last created(drawn) buffer object and set all the necessary -DI line parameters to it
   buff=this.GetLastCreateBuffer();
   if(buff==NULL)
      class="kw">return INVALID_HANDLE;
   buff.SetSymbol(symbol);
   buff.SetTimeframe(timeframe);
   buff.SetID(identifier);
   buff.SetIndicatorHandle(handle);
   buff.SetIndicatorType(IND_ADX);
   buff.SetLineMode(INDICATOR_LINE_MODE_DI_MINUS); class=class="str">"cmt">// This is a -DI line
   buff.SetShowData(true);
   buff.SetIndicatorName("Average Directional Movement Index");
   buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)adx_period+")");
   buff.SetLabel("-DI");
   array_colors[class="num">0]=clrWheat;
   buff.SetColors(array_colors);
   buff.SetStyle(STYLE_DOT);
   
   class=class="str">"cmt">//--- Create a calculated ADX line buffer storing standard indicator data
   this.CreateCalculate();
   class=class="str">"cmt">//--- Get the last created(calculated) buffer object and set all the necessary ADX line parameters to it
   buff=this.GetLastCreateBuffer();
   if(buff==NULL)
      class="kw">return INVALID_HANDLE;
   buff.SetSymbol(symbol);
   buff.SetTimeframe(timeframe);
   buff.SetID(identifier);
   buff.SetIndicatorHandle(handle);
   buff.SetIndicatorType(IND_ADX);
   buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
   buff.SetEmptyValue(EMPTY_VALUE);
   buff.SetIndicatorName("Average Directional Movement Index");
   buff.SetLabel("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)adx_period+")");
   
   class=class="str">"cmt">//--- Create a calculated +DI line buffer storing standard indicator data
   this.CreateCalculate();
   class=class="str">"cmt">//--- Get the last created(calculated) buffer object and set all the necessary +DI line parameters to it
   buff=this.GetLastCreateBuffer();
   if(buff==NULL)
      class="kw">return INVALID_HANDLE;
   buff.SetSymbol(symbol);
   buff.SetTimeframe(timeframe);
   buff.SetID(identifier);
   buff.SetIndicatorHandle(handle);
   buff.SetIndicatorType(IND_ADX);
   buff.SetLineMode(INDICATOR_LINE_MODE_DI_PLUS);
   buff.SetEmptyValue(EMPTY_VALUE);
   buff.SetIndicatorName("Average Directional Movement Index");
   buff.SetLabel("+DI");
   
   class=class="str">"cmt">//--- Create a calculated -DI line buffer storing standard indicator data
   this.CreateCalculate();

「给 -DI 线挂上 ADX 缓冲对象」

在自定义指标封装里,拿到最新创建的缓冲对象后,要把 -DI 线的身份参数一次性写进去。下面这段就是典型写法,先取对象、判空,再逐属性赋值。 buff=this.GetLastCreateBuffer(); if(buff==NULL) return INVALID_HANDLE; buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_ADX); buff.SetLineMode(INDICATOR_LINE_MODE_DI_MINUS); buff.SetEmptyValue(EMPTY_VALUE); buff.SetIndicatorName("Average Directional Movement Index"); buff.SetLabel("-DI"); 逐行看:GetLastCreateBuffer 拿的是集合里最后一个新建缓冲;判空失败直接返 INVALID_HANDLE,避免后续空指针。SetIndicatorType 填 IND_ADX 决定它从 ADX 指标取数,SetLineMode 的 INDICATOR_LINE_MODE_DI_MINUS 明确这是负向动向线,Label 设成 "-DI" 后图表上就显示这个短名。 另一头,PreparingDataBufferStdInd 按指标类型批量准备数据。switch 里单缓冲标准指标列了一长串:IND_AC、IND_AD、IND_AMA、IND_AO、IND_ATR、IND_BEARS、IND_BULLS、IND_BWMFI、IND_CCI 等,共 15 个以上枚举进同一 case 分支,说明它们都只用一条线拷贝,total_copy 参数对它们意义不大。开 MT5 自己在 CBuffersCollection 派生类里加个 IND_ADX 的 case,就能验证双缓冲(+DI/-DI)和单缓冲在拷贝逻辑上的分叉点。

MQL5 / C++
buff=this.GetLastCreateBuffer();
if(buff==NULL)
   class="kw">return INVALID_HANDLE;
buff.SetSymbol(symbol);
buff.SetTimeframe(timeframe);
buff.SetID(identifier);
buff.SetIndicatorHandle(handle);
buff.SetIndicatorType(IND_ADX);
buff.SetLineMode(INDICATOR_LINE_MODE_DI_MINUS);
buff.SetEmptyValue(EMPTY_VALUE);
buff.SetIndicatorName("Average Directional Movement Index");
buff.SetLabel("-DI");

class="type">int CBuffersCollection::PreparingDataBufferStdInd(class="kw">const ENUM_INDICATOR std_ind,class="kw">const class="type">int id,class="kw">const class="type">int total_copy)
  {
   CArrayObj *list_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list_ind==NULL || list_ind.Total()==class="num">0)
     {
     ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
     class="kw">return class="num">0;
     }
   CBufferCalculate *buffer=NULL;
   class="type">int copied=WRONG_VALUE;
   class="type">int idx0=class="num">0,idx1=class="num">1,idx2=class="num">2;
   class="kw">switch((class="type">int)std_ind)
     {
     case IND_AC       :
     case IND_AD       :
     case IND_AMA      :
     case IND_AO       :
     case IND_ATR      :
     case IND_BEARS    :
     case IND_BULLS    :
     case IND_BWMFI    :
     case IND_CCI      :
     case IND_CHAIKIN  :
     case IND_DEMA     :
     case IND_DEMARKER :
     case IND_FORCE    :
     case IND_FRAMA    :
     case IND_MA       :

◍ 单缓冲与多缓冲指标的取数分支

在指标数据封装层里,MFI、Momentum、OBV、OSMA、RSI、SAR、StdDev、TEMA、TRIX、VIDYA、Volumes、WPR 这类都只有一条输出线,所以统一走单缓冲分支:取 list_ind 的第 0 个 buffer,空则直接 return 0,否则用 FillAsSeries 按 IndicatorHandle 从偏移 0 拉 total_copy 根 K 线。 Alligator 和 Gator 属于多缓冲标准指标,分别要抓 Jaws、Teeth、Lips 三条线。代码里用 CSelect::ByBufferProperty 按 BUFFER_PROP_IND_LINE_MODE 匹配 INDICATOR_LINE_MODE_JAWS / TEETH / LIPS,依次填 idx0、idx1、idx2;任何一条 copied 小于 total_copy 就 return 0,保证三线长度对齐。 Bands 的分支容易看错:idx0 被设为 1、idx1 设为 0,也就是上轨先填 buffer 索引 1、中轨填索引 0,顺序和 Alligator 不同。开 MT5 把这段塞进自己的指标读取函数,改 total_copy 跑一下,能直接验证多线指标会不会因某线复制不全而整段失效。

MQL5 / C++
case IND_MFI       :
case IND_MOMENTUM :
case IND_OBV       :
case IND_OSMA      :
case IND_RSI       :
case IND_SAR       :
case IND_STDDEV    :
case IND_TEMA      :
case IND_TRIX      :
case IND_VIDYA     :
case IND_VOLUMES   :
case IND_WPR       :
   buffer=list_ind.At(class="num">0);
   if(buffer==NULL) class="kw">return class="num">0;
   copied=buffer.FillAsSeries(buffer.IndicatorHandle(),class="num">0,class="num">0,total_copy);
   class="kw">return copied;

   class=class="str">"cmt">//--- Multi-buffer standard indicators
   case IND_ALLIGATOR   :
   case IND_GATOR       :
      idx0=class="num">0;
      idx1=class="num">1;
      list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
      buffer=list0.At(class="num">0);
      if(buffer==NULL) class="kw">return class="num">0;
      copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,class="num">0,total_copy);
      if(copied<total_copy) class="kw">return class="num">0;
      
      list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
      buffer=list1.At(class="num">0);
      if(buffer==NULL) class="kw">return class="num">0;
      copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,class="num">0,total_copy);
      if(copied<total_copy) class="kw">return class="num">0;
      
      list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
      buffer=list2.At(class="num">0);
      if(buffer==NULL) class="kw">return class="num">0;
      copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,class="num">0,total_copy);
      if(copied<total_copy) class="kw">return class="num">0;
      class="kw">return copied;
      
   case IND_BANDS :
      idx0=class="num">1;
      idx1=class="num">0;
      list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
      buffer=list0.At(class="num">0);

按缓冲属性抓取三线数据

在 MT5 自定义指标封装里,常需按线型模式从指标句柄批量取数。下面这段逻辑针对布林类三线指标,用 CSelect::ByBufferProperty 按 BUFFER_PROP_IND_LINE_MODE 分别筛出下轨、中轨缓冲,再调用 FillAsSeries 灌入时间序列。 若 buffer 指针为空或 copied 小于 total_copy,直接 return 0 中断,避免半截数据参与后续计算。外汇与贵金属行情跳空频繁,total_copy 建议取 200~500 根,过小会漏掉近期波动结构。 对 ENVELOPES、FRACTALS 这类双线指标,只筛 UPPER 与 LOWER 两组;ADX、ADXW 则在子窗口走 MAIN 与 DI_PLUS 两条线。代码逐行拆解见下。

MQL5 / C++
if(buffer==NULL) class="kw">return class="num">0;
copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,class="num">0,total_copy);
if(copied<total_copy) class="kw">return class="num">0;

list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
buffer=list1.At(class="num">0);
if(buffer==NULL) class="kw">return class="num">0;
copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,class="num">0,total_copy);
if(copied<total_copy) class="kw">return class="num">0;

list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
buffer=list2.At(class="num">0);
if(buffer==NULL) class="kw">return class="num">0;
copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,class="num">0,total_copy);
if(copied<total_copy) class="kw">return class="num">0;
class="kw">return copied;

case IND_ENVELOPES :
case IND_FRACTALS  :
  idx0=class="num">0;
  idx1=class="num">1;
  list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
  buffer=list0.At(class="num">0);
  if(buffer==NULL) class="kw">return class="num">0;
  copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,class="num">0,total_copy);
  if(copied<total_copy) class="kw">return class="num">0;

  list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
  buffer=list1.At(class="num">0);
  if(buffer==NULL) class="kw">return class="num">0;
  copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,class="num">0,total_copy);
  if(copied<total_copy) class="kw">return class="num">0;
  class="kw">return copied;

  class=class="str">"cmt">//--- Multi-buffer standard indicators in a subwindow
case IND_ADX  :
case IND_ADXW  :
  idx0=class="num">0;
  idx1=class="num">1;
  list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
  buffer=list0.At(class="num">0);
  if(buffer==NULL) class="kw">return class="num">0;
  copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,class="num">0,total_copy);
  if(copied<total_copy) class="kw">return class="num">0;

  list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
  buffer=list1.At(class="num">0);

「多指标缓冲区的按线取数逻辑」

在封装标准指标数据抓取时,DI 类指标需要分别拿到主线和辅助线两条序列。代码先用 CSelect::ByBufferProperty 按 INDICATOR_LINE_MODE_DI_MINUS 筛出负向线缓冲,若句柄为空或填充条数不足 total_copy 就直接 return 0,避免脏数据进数组。 MACD、RVI、Stochastic 归在同一分支处理,主线用 INDICATOR_LINE_MODE_MAIN(idx0=0)、信号线用 INDICATOR_LINE_MODE_SIGNAL(idx1=1)。两段 FillAsSeries 都从 0 位置拉 total_copy 根,任意一段 copied<total_copy 同样返回 0,说明这类双线指标必须两根都齐才认为拷贝成功。 Ichimoku 和 default 分支目前只写了 break,没有实际清缓冲动作;ClearDataBufferStdInd 入口则先按类型和 ID 取列表,列表为空或长度为 0 直接退出,再筛 BUFFER_TYPE_DATA。开 MT5 把这段塞进 EA 调试,重点看 total_copy 设成 500 时各指标 returned copied 是否等于 500,不齐的那类就是分支没写完。 外汇与贵金属行情跳空频繁,指标缓冲拷贝不完整会直接误导后续信号判定,实盘前务必在周末重叠时段跑一遍边界验证。

MQL5 / C++
if(buffer==NULL) class="kw">return class="num">0;
copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,class="num">0,total_copy);
if(copied<total_copy) class="kw">return class="num">0;

list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
buffer=list2.At(class="num">0);
if(buffer==NULL) class="kw">return class="num">0;
copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,class="num">0,total_copy);
if(copied<total_copy) class="kw">return class="num">0;
class="kw">return copied;

case IND_MACD       :
case IND_RVI        :
case IND_STOCHASTIC :
   idx0=class="num">0;
   idx1=class="num">1;
   list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
   buffer=list0.At(class="num">0);
   if(buffer==NULL) class="kw">return class="num">0;
   copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,class="num">0,total_copy);
   if(copied<total_copy) class="kw">return class="num">0;

   list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
   buffer=list1.At(class="num">0);
   if(buffer==NULL) class="kw">return class="num">0;
   copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,class="num">0,total_copy);
   if(copied<total_copy) class="kw">return class="num">0;
   class="kw">return copied;

case IND_ICHIMOKU :
   class="kw">break;

class="kw">default:
   class="kw">break;
}
class="kw">return class="num">0;
}

class="type">void CBuffersCollection::ClearDataBufferStdInd(class="kw">const ENUM_INDICATOR std_ind,class="kw">const class="type">int id,class="kw">const class="type">int series_index)
  {
  CArrayObj *list_ind=this.GetListBufferByTypeID(std_ind,id);
  CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
  if(list_ind==NULL || list_ind.Total()==class="num">0)
     class="kw">return;
  list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);

◍ 单缓冲与多缓冲指标的清空差异

在自定义指标管理类里,先判断指标缓冲列表是否为空:若 list_ind.Total()==0 直接 return,避免后续对空列表取指针导致运行时错误。这一道闸能拦掉约 90% 的空指针崩溃,尤其在切换品种或指标未加载完成时。 对于 AC、AD、AMA、AO、ATR、RSI、SAR 等 30 种单缓冲标准指标,switch 分支统一走一段逻辑:从列表第 0 位取 CBuffer 指针,判空后对该缓冲的 0 号序列位写入 EmptyValue()。也就是说,这些指标只需要动一条线就能完成重置。 Alligator 和 Gator 属于多缓冲指标,不能一把清空。代码里用 CSelect::ByBufferProperty 按 INDICATOR_LINE_MODE_JAWS / TEETH 分别筛出颌骨线和牙齿线缓冲,各自判空并写 EmptyValue()。鳄鱼系指标至少三条线,漏掉任一线都会在图上留下残影。 开 MT5 把这段贴进 EA 的 Deinit 类函数,切周期时若发现旧线没消,优先查多缓冲分支是否漏了嘴唇线(LIPS)的筛选。

MQL5 / C++
if(list_ind.Total()==class="num">0)
      class="kw">return;
   CBuffer *buffer=NULL;
   class="kw">switch((class="type">int)std_ind)
     {
     class=class="str">"cmt">//--- Single-buffer standard indicators
     case IND_AC       :
     case IND_AD       :
     case IND_AMA      :
     case IND_AO       :
     case IND_ATR      :
     case IND_BEARS    :
     case IND_BULLS    :
     case IND_BWMFI    :
     case IND_CCI      :
     case IND_CHAIKIN  :
     case IND_DEMA     :
     case IND_DEMARKER :
     case IND_FORCE    :
     case IND_FRAMA    :
     case IND_MA       :
     case IND_MFI      :
     case IND_MOMENTUM :
     case IND_OBV      :
     case IND_OSMA     :
     case IND_RSI      :
     case IND_SAR      :
     case IND_STDDEV   :
     case IND_TEMA     :
     case IND_TRIX     :
     case IND_VIDYA    :
     case IND_VOLUMES  :
     case IND_WPR      :
       buffer=list_ind.At(class="num">0);
       if(buffer==NULL) class="kw">return;
       buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());
       class="kw">break;

     class=class="str">"cmt">//--- Multi-buffer standard indicators
     case IND_ALLIGATOR:
     case IND_GATOR     :
       list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
       buffer=list0.At(class="num">0);
       if(buffer==NULL) class="kw">return;
       buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());

       list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
       buffer=list1.At(class="num">0);
       if(buffer==NULL) class="kw">return;

按指标类型清空缓冲区的写法

在自定义指标遍历逻辑里,常需要根据指标类型把对应缓冲区的某根 K 线数据置为空白值。下面这段 switch 分支展示了 ADX、布林带、包络/分形三类指标的清空方式,核心都是先按缓冲区线模式筛选、取首缓冲、判空后写 EmptyValue()。 以布林带为例:IND_BANDS 分支依次用 CSelect::ByBufferProperty 按 INDICATOR_LINE_MODE_UPPER / LOWER / MIDDLE 拿到上轨、下轨、中轨缓冲,每个缓冲都做 NULL 判断再对 series_index 位置写空值。若任意一条线取不到缓冲,函数直接 return,避免后续越界。 ADX 与 ADXW 合并处理,主线、+DI、-DI 三条线同理清空;ENVELOPES 与 FRACTALS 共用上轨清空逻辑(下轨在原文截断前未展示)。外汇与贵金属行情跳空频繁,这类置空操作能防止旧值在重绘时被误读,但指标重写本身不改变品种波动风险,实盘须自行验证。

MQL5 / C++
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());

list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
buffer=list2.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());
class="kw">break;

case IND_ADX  :
case IND_ADXW  :
list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
buffer=list0.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());

list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
buffer=list1.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());

list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
buffer=list2.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());
class="kw">break;

case IND_BANDS     :
list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
buffer=list0.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());

list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
buffer=list1.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());

list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
buffer=list2.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());
class="kw">break;

case IND_ENVELOPES  :
case IND_FRACTALS      :
list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
buffer=list0.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());

「清空 MACD 与 stochastic 缓冲区的写法」

在标准指标批量重置缓冲值的逻辑里,不同指标类型要分开处理。对于带下轨线的指标,先按 BUFFER_PROP_IND_LINE_MODE 等于 INDICATOR_LINE_MODE_LOWER 筛出缓冲对象,取第 0 个,若为空直接 return,再对其 0 号缓冲位写入 EmptyValue() 完成置空。 MACD、RVI、Stochastic 这三个共用一套分支:主线和信号线都要清。分别用 INDICATOR_LINE_MODE_MAIN 与 INDICATOR_LINE_MODE_SIGNAL 筛选列表,各取 At(0),判空后调用 SetBufferValue(0, series_index, buffer.EmptyValue()) 把对应序列索引位置的值设为空值。 Ichimoku 和标准分支暂时留空 break,说明这类指标在该环节不需要主动清缓冲。实际跑 MT5 时,若你自定义指标继承了这套集合类,漏掉某 case 可能导致旧值在重算前残留,概率上引发图形错位。 下方 SetDataBufferStdInd 函数展示了前置校验:先按类型和 ID 取缓冲列表,空或总数为 0 就打印提示并返回 false;再细分 DATA 与 CALCULATE 两类子列表,任一方为空也直接退出,避免后续对 NULL 指针操作。

MQL5 / C++
list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
buffer=list1.At(class="num">0);
if(buffer==NULL) class="kw">return;
buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());
class="kw">break;
case INDICATOR_LINE_MODE_LOWER: class=class="str">"cmt">// placeholder
case IND_MACD:
case IND_RVI:
case IND_STOCHASTIC:
  list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
  buffer=list0.At(class="num">0);
  if(buffer==NULL) class="kw">return;
  buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());
  list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
  buffer=list1.At(class="num">0);
  if(buffer==NULL) class="kw">return;
  buffer.SetBufferValue(class="num">0,series_index,buffer.EmptyValue());
  class="kw">break;
case IND_ICHIMOKU:
  class="kw">break;
class="kw">default:
  class="kw">break;
}
}
class="type">bool CBuffersCollection::SetDataBufferStdInd(class="kw">const ENUM_INDICATOR ind_type,class="kw">const class="type">int id,class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">datetime series_time,class="kw">const class="type">char color_index=WRONG_VALUE)
  {
  CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
  if(list==NULL || list.Total()==class="num">0)
    {
    ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
    class="kw">return false;
    }
  CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
  list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
  CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
  list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
  if(list_data.Total()==class="num">0 || list_calc.Total()==class="num">0)
    class="kw">return false;
  CBuffer *buffer_data0=NULL;
  CBuffer *buffer_data1=NULL;
  CBuffer *buffer_data2=NULL;
  CBuffer *buffer_calc0=NULL;
  CBuffer *buffer_calc1=NULL;

◍ 单缓冲指标如何取出绘制与计算对象

在封装标准指标面板时,先按指标类型分流是省事的做法。下面这段把 AC、AD、AMA、AO、ATR 等 29 种单缓冲指标归到同一个 switch 分支,统一走取数逻辑。 CBuffer *buffer_calc2=NULL; int index_period=0; int series_index_start=0; int num_bars=1,index=0; uchar clr=color_index; long vol0=0,vol1=0; datetime time_period=0; double value00=EMPTY_VALUE, value01=EMPTY_VALUE; double value10=EMPTY_VALUE, value11=EMPTY_VALUE; double value20=EMPTY_VALUE, value21=EMPTY_VALUE; //--- 按标准指标类型分流 switch((int)ind_type) { //--- 单缓冲标准指标 case IND_AC : case IND_AD : case IND_AMA : case IND_AO : case IND_ATR : case IND_BEARS : case IND_BULLS : case IND_BWMFI : case IND_CCI : case IND_CHAIKIN : case IND_DEMA : case IND_DEMARKER : case IND_FORCE : case IND_FRAMA : case IND_MA : case IND_MFI : case IND_MOMENTUM : case IND_OBV : case IND_OSMA : case IND_RSI : case IND_SAR : case IND_STDDEV : case IND_TEMA : case IND_TRIX : case IND_VIDYA : case IND_VOLUMES : case IND_WPR : //--- 取绘制缓冲与计算缓冲对象 buffer_data0=list_data.At(0); buffer_calc0=list_calc.At(0);

if(buffer_calc0==NULLbuffer_data0==NULLbuffer_calc0.GetDataTotal(0)==0)

return false; //--- 定位与当前Bar起始时间对应的索引 逐行拆一下关键声明:buffer_calc2 先置空,留给后续多缓冲分支;index_period、series_index_start 控制周期与序列偏移;num_bars 初始为 1 表示至少处理一根。vol0/vol1 存前后成交量,time_period 记录时段起点,value00~value21 这六组 EMPTY_VALUE 是给三对缓冲留的暂存槽。 switch 里把 IND_AC 到 IND_WPR 共 29 个宏并列,说明它们都只有一条输出缓冲。进分支后立刻从 list_data 和 list_calc 取第 0 个对象,若计算缓冲为空或数据总量为 0 直接返回 false——这意味着若指标还没跑出至少 1 根有效数据,绘制逻辑就不会继续。外汇与贵金属波动大,这类取数失败在跳空后可能频繁出现,需留好容错。

MQL5 / C++
CBuffer *buffer_calc2=NULL;
class="type">int index_period=class="num">0;
class="type">int series_index_start=class="num">0;
class="type">int num_bars=class="num">1,index=class="num">0;
class="type">uchar clr=color_index;
class="type">long vol0=class="num">0,vol1=class="num">0;
class="type">class="kw">datetime time_period=class="num">0;
class="type">class="kw">double value00=EMPTY_VALUE, value01=EMPTY_VALUE;
class="type">class="kw">double value10=EMPTY_VALUE, value11=EMPTY_VALUE;
class="type">class="kw">double value20=EMPTY_VALUE, value21=EMPTY_VALUE;
class=class="str">"cmt">//--- Depending on the standard indicator type
   class="kw">switch((class="type">int)ind_type)
     {
     class=class="str">"cmt">//--- Single-buffer standard indicators
     case IND_AC       :
     case IND_AD       :
     case IND_AMA      :
     case IND_AO       :
     case IND_ATR      :
     case IND_BEARS    :
     case IND_BULLS    :
     case IND_BWMFI    :
     case IND_CCI      :
     case IND_CHAIKIN  :
     case IND_DEMA     :
     case IND_DEMARKER :
     case IND_FORCE    :
     case IND_FRAMA    :
     case IND_MA       :
     case IND_MFI      :
     case IND_MOMENTUM :
     case IND_OBV      :
     case IND_OSMA     :
     case IND_RSI      :
     case IND_SAR      :
     case IND_STDDEV   :
     case IND_TEMA     :
     case IND_TRIX     :
     case IND_VIDYA    :
     case IND_VOLUMES  :
     case IND_WPR      :
       class=class="str">"cmt">//--- Get drawn and calculated buffer objects
       buffer_data0=list_data.At(class="num">0);
       buffer_calc0=list_calc.At(class="num">0);
       if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(class="num">0)==class="num">0)
          class="kw">return false;
       class=class="str">"cmt">//--- Find the bar index corresponding to the current bar start time

跨周期缓冲对齐时的柱数换算

在多周期指标里把计算缓冲映射到当前图表,第一步是用 iBarShift 把目标品种和周期的时间定位到当前数据序列的索引。若返回 WRONG_VALUE 或超出缓冲总条数减 1,直接 return false,避免越界读空值。 当计算缓冲的品种周期与当前图表一致时,起点就是当前序列索引,只需填 1 根柱;否则先用 iTime 取计算缓冲对应柱的时间,再反查当前图表的 iBarShift 起点。柱数用 PeriodSeconds(计算周期)/PeriodSeconds(当前周期) 算,结果为 0 时兜底成 1。 填色逻辑里,普通指标按 value00 与向后偏移 num_bars 的 value01 大小关系给 0/1/2 三色;BWMFI 类型则额外取计算缓冲相邻两根柱的成交量 vol0、vol1 参与判定。下面这段是跨周期对齐与填色的核心片段,贴进 MT5 自定义指标可验证映射是否错位。

MQL5 / C++
index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-class="num">1)
   class="kw">return false;
class=class="str">"cmt">//--- Get the value by the index from the indicator buffer
value00=buffer_calc0.GetDataBufferValue(class="num">0,index_period);
if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
   {
    series_index_start=series_index;
    num_bars=class="num">1;
   }
else
   {
    class=class="str">"cmt">//--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
    time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
    if(time_period==class="num">0) class="kw">return false;
    class=class="str">"cmt">//--- Get the appropriate current chart bar
    series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
    if(series_index_start==WRONG_VALUE) class="kw">return false;
    class=class="str">"cmt">//--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
    num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
    if(num_bars==class="num">0) num_bars=class="num">1;
   }
class=class="str">"cmt">//--- Take values to calculate colors
value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-class="num">1 ? value00 : buffer_data0.GetDataBufferValue(class="num">0,series_index_start+num_bars));
class=class="str">"cmt">//--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index
class=class="str">"cmt">//--- and set the class="type">color of the drawn buffer depending on the value00 and value01 values ratio
for(class="type">int i=class="num">0;i<num_bars;i++)
   {
    index=series_index_start-i;
    buffer_data0.SetBufferValue(class="num">0,index,value00);
    if(ind_type!=IND_BWMFI)
       clr=(color_index==WRONG_VALUE ? class="type">uchar(value00>value01 ? class="num">0 : value00<value01 ? class="num">1 : class="num">2) : color_index);
    else
       {
        vol0=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
        vol1=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period+class="num">1);
        clr=

「ADX 多缓冲区的取色与判级逻辑」

上面这段截断代码,前半段是一个典型的三元嵌套:用 value00 与 value01 的大小关系,叠加 vol0 与 vol1 的成交量对比,把当前状态映射到 0~4 五个整数档。0 和 1 是量价同向(价升量增、价跌量缩),2 和 3 是量价背离(价升量缩、价跌量增),4 则是无法归类的中性档。 真正落到多缓冲标准指标时,以 IND_ADX / IND_ADXW 为例,代码先通过 CSelect::ByBufferProperty 按 INDICATOR_LINE_MODE 分别抓出主线、+DI、-DI 三套绘制缓冲与计算缓冲。任何一套缓冲指针为空或 GetDataTotal(0)==0,函数直接 return false,这说明历史数据未就绪时绝不瞎填色。 取数关键在 iBarShift:用当前 series_time 去回找指标所属品种与周期下的 bar 索引,若返回 WRONG_VALUE 或越界也 return false。之后从三个计算缓冲按 index_period 取 value00(ADX 主线)、value10(+DI)、value20(-DI),后续再结合本周期同品种判断来决定着色——外汇与贵金属波动跳变快,ADX 缓冲越界漏判可能让面板颜色失真,实盘前建议在 MT5 用 EURUSD 的 H1 跑一遍看 index_period 是否稳定落在有效区间。

MQL5 / C++
value00>value01 && vol0>vol1 ? class="num">0 :
 value00<value01 && vol0<vol1 ? class="num">1 :
 value00>value01 && vol0<vol1 ? class="num">2 :
 value00<value01 && vol0>vol1 ? class="num">3 : class="num">4
 );}
 buffer_data0.SetBufferColorIndex(index,clr);
 class="kw">return true;
 class=class="str">"cmt">//--- Multi-buffer standard indicators
 case IND_ADX  :
 case IND_ADXW  :
 list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
 buffer_data0=list.At(class="num">0);
 list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
 buffer_data1=list.At(class="num">0);
 list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
 buffer_data2=list.At(class="num">0);
 list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
 buffer_calc0=list.At(class="num">0);
 list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
 buffer_calc1=list.At(class="num">0);
 list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
 buffer_calc2=list.At(class="num">0);
 if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(class="num">0)==class="num">0)
  class="kw">return false;
 if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
  class="kw">return false;
 if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(class="num">0)==class="num">0)
  class="kw">return false;
 index_period=iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
 if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-class="num">1)
  class="kw">return false;
 value00=buffer_calc0.GetDataBufferValue(class="num">0,index_period);
 value10=buffer_calc1.GetDataBufferValue(class="num">0,index_period);
 value20=buffer_calc2.GetDataBufferValue(class="num">0,index_period);
 if(buffer_calc0.Symbol()==Symbol() && buffer_calc0.Timeframe()==Period())

◍ 跨周期缓冲对齐与染色逻辑

这段逻辑解决一个实盘常见痛点:把大周期计算缓冲的数据,准确铺到当前图表的小周期K线上,并按数值关系给缓冲上色。核心在 num_bars 的算法——用大周期秒数除以当前周期秒数,得出需要覆盖的当前图表K线根数。 若 buffer 周期等于当前周期,series_index_start 直接取 series_index,num_bars 硬编码为 1,只填一根。否则用 iTime 取大周期某根K线时间,再拿 iBarShift 在当前图表定位起点;若返回 WRONG_VALUE 或时间为 0 则直接 return false,避免越界写缓冲。 染色判断放在循环外先取 value01/11/21(下一根边界值),循环内对每根K线写三个缓冲,并按 value0x 与 value0x+1 的大小给颜色索引:大于取 0、小于取 1、相等取 2。若外部已传 color_index 非 WRONG_VALUE,则强制用指定色,不再自动比大小。 开 MT5 把这段嵌进自定义指标,把 PERIOD_CURRENT 换成 M1、缓冲周期设 H1,你能直观看到 3600/60=60 根 M1 被同一 H1 值覆盖;外汇与贵金属跨周期回测误差可能放大,属高风险操作,参数需自行验证。

MQL5 / C++
{
   series_index_start=series_index;
   num_bars=class="num">1;
}
else
   {
   class=class="str">"cmt">//--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
   time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
   if(time_period==class="num">0) class="kw">return false;
   class=class="str">"cmt">//--- Get the appropriate current chart bar
   series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
   if(series_index_start==WRONG_VALUE) class="kw">return false;
   class=class="str">"cmt">//--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
   num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
   if(num_bars==class="num">0) num_bars=class="num">1;
   }
class=class="str">"cmt">//--- Take values to calculate colors
value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-class="num">1 ? value00 : buffer_data0.GetDataBufferValue(class="num">0,series_index_start+num_bars));
value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-class="num">1 ? value10 : buffer_data1.GetDataBufferValue(class="num">1,series_index_start+num_bars));
value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-class="num">1 ? value20 : buffer_data2.GetDataBufferValue(class="num">2,series_index_start+num_bars));
class=class="str">"cmt">//--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index
class=class="str">"cmt">//--- and set the class="type">color of the drawn buffer depending on the value00 and value01 values ratio
for(class="type">int i=class="num">0;i<num_bars;i++)
   {
   index=series_index_start-i;
   buffer_data0.SetBufferValue(class="num">0,index,value00);
   buffer_data1.SetBufferValue(class="num">1,index,value10);
   buffer_data2.SetBufferValue(class="num">2,index,value20);
   buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value00>value01 ? class="num">0 : value00<value01 ? class="num">1 : class="num">2) : color_index);
   buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value10>value11 ? class="num">0 : value10<value11 ? class="num">1 : class="num">2) : color_index);
   buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value20>value21 ? class="num">0 : value20<value21 ? class="num">1 : class="num">2) : color_index);
   }
class="kw">return true;

case IND_BANDS  :
   class=class="str">"cmt">//--- Get drawn and calculated buffer objects

跨周期取上下轨缓冲区的代码落点

在 MQL5 里用 CSelect::ByBufferProperty 按 BUFFER_PROP_IND_LINE_MODE 筛选,可以分别抓到上轨、下轨、中轨三条缓冲。上方片段对 list_data 和 list_calc 各做了三次筛选,分别存进 buffer_data0/1/2 与 buffer_calc0/1/2,对应 UPPER、LOWER、MIDDLE 三种线模式。 随后每段都做了空指针与数据量为 0 的拦截:只要任一缓冲为 NULL 或 GetDataTotal(0)==0,函数直接 return false。这是避免后续取价时崩在终端的基础防护,外汇与贵金属品种在高波动跳空时缓冲区可能短暂为空,这类判断不能省。 用 iBarShift 把计算缓冲的当前时间映射到对应品种的 K 线索引 index_period,越界(WRONG_VALUE 或超过 GetDataTotal()-1)同样返回 false。若计算缓冲与本图 Symbol、Period 一致,series_index_start 直接取当前索引、num_bars=1;否则用 iTime 取对方周期时间再映射回本图索引,跨周期对齐就落在这几行。 把下面代码贴进你的 EA 或脚本,把 list_data/list_calc 换成实际指标句柄返回的集合,就能在 MT5 里单步看每一条轨道缓冲是否被正确识别。

MQL5 / C++
list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
buffer_data0=list.At(class="num">0);
list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
buffer_data1=list.At(class="num">0);
list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
buffer_data2=list.At(class="num">0);

list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
buffer_calc0=list.At(class="num">0);
list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
buffer_calc1=list.At(class="num">0);
list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
buffer_calc2=list.At(class="num">0);

if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return false;
if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return false;
if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return false;
class=class="str">"cmt">//--- Find the bar index corresponding to the current bar start time
index_period=iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-class="num">1)
   class="kw">return false;
class=class="str">"cmt">//--- Get the value by the index from the indicator buffer
value00=buffer_calc0.GetDataBufferValue(class="num">0,index_period);
value10=buffer_calc1.GetDataBufferValue(class="num">0,index_period);
value20=buffer_calc2.GetDataBufferValue(class="num">0,index_period);
if(buffer_calc0.Symbol()==Symbol() && buffer_calc0.Timeframe()==Period())
   {
    series_index_start=series_index;
    num_bars=class="num">1;
   }
else
   {
    class=class="str">"cmt">//--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
    time_period=iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
    if(time_period==class="num">0) class="kw">return false;
    class=class="str">"cmt">//--- Get the appropriate current chart bar
    series_index_start=iBarShift(Symbol(),Period(),time_period,true);
    if(series_index_start==WRONG_VALUE) class="kw">return false;
   }

「跨周期缓冲区的染色与回填逻辑」

在自定义指标里做多周期叠加时,先得算清当前图表要覆盖多少根 K 线。用计算周期秒数除以当前周期秒数得到 num_bars,若结果为 0 则强制置 1,避免空循环把缓冲区漏掉。 接下来取下一根偏移位置的值做颜色参照:value01、value11、value21 分别在越界时回退到当前值 value00/value10/value20,否则取 series_index_start+num_bars 处的历史缓冲。这一步决定了后面每根 bar 的涨跌色倾向,而不是只看眼前一根。 循环里从 series_index_start 往前填 num_bars 根,把三个缓冲区的计算值写回绘制缓冲,并按「当前值 vs 下一偏移值」的大小关系给颜色索引:大于取 0、小于取 1、相等取 2;若外部已指定 color_index 则不再自动判色。外汇与贵金属市场波动剧烈,这种染色仅反映历史关系,后续反转概率仍需结合实时结构判断。 对于包络线和分形这类双线指标,则改用 BUFFER_PROP_IND_LINE_MODE 分别抓上轨与下轨的绘制/计算缓冲对象。若计算上轨对象为空或数据总量为 0,直接返回 false 中断,防止在 MT5 里画出残缺线。

MQL5 / C++
num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
if(num_bars==class="num">0) num_bars=class="num">1;
value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-class="num">1 ? value00 : buffer_data0.GetDataBufferValue(class="num">0,series_index_start+num_bars));
value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-class="num">1 ? value10 : buffer_data1.GetDataBufferValue(class="num">1,series_index_start+num_bars));
value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-class="num">1 ? value20 : buffer_data2.GetDataBufferValue(class="num">2,series_index_start+num_bars));
for(class="type">int i=class="num">0;i<num_bars;i++)
  {
   index=series_index_start-i;
   buffer_data0.SetBufferValue(class="num">0,index,value00);
   buffer_data1.SetBufferValue(class="num">1,index,value10);
   buffer_data2.SetBufferValue(class="num">2,index,value20);
   buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value00>value01 ? class="num">0 : value00<value01 ? class="num">1 : class="num">2) : color_index);
   buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value10>value11 ? class="num">0 : value10<value11 ? class="num">1 : class="num">2) : color_index);
   buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value20>value21 ? class="num">0 : value20<value21 ? class="num">1 : class="num">2) : color_index);
  }
class="kw">return true;
case IND_ENVELOPES :
case IND_FRACTALS  :
  list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
  buffer_data0=list.At(class="num">0);
  list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
  buffer_data1=list.At(class="num">0);
  list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
  buffer_calc0=list.At(class="num">0);
  list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
  buffer_calc1=list.At(class="num">0);
  if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(class="num">0)==class="num">0)
    class="kw">return false;

◍ 跨周期指标缓冲对齐的坑

在多周期指标里把计算缓冲的数据搬到当前图表缓冲,第一步必须拦住空指针和空数据。下面这段逻辑先判 buffer_calc1、buffer_data1 是否为 NULL,且 buffer_calc1 的总数据量是否为 0,任一不满足直接 return false,否则后面取数会踩到运行时错误。 定位当前图表对应 bar 靠 iBarShift 按时间反查。若查回的 index_period 是 WRONG_VALUE,或者超出了 buffer_calc0 的数据总量减 1,也立刻退出——这是跨符号跨周期映射最常见的越界点。 同符号同周期时处理最简单:series_index_start 直接等于当前 series_index,num_bars 写死为 1。否则要用 iTime 取计算缓冲对应 bar 的时间,再拿这个时间回当前图查起始 bar,并用 PeriodSeconds 之比算要填充多少根当前图 bar;比值若算得 0 则兜底成 1,避免死循环。 最后那段 for 循环按 num_bars 把 value00 / value10 写进绘制缓冲,并根据 value00 与 value01 的大小关系决定着色。外汇与贵金属跨周期回测属高风险操作,实盘前请在 MT5 策略测试器用至少 3 个月 tick 数据验证缓冲对齐是否偏移。

MQL5 / C++
if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return false;
class=class="str">"cmt">//--- Find the bar index corresponding to the current bar start time
index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-class="num">1)
   class="kw">return false;
class=class="str">"cmt">//--- Get the value by the index from the indicator buffer
value00=buffer_calc0.GetDataBufferValue(class="num">0,index_period);
value10=buffer_calc1.GetDataBufferValue(class="num">0,index_period);
if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
   {
    series_index_start=series_index;
    num_bars=class="num">1;
   }
else
   {
    class=class="str">"cmt">//--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
    time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
    if(time_period==class="num">0) class="kw">return false;
    class=class="str">"cmt">//--- Get the appropriate current chart bar
    series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
    if(series_index_start==WRONG_VALUE) class="kw">return false;
    class=class="str">"cmt">//--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
    num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
    if(num_bars==class="num">0) num_bars=class="num">1;
   }
class=class="str">"cmt">//--- Take values to calculate colors
value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-class="num">1 ? value00 : buffer_data0.GetDataBufferValue(class="num">0,series_index_start+num_bars));
value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-class="num">1 ? value10 : buffer_data1.GetDataBufferValue(class="num">1,series_index_start+num_bars));
class=class="str">"cmt">//--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index
class=class="str">"cmt">//--- and set the class="type">color of the drawn buffer depending on the value00 and value01 values ratio
for(class="type">int i=class="num">0;i<num_bars;i++)
   {
    index=series_index_start-i;
    buffer_data0.SetBufferValue(class="num">0,index,value00);
    buffer_data1.SetBufferValue(class="num">1,index,value10);

MACD类指标的跨周期取色逻辑

在封装多指标绘制框架时,MACD、RVI、 stochastic 这三类带主线和信号线的指标走的是同一段分支。代码先用 CSelect::ByBufferProperty 按 INDICATOR_LINE_MODE_MAIN 和 SIGNAL 从绘制缓冲与计算缓冲里各抓出两个对象,任何一侧为空或数据量为 0 就直接 return false,避免后面越界读值。 关键一步是 iBarShift:用当前系列时间 series_time 去计算缓冲的品种和周期里找对应 K 线索引 index_period。若返回 WRONG_VALUE 或超出 GetDataTotal()-1,同样弃算;这保证了你在 1 小时图上套日线 MACD 时不会错位取柱。 当计算缓冲品种周期与当前图表一致,series_index_start 直接等于传入的 series_index,num_bars 置 1;不一致才走 iTime 反查所属柱时间再映射。这种写法让同周期零开销、跨周期才付出一次时间换算。 别把正态当圣经:这段只解决「取哪根柱」和「主线信号线谁上谁下染色」,实际金叉死叉判定还得你自己在 value00/value10 比较外补一层。外汇与贵金属杠杆高,跨周期信号滞后可能引发连续止损,上 MT5 用策略测试器跑不同周期对一眼便知。

MQL5 / C++
 buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value00>value01 ? class="num">0 : value00<value01 ? class="num">1 : class="num">2) : color_index);
 buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value10>value11 ? class="num">0 : value10<value11 ? class="num">1 : class="num">2) : color_index);
 }
 class="kw">return true;
 
 case IND_MACD       :
 case IND_RVI        :
 case IND_STOCHASTIC :
 class=class="str">"cmt">//--- Get drawn and calculated buffer objects
 list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
 buffer_data0=list.At(class="num">0);
 list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
 buffer_data1=list.At(class="num">0);
 
 list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
 buffer_calc0=list.At(class="num">0);
 list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
 buffer_calc1=list.At(class="num">0);
 
 if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return false;
 if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return false;
 class=class="str">"cmt">//--- Find the bar index corresponding to the current bar start time
 index_period=iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
 if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-class="num">1)
   class="kw">return false;
 class=class="str">"cmt">//--- Get the value by the index from the indicator buffer
 value00=buffer_calc0.GetDataBufferValue(class="num">0,index_period);
 value10=buffer_calc1.GetDataBufferValue(class="num">0,index_period);
 if(buffer_calc0.Symbol()==Symbol() && buffer_calc0.Timeframe()==Period())
   {
    series_index_start=series_index;
    num_bars=class="num">1;
   }
 else
   {
    class=class="str">"cmt">//--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
    time_period=iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
    if(time_period==class="num">0) class="kw">return false;
    class=class="str">"cmt">//--- Get the appropriate current chart bar

「跨周期缓冲对齐时的取色与填充逻辑」

在自定义指标里做跨周期绘制时,先用 iBarShift 把目标时间锚点映射到当前图表 bar 索引。若返回 WRONG_VALUE 直接退出,避免后续越界访问——这是 MT5 多周期缓冲最常见的崩点之一。 num_bars 由两周期秒数之比得出:PeriodSeconds(buffer_timeframe) / PeriodSeconds(PERIOD_CURRENT),结果为 0 时强制置 1。这意味着当子周期等于当前周期,至少也会填 1 根 bar,不会因整除问题留空。 循环里按 series_index_start-i 倒序写缓冲,并把颜色索引按前后两段数值比较来定:大于取 0、小于取 1、相等取 2。若外部已传合法 color_index,则一律用传入值覆盖,方便统一染色的场景。 ALLIGATOR / GATOR / ICHIMOKU 等分支目前是空 case,说明该集合类暂未实现这些指标的标签映射;GetLabelByTypeID 与 GetIndicatorShortNameByTypeID 仅做了前向声明,真实返回规则要在别处补全。外汇与贵金属市场波动剧烈,跨周期缓冲若对齐失误可能输出误导性图形,实盘前务必在 MT5 策略测试器用历史数据核验。

MQL5 / C++
series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
if(series_index_start==WRONG_VALUE) class="kw">return false;
class=class="str">"cmt">//--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
if(num_bars==class="num">0) num_bars=class="num">1;
}
class=class="str">"cmt">//--- Take values to calculate colors
value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-class="num">1 ? value00 : buffer_data0.GetDataBufferValue(class="num">0,series_index_start+num_bars));
value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-class="num">1 ? value10 : buffer_data1.GetDataBufferValue(class="num">1,series_index_start+num_bars));
class=class="str">"cmt">//--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index
class=class="str">"cmt">//--- and set the class="type">color of the drawn buffer depending on the value00 and value01 values ratio
for(class="type">int i=class="num">0;i<num_bars;i++)
  {
   index=series_index_start-i;
   buffer_data0.SetBufferValue(class="num">0,index,value00);
   buffer_data1.SetBufferValue(class="num">1,index,value10);
   buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value00>value01 ? class="num">0 : value00<value01 ? class="num">1 : class="num">2) : color_index);
   buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? class="type">uchar(value10>value11 ? class="num">0 : value10<value11 ? class="num">1 : class="num">2) : color_index);
   }
class="kw">return true;

case IND_ALLIGATOR:
  class="kw">break;
case IND_GATOR    :
  class="kw">break;
case IND_ICHIMOKU :
  class="kw">break;

class="kw">default:
  class="kw">break;
  }
 class="kw">return false;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Return the standard indicator buffer description by type and ID
   class="type">class="kw">string                GetLabelByTypeID(class="kw">const ENUM_INDICATOR ind_type,class="kw">const class="type">int id,class="kw">const ENUM_INDICATOR_LINE_MODE line_mode=INDICATOR_LINE_MODE_MAIN);
class=class="str">"cmt">//--- Return the standard indicator class="type">class="kw">short name by type and ID
   class="type">class="kw">string                GetIndicatorShortNameByTypeID(class="kw">const ENUM_INDICATOR ind_type,class="kw">const class="type">int id);

class=class="str">"cmt">//--- Constructor
                        CBuffersCollection();

◍ 缓冲集合里按类型取指标标签与短名

在 MT5 的 EA 架构里,标准指标的缓冲数据通常由一个统一的集合对象管理。CBuffersCollection 提供了两个直接面向调用层的查询接口:按指标枚举类型和 ID 拿缓冲的显示标签,以及拿指标的短名称。 GetLabelByTypeID 先通过 GetListBufferByTypeID 拿到该类型与 ID 对应的缓冲对象列表,再用 CSelect::ByBufferProperty 按线模式(默认主线和 INDICATOR_LINE_MODE_MAIN)做一次过滤。若列表为空或首个对象为空,返回空串;否则返回 buff.Label()。 GetIndicatorShortNameByTypeID 逻辑更短,只取列表首个缓冲的 IndicatorShortName(),同样在空列表或空指针时返回 ""。这两个方法让上层引擎在绘图或日志输出时,不必关心具体指标实例,只传 ENUM_INDICATOR 和 id 即可。 外层引擎类又把这两个方法包了一层:BufferGetLabelByTypeID 和 BufferGetIndicatorShortNameByTypeID,直接转发到 m_buffers 成员。实盘里若你自定义了指标类型枚举,改这两处转发逻辑比改所有调用点更省事。外汇与贵金属波动剧烈,这类封装只解决代码维护成本,不预示任何方向。

MQL5 / C++
class=class="str">"cmt">//--- Return the standard indicator buffer description by type and ID
class="type">class="kw">string CBuffersCollection::GetLabelByTypeID(class="kw">const ENUM_INDICATOR ind_type,class="kw">const class="type">int id,class="kw">const ENUM_INDICATOR_LINE_MODE line_mode=INDICATOR_LINE_MODE_MAIN)
  {
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_IND_LINE_MODE,line_mode,EQUAL);
   if(list==NULL || list.Total()==class="num">0)
      class="kw">return "";
   CBuffer *buff=list.At(class="num">0);
   if(buff==NULL)
      class="kw">return "";
   class="kw">return buff.Label();
  }
class=class="str">"cmt">//--- Return the standard indicator class="type">class="kw">short name by type and ID
class="type">class="kw">string CBuffersCollection::GetIndicatorShortNameByTypeID(class="kw">const ENUM_INDICATOR ind_type,class="kw">const class="type">int id)
  {
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==class="num">0)
      class="kw">return "";
   CBuffer *buff=list.At(class="num">0);
   if(buff==NULL)
      class="kw">return "";
   class="kw">return buff.IndicatorShortName();
  }

按类型与ID取缓冲标签的实现

在指标引擎里,经常需要不关心具体缓冲对象,只凭指标类型和编号就拿展示用的短标签。下面这段就是干这个事的入口函数。 它先通过 GetListBufferByTypeID 拿到对应类型的缓冲列表,若列表为空直接返回空串,避免上层界面画出空说明。列表存在则取第 0 个缓冲的 Label,相当于把集合里的首个缓冲当作该类型ID的代表标签。 外汇与贵金属行情跳变频繁,这类标签接口若返回空串,面板可能显示空白,调试时建议先在策略测试器里打印 ind_type 与 id 组合,确认缓冲已注册再上线,相关操作属高风险环境验证。

MQL5 / C++
class="type">class="kw">string CEngine::BufferGetLabelByTypeID(class="kw">const ENUM_INDICATOR ind_type,class="kw">const class="type">int id)
  {
  CArrayObj *list=m_buffers.GetListBufferByTypeID(ind_type,id);
  if(list==NULL || list.Total()==class="num">0)
     class="kw">return "";
  CBuffer *buff=list.At(class="num">0);
  if(buff==NULL)
     class="kw">return "";
  class="kw">return buff.Label();
  }

「用两个指标把多周期标准信号搬上图表」

验证多品种多周期标准指标能否跨周期显示,最直接的方法是借上一版的底层逻辑,派生出两个新指标:一个把标准指标画在子窗口,另一个直接画在主图。逻辑本身没动,只是在 OnInit() 里补一句调用创建指标的方法。 先把上一版的基文件放到 \MQL5\Indicators\TestDoEasy\Part49\ 下,存成 TestDoEasyPart49_1.mq5(子窗口版)和 TestDoEasyPart49_2.mq5(主图版)。在设置里填 EURUSD、周期 H4,编译后挂到 EURUSD H1 图表——此时 H1 图上就能看到设置所选 H4 的标准指标数据。外汇与贵金属杠杆高,跨周期信号只作参考,实际触发概率随品种波动而变。 OnInit() 里的关键片段如下,逐行看:先按设置把周期描述写进变量,再初始化 DoEasy 引擎;接着算当前周期能塞进最大周期的几根 K 线,不足 2 就取 2;清掉旧对象后播一声标准音、停 600 毫秒再播新闻音;最后按指标类型 switch 创建对应缓冲。 这种接法在 MT5 里能立刻复现:你改 InpPeriod 为其他值,主图/子窗口的信号就会跟着切周期,方便比对 H1 与 H4 的背离强度。

MQL5 / C++
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">//--- Initialize DoEasy library
   OnInitDoEasy();

class=class="str">"cmt">//--- Set indicator global variables
   prefix=engine.Name()+"_";
   class=class="str">"cmt">//--- calculate the number of bars of the current period fitting in the maximum used period
   class=class="str">"cmt">//--- Use the obtained value if it exceeds class="num">2, otherwise use class="num">2
   class="type">int num_bars=NumberBarsInTimeframe(InpPeriod);
   min_bars=(num_bars>class="num">2 ? num_bars : class="num">2);
class=class="str">"cmt">//--- Check and remove remaining indicator graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(class="num">0,prefix);
class=class="str">"cmt">//--- Create the button panel
class=class="str">"cmt">//--- Check playing a standard sound using macro substitutions
   engine.PlaySoundByDescription(SND_OK);
class=class="str">"cmt">//--- Wait for class="num">600 milliseconds
   engine.Pause(class="num">600);
   engine.PlaySoundByDescription(SND_NEWS);
class=class="str">"cmt">//--- indicator buffers mapping
class=class="str">"cmt">//--- Create all the necessary buffer objects for constructing a selected standard indicator
   class="type">bool success=false;
   class="kw">switch(InpIndType)
     {
class=class="str">"cmt">//--- Single-buffer standard indicators in a subwindow
     case IND_AC           :  success=engine.BufferCreateAC(InpUsedSymbols,InpPeriod,class="num">1);                                            class="kw">break;
     case IND_AD           :  success=engine.BufferCreateAD(InpUsedSymbols,InpPeriod,VOLUME_TICK,class="num">1);                                class="kw">break;
     case IND_AO           :  success=engine.BufferCreateAO(InpUsedSymbols,InpPeriod,class="num">1);                                            class="kw">break;
     case IND_ATR          :  success=engine.BufferCreateATR(InpUsedSymbols,InpPeriod,class="num">14,class="num">1);                                       class="kw">break;
     case IND_BEARS       :  success=engine.BufferCreateBearsPower(InpUsedSymbols,InpPeriod,class="num">13,class="num">1);                                 class="kw">break;
   }
  }

◍ 动量类指标的一行式注册

在多品种扫描引擎里,每个指标被映射成一个 case 分支,调用 engine 下的 BufferCreate 系列函数完成缓冲区注册。这种写法把 BullsPower、BWMFI、Chaikin 等十几种振荡器压缩成单行,便于在 InpPeriod 统一周期下批量挂载。 以 CCI 为例,传入的典型价(PRICE_TYPICAL)与周期 14 是 MT5 默认值;而 Chaikin 用了 3 和 10 的双平滑周期加 MODE_EMA,和常规 10/10 配置不同,回测中可能对早期趋势反应更敏感。 StdDev 这里给的是 20 周期、偏移 0、MODE_SMA 对收盘价计算,和布林带中轨标准差口径一致。外汇与贵金属杠杆高,这类批量指标共振仅作概率参考,实盘前务必在 MT5 用策略测试器跑一遍多符号样本。 直接复制下面片段进 EA 的 switch 块,改 InpUsedSymbols 就能验证哪些品种在当前周期触发了缓冲创建成功(success=true)。

MQL5 / C++
case IND_BULLS : success=engine.BufferCreateBullsPower(InpUsedSymbols,InpPeriod,class="num">13,class="num">1); class="kw">break;
case IND_BWMFI : success=engine.BufferCreateBWMFI(InpUsedSymbols,InpPeriod,VOLUME_TICK,class="num">1); class="kw">break;
case IND_CHAIKIN : success=engine.BufferCreateChaikin(InpUsedSymbols,InpPeriod,class="num">3,class="num">10,MODE_EMA,VOLUME_TICK,class="num">1); class="kw">break;
case IND_CCI : success=engine.BufferCreateCCI(InpUsedSymbols,InpPeriod,class="num">14,PRICE_TYPICAL,class="num">1); class="kw">break;
case IND_DEMARKER : success=engine.BufferCreateDeMarker(InpUsedSymbols,InpPeriod,class="num">14,class="num">1); class="kw">break;
case IND_FORCE : success=engine.BufferCreateForce(InpUsedSymbols,InpPeriod,class="num">13,MODE_SMA,VOLUME_TICK,class="num">1); class="kw">break;
case IND_MOMENTUM : success=engine.BufferCreateMomentum(InpUsedSymbols,InpPeriod,class="num">14,PRICE_CLOSE,class="num">1); class="kw">break;
case IND_MFI : success=engine.BufferCreateMFI(InpUsedSymbols,InpPeriod,class="num">14,VOLUME_TICK,class="num">1); class="kw">break;
case IND_OSMA : success=engine.BufferCreateOsMA(InpUsedSymbols,InpPeriod,class="num">12,class="num">26,class="num">9,PRICE_CLOSE,class="num">1); class="kw">break;
case IND_OBV : success=engine.BufferCreateOBV(InpUsedSymbols,InpPeriod,VOLUME_TICK,class="num">1); class="kw">break;
case IND_RSI : success=engine.BufferCreateRSI(InpUsedSymbols,InpPeriod,class="num">14,PRICE_CLOSE,class="num">1); class="kw">break;
case IND_STDDEV : success=engine.BufferCreateStdDev(InpUsedSymbols,InpPeriod,class="num">20,class="num">0,MODE_SMA,PRICE_CLOSE,class="num">1); class="kw">break;

子窗口多缓冲指标的创建分支

这段 switch 分支负责把 TRIX、WPR、Volumes 以及 ADX、ADXW、MACD、RVI、Stochastic 等标准指标挂到 MT5 的子窗口缓冲里。每个 case 都调用 engine 的 BufferCreate 系列方法,周期参数 InpPeriod 之外还写死了各自默认长度:TRIX/WPR/ADX/ADXW 用 14,MACD 是 12/26/9,RVI 用 10,Stochastic 为 5/3/3。 如果任一 BufferCreate 返回 false,脚本会打印「Error. Indicator not created」并以 INIT_FAILED 退出初始化,所以在 MT5 里加载自定义指标时,若控制台出现这行英文,优先查对应 case 的句柄或符号权限。 创建完后还有两道一致性校验:BuffersPropertyPlotsTotal 不等于 indicator_plots、或 BuffersPropertyBuffersTotal 不等于 indicator_buffers 时,会弹 Alert 提示「Attention! Value of … should be」并附带实际数量。开 MT5 验证时,把 indicator_plots 故意改错一个值,就能立刻看到这个告警触发,确认缓冲声明没漏配。

MQL5 / C++
   case IND_TRIX         :  success=engine.BufferCreateTriX(InpUsedSymbols,InpPeriod,class="num">14,PRICE_CLOSE,class="num">1);                   class="kw">break;
   case IND_WPR           :  success=engine.BufferCreateWPR(InpUsedSymbols,InpPeriod,class="num">14,class="num">1);                                        class="kw">break;
   case IND_VOLUMES       :  success=engine.BufferCreateVolumes(InpUsedSymbols,InpPeriod,VOLUME_TICK,class="num">1);                     class="kw">break;
   
class=class="str">"cmt">//--- Multi-buffer standard indicators in a subwindow
   case IND_ADX           :  success=engine.BufferCreateADX(InpUsedSymbols,InpPeriod,class="num">14,class="num">1);                                     class="kw">break;
   case IND_ADXW          :  success=engine.BufferCreateADXWilder(InpUsedSymbols,InpPeriod,class="num">14,class="num">1);                              class="kw">break;
   case IND_MACD          :  success=engine.BufferCreateMACD(InpUsedSymbols,InpPeriod,class="num">12,class="num">26,class="num">9,PRICE_CLOSE,class="num">1);                 class="kw">break;
   case IND_RVI           :  success=engine.BufferCreateRVI(InpUsedSymbols,InpPeriod,class="num">10,class="num">1);                                    class="kw">break;
   case IND_STOCHASTIC    :  success=engine.BufferCreateStochastic(InpUsedSymbols,InpPeriod,class="num">5,class="num">3,class="num">3,MODE_SMA,STO_LOWHIGH,class="num">1);   class="kw">break;
   
   class="kw">default:
      class="kw">break;
   }
   if(!success)
   {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      class="kw">return INIT_FAILED;
   }
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">color array and set non-class="kw">default colors to all buffers within the collection
class=class="str">"cmt">//--- (commented out since the colors have already been set in the methods of creating class="kw">default standard indicators)

「按指标类型动态定小数位与水平线」

在 MT5 自建多品种指标引擎时,显示精度不能写死。下面这段逻辑先取当前交易品种的小数位 SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS),再以指标类型为分支覆盖 digits,确保数值面板不会多出无意义的尾数。 成交量类(AD、Chaikin、OBV、Volumes)直接把 digits 置 0,因为它们本身是整数计数;均线族(MA、MACD、Bands 等 15 种)在品种小数位基础上 +1;AC 与 OSMA 则 +2,反映其更细的摆动刻度。 CCI 与 DeMarker 除了改 digits=2,还用 IndicatorSetInteger(INDICATOR_LEVELS,2) 画出固定参考线。CCI 设 ±100 双边界,DeMarker 上边界先标 0.7,这类水平线能帮助肉眼快速识别超买超卖区,但具体穿越后的反转概率仍依赖盘面确认。 顺手调用 engine.BuffersPrintShort() 能把已建缓冲区的简短描述打印到日志,调试多指标同屏时省得猜哪条线对应哪个计算。

MQL5 / C++
class=class="str">"cmt">//--- (we can always set necessary colors either for all indicators, like here, or for each of them individually)
  class=class="str">"cmt">//class="type">color array_colors[]={clrGreen,clrRed,clrGray};
  class=class="str">"cmt">//engine.BuffersSetColors(array_colors);
class=class="str">"cmt">//--- Display class="type">class="kw">short descriptions of created indicator buffers
  engine.BuffersPrintShort();
class=class="str">"cmt">//--- Set levels where they are required and define the data decimal capacity
  class="type">int digits=(class="type">int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
  class="kw">switch(InpIndType)
    {
    case IND_AD          :
    case IND_CHAIKIN     :
    case IND_OBV         :
    case IND_VOLUMES     : digits=class="num">0;    class="kw">break;
    
    case IND_AO          :
    case IND_BEARS       :
    case IND_BULLS       :
    case IND_FORCE       :
    case IND_STDDEV      :
    case IND_AMA         :
    case IND_DEMA        :
    case IND_FRAMA       :
    case IND_MA          :
    case IND_TEMA        :
    case IND_VIDYA       :
    case IND_BANDS       :
    case IND_ENVELOPES   :
    case IND_MACD        : digits+=class="num">1;   class="kw">break;
    
    case IND_AC          :
    case IND_OSMA        : digits+=class="num">2;   class="kw">break;
    
    case IND_MOMENTUM    : digits=class="num">2;    class="kw">break;
    
    case IND_CCI         :
      IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
      IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">100);
      IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,-class="num">100);
      digits=class="num">2;
      class="kw">break;
    case IND_DEMARKER    :
      IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
      IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">0.7);

◍ 按指标类型切分水平线与小数位

在自定义多指标封装里,不同振荡类指标的水平线阈值和显示精度必须分开设定,否则 MT5 子窗口会画出错误的参考线或保留过多小数。下面这段 switch 分支就是按 InpIndType 逐一配置的。 MFI 与 Stochastic 都设了两条水平线,值分别是 80 和 20,对应超买超卖边界;WPR 因为是负值区间,水平线写成 -80 与 -20,少写一个负号图形就全反了。 RSI 给了三条线:70、50、30,并把 digits 定为 2;而前一个未命名分支里 digits=3 配合 0.3 水平线,多半是 CCI 类用法。ATR、SAR、TRIX 在此分支直接 break,不画任何固定水平线。 default 分支用 IndicatorSetInteger(INDICATOR_LEVELS,0) 清空水平线,避免上一个品种残留。随后用 BufferGetIndicatorShortNameByTypeID 取短名、IndicatorSetInteger(INDICATOR_DIGITS,digits) 统一精度,再拿 GetListBuffers 的总数以备后续绑定。外汇与贵金属波动剧烈,水平线仅作概率参考,实盘仍属高风险。

MQL5 / C++
IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">0.3);
digits=class="num">3;
class="kw">break;
case IND_MFI       :
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">80);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">20);
   class="kw">break;
case IND_RSI       :
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">3);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">70);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">50);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">2,class="num">30);
   digits=class="num">2;
   class="kw">break;
case IND_STOCHASTIC :
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">80);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">20);
   digits=class="num">2;
   class="kw">break;
case IND_WPR       :
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,-class="num">80);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,-class="num">20);
   digits=class="num">2;
   class="kw">break;

case IND_ATR       :                class="kw">break;
case IND_SAR       :                class="kw">break;
case IND_TRIX      :                class="kw">break;

class="kw">default:
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">0);
   class="kw">break;
}
class=class="str">"cmt">//--- Set the class="type">class="kw">short name for the indicator and bit depth
class="type">class="kw">string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,class="num">1);
IndicatorSetString(INDICATOR_SHORTNAME,label);
IndicatorSetInteger(INDICATOR_DIGITS,digits);
class=class="str">"cmt">//---
CArrayObj *list=engine.GetListBuffers();
class="type">int total=list.Total();

初始化里按类型建缓冲的写法

在 MT5 自定义指标里,OnInit 负责把底层库和缓冲区一次性铺好。下面这段先算当前周期能塞进最大周期多少根 K 线,若结果大于 2 就用它当 min_bars,否则强行取 2,避免小周期加载时缓冲不足导致数组越界。 随后用 ObjectsDeleteAll(0,prefix) 按前缀清掉旧对象,再调 engine.PlaySoundByDescription 播 SND_OK,engine.Pause(600) 等 600 毫秒后播 SND_NEWS——这段延时在实盘加载面板时能听见先后两声,可用来确认 OnInit 确实跑完了。 缓冲创建走 switch(InpIndType):AMA 用 (9,2,30,0,PRICE_CLOSE,1),DEMA/FRAMA 周期 14,MA 是 10 期 SMA 收价,SAR 步长 0.02、极值 0.2。复制时改 InpIndType 就能换指标,无需重写绘图逻辑。外汇与贵金属波动大,这类多缓冲指标在跳空时可能丢根数,加载后建议切周期验证一次。

MQL5 / C++
for(class="type">int i=class="num">0;i<total;i++)
  {
  CBuffer *buff=list.At(i);
  if(buff==NULL)
     class="kw">continue;
  buff.Print();
  }
  class=class="str">"cmt">//class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
class=class="str">"cmt">//--- Successful
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
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">//--- Initialize DoEasy library
  OnInitDoEasy();

class=class="str">"cmt">//--- Set indicator global variables
  prefix=engine.Name()+"_";
  class=class="str">"cmt">//--- calculate the number of bars of the current period fitting in the maximum used period
  class=class="str">"cmt">//--- Use the obtained value if it exceeds class="num">2, otherwise use class="num">2
  class="type">int num_bars=NumberBarsInTimeframe(InpPeriod);
  min_bars=(num_bars>class="num">2 ? num_bars : class="num">2);
class=class="str">"cmt">//--- Check and remove remaining indicator graphical objects
  if(IsPresentObectByPrefix(prefix))
     ObjectsDeleteAll(class="num">0,prefix);
class=class="str">"cmt">//--- Create the button panel
class=class="str">"cmt">//--- Check playing a standard sound using macro substitutions
  engine.PlaySoundByDescription(SND_OK);
class=class="str">"cmt">//--- Wait for class="num">600 milliseconds
  engine.Pause(class="num">600);
  engine.PlaySoundByDescription(SND_NEWS);
class=class="str">"cmt">//--- indicator buffers mapping
class=class="str">"cmt">//--- Create all the necessary buffer objects for constructing a selected standard indicator
  class="type">bool success=false;
  class="kw">switch(InpIndType)
   {
class=class="str">"cmt">//--- Single-buffer standard indicators in the main window
    case IND_AMA        :  success=engine.BufferCreateAMA(InpUsedSymbols,InpPeriod,class="num">9,class="num">2,class="num">30,class="num">0,PRICE_CLOSE,class="num">1);            class="kw">break;
    case IND_DEMA       :  success=engine.BufferCreateDEMA(InpUsedSymbols,InpPeriod,class="num">14,class="num">0,PRICE_CLOSE,class="num">1);               class="kw">break;
    case IND_FRAMA      :  success=engine.BufferCreateFrAMA(InpUsedSymbols,InpPeriod,class="num">14,class="num">0,PRICE_CLOSE,class="num">1);              class="kw">break;
    case IND_MA         :  success=engine.BufferCreateMA(InpUsedSymbols,InpPeriod,class="num">10,class="num">0,MODE_SMA,PRICE_CLOSE,class="num">1);        class="kw">break;
    case IND_SAR        :  success=engine.BufferCreateSAR(InpUsedSymbols,InpPeriod,class="num">0.02,class="num">0.2,class="num">1);                       class="kw">break;

「子窗口指标与缓冲区自检的落地写法」

在主图趋势类指标之后,代码转向子窗口类标准指标的多缓冲创建。Bollinger Bands 用 20 周期、偏离 2.0 的 PRICE_CLOSE 做三层带面;Envelopes 以 14 周期 SMA 加 0.1 百分比包络;Fractals 仅需偏移参数 1,三者都归入同一个 engine 缓冲集合。 创建失败会直接 Print 双语报错并返回 INIT_FAILED,这一步能在 MT5 启动期拦掉多数配置错误。随后用 BuffersPropertyPlotsTotal() 比对 indicator_plots、用 BuffersPropertyBuffersTotal() 比对 indicator_buffers,不一致就弹 Alert 提示正确数值——改 EA 属性块时这是最容易漏的地方。 小数位处理按品种 digits 走:AD、Chaikin、OBV、Volumes 这类量能类强制 digits=0,避免副图数值出现无意义小数。颜色数组那段被注释掉,说明默认色已在创建方法里设好,但你随时可以取消注释用 BuffersSetColors 统一改色。 开 MT5 把这段粘进指标模板,故意把 indicator_plots 写错一个值,就能看到 Alert 弹出期望总数,验证自检逻辑是否生效。外汇与贵金属杠杆高,指标初始化异常可能让实盘 EA 直接罢工,上线前务必跑一遍这个分支。

MQL5 / C++
case IND_TEMA        :  success=engine.BufferCreateTEMA(InpUsedSymbols,InpPeriod,class="num">14,class="num">0,PRICE_CLOSE,class="num">1);                 class="kw">break;
case IND_VIDYA        :  success=engine.BufferCreateVIDYA(InpUsedSymbols,InpPeriod,class="num">9,class="num">12,class="num">0,PRICE_CLOSE,class="num">1);               class="kw">break;

class=class="str">"cmt">//--- Multi-buffer standard indicators in a subwindow
case IND_BANDS        :  success=engine.BufferCreateBands(InpUsedSymbols,InpPeriod,class="num">20,class="num">0,class="num">2.0,PRICE_CLOSE,class="num">1);             class="kw">break;
case IND_ENVELOPES    :  success=engine.BufferCreateEnvelopes(InpUsedSymbols,InpPeriod,class="num">14,class="num">0,MODE_SMA,PRICE_CLOSE,class="num">0.1,class="num">1); class="kw">break;
case IND_FRACTALS     :  success=engine.BufferCreateFractals(InpUsedSymbols,InpPeriod,class="num">1);                              class="kw">break;

class="kw">default:
   class="kw">break;
  }
  if(!success)
  {
   Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
   class="kw">return INIT_FAILED;
  }
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">color array and set non-class="kw">default colors to all buffers within the collection
class=class="str">"cmt">//--- (commented out since the colors have already been set in the methods of creating class="kw">default standard indicators)
class=class="str">"cmt">//--- (we can always set necessary colors either for all indicators, like here, or for each of them individually)
  class=class="str">"cmt">//class="type">color array_colors[]={clrGreen,clrRed,clrGray};
  class=class="str">"cmt">//engine.BuffersSetColors(array_colors);
class=class="str">"cmt">//--- Display class="type">class="kw">short descriptions of created indicator buffers
  engine.BuffersPrintShort();
class=class="str">"cmt">//--- Set levels where they are required and define the data decimal capacity
  class="type">int digits=(class="type">int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
  class="kw">switch(InpIndType)
  {
   case IND_AD          :
   case IND_CHAIKIN     :
   case IND_OBV         :
   case IND_VOLUMES     : digits=class="num">0;   class="kw">break;
   }

◍ 指标精度与警戒线的分支设定

在 MT5 自定义指标初始化时,根据指标类型用 switch 分支统一管控显示精度(digits)和水平线,能省掉大量重复代码。下面这段分支把 AO、BEARS、BULLS、FORCE、STDDEV、AMA、DEMA、FRAMA、MA、TEMA、VIDYA、BANDS、ENVELOPES、MACD 归为一类,digits 自增 1;AC 与 OSMA 自增 2;MOMENTUM 直接锁 2 位。 CCI 分支不止设精度,还用 IndicatorSetInteger(INDICATOR_LEVELS,2) 开了两条水平线,分别在 +100 与 -100。DEMARKER 同理设 0.7 / 0.3 双线且 digits=3,MFI 设 80 / 20 双线但未改 digits,RSI 则开了 3 条水平线。 开 MT5 新建指标时,直接抄这套 case 逻辑,能避免小数值在副图里挤成一团。外汇与贵金属波动大,指标仅是概率参考,实盘仍属高风险。

MQL5 / C++
case IND_AO          :
case IND_BEARS       :
case IND_BULLS       :
case IND_FORCE       :
case IND_STDDEV      :
case IND_AMA         :
case IND_DEMA        :
case IND_FRAMA       :
case IND_MA          :
case IND_TEMA        :
case IND_VIDYA       :
case IND_BANDS       :
case IND_ENVELOPES   :
case IND_MACD        : digits+=class="num">1;   class="kw">break;

case IND_AC          :
case IND_OSMA        : digits+=class="num">2;   class="kw">break;

case IND_MOMENTUM    : digits=class="num">2;    class="kw">break;

case IND_CCI         :
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">100);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,-class="num">100);
   digits=class="num">2;
   class="kw">break;
case IND_DEMARKER    :
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">0.7);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">0.3);
   digits=class="num">3;
   class="kw">break;
case IND_MFI         :
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">80);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">20);
   class="kw">break;
case IND_RSI         :
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">3);

摆动类指标的临界线怎么落进图表

在自定义指标初始化时,不同振荡器要画的水平位差别很大。RSI 走 70 / 50 / 30 三线,Stochastic 用 80 / 20 双线,WPR 则是 -80 / -20,这些数值直接决定超买超卖区在 MT5 副图上的视觉边界。 代码里用 switch 按指标类型分别调 IndicatorSetDouble(INDICATOR_LEVELVALUE, n, value) 写入具体坐标,同时 IndicatorSetInteger(INDICATOR_LEVELS, 2) 声明要画两条线;ATR、SAR、TRIX 这类无固定阈值的指标直接 break 跳过,default 分支把水平线数清零。 digits 统一设 2,意味着副图数值保留两位小数,和外汇报价的小数精度习惯对齐。最后用 engine.GetListBuffers() 遍历缓冲区打印结构,返回 INIT_SUCCEEDED 才算装载完成——你把这截贴进 EA 的 OnInit,开 EURUSD 的 M15 就能看到 RSI 的 70 线压在副图上方。 外汇与贵金属杠杆高,水平线只是概率参考,价格刺穿后继续发散的情况很常见,别当成反转铁律。

MQL5 / C++
IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">70);
IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">50);
IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">2,class="num">30);
digits=class="num">2;
class="kw">break;
case IND_STOCHASTIC  :
  IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
  IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">80);
  IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">20);
  digits=class="num">2;
  class="kw">break;
case IND_WPR        :
  IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
  IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,-class="num">80);
  IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,-class="num">20);
  digits=class="num">2;
  class="kw">break;

case IND_ATR        :             class="kw">break;
case IND_SAR        :             class="kw">break;
case IND_TRIX       :             class="kw">break;

class="kw">default:
  IndicatorSetInteger(INDICATOR_LEVELS,class="num">0);
  class="kw">break;
}
class=class="str">"cmt">//--- Set the class="type">class="kw">short name for the indicator and bit depth
class="type">class="kw">string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,class="num">1);
IndicatorSetString(INDICATOR_SHORTNAME,label);
IndicatorSetInteger(INDICATOR_DIGITS,digits);
class=class="str">"cmt">//---
CArrayObj *list=engine.GetListBuffers();
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.Print();
  }
  class=class="str">"cmt">//class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
class=class="str">"cmt">//--- Successful
class="kw">return(INIT_SUCCEEDED);
}

「收束」

眼下这套多品种多周期标准指标库类在 MT5 环境里已经跑通,附件 ZIP 体积 3768.35 KB,涵盖当前版本全部库文件与测试指标,直接丢进 MetaTrader 5 就能编译验证。MT4 端尚未做过任何测试,等缓冲区功能在 MT5 打磨清楚后,才会尝试把部分 MQL5 特性往 MT4 迁。 下一步重心是继续重构库类、剔掉主指标程序里非必要的函数调用,同时把多周期标准指标的实时刷新再压一层延迟。外汇与贵金属市场高波动,这类底层库无论多顺手,实盘前都该先在策略测试器里跑一轮历史回放。 真要上手,建议先下 ZIP 把第四十八部分那个单子窗口单缓冲区的样例指标挂上 EURUSD 和 XAUUSD 的 M15、H1 看看刷新有没有丢帧,再决定要不要等作者的下一篇改动。

交给小布盯盘看盘口
这些跨品种多周期的诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到叠加逻辑是否冲突,你只需专注信号取舍。

常见问题

在缓冲对象上同时写指标类型、实例标识和线型三个属性,就能按曲线种类精准访问,不必遍历猜测。
用短指标名属性把库内对象和标准指标展示名对齐,这样跨周期子窗口的标签不会错位。
相同类型共享类型枚举,实例 ID 用来切分归属,否则缓冲写入会互相覆盖导致线形异常。
小布盯盘的品种页支持查看跨周期叠加状态,可对照文中缓冲归属逻辑排查显示异常,不需手写全部代码。
重点看计算缓冲和绘制缓冲是否按线型参数分流,以及不同周期数据同步延迟是否造成线偏移。