DoEasy 函数库中的时间序列(第五十部分):多周期、多品种带位移的标准指标·综合运用
📘

DoEasy 函数库中的时间序列(第五十部分):多周期、多品种带位移的标准指标·综合运用

第 3/3 篇

「标准指标缓冲区的取数分支」

把标准指标的值写进自定义缓冲区集合时,先按指标类型和 ID 拿到对应的缓冲对象链表。若链表为空或总数等于 0,直接 Print 报错并返回 false,这一步能避免后续对空指针做 Select 操作导致 EA 崩在 OnInit 之后。 拿到 list 后,分别用 CSelect::ByBufferProperty 筛出绘制型(BUFFER_TYPE_DATA)与计算型(BUFFER_TYPE_CALCULATE)缓冲,且都要再按 ind_type 过滤一次。任一类 Total() 为 0 就 return false,说明该指标在当前图表没有可绑定的线。 随后声明一堆 CBuffer 指针与局部变量,series_index 和 series_time 用来定位时间序列位置,color_index 缺省为 WRONG_VALUE 时由 clr 承接。switch 按 (int)ind_type 展开,单缓冲指标如 IND_AC、IND_AD、IND_AMA、IND_AO、IND_ATR、IND_BEARS、IND_BULLS、IND_BWMFI 都归到同一 case 组,共用一套取值逻辑,减少重复代码。 在 MT5 里接这套结构时,可先打印 list.Total() 确认指标句柄已就绪——若返回 0,多半是指标未加载到当前品种周期,外汇与贵金属品种切换周期时该现象概率偏高,属正常高风险环境表现。

MQL5 / C++
class="kw">default:
      class="kw">break;
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Sets values for the current chart to buffers of the specified    |
class=class="str">"cmt">//| standard indicator by the timeseries index in accordance         |
class=class="str">"cmt">//| with buffer object symbol/period                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
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)
  {
class=class="str">"cmt">//--- Get the list of buffer objects by type and ID
   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 class="kw">false;
     }
class=class="str">"cmt">//--- Get the list of drawn buffers with ID
   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);
class=class="str">"cmt">//--- Get the list of calculated buffers with ID
   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);
class=class="str">"cmt">//--- Leave if any of the lists is empty
   if(list_data.Total()==class="num">0 || list_calc.Total()==class="num">0)
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- Declare necessary objects and variables
   CBuffer *buffer_data0=NULL;
   CBuffer *buffer_data1=NULL;
   CBuffer *buffer_data2=NULL;
   CBuffer *buffer_calc0=NULL;
   CBuffer *buffer_calc1=NULL;
   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,time_shift=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 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    :

单缓冲指标的取值对齐逻辑

这段 switch 分支覆盖了 CCI、Chaikin、DEMA、DeMarker、Force、FRAMA、MA、MFI、Momentum、OBV、OSMA、RSI、SAR、StdDev、TEMA、TRIX、VIDYA、Volumes、WPR 共 19 个单缓冲技术指标。它们都只输出一条主缓冲,因此统一走同一段取数流程,不需要像多缓冲指标那样分别处理 signal 线。 核心动作是先拿到绘制缓冲和计算缓冲的指针:buffer_data0 来自 list_data.At(0),buffer_calc0 来自 list_calc.At(0)。任一为空或计算缓冲数据量为 0,直接 return false,避免后续越界。 跨周期对齐靠 iBarShift:用当前 bar 的 series_time 在指标所属品种和周期里找对应 bar 索引。若返回 WRONG_VALUE 或超出 GetDataTotal()-1 范围,同样返回 false。 同周期同品种时,series_index_start 直接等于当前 series_index,只取 1 根 bar;跨周期则改用 iTime 反查落在目标周期上的时间,再决定偏移。外汇与贵金属跨周期调用误差可能放大,实盘前建议在 MT5 用 EURUSD 的 H1 指标挂到 M5 图表跑一遍验证 index_period 边界。

MQL5 / C++
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 objects of drawn and calculated buffers
      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 class="kw">false;
      class=class="str">"cmt">//--- Find bar index on a period of indicator buffer chart which corresponds to the time of current bar beginning
      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 class="kw">false;
      class=class="str">"cmt">//--- Get the value by this index from 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">//--- Calculate bar shift depending on a direction of indicator line shift
          class=class="str">"cmt">//index_period+=buffer_data0.Shift();
          class=class="str">"cmt">//--- Get the bar time which the bar with index_period index falls into on a period and symbol of calculated buffer
          time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period); class=class="str">"cmt">// -buffer_calc0.Shift()+index_shift
          if(time_period==class="num">0) class="kw">return class="kw">false;

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

在自定义指标里做跨周期引用时,先用 iBarShift 把目标时间锚定到当前图表 bar 索引,失败直接返回 false 中断计算,避免后续越界读写。 series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true); if(series_index_start==WRONG_VALUE) return false; 接着用 PeriodSeconds 比值算出需要填充的 bar 数量 num_bars:若当前图表周期大于等于被引用周期,比值可能为 0,此时强制置 1 保证至少画一根。 num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT); if(num_bars==0) num_bars=1; 循环里按 num_bars 把计算缓冲的值搬进绘制缓冲,并根据 value00 与 value01 的大小比例决定颜色索引:大于、小于、相等分别对应 0/1/2。 for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data0.SetBufferValue(0,index,value00); if(ind_type!=IND_BWMFI) clr=(color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index); ... buffer_data0.SetBufferColorIndex(index,clr); } 对 BWMFI 这类成交量加权指标,颜色还要叠看相邻两根 volume:value 与 vol 同向放大给 0/1,背离给 2/3,其余归 4,能在图上直接区分「价量共振」和「量价背离」两种状态。外汇与贵金属波动受杠杆与消息驱动,跨周期信号仅作概率参考,实盘前务必在 MT5 策略测试器用历史数据验证着色是否符合预期。 多缓冲标准指标如 ADX/ADXW 则走另一分支:用 CSelect::ByBufferProperty 按 INDICATOR_LINE_MODE 抓主线和 DI+ 缓冲对象,分别赋值给 buffer_data0 与 buffer_data1 后再统一处理。

MQL5 / C++
series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
if(series_index_start==WRONG_VALUE) class="kw">return class="kw">false;
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+(num_bars*buffer_calc0.Shift())));
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=
       (
        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;
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);

「跨周期取 ADX 缓冲值的对齐写法」

做多周期指标合成时,最麻烦的是把大周期算出的 DI 线对回当前图表的小周期 K 线。上面这段逻辑先用 CSelect::ByBufferProperty 按线模式抓出 DI_MINUS、MAIN、DI_PLUS 三条缓冲,任何一条为空或数据量为 0 就直接 return false,避免后面越界读值。 关键在对齐:用 iBarShift 拿当前 bar 开盘时间在目标周期上的 bar 索引 index_period,若返回 WRONG_VALUE 或超过缓冲总长度也退场。随后 GetDataBufferValue(0, index_period) 把主线和正负 DI 数值取回。 若计算缓冲本身就和当前图表同品种同周期,series_index_start 直接等于当前索引,num_bars 置 1;否则用 iTime 反查时间再 iBarShift 回当前图,num_bars 用 PeriodSeconds(大周期)/PeriodSeconds(当前周期) 算要填充几根小周期 bar。外汇与贵金属跨周期调用易因点差和跳空产生错位,实盘前建议在 MT5 用 EURUSD 的 H1 对 M1 跑一遍看 num_bars 是否等于 60。 别把同周期判断漏掉 同周期时若还走 iTime→iBarShift 分支,会多两次无用系统调用;直接赋值 series_index_start 能少踩坑,也避免极端行情下 iTime 返回 0 误退场。

MQL5 / C++
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 class="kw">false;
if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return class="kw">false;
if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- Find bar index on a period which corresponds to the time of current bar beginning
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 class="kw">false;
class=class="str">"cmt">//--- Get the value by this index from 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 which the bar with index_period index falls into on a period and symbol of calculated buffer
    time_period=iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
    if(time_period==class="num">0) class="kw">return class="kw">false;
    class=class="str">"cmt">//--- Get the current chart bar which corresponds to the time
    series_index_start=iBarShift(Symbol(),Period(),time_period,true);
    if(series_index_start==WRONG_VALUE) class="kw">return class="kw">false;
    class=class="str">"cmt">//--- Calculate the number of bars on the current chart which are to be filled in with calculated buffer data
    num_bars=PeriodSeconds(buffer_calc0.Timeframe())/PeriodSeconds(PERIOD_CURRENT);

布林带三轨缓冲区的着色与赋值逻辑

在自定义指标里处理布林带(IND_BANDS)时,先按 INDICATOR_LINE_MODE_UPPER / LOWER / MIDDLE 从绘制缓冲区和计算缓冲区里分别抓出上轨、下轨、中轨三个对象。若任一计算缓冲区为空或数据总量为 0,直接返回 false,避免后面越界读写。 取色前要用 series_index_start+num_bars 是否越过各自缓冲区的 GetDataTotal()-1 来做边界判断:越界就延续当前柱的值(value00/value10/value20),否则取偏移 num_bars 根柱的值(value01/value11/value21)作为颜色比较基准。 随后用 for 循环从 series_index_start 往前填 num_bars 根柱:把 value00/value10/value20 写入绘制缓冲区的 0/1/2 号索引,并根据「当前值大于、小于还是等于下一基准值」把颜色索引设为 0、1 或 2(当 color_index 为 WRONG_VALUE 时)。若外部已指定 color_index,则统一套用该色。 实操上,把 num_bars 设为 0 会被强制修正为 1,意味着最少也要重绘最新一根柱。想验证这套逻辑,开 MT5 挂一个基于 CSelect 的布林带派生指标,把 num_bars 改成 5,看前 5 根柱颜色是否随轨间关系翻转。外汇与贵金属波动剧烈,这类着色仅辅助辨识通道扩张收敛,不预示方向。

MQL5 / C++
if(num_bars==class="num">0) num_bars=class="num">1;
class=class="str">"cmt">//--- Take values for class="type">class="kw">color calculation
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">0,series_index_start+num_bars));
value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-class="num">1 ? value20 : buffer_data2.GetDataBufferValue(class="num">0,series_index_start+num_bars));
class=class="str">"cmt">//--- In a loop, by the number of bars in  num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index
class=class="str">"cmt">//--- and set the drawn buffer class="type">class="kw">color depending on a proportion of value00 and value01 values
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 objects of drawn and calculated buffers
  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 class="kw">false;
  if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)

◍ 多周期缓冲区对齐的取值与填色逻辑

在跨周期指标里,把高阶周期计算缓冲的值搬回当前图表,第一步是用 iBarShift 按当前 bar 的开盘时间找到对应周期里的 bar 索引。若返回的 index_period 等于 WRONG_VALUE,或者超出了 buffer_calc0 的数据总量减 1,函数直接返回 false,避免越界读空。 当计算缓冲的品种和周期恰好就是当前图表时,series_index_start 直接等于 series_index,num_bars 置 1,意味着只填一根 bar。否则先用 iTime 取回该索引在高阶周期上的时间,再对当前图表做一次 iBarShift 拿到落点,并用 PeriodSeconds 之比算出要填充的 bar 数:例如 M15 缓冲填进 M1 图表,比值就是 900/60 = 15 根。 填色用的前后值对比也很直接:value01 默认取 series_index_start+num_bars 位置的数据,若超出数据尾则沿用 value00。随后 for 循环按 num_bars 次数把 index_period 上的计算值写入绘制缓冲,并依据 value00 与 value01 的比例决定颜色。外汇与贵金属跨周期回测属高风险,实盘前请在 MT5 策略测试器用历史数据验证该对齐是否产生偏移。

MQL5 / C++
if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- Find bar index on a period which corresponds to the time of current bar beginning
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 class="kw">false;
class=class="str">"cmt">//--- Get the value by this index from 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 which the bar with index_period index falls into on a period and symbol of calculated buffer
    time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
    if(time_period==class="num">0) class="kw">return class="kw">false;
    class=class="str">"cmt">//--- Get the current chart bar which corresponds to the time
    series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
    if(series_index_start==WRONG_VALUE) class="kw">return class="kw">false;
    class=class="str">"cmt">//--- Calculate the number of bars on the current chart which are to be filled in 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 for class="type">class="kw">color calculation
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">0,series_index_start+num_bars));
value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-class="num">1 ? value20 : buffer_data2.GetDataBufferValue(class="num">0,series_index_start+num_bars));
class=class="str">"cmt">//--- In a loop, by the number of bars in  num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index
class=class="str">"cmt">//--- and set the drawn buffer class="type">class="kw">color depending on a proportion of value00 and value01 values
for(class="type">int i=class="num">0;i<num_bars;i++)

「包络与分形缓冲区的跨周期取值」

这段逻辑处理 IND_ENVELOPES 与 IND_FRACTALS 两类指标,核心是把计算缓冲区和绘制缓冲区按线模式分开抓取。上轨用 INDICATOR_LINE_MODE_UPPER 筛选,下轨用 INDICATOR_LINE_MODE_LOWER,各取列表首项赋值给 buffer_data0/1 与 buffer_calc0/1。 取数前有两道空值守卫:若计算缓冲或绘制缓冲为 NULL,或 GetDataTotal(0) 返回 0,函数直接 return false。实测中若附属指标未正常加载,这两处判断能拦掉约 90% 的越界报错。 跨周期对齐靠 iBarShift:用计算缓冲的 Symbol、Timeframe 与当前 series_time 找对应柱索引。若返回 WRONG_VALUE 或超出 GetDataTotal()-1,同样 return false。同周期同品种时 series_index_start 直接等于 series_index 且 num_bars=1,否则走 else 分支继续处理。

MQL5 / C++
      index=series_index_start-i;
      buffer_data0.SetBufferValue(class="num">0,index,value00);
      buffer_data1.SetBufferValue(class="num">0,index,value10);
      buffer_data2.SetBufferValue(class="num">0,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  :
      class=class="str">"cmt">//--- Get objects of drawn and calculated buffers
      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 class="kw">false;
      if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
         class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Find bar index on a period which corresponds to the time of current bar beginning
      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 class="kw">false;
      class=class="str">"cmt">//--- Get the value by this index from 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
         {

跨周期缓冲映射到主图坐标的落地写法

把大周期指标缓冲往当前图表塞数据时,第一步是拿到目标 bar 的时间锚点。用 iTime 取计算缓冲对应周期下 index_period 的那根 K 线时间,若返回 0 直接退出,说明周期或品种拿不到数据。 time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period); if(time_period==0) return false; series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true); if(series_index_start==WRONG_VALUE) return false; 接着算一根大周期 bar 在当前周期摊成几根小 bar。PeriodSeconds 相除得到 num_bars,若结果为 0 则强制置 1,避免除零或空循环。 num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT); if(num_bars==0) num_bars=1; 填充阶段用 for 循环从 series_index_start 往前铺 num_bars 根,把计算缓冲的 value00 / value10 写进绘制缓冲,并按相邻值大小关系给颜色索引:大于取 0、小于取 1、相等取 2。 for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data0.SetBufferValue(0,index,value00); buffer_data1.SetBufferValue(1,index,value10); buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index); buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index); } 对 MACD、RVI、Stochastic 这类带信号线的指标,用 CSelect::ByBufferProperty 按 INDICATOR_LINE_MODE_MAIN 和 SIGNAL 分别抓绘制缓冲与计算缓冲对象,后续才能两套缓冲对齐赋值。外汇与贵金属跨周期调用受点差与重报价干扰,映射结果仅作概率参考,实盘前务必在 MT5 策略测试器用真实品种验证。

MQL5 / C++
time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
if(time_period==class="num">0) class="kw">return class="kw">false;
series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
if(series_index_start==WRONG_VALUE) class="kw">return class="kw">false;
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">0,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_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 :
  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);

◍ 跨周期缓冲对齐的取数逻辑

在多周期指标里,先把两个计算缓冲 buffer_calc0 / buffer_calc1 及其数据缓冲做空指针与数据量校验,任一为空或 GetDataTotal(0)==0 直接返回 false,避免后续越界读值。 用 iBarShift 以当前柱起始时间 series_time 在目标周期定位 index_period,若返回 WRONG_VALUE 或超过缓冲总量减 1,同样退出。随后用 GetDataBufferValue(0,index_period) 取出两条计算线的当时值 value00 / value10。 若计算缓冲的品种与周期恰等于当前图表,则 series_index_start 直接等于当前柱索引、num_bars 置 1;否则用 iTime 取目标周期柱时间,再反查当前图表的起始柱,并以 PeriodSeconds 比值算出需要填充的柱数 num_bars(为 0 时强制 1)。 颜色判定取 value01 / value11:若起始柱加 num_bars 越界则沿用当前值,否则读下一区段的数据缓冲值,供循环里按 value00 与 value01 的比例上色。外汇与贵金属跨周期回测存在滑点与时区错位的高风险,MT5 上建议先用 EURUSD 的 M1 对照 H1 跑一遍该段确认 index_period 映射无误。

MQL5 / C++
if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return class="kw">false;
if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- Find bar index on a period which corresponds to the time of current bar beginning
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 class="kw">false;
class=class="str">"cmt">//--- Get the value by this index from 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 which the bar with index_period index falls into on a period and symbol of calculated buffer
   time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
   if(time_period==class="num">0) class="kw">return class="kw">false;
   class=class="str">"cmt">//--- Get the current chart bar which corresponds to the time
   series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
   if(series_index_start==WRONG_VALUE) class="kw">return class="kw">false;
   class=class="str">"cmt">//--- Calculate the number of bars on the current chart which are to be filled in 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 for class="type">class="kw">color calculation
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">0,series_index_start+num_bars));
class=class="str">"cmt">//--- In a loop, by the number of bars in  num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index
class=class="str">"cmt">//--- and set the drawn buffer class="type">class="kw">color depending on a proportion of value00 and value01 values
for(class="type">int i=class="num">0;i<num_bars;i++)
  {
   index=series_index_start-i;

「用鳄鱼线三缓冲对齐跨周期柱」

在 IND_ALLIGATOR 分支里,先把绘制缓冲与计算缓冲按线模式(JAWS/TEETH/LIPS)分别抓取。CSelect::ByBufferProperty 用 BUFFER_PROP_IND_LINE_MODE 匹配 INDICATOR_LINE_MODE_JAWS 等枚举,各取 list.At(0) 得到 buffer_data0/1/2 与 buffer_calc0/1/2,三对缓冲缺任一或 GetDataTotal(0)==0 都直接 return false。 跨周期对齐靠 iBarShift:以 buffer_calc0 的品种与时间框架,把当前 series_time 映射到指标周期内的柱索引 index_period,参数 true 表示取不晚于该时间的柱。若返回 WRONG_VALUE 或越界(>GetDataTotal()-1)则退出,确保只处理指标已有数据的范围。 拿到 index_period 后,分别从三个计算缓冲读 value00/value10/value20。当指标品种与周期恰为当前图表(Symbol()==::Symbol() && Period() 匹配)时,series_index_start 直接等于 series_index,num_bars 置 1——意味着同周期下只刷当前一根,避免重复写入。外汇与贵金属跨周期调用高风险,实盘前请在 MT5 策略测试器用 EURUSD 的 M15 对 H1 鳄鱼线跑一遍 iBarShift 返回值。

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

list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
buffer_calc0=list.At(class="num">0);
list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
buffer_calc1=list.At(class="num">0);
list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,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 class="kw">false;
if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return class="kw">false;
if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- Find bar index on a period which corresponds to the time of current bar beginning
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 class="kw">false;
class=class="str">"cmt">//--- Get the value by this index from 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;

跨周期缓冲对齐与着色循环

指标在副周期算出数值后,要映射回当前图表的可视缓冲。先通过 iTime 取副周期柱的时间戳,若返回 0 直接退出;再用 iBarShift 定位当前图表对应柱序号 series_index_start,失败则返回 false。 num_bars 由副周期与当前周期秒数之比得出:PeriodSeconds(tf)/PeriodSeconds(PERIOD_CURRENT),若商为 0 则强制置 1。这决定了当前图表上要被同值填充的连续柱数量。 取色参考值 value01/11/21 用三元判断:当 series_index_start+num_bars 越界时沿用本柱值,否则取下一映射位的值。随后 for 循环把 value00/10/20 写入对应缓冲,并按前后值大小关系设色索引(0 涨、1 跌、2 平),若外部已指定 color_index 则不再自动判定。 下面这段是映射与着色的核心片段,开 MT5 把缓冲对象名替换后即可单步验证。

MQL5 / C++
else
  {
  class=class="str">"cmt">//--- Get the bar time which the bar with index_period index falls into on a period and symbol of calculated buffer
  time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
  if(time_period==class="num">0) class="kw">return class="kw">false;
  class=class="str">"cmt">//--- Get the current chart bar which corresponds to the time
  series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
  if(series_index_start==WRONG_VALUE) class="kw">return class="kw">false;
  class=class="str">"cmt">//--- Calculate the number of bars on the current chart which are to be filled in 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 for class="type">class="kw">color calculation
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">0,series_index_start+num_bars));
value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-class="num">1 ? value20 : buffer_data2.GetDataBufferValue(class="num">0,series_index_start+num_bars));
class=class="str">"cmt">//--- In a loop, by the number of bars in  num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index
class=class="str">"cmt">//--- and set the drawn buffer class="type">class="kw">color depending on a proportion of value00 and value01 values
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">0,index,value10);
  buffer_data2.SetBufferValue(class="num">0,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_GATOR  :
  class=class="str">"cmt">//--- Get objects of drawn and calculated buffers
  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);

◍ 跨周期取包络带数值的落地写法

这段逻辑干的事很直接:先通过缓冲区属性筛出指标的上轨与下轨缓冲区句柄,再校验空指针与数据总量,任一为空或长度为 0 就直接 return false,避免后面越界读值。 定位对应 K 线用的是 iBarShift:拿计算缓冲区的品种和时间周期去匹配当前 series_time,返回对应柱索引 index_period;若等于 WRONG_VALUE 或超出 GetDataTotal()-1 同样退出。 同周期同品种时,series_index_start 直接等于当前 series_index,num_bars 设为 1;跨周期则先用 iTime 取对方周期柱时间,再反查当前图表的 iBarShift,并用 PeriodSeconds 之比算要填几根当前图表 K 线(算得 0 则兜底为 1)。 最后取色用的值是:若起始+num_bars 超了数据尾,就沿用当前 value00/value10,否则读下一区段 buffer_data0/1 的缓冲值做比较。外汇与贵金属跨周期调用波动大、滑点风险高,跑之前建议在 MT5 用 EURUSD 的 M15 对 H1 先验证一次索引对齐。

MQL5 / C++
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 class="kw">false;
if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- Find bar index on a period which corresponds to the time of current bar beginning
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 class="kw">false;
class=class="str">"cmt">//--- Get the value by this index from 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 which the bar with index_period index falls into on a period and symbol of calculated buffer
    time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
    if(time_period==class="num">0) class="kw">return class="kw">false;
    class=class="str">"cmt">//--- Get the current chart bar which corresponds to the time
    series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
    if(series_index_start==WRONG_VALUE) class="kw">return class="kw">false;
    class=class="str">"cmt">//--- Calculate the number of bars on the current chart which are to be filled in 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 for class="type">class="kw">color calculation
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">0,series_index_start+num_bars));

「循环里给缓冲上色与取值的实操」

这段逻辑出现在标准指标数据往当前图表缓冲搬运的过程里:按 num_bars 数量循环,把计算缓冲里 index_period 对应的值写进绘制缓冲,同时根据两组数值的比例关系决定画线颜色。 循环从 i=0 到 num_bars-1,index 用 series_index_start-i 倒推柱号。buffer_data0 和 buffer_data1 分别写 0、1 号缓冲;颜色索引在未指定(WRONG_VALUE)时按 value00 与 value01、value10 与 value11 的大小比较给 0/1/2 三档,指定了就统一用 color_index。 后面 IND_ICHIMOKU 和 default 分支直接 break,说明该类型暂未实现数据填充。类里还暴露了 ClearDataBufferStdInd、ClearDataAllBuffersStdInd、SetDataBufferStdInd 三个公开方法,以及私有的 PreparingSetDataStdInd 预处理函数,最多接 6 个 CBuffer 指针。 开 MT5 把这段塞进你自己的指标封装类,改 num_bars 和 series_index_start 就能控制重绘范围;外汇与贵金属波动大、滑点高,缓冲颜色逻辑仅作可视化参考,实盘信号须自行验证。

MQL5 / C++
   class=class="str">"cmt">//--- In a loop, by the number of bars in  num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index
   class=class="str">"cmt">//--- and set the drawn buffer class="type">class="kw">color depending on a proportion of value00 and value01 values
   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_ICHIMOKU :
     class="kw">break;

   class="kw">default:
     class="kw">break;
   }
 class="kw">return class="kw">false;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Clear buffer data of(class="num">1) the specified standard indicator, (class="num">2) all the created standard indicators by the timeseries index
   class="type">void                 ClearDataBufferStdInd(class="kw">const ENUM_INDICATOR std_ind,class="kw">const class="type">int id,class="kw">const class="type">int series_index);
   class="type">void                 ClearDataAllBuffersStdInd(class="type">int series_index);
class=class="str">"cmt">//--- Set values for the current chart to buffers of the specified standard indicator by the timeseries index in accordance with buffer object symbol/period
   class="type">bool                 SetDataBufferStdInd(class="kw">const ENUM_INDICATOR std_ind,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);
class="kw">private:
class=class="str">"cmt">//--- Prepare data of the specified standard indicator for setting values on the current symbol chart
   class="type">int                  PreparingSetDataStdInd(CBuffer *buffer_data0,CBuffer *buffer_data1,CBuffer *buffer_data2,
                                               CBuffer *buffer_calc0,CBuffer *buffer_calc1,CBuffer *buffer_calc2,
                                               class="kw">const ENUM_INDICATOR ind_type,
                                               class="kw">const class="type">int series_index,

标准指标缓冲区的取数接口拆解

在 MT5 自建指标框架里,跨周期取标准指标数值通常走一个带多引用的函数签名。下面这段声明把时间、周期、索引和六个 double 引用一次性传进去,调用方据此回填矩阵式缓冲。 const datetime series_time, int &index_period, int &num_bars, double &value00, double &value01, double &value10, double &value11, double &value20, double &value21); 逐行看:series_time 锁定要取数的 K 线时间;index_period 以引用传入周期,函数内可能改写;num_bars 是实际参与计算的柱数。value00~value21 这六个引用对应三类双缓冲(每组两个),常见的如主线/信号线各一对,调用后由函数填值。 public 段注释写明,缓冲定位靠图形序列名、时间帧、Plot 索引、集合内对象序号、最近创建标记、缓冲 ID、标准指标类型与类型 ID 八要素。PreparingSetDataStdInd 方法就是按这些要素把标准指标数据铺到当前品种图表。 CBuffersCollection::PreparingSetDataStdInd 形参里出现 buffer_data0~2 与 buffer_calc0~2 两组三指针,说明展示缓冲与计算缓冲分离;ind_type 指定 ENUM_INDICATOR 标准类型,series_index 定位集合对象,series_time 与 index_period 复用前述含义。实盘接这段代码时,先确认自己图表周期与 index_period 不一致是否会触发重算,外汇与贵金属波动剧烈,跨周期取数延迟可能放大滑点风险。

MQL5 / C++
class="kw">const class="type">class="kw">datetime series_time,
class="type">int &index_period,
class="type">int &num_bars,
class="type">class="kw">double &value00,
class="type">class="kw">double &value01,
class="type">class="kw">double &value10,
class="type">class="kw">double &value11,
class="type">class="kw">double &value20,
class="type">class="kw">double &value21);
class="kw">public:
class=class="str">"cmt">//--- Return the buffer(class="num">1) by the graphical series name, (class="num">2) timeframe,
class=class="str">"cmt">//--- (class="num">3) Plot index, (class="num">4) object index in the collection list, (class="num">5) the last created,
class=class="str">"cmt">//--- buffer list(class="num">6) by ID, (class="num">7) standard indicator type, (class="num">8) type and ID
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Prepare data of the specified standard indicator                |
class=class="str">"cmt">//| for setting values on the current symbol chart                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CBuffersCollection::PreparingSetDataStdInd(CBuffer *buffer_data0,CBuffer *buffer_data1,CBuffer *buffer_data2,
                     CBuffer *buffer_calc0,CBuffer *buffer_calc1,CBuffer *buffer_calc2,
                     class="kw">const ENUM_INDICATOR ind_type,
                     class="kw">const class="type">int series_index,
                     class="kw">const class="type">class="kw">datetime series_time,
                     class="type">int &index_period,

◍ 跨周期取值时的索引对齐细节

在把多周期指标缓冲同步到主图时,先用 iBarShift 按当前 bar 的开盘时间找到目标周期对应的 bar 索引。若返回 WRONG_VALUE 或该索引已超过缓冲总数据量减 1,函数直接退出,避免越界读空值。 随后从三个缓冲各取索引位置的第 0 列数据赋给 value00、value10、value20,前提是对应缓冲指针非空。这里只取主线缓冲,不碰副缓冲,减少不必要的内存访问。 当前图表与计算缓冲同品种同周期时,只处理 1 根 bar(num_bars=1),不需要回溯统计。否则用 iTime 拿目标周期 bar 时间,再反查主图 iBarShift 得到 series_index_start;若 time_period 为 0 或反查失败也直接返回,保证跨周期映射至少落在已知 bar 上。外汇与贵金属跨周期调用受点差和跳空影响,实际对齐可能偏 1 根,建议开 MT5 用不同品种周期跑这段验证偏移。

MQL5 / C++
class="type">int &num_bars,
              class="type">class="kw">double &value00,
              class="type">class="kw">double &value01,
              class="type">class="kw">double &value10,
              class="type">class="kw">double &value11,
              class="type">class="kw">double &value20,
              class="type">class="kw">double &value21)
  {
   class=class="str">"cmt">//--- Find bar index on a period which corresponds to the time of current bar beginning
   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 WRONG_VALUE;
   
   class=class="str">"cmt">//--- Get the value by this index from indicator buffer
   if(buffer_calc0!=NULL)
      value00=buffer_calc0.GetDataBufferValue(class="num">0,index_period);
   if(buffer_calc1!=NULL)
      value10=buffer_calc1.GetDataBufferValue(class="num">0,index_period);
   if(buffer_calc2!=NULL)
      value20=buffer_calc2.GetDataBufferValue(class="num">0,index_period);
   
   class="type">int series_index_start=series_index;
   class=class="str">"cmt">//--- For the current chart we don’t need to calculate a number of bars processed - only one bar is available
   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 which the bar with index_period index falls into on a period and symbol of calculated buffer
      class="type">class="kw">datetime time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
      if(time_period==class="num">0) class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Get the current chart bar which corresponds to the time
      series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
      if(series_index_start==WRONG_VALUE) class="kw">return WRONG_VALUE;

「缓冲对象按时间帧换算填充根数」

跨时间帧把指标缓冲数据搬到当前图表时,先得算清楚要填几根 K 线。核心一句是用目标缓冲的时间帧秒数除以当前图表周期秒数:num_bars=PeriodSeconds(buffer_calc0.Timeframe())/PeriodSeconds(PERIOD_CURRENT); 若结果为 0 则强制置 1,避免空循环。 取色用的参考值也按这个偏移量向后挪:当 series_index_start+num_bars 超过对应数据缓冲总量减 1 时,退回用当前值 value00/value10/value20,否则取该索引位的 GetDataBufferValue(0, ...)。这三组判断分别覆盖三个缓冲计算对象,缺哪个(NULL)就跳过。 SetDataBufferStdInd 里先把同类型同 ID 的缓冲对象捞出来,分成 drawn 与 calculated 两个子列表;任一为空直接返回 false。随后声明双份缓冲指针与六枚 double 值(value00/01、value10/11、value20/21),为后面逐根回写标准指标缓冲做准备。外汇与贵金属行情跳空频繁,跨周期映射时 num_bars 算错可能导致缓冲错位,建议开 MT5 在 EURUSD 的 M1 图表挂一个 H1 缓冲指标单步验证。

MQL5 / C++
   class=class="str">"cmt">//--- Calculate the number of bars on the current chart which are to be filled in 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 for class="type">class="kw">color calculation
    if(buffer_calc0!=NULL)
      value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-class="num">1 ? value00 : buffer_data0.GetDataBufferValue(class="num">0,series_index_start+num_bars));
    if(buffer_calc1!=NULL)
      value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-class="num">1 ? value10 : buffer_data1.GetDataBufferValue(class="num">0,series_index_start+num_bars));
    if(buffer_calc2!=NULL)
      value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-class="num">1 ? value20 : buffer_data2.GetDataBufferValue(class="num">0,series_index_start+num_bars));

    class="kw">return series_index_start;
    }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Sets values for the current chart to buffers of the specified
class=class="str">"cmt">//| standard indicator by the timeseries index in accordance
class=class="str">"cmt">//| with buffer object symbol/period
class=class="str">"cmt">//+------------------------------------------------------------------+
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)
  {
class=class="str">"cmt">//--- Get the list of buffer objects by type and ID
   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 class="kw">false;
     }
class=class="str">"cmt">//--- Get the list of drawn buffers with ID
   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);
class=class="str">"cmt">//--- Get the list of calculated buffers with ID
   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);
class=class="str">"cmt">//--- Leave if any of the lists is empty
   if(list_data.Total()==class="num">0 || list_calc.Total()==class="num">0)
      class="kw">return class="kw">false;

class=class="str">"cmt">//--- Declare necessary objects and variables
   CBuffer *buffer_data0=NULL;
   CBuffer *buffer_data1=NULL;
   CBuffer *buffer_data2=NULL;
   CBuffer *buffer_calc0=NULL;
   CBuffer *buffer_calc1=NULL;
   CBuffer *buffer_calc2=NULL;
   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="type">long vol0=class="num">0,vol1=class="num">0;
   class="type">int series_index_start=series_index,index_period=class="num">0, index=class="num">0,num_bars=class="num">1;
   class="type">uchar clr=class="num">0;

单缓冲标准指标的取数分支

在 MT5 自建指标封装里,先按枚举把标准指标分成单缓冲和多缓冲两类。上面这段 switch 把 AC、AD、AMA、AO、ATR、BearsPower、BullsPower、BWMFI、CCI、Chaikin、DEMA、DeMarker、Force、FRAMA、MA、MFI、Momentum、OBV、OsMA、RSI、SAR、StdDev、TEMA、TRIX、VIDYA、Volumes、WPR 全部归到单缓冲分支,共 27 个宏常量。 进入分支后,用 CSelect::ByBufferProperty 按 BUFFER_PROP_IND_LINE_MODE 等于 0 过滤,分别从数据缓冲列表和计算缓冲列表取出第 0 个对象赋给 buffer_data0 与 buffer_calc0。若两者任一为空,或 buffer_calc0 的总数据量为 0,函数直接返回 false,避免后续越界。 随后调用 PreparingSetDataStdInd,传入六组缓冲指针、指标类型、序列索引、时间数组、周期索引、所需 bar 数以及六个值引用。返回值若为 WRONG_VALUE 同样返回 false;正常时拿到的 series_index_start 会作为循环起点,按 num_bars 数量把计算缓冲中 index_period 位置的值逐根填进绘制缓冲。 别把正态当圣经 单缓冲分支覆盖的指标里,RSI 在贵金属 1 小时图上常出现缓冲长度为 0 的初始化空窗,实盘前应在 MT5 策略测试器里先跑 200 根以上确认 buffer_calc0.GetDataTotal(0) 非 0。外汇与贵金属杠杆高,缓冲取数异常可能导致信号漏画,须以历史回放验证。

MQL5 / C++
class=class="str">"cmt">//--- Depending on 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      :
         list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,class="num">0,EQUAL);
         buffer_data0=list.At(class="num">0);
         list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,class="num">0,EQUAL);
         buffer_calc0=list.At(class="num">0);
         
         if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(class="num">0)==class="num">0)
           class="kw">return class="kw">false;
         
         series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_calc0,buffer_calc1,buffer_calc2,
                                   ind_type,series_index,series_time,index_period,num_bars,value00,value01,value10,value11,value20,value21);
         if(series_index_start==WRONG_VALUE)
           class="kw">return class="kw">false;
         class=class="str">"cmt">//--- In a loop, by the number of bars in  num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index

◍ 双缓冲指标的颜色与成交量判定

这段逻辑负责给绘制缓冲上色,核心是比较 value00 与 value01 两个序列值的相对大小。普通指标类型直接按大小映射成 0/1/2 三种色标,若 color_index 不是 WRONG_VALUE 则强制用指定色。 当指标类型为 IND_BWMFI(布林带宽度资金流量指数)时,规则变复杂:不仅要比 value00 和 value01,还要拉出两根 K 线的成交量 vol0 与 vol1。代码里用 iVolume 取 index_period 和 index_period+1 位置的成交量,组合出 5 种状态色标 0~4,把「价量同向 / 背离」编码进颜色。 循环从 0 到 num_bars-1,逐根回填 buffer_data0 的数值与颜色索引。注意 value00>value01 且 vol0<vol1 被归为色标 2,这是一种量价背离的可视化标记,在 MT5 里打开 BWMFI 类指标能直接看到此类异色点。 下方多缓冲标准指标分支(IND_ENVELOPES、IND_FRACTALS)先用 CSelect::ByBufferProperty 按 IND_LINE_MODE 0/1 挑出上下轨缓冲,再校验 calc 与 data 缓冲非空且数据量大于 0,任一缺失立即 return false,避免空指针绘制崩溃。

MQL5 / C++
class=class="str">"cmt">//--- and set the drawn buffer class="type">class="kw">color depending on a proportion of value00 and value01 values
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=
        (
         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_ENVELOPES :
case IND_FRACTALS  :
   list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,class="num">0,EQUAL);
   buffer_data0=list.At(class="num">0);
   list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,class="num">1,EQUAL);
   buffer_data1=list.At(class="num">0);

   list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,class="num">0,EQUAL);
   buffer_calc0=list.At(class="num">0);
   list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,class="num">1,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 class="kw">false;
   if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
     class="kw">return class="kw">false;

「多缓冲指标的颜色与数据回填」

上面这段逻辑处理的是多线指标(ADX、MACD、布林带、 stochastic 等)在绘制缓冲与计算缓冲之间的映射。先用 CSelect::ByBufferProperty 按 BUFFER_PROP_IND_LINE_MODE 分别抓出 line mode 为 0、1、2 的三组缓冲,绘制侧与计算侧各取一套,保证主图线和计算线一一对应。 回填前有两道空值闸门:buffer_calc0 和 buffer_data0 任一为 NULL,或 calc0 的数据总量 GetDataTotal(0)==0,直接 return false;buffer_calc1 与 buffer_data1 同样要过一遍。这意味着若指标刚加载、历史根数不足,函数会安静退出而不是画错线。 前面的循环段则针对 num_bars 根 K 线,把 value00/value10 写进绘制缓冲,并按 value00 与 value01 的大小比例决定颜色索引:大于取 0、小于取 1、相等取 2;若外部已强制 color_index,则无视比较结果沿用指定色。外汇与贵金属波动大,这类着色逻辑只反映历史比例关系,不预示后续方向,实际信号需结合结构判断。

MQL5 / C++
if(series_index_start==WRONG_VALUE)
   class="kw">return class="kw">false;
class=class="str">"cmt">//--- In a loop, by the number of bars in  num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index
class=class="str">"cmt">//--- and set the drawn buffer class="type">class="kw">color depending on a proportion of value00 and value01 values
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_ADX        :
case IND_ADXW       :
case IND_BANDS      :
case IND_MACD       :
case IND_RVI        :
case IND_STOCHASTIC :
case IND_ALLIGATOR  :
   list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,class="num">0,EQUAL);
   buffer_data0=list.At(class="num">0);
   list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,class="num">1,EQUAL);
   buffer_data1=list.At(class="num">0);
   list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,class="num">2,EQUAL);
   buffer_data2=list.At(class="num">0);
   
   list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,class="num">0,EQUAL);
   buffer_calc0=list.At(class="num">0);
   list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,class="num">1,EQUAL);
   buffer_calc1=list.At(class="num">0);
   list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,class="num">2,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 class="kw">false;
   if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(class="num">0)==class="num">0)
      class="kw">return class="kw">false;

缓冲校验与多缓冲着色循环的实现细节

在自定义指标里往绘制缓冲写数之前,必须先确认计算缓冲和数据缓冲都不是空指针,且计算缓冲里至少有 1 根柱子的数据。若 buffer_calc2 为 NULL、或 buffer_data2 为 NULL、或 buffer_calc2.GetDataTotal(0)==0,函数直接 return false,避免后面越界读写。 随后调用 PreparingSetDataStdInd 去定位起始序列索引 series_index_start。这个函数会按 ind_type、周期、num_bars 等参数把三套缓冲对齐;如果返回 WRONG_VALUE,同样 return false,说明数据准备阶段出了问题,不会进入填值循环。 进入 for(i=0;i<num_bars;i++) 后,每一根柱子 index = series_index_start - i,依次把 value00/value10/value20 写进 buffer_data0/1/2 的对应缓冲位(0、1、2 号)。着色逻辑是:当 color_index 等于 WRONG_VALUE 时,按当前值与对照值(value01/11/21)的大小比较决定颜色索引——大于取 0、小于取 1、相等取 2;若外部已指定 color_index,则整段统一用该色。 Gator 和 Ichimoku 在 switch 里只写了 break,说明这两类指标走另一分支或暂未在此函数内处理。开 MT5 把这段贴进你的指标源码,改 num_bars 和 value 对照组,能在可视化上直接看出缓冲着色是否按预期切换。外汇与贵金属波动剧烈,这类着色逻辑仅辅助判读,不预示方向。

MQL5 / C++
if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(class="num">0)==class="num">0)
     class="kw">return class="kw">false;

   series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_calc0,buffer_calc1,buffer_calc2,
                           ind_type,series_index,series_time,index_period,num_bars,value00,value01,value10,value11,value20,value21);
   if(series_index_start==WRONG_VALUE)
      class="kw">return class="kw">false;
class=class="str">"cmt">//--- In a loop, by the number of bars in  num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index
class=class="str">"cmt">//--- and set the drawn buffer class="type">class="kw">color depending on a proportion of value00 and value01 values
   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_GATOR   :
   case IND_ICHIMOKU :
      class="kw">break;

   class="kw">default:
      class="kw">break;
   }
 class="kw">return class="kw">false;

◍ 主窗指标位移的实测差异

把上一篇文章的测试指标另存为 TestDoEasyPart50.mq5,丢进 \MQL5\Indicators\TestDoEasy\Part50\ 目录,再补一个外部参数 InpShift 专门控制曲线位移。标准指标大多画在主窗口,鳄鱼振荡器那种子窗口显示暂未实现,所以 OnInit() 里只建主窗口标准指标对象,并把 InpShift 传进创建方法。 鳄嘴(Alligator)三条线自带位移逻辑,作者把默认值写死在创建时的指标理念里,外部 InpShift 对它不起作用;而布林带、MA 这类单缓冲指标会直接吃外部位移值。原先子窗口级别与数据窗口显示的代码块,在最终指标里删掉,改到 \MQL5\Include\DoEasy\Services\DELib.mqh 写个新函数替掉,OnInit() 末尾调一下就行,代码更干净。 编译后挂 EURUSD H1,设置里预先选 EURUSD H4,给曲线设位移 4 根柱线再切到布林带:布林带真的右移 4 根柱,鳄嘴没反应——因为它创建时用了和标准指标相等的默认位移。换鳄嘴本身则能正常按标准位移画。外汇与贵金属波动剧烈,这类位移只是视觉错位辅助,不改变信号概率,实盘前请在 MT5 策略测试器自行验证。 下边这段 input 与 switch 创建是位移生效的关键,注意高亮行 InpShift 如何透传:

MQL5 / C++
class=class="str">"cmt">//--- class="kw">input variables
sinput  class="type">class="kw">string                InpUsedSymbols  =  "GBPUSD";      class=class="str">"cmt">// Used symbol(one only)
sinput  ENUM_TIMEFRAMES       InpPeriod       =  PERIOD_M30;     class=class="str">"cmt">// Used chart period
sinput  ENUM_INDICATOR        InpIndType      =  IND_AC;         class=class="str">"cmt">// Type standard indicator
sinput  class="type">int                   InpShift        =  class="num">0;              class=class="str">"cmt">// Indicator line shift
class=class="str">"cmt">//---
sinput  class="type">bool                  InpUseSounds    =  true;           class=class="str">"cmt">// Use sounds
class=class="str">"cmt">//--- indicator buffers
class=class="str">"cmt">//--- indicator buffers mapping
class=class="str">"cmt">//--- Create all the necessary buffer objects for constructing the selected standard indicator
  class="type">bool success=class="kw">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,InpShift,PRICE_CLOSE,class="num">1);             class="kw">break;
    case IND_DEMA       :  success=engine.BufferCreateDEMA(InpUsedSymbols,InpPeriod,class="num">14,InpShift,PRICE_CLOSE,class="num">1);                 class="kw">break;
    case IND_FRAMA      :  success=engine.BufferCreateFrAMA(InpUsedSymbols,InpPeriod,class="num">14,InpShift,PRICE_CLOSE,class="num">1);                class="kw">break;
    case IND_MA         :  success=engine.BufferCreateMA(InpUsedSymbols,InpPeriod,class="num">10,InpShift,MODE_SMA,PRICE_CLOSE,class="num">1);          class="kw">break;

「指标派发里的缓冲区与精度分支」

这段 switch 把不同指标类型映射到引擎的 BufferCreate 系列方法,主窗口里的多缓冲指标(鳄鱼、布林、包络、分形)都在这层统一建缓冲。SAR 写死加速步长 0.02、最大值 0.2;TEMA 周期 14 且吃 InpShift 偏移;VIDYA 用 9 和 12 双周期。 Alligator 的参数是 13/8/8/5/5/3,分别对应颚、齿、唇的平滑周期与偏移,平滑模式 MODE_SMMA、价格 PRICE_MEDIAN。Bands 默认 20 周期、偏离 2.0;Envelopes 偏差 0.1;Fractals 只需周期 1。 若 success 为假,直接 Print 报错并返回 INIT_FAILED,EA 初始化失败,不会静默跑空。 下半段按指标类别重设 digits:AD、Chaikin、OBV、Volumes 这类量能类强制 digits=0,其余沿用 SymbolInfoInteger 拿到的 SYMBOL_DIGITS。外汇与贵金属点值精度差异大,这步决定缓冲区数值显示与后续比大小的逻辑,实操中改完 InpIndType 最好到 MT5 用数据窗口核对位数。

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

class=class="str">"cmt">//--- Multi-buffer standard indicators in the main window
case IND_ALLIGATOR    :  success=engine.BufferCreateAlligator(InpUsedSymbols,InpPeriod,class="num">13,class="num">8,class="num">8,class="num">5,class="num">5,class="num">3,MODE_SMMA,PRICE_MEDIAN,class="num">1);   class="kw">break;
case IND_BANDS        :  success=engine.BufferCreateBands(InpUsedSymbols,InpPeriod,class="num">20,InpShift,class="num">2.0,PRICE_CLOSE,class="num">1);               class="kw">break;
case IND_ENVELOPES    :  success=engine.BufferCreateEnvelopes(InpUsedSymbols,InpPeriod,class="num">14,InpShift,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">//--- Set the levels where they are required and determine data 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          :

按指标类型自动配小数位与水准线

在自定义指标初始化时,用 switch 对指示器类型分流,可以省掉手工逐个设小数位(digits)和水平线的麻烦。下面这段分支逻辑把十几种常见指标归了类:熊力、牛力、Force、标准差、AMA、DEMA、FRAMA、MA、TEMA、VIDYA、布林带、包络线、MACD 统一 digits+1;AC 和 OSMA 是 digits+2;Momentum 直接锁 digits=2。 CCI 和 DeMarker 除了调小数位,还用 IndicatorSetInteger(INDICATOR_LEVELS,2) 设了两条参考线。CCI 放在 ±100,DeMarker 放在 0.7 与 0.3,对应的 digits 分别是 2 和 3。 MFI 与 RSI 只设水平线不设 digits 覆盖:MFI 两条线为 80 / 20,RSI 三条线起点是 70。外汇与贵金属波动剧烈,这类超买超卖线仅作概率参考,实盘须结合风控,杠杆品种高风险。 开 MT5 新建指标把这段 case 贴进 OnInit,编译后切到 XAUUSD 的 M15,看 CCI 是否自动画出 ±100 双线,能立刻验证分支是否生效。

MQL5 / C++
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);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">70);

◍ 在 MT5 里按指标类型写死基准水平线

给自定义指标封装层做初始化时,不同振荡类指标的水平线不能混用一套数值。上面这段 switch 逻辑针对 RSI 设了 50 / 30 两条线,随机指标(Stochastic)用 80 / 20,WPR 则是 -80 / -20,都是双水平、保留两位小数。 RSI 的 50 中轴配合 30 超卖线,在欧元兑美元这种主流货币对上,价格触碰 30 后反弹的概率倾向高于单纯看裸 K。但外汇和贵金属杠杆高,水平线只是参考,不代表反转必然发生。 ATR、SAR、TRIX 这几个分支直接 break,不挂任何水平线——它们本就不是固定区间振荡器,硬画线反而误导。default 分支把 INDICATOR_LEVELS 清零,避免上一个品种残留线条。 下面另有一个 SetIndicatorLevels 函数,按 symbol 取 SYMBOL_DIGITS 决定小数位;AD、Chaikin、OBV、Volumes 这类量能类直接 digits=0,而 AO、Bears 等未写完的分支也走空 break。复制这段代码到你的指标 OnInit 里,改 InpIndType 就能快速验证水平线是否按预期加载。

MQL5 / C++
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 a class="type">class="kw">short name for the indicator and data capacity
   class="type">class="kw">string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,class="num">1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set capacity and levels to standard indicator                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SetIndicatorLevels(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_INDICATOR ind_type)
  {
   class="type">int digits=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   class="kw">switch(ind_type)
    {
     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      :

「按指标类型自动配小数点与参考线」

在 MT5 自定义指标里,用 switch 按枚举类型统一处理显示精度,能省掉大量重复代码。下面这段把布林带、MACD、AMA 等一众趋势/波动类指标都归到同一分支,digits 变量额外加 1,意味着这类指标在面板里比默认多显示一位小数,EURUSD 的 M15 周期上读数会从 1.234 变成 1.2345 这种粒度。 AC 和 OSMA 加 2 位、Momentum 直接锁 2 位,属于对零轴附近摆荡指标的细分处理。CCI 除了定 2 位小数,还用 IndicatorSetInteger 设了 2 条水平线,分别在 +100 和 -100——这是 CCI 传统超买超卖边界,黄金 XAUUSD 的 H1 上刺穿这两个值往往暗示均值回归概率升高。 DeMarker 的水平线设在 0.7 与 0.3,小数位给到 3 位;MFI 只画线不设 digits,说明它沿用父级精度。RSI 则设了 3 条线:70、50、30,50 是中轴,外汇品种里 RSI 碰 70/30 后反转倾向较强,但高杠杆下仍属高风险信号。 把这套分支直接拷进你的 OnInit,就能在加载指标时自动规整显示,不用每个品种手调。

MQL5 / C++
   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);
      IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,class="num">70);
      IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">50);

摆荡类指标的临界线硬编码

在 MT5 自定义指标初始化阶段,针对随机指标与 WPR 这类摆荡器,代码直接写死了超买超卖参考线。随机指标设了 80 / 20 双水平,WPR 则取 -80 / -20,两者都保留两位小数精度,属于典型的「不靠外部输入、编译即定」做法。 这种写法好处是加载就出线,不用交易者手动补水平;但坏处也明显——若你做跨周期黄金剥头皮,默认 80/20 在 15M 上可能频繁假信号,倾向需要按品种波动率重设。 ATR、SAR、TRIX 在 switch 里直接 break 不设水平线,说明它们本就不依赖固定阈值;default 分支把水平数归零,避免残留上一种指标的线。最后统一用 IndicatorSetInteger(INDICATOR_DIGITS,digits) 收口精度。 开 MT5 把下面这段塞进你的指标 OnInit,编译后切到 EURUSD 看随机指标是否自动带 80/20 线,就能验证这套逻辑。

MQL5 / C++
      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;
   }
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Set a class="type">class="kw">short name for the indicator, data capacity and levels
   class="type">class="kw">string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,class="num">1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   SetIndicatorLevels(InpUsedSymbols,InpIndType);
class=class="str">"cmt">//--- Succeeded
   class="kw">return(INIT_SUCCEEDED);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Multi-buffer standard indicators in the main window
         case IND_ALLIGATOR  :   success=engine.BufferCreateAlligator(InpUsedSymbols,InpPeriod,class="num">13,class="num">8,class="num">8,class="num">5,class="num">5,class="num">3,MODE_SMMA,PRICE_MEDIAN,class="num">1);      class="kw">break;

◍ MT4 兼容还得等下一轮

这一篇把多周期多品种标准指标的对象开发收了尾,顺带把当前函数库所有文件和测试指标打包放出,ZIP 体积 3761.39 KB,仅适配 MT5。MT4 端目前还没跑过缓冲区相关功能,作者明确说要等开发测试完指标缓冲区后,再尝试把部分 MQL5 特性移植过去。 如果你手头只有 MT4 终端,现在直接塞这套库大概率编译不过或缓冲区行为异常,别白费工夫。有 MT5 的可以直接下附件开终端验证多品种多周期刷新逻辑。 外汇和贵金属杠杆高、滑点跳空频繁,这类多周期指标只解决数据组织问题,不替代仓位和风控。等 MT4 分支出来前,先用 MT5 跑通再谈迁移。

常见问题

位移是基于各自周期 K 线序号的,跨周期直接搬序号会错位;应先按时间把高周期柱对应到低周期索引,再取缓冲值。
用缓冲区索引 0 配合柱序号 0 和 1 分别取当前与前值,避免复用同一变量导致赋值覆盖。
可以,小布能按你给的品种和周期组合跑一遍取数逻辑,标出错位缓冲和空值位置,省去手动逐根核对。
上轨、中轨、下轨对应缓冲 0、1、2,分别用不同画笔色赋值,注意只给可见柱填色避免重绘卡顿。
ADX 双缓冲位移要和主周期时间对齐后再偏移,否则高周期未成型的柱会返回空值导致信号丢失。