首页 / 交易学院 / 小布知识库 / 从零写一个 MQL5 自定义指标:SMA 全解剖
从零写一个 MQL5 自定义指标:SMA 全解剖
📐

从零写一个 MQL5 自定义指标:SMA 全解剖

代码结构、#property、OnInit/OnCalculate 与增量计算

MQL5 编程 难度 · 入门 2026-07-10 13 分钟阅读
#MQL5#自定义指标#SMA#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 里重写一遍计算逻辑。

⚠ 指标里别偷偷下单
指标的本职是算与画。若在指标里写 OrderSend,既破坏分工,又让信号线行为难以在 EA 外独立验证。下单放 EA,指标只管产出信号。

六、常见坑

七、多缓冲指标示例

许多指标有两条线(如 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] 判断信号 */;
✦ 联调三事故
文件名拼错、参数错位、缓冲编号越界,是指标与 EA 联调最常见的三类事故,逐一用 Comment 打印核对即可定位。

十一、小结

✦ 指标=数据+逻辑+表现
把缓冲、计算、绘图分开想,自定义指标就不再是黑盒。守住在已收盘 K 线落值的纪律,你的指标既好看又不重绘,EA 也敢放心调用。

常见问题

能,但不该。指标只算与画,下单放 EA 才是好分工——这样信号线既能被肉眼验证,也能被多个 EA 复用,行为更可控。
多半是 OnCalculate 在末根 K 线(未收盘)也写了值。坚守「只对 i<rates_total-1 的已收盘 K 线落值」,末根只显示,就不重绘。
每条线是一个缓冲,索引 0、1…。EA 里用 CopyBuffer(handle, 0, ...) 取主线、CopyBuffer(handle, 1, ...) 取信号线,索引别混。