自定义指标(第一部份):在MQL5中逐步开发简单自定义指标的入门指南·进阶篇
(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 同步前移,可直观比对敏感度。
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) 的那个删掉,否则指标不画值。
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 能在小周期更快出信号,但外汇与贵金属杠杆高,信号失效概率随周期缩小而上升。
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,让下一帧接着增量更新。外汇与贵金属杠杆高,指标仅描述历史加权关系,反转概率不保证。
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 策略测试器用历史数据验证权重参数。
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 周期参数对比噪音消减效果。外汇贵金属点差瞬间扩大是常态,指标只帮你看见,不替你决策,实盘前务必在模拟盘验过逻辑。
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);