从零写一个 MQL5 自定义指标:SMA 全解剖
代码结构、#property、OnInit/OnCalculate 与增量计算
一、自定义指标的三要素
一个 MQL5 指标由三件事构成:缓冲(buffer,存放计算结果的数据线)、计算(OnCalculate 里填缓冲)、绘图(用 #property 声明线型/颜色/宽度)。三者分工:缓冲是数据,计算是逻辑,绘图是表现。理解这个骨架,写任何指标都不慌。
指标和 EA 最大的区别:指标不交易,只产出可供观察和被 EA 调用的信号线。它可以是你肉眼看的辅助线,也可以是 EA 通过 iCustom+CopyBuffer 取数的数据源。同一个指标,两种用法。
二、在 OnInit 里定义缓冲
用 #property indicator_buffers N 声明缓冲数量,在 OnInit 里对每条缓冲调用 SetIndexBuffer,并设线型(画线/画箭头/画柱状)、颜色、宽度。缓冲 0、1、2…分别对应 main、signal 等线。漏声明或索引错,指标要么不显示要么数据错位。
#property indicator_buffers 2
#property indicator_plots 1
int OnInit()
{
SetIndexBuffer(0, ExtMainBuffer, INDICATOR_DATA);
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE);
return INIT_SUCCEEDED;
}
三、OnCalculate 与 prev_calculated
OnCalculate 在每次数据更新时被调用,返回「下次应从哪根开始算」的索引(通常返回 rates_total)。借助 prev_calculated 跳过已计算的 K 线,只对新增部分计算,效率极高。这也是非重绘写法的核心:只在已收盘 K 线落值,末根只显示。
int OnCalculate(const int rates_total, const int prev_calculated,
const int begin, const double &price[])
{
int start = (prev_calculated>0) ? prev_calculated-1 : begin;
for(int i=start; i<rates_total; i++)
ExtMainBuffer[i] = (price[i]+price[i-1])/2.0;
return rates_total;
}
四、缓冲的三种画法
DRAW_LINE 画线(如均线)、DRAW_ARROW 画箭头(买卖信号)、DRAW_HISTOGRAM 画柱状(如 MACD 柱)。箭头类指标用 PlotIndexSetInteger 设箭头字符与上下偏移;柱状类要注意正负值的不同着色。选对画法,信号一眼可辨。
五、指标与 EA 的边界
把「算信号」放指标、把「下单」放 EA,是良好分工:指标专注于产生干净、可复用的信号线,EA 专注于风控与执行。这样同一个指标既能被你肉眼验证,也能被多个 EA 复用,而不必在每个 EA 里重写一遍计算逻辑。
六、常见坑
- #property 缓冲数与实际 SetIndexBuffer 数不一致
- 在末根 K 线写值导致重绘(应用 prev_calculated 守已收盘)
- 缓冲未初始化,首根出现 0 或乱值
- 箭头指标忘了设箭头 code 与偏移
- OnCalculate 返回的索引逻辑写错导致重复/漏算
七、多缓冲指标示例
许多指标有两条线(如 MACD 的 main 与 signal),用两个缓冲即可:缓冲 0 算快线,缓冲 1 算慢线(或对快线再平滑)。EA 取数时用 CopyBuffer(handle, 0/1, ...) 分别取,索引别搞混。两条线职责清晰,信号才可靠。
八、调试指标
指标调试靠 Comment() 在图上打印中间值、用数据窗口看每条缓冲的实时数值,或在 EA 里 CopyBuffer 后 Print 前几根。最常被问的「线不对」多半是缓冲索引错或 OnCalculate 的 start 逻辑写错,按这两处查最快。
九、指标预热与首根有效柱(DRAW_BEGIN)
指标刚加载时,前几根 K 线往往还没凑齐计算所需的历史,比如 14 周期均线在前 13 根根本算不出。如果直接画线,前 N 根会显示 0 或错值,误导看图的人。MQL5 用 PlotIndexSetInteger(plot_index, PLOT_DRAW_BEGIN, n) 告诉终端「从第 n 根开始画」,前面留空。
更稳妥的做法是在 OnCalculate 里对 rates_total 做保护:若 rates_total 小于最小所需周期,直接返回 0 让终端稍后再调;同时用 prev_calculated 跳过已算好的柱、只处理新增的那一根,既正确又高效。忽视预热的指标,在 EA 用 iCustom 加 CopyBuffer 读取时也会拿到脏值,进而发出错误信号。
十、在 EA 里调用自定义指标(iCustom)
指标写完,真正的价值是让 EA 调用。EA 用 iCustom(symbol, timeframe, "指标文件名", 参数1, 参数2, ...) 拿到 handle,再用 CopyBuffer 读它的缓冲。注意参数顺序必须和指标 OnInit 里 inputs 的声明顺序完全一致,少一个或多一个都会导致 handle 指向错误的计算配置——这类错误不会编译报错,只会在运行时给你一条莫名其妙的空线。
调试调用时,先用 Comment() 把 CopyBuffer 拷出来的前几个值打到图上,确认数字合理(不是全 0、不是 EMPTY_VALUE),再接入交易逻辑。很多「EA 不开仓」的真相,就是 iCustom 参数写错导致句柄无效、CopyBuffer 返回 0,信号永远为空。
int h = iCustom(_Symbol, PERIOD_H1, "MyIndicator", 14, PRICE_CLOSE); double sig[1]; if(CopyBuffer(h, 0, 0, 1, sig) > 0 && sig[0] != EMPTY_VALUE) /* 用 sig[0] 判断信号 */;