DoEasy 函数库中的时间序列(第四十八部分):在单一子窗口里基于一个缓冲区的多周期、多品种指标·进阶篇
📘

DoEasy 函数库中的时间序列(第四十八部分):在单一子窗口里基于一个缓冲区的多周期、多品种指标·进阶篇

第 2/3 篇

指标枚举的空分支骨架

这段 switch 结构把 MT5 内置指标常量挨个列了一遍,从布林带、熊力牛力到 Ichimoku、MACD、RSI 共 30 个 IND_* 宏,外加一个 default,但每个 case 里只写了 break,没有任何实际处理逻辑。 它在代码层的作用相当于一张「指标清单占位表」:你后续要给某个指标写专属计算或绘图分支,直接在这个对应 case 里填代码即可,不用再管枚举值是否合法。 开 MT5 按 F4 进 MetaEditor,搜 IND_BULLS 或 IND_WPR 就能看到标准枚举定义;当前这段若原样编译,运行时走到任何指标分支都会立刻跳出,不会报错也不会出图。 别把占位当实现 空 break 只是防止编译警告,不代表指标已接入。真要跑信号,得在 case IND_RSI: 这类分支里补 iRSI 调用与缓冲区绑定,否则 EA 对行情毫无反应。

MQL5 / C++
   case IND_BANDS :
      class="kw">break;
   case IND_BEARS :
      class="kw">break;
   case IND_BULLS :
      class="kw">break;
   case IND_BWMFI :
      class="kw">break;
   case IND_CCI :
      class="kw">break;
   case IND_CHAIKIN :
      class="kw">break;
   case IND_DEMA :
      class="kw">break;
   case IND_DEMARKER :
      class="kw">break;
   case IND_ENVELOPES :
      class="kw">break;
   case IND_FORCE :
      class="kw">break;
   case IND_FRACTALS :
      class="kw">break;
   case IND_FRAMA :
      class="kw">break;
   case IND_GATOR :
      class="kw">break;
   case IND_ICHIMOKU :
      class="kw">break;
   case IND_MA :
      class="kw">break;
   case IND_MACD :
      class="kw">break;
   case IND_MFI :
      class="kw">break;
   case IND_MOMENTUM :
      class="kw">break;
   case IND_OBV :
      class="kw">break;
   case IND_OSMA :
      class="kw">break;
   case IND_RSI :
      class="kw">break;
   case IND_RVI :
      class="kw">break;
   case IND_SAR :
      class="kw">break;
   case IND_STDDEV :
      class="kw">break;
   case IND_STOCHASTIC :
      class="kw">break;
   case IND_TEMA :
      class="kw">break;
   case IND_TRIX :
      class="kw">break;
   case IND_VIDYA :
      class="kw">break;
   case IND_VOLUMES :
      class="kw">break;
   case IND_WPR :
      class="kw">break;
   
   class="kw">default:
      class="kw">break;
   }

「单缓冲子窗口指标的批量填充逻辑」

在自定义指标库里,CBuffersCollection::PreparingDataBufferStdInd 负责把标准指标的计算缓冲搬到自己的缓冲集合。它先按指标类型和 id 取出列表,再用 ByBufferProperty 筛出 BUFFER_TYPE_CALCULATE 类型的对象;若列表为空就打印错误并返回 0。 对于 AC、AD、AO、ATR、CCI、RSI 等 19 个单缓冲且绘制在子窗口的标准指标,代码走同一个 case 分支:取列表第 0 个缓冲对象,调用 FillAsSeries 按 IndicatorHandle、起始位 0、偏移 0、复制量 total_copy 把序列数据填进去,返回实际拷贝数。 ADX 与 ADXW 在当前实现里只是 case 占位并 break,ALLIGATOR 等多缓冲指标的分支在原文后续展开。开 MT5 把这段贴进同类封装,改 total_copy 为 500 可验证 EURUSD 的 RSI 缓冲是否一次拉满 500 根。

MQL5 / C++
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=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.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 copies=WRONG_VALUE;
   class="kw">switch((class="type">int)std_ind)
     {
      class=class="str">"cmt">//--- Single-buffer standard indicators in a subwindow
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :
        buffer=list.At(class="num">0);
        if(buffer==NULL) class="kw">return class="num">0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),class="num">0,class="num">0,total_copy);
        class="kw">return copies;

      case IND_ADX :
        class="kw">break;
      case IND_ADXW :
        class="kw">break;
      case IND_ALLIGATOR :

◍ 标准指标缓冲清理的空壳分支

这段 switch 结构出现在某指标缓冲区管理类的清理函数里,针对 ENUM_INDICATOR 枚举逐个 case 留空 break,相当于给 AMA、BANDS、DEMA、ICHIMOKU、MACD 等 18 种内置指标都预留了挂钩点却暂未实现具体清缓冲逻辑。 从代码可见,函数先通过 GetListBufferByTypeID 拿到指定类型和 ID 的缓冲对象列表,若为空直接 return;再用 CSelect::ByBufferProperty 筛出 BUFFER_TYPE_DATA 类型的缓冲,无数据也退出。 注释里点出 IND_AC、IND_AD、IND_AO 属于「子窗口单缓冲标准指标」,这类品种在 ClearDataBufferStdInd 里同样只是 case 穿透到 break。开 MT5 在 MetaEditor 搜 CBuffersCollection::ClearDataBufferStdInd,能看到整套枚举 case 目前都是占位,若你要接自己的缓冲回收逻辑,从这几个子窗口单缓冲分支下手最干净。

MQL5 / C++
      class="kw">break;
      case IND_AMA :
         class="kw">break;
      case IND_BANDS :
         class="kw">break;
      case IND_BWMFI :
         class="kw">break;
      case IND_DEMA :
         class="kw">break;
      case IND_ENVELOPES :
         class="kw">break;
      case IND_FRACTALS :
         class="kw">break;
      case IND_FRAMA :
         class="kw">break;
      case IND_GATOR :
         class="kw">break;
      case IND_ICHIMOKU :
         class="kw">break;
      case IND_MA :
         class="kw">break;
      case IND_MACD :
         class="kw">break;
      case IND_RVI :
         class="kw">break;
      case IND_SAR :
         class="kw">break;
      case IND_STOCHASTIC :
         class="kw">break;
      case IND_TEMA :
         class="kw">break;
      case IND_VIDYA :
         class="kw">break;
      class="kw">default:
         class="kw">break;
      }
   class="kw">return class="num">0;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Clear buffer data of the specified standard indicator             |
class=class="str">"cmt">//| by the timeseries index                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
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)
  {
class=class="str">"cmt">//--- Get the list of buffer objects by type and ID
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   if(list==NULL || list.Total()==class="num">0)
      class="kw">return;
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list.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 in a subwindow
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :

单缓冲指标的统一空值处理

在 MT5 自定义指标的多类型分发逻辑里,ATR、RSI、CCI、OBV 这类只输出一条主缓冲区的指标,可以共用同一段收尾代码。它们没有副线缓冲,初始化时只需取 list 的第 0 号缓冲并写入空值占位。 下面这段 switch-case 把 17 个单缓冲枚举(IND_ATR 到 IND_WPR)归到一组:先拿 buffer=list.At(0),若为空直接 return 防崩,再用 SetBufferValue(0, series_index, buffer.EmptyValue()) 把当前序列位标成空值,最后 break。 而 ADX、布林带、MACD、Ichimoku 等 16 个多缓冲或特殊结构指标,在 case 里只写 break,说明它们的缓冲清理要在各自专属分支处理,不能套用上面的统一写法。 实盘接数据前,建议在 MT5 用 iATR 与 iRSI 各挂一个空图表验证:单缓冲分支走通后,对应序列位应显示为指标空白而非 0 值,外汇与贵金属波动剧烈,误把空值当 0 会扭曲后续信号概率。

MQL5 / C++
case IND_ATR:
case IND_BEARS:
case IND_BULLS:
case IND_CHAIKIN:
case IND_CCI:
case IND_DEMARKER:
case IND_FORCE:
case IND_MOMENTUM:
case IND_MFI:
case IND_OSMA:
case IND_OBV:
case IND_RSI:
case IND_STDDEV:
case IND_TRIX:
case IND_VOLUMES:
case IND_WPR:
   buffer=list.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 :
   class="kw">break;
case IND_ADXW :
   class="kw">break;
case IND_ALLIGATOR :
   class="kw">break;
case IND_AMA :
   class="kw">break;
case IND_BANDS :
   class="kw">break;
case IND_BWMFI :
   class="kw">break;
case IND_DEMA :
   class="kw">break;
case IND_ENVELOPES :
   class="kw">break;
case IND_FRACTALS :
   class="kw">break;
case IND_FRAMA :
   class="kw">break;
case IND_GATOR :
   class="kw">break;
case IND_ICHIMOKU :
   class="kw">break;
case IND_MA :
   class="kw">break;
case IND_MACD :
   class="kw">break;
case IND_RVI :
   class="kw">break;
case IND_SAR :
   class="kw">break;
case IND_STOCHASTIC :
   class="kw">break;

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

CBuffersCollection::SetDataBufferStdInd 这个函数负责把当前图表上标准指标的计算结果,按时间序列索引塞进指定的缓冲对象。它先通过 GetListBufferByTypeID 拿到同类型同 ID 的缓冲列表,列表为空就直接 Print 报错并返回 false,这一步决定了后续所有取数都不会在空对象上跑。 函数内部用 CSelect 按 BUFFER_PROP_TYPE 把缓冲拆成 DATA 和 CALCULATE 两类,再各自按 ind_type 过滤;只要其中一类总数为 0 就退出。这意味着单缓冲指标(如 IND_ATR、IND_CCI、IND_MOMENTUM)和多缓冲指标在写入前就被分开了,不会混写。 switch 里单缓冲标准指标列了一串:IND_AC、IND_AD、IND_AO、IND_ATR、IND_BEARS、IND_BULLS、IND_CHAIKIN、IND_CCI、IND_DEMARKER、IND_FORCE、IND_MOMENTUM 都走同一分支。你在 MT5 里接自己写的指标面板时,若发现 ATR 或 CCI 数值没刷新,优先查 list_data 和 list_calc 是否因属性过滤被清空,而不是怀疑指标本身算错。 外汇与贵金属波动剧烈,这类缓冲同步逻辑若漏掉某一帧,可能造成信号延迟,实盘前务必用历史数据回放验证。

MQL5 / C++
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 objects 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">//--- Exit 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 the necessary objects and variables
   CBuffer *buffer_data=NULL;
   CBuffer *buffer_calc=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">class="kw">datetime time_period=class="num">0;
   class="type">class="kw">double value0=EMPTY_VALUE, value1=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_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :

◍ 跨周期指标缓冲的取数对齐

上面这段 switch 分支集中处理了 MFI、OSMA、OBV、RSI、STDDEV、TRIX、VOLUMES、WPR 等单缓冲指标。它们的共性是:计算缓冲只有一个,绘制缓冲也只有一个,所以取数逻辑可以复用同一套。 核心动作是先拿到 list_data 和 list_calc 的第 0 号缓冲对象,任一为空或计算缓冲总数为 0 就直接返回 false,避免后续越界。接着用 iBarShift 按当前系列时间 series_time 在计算缓冲的周期与品种上反查柱索引 index_period,若返回 WRONG_VALUE 或超过末位索引同样退出。 同周期同品种时,series_index_start 直接等于传入的 series_index,num_bars 固定为 1。跨周期时则先用 iTime 取计算缓冲那根柱的起始时间,再在当前图表用 iBarShift 反查落点;num_bars 由两周期秒数之比得出(如 H1 对 M1 得 60),为 0 时兜底成 1。 最后 value1 取当前绘制起点向后推 num_bars 的根值,用于着色比较;若越界则回退用 value0。开 MT5 把这段塞进你的指标绘制函数,调不同 Timeframe 组合能看到缓冲对齐是否错位。外汇与贵金属跨周期回测存在滑点与时区偏差,结论仅代表代码逻辑层面的可能表现。

MQL5 / C++
case IND_MFI       :
case IND_OSMA      :
case IND_OBV       :
case IND_RSI       :
case IND_STDDEV    :
case IND_TRIX      :
case IND_VOLUMES   :
case IND_WPR       :
      class=class="str">"cmt">//--- Get drawn and calculated buffer objects
      buffer_data=list_data.At(class="num">0);
      buffer_calc=list_calc.At(class="num">0);
      if(buffer_calc==NULL || buffer_data==NULL || buffer_calc.GetDataTotal(class="num">0)==class="num">0) class="kw">return class="kw">false;
      
      class=class="str">"cmt">//--- Find the bar index corresponding to the current bar start time
      index_period=::iBarShift(buffer_calc.Symbol(),buffer_calc.Timeframe(),series_time,true);
      if(index_period==WRONG_VALUE || index_period>buffer_calc.GetDataTotal()-class="num">1) class="kw">return class="kw">false;
      class=class="str">"cmt">//--- Get the value by the index from the indicator buffer
      value0=buffer_calc.GetDataBufferValue(class="num">0,index_period);
      if(buffer_calc.Symbol()==::Symbol() && buffer_calc.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_calc.Symbol(),buffer_calc.Timeframe(),index_period);
         if(time_period==class="num">0) class="kw">return class="kw">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 class="kw">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_calc.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
         if(num_bars==class="num">0) num_bars=class="num">1;
        }
      class=class="str">"cmt">//--- Take values to calculate colors
      value1=(series_index_start+num_bars>buffer_data.GetDataTotal()-class="num">1 ? value0 : buffer_data.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

常见问题

在指标主循环里按品种+周期枚举逐个取数,用偏移量把各周期数据顺序写入同一缓冲,避免开多个子窗口。
以当前图表周期为基准,用 iBarShift 把大周期柱映射到小周期索引,空位填 EMPTY_VALUE 保持对齐。
可以,小布能读取你加载的指标逻辑并提示缓冲越界、周期对齐缺失等常见错误,省去手动逐行排查。
通常留空壳仅做结构占位,真正清理在统一空值处理里把未赋值位置写 EMPTY_VALUE 即可。
统一兜底减少重复代码,取数分支只管写有效值,无效位由末段集中填 EMPTY_VALUE 更不易漏。