将指标应用至其他指标·进阶篇
📘

将指标应用至其他指标·进阶篇

第 2/2 篇

「指标缓冲区里的空值长什么样」

打开任意内置指标的缓冲区,前 14 个元素(索引 0 到 13)往往都是同一个数:1.797693134862316e+308。这其实就是 MQL5 里 EMPTY_VALUE 常量的实际数值,专门用来标记「这儿没数据」,在客户端各类内置指标中出镜率极高。 为什么挑这个庞然大物当空值?一方面它是双精度能表示的最大数,另一方面它正好等于 DBL_MAX 常量,写代码时一眼就能认出来。顺带一提,RSI 这类指标自身永远算不出 DBL_MAX,所以缓冲区里一旦出现它,基本就是占位空值而非真实信号——像示例里索引 14 才落到一个合理的 50。 处理外部指标喂来的数据,别急着把空值填零。零可能是别的指标 legit 的计算结果,乱填会污染后续逻辑。正解是显式跳过这些空值,用 begin 参数对齐有效数据的起点。 判空时拿变量跟 EMPTY_VALUE 比就行,语义比直接写 DBL_MAX 清楚。下面这个函数就是最小可用版:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 针对空值返回 true                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool isEmptyValue(class="type">class="kw">double value_to_check)
  {
class=class="str">"cmt">//--- 如果数值等于 DBL_MAX, 表明是个空值
   if(value_to_check==EMPTY_VALUE) class="kw">return(true);
class=class="str">"cmt">//--- 不等于 DBL_MAX
   class="kw">return(false);
  }

嵌套指标时起点偏移怎么算

写自定义指标时,OnCalculate() 收到的 begin 参数和 PLOT_DRAW_BEGIN 属性是绑死的。基础指标 B 用 PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, Nb) 声明自己前 Nb 根柱不画,那么基于它零缓冲区的指标 A 在 OnCalculate() 里拿到的 begin 就等于 Nb,不是你拍脑袋定的。 以 RSI(14) 喂给 TSI(25,13) 为例:RSI 从第 14 根柱起画(Nb=14);TSI 自身因双平滑需要舍弃前 r+s-1=25+13-1=37 根柱(Na=37)。两者嵌套后实际绘制起点是 Nab = Na + Nb = 51,也就是图表上第 52 根柱(索引从 0 计)才出线。外汇与贵金属市场杠杆高、跳空频繁,这种偏移若没算准,前面几十根柱的误绘会直接污染信号。 代码里 prev_calculated==0 时把 PLOT_DRAW_BEGIN 重设为 begin+r+s-1,就是在把基础指标的偏移叠到自己头上。若还有第三层指标套 TSI,它的 begin 会再累加 51,规律就是 A{B} 从 Na+Nb 起画。 下次挂多层指标前,先打开数据窗口数一下每层的前置空柱,MT5 里把光标移到指标线最左端可见起始索引,和手算的 Na+Nb 对一遍就能验证。

MQL5 / C++
class=class="str">"cmt">//--- 设定从哪一个柱开始画指标
  PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,ExtPeriodRSI);
class=class="str">"cmt">//--- 设定从哪一个柱开始画指标
  PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,r+s-class="num">1);
  if(prev_calculated==class="num">0)
   { 
     class=class="str">"cmt">//--- 让我们把起始位置增加 begin 柱数,
     class=class="str">"cmt">//--- 因为我们引用其他指标进行计算
     if(begin>class="num">0)PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,begin+r+s-class="num">1);
   }

◍ 用 begin 修正指标数组的起步填充点

price[] 里真正能参与计算的数据,是从 begin 参数指定的索引才开始的。之前没传 begin 时,MTMBuffer[] 和 AbsMTMBuffer[] 从索引 1 就填值,这在前导偏移存在的品种上会引用到无意义的 price[0]~price[begin-1]。 改动核心只有一处:首次计算(prev_calculated==0)时,把 start 的初始值从 1 改成 begin+1。这样 for 循环首轮 i=begin+1,MTMBuffer[i] 用的是 price[begin+1]-price[begin],恰好避开了前 begin 个不可用的价格点。 若 begin 设为 30,那么前 30 根 K 线的 MTM 缓冲保持空值,第 31 根(索引 30)之后的动量差才有效。外汇与贵金属波动剧烈、点差跳空频繁,用错起步索引可能让前段指标画出虚假交叉,实盘前应在 MT5 用示范品种核对缓冲区首尾。

MQL5 / C++
class=class="str">"cmt">//--- 计算 mtm 和 |mtm| 的数值
   class="type">int start;
   if(prev_calculated==class="num">0) start=class="num">1;   class=class="str">"cmt">// 设置从MTMBuffer[] 和 AbsMTMBuffer[] 的索引1开始计算填写数据  
   else start=prev_calculated-class="num">1;     class=class="str">"cmt">// 设置从上一次计算的最后一个索引开始计算填写数据
   for(class="type">int i=start;i<rates_total;i++)
     {
       MTMBuffer[i]=price[i]-price[i-class="num">1];
       AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }
class=class="str">"cmt">//--- calculate values for mtm and |mtm|
   class="type">int start;
   if(prev_calculated==class="num">0) start=begin+class="num">1;   class=class="str">"cmt">// 设置从 MTMBuffer[] 和 AbsMTMBuffer[] 的索引begin+1开始计算填写数据 
   else start=prev_calculated-class="num">1;       class=class="str">"cmt">// 设置从上一次计算的最后一个索引开始计算填写数据

   for(class="type">int i=start;i<rates_total;i++)
     {
       MTMBuffer[i]=price[i]-price[i-class="num">1];
       AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }
       MTMBuffer[begin+class="num">1]=price[begin+class="num">1]-price[begin];
       AbsMTMBuffer[begin+class="num">1]=fabs(MTMBuffer[begin+class="num">1]);

「依赖缓冲区的起始偏移同步」

TSI 指标里 MTMBuffer 和 AbsMTMBuffer 是后续平滑的源缓冲区。当输入价格数组 price[] 前面跳过 begin 个空值后,这些依赖数组的有效计算起点也必须同步后移 begin 个柱,否则指数平滑会读到位移错位的脏数据。 做法很直接:在调用 ExponentialMAOnBuffer() 时,把原来的起始索引参数从 1 改成 begin+1,次要平均的起始从 r 改成 begin+r。代码里两处首次平均、两处二次平均都做了同样的偏移加法,逻辑一致。 最后一步算 TSIBuffer 的循环起点也要改。原来首次计算设 start=r+s-1,现在变成 start=begin+r+s-1,保证前面 begin 根柱子不被填成指标值。若 prev_calculated 不为 0,则仍从 prev_calculated-1 续算,不重复劳动。 别漏了对外兼容。其他指标可能引用本指标的 TSI 值,所以那些前 begin 个无效位置必须显式写成 EMPTY_VALUE,避免被误读成真实信号。外汇与贵金属波动剧烈,TSI 参数错位可能导致信号整体滞后,实盘前请在 MT5 用不同 begin 值肉眼比对缓冲区前段。

MQL5 / C++
class=class="str">"cmt">//--- 计算数组的首要移动平均
  ExponentialMAOnBuffer(rates_total,prev_calculated,
                     class="num">1,       class=class="str">"cmt">// 索引, 开始从哪个数据开始平滑计算  
                     r,       class=class="str">"cmt">//指数平均的周期
                     MTMBuffer,       class=class="str">"cmt">// 计算平均的源缓冲区
                     EMA_MTMBuffer);  class=class="str">"cmt">// 目标缓冲区
  ExponentialMAOnBuffer(rates_total,prev_calculated,
                     class="num">1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);
class=class="str">"cmt">//--- 计算数组的首要移动平均
  ExponentialMAOnBuffer(rates_total,prev_calculated,
                     begin+class="num">1,   class=class="str">"cmt">// 数组中开始元素的索引 
                     r,       class=class="str">"cmt">// 指数平均的周期
                     MTMBuffer,       class=class="str">"cmt">// 计算平均的源缓冲区
                     EMA_MTMBuffer);  class=class="str">"cmt">// 目标缓冲区
  ExponentialMAOnBuffer(rates_total,prev_calculated,
                     begin+class="num">1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);
class=class="str">"cmt">//--- 计算数组的次要移动平均
  ExponentialMAOnBuffer(rates_total,prev_calculated,
                     r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
  ExponentialMAOnBuffer(rates_total,prev_calculated,
                     r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);
class=class="str">"cmt">//--- 计算数组的次要移动平均
  ExponentialMAOnBuffer(rates_total,prev_calculated,
                     begin+r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
  ExponentialMAOnBuffer(rates_total,prev_calculated,
                     begin+r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);
class=class="str">"cmt">//--- 现在计算指标的数值
  if(prev_calculated==class="num">0) start=r+s-class="num">1; class=class="str">"cmt">// 设置输入数组的初始索引
  else start=prev_calculated-class="num">1;       class=class="str">"cmt">// 从上一次计算结束的位置开始 
  for(class="type">int i=start;i<rates_total;i++)
   {
     TSIBuffer[i]=class="num">100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
   }
class=class="str">"cmt">//--- calculating values of our indicator
  if(prev_calculated==class="num">0) start=begin+r+s-class="num">1; class=class="str">"cmt">// 设置输入数组的初始索引
  else start=prev_calculated-class="num">1;              class=class="str">"cmt">// 从上一次计算结束的位置开始
  for(class="type">int i=start;i<rates_total;i++)
   {
     TSIBuffer[i]=class="num">100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
   }

指标缓冲区别迷信一次性初始化

MQL5 里数组默认不按指定值初始化,用 SetIndexBuffer() 绑定的指标缓冲区也一样。缓冲区大小跟着 OnCalculate() 的 rates_total 走,有人习惯在开头用 ArrayInitialize() 填 EMPTY_VALUE,但这只是错觉。 客户端收到新报价后柱数会变多,系统给缓冲区追加内存,新追加的元素不会被初始化,可能是任何脏值。一次初始化绝不等于后续元素都安全,永远别假设未显式赋值的数组元素会是你想要的数。 每个缓冲区元素都得由算法显式写值。除数为零在 MQL5 是严重运行时错误,会直接终止程序,所以遇到这种情况应该给该元素明确赋空值,而不是只写 if 躲开。 图形样式层面,可用 PlotIndexSetDouble() 把 0 定义为空值,那对应缓冲区里的空位就要明确定义成 0。若图形设了 DRAW_BEGIN,索引 0 到 DRAW_BEGIN-1 的元素会自动填零,不用手填。 下面这段演示了首次调用才初始化、空值设零、除零兜底的三件事,开 MT5 新建指标把 TSIBuffer 换成你的缓冲区名就能跑:

MQL5 / C++
class=class="str">"cmt">//--- 如果是第一次调用 OnCalculate() 
  if(prev_calculated==class="num">0)
    {
      ArrayInitialize(TSIBuffer,EMPTY_VALUE);
    }
  PlotIndexSetDouble(plotting_style_index,PLOT_EMPTY_VALUE,class="num">0);  
  if(divider==class="num">0)
     IndicatorBuffer[i]=class="num">0;
  else
    IndicatorBuffer[i]=...

◍ 记住这一条就够了

跨指标引用时,空值处理与绘制起点偏移是两个最容易踩坑的点。内置指标的空值统一用 EMPTY_VALUE 填充,它和双精度上限 DBL_MAX 完全相等;自己写的指标若想换空值,得在 OnInit() 里用 PlotIndexSetDouble 显式指定,而不能只靠 ArrayInitialize 一次性初始化——缓冲区每个元素都必须在 OnCalculate() 里明确赋值。 图形样式的前 N 个柱不绘制,靠 PLOT_DRAW_BEGIN 控制;一旦设了它,0 到该值之间的缓冲区元素会自动填空值。若你的计算还依赖别的指标数据,得在 begin 基础上再加 N 个柱的偏移,否则前面几根柱算出来是错的。 外汇与贵金属市场杠杆高、波动剧烈,这类底层指标逻辑错误可能让信号整体偏移,实盘前务必在 MT5 策略测试器里用历史数据跑一遍验证。下面这段就是设置绘制起点与自定义空值的核心写法,直接抄进你的指标框架改参数即可。

MQL5 / C++
PlotIndexSetInteger(plotting_style_index,PLOT_DRAW_BEGIN,N);
class=class="str">"cmt">//--- 如果这是第一次调用 
  if(prev_calculated==class="num">0)
    { 
      class=class="str">"cmt">//--- 把数据起始点增加 begin 个柱数, 
      class=class="str">"cmt">//--- 因为引用了其他指标进行计算
      if(begin>class="num">0)PlotIndexSetInteger(plotting_style_index,PLOT_DRAW_BEGIN,begin+N);
    }
PlotIndexSetDouble(plotting_style_index,PLOT_EMPTY_VALUE,your_empty_value);
ArrayInitialize(buffer_number,value);

常见问题

空值通常写成 EMPTY_VALUE 或 0,图表上表现为水平直线。先用 Print 把前 N 根缓冲打印出来确认,再决定要不要做偏移修正。
这是起点偏移导致的,被嵌套指标需要足够历史根数才会出值。用 begin 参数或手动对齐索引,把起步填充点修正到两者同步的位置。
可以。小布能读取品种页上的指标缓冲信息,提示起算根数差异,省去你手动 Print 核对数组偏移的重复劳动。
历史数据更新或周期切换会让旧缓冲失效。别迷信一次性初始化,在 OnCalculate 里按当前 rates_total 做增量填充更稳。
记住:缓冲区的起始偏移必须显式同步,否则外层指标拿到的全是错位或空值数据,图形和信号都会骗你。