MQL5 简介(第 12 部分):构建自定义指标的初学者指南·综合运用
(3/3)·不靠 iMA 也能画出均线,并用缓冲区与 OnCalculate 把线变烛形
很多交易者以为没有 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),避免前面几根数据不足时乱画。外汇与贵金属波动剧烈,这类自定义均线仅作概率参考,实盘前务必在策略测试器跑足够样本。
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 小时图上的平滑重绘倾向更明显,但外汇与贵金属杠杆高,信号仅作概率参考。
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 小时图,你能直观看到小周期毛刺被抹平、颜色块切换频率下降约七成,适合用来辅助判别横盘与突破倾向。
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;真要常驻图表,得自己补上那道判断。