6.4 自定义指标与指标缓冲区
指标的三段结构
一个最小指标包含三部分:属性声明、OnInit 初始化、OnCalculate 计算。属性告诉终端“画在图表还是副图、有几条缓冲区、每条什么颜色”;OnInit 把数组绑定到缓冲区并设置样式;OnCalculate 在每次行情变化时被调用,你负责把值写进缓冲区。
#property indicator_chart_window:画在主图;改成indicator_separate_window则画在副图。#property indicator_buffers 1:声明 1 条缓冲区。SetIndexBuffer(0, MaBuf):把数组 MaBuf 绑定到 0 号缓冲区。SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2, clrDodgerBlue):线宽 2、蓝色实线。
OnCalculate 的参数
rates_total 是当前总 K 线数,prev_calculated 是上一次调用已经算到的位置。第一次调用 prev_calculated 为 0,之后通常等于上一次的 rates_total。新 K 线到来时,rates_total 增加 1,prev_calculated 仍是旧值,所以循环从 prev_calculated 开始,只算新增的一格。
如果不使用 prev_calculated,每次从头算全部历史,复杂度是 O(n²)。在 M1 图表上跑几天,终端会明显变卡。正确写法是“从 prev_calculated 开始,处理到 rates_total-1”。
缓冲区方向:0 是最旧还是最新?
这是 MQL4 最容易混淆的地方。OnCalculate 里的价格数组和指标缓冲区默认都是“0 最旧、rates_total-1 最新”,和 EA 里 `Close[0]` 表示最新价相反。写指标时,`MaBuf[i]` 的 i 越大越新;画线时终端会自动按时间轴排列。
如果你调用 `ArraySetAsSeries(MaBuf, true)`,方向会反转成 0 最新。但多数指标不需要这么做,保持默认方向、用 prev_calculated 递增更直观。无论哪种方向,都要在注释里写清楚,避免下次自己看不懂。
完整示例:MA3 指标
下面的指标在主图画出 3 根收盘价的简单均线。前两格没有足够数据,保持默认空值;从 i=2 开始计算。注意循环从 start 开始,start 同时受 prev_calculated 和最小预热根数约束。
编译后把指标拖到图表,应该看到一条蓝色均线。切换周期、刷新历史时,prev_calculated 会重新从 0 开始,指标会自动重算。
在 EA 里调用:iCustom 的 shift
EA 不能直接读指标的缓冲区数组,要用 iCustom 按“缓冲区序号 + shift”取值。shift 的方向和缓冲区下标相反:shift=0 是最新一根,shift=1 是上一根,shift=2 是上两根。下面的调用读取 0 号缓冲区:
double ma0 = iCustom(NULL, 0, "MyMA3", 0, 0);最新值,可能随当前未收盘 Bar 变化。double ma1 = iCustom(NULL, 0, "MyMA3", 0, 1);上一根收盘值,已固定,适合做信号。
如果指标有输入参数,iCustom 的参数顺序是“指标输入参数…,缓冲区序号,shift”。参数写错时返回 EMPTY_VALUE 或 0,不会报错,所以调用后要检查 `ma1 != EMPTY_VALUE`。
常见错误
- 循环从 0 开始:每次重算全部历史,终端越来越卡。用 prev_calculated。
- 把缓冲区当 series:以为 buffer[0] 是最新,结果画出来的线整体错位。默认 0 最旧。
- 预热不足:MA20 从 i=0 开始算,前 19 格用了不存在的负索引或 0 值。从周期-1 开始。
- iCustom 参数顺序错:把 shift 当成缓冲区序号,读到的值一直不变。
- 在指标里下单:指标进程不允许交易,OrderSend 会失败。信号应通过缓冲区或全局变量交给 EA。
小结
指标的三件事:绑定缓冲区、只算新增、方向写清楚。记住“OnCalculate 默认 0 最旧、iCustom 的 shift=0 最新”,再养成从 prev_calculated 开始循环的习惯,你就能写出既正确又不卡的指标。
多缓冲区与显示顺序
一个指标可以有多条缓冲区,例如布林带需要上轨、中轨、下轨三条。声明 `#property indicator_buffers 3`,在 OnInit 里分别 SetIndexBuffer(0/1/2, ...),再用 SetIndexStyle 设置颜色和线型。缓冲区序号从 0 开始,显示顺序由序号决定;序号写错会导致“线画在错误的窗口”或“数据窗口里全是 0”。
副图指标还要设置 `#property indicator_separate_window` 和 `#property indicator_minimum/maximum`,否则终端会自动缩放,导致不同周期的图形不可比。主图指标不要设置 min/max,否则会把图表压缩到一条窄带。
EMPTY_VALUE 与预热
缓冲区默认值是 EMPTY_VALUE,表示“这一格没有数据”。计算 MA20 时,前 19 格没有足够的历史,应该保持 EMPTY_VALUE,而不是填 0。填 0 会在图表上画出一条从 0 开始的假线,也会让 iCustom 读到错误值。正确做法是从周期-1 开始循环,之前的格子不赋值。
如果指标需要多级预热(例如先算 EMA 再算 MACD),要在注释里写清楚每一级的起始下标。调试时打开“数据窗口”,把鼠标移到第一根有值的 K 线,确认下标与预期一致。
指标调试
指标不能用 Print 刷屏,否则每次 Tick 都会输出。推荐用 `Comment()` 在图表左上角显示关键变量,或者用 `Print` 只在 `prev_calculated == 0` 时打印一次初始化信息。数据窗口(Ctrl+D)可以看到光标所在 K 线的缓冲区值,是检查下标方向最快的方法。
如果线不显示,按顺序排查:缓冲区是否 SetIndexBuffer 绑定;属性 indicator_buffers 是否足够;循环是否真的写入了值;值是否超出 min/max 被裁掉;颜色是否与背景相同。大多数“指标不画线”都是这五个原因之一。
指标与 EA 的分工
指标负责“算”,EA 负责“做”。指标把结果写进缓冲区,EA 用 iCustom 读取;不要让指标直接下单,也不要让 EA 重复实现一套指标算法。这样同一套算法既能画图,又能被策略复用,改一处两边同步。
iCustom 调用有性能成本,尤其是每 Tick 调用多个指标。可以在 EA 里缓存最近一根 K 线的指标值,只有新 K 线才重新读取。缓存时把 `Time[0]` 一起存,避免用旧值做新决策。
多周期与数据对齐
指标可以在当前图表周期计算,也可以指定其他周期,例如 `iMA(NULL, PERIOD_H1, 20, 0, MODE_SMA, PRICE_CLOSE, 1)`。跨周期读取时要注意“未来数据”问题:H1 的当前 Bar 可能还没收盘,shift=0 的值会变;用 shift=1 取上一根已收盘的 H1,信号更稳定。不同周期的 K 线时间戳不一定对齐,比较前先确认 Time[0] 的周期。
自定义指标内部如果要读其他周期的数据,用 iClose/iHigh 等函数并显式传入周期,不要假设当前图表周期就是策略周期。多周期策略的调试成本高,建议先把单周期版本跑通,再逐步加周期过滤。
性能与缓存
指标的计算成本主要来自循环。用 prev_calculated 只算新增,能把 O(n²) 降到 O(n)。如果指标内部还要遍历历史找极值,尽量用增量更新或滑动窗口,不要每次都从头扫。EA 调用 iCustom 也有成本,缓存“上一根 K 线的指标值 + 对应 Time[0]”,只有新 K 线才重新读。
缓存不是万能的:指标参数改变、图表周期切换、历史数据刷新都会让缓存失效。缓存键至少包含品种、周期、参数、K 线时间;失效时重新计算,不要用旧值硬撑。
交互演示:缓冲区如何增长,shift 如何取值
8 根 K 线先补齐 MA3,第 9 根到来时只算新增一格;再用 iCustom 的 shift=0/1 取最新与上一根。
提示:动画默认暂停。点「播放」连续演示,点「单步」自己控制节奏;桌面端也可以用空格播放、方向键单步、R 重置。
练习题
-
6.4 自定义指标与指标缓冲区 选择题1.自定义指标中,把数组绑定到 0 号缓冲区的函数是? (2分) [选择]A. SetIndexBuffer(0, MaBuf)B. SetIndexStyle(0, DRAW_LINE)C. iCustom(0, 0)D. ArraySetAsSeries(MaBuf, true)2.OnCalculate 中 prev_calculated 的主要作用是? (2分) [选择]A. 记录指标颜色B. 指示上一次已计算到的位置,避免全量重算C. 返回当前图表周期D. 保存账户余额3.在默认设置下,指标缓冲区下标 0 对应最旧的 K 线。 (2分) [判断]
-
6.4 自定义指标与指标缓冲区 编程实战(AI 批改)1.【实战】写一个 MA3 自定义指标 (10分) [MQL4自动批改]写一个主图指标:声明 1 条缓冲区;OnInit 中 SetIndexBuffer(0, MaBuf)、SetIndexStyle 为蓝色实线并 SetIndexLabel;OnCalculate 中若 rates_total<3 返回 0,start=prev_calculated,start<3 时置 3,循环 i=start 到 rates_total-1,令 MaBuf[i]=(close[i]+close[i-1]+close[i-2])/3.0,最后返回 rates_total。必须出现 SetIndexBuffer、prev_calculated、OnCalculate、rates_total。
《MQL4 教程与 MT4 EA 编程》课程目录
系统讲解 MQL4 编程:从变量、循环、函数到 OrderSend 下单、iMA 指标调用与 EA 回测。36 节中文教程,覆盖 MT4 编程入门、外汇 EA 编写与外汇量化策略三类实战,每节配可运行代码和 AI 自动批改练习
- 第1章 基本语法
- 1.1 第一个EA
- 1.2 如何学习
- 1.3 变量
- 1.4 语句
- 1.5 运算符
- 1.6 常量与枚举
- 第2章 条件
- 2.1 if语句
- 2.2 关系运算符
- 2.3 逻辑运算符
- 2.4 复合条件
- 2.5 switch语句
- 第3章 循环
- 3.1 for循环
- 3.2 while循环
- 3.3 break和continue
- 3.4 嵌套循环
- 第4章 函数
- 4.1 函数的定义
- 4.2 参数和返回值
- 4.3 变量的作用域
- 4.4 常用内置函数
- 4.5 日期和时间函数
- 4.6 交易函数
- 4.7 账户和市场信息函数
- 4.8 图表和对象函数
- 4.9 文件操作函数
- 4.10 数组函数
- 4.11 技术指标函数
- 第5章 如何写EA
- 5.1 EA的基本结构
- 5.2 常用内置变量
- 5.3 下单函数
- 5.4 技术指标
- 5.5 实战:均线交叉EA
- 第6章 进阶实战:从能跑走向可靠
- 6.1 OnTimer 定时器与事件处理
- 6.2 订单池遍历与 OrderSelect
- 6.3 错误处理与 GetLastError
- 6.4 自定义指标与指标缓冲区
- 6.5 图表对象与交易面板
- 6.6 回测与参数优化