自定义指标(第一部份):在MQL5中逐步开发简单自定义指标的入门指南·进阶篇
📊

自定义指标(第一部份):在MQL5中逐步开发简单自定义指标的入门指南·进阶篇

(2/3)· 搞懂技术指标与自定义指标的边界,才能把内置工具改造成自己的交易视角

偏理论 第 2/3 篇
很多交易者把指标当成了自动下单的机器,结果在震荡市里被假信号反复扫损。指标只做数学计算与可视化,不碰头寸——这个边界不清,再漂亮的曲线也救不了你的账户。外汇贵金属杠杆高,先把分析工具和执行动作分开,是活下来的前提。

「双缓冲指标的头文件与初始化骨架」

这段代码片段展示了一个自定义指标在 MT5 里的声明与初始化前半段:用 #property 把两个绘图层(直方图 + 价格线)和各自样式钉死,再在 GetInit() 里把数组绑到指标缓冲区。外汇与贵金属杠杆高,直接挂实盘前务必在策略测试器里先跑历史数据验证。 #property indicator_buffers 2 和 indicator_plots 2 意味着该指标占用 2 个数据缓冲、画出 2 个图层;MA_Histogram 用 DRAW_HISTOGRAM 且色为 clrDodgerBlue,Current_Price_Line 用 DRAW_LINE 且线宽 2、色为 clrGoldenrod。输入参数 _maPeriod 默认 50、_maShift 默认 0,决定均线周期与水平偏移。 GetInit() 中 SetIndexBuffer(0, maHistogramBuffer, INDICATOR_DATA) 把索引 0 绑到直方图缓冲,SetIndexBuffer(1, priceLineBuffer, INDICATOR_DATA) 把索引 1 绑到价格线缓冲。随后 IndicatorSetInteger(INDICATOR_DIGITS, _Digits+1) 把显示精度多加 1 位;PlotIndexSetInteger 对两个 plot 分别设 PLOT_DRAW_BEGIN(直方图从 _maPeriod 根 K 线起画,价格线从 0 起)与 PLOT_SHIFT(直方图偏移 _maShift,价格线不偏移)。 开 MT5 新建指标把这段头声明和 GetInit 原样贴入,编译后能在副图看到蓝直方图与金线两层结构;改 _maPeriod 从 50 到 20,直方图起始根数会随 PLOT_DRAW_BEGIN 同步前移,可直观比对敏感度。

MQL5 / C++
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Indicator deinitialization function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   class=class="str">"cmt">//-- deinitialization code
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property description "A custom indicator to demonstrate a linear weighted moving average."
class="macro">#class="kw">property description "A histogram and line are drawn in a separate window to indicate "
class="macro">#class="kw">property description "the moving average direction."
class=class="str">"cmt">//--- indicator buffers and plots
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- plots1 details for the ma histogram
class="macro">#class="kw">property indicator_label1   "MA_Histogram"
class="macro">#class="kw">property indicator_type1    DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1   clrDodgerBlue
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plots2 details for the price line
class="macro">#class="kw">property indicator_label2   "Current_Price_Line"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrGoldenrod
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">2
class=class="str">"cmt">//--- class="kw">input parameters for the moving averages
class="kw">input class="type">int               _maPeriod = class="num">50;      class=class="str">"cmt">// MA Period
class="kw">input class="type">int               _maShift = class="num">0;        class=class="str">"cmt">// MA Shift
class=class="str">"cmt">//--- indicator buffer
class="type">class="kw">double maHistogramBuffer[], priceLineBuffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User custom function for custom indicator initialization         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GetInit()
  {
class=class="str">"cmt">//--- set the indicator buffer mapping
   SetIndexBuffer(class="num">0, maHistogramBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">1, priceLineBuffer, INDICATOR_DATA);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- set the indicators accuracy
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits + class="num">1);
class=class="str">"cmt">//--- set the first bar from where the index will be drawn
   PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, _maPeriod);
   PlotIndexSetInteger(class="num">1, PLOT_DRAW_BEGIN, class="num">0);
class=class="str">"cmt">//--- set the indicator shifts when drawing
   PlotIndexSetInteger(class="num">0, PLOT_SHIFT, _maShift);
   PlotIndexSetInteger(class="num">1, PLOT_SHIFT, class="num">0);

◍ LWMA 直方图指标的初始化与加权计算内核

在 MT5 自定义指标里,先给 DataWindow 一个可读的短名,再把两条绘图(直方图与价格线)的空值锚定到 0.0,可以避免早期 K 线画出无意义的零轴噪点。 IndicatorSetString(INDICATOR_SHORTNAME, "LWMA_Histo" + "(" + string(_maPeriod) + ")"); 这行把周期参数拼进名称,切换周期时图例自动跟着变,不用手动改标签。 线性加权移动平均的核心在 GetLWMA:权重按 1、2、…、_maPeriod 线性递增,分母 weight 是这些整数之和。首次计算时若 prev_calculated <= _maPeriod + begin + 2,前 start = _maPeriod + begin 根 K 线强制写 0.0,保证加权窗口拉满前不输出半成品。 主循环用增量更新省掉每根重算:sum = sum - lsum + price[x] * _maPeriod,lsum = lsum - price[x - _maPeriod] + price[x],复杂度从 O(N*P) 降到 O(N),在 EURUSD 的 M1 上跑 10 万根历史可能少卡顿几秒。外汇与贵金属波动剧烈,这类指标仅描述已发生的价格加权重心,不预示方向。 注意源码里出现了两个 OnCalculate 重载,MT5 编译会报重复定义,实盘前必须把带空 return(rates_total) 的那个删掉,否则指标不画值。

MQL5 / C++
class=class="str">"cmt">//--- set the name to be displayed in the MT5 DataWindow
  IndicatorSetString(INDICATOR_SHORTNAME, "LWMA_Histo" + "(" + class="type">class="kw">string(_maPeriod) + ")");
class=class="str">"cmt">//--- set the drawing histogram and line to an empty value
  PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, class="num">0.0);
  PlotIndexSetDouble(class="num">1, PLOT_EMPTY_VALUE, class="num">0.0);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Function to calculate the linear weighted moving average          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GetLWMA(class="type">int rates_total, class="type">int prev_calculated, class="type">int begin, const class="type">class="kw">double &price[])
  {
   class="type">int    weight = class="num">0;
   class="type">int    x, l, start;
   class="type">class="kw">double sum = class="num">0.0, lsum = class="num">0.0;
class=class="str">"cmt">//--- first calculation or number of bars was changed
   if(prev_calculated <= _maPeriod + begin + class="num">2)
     {
      start = _maPeriod + begin;
      class=class="str">"cmt">//--- set empty value for first start bars
      for(x=class="num">0; x < start; x++)
        {
         maHistogramBuffer[x] = class="num">0.0;
         priceLineBuffer[x] = price[x];
        }
     }
   else
      start = prev_calculated - class="num">1;
   for(x = start - _maPeriod, l = class="num">1; x < start; x++, l++)
     {
      sum  += price[x] * l;
      lsum  += price[x];
      weight += l;
     }
   maHistogramBuffer[start-class="num">1] = sum/weight;
   priceLineBuffer[x] = price[x];
class=class="str">"cmt">//--- main loop
   for(x=start; x<rates_total && !IsStopped(); x++)
     {
      sum               = sum - lsum + price[x] * _maPeriod;
      lsum              = lsum - price[x - _maPeriod] + price[x];
      maHistogramBuffer[x] = sum / weight;
      priceLineBuffer[x] = price[x];
     }
  }
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">int begin,
                const class="type">class="kw">double &price[])
  {
   class="kw">return(rates_total);
  }
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">int begin,
                const class="type">class="kw">double &price[])
  {
class=class="str">"cmt">//--- check if we have enough bars to do the calculations
   if(rates_total < _maPeriod - class="num">1 + begin)

指标双图层的初始化与计算骨架

这段 MT5 自定义指标把窗口拆成两层:缓冲区设了 2 个、绘图也设了 2 个,分别挂直方图与价格线。直方图用 DRAW_HISTOGRAM 渲染、颜色 clrDodgerBlue、线宽 1;价格线用 DRAW_LINE、颜色 clrGoldenrod、线宽 2,肉眼区分度够高。 输入参数只有两个:_maPeriod 默认 50,_maShift 默认 0。50 周期线性加权均线在 EURUSD 日线测试时,前 49 根 K 线因数据不足不绘图,这是 PLOT_DRAW_BEGIN 被设为 _maPeriod-1+begin 的直接结果。 OnInit 只干一件事——调用 GetInit 做缓冲区绑定,返回 INIT_SUCCEEDED。真正的计算在 OnCalculate:若 rates_total 小于 _maPeriod-1+begin 直接 return(0) 退出;首次运行(prev_calculated==0)会清空直方图缓冲并设定绘图起点。 首次计算后调用 GetLWMA 填充均线,最后 return(rates_total) 把已处理根数交还给下一轮。你打开 MT5 新建指标把这段贴进 MQ5,改 _maPeriod 到 20 能在小周期更快出信号,但外汇与贵金属杠杆高,信号失效概率随周期缩小而上升。

MQL5 / C++
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_separate_window
class=class="str">"cmt">//--- indicator buffers and plots
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- plots1 details for the ma histogram
class="macro">#class="kw">property indicator_label1  "MA_Histogram"
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1   clrDodgerBlue
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plots2 details for the price line
class="macro">#class="kw">property indicator_label2  "Current_Price_Line"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrGoldenrod
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">2
class=class="str">"cmt">//--- class="kw">input parameters for the moving averages
class="kw">input class="type">int               _maPeriod = class="num">50;      class=class="str">"cmt">// MA Period
class="kw">input class="type">int               _maShift = class="num">0;        class=class="str">"cmt">// MA Shift
class=class="str">"cmt">//--- indicator buffer
class="type">class="kw">double maHistogramBuffer[], priceLineBuffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- call the custom initialization function
   GetInit();
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
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">int begin,
                const class="type">class="kw">double &price[])
  {
class=class="str">"cmt">//--- check if we have enough bars to do the calculations
   if(rates_total < _maPeriod - class="num">1 + begin)
      class="kw">return(class="num">0);
class=class="str">"cmt">//--- first calculation or number of bars was changed
   if(prev_calculated == class="num">0)
      {
       ArrayInitialize(maHistogramBuffer, class="num">0);
       PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, _maPeriod - class="num">1 + begin);
      }
class=class="str">"cmt">//--- calculate the linear weighted moving average and plot it on the chart
   GetLWMA(rates_total, prev_calculated, begin, price);
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
   class="kw">return(rates_total);
  }

「LWMA 直方图指标的缓冲与权重落地」

在 MT5 自定义指标里,GetInit 负责把两个缓冲区绑到图表:索引 0 挂直方图 maHistogramBuffer,索引 1 挂价格线 priceLineBuffer,两者都标为 INDICATOR_DATA。精度被刻意拉到 _Digits+1,贵金属 XAUUSD 在 5 位平台下就会显示到小数点后 3 位,比报价多一位。 PLOT_DRAW_BEGIN 控制起绘位置:直方图从 _maPeriod 根 K 线后才画,避免加权样本不足的前段失真;价格线从 0 开始。直方图还加了 _maShift 偏移,方便和主图蜡烛错开看。短名拼成 LWMA_Histo(周期),在数据窗口一眼能认。 GetLWMA 的权重循环是核心:x 从 start-_maPeriod 起,l 从 1 递增,sum += price[x]*l 做线性加权,lsum 则累加未加权价格。首次计算时若 prev_calculated 小于 _maPeriod+begin+2,前 start 根缓冲先填 0.0,这段在 EURUSD 的 M15 上通常对应前 20~30 根空白,属正常预热。 调用层里 prev_calculated==0 时会 ArrayInitialize 清缓冲,并把 PLOT_DRAW_BEGIN 重设为 _maPeriod-1+begin;否则直接 GetLWMA 算完 return rates_total,让下一帧接着增量更新。外汇与贵金属杠杆高,指标仅描述历史加权关系,反转概率不保证。

MQL5 / C++
  class="kw">return(class="num">0);
class=class="str">"cmt">//--- first calculation or number of bars was changed
  if(prev_calculated == class="num">0)
    {
      ArrayInitialize(maHistogramBuffer, class="num">0);
      PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, _maPeriod - class="num">1 + begin);
    }
class=class="str">"cmt">//--- calculate the linear weighted moving average and plot it on the chart
  GetLWMA(rates_total, prev_calculated, begin, price);
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User custom function for custom indicator initialization          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GetInit()
  {
class=class="str">"cmt">//--- set the indicator buffer mapping
  SetIndexBuffer(class="num">0, maHistogramBuffer, INDICATOR_DATA);
  SetIndexBuffer(class="num">1, priceLineBuffer, INDICATOR_DATA);
class=class="str">"cmt">//--- set the indicators accuracy
  IndicatorSetInteger(INDICATOR_DIGITS, _Digits + class="num">1);
class=class="str">"cmt">//--- set the first bar from where the index will be drawn
  PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, _maPeriod);
  PlotIndexSetInteger(class="num">1, PLOT_DRAW_BEGIN, class="num">0);
class=class="str">"cmt">//--- set the indicator shifts when drawing
  PlotIndexSetInteger(class="num">0, PLOT_SHIFT, _maShift);
  PlotIndexSetInteger(class="num">1, PLOT_SHIFT, class="num">0);
class=class="str">"cmt">//--- set the name to be displayed in the MT5 DataWindow
  IndicatorSetString(INDICATOR_SHORTNAME, "LWMA_Histo" + "(" + class="type">class="kw">string(_maPeriod) + ")");

class=class="str">"cmt">//--- set the drawing histogram and line to an empty value
  PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, class="num">0.0);
  PlotIndexSetDouble(class="num">1, PLOT_EMPTY_VALUE, class="num">0.0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Function to calculate the linear weighted moving average         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GetLWMA(class="type">int rates_total, class="type">int prev_calculated, class="type">int begin, const class="type">class="kw">double &price[])
  {
  class="type">int     weight = class="num">0;
  class="type">int     x, l, start;
  class="type">class="kw">double sum = class="num">0.0, lsum = class="num">0.0;
class=class="str">"cmt">//--- first calculation or number of bars was changed
  if(prev_calculated <= _maPeriod + begin + class="num">2)
    {
      start = _maPeriod + begin;
      class=class="str">"cmt">//--- set empty value for first start bars
      for(x=class="num">0; x < start; x++)
        {
         maHistogramBuffer[x] = class="num">0.0;
         priceLineBuffer[x] = price[x];
        }
    }
  else
      start = prev_calculated - class="num">1;
  for(x = start - _maPeriod, l = class="num">1; x < start; x++, l++)
    {
      sum  += price[x] * l;
      lsum  += price[x];

◍ 主循环里的加权移动均值递推

这段逻辑跑在指标主循环里,从 start 位置一路算到 rates_total,中途用 IsStopped() 做退出判断,避免 MT5 终端关闭时还空转。 核心是把前一段的 sum 和 lsum 做增量更新:sum 减去最旧窗口的 lsum、加上当前价格乘周期长度;lsum 则滑窗丢掉 x-_maPeriod 处的老价格、纳入当前价格。这样每根 K 线只做常数级运算,比每次重算整段均值快一个数量级。 maHistogramBuffer[x] 直接拿 sum 除以预设 weight 得到加权均线值,priceLineBuffer[x] 同步存原始价格,方便后续在图表上叠加价格线与直方图做背离观察。外汇与贵金属波动大、杠杆高,这类指标仅作概率参考,实盘前务必在 MT5 策略测试器用历史数据验证权重参数。

MQL5 / C++
   weight += l;
   }
   maHistogramBuffer[start-class="num">1] = sum/weight;
   priceLineBuffer[x] = price[x];
class=class="str">"cmt">//--- main loop
   for(x=start; x<rates_total && !IsStopped(); x++)
   {
      sum            = sum - lsum + price[x] * _maPeriod;
      lsum           = lsum - price[x - _maPeriod] + price[x];
      maHistogramBuffer[x] = sum / weight;
      priceLineBuffer[x] = price[x];
   }
}

用点差监控和彩色K线练手自定义指标

把前面讲过的自定义指标骨架落到实处,最快方式是照着两个现成例子在 MT5 里敲一遍。一个是 SpreadMonitor,一个是 SmoothedCandlesticks,两者都不复杂,但覆盖了多色绘图、标准指标函数调用和长版 OnCalculate 这几个关键动作。 SpreadMonitor 跑在独立子窗口,用 DRAW_COLOR_HISTOGRAM 画点差柱。当前点差高于前一根柱时柱体转红,低于时转蓝,浮动点差品种一眼就能看出飙升时段。原文示例加载在 GBPJPY 的 M5 图上,这类交叉盘点差跳变比直盘更频繁,适合验证。 SmoothedCandlesticks 则直接在价格窗口重绘 K 线:OHLC 四价全在平滑 MA 之上染绿,全在下染红,MA 穿进实体则深灰表示无信号。它用 iMA 标准函数拿平滑线句柄,省掉自己写均线的麻烦,总共开了 6 个缓冲、2 个绘图。 下面这段是 SpreadMonitor 的核心属性与缓冲声明片段,注意 indicator_buffers 设 2、plots 设 1,颜色给了一对 clrDarkBlue 和 clrTomato 由颜色索引缓冲切换。 真要吸收,就建个 Article 文件夹,两文件都丢进去编译,挂 GBPJPY 五分钟图看柱色变化,再切到 SmoothedCandlesticks 调 MA 周期参数对比噪音消减效果。外汇贵金属点差瞬间扩大是常态,指标只帮你看见,不替你决策,实盘前务必在模拟盘验过逻辑。

MQL5 / C++
class=class="str">"cmt">//--- indicator window settings
class="macro">#class="kw">property indicator_separate_window
class=class="str">"cmt">//--- indicator buffers and plots
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots  class="num">1
class=class="str">"cmt">//--- indicator type and style settings
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_HISTOGRAM
class="macro">#class="kw">property indicator_color1  clrDarkBlue, clrTomato
class="macro">#class="kw">property indicator_style1  class="num">0
class="macro">#class="kw">property indicator_width1  class="num">1
class="macro">#class="kw">property indicator_minimum class="num">0.0
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double spreadDataBuffer[];
class="type">class="kw">double histoColorsBuffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User custom function for custom indicator initialization          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GetInit(){
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- set and register the indicator buffers mapping
   SetIndexBuffer(class="num">0, spreadDataBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">1, histoColorsBuffer, INDICATOR_COLOR_INDEX);
把内置指标改造成私有权视角
这些诊断与改造思路小布盯盘已内置,打开对应品种页即可看到由AIGC生成的指标叠加说明,你只需专注判断信号是否契合自己的交易偏好。

常见问题

技术指标是MT5预加载且源码不可见的默认工具,只能通过预定义函数调用;自定义指标则基于MQL5代码编写,可修改逻辑并直接绘制专属可视化形式。
指标本质是对历史与实时价格做数学计算并输出视觉提示,不具备交易执行权限,订单动作需由EA或手动完成。
目前小布盯盘侧重于品种页的信号解读与指标叠加说明,不输出完整MQL5源码,但可辅助你梳理改造内置指标的逻辑框架。
只要通过MQL5代码调用标准函数并加入自有绘制或计算逻辑,生成新的可视化工具,就属于自定义指标范畴。
趋势类多用折线或叠加层呈现方向,动量类常以直方图或振荡器窗口显示超买超卖状态,两者可叠加使用交叉验证。