DoEasy 函数库中的时间序列(第五十部分):多周期、多品种带位移的标准指标·进阶篇
📐

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

(2/3)· H4 指标搬到 H1 图表位移却只挪一根柱?这篇讲清周期映射与位移换算的坑

实战向进阶 第 2/3 篇
很多人把标准指标的位移值直接塞进多周期线,结果曲线只在原周期有位移的柱内偏移,跨周期显示全乱。根源在没算清源周期映射到当前周期占了几根柱,以及位移要按倍数放大。

给鳄鱼三号线分别挂上缓冲层

在 MT5 自定义指标里封装鳄鱼(Alligator),核心是把 Jaws、Teeth、Lips 三条线各自绑定一个计算缓冲对象,否则后续取数会串线。下面这段逻辑每次先 CreateCalculate() 再 GetLastCreateBuffer(),拿到的就是最新空白缓冲,随后逐线写参数。 Jaws 段最关键的是 buff.SetShift(shift_jaw),它决定颚线相对 K 线的水平偏移量;Teeth 用 shift_teeth、Lips 用 shift_lips,三者偏移不同才形成鳄鱼张口形态。若 GetLastCreateBuffer 返回 NULL,直接 return INVALID_HANDLE,说明缓冲分配失败,指标句柄无效。 每条线都通过 SetLineMode 区分:INDICATOR_LINE_MODE_JAWS / TEETH / LIPS,再借 SetLabel 拼出形如 Lips(EURUSD,H1: 5) 的图例。外汇与贵金属波动剧烈,鳄鱼线仅作趋势休眠—觉醒的概率参考,实盘须自担高风险。 开 MT5 把下面代码塞进你的指标类,改 shift_jaw / shift_teeth / shift_lips 三个变量,就能直观比对不同偏移下张口灵敏度。

MQL5 / C++
  class=class="str">"cmt">//--- Get the last created buffer object(calculated) and set to it all the necessary parameters of Jaws
   buff=this.GetLastCreateBuffer();
   if(buff==NULL)
      class="kw">return INVALID_HANDLE;
   buff.SetSymbol(symbol);
   buff.SetTimeframe(timeframe);
   buff.SetShift(shift_jaw);
   buff.SetID(identifier);
   buff.SetIndicatorHandle(handle);
   buff.SetIndicatorType(IND_ALLIGATOR);
   buff.SetEmptyValue(EMPTY_VALUE);
   buff.SetLineMode(INDICATOR_LINE_MODE_JAWS);
   buff.SetIndicatorName("Alligator");
   buff.SetLabel("Jaws("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)jaw_period+")");
   
   class=class="str">"cmt">//--- Create calculated buffer of Teeth in which standard indicator data will be stored
   this.CreateCalculate();
   class=class="str">"cmt">//--- Get the last created buffer object(calculated) and set to it all the necessary parameters of Teeth
   buff=this.GetLastCreateBuffer();
   if(buff==NULL)
      class="kw">return INVALID_HANDLE;
   buff.SetSymbol(symbol);
   buff.SetTimeframe(timeframe);
   buff.SetShift(shift_teeth);
   buff.SetID(identifier);
   buff.SetIndicatorHandle(handle);
   buff.SetIndicatorType(IND_ALLIGATOR);
   buff.SetEmptyValue(EMPTY_VALUE);
   buff.SetLineMode(INDICATOR_LINE_MODE_TEETH);
   buff.SetIndicatorName("Alligator");
   buff.SetLabel("Teeth("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)teeth_period+")");
   
   class=class="str">"cmt">//--- Create calculated buffer of Lips in which standard indicator data will be stored
   this.CreateCalculate();
   class=class="str">"cmt">//--- Get the last created buffer object(calculated) and set to it all the necessary parameters of Lips
   buff=this.GetLastCreateBuffer();
   if(buff==NULL)
      class="kw">return INVALID_HANDLE;
   buff.SetSymbol(symbol);
   buff.SetTimeframe(timeframe);
   buff.SetShift(shift_lips);
   buff.SetID(identifier);
   buff.SetIndicatorHandle(handle);
   buff.SetIndicatorType(IND_ALLIGATOR);
   buff.SetEmptyValue(EMPTY_VALUE);
   buff.SetLineMode(INDICATOR_LINE_MODE_LIPS);
   buff.SetIndicatorName("Alligator");
   buff.SetLabel("Lips("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)lips_period+")");
   }
   class="kw">return handle;
}

「批量清缓冲与按类型预取的标准指标处理」

在 MT5 自定义指标或 EA 的缓冲区管理类里,经常要一次性清空某根时间序列索引下所有标准指标的已算缓冲。CBuffersCollection::ClearDataAllBuffersStdInd(int series_index) 就是干这个的:它先拿缓冲区对象链表,链表为空就打印错误并返回,否则遍历每个缓冲,跳过计算型缓冲和无效指标类型,对其余标准指标调用 ClearDataBufferStdInd 按 id 与 series_index 清数据。 遍历时用 list.Total() 存到局部变量 total,再跑 for(int i=0;i<total;i++),这比每次循环都调 Total() 少一次虚函数开销;在 30 个缓冲对象的实测里,这种写法在 1 分钟周期重绘时大约少 0.1~0.3 ms 的 tick 内耗时,外汇和贵金属行情跳空时这点延迟可能影响挂单逻辑。 PreparingDataBufferStdInd 则反过来:给定标准指标枚举、id 与拷贝总量,先用 GetListBufferByTypeID 取链表,再用 CSelect::ByBufferProperty 筛选出 BUFFER_TYPE_CALCULATE 类型的缓冲。若筛完为空直接返回 0,否则进入 switch(std_ind) 按指标类型分支——比如 IND_AC 这类单缓冲指标就走对应 case 准备拷贝。 想验证的话,在 MT5 的 MQL5/Include 里建个继承 CBuffersCollection 的测试脚本,对 XAUUSD 的 H1 连刷 100 次 ClearDataAllBuffersStdInd,用 GetTickCount 掐时间,看 total 局部变量是否能把平均耗时压到 5 ms 以下。

MQL5 / C++
class="type">void ClearDataAllBuffersStdInd(class="type">int series_index)
  {
  CArrayObj *list=this.GetListBuffersWithID();
  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="type">int total=list.Total();
  for(class="type">int i=class="num">0;i<total;i++)
     {
     CBuffer *buff=list.At(i);
     if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_CALCULATE || buff.IndicatorType()==WRONG_VALUE)
       class="kw">continue;
     this.ClearDataBufferStdInd(buff.IndicatorType(),buff.ID(),series_index);
     }
  }

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)
     {
     class=class="str">"cmt">//--- Single-buffer standard indicators
     case IND_AC       :

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

在封装指标数据抓取时,MT5 的内置指标要先按缓冲区数量分两条路走。上面罗列的 IND_AD、IND_RSI、IND_ATR 等 28 个宏,都属于单缓冲区标准指标,它们共用同一段取数逻辑:从指标列表取第一个 buffer,空则直接返回 0,否则按 IndicatorHandle、偏移 Shift 和总量 total_copy 填系列数组。 单缓冲分支里能见到 AD(累积/派发)、AMA(自适应均线)、BWMFI(布林带宽度资金流)这类名字,说明它们输出只有一条线,复制一次就够。外汇与贵金属行情跳空频繁,ATR、STDDEV 这类波动率指标在复制前若 handle 失效,返回 0 会让后续计算静默出错,开 MT5 跑这段代码时建议先打印 buffer==NULL 的触发次数。 多缓冲标准指标则单列一组:IND_ENVELOPES、IND_FRACTALS、IND_MACD、IND_RVI、IND_STOCHASTIC 各有两条以上输出线。代码里用 CSelect::ByBufferProperty 按 BUFFER_PROP_IND_LINE_MODE 等于 0 筛出主线 buffer,再对 idx0=0 做 FillAsSeries;MACD 的柱线和信号线、 stochastic 的 %K 与 %D 都靠这种索引区分。 实际调参时,total_copy 若设得比图表可见 K 线小,复制到的点数会截断;把 Shift 从 0 改成 1 可让小布盯盘类脚本拿到滞后一根的值做确认,贵金属短线假突破概率可能因此下降。

MQL5 / C++
   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 class="num">0;
      copied=buffer.FillAsSeries(buffer.IndicatorHandle(),class="num">0,buffer.Shift(),total_copy);
      class="kw">return copied;

   class=class="str">"cmt">//--- Multi-buffer standard indicators
   case IND_ENVELOPES   :
   case IND_FRACTALS     :
   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,class="num">0,EQUAL);
      buffer=list0.At(class="num">0);
      if(buffer==NULL) class="kw">return class="num">0;
      copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,buffer.Shift(),total_copy);

布林带与鳄鱼指标的多缓冲取法

在 MT5 自定义指标里批量抓取标准指标的缓冲数据时,不同指标的线序(line mode)并不统一。以 IND_BANDS 为例,它的主缓冲排列是 1/2/0,而 ALLIGATOR、ADX、ADXW 等则是 0/1/2,写错索引会直接拿到错位序列。 下面这段分支处理同时覆盖了布林带和鳄鱼类指标。先按 std_ind 判断是否为布林带,再分别给 idx0~idx2 赋值,随后用 CSelect::ByBufferProperty 按 BUFFER_PROP_IND_LINE_MODE 挑出对应缓冲,逐个 FillAsSeries 拷贝。 [CODE] if(copied<total_copy) return 0; list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL); buffer=list1.At(0); if(buffer==NULL) return 0; copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,buffer.Shift(),total_copy); if(copied<total_copy) return 0; return copied; case IND_ALLIGATOR : case IND_ADX : case IND_ADXW : case IND_BANDS : if(std_ind==IND_BANDS) { idx0=1; idx1=2; idx2=0; } else { idx0=0; idx1=1; idx2=2; } list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer=list0.At(0); if(buffer==NULL) return 0; copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,buffer.Shift(),total_copy); if(copied<total_copy) return 0; list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL); buffer=list1.At(0); if(buffer==NULL) return 0; copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,buffer.Shift(),total_copy); if(copied<total_copy) return 0; list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,2,EQUAL); buffer=list2.At(0); if(buffer==NULL) return 0; copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,buffer.Shift(),total_copy); if(copied<total_copy) return 0; return copied; case IND_GATOR : [/CODE] 逐行看:第 1 行先校验上一段拷贝量,不足就退出;接着 list1 按线模式 1 筛缓冲,At(0) 取首个,空指针即返回 0;FillAsSeries 的第三个参是 Shift,决定从哪根 K 线倒序填充。 布林带分支里 idx0=1 对应中轨、idx1=2 为上轨、idx2=0 为下轨,而鳄鱼系保持 0/1/2 的自然序。外汇与贵金属行情跳空频繁,这类多缓冲拷贝若少拷一根,EA 在 H4 以上周期可能倾向产生信号延迟,实盘前务必在策略测试器用 2020—2023 年 XAUUSD 数据跑一遍完整性校验。

MQL5 / C++
if(copied<total_copy) class="kw">return class="num">0;

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

case IND_ALLIGATOR   :
case IND_ADX         :
case IND_ADXW        :
case IND_BANDS       :
  if(std_ind==IND_BANDS)
  {
   idx0=class="num">1;
   idx1=class="num">2;
   idx2=class="num">0;
  }
  else
  {
   idx0=class="num">0;
   idx1=class="num">1;
   idx2=class="num">2;
  }
  list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,class="num">0,EQUAL);
  buffer=list0.At(class="num">0);
  if(buffer==NULL) class="kw">return class="num">0;
  copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,buffer.Shift(),total_copy);
  if(copied<total_copy) class="kw">return class="num">0;

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

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

case IND_GATOR       :

「按指标类型清空缓冲区的实现切口」

在封装标准指标缓冲区管理时,ClearDataBufferStdInd 这个函数承接了「按 timeseries 索引清数据」的职责。它先通过 GetListBufferByTypeID 拿到某类指标、某 ID 下的缓冲区对象数组,若指针为空或总数为 0 直接 return,避免对不存在的缓冲做无效操作。 随后用 CSelect::ByBufferProperty 按 BUFFER_PROP_TYPE 与 BUFFER_TYPE_DATA 做相等筛选,只保留数据型缓冲;若筛选后 Total() 仍为 0 也立刻退出。这一道过滤很关键,能把画布型、辅助型缓冲挡在清零逻辑之外。 switch 里先列了一批单缓冲标准指标:IND_AC、IND_AD、IND_AMA、IND_AO、IND_ATR、IND_BEARS、IND_BULLS、IND_BWMFI、IND_CCI、IND_CHAIKIN、IND_DEMA、IND_DEMARKER、IND_FORCE、IND_FRAMA、IND_MA、IND_MFI、IND_MOMENTUM、IND_OBV、IND_OSMA、IND_RSI、IND_SAR、IND_STDDEV 等都在列。开 MT5 在 MetaEditor 搜这些宏,能看到它们对应内置指标枚举值均为单输出缓冲,所以共用同一段 case 处理即可。 外汇与贵金属杠杆高、滑点突变频繁,缓冲区残留旧数据可能导致信号重绘误判;在 EA 初始化或品种切换时主动调一次这类清理,能降低跨周期串数据的倾向。

MQL5 / C++
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_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);
   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       :

◍ 多缓冲指标怎么逐个清空数据线

在 MT5 自定义指标里批量复位缓冲,得先按指标类型区分单线还是多线。上面这段代码把 TEMA、TRIX、VIDYA、VOLUMES、WPR 归为单缓冲组,只取 list_ind 的第 0 个缓冲,判空后写一次 EmptyValue 就退出。 ENVELOPES、FRACTALS、MACD、RVI、STOCHASTIC、GATOR 是双线标准指标,用 CSelect::ByBufferProperty 按 BUFFER_PROP_IND_LINE_MODE 分别抓 0 和 1 两条线,各自判空并置空。ALLIGATOR、ADX、ADXW、BANDS 则扩到三线,依次处理 line mode 0/1/2。 ICHIMOKU 在 switch 里直接 break,说明云图这类复合缓冲不在当前复位逻辑内,若你要清它的缓冲得单独写分支。外汇与贵金属杠杆高,跑这类脚本前先在策略测试器用 2023 年 EURUSD 1 小时数据验证缓冲行为,避免实盘误清空。

MQL5 / C++
    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_ENVELOPES  :
      case IND_FRACTALS   :
      case IND_MACD       :
      case IND_RVI        :
      case IND_STOCHASTIC :
      case IND_GATOR      :
          list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,class="num">0,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,class="num">1,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_ALLIGATOR  :
        case IND_ADX        :
        case IND_ADXW       :
        case IND_BANDS      :
            list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,class="num">0,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,class="num">1,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,class="num">2,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_ICHIMOKU :
            class="kw">break;
让小布替你跑这套映射
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多周期指标位移是否按映射倍数校准,把重复劳动交给小布,你专注决策。

常见问题

因为默认缓冲区位移为零,且未计算源周期柱线映射到当前周期的数量,导致曲线仅在原位移柱内偏移而非整体倍数位移。
倾向位移 4 根,因 H4 一根柱对应 H1 四根柱,映射后位移须按周期倍数放大才可能对齐。
两者作者原意即带位移绘制,目前库未处理其计算内位移映射;鳄嘴因各线独立位移反而易转换。
可以,小布盯盘对应品种页内置了跨周期位移倍数诊断,省去手动核对映射关系。
须指定从源数组复制的起始参考点柱数,否则接收数组初始对齐错乱,曲线整体偏移。