探索新功能:MQL5 的自定义指标(基础篇)
📘

探索新功能:MQL5 的自定义指标(基础篇)

第 1/3 篇

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

MQL5 把指标和计算逻辑彻底拆开,自定义指标不再像 MQL4 那样挤在脚本里,而是独立编译成 ex5 挂到图表。对做价格行为分析的人,这意味着你能把一套形态识别逻辑直接变成副图,不用每次手动标。 这套机制从 MT5 上线就开放,官方示例库里最早的一批自定义指标文档发布于 2013-08-21,至今在终端里仍能编译运行,说明接口相对稳定。 想在 MT5 验证很简单:打开 MetaEditor,新建 Custom Indicator,选空模板,编译后拖到 EURUSD 图表,就能看到副图容器。外汇和贵金属杠杆高、滑点大,指标只是概率参考,别当成进出场唯一依据。

「MT5 到底新在哪」

MetaTrader 5 这套终端值得老交易者专门切过去试一次,核心变化不在界面,而在底层的 MQL5 语言和执行环境。它从根上改了编程语言,既能走结构化老路,也正式放开了面向对象编程,这对写复杂策略的人是实打实的利好。 相比 MT4,MQL5 的代码执行速度快了一个量级,回测和实时计算的卡顿明显更少;同时终端显示必要信息的扩展潜力也比以前强太多,自定义面板和图形对象不再挤在有限接口里。 本文不堆砌所有新特性,也不在这节放 OOP 代码——那部分太重,得单开章节讲。接下来我们只拆指标:MQL5 指标的结构、图形、类型与编程细节,和 MQL4 的差异会落到具体代码上。 随附文件可以直接丢进终端编译运行,无论你是刚摸 MT5 还是写了几年 EA,都能当场验证文里的点,外汇和贵金属品种波动大、杠杆高,跑之前先在小账户或策略测试器里确认风险。

MT5指标骨架:三大函数与属性定义

从 MQL4 切到 MQL5,指标的总体结构并没有颠覆:依然由初始化、计算、去初始化三个核心函数撑起整个生命周期。许多显示层面的设定可以靠 #property 在全局上下文直接声明,输入参数也照旧用 input 标识符写在文件头部,和旧版写法一脉相承。 一个实用变化是,input 现在支持枚举类型,下拉框会列出所有合法值,能有效挡掉手误填错参数的情况。上面代码里 AppliedPrice 用 ENUM_APPLIED_PRICE,自定义的 DayOfWeek 枚举也会进同一个下拉列表,开 MT5 挂上指标就能看到效果。 OnInit 里做缓冲区绑定和句柄分配,OnCalculate 负责逐根 K 线算值,OnDeinit 一般只用来释放文件句柄这类资源,多数简单指标可以留空。下面这段删节自一个彩色 RSI 实例,重点看属性声明和输入参数怎么铺。 代码里 indicator_buffers 设了 2,但 indicator_plots 只设 1,说明第二个缓冲区是颜色索引而非独立画线;RSIPeriod 默认 5、SmoothPeriod 默认 5,都是可直接在外部面板改的。外汇与贵金属波动剧烈,指标仅作概率参考,实盘前务必在策略测试器跑一遍验证。

MQL5 / C++
class=class="str">"cmt">//--- 数据属性部分
class="macro">#class="kw">property copyright "TheXpert"
class="macro">#class="kw">property link      "theforexpert@gmail.com"
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//--- 指标的描述一共不能超过511个字符
class=class="str">"cmt">//--- 包括换行符在内
class="macro">#class="kw">property description "      "
class="macro">#class="kw">property description "      根据彩色相对强弱指数指标(RSI)实例"
class="macro">#class="kw">property description "      的指标创建演示。"
class=class="str">"cmt">//--- 指标属性
class="macro">#class="kw">property indicator_separate_window class=class="str">"cmt">// 指标在单独子窗口中显示
class="macro">#class="kw">property indicator_buffers class="num">2      class=class="str">"cmt">// 使用缓冲区的数量
class="macro">#class="kw">property indicator_plots   class="num">1      class=class="str">"cmt">// 显示缓冲区的数量
class=class="str">"cmt">//--- 绘图1
class="macro">#class="kw">property indicator_color1 clrDarkSalmon, clrDeepSkyBlue class=class="str">"cmt">// 使用两种颜色
class="macro">#class="kw">property indicator_type1  DRAW_COLOR_LINE              class=class="str">"cmt">// 显示样式
class=class="str">"cmt">//---- 缓冲区
class="type">class="kw">double Values[];          class=class="str">"cmt">// 数值缓冲区
class="type">class="kw">double ValuesPainting[];  class=class="str">"cmt">// 颜色索引缓冲区
class=class="str">"cmt">//--- 指标输入参数
input class="type">class="kw">string      _1      = "RSI parameters";
input class="type">int         RSIPeriod   = class="num">5;
input class="type">int         SmoothPeriod = class="num">5;
input ENUM_APPLIED_PRICE AppliedPrice = PRICE_CLOSE;
input class="type">class="kw">string      _2      = "Color settings";
input class="type">color       Down        = clrDarkSalmon;
input class="type">color       Up          = clrDeepSkyBlue;
class=class="str">"cmt">//---存放指标句柄的变量
class="type">int RSIHandle;
class=class="str">"cmt">//...
enum DayOfWeek
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};
input DayOfWeek Day;
class=class="str">"cmt">//...
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 绑定缓冲区
class=class="str">"cmt">//--- 用于做显示缓冲区的数值
   SetIndexBuffer(class="num">0,Values,INDICATOR_DATA);
class=class="str">"cmt">//--- 于做保存颜色缓冲区的数值
   SetIndexBuffer(class="num">1,ValuesPainting,INDICATOR_COLOR_INDEX);
class=class="str">"cmt">//--- 设置绘图缓冲区起点
   PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,RSIPeriod);
class=class="str">"cmt">//--- 设置指标名称
   IndicatorSetString(INDICATOR_SHORTNAME,"Color RSI("+class="type">class="kw">string(RSIPeriod)+")");
class=class="str">"cmt">//--- 设置绘图的空值

◍ RSI双色绘制与增量计算内核

在 MT5 自定义指标里,用 PlotIndexSetInteger 给 0 号绘图索引分别绑定索引 0(Down)与索引 1(Up)两种线色,就能让 RSI 曲线随涨跌自动换色。空值先交给 PLOT_EMPTY_VALUE 接 EMPTY_VALUE,避免历史缺口被误绘成零轴横线。 初始化阶段用 iRSI(NULL,0,RSIPeriod,AppliedPrice) 拿句柄,当前图表、当前周期由 NULL 与 0 指定;两个数组 ArraySetAsSeries(...,true) 改成时间倒序,保证 Values[0] 永远是最新一根 K。 OnCalculate 里 toCount 取 rates_total 与 rates_total-prev_calculated+1 的较小值,这是典型的增量计算写法——只处理新增 K 线,老数据不重复跑。CopyBuffer 拉 RSI 缓冲若返回 -1,直接 Print 错误码并 return(0) 等下一 tick 重算。 颜色判定循环从 toCount-2 倒退到 0:当 Values[i] > Values[i+1] 且前值非 EMPTY_VALUE 时,ValuesPainting[i] 置 1(涨色),否则置 0(跌色)。最后 return(rates_total),把总量回传成下次的 prev_calculated,外汇与贵金属波动大,这种写法在 1 分钟图负载会明显低于全量重算。

MQL5 / C++
  PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
class=class="str">"cmt">//--- 设置绘图颜色
  PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">0,Down);
  PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">1,Up);
class=class="str">"cmt">//--- 获取指标句柄
  RSIHandle=iRSI(NULL,class="num">0,RSIPeriod,AppliedPrice);
class=class="str">"cmt">//--- 设置缓冲区索引方向
  ArraySetAsSeries(Values,true);
  ArraySetAsSeries(ValuesPainting,true);
class=class="str">"cmt">//--- 成功执行
  class="kw">return(class="num">0);
  }
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">//--- 用于计算的柱数
  class="type">int toCount=(class="type">int)MathMin(rates_total,rates_total-prev_calculated+class="num">1);
class=class="str">"cmt">//--- 尝试复制iRSI指标数据
  if(CopyBuffer(RSIHandle,class="num">0,class="num">0,toCount,Values)==-class="num">1)
    {
      Print("数据复制错误, 错误码为",GetLastError());
      class=class="str">"cmt">//--- 返回重新计算指标数据
      class="kw">return(class="num">0);
    }
class=class="str">"cmt">//--- 计算颜色部分
  for(class="type">int i=toCount-class="num">2;i>=class="num">0;--i)
    {
      class=class="str">"cmt">//--- 计算第一条线的颜色
      if(Values[i+class="num">1]!=EMPTY_VALUE && Values[i]>Values[i+class="num">1])
        ValuesPainting[i]=class="num">1;
      else
        ValuesPainting[i]=class="num">0;
    }
class=class="str">"cmt">//--- 返回值为下一次调用的 prev_calculated 值
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//--- 此函数不是必需的
class=class="str">"cmt">/*
class="type">void OnDeinit()
{
}
*/

「MQL5 指标的两套数据接法」

从 MQL4 切到 MQL5,指标骨架先变一件事:原来靠 Start() 跑循环,现在统一交给 OnCalculate()。图表上的 OHLC、成交量、点差直接以数组引用塞进参数,不用再手写 CopyBuffer 去拉数据,代码量肉眼可见地降下来。 OnCalculate 第一个参数 rates_total 是数组总长度,第二个 prev_calculated 就是老版 IndicatorCounted() 的替代物——首次加载或补历史数据时被传 0,之后只传新增柱数。这个设计让增量计算变顺,但刚转过来的老手容易在 prev_calculated=0 的分支里漏掉初始化。 另一套概念是 i<…>OnArray 的扩展:指标不只能吃图表价格,还能吃别的指标句柄。参数 applied_price_or_handle 里传句柄,就能把 RSI 的输出喂给 MA 再平滑一层。终端自带的 Custom Moving Average 就是这么干的,三层嵌套能拿到三倍平滑度。 把零缓冲区当主输出,还能写「服务指标」——比如 Direction_Brush.mq5 把方向着色单独拆出来,挂到别的指标上就用。零缓冲区通用性有限,但写自定义指标时把核心信息放零缓冲,外围统计、信号、着色全丢给外部服务指标,主代码能干净不少。外汇和贵金属波动大、杠杆高,这类堆叠指标在跳空时可能延迟刷新,实盘前务必在 MT5 用历史数据跑一遍句柄链。

MQL5 / C++
class="type">int OnCalculate(const class="type">int rates_total,      class=class="str">"cmt">// 数组大小
                const class="type">int prev_calculated,   class=class="str">"cmt">// 上一次调用计算的柱数
                const class="type">class="kw">datetime& time[],      class=class="str">"cmt">// 当前图表和周期下的数据
                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="kw">return rates_total;
}
class="type">int OnCalculate(const class="type">int rates_total,      class=class="str">"cmt">// price[] 数组的大小
                const class="type">int prev_calculated,   class=class="str">"cmt">// 上一次调用计算的柱数
                const class="type">int begin,             class=class="str">"cmt">// 数据计算起点
                const class="type">class="kw">double& price[]        class=class="str">"cmt">// 用于计算的数据数组
                 )
  {
  class="kw">return rates_total;
  }
  {
  class=class="str">"cmt">//...
  RSIHandle = iRSI(NULL, class="num">0, RSIPeriod, AppliedPrice);
  SmoothHandle = iMA(NULL, class="num">0, SmoothPeriod, class="num">0, MODE_EMA, RSIHandle);
  class=class="str">"cmt">//...
  }

常见问题

先建一个空白指标文件,只写 OnInit 初始化和 OnCalculate 计算两个空函数,能编译通过就算跑通环境,再慢慢加逻辑。
主要是增量计算与多缓冲区分色绘制,不用每次重算全部 K 线,RSI 涨跌还能用不同颜色区分,看图更直观。
可以,把指标逻辑或截图发给小布,它能帮你核对函数结构和增量计算是否有漏,并提示常见写法坑。
开两个颜色缓冲分别存涨跌值,OnCalculate 里按当前 RSI 与昨值比较填对应缓冲,另一个置空,plot 设双色即可。
一套接价格序列适合跟着K线算,一套接其他指标缓冲适合做嵌套;接错会导致数组越界或算出来全是空值。