DoEasy 函数库中的时间序列(第五十部分):多周期、多品种带位移的标准指标(基础篇)
「在 DoEasy 里拉多周期多品种指标数据」
DoEasy 函数库这一版把标准指标封装成可跨周期、跨品种、带位移调用的对象。对 MT5 用户来说,意味着不用每次手写 iMA、iRSI 再去 CopyBuffer,直接拿现成接口要某品种某周期某偏移位的指标值即可。 原文示例发布于 2021 年 1 月 18 日 15:03,截至文档统计显示阅读量 1 484、评论 0,说明这套封装在早期就属于低讨论但高实用的底层改造。 外汇与贵金属市场杠杆高、跳空频繁,跨周期取数若忽略位移参数,容易把上一根 K 线的信号误判成当前信号,实盘前务必在策略测试器里用不同品种核对返回值。
◍ 多周期指标位移的坑与统一渲染思路
把 H4 算出来的标准指标搬到 H1 图表显示,一根 H4 柱要对应四根 H1 柱。如果标准指标线在 H4 上位移 1 根柱,那么在 H1 上必须位移 4 根,否则曲线会错位——这是多周期映射最基本的柱数换算。 之前我们用的缓冲区类构造函数里,指标线位移默认是 0。直接把标准指标的位移值塞给多周期线也不行:曲线只在那些“标准线本身有位移的柱”里偏移,其余地方不动。根因是没算清标准周期映射到当前周期后占了几根柱、以及线该整体挪几根。 填充缓冲区数组时还得反向扣掉源指标线的位移,作为从源数组拷到接收数组的起始参考点。这套规则跑通后,带位移的标准指标才能在多周期模式正常画出来。 注意,不是所有标准指标都能顺手转。鳄鱼(Alligator)每条线自带独立位移,反而好办;但 Gator 振荡器和 Ichimoku 目前还无法直接出多周期带位移版,后续要在库里补功能。再往后把指标数据塞进时间序列柱对象,就能跨品种跨周期快速搜任意指标组合。 今天另一件事是把每款标准指标各自的显示方法收成统一方法,代码量能砍掉一大截;同时把子窗口级别、数据窗口信息这类代码从终端指标移到库服务文件,最终指标只管调用。外汇/贵金属多周期验证前先开 MT5 用 Alligator 试一根 H4→H1 的 4 倍位移,确认曲线不错位再扩到别的指标。
把不同指标曲线归成三类统一处理
要让一套程序同时伺候多个指标,前提是给每条曲线贴上统一的角色标签。实操里可把任意指标的曲线收拢成三种语义:上边线/主线(鳄鱼的下颌、布林上轨、+DI 都算这一类,枚举值 0)、下边线/信号线(牙齿、-DI、布林下轨,枚举值 1)、平均线/中线(嘴唇、布林中轨,枚举值 2)。Ichimoku 暂且搁置,留到多周期阶段再补。 归类之后,原本要为每个指标写一套处理分支,现在只要按 0/1/2 三种模式走通,就能用同一段逻辑驱动所有曲线。修改 \MQL5\Include\DoEasy\Defines.mqh 里的 ENUM_INDICATOR_LINE_MODE 即可落地,代码见附。 随之而来的副作用是 Buffer.mqh 里设置图形位移的方法得松绑:原先对非计算缓冲区才允许设位移,现在要先给全部缓冲区写位移值,再单独跳过计算用缓冲区。删除那个多余的判断后,复制源数组到计算缓冲区的起点也能用负号传参自动对齐——位移为正就负向取数,位移为负就正向取数,源数据起点不会再错。 BuffersCollection.mqh 里十个创建方法(CreateAlligator、CreateAMA、CreateBands 等)都按此改完,图表上不用的指标残影可用按时间序列索引清空缓冲区的公开方法抹掉:遍历带指标 ID 的缓冲对象,跳过计算缓冲和未设类型的,调清除接口即可。外汇与贵金属市场波动剧烈,这类底层重构请在 MT5 策略测试器里用小周期先验证再上实盘。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Indicator lines | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_INDICATOR_LINE_MODE { INDICATOR_LINE_MODE_MAIN = class="num">0, class=class="str">"cmt">// Main line INDICATOR_LINE_MODE_SIGNAL = class="num">1, class=class="str">"cmt">// Signal line INDICATOR_LINE_MODE_UPPER = class="num">0, class=class="str">"cmt">// Upper line INDICATOR_LINE_MODE_LOWER = class="num">1, class=class="str">"cmt">// Lower line INDICATOR_LINE_MODE_MIDDLE = class="num">2, class=class="str">"cmt">// Middle line INDICATOR_LINE_MODE_JAWS = class="num">0, class=class="str">"cmt">// Jaws line INDICATOR_LINE_MODE_TEETH = class="num">1, class=class="str">"cmt">// Teeth line
「缓冲区偏移与跨周期鳄鱼构造的细节坑」
CBuffer::SetShift 的第一版实现里,只要缓冲区类型是 BUFFER_TYPE_CALCULATE 就直接 return,不设置任何图形偏移。这意味着计算型缓冲在 MT5 里不会随 K 线右移,做多周期对齐时容易和主图错位。 后续修订版把判断反过来:先写 BUFFER_PROP_SHIFT,再对非计算型缓冲调用 PlotIndexSetInteger 设 PLOT_SHIFT。这样计算缓冲只存数据、绘图缓冲才位移,逻辑更干净,也避免了对不可绘图缓冲误发绘图指令。 FillAsSeries 的拷贝入口值得注意:start_pos 传入后被取负变成 -start_pos 再喂给 CopyBuffer。也就是说外部按正序给起点,内部自动转成从右往左的系列索引,调用方若没意识到这点,复制到的可能是反方向数据。 CreateAlligator 支持 symbol 与 timeframe 双参数,说明可以在非当前图表上建鳄鱼线。外汇与贵金属杠杆高、跨周期信号易漂移,这类多符号实例仅在策略测试器或实盘小仓位验证后再信。
class="type">void CBuffer::SetShift(class="kw">const class="type">int shift) { if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE) class="kw">return; this.SetProperty(BUFFER_PROP_SHIFT,shift); ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,shift); } class="type">void CBuffer::SetShift(class="kw">const class="type">int shift) { this.SetProperty(BUFFER_PROP_SHIFT,shift); if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,shift); } class="type">int CBufferCalculate::FillAsSeries(class="kw">const class="type">int indicator_handle,class="kw">const class="type">int buffer_num,class="kw">const class="type">int start_pos,class="kw">const class="type">int count) { class="kw">return ::CopyBuffer(indicator_handle,buffer_num,-start_pos,count,this.DataBuffer[class="num">0].Array); } class="type">int CBuffersCollection::CreateAlligator(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe, class="kw">const class="type">int jaw_period, class="kw">const class="type">int jaw_shift,
◍ 把鳄鱼三线的位移折算成跨周期柱数
封装鳄鱼指标时最易被忽略的是:jaw_shift、teeth_shift、lips_shift 这些位移参数,是以当前图表周期为基准的。一旦你要在更高或更低周期上挂 Alligator,直接把原始 shift 丢进 iAlligator 会让三线错位。 代码里先用 PeriodSeconds(timeframe)/PeriodSeconds(PERIOD_CURRENT) 算出目标周期相对当前周期的柱数倍数 num_bars。例如切到 PERIOD_H1 而当前是 PERIOD_M15,num_bars 就是 4,原来 shift=2 的嘴唇线实际要右移 8 根 H1 柱。 随后 shift_jaw、shift_teeth、shift_lips 分别乘上 num_bars,再把折算后的位移传进 iAlligator。缓冲区对象还要用 buff.SetShift(shift_jaw) 同步视觉偏移,否则指标线和内部句柄对不上。外汇与贵金属杠杆高,跨周期验证前先开 MT5 用 EURUSD 的 M15→H1 跑一遍,看三线是否如期平移。
class="kw">const class="type">int teeth_period, class="kw">const class="type">int teeth_shift, class="kw">const class="type">int lips_period, class="kw">const class="type">int lips_shift, class="kw">const ENUM_MA_METHOD ma_method, class="kw">const ENUM_APPLIED_PRICE applied_price, class="kw">const class="type">int id=WRONG_VALUE) { class=class="str">"cmt">//--- Calculate and set the number of bars of line shift class="type">int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(PERIOD_CURRENT); class="type">int shift_jaw=jaw_shift*num_bars; class="type">int shift_teeth=teeth_shift*num_bars; class="type">int shift_lips=lips_shift*num_bars; class=class="str">"cmt">//--- Create indicator handle and set class="kw">default indicator class="type">int handle=::iAlligator(symbol,timeframe,jaw_period,shift_jaw,teeth_period,shift_teeth,lips_period,shift_lips,ma_method,applied_price); class="type">int identifier=(id==WRONG_VALUE ? IND_ALLIGATOR : id); class="type">class="kw">color array_colors[class="num">1]={clrBlue}; CBuffer *buff=NULL; if(handle!=INVALID_HANDLE) { class=class="str">"cmt">//--- Create line buffer this.CreateLine(); class=class="str">"cmt">//--- Get the last created buffer object(drawn) 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.SetShowData(true); buff.SetLineMode(INDICATOR_LINE_MODE_JAWS); buff.SetIndicatorName("Alligator");
把三条鳄鱼线塞进自定义缓冲
这段逻辑在给 Alligator 的三条线(Jaw / Teeth / Lips)分别挂上独立的绘制缓冲。每条线都先 CreateLine() 拿一个空 buffer 对象,再用 GetLastCreateBuffer() 抓回来填参数,任一返回 NULL 就直接甩 INVALID_HANDLE,等于初始化失败。 Jaw 段里 shift 用的是 jaw_period 对应的偏移量,Teeth 段把 array_colors[0] 改成 clrRed 后再 SetColors,Lips 段换成 clrLime。三段的 SetIndicatorShortName 都拼了 symbol、周期描述以及三个周期参数,例如默认 13/8/5 会显示成 Alligator(EURUSD,H1: 13,8,5),方便在副图一眼区分多品种多周期实例。 最后一句 CreateCalculate() 建的是 Jaw 的计算缓冲,用来接标准指标算出来的数据,不画出来。外汇和贵金属杠杆高,这类多缓冲封装若 shift 设错,线会整体平移导致假信号,开 MT5 把 shift_jaw / shift_teeth / shift_lips 打印出来核对偏移再上实盘。
buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)jaw_period+","+(class="type">class="kw">string)teeth_period+","+(class="type">class="kw">string)lips_period+")"); buff.SetLabel("Jaws("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)jaw_period+")"); buff.SetColors(array_colors); class=class="str">"cmt">//--- Create line buffer this.CreateLine(); class=class="str">"cmt">//--- Get the last created buffer object(drawn) 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.SetShowData(true); buff.SetLineMode(INDICATOR_LINE_MODE_TEETH); buff.SetIndicatorName("Alligator"); buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)jaw_period+","+(class="type">class="kw">string)teeth_period+","+(class="type">class="kw">string)lips_period+")"); buff.SetLabel("Teeth("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)teeth_period+")"); array_colors[class="num">0]=clrRed; buff.SetColors(array_colors); class=class="str">"cmt">//--- Create line buffer this.CreateLine(); class=class="str">"cmt">//--- Get the last created buffer object(drawn) 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.SetShowData(true); buff.SetLineMode(INDICATOR_LINE_MODE_LIPS); buff.SetIndicatorName("Alligator"); buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)jaw_period+","+(class="type">class="kw">string)teeth_period+","+(class="type">class="kw">string)lips_period+")"); buff.SetLabel("Lips("+symbol+","+TimeframeDescription(timeframe)+": "+(class="type">class="kw">string)lips_period+")"); array_colors[class="num">0]=clrLime; buff.SetColors(array_colors); class=class="str">"cmt">//--- Create calculated buffer of Jaws in which standard indicator data will be stored this.CreateCalculate();