DoEasy 函数库中的时间序列(第四十八部分):在单一子窗口里基于一个缓冲区的多周期、多品种指标·进阶篇
指标枚举的空分支骨架
这段 switch 结构把 MT5 内置指标常量挨个列了一遍,从布林带、熊力牛力到 Ichimoku、MACD、RSI 共 30 个 IND_* 宏,外加一个 default,但每个 case 里只写了 break,没有任何实际处理逻辑。 它在代码层的作用相当于一张「指标清单占位表」:你后续要给某个指标写专属计算或绘图分支,直接在这个对应 case 里填代码即可,不用再管枚举值是否合法。 开 MT5 按 F4 进 MetaEditor,搜 IND_BULLS 或 IND_WPR 就能看到标准枚举定义;当前这段若原样编译,运行时走到任何指标分支都会立刻跳出,不会报错也不会出图。 别把占位当实现 空 break 只是防止编译警告,不代表指标已接入。真要跑信号,得在 case IND_RSI: 这类分支里补 iRSI 调用与缓冲区绑定,否则 EA 对行情毫无反应。
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 根。
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 目前都是占位,若你要接自己的缓冲回收逻辑,从这几个子窗口单缓冲分支下手最干净。
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 会扭曲后续信号概率。
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 是否因属性过滤被清空,而不是怀疑指标本身算错。 外汇与贵金属波动剧烈,这类缓冲同步逻辑若漏掉某一帧,可能造成信号延迟,实盘前务必用历史数据回放验证。
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 组合能看到缓冲对齐是否错位。外汇与贵金属跨周期回测存在滑点与时区偏差,结论仅代表代码逻辑层面的可能表现。
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