针对初学者的 MQL 5 中的自定义指标(基础篇)
📘

针对初学者的 MQL 5 中的自定义指标(基础篇)

第 1/2 篇

在 MT5 里写第一个自定义指标

MQL5 的自定义指标和 EA 不同,它不参与下单,只负责在图表上算数、画线、出缓冲。对刚接触 MT5 的开发者来说,先搞懂指标文件结构比背语法更重要。 一个最基础的指标要声明 #property indicator_separate_window 或主图叠加,并至少定义一个缓冲区(buffer)用来存计算结果。MT5 会在每根 K 线走完或实时 tick 进来时,调用 OnCalculate 把值写进 buffer。 官方示例库里有一篇 2013-09-13 发布的入门贴,累计浏览 8 066、评论 23,作者 Nikolay Kositsin。这个数字说明:即便多年过去,最朴素的自定义指标写法仍是新手高频查阅对象,你照着敲一遍比看十篇概念文更实在。

「中年起步的交易编程门槛」

任何学科的底子都建立在基础认知上,数学、音乐、编程皆如此。若从很小就开始接触相似训练,底层逻辑会建立得更具体全面,后续学复杂内容阻力小。 现实里,多数做外汇和贵金属的人是人到中年才第一次碰金融与股票市场,此时再回头补基础,理解成本明显偏高,且容易卡在工具层。 本文这一节只做一件事:帮你跨过用 MQL5 给 MetaTrader 5 写自定义指标的最初那道坎。外汇与贵金属杠杆高、波动剧烈,先能把工具跑通,才有谈策略的前提。

◍ 用一段裸 SMA 代码看清指标骨架

想弄懂 MT5 自定义指标怎么跑,最快的路子不是看文档,而是直接读一段能编译、能上图的裸代码。下面这份简单移动平均线(SMA)示例不夹任何花活,只做一件事:把最近 MAPeriod 根 K 线的收盘价加总除以周期数。 公式层面,SMA = SUM(CLOSE(i), MAPeriod) / MAPeriod。SUM 是窗口内收盘价求和,CLOSE(i) 是第 i 根柱的收盘,MAPeriod 就是平均周期。代码里默认 MAPeriod=13、MAShift=0,意味着主图红线上没有任何水平偏移。 代码头部用 #property 声明了三件关键事:indicator_chart_window 表示画在主图而非独立窗口;indicator_buffers 1 和 indicator_plots 1 说明只有一个数据缓冲、一条绘制线;indicator_type1 DRAW_LINE 配合 indicator_color1 Red 把这条线渲染成红色。 OnInit 里 SetIndexBuffer(0, ExtLineBuffer, INDICATOR_DATA) 把数组绑给 0 号缓冲;PlotIndexSetInteger 两行分别设了线偏移和起始绘制位置(MAPeriod-1 之前数据不足不画)。OnCalculate 中若 rates_total 小于 MAPeriod-1 直接返回 0;首次计算从 MAPeriod-1+begin 起,后续借助 prev_calculated 只补算新 bar,双层循环求 Sum 再除周期写进缓冲。 打开 MT5 新建一个 SMA.mq5,把上面代码整段贴入编译,拖到 EURUSD 图表上,你就能看到一条 13 期红色均线。外汇与贵金属杠杆高、回测不代表实盘,调 MAPeriod 从 13 改成 5 或 50,可直观比对平滑度差异。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                  SMA.mq5 |
class=class="str">"cmt">//|             Copyright class="num">2009, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">1
class="macro">#class="kw">property indicator_plots  class="num">1
class="macro">#class="kw">property indicator_type1  DRAW_LINE
class="macro">#class="kw">property indicator_color1 Red

class="kw">input class="type">int MAPeriod = class="num">13;
class="kw">input class="type">int MAShift = class="num">0; 

class="type">class="kw">double ExtLineBuffer[]; 
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+  
class="type">void OnInit()
  {
  SetIndexBuffer(class="num">0, ExtLineBuffer, INDICATOR_DATA);
  PlotIndexSetInteger(class="num">0, PLOT_SHIFT, MAShift);
  PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, MAPeriod - class="num">1);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[])
  {
  if (rates_total < MAPeriod - class="num">1)
   class="kw">return(class="num">0);
   
  class="type">int first, bar, iii;
  class="type">class="kw">double Sum, SMA;
  
  if (prev_calculated == class="num">0)
   first = MAPeriod - class="num">1 + begin;
  else first = prev_calculated - class="num">1;
  for(bar = first; bar < rates_total; bar++)
   {
    Sum = class="num">0.0;
    for(iii = class="num">0; iii < MAPeriod; iii++)
     Sum += price[bar - iii];
    
    SMA = Sum / MAPeriod;
     
    ExtLineBuffer[bar] = SMA;
   }
   
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

注释只是给读代码的人看的

打开一段 MT5 指标源码,最先撞进眼里的往往是成排的 "//" 开头行。它们就是单行注释,从 "//" 起、到换行止,编译器直接跳过,不参与任何运算。 把注释整段删掉,程序该算什么还算什么,逻辑零损伤;但一周后你回看自己的 SMA 逻辑,大概率得从头脑补哪段是 OnInit 哪段是 OnCalculate。注释存在的唯一理由,是保住代码的可读性。 示例里第一部分写指标名和版权,中间用分隔条标出初始化函数与迭代函数,最后一行收尾关代码。作者想在 "//" 后面写任何帮理解的话都行,外汇与贵金属 EA 开发高风险,注释写清假设比写漂亮话有用。 下面这段就是典型单行注释块,逐行看:前四行用 "//+---+" 画框并标出 SMA.mq5 及版权信息;接着两个分隔块分别标 "自定义指标初始化函数" 和 "自定义指标迭代函数";最末 "//+---+" 只是收尾。全部以 "//" 起头,对执行无任何影响。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                  SMA.mq5 |
class=class="str">"cmt">//|             Copyright class="num">2009, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+  
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

能。先从一段裸 SMA 代码看清指标骨架,理解 OnInit、OnCalculate 和绘图缓冲区的分工,再动手改参数即可起步。
一个初始化函数、一个计算函数、一个用于存储画线的缓冲区数组,以及把缓冲区绑定到图表线的语句。
小布可帮你生成指标代码骨架并标注中文注释,你只需填入计算逻辑和参数,不必从空白文件啃语法。
注释只给读代码的人看,编译后不执行也不改变任何信号,但留清晰注释能让你隔周回来还看得懂逻辑。
多数情况是没把缓冲区用 SetIndexBuffer 绑定到可见线,或 OnCalculate 里没把算出来的值写进缓冲区数组。