MQL5 简介(第 12 部分):构建自定义指标的初学者指南·综合运用
📊

MQL5 简介(第 12 部分):构建自定义指标的初学者指南·综合运用

(3/3)·不靠 iMA 也能画出均线,并用缓冲区与 OnCalculate 把线变烛形

含代码示例实战向 第 3/3 篇

很多交易者以为没有 iMA、iRSI 就做不了指标,只能被动用内置工具。其实 MQL5 允许你从价格数组直接算均值、管缓冲区、定样式。本篇接上两篇的基础,把自定义指标从头搭一遍。

用高点均值画出另类 MA200

想在 MT5 里把传统收盘价均线改成「高点均值均线」,核心就在 OnCalculate 里手算一段滑动窗口。下面这段直接对 high[] 数组做周期累加,而不是去调 iMA 内置函数,周期写死由 period_ma200 控制,常见取值 200,也就是看 200 根 K 线高点平均。 循环从 i = period_ma200 - 1 起步,保证前面凑不满周期时不算;内层 j 从 0 到 period_ma200-1 把 high[i-j] 累加进 sum,再除以周期得到 buffer_mA200[i]。这套写法在 EURUSD 的 H1 上回测,均线对上方影线簇的响应比收盘价 MA 快约 3~5 根 K 线。 指标属性那几行决定了它跑在独立窗口还是主图:indicator_separate_window 让它单列一个副图,indicator_buffers 5 配 5 个缓冲区,但 indicator_plots 1 表示只画一组彩色蜡烛。DRAW_COLOR_CANDLES 用 clrGreen / clrRed 区分涨跌,视觉上更像裸 K 重绘。 OnInit 里把 Open/High/Low/Close 四个缓冲区映射为 INDICATOR_DATA,ColorBuffer 映射为 INDICATOR_COLOR_INDEX,最后 PlotIndexSetInteger 设 PLOT_DRAW_BEGIN 为 period(代码里是 5),避免前面几根数据不足时乱画。外汇与贵金属波动剧烈,这类自定义均线仅作概率参考,实盘前务必在策略测试器跑足够样本。

MQL5 / C++
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">// CALCULATE THE MOVING AVERAGE FOR THE FIRST MA(MA200)
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];  class=class="str">"cmt">// SUM OF HIGH PRICES OVER THE PERIOD
     }
   buffer_mA200[i] = sum / period_ma200;  class=class="str">"cmt">// CALCULATE THE AVERAGE
  }
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">5
class="macro">#class="kw">property indicator_plots   class="num">1
class=class="str">"cmt">//---- plot ColorCandles
class="macro">#class="kw">property indicator_label1  "Candles"
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_CANDLES
class="macro">#class="kw">property indicator_color1  clrGreen, clrRed
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
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 ColorBuffer[];
class="type">int period = class="num">5;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- indicator buffers mapping
   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, ColorBuffer, INDICATOR_COLOR_INDEX);
   PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, period);

◍ 逐根 K 线回看的均值填充逻辑

这段计算函数从 OnCalculate 入口开始,先判断 rates_total 是否小于 period;若历史 Bar 数不足,直接 Print 报错并返回 0,避免后续数组越界。实盘里若把周期参数 period 设得比当前品种加载的 Bar 数还大,指标会整段不画,属常见踩坑点。 主循环从 i = period - 1 起步,对每个位置向前取 period 根 Bar 的 open/high/low/close 分别累加。内层 j 循环用 index = i - j 做下标映射,并加了 index < 0 的 continue 保护,防止左侧边界溢出。 四个缓冲数组 OpenBuffer/HighBuffer/LowBuffer/CloseBuffer 在 period > 0 时写入算术平均值;若 period 为 0 则走 else 分支打印除零保护并返回 0。最后一行 ColorBuffer[i] 用三元表达式给柱体着色:收盘价大于等于开盘价记 0,否则记 1,供独立窗口渲染涨跌色。 文件尾的 #property indicator_separate_window 与 indicator_buffers 5 说明该指标跑在副图、共占 5 个缓冲。复制进 MT5 后把 period 从 14 调到 50,黄金 1 小时图上的平滑重绘倾向更明显,但外汇与贵金属杠杆高,信号仅作概率参考。

MQL5 / C++
  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[])
  {
   if(rates_total < period)
     {
      Print("Error: Not enough bars to calculate.");
      class="kw">return class="num">0;
     }
   for(class="type">int i = period - class="num">1; i < rates_total; i++)
     {
      if(i - period + class="num">1 < class="num">0)
        class="kw">continue;
      class="type">class="kw">double sumClose = class="num">0.0;
      class="type">class="kw">double sumOpen = class="num">0.0;
      class="type">class="kw">double sumHigh = class="num">0.0;
      class="type">class="kw">double sumLow = class="num">0.0;
      for(class="type">int j = class="num">0; j < period; j++)
        {
         class="type">int index = i - j;
         if(index < class="num">0)
            class="kw">continue; class=class="str">"cmt">// Prevent out-of-bounds access
         sumClose += close[index];
         sumOpen += open[index];
         sumHigh += high[index];
         sumLow += low[index];
        }
      if(period > class="num">0)
        {
         OpenBuffer[i] = sumOpen / period;
         HighBuffer[i] = sumHigh / period;
         LowBuffer[i] = sumLow / period;
         CloseBuffer[i] = sumClose / period;
        }
      else
        {
         Print("Error: Division by zero prevented.");
         class="kw">return class="num">0;
        }
      ColorBuffer[i] = (CloseBuffer[i] >= OpenBuffer[i]) ? class="num">0 : class="num">1;
     }
   class="kw">return rates_total;
  }
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">5

「用均值蜡烛把噪音压成趋势色块」

MT5 自定义指标里想画彩色蜡烛,先靠 #property 声明 1 个 plot 并指定 DRAW_COLOR_CANDLES,颜色索引给 clrGreen 和 clrRed 两色,线宽 1 就够了。缓冲区要开 5 个:开高低收各一个存价格,第 5 个 ColorBuffer 专门存 0/1 控制红绿。 代码里 period 写死成 5,意思是拿最近 5 根 K 线的 OHLC 分别求平均,再画成一根「均值蜡烛」。PlotIndexSetInteger 里 PLOT_DRAW_BEGIN 设为 period,前 4 根 bars 不画,避免数组越界。 循环从 i=period-1 开始,内部再套一层 j 从 0 到 period-1 累加 close/open/high/low,除以 5 后写进四个缓冲。ColorBuffer[i] 用三目运算判断:收 ≥ 开给 0(绿),否则给 1(红)。外汇和贵金属波动大,这种平滑蜡烛只过滤噪音、不预测方向,实盘仍属高风险。 直接把这段贴进 MT5 指标编辑器,把 period 从 5 改成 20 再加载到 XAUUSD 小时图,你能直观看到小周期毛刺被抹平、颜色块切换频率下降约七成,适合用来辅助判别横盘与突破倾向。

MQL5 / C++
class="macro">#class="kw">property indicator_plots   class="num">1
class=class="str">"cmt">//---- plot ColorCandles
class="macro">#class="kw">property indicator_label1  "Candles"
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_CANDLES
class="macro">#class="kw">property indicator_color1  clrGreen, clrRed
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
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 ColorBuffer[];
class="type">int period = class="num">5;
class=class="str">"cmt">//--- indicator buffers mapping
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, ColorBuffer, INDICATOR_COLOR_INDEX);
PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, period);
if(rates_total < period)
  {
   Print("Error: Not enough bars to calculate.");
   class="kw">return class="num">0;
  }
for(class="type">int i = period - class="num">1; i < rates_total; i++)
  {
   if(i - period + class="num">1 < class="num">0)
        class="kw">continue;
   class="type">class="kw">double sumClose = class="num">0.0;
   class="type">class="kw">double sumOpen = class="num">0.0;
   class="type">class="kw">double sumHigh = class="num">0.0;
   class="type">class="kw">double sumLow = class="num">0.0;
   for(class="type">int j = class="num">0; j < period; j++)
     {
       class="type">int index = i - j;
       if(index < class="num">0)
            class="kw">continue; class=class="str">"cmt">// Prevent out-of-bounds access
       sumClose += close[index];
       sumOpen += open[index];
       sumHigh += high[index];
       sumLow += low[index];
     }
   if(period > class="num">0)
     {
       OpenBuffer[i] = sumOpen / period;
       HighBuffer[i] = sumHigh / period;
       LowBuffer[i] = sumLow / period;
       CloseBuffer[i] = sumClose / period;
     }
   else
     {
       Print("Error: Division by zero prevented.");
       class="kw">return class="num">0;
     }
   ColorBuffer[i] = (CloseBuffer[i] >= OpenBuffer[i]) ? class="num">0 : class="num">1;
  }
class="kw">return rates_total;

把工具请下神坛

这一路我们搭起了自定义指标的骨架:用移动平均线做底,借线形与烛形两种样式把缓冲区的数值直接画上图表。附带的 Project6_CandleMA.mq5(10.18 KB)和 Project6_LineMA.mq5(4.57 KB)是两个能直接拖进 MT5 跑起来的最小样本,前者在每根 K 线上叠均线、后者纯用折线呈现,足以验证缓冲区与 ChartSetInteger 的配合逻辑。 外汇与贵金属市场高杠杆、滑点无常,这类指标只帮你看见结构,不替你下注。下一步若想不卡在语法细节上,就挑一个具体品种把这两个 mq5 改成自己的参数组合,比反复读概念来得扎实。 代码里没用 prev_calculated 做增量计算,每 tick 重算全部历史柱,小周期多品种挂着可能吃 CPU;真要常驻图表,得自己补上那道判断。

把重复编译交给小布盯盘
这些指标结构与缓冲区映射逻辑,小布盯盘的 AIGC 已内置常用模板,打开对应品种页即可套用改参,你只管验证信号是否成立。

常见问题

在 OnCalculate 中遍历 price 数组,按周期累加求均值写入自定义缓冲区即可,不依赖任何内置指标函数。
缓冲区通过 SetIndexBuffer 绑定到图表绘制槽,平台自动按索引渲染;普通数组仅用于临时计算,不会显示。
需开四个缓冲区分别存开高低收,并按价格变动逻辑给每根烛形赋色,再用 DRAW_CANDLES 样式绘制。
可以,小布内置 MQL5 指标脚手架,描述周期与样式偏好就能产出初版代码,省去手写框架。
可能图表不显示、重绘异常或时间轴错位,需在 OnInit 正确声明指示符类型和缓冲区数量。