面向初学者的创建具有多个指标缓冲区的指标·进阶篇
📘

面向初学者的创建具有多个指标缓冲区的指标·进阶篇

第 2/2 篇

Aroon 指标里最高最低柱的索引抓取

写 Aroon 类指标时,第一步要决定从哪根柱开始算。若是 OnCalculate 的首次调用(prev_calculated == 0),就从 AroonPeriod - 1 这根柱起步;否则只补算新柱,从 prev_calculated - 1 开始,避免每 tick 重算全历史。 下面两个函数替代了系统自带的 iHighest / iLowest 思路,但返回的是「索引」而非价格。iHighest 在 array 中从 startPos 往前数 count 根,找最大元素所在位置;iLowest 反之找最小。注意若 startPos - count < 0,会把 count 截断为 startPos,防止越界读数组。 实战里若传入 startPos 为负,函数直接 Print 报错并返回 0,这会在 EURUSD、XAUUSD 这类高波动品种上悄悄让指标首根画歪。外汇与贵金属杠杆高、滑点大,这类边界 bug 可能让信号延迟一根柱,建议开 MT5 用脚本单步验证。

MQL5 / C++
if (prev_calculated == class="num">0)        class=class="str">"cmt">// 检查OnCalulate函数的第一次调用
     first = AroonPeriod - class="num">1;       class=class="str">"cmt">// 所有柱形的开始计算位置 
else first = prev_calculated - class="num">1;  class=class="str">"cmt">//新柱形计算的开始位置
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|   搜索最高价柱形的索引                                          |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int iHighest(const class="type">class="kw">double &array[], class=class="str">"cmt">// 用于搜索最大元素的索引的数组
             class="type">int count,             class=class="str">"cmt">// 数组中元素的个数(降序), 
             class="type">int startPos           class=class="str">"cmt">//开始位的索引
             )  
  {
class=class="str">"cmt">//---+
   class="type">int index = startPos;

   class=class="str">"cmt">//---- 检查开始位索引
   if (startPos < class="num">0)
     {
       Print("Incorrect value in the function iHighest, startPos = ", startPos);
       class="kw">return (class="num">0);
     }
   class=class="str">"cmt">//---- 检查 startPos 的值
   if (startPos - count < class="num">0) count = startPos;

   class="type">class="kw">double max = array[startPos];

   class=class="str">"cmt">//---- 索引搜索
   for(class="type">int i = startPos; i > startPos - count; i--)
     {
       if(array[i] > max)
         {
          index = i;
          max = array[i];
         }
     }
class=class="str">"cmt">//---+ 返回最大值所在柱形的索引
   class="kw">return(index);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|   搜索最低价柱形的索引                                          |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int iLowest(
             const class="type">class="kw">double &array[], class=class="str">"cmt">// 用于搜索最大元素索引的数组
             class="type">int count,             class=class="str">"cmt">// 数组中元素的个数(降序),
             class="type">int startPos           class=class="str">"cmt">//开始位的索引
             )
{
class=class="str">"cmt">//---+
   class="type">int index = startPos;

   class=class="str">"cmt">//--- 检查开始位的索引
   if (startPos < class="num">0)
     {
       Print("Incorrect value in the iLowest function, startPos = ",startPos);
       class="kw">return(class="num">0);
     }

   class=class="str">"cmt">//--- 检查 startPos 的值
   if (startPos - count < class="num">0) count = startPos;

   class="type">class="kw">double min = array[startPos];

   class=class="str">"cmt">//--- 索引搜索
   for(class="type">int i = startPos; i > startPos - count; i--)
     {
       if (array[i] < min)
         {
          index = i;
          min = array[i];
         }
     }
class=class="str">"cmt">//---+ 返回最低值的柱形索引
   class="kw">return(index);
  }
class=class="str">"cmt">//+------------------------------------------------------------------

◍ Aroon 指标的 OnCalculate 落地写法

在 MT5 自定义指标里,OnCalculate 是每次报价或新柱进来时都会被调用的核心函数。Aroon 这类基于 N 根 K 线最高/最低位置计算动能的指标,第一道关卡就是数据量是否够用:代码里用 rates_total < AroonPeriod - 1 直接 return(0),意味着若总柱数不足周期减一根,指标不输出任何值,避免在开头产生误导信号。外汇与贵金属波动大,周期设太短(如 10 以下)容易在毛刺上反复翻转,实盘前建议在策略测试器里先跑一遍不同 AroonPeriod。 首次计算与后续增量计算的起点不同:prev_calculated == 0 时从 AroonPeriod - 1 这根开始填缓存,否则只从 prev_calculated - 1 补算新柱,这是 MT5 指标避免重复运算的标准手法。主循环里 BULLS 和 BEARS 的算式用了 iHighest / iLowest 定位窗口内极值偏移,再套 100 - (bar - 位置 + 0.5) * 100 / AroonPeriod 映射到 0~100。 下面这段是可直接贴进 .mq5 的指标迭代骨架,注意 BullsAroonBuffer / BearsAroonBuffer 需在文件前部用 SetIndexBuffer 绑定。打开 MT5 的 MetaEditor,新建自定义指标把缓存名改成你的数组,就能在 EURUSD 的 H1 上验证 Aroon 多头线是否如预期在趋势段贴近 100。

MQL5 / C++
class="type">int OnCalculate(const class="type">int rates_total,   class=class="str">"cmt">// 当前价格变动时的所有柱形
                  const class="type">int prev_calculated, class=class="str">"cmt">// 先前价格变动时的所有柱形
                  const class="type">class="kw">datetime& time[],
                  const class="type">class="kw">double& open[],
                  const class="type">class="kw">double& high[],      class=class="str">"cmt">// 用于指标计算的最高价格数组
                  const class="type">class="kw">double& low[],       class=class="str">"cmt">// 用于指标计算的最低价格数组
                  const class="type">class="kw">double& close[],
                  const class="type">long& tick_volume[],
                  const class="type">long& volume[],
                  const class="type">int& spread[])
  {
   class=class="str">"cmt">//--- 检查柱形的数量
   if(rates_total < AroonPeriod - class="num">1)
      class="kw">return(class="num">0);

   class=class="str">"cmt">//--- 声明本地变量
   class="type">int first, bar;
   class="type">class="kw">double BULLS, BEARS;

   class=class="str">"cmt">//--- 计算开始位的索引
   if(prev_calculated == class="num">0) class=class="str">"cmt">//检查第一个指标值的计算
      first = AroonPeriod - class="num">1; class=class="str">"cmt">// 所有柱形的开始计算位置
   else first = prev_calculated - class="num">1; class=class="str">"cmt">// 新柱形计算的开始位置
   class=class="str">"cmt">//--- 主循环
   for(bar = first; bar < rates_total; bar++)
     {
      class=class="str">"cmt">//--- 求值
      BULLS = class="num">100 - (bar - iHighest(high, AroonPeriod, bar) + class="num">0.5) * class="num">100 / AroonPeriod;
      BEARS = class="num">100 - (bar - iLowest(low,  AroonPeriod, bar) + class="num">0.5) * class="num">100 / AroonPeriod;
      class=class="str">"cmt">//--- 用计算得到的值填充指标缓存
      BullsAroonBuffer[bar] = BULLS;
      BearsAroonBuffer[bar] = BEARS;
     }
   class="kw">return(rates_total);
  }

「Aroon 多头空头皮毛的循环填值」

这段自定义指标的 OnCalculate 入口先吃进 high、low、close 等六类序列引用,其中 low 那行注释误写成了“最高价格数组”,实际应是最低价,复制时别照抄错字。 函数开头用 rates_total < AroonPeriod - 1 做护栏,数据不够一个完整周期直接 return(0),避免数组越界把 MT5 终端搞卡。 ArraySetAsSeries 把 high、low 及两个输出缓冲都设为时间序列,索引 0 对应最新柱,这和手动数组的默认方向相反,是 EA 里最常踩的坑。 首次计算 prev_calculated==0 时,limit 推到 rates_total - AroonPeriod - 1;后续只扫新增柱,limit = rates_total - prev_calculated,省 CPU。 主循环从 limit 倒序到 0:BULLS 拿 ArrayMaximum(high, bar, AroonPeriod) 定位窗口内最高价所在 bar,套 100 + (bar - 位置 - 0.5)*100/AroonPeriod;BEARS 对最低价做同样动作。AroonPeriod 取 25 时,若最高价就在当前 bar,BULLS 读到约 100,趋势强度倾向高。外汇与贵金属波动大,该值仅描述已发生强弱,不预示必反转。

MQL5 / C++
const class="type">class="kw">double& high[],   class=class="str">"cmt">// 用于指标计算的最高价格数组
const class="type">class="kw">double& low[],     class=class="str">"cmt">// 用于指标计算的最高价格数组
const class="type">class="kw">double& close[],
const class="type">long& tick_volume[],
const class="type">long& volume[],
const class="type">int& spread[]
)
{
class=class="str">"cmt">//---+  
  class=class="str">"cmt">//--- 检查柱形的数量
  if (rates_total < AroonPeriod - class="num">1)
   class="kw">return(class="num">0);
   
  class=class="str">"cmt">//--- 将索引设置为时间序列
  ArraySetAsSeries(high, true);
  ArraySetAsSeries(low,  true);
  ArraySetAsSeries(BullsAroonBuffer, true);
  ArraySetAsSeries(BearsAroonBuffer, true);

  class=class="str">"cmt">//--- 声明本地变量
  class="type">int limit, bar;
  class="type">class="kw">double BULLS, BEARS;

  class=class="str">"cmt">//--- 计算开始位的索引
  if (prev_calculated == class="num">0)                    class=class="str">"cmt">// 检查OnCalulate函数的第一次调用
     limit = rates_total - AroonPeriod - class="num">1;   class=class="str">"cmt">// 所有柱形的开始计算位置
  else limit = rates_total - prev_calculated; class=class="str">"cmt">// 新柱形计算的开始位置

  class=class="str">"cmt">//--- 主循环
  for(bar = limit; bar >= class="num">0; bar--)
  {
   class=class="str">"cmt">//--- 计算指标值
   BULLS = class="num">100 + (bar - ArrayMaximum(high, bar, AroonPeriod) - class="num">0.5) * class="num">100 / AroonPeriod;
   BEARS = class="num">100 + (bar - ArrayMinimum(low,  bar, AroonPeriod) - class="num">0.5) * class="num">100 / AroonPeriod;
   class=class="str">"cmt">//--- 用计算得到的值填充指标缓存
   BullsAroonBuffer[bar] = BULLS;
   BearsAroonBuffer[bar] = BEARS;
   
}
class=class="str">"cmt">//----+    
  class="kw">return(rates_total);

}
class=class="str">"cmt">//+------------------------------------------------------------------

收束

两套 Aroon 指标加上配套的 SMA、ZigZag 样例文件都已落地,ZIP 里 aroon.mq5 占 13.39 KB、aroons.mq5 仅 10.44 KB,差异就在是否用序列缓存做中间计算。 有读者在 2023 年复刻过文中 MT4 循环思路并移植到 MT5,反馈生成的图表与原文一致,但这类基础材料在公开检索里极难触达。 外汇与贵金属波动剧烈、杠杆风险高,直接挂这些指标前先在 MT5 策略测试器跑一轮历史回放,确认信号延迟在你的手续费结构下还能覆盖成本。

常见问题

用 iHighest 和 iLowest 分别传入价格数组与回溯周期,返回的就是最高/最低柱相对于当前柱的偏移索引,再代入 Aroon 公式算百分比。
在循环里按当前柱位置给 UpBuffer、DownBuffer 分别赋值,数组下标与柱状图索引对齐,最后 return rates_total 让系统继续推进。
可以,小布已内置 Aroon 类多指标诊断,打开对应品种页就能看到多空缓冲区读数和临界提醒,省去手搓代码。
注意前 N 根数据不足时缓冲区置空或补零,避免越界;另外跨周期切换要重算索引,否则信号会错位。
那是回溯周期未填满导致的,限制 start 从周期长度后开始计算,前面留空即可消除毛刺。