自定义指标(第一部份):在MQL5中逐步开发简单自定义指标的入门指南·综合运用
📊

自定义指标(第一部份):在MQL5中逐步开发简单自定义指标的入门指南·综合运用

(3/3)·把前两轮的概念与代码拼成完整指标,避开「能编译却看不懂图」的常见坑

偏理论进阶 第 3/3 篇

很多交易者以为写完 OnInit 和 OnCalculate 就拥有了自定义指标,结果图表上要么空白要么重绘错位。问题往往不在语法,而在没理清指标缓冲区与价格序列的映射关系。这一篇把前面拆开的逻辑重新拼回整体,让你拿到能直接用的完整思路。

◍ 点差直方图的指标初始化与计算骨架

在 MT5 里写一个 spread histogram 指标,第一步是先给数据窗口和子窗口打标:用 IndicatorSetString(INDICATOR_SHORTNAME,"Spread Histogram") 把短名钉死,再用 IndicatorSetInteger(INDICATOR_DIGITS,0) 强制精度为 0 位——点差本身就是整数 tick,留小数没意义。 真正算数的是 GetSpreadData 这个函数。它把外部传入的 spreadData[] 逐根搬进 spreadDataBuffer,并且拿当前点差和前一根比:currentSpread > previousSpread 时给 histoColorsBuffer 写 1.0(后面绑 clrTomato 画红柱),否则写 0.0(绑 clrDarkBlue 画深蓝柱)。这样肉眼一眼能看出点差是扩张还是收缩。 OnCalculate 是 MT5 指标的主循环入口。开头有个硬判断:rates_total < 2 直接 return(0),因为 x-1 至少要有一根前值才能比,少于两根数据跑循环会越界。position 变量用 prev_calculated - 1 来定位,避免每 tick 都把全历史重算一遍,只在有新柱时从上一根续算。 外汇和贵金属点差受流动性影响波动剧烈,属于高风险品种;这套着色逻辑只反映点差相对变化,不预示任何价格方向。

MQL5 / C++
class=class="str">"cmt">//--- name for mt5 datawindow and the indicator subwindow label
  IndicatorSetString(INDICATOR_SHORTNAME,"Spread Histogram");
class=class="str">"cmt">//--- set the indicators accuracy digits
  IndicatorSetInteger(INDICATOR_DIGITS, class="num">0);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom function for calculating the spread                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void GetSpreadData(const class="type">int position, const class="type">int rates_total, const class="type">int& spreadData[])
  {
  spreadDataBuffer[class="num">0] = (class="type">class="kw">double)spreadData[class="num">0];
  histoColorsBuffer[class="num">0] = class="num">0.0;
class=class="str">"cmt">//---
   for(class="type">int x = position; x < rates_total && !IsStopped(); x++)
     {
      class="type">class="kw">double currentSpread = (class="type">class="kw">double)spreadData[x];
      class="type">class="kw">double previousSpread = (class="type">class="kw">double)spreadData[x - class="num">1];
      class=class="str">"cmt">//--- calculate and save the spread
      spreadDataBuffer[x] = currentSpread;
      if(currentSpread > previousSpread)
        {
         histoColorsBuffer[x] = class="num">1.0; class=class="str">"cmt">//-- set the histogram to clrTomato
        }
      else
        {
         histoColorsBuffer[x] = class="num">0.0; class=class="str">"cmt">//-- set the histogram to clrDarkBlue
        }
     }
class=class="str">"cmt">//---
  }
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">//--- check if we have enough data start calculating
   if(rates_total < class="num">2) class=class="str">"cmt">//--- don&class="macro">#x27;t do any calculations, exit and reload function
      class="kw">return(class="num">0);
class=class="str">"cmt">//--- we have new data, starting the calculations
   class="type">int position = prev_calculated - class="num">1;
class=class="str">"cmt">//--- update the position variable
   if(position < class="num">1)
     {

「点差直方图指标的初始化与迭代骨架」

在 MT5 自定义指标里,把点差画成独立窗口的彩色直方图,先得把窗口和缓冲区的属性钉死。下面这段预编译指令把指标丢进副图(indicator_separate_window),开 2 个 buffer、挂 1 个 plot,绘图类型选 DRAW_COLOR_HISTOGRAM,颜色给深蓝和番茄红两色,线宽 1、下限 0.0——这意味着点差非负,且颜色随缓冲值切换。 初始化函数 OnInit 只做一件事:调 GetInit() 完成缓冲绑定,返回 INIT_SUCCEEDED。真正的计算在 OnCalculate 里跑,它吃进包括 spread[] 在内的 11 个数组参数,MT5 每次来新柱或重算都会推这批数据进来。 迭代开头有个硬门槛:rates_total < 2 直接 return(0),不算,等数据凑够两根柱。否则用 position = prev_calculated - 1 接着上轮进度算;若 position 掉到 1 以下,把 spreadDataBuffer[0] 清零并把 position 重置为 1,避免首根乱跳。之后丢给 GetSpreadData(position, rates_total, spread) 填数据,最后 return(rates_total) 把已算根数交还终端。 开 MT5 新建指标把这段属性抄进去,副图就能看到点差柱;外汇和贵金属点差受流动性影响波动大,属高风险品种,柱体突然拉宽可能预示滑点概率上升,仅作观察不替代执行信号。

MQL5 / C++
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">1
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="type">class="kw">double spreadDataBuffer[];
class="type">class="kw">double histoColorsBuffer[];
class="type">int OnInit()
  {
  GetInit();
  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[])
  {
  if(rates_total < class="num">2)
    class="kw">return(class="num">0);
  class="type">int position = prev_calculated - class="num">1;
  if(position < class="num">1)
    {
    spreadDataBuffer[class="num">0] = class="num">0;
    position = class="num">1;
    }
  GetSpreadData(position, rates_total, spread);
  class="kw">return(rates_total);
  }

点差直方图与平滑蜡烛的缓冲绑定

这段自定义函数把点差数据塞进独立子窗口的直方图里,并用颜色标记点差扩张。GetSpreadData 里对每一根 K 线比较 currentSpread 与上一根 previousSpread,扩大就给 histoColorsBuffer 写 1.0(对应 clrTomato),否则写 0.0(对应 clrDarkBlue),MT5 上红柱冒头往往意味着流动性瞬时收紧,外汇和贵金属在高波动时段这种红柱密度会明显上升,属高风险信号。 GetInit 用 SetIndexBuffer 把 spreadDataBuffer 映射为 INDICATOR_DATA、histoColorsBuffer 映射为 INDICATOR_COLOR_INDEX,短名设为 Spread Histogram、精度 0 位。预处理指令里 indicator_separate_window 让指标脱离主图,indicator_buffers 6 与 indicator_plots 2 说明除点差外还预留了平滑蜡烛(DRAW_COLOR_CANDLES)和金色平滑线(DRAW_LINE)的绘图位。 直接把下面代码贴进 MT5 自定义指标,编译后拖到 XAUUSD 的 M15 图表,观察美盘开盘前后红柱出现频率,可以验证点差跳变与价格毛刺的同步性。

MQL5 / C++
class="type">void GetSpreadData(const class="type">int position, const class="type">int rates_total, const class="type">int& spreadData[])
  {
  spreadDataBuffer[class="num">0] = (class="type">class="kw">double)spreadData[class="num">0];
  histoColorsBuffer[class="num">0] = class="num">0.0;
class=class="str">"cmt">//---
  for(class="type">int x = position; x < rates_total && !IsStopped(); x++)
    {
    class="type">class="kw">double currentSpread = (class="type">class="kw">double)spreadData[x];
    class="type">class="kw">double previousSpread = (class="type">class="kw">double)spreadData[x - class="num">1];
    class=class="str">"cmt">//--- calculate and save the spread
    spreadDataBuffer[x] = currentSpread;
    if(currentSpread > previousSpread)
      {
      histoColorsBuffer[x] = class="num">1.0; class=class="str">"cmt">//-- set the histogram to clrTomato
      }
    else
      {
      histoColorsBuffer[x] = class="num">0.0; class=class="str">"cmt">//-- set the histogram to clrDarkBlue
      }
    }
class=class="str">"cmt">//---
  }

class="type">void GetInit()
  {
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);
class=class="str">"cmt">//--- name for mt5 datawindow and the indicator subwindow label
  IndicatorSetString(INDICATOR_SHORTNAME,"Spread Histogram");
class=class="str">"cmt">//--- set the indicators accuracy digits
  IndicatorSetInteger(INDICATOR_DIGITS, class="num">0);
  }

class="macro">#class="kw">property indicator_separate_window
class=class="str">"cmt">//class="macro">#class="kw">property indicator_chart_window
class=class="str">"cmt">//--- indicator buffers
class="macro">#class="kw">property indicator_buffers class="num">6
class=class="str">"cmt">//--- indicator plots
class="macro">#class="kw">property indicator_plots  class="num">2
class=class="str">"cmt">//--- plots1 details for the smoothed candles
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_CANDLES
class="macro">#class="kw">property indicator_color1  clrDodgerBlue, clrTomato, clrDarkGray
class="macro">#class="kw">property indicator_label1  "Smoothed Candle Open;Smoothed Candle High;Smoothed Candle Low;Smoothed Candle Close;"
class=class="str">"cmt">//--- plots2 details for the smoothing line
class="macro">#class="kw">property indicator_label2  "Smoothing 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

◍ 平滑K线指标的缓冲区与均线句柄初始化

在 MT5 自定义指标里,先把 OHLC 四价和颜色索引各绑一个缓冲区,再单独留一条给平滑均线,才不会在绘图时串数据。下面这段初始化把 0~4 号 buffer 映射成 K 线数据,5 号放 iMA 计算结果,并顺手用 _Digits 对齐品种报价精度。 均线句柄走 iMA(_Symbol, PERIOD_CURRENT, _maPeriod, 0, MODE_SMMA, _maAppliedPrice),默认周期 50、收盘价、平滑移动平均(SMMA)。若返回 INVALID_HANDLE 就打印错误码并终止,实盘里欧元兑美元这种小数点后 5 位的品种,INDICATOR_DIGITS 不设对就会显示错位。 短名称用 StringFormat 拼成 "SmoothedCandles(品种, Period 50, PRICE_CLOSE)",方便在副图一眼区分参数。开 MT5 把 _maPeriod 改成 20 或 100,重载指标能看到平滑 K 线密度明显变化,外汇和贵金属波动大,参数乱调可能放大假信号,属高风险操作。

MQL5 / C++
class=class="str">"cmt">//--- user class="kw">input parameters for the moving averages
class="kw">input class="type">int                _maPeriod = class="num">50;                    class=class="str">"cmt">// Period
class="kw">input ENUM_APPLIED_PRICE _maAppliedPrice = PRICE_CLOSE;     class=class="str">"cmt">// Applied Price
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double openBuffer[];
class="type">class="kw">double highBuffer[];
class="type">class="kw">double lowBuffer[];
class="type">class="kw">double closeBuffer[];
class="type">class="kw">double candleColorBuffer[];
class=class="str">"cmt">//Moving average dynamic array(buffer) and variables
class="type">class="kw">double iMA_Buffer[];
class="type">int maHandle; class=class="str">"cmt">//stores the handle of the iMA indicator
class=class="str">"cmt">//--- integer to store the number of values in the moving average indicator
class="type">int barsCalculated = class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User custom function for custom indicator initialization          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool GetInit()
  {
class=class="str">"cmt">//--- set the indicator buffer mapping by assigning the indicator buffer array
   SetIndexBuffer(class="num">0, openBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">1, highBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">2, lowBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">3, closeBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">4, candleColorBuffer, INDICATOR_COLOR_INDEX);
class=class="str">"cmt">//--- buffer for iMA
   SetIndexBuffer(class="num">5, iMA_Buffer, INDICATOR_DATA);
class=class="str">"cmt">//--- set the price display precision to digits similar to the symbol prices
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
class=class="str">"cmt">//--- set the symbol, timeframe, period and smoothing applied price of the indicator as the class="type">short name
   class="type">class="kw">string indicatorShortName = StringFormat("SmoothedCandles(%s, Period %d, %s)", _Symbol,
                                        _maPeriod, EnumToString(_maAppliedPrice));
   IndicatorSetString(INDICATOR_SHORTNAME, indicatorShortName);
class=class="str">"cmt">//IndicatorSetString(INDICATOR_SHORTNAME, "Smoothed Candlesticks");
class=class="str">"cmt">//--- set line drawing to an empty value
   PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, class="num">0.0);
class=class="str">"cmt">//--- create the maHandle of the smoothing indicator
   maHandle = iMA(_Symbol, PERIOD_CURRENT, _maPeriod, class="num">0, MODE_SMMA, _maAppliedPrice);
class=class="str">"cmt">//--- check if the maHandle is created or it failed
   if(maHandle == INVALID_HANDLE)
     {
      class=class="str">"cmt">//--- creating the handle failed, output the error code
      ResetLastError();
      PrintFormat("Failed to create maHandle of the iMA for symbol %s, error code %d",
                   _Symbol, GetLastError());
      class=class="str">"cmt">//--- we terminate the program and exit the init function
      class="kw">return(false);
     }
   class="kw">return(true); class=class="str">"cmt">// class="kw">return true, initialization of the indicator ok
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+

「初始化与首帧数据搬运的坑」

自定义指标在 MT5 里跑起来,第一道关是 OnInit。里面只做一件事:调 GetInit() 拿句柄和缓冲,失败就直接 return(INIT_FAILED),终端会终止加载,不会让你带病运行。 OnCalculate 的签名里塞了 11 个参数,从 rates_total 到 spread[] 全量传入。实际写缓冲逻辑时,先用 BarsCalculated(maHandle) 问一句均线句柄算了多少根,返回 ≤0 就 PrintFormat 报错并 return(0),这一步能拦掉九成以上的『指标空白』投诉。 首帧处理最易写错:prev_calculated==0 时,把 low[0]、high[0]、open[0]、close[0] 直接塞进各自 buffer,start 从 1 起;否则 start 取 prev_calculated-1,避免重算整列。iMA_valuesToCopy 在首帧按 iMA_calculated 与 rates_total 较小值走,后续帧则用 (rates_total-prev_calculated)+1。 别把正态当圣经 上面这段只在『历史数据连续』时成立。若你加载的是外汇或贵金属 XAUUSD 这类高跳空品种,服务器补历史可能让 BarsCalculated 突然大于 rates_total,首帧分支里的 iMA_valuesToCopy=rates_total 就是防这个的,但复盘时仍建议在 EURUSD 与 XAUUSD 各跑一次验证缓冲对齐。

MQL5 / C++
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
   if(!GetInit())
     {
       class="kw">return(INIT_FAILED); class=class="str">"cmt">//-- if initialization failed terminate the app
     }
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">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">//--- declare a class="type">int to save the number of values copied from the iMA indicator
   class="type">int iMA_valuesToCopy;
class=class="str">"cmt">//--- find the number of values already calculated in the indicator
   class="type">int iMA_calculated = BarsCalculated(maHandle);
   if(iMA_calculated <= class="num">0)
     {
       PrintFormat("BarsCalculated() for iMA handle returned %d, error code %d", iMA_calculated, GetLastError());
       class="kw">return(class="num">0);
     }
   class="type">int start;
class=class="str">"cmt">//--- check if it&class="macro">#x27;s the indicators first call of OnCalculate() or we have some new uncalculated data
   if(prev_calculated == class="num">0)
     {
       class=class="str">"cmt">//--- set all the buffers to the first index
       lowBuffer[class="num">0] = low[class="num">0];
       highBuffer[class="num">0] = high[class="num">0];
       openBuffer[class="num">0] = open[class="num">0];
       closeBuffer[class="num">0] = close[class="num">0];
       start = class="num">1;
       if(iMA_calculated > rates_total)
         iMA_valuesToCopy = rates_total;
       else   class=class="str">"cmt">//--- copy the calculated bars which are less than the indicator buffers data
         iMA_valuesToCopy = iMA_calculated;
     }
   else
     start = prev_calculated - class="num">1;
   iMA_valuesToCopy = (rates_total - prev_calculated) + class="num">1;
class=class="str">"cmt">//--- fill the iMA_Buffer array with values of the Moving Average indicator
class=class="str">"cmt">//--- reset error code
   ResetLastError();

用均线位置给蜡烛定性

这段逻辑把均线和整根蜡烛的四个价格做全位比较,用来判断当前是多头、空头还是无趋势。若开盘、收盘、最高、最低全部站在 iMA_Buffer[x] 之上,就把 candleColorBuffer 标为 0.0(蓝,偏多);全部在下则标 1.0(红,偏空);否则默认 2.0(灰,无明确倾向)。外汇与贵金属波动剧烈,这种判定只反映价格相对均线的位置,不代表后续必然延续。 CopyBuffer 失败时直接 return(0),MT5 会认为本轮计算未生效,避免脏数据写入缓冲区。循环里先把 open/close/high/low 存入局部 double 变量,再写进各自的 Buffer,能减少数组下标重复寻址,在 rates_total 较大时略降开销。 指标被放在独立窗口(indicator_separate_window),共声明 6 个 buffer、2 个 plot:第一个用 DRAW_COLOR_CANDLES 画三色蜡烛,第二个画平滑线。开 MT5 把这段接进自定义指标,调 iMA_Buffer 的周期参数,能看到灰色蜡烛占比随均线周期拉长而上升。

MQL5 / C++
class=class="str">"cmt">//--- copy a part of iMA_Buffer array with data in the zero index of the the indicator buffer
  if(CopyBuffer(maHandle, class="num">0, class="num">0, iMA_valuesToCopy, iMA_Buffer) < class="num">0)
    {
      class=class="str">"cmt">//--- if the copying fails, print the error code
      PrintFormat("Failed to copy data from the iMA indicator, error code %d", GetLastError());
      class=class="str">"cmt">//--- exit the function with zero result to specify that the indicator calculations were not executed
      class="kw">return(class="num">0);
    }
class=class="str">"cmt">//--- iterate through the main calculations loop and execute all the calculations
  for(class="type">int x = start; x < rates_total && !IsStopped(); x++)
    {
      class=class="str">"cmt">//--- save all the candle array prices in new non-array variables for quick access
      class="type">class="kw">double candleOpen = open[x];
      class="type">class="kw">double candleClose = close[x];
      class="type">class="kw">double candleHigh = high[x];
      class="type">class="kw">double candleLow  = low[x];
      lowBuffer[x] = candleLow;
      highBuffer[x] = candleHigh;
      openBuffer[x] = candleOpen;
      closeBuffer[x] = candleClose;
      class=class="str">"cmt">//--- scan for the different trends signals and set the required candle class="type">color
      candleColorBuffer[x] = class="num">2.0; class=class="str">"cmt">// set class="type">color clrDarkGray - class="kw">default (signal for no established trend)
      if(candleOpen > iMA_Buffer[x] && candleClose > iMA_Buffer[x] && candleHigh > iMA_Buffer[x] && candleLow > iMA_Buffer[x])
        candleColorBuffer[x]=class="num">0.0; class=class="str">"cmt">// set class="type">color clrDodgerBlue - signal for a class="type">long/buy trend
      if(candleOpen < iMA_Buffer[x] && candleClose < iMA_Buffer[x] && candleHigh < iMA_Buffer[x] && candleLow < iMA_Buffer[x])
        candleColorBuffer[x]=class="num">1.0; class=class="str">"cmt">// set class="type">color clrTomato - signal for a class="type">short/sell trend
    }
class=class="str">"cmt">//--- class="kw">return the rates_total which includes the prev_calculated value for the 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)
  {
   if(maHandle != INVALID_HANDLE)
     {
      IndicatorRelease(maHandle);class=class="str">"cmt">//-- clean up and release the iMA handle
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- specify where to display the indicator
class="macro">#class="kw">property indicator_separate_window
class=class="str">"cmt">//class="macro">#class="kw">property indicator_chart_window
class=class="str">"cmt">//--- indicator buffers
class="macro">#class="kw">property indicator_buffers class="num">6
class=class="str">"cmt">//--- indicator plots
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- plots1 details for the smoothed candles
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_CANDLES
class="macro">#class="kw">property indicator_color1  clrDodgerBlue, clrTomato, clrDarkGray
class="macro">#class="kw">property indicator_label1  "Smoothed Candle Open;Smoothed Candle High;Smoothed Candle Low;Smoothed Candle Close;"
class=class="str">"cmt">//--- plots2 details for the smoothing line
class="macro">#class="kw">property indicator_label2  "Smoothing Line"

◍ 把K线与均线塞进同一指标窗口

想在MT5里把裸K和一条50周期均线叠在同一副图,而不是各开一个窗口,核心是先声明第二绘图层、再挂iMA句柄。下面这段初始化与计算骨架,直接决定了后面能否用close数组对齐均线值。 第二绘图层用indicator_type2设成DRAW_LINE,颜色clrGoldenrod、线宽2,这就是你肉眼看到的金色均线条。输入参数只留_maPeriod=50和_price_close,足够覆盖大多数外汇与贵金属品种的中速趋势观察。 OnInit里只做一件事:调GetInit(),失败就INIT_FAILED退出,成功才放行。OnCalculate拿到time/open/high/low/close等引用数组后,先用BarsCalculated(maHandle)探iMA已算多少根;若返回≤0就PrintFormat报错,避免空句柄把整轮计算带崩。 外汇与贵金属杠杆高、跳空频繁,50周期均线在重大数据夜可能瞬间被穿透,仅作概率参考而非方向保证。开MT5把这段贴进自定义指标,改_maPeriod到20或100,能立刻对比不同品种的反应节奏。

MQL5 / C++
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">//--- user class="kw">input parameters for the moving averages
class="kw">input class="type">int                _maPeriod = class="num">50;                     class=class="str">"cmt">// Period
class="kw">input ENUM_APPLIED_PRICE _maAppliedPrice = PRICE_CLOSE;      class=class="str">"cmt">// Applied Price
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double openBuffer[];
class="type">class="kw">double highBuffer[];
class="type">class="kw">double lowBuffer[];
class="type">class="kw">double closeBuffer[];
class="type">class="kw">double candleColorBuffer[];
class=class="str">"cmt">//Moving average dynamic array(buffer) and variables
class="type">class="kw">double iMA_Buffer[];
class="type">int maHandle; class=class="str">"cmt">//stores the handle of the iMA indicator
class=class="str">"cmt">//--- integer to store the number of values in the moving average indicator
class="type">int barsCalculated = class="num">0;
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
   if(!GetInit())
     {
       class="kw">return(INIT_FAILED); class=class="str">"cmt">//-- if initialization failed terminate the app
     }
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">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">//--- declare a class="type">int to save the number of values copied from the iMA indicator
   class="type">int iMA_valuesToCopy;
class=class="str">"cmt">//--- find the number of values already calculated in the indicator
   class="type">int iMA_calculated = BarsCalculated(maHandle);
   if(iMA_calculated <= class="num">0)
     {
       PrintFormat("BarsCalculated() for iMA handle returned %d, error code %d", iMA_calculated, GetLastError());

「OnCalculate 里如何把均线信号染到蜡烛上」

这段逻辑跑在自定义指标的 OnCalculate 里,核心是把一根移动平均线的位置跟每根 K 线的开高低收做全维度比较,再给蜡烛涂上代表趋势倾向的颜色。外汇与贵金属市场波动剧烈、杠杆风险高,下面只是机制说明,不构成方向建议。 首次调用时 prev_calculated 为 0,代码把第 0 根缓冲写成立即行情,start 置 1;若已有历史计算量 iMA_calculated 大于总柱数 rates_total,则只拷 rates_total 根,否则拷 iMA_calculated 根。后续调用走 else 分支,start 取 prev_calculated-1,避免重复算已完成的柱。 CopyBuffer 从均线句柄 maHandle 拷 iMA_valuesToCopy 根到 iMA_Buffer,失败就 PrintFormat 报错并返回 0,这一道防护能保证指标不会在数据源异常时瞎画。 主循环从 start 扫到 rates_total,把开高低收存进局部 double 变量加快访问,并默认 candleColorBuffer 写 2.0(深灰,无趋势)。当整根 K 线四个价都高于均线缓冲,写 0.0(蓝,偏多倾向);都低于则写 1.0(红,偏空倾向)。这种「四价同侧」判定比只看收盘价更不容易被影线噪音骗。 最后 return rates_total,把已算柱数交还系统,下一帧只补算新增部分。你在 MT5 里挂上这类指标,切换周期时会明显看到:只有边界几根重算,历史着色不动——这就是 prev_calculated 机制在省 CPU。

MQL5 / C++
   class="kw">return(class="num">0);
   }
   class="type">int start;
class=class="str">"cmt">//--- check if it&class="macro">#x27;s the indicators first call of OnCalculate() or we have some new uncalculated data
   if(prev_calculated == class="num">0)
   {
   class=class="str">"cmt">//--- set all the buffers to the first index
      lowBuffer[class="num">0] = low[class="num">0];
      highBuffer[class="num">0] = high[class="num">0];
      openBuffer[class="num">0] = open[class="num">0];
      closeBuffer[class="num">0] = close[class="num">0];
      start = class="num">1;
      if(iMA_calculated > rates_total)
         iMA_valuesToCopy = rates_total;
      else   class=class="str">"cmt">//--- copy the calculated bars which are less than the indicator buffers data
         iMA_valuesToCopy = iMA_calculated;
   }
   else
      start = prev_calculated - class="num">1;
   iMA_valuesToCopy = (rates_total - prev_calculated) + class="num">1;
class=class="str">"cmt">//--- fill the iMA_Buffer array with values of the Moving Average indicator
class=class="str">"cmt">//--- reset error code
   ResetLastError();
class=class="str">"cmt">//--- copy a part of iMA_Buffer array with data in the zero index of the the indicator buffer
   if(CopyBuffer(maHandle, class="num">0, class="num">0, iMA_valuesToCopy, iMA_Buffer) < class="num">0)
   {
   class=class="str">"cmt">//--- if the copying fails, print the error code
      PrintFormat("Failed to copy data from the iMA indicator, error code %d", GetLastError());
   class=class="str">"cmt">//--- exit the function with zero result to specify that the indicator calculations were not executed
      class="kw">return(class="num">0);
   }
class=class="str">"cmt">//--- iterate through the main calculations loop and execute all the calculations
   for(class="type">int x = start; x < rates_total && !IsStopped(); x++)
   {
   class=class="str">"cmt">//--- save all the candle array prices in new non-array variables for quick access
      class="type">class="kw">double candleOpen = open[x];
      class="type">class="kw">double candleClose = close[x];
      class="type">class="kw">double candleHigh = high[x];
      class="type">class="kw">double candleLow  = low[x];
      lowBuffer[x] = candleLow;
      highBuffer[x] = candleHigh;
      openBuffer[x] = candleOpen;
      closeBuffer[x] = candleClose;
   class=class="str">"cmt">//--- scan for the different trends signals and set the required candle class="type">color
      candleColorBuffer[x] = class="num">2.0; class=class="str">"cmt">// set class="type">color clrDarkGray - class="kw">default (signal for no established trend)
      if(candleOpen > iMA_Buffer[x] && candleClose > iMA_Buffer[x] && candleHigh > iMA_Buffer[x] && candleLow > iMA_Buffer[x])
         candleColorBuffer[x]=class="num">0.0; class=class="str">"cmt">// set class="type">color clrDodgerBlue - signal for a class="type">long/buy trend
      if(candleOpen < iMA_Buffer[x] && candleClose < iMA_Buffer[x] && candleHigh < iMA_Buffer[x] && candleLow < iMA_Buffer[x])
         candleColorBuffer[x]=class="num">1.0; class=class="str">"cmt">// set class="type">color clrTomato - signal for a class="type">short/sell trend
   }
class=class="str">"cmt">//--- class="kw">return the rates_total which includes the prev_calculated value for the next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Indicator deinitialization function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+

指标句柄的回收与缓冲绑定

自定义指标退出时若不释放 iMA 句柄,MT5 会在反复加载/卸载后堆积无效句柄。OnDeinit 里用 IndicatorRelease(maHandle) 做清理,前提是句柄不等于 INVALID_HANDLE,否则对空句柄释放会触发日志报错。 GetInit 负责把 6 个缓冲区映射到指标:0~3 号存 OHLC 数据,4 号存蜡烛颜色索引,5 号存平滑均线值。IndicatorSetInteger(INDICATOR_DIGITS, _Digits) 让输出精度跟随当前品种小数位,避免黄金报价格和 EURUSD 混用同一位数。 maHandle = iMA(_Symbol, PERIOD_CURRENT, _maPeriod, 0, MODE_SMMA, _maAppliedPrice) 创建的是平滑移动平均(SMMA)。若返回 INVALID_HANDLE,ResetLastError 清错后用 PrintFormat 打出错误码并 return(false),初始化即中止。外汇与贵金属杠杆高,指标加载失败可能让你漏看信号,上 MT5 跑这段代码时先确认 _maPeriod 已赋值。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
  {
   if(maHandle != INVALID_HANDLE)
     {
      IndicatorRelease(maHandle);class=class="str">"cmt">//-- clean up and release the iMA handle
     }
  }
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">bool GetInit()
  {
class=class="str">"cmt">//--- set the indicator buffer mapping by assigning the indicator buffer array
   SetIndexBuffer(class="num">0, openBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">1, highBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">2, lowBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">3, closeBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">4, candleColorBuffer, INDICATOR_COLOR_INDEX);
class=class="str">"cmt">//--- buffer for iMA
   SetIndexBuffer(class="num">5, iMA_Buffer, INDICATOR_DATA);
class=class="str">"cmt">//--- set the price display precision to digits similar to the symbol prices
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
class=class="str">"cmt">//--- set the symbol, timeframe, period and smoothing applied price of the indicator as the class="type">short name
   class="type">class="kw">string indicatorShortName = StringFormat("SmoothedCandles(%s, Period %d, %s)", _Symbol,
                                            _maPeriod, EnumToString(_maAppliedPrice));
   IndicatorSetString(INDICATOR_SHORTNAME, indicatorShortName);
class=class="str">"cmt">//IndicatorSetString(INDICATOR_SHORTNAME, "Smoothed Candlesticks");
class=class="str">"cmt">//--- set line drawing to an empty value
   PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, class="num">0.0);
class=class="str">"cmt">//--- create the maHandle of the smoothing indicator
   maHandle = iMA(_Symbol, PERIOD_CURRENT, _maPeriod, class="num">0, MODE_SMMA, _maAppliedPrice);
class=class="str">"cmt">//--- check if the maHandle is created or it failed
   if(maHandle == INVALID_HANDLE)
     {
      class=class="str">"cmt">//--- creating the handle failed, output the error code
      ResetLastError();
      PrintFormat("Failed to create maHandle of the iMA for symbol %s, error code %d",
                  _Symbol, GetLastError());
      class=class="str">"cmt">//--- we terminate the program and exit the init function
      class="kw">return(false);
     }
   class="kw">return(true); class=class="str">"cmt">// class="kw">return true, initialization of the indicator ok
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 能写简单指标就是起点

走完这一程,你已经碰过三类自定义指标的骨架:线性均线直方图、点差监控、平滑K线,配套 mq5 文件分别是 5.31 KB、4.4 KB、8.46 KB,直接丢进 MT5 的 MQL5/Indicators 目录就能编译跑起来。 MQL5 写指标不是一遍文章能填平的深坑,但此刻你至少具备把一条计算逻辑落成可加载指标的能力。下一步建议挑一个你盯盘时手算过的数据,比如真实波幅或持仓长短,照着 SpreadMonitor 的结构改几行,比看十篇教程都实在。 外汇和贵金属杠杆高、滑点狠,自己写的指标只帮你看清概率,不替你管风险。写完编译通过的那一刻,真正的练习才刚开始。

把重复编译交给小布盯盘
这些指标结构诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到缓冲区与重绘风险提示,你专注策略逻辑而非反复改代码。

常见问题

自定义指标侧重可视化与信号输出,不直接下单;EA通过iCustom或标准函数读取指标缓冲区值来触发交易,二者职责分离。
大概率是没有在OnCalculate里按rates_total和prev_calculated正确处理索引偏移,导致缓冲区写入错位,需显式管理起始位。
可以,小布盯盘内置的AIGC会扫描指标结构并标记可能重绘的缓冲区写法,打开品种页即可看到对应提示。
逻辑上可用,但贵金属跳空与高点差会放大视觉滞后,信号仅作概率参考,实盘前建议用历史分周期验证。
自定义指标无法修改订单,也不能访问其他图表品种数据除非显式用CopyBuffer跨品种读取,完整跨篇讨论见《自定义指标入门指南·基础篇》。