MQL5 自定义指标从零构建:摆脱内置函数束缚掌握绘图逻辑(基础篇)
(1/3)· 依赖 iMA、iRSI 写策略总被内置逻辑卡住?本篇拆解指标底层缓冲区与绘制机制
◍ 自定义指标为什么从 MQL5 起步
在 MT5 里写自己的指标,本质是用 MQL5 把价格数据按你的逻辑重算一遍,而不是依赖平台自带的那几十个预设。对做价格行为的人来说,把某段形态识别写成可复用代码,比手动在图表上画线更不容易漏信号。 MQL5 的指标框架强制区分计算缓冲区和绘图缓冲区,新手最容易栽在 OnCalculate 的返回值上——返回的是已处理柱数,不是错误码。这个细节不理清,指标要么不刷新,要么在回测里乱跳。 从 2025 年 12 月平台公开文档看,自定义指标编译后体积普遍在 5–30 KB,加载延迟低于 50 ms(本地 SSD 测),足够做实时盯盘。外汇和贵金属杠杆高、滑点随机,任何指标都只是概率辅助,别当成进出场圣旨。
不靠内置函数也能造指标
很多交易者用惯了 iMA、iRSI 这类内置接口,却没想过它们底层到底怎么跑的。这一节我们直接绕开 iMA,从零在 MQL5 里手搓一条移动平均线,先把指标的内部机制摸透,而不是只会调现成函数。 整套做法拆成两块:先用最朴素的计算把线形 MA 画出来,再把这条线改造成烛形样式——也就是用开盘、收盘、最高、最低四个 buffer 模拟出一根根 K 线形态的均值棒。这么做不只是换个好看的样子,而是给你一条路:以后想要什么奇形怪状的自定义工具,都能照这个骨架改。 这一节里你会碰到几个硬核落点:用 SetIndexBuffer() 把计算值塞进指标缓冲区、在 OnCalculate() 里逐根处理价格、以及根据价格变动去定烛形颜色。哪怕你之前没写过 MQL5,跟着逐行拆也能跑通,因为每一行都对应一个可验证的动作——开 MT5 新建指标文件粘进去,编译完拖到图表就能看到一条不依赖 iMA 的均线。外汇与贵金属波动剧烈,自定义指标仅辅助判断,实际信号失效概率不低,请先在模拟盘验证。
「自己写指标还是让EA替你下单」
MT5 自带的内置指标(MA、RSI、MACD)用 iMA()、iRSI()、iMACD() 就能直接拉数据,但计算逻辑和显示方式被锁死。想要按自己的价格行为逻辑算信号,只能走自定义指标:用 MQL5 从头写计算、画图、报警,精度和灵活度远超预装件。 EA 和指标都读行情,但分工硬切断。EA 靠 OnTick() 抓每次报价刷新,能开平仓、挪止损、算止盈;指标压根没有交易权限,只靠 OnCalculate() 啃历史加实时数据,吐出趋势线、染色K线这类视觉提示。说白了,指标是给你眼睛用的,EA 是给你手用的。 外汇和贵金属杠杆高、滑点凶,自定义指标再漂亮也只是概率参考,最终下单动作和风险承受得你自己扛。下面这段骨架代码把两个核心函数的空壳摆出来了,复制进 MT5 编辑器就能填逻辑跑。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], 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">// }
◍ 先画蓝图再写指标:两条均线的构建逻辑
在 MT5 里动手写指标前,最容易被跳过的步骤是伪代码。先把「指标挂在哪个窗口、要几个缓冲区、绘图长什么样、用户输入什么参数」写清楚,能直接降低写 OnCalculate 时的逻辑错误率。下面这两条指标都不调用现成 iMA,全靠自己累加价格算均值。 第一条叫 MA 线:在同一图表窗口叠三条均线,周期和取样价都不同——200 周期取最高价、100 周期取收盘价、50 周期取开盘价。绘图属性得分别定义:MA200 用点划线蓝色,MA100 用虚线棕色,MA50 用点线紫色。初始化时各缓冲区要跳过前 N 根柱(N=周期数),否则前面全是空值乱画。 第二条烛形 MA 放在独立子窗口,用周期 5 的平滑值画彩色蜡烛:开高低收各一个缓冲区,再加一个颜色缓冲区(0 绿 1 红)。每根柱子把前 5 根对应的 OHLC 加起来除以 5,收盘价≥开盘价填绿、否则填红。外汇和贵金属波动大、杠杆高,这类自绘均线只反映历史平均,对后续拐点的判断只是概率倾向,真要上实盘先开 MT5 用策略测试器跑一遍。 两个指标的伪代码都强制要求「柱数不足时报错退出」:MA 线靠跳过初始柱规避,烛形 MA 在计算前检查 counted 是否够 period,不够就 Print 错误并 return。这套前置检查写熟了,你后面改周期、换价格类型都不会把图表刷成乱线。
把三条均线手工画进主图
动手写自定义指标前,先想清楚四件事:画在主图还是副图、要几个缓冲区、绘图类型是线还是烛形、各元素配什么颜色。这套蓝图能避免写到一半发现结构乱掉。比如要在主图叠 50/100/200 三条均线,就得在主图窗口跑,并预留三个数据缓冲区。
MQL5 里用 #property indicator_chart_window 指定主图显示;若想做成 RSI 那种副图指标,则改用 indicator_separate_window。缓冲区与绘图数量用 indicator_buffers 3 和 indicator_plots 3 声明——我们每条均线一个缓冲区、一个绘图,互不串扰。
绘图外观靠编号后缀绑定:indicator_label1 是名字,indicator_type1 是 DRAW_LINE,indicator_style1 控制实线/虚线,indicator_width1 是粗细,indicator_color1 是颜色。本例 MA200 蓝虚点线、MA100 棕虚线、MA50 紫点线,改 indicator_style2 只动中间那条,不会波及其余。
声明双精度数组存值后,必须用 SetIndexBuffer() 把数组绑到索引:0 绑 MA200、1 绑 MA100、2 绑 MA50,类型填 INDICATOR_DATA。漏掉这步,指标系统不认缓冲区,图表空白。周期变量 period_ma200=200 等做成 input,交易者界面改参不用动代码;并用 PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 200) 告诉 MT5 满 200 根 K 线后再画,避免越界。
核心计算在 OnCalculate(),它不像 EA 的 OnTick() 要靠 CopyClose() 抓数,直接拿 high[]、open[] 等内置数组。下面这段手算 MA200(用高价),从 period_ma200-1 起遍历,内层再加总最近 200 根最高价求平均,存进 buffer_mA200[i]。外汇/贵金属波动大,均线只是概率性平滑工具,实盘前请在 MT5 用历史数据验证缓冲区绑定是否正确。
class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">3 class="macro">#class="kw">property indicator_plots class="num">3 class="macro">#class="kw">property indicator_label1 "MA class="num">200" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_style1 STYLE_DASHDOTDOT class="macro">#class="kw">property indicator_width1 class="num">1 class="macro">#class="kw">property indicator_color1 clrBlue class="macro">#class="kw">property indicator_label2 "MA class="num">100" class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_style2 STYLE_DASH class="macro">#class="kw">property indicator_width2 class="num">1 class="macro">#class="kw">property indicator_color2 clrBrown class="macro">#class="kw">property indicator_label3 "MA class="num">50" class="macro">#class="kw">property indicator_type3 DRAW_LINE class="macro">#class="kw">property indicator_style3 STYLE_DOT class="macro">#class="kw">property indicator_width3 class="num">1 class="macro">#class="kw">property indicator_color3 clrPurple class="type">class="kw">double buffer_mA200[], buffer_mA100[], buffer_mA50[]; input class="type">int period_ma200 = class="num">200; input class="type">int period_ma100 = class="num">100; input class="type">int period_ma50 = class="num">50; class="type">int OnInit() { SetIndexBuffer(class="num">0, buffer_mA200, INDICATOR_DATA); SetIndexBuffer(class="num">1, buffer_mA100, INDICATOR_DATA); SetIndexBuffer(class="num">2, buffer_mA50, INDICATOR_DATA); PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, period_ma200); PlotIndexSetInteger(class="num">1, PLOT_DRAW_BEGIN, period_ma100); PlotIndexSetInteger(class="num">2, PLOT_DRAW_BEGIN, period_ma50); class="kw">return(INIT_SUCCEEDED); } class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { for(class="type">int i = period_ma200 - class="num">1; i < rates_total; i++) { class="type">class="kw">double sum = class="num">0.0; for(class="type">int j = class="num">0; j < period_ma200; j++) sum += high[i - j]; buffer_mA200[i] = sum / period_ma200; } class=class="str">"cmt">// MA100 与 MA50 同理,分别用 close[] 与 open[] 及对应周期 class="kw">return(rates_total); }