DoEasy 函数库中的时间序列(第五十部分):多周期、多品种带位移的标准指标·进阶篇
(2/3)· H4 指标搬到 H1 图表位移却只挪一根柱?这篇讲清周期映射与位移换算的坑
给鳄鱼三号线分别挂上缓冲层
在 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 三个变量,就能直观比对不同偏移下张口灵敏度。
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 以下。
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 可让小布盯盘类脚本拿到滞后一根的值做确认,贵金属短线假突破概率可能因此下降。
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 数据跑一遍完整性校验。
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 初始化或品种切换时主动调一次这类清理,能降低跨周期串数据的倾向。
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 小时数据验证缓冲行为,避免实盘误清空。
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;