面向初学者的创建具有多个指标缓冲区的指标·进阶篇
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 用脚本单步验证。
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。
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,趋势强度倾向高。外汇与贵金属波动大,该值仅描述已发生强弱,不预示必反转。
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 策略测试器跑一轮历史回放,确认信号延迟在你的手续费结构下还能覆盖成本。