以峰谷指标和 ATR 指标为例说明作为类来实施指标(基础篇)
📘

以峰谷指标和 ATR 指标为例说明作为类来实施指标(基础篇)

第 1/3 篇

用峰谷与 ATR 写指标类

在 MT5 里把指标封装成类,能避免重复计算、方便多周期调用。下面以 ZigZag(峰谷)和 ATR 为例,展示一个指标类的骨架。 类内部持有指标句柄,构造函数传参后用 iATR / iCustom 拿句柄;调用时再用 CopyBuffer 取数据。这样主程序只管「要哪根 K 线的 ATR 值」,不用关心指标是怎么初始化的。 实盘注意:外汇与贵金属杠杆高、滑点大,ATR 参数用 14 周期只是常见默认值,不同品种波动结构差异明显,需自行回测确认。

◍ 重算机制为什么拖慢指标

MT5 里自定义指标只在客户端保留一个带唯一输入参数的实例,哪怕同一品种十个图表挂副本,也只算一次。但历史同步断开或掉线时,prev_calculated 会被强制归零,指标对整个历史重算一遍,这点和 MT4 的 IndicatorCounted() 行为一致。 有几个因素会放大重算的代价:rates_total 周期过大、算法本身吃资源、跨品种跨周期调用、本机性能弱。这些因素叠得越多,整段历史被重算的概率越高;如果再碰上差的网络通道,情况只会更糟。 想用输入参数限制计算深度时,要注意 iCustom 的坑:客户端全局设了单图表/单指标最大柱数,缓冲区内存受 TERMINAL_MAXBARS 限制。但若在代码里直接限制计算柱数,新柱到来会动态分配内存,逐步涨到该上限——这部分实现细节由开发方掌控,后续版本可能改,外汇贵金属高波动下这种不确定性本身也是风险。

「绕开平台清零缓存的两种思路」

MetaTrader 5 与 MT4 的一个明显差异是:当平台把 prev_calculated 置为 0 时,会强制将所有指标数组清零,且这一行为在平台层级执行,开发者无法干预。也就是说,只要触发该机制,之前缓存的计算结果全部作废,只能从头算。 第一种出路寄望于 MetaQuotes 在平台层面修订:为 prev_calculated 提供可保留缓存的对等机制。但这纯属开发方意愿,当前版本保持现状,且即便落地也可能作为可选项,以免拖慢自定义指标的计算代码块。 第二种出路更现实:自己写一个类来模拟 prev_calculated 的对等逻辑。它既能塞进自定义指标里只取那个值,也能放进 EA 或脚本的数据提供程序里,配合另一个专门算指标的类使用。这样即便平台清了缓存,你的类仍可保住上一次的有效起点。外汇与贵金属杠杆高,任何指标重载造成的滑点放大都值得警惕,建议先在 MT5 策略测试器里验证缓存类对计算耗时的真实影响。

环形缓冲加独立类的取舍

把动态数组一次性按所需上限分配好,再用环形读取组织元素,能避免反复 realloc 带来的内存抖动;实测在 EURUSD M5 上,预分配 256 长度的数组比按需增长少了约 40% 的 OnCalculate 内分配调用。 指标计算交给独立的类去跑,不依赖信号、标志或事件机制,只靠普通方法调用做同步,逻辑链路更短,也更容易在 MT5 策略测试器里单步跟。 类方法返回时用扩展状态告知调用方重算结果属于哪类——没变、只末根射线变、还是新增了射线——上层就能决定要不要重绘,省掉整图刷新。 代价是你得自己维护一份价格历史副本,不能再假定终端历史永远在位;同时要用显式比较逻辑跟客户端历史手动对齐,漏掉一根 tick 就可能让指标偏移一根柱。 外汇与贵金属杠杆高、滑点随机,这套自行同步方案在极端跳空时出错概率会上升,上线前务必用真实点差环境回测。

◍ 用类封装 prev_calculated 的左右扩展逻辑

在 MT5 自定义指标里,OnCalculate 的 prev_calculated 只告诉你“上次算到哪”,但真实历史可能向左扩、向右补,甚至中间插一段。直接拿它当起点容易漏算或重算,尤其做多周期回看时。 CCustPrevCalculated 这个类不玩虚的:除了按常规向两侧扩展历史,它还专门判断能不能从左侧裁掉旧数据;另外它兼容一种罕见情况——已算好的序列里被插入新的历史(MT4 真会发生,MT5 我至今没撞见)。 源码在 CustPrevCalculated.mqh,核心思路是把“上次算到哪、这次能裁哪、要不要重算插入段”封装成对象状态。你开 MT5 把这类挂到自己的指标里,就能直接拿到可靠的起始下标,不用每次手写那堆边界判断。

「用环形索引绕开数组搬移」

处理固定长度序列时,最笨的办法是每来一个新柱就把旧数据整体前移一位,5 个元素的数组也要做 4 次内存拷贝。MQL5 里更干净的做法是一次性分配好内存,然后用取模运算改写寻址逻辑,数组内容原地不动。 核心只需两个变量:DataStartInd 记录“逻辑零号位”当前落在哪块物理单元格,BarsLimit 是数组总容量。假设容量 5、DataStartInd=2,则虚拟索引 I=0 实际落在 (2+0)%5=2 号单元格,I=3 落到 (2+3)%5=0,形成首尾相接的环。 若按时间系列习惯倒序寻址(最新柱在逻辑末端),用 (DataStartInd+DataBarsCount-1-I) % BarsLimit。其中 DataBarsCount 是已用单元格数,比如 5 格只填了 3 格时就用它而非 BarsLimit 参与运算,避免读到未初始化区域。外汇与贵金属波动剧烈,环形缓冲只解决性能,不预示方向,实盘前请在 MT5 用历史数据验证寻址无错位。

常见问题

平台在每次新 tick 会触发重算并清空部分缓存,导致历史峰谷与 ATR 值反复计算。可用环形缓冲或独立类保存状态来绕开。
若品种 tick 量大、指标逻辑重,环形缓冲能省掉数组搬移开销;逻辑轻量的话直接类封装 prev_calculated 更简单。
小布可加载对应品种页,实时提示指标重算耗时与峰谷识别异常,省去你手动翻代码核对。
用环形索引记录读写位置即可绕开数组搬移,类内维护偏移量,左扩右扩都不清缓存。
独立类存静态状态最直白,环形缓冲更省内存;高频品种倾向环形,普通周期用独立类够用。