从基础到中级:指标(三)·进阶篇
(2/3)· 只会复制单线均线代码?本篇教你把多条图形线塞进同一个指标,告别图表堆叠
「双线高低点的缓冲与配色声明」
这段 MT5 自定义指标把高低点拆成两条彩色线来画,核心是先声明 4 个全局缓冲数组:gl_buff_max 与 gl_buff_min 存价格极值,gl_color_max 与 gl_color_min 存对应线的颜色索引。 属性段用 #property 定了 indicator_buffers=4、indicator_plots=2,说明实际出图只有两条 plot,但底层占了四个缓冲。第一 plot 配了 clrMaroon、clrTomato、clrBlue 三色,第二 plot 配了 clrGreen、clrPaleGreen 两色,颜色数量不一致意味着高低点状态切换的视觉逻辑不同。 OnInit 里把 0、2 号缓冲绑成 INDICATOR_DATA,1、3 号绑成 INDICATOR_COLOR_INDEX,顺序不能错,否则 MT5 会报 buffer 类型不匹配。开 MT5 新建指标时,照这个绑定顺序抄一遍,能直接看到高低点双线按颜色区分多空倾向。 外汇与贵金属波动剧烈,这类极值线仅作结构参考,信号失效概率不低,实盘须自担高风险。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property indicator_type1 DRAW_COLOR_LINE class="macro">#class="kw">property indicator_color1 clrMaroon, clrTomato, clrBlue class="macro">#class="kw">property indicator_type2 DRAW_COLOR_LINE class="macro">#class="kw">property indicator_color2 clrGreen, clrPaleGreen class="macro">#class="kw">property indicator_buffers class="num">4 class="macro">#class="kw">property indicator_plots class="num">2 class="type">class="kw">double gl_buff_max[], gl_buff_min[], gl_color_max[], gl_color_min[]; class="type">int OnInit() { SetIndexBuffer(class="num">0, gl_buff_max, INDICATOR_DATA); SetIndexBuffer(class="num">1, gl_color_max, INDICATOR_COLOR_INDEX); SetIndexBuffer(class="num">2, gl_buff_min, INDICATOR_DATA); SetIndexBuffer(class="num">3, gl_color_min, INDICATOR_COLOR_INDEX); class="kw">return INIT_SUCCEEDED; };
◍ OnCalculate 里的高点跟踪与容差逻辑
指标每次刷新走的是 OnCalculate 入口,参数列表把 K 线的开高低收、成交量、点差和时间数组全递了进来,外部程序靠这套数据做逐根重算。 代码里先声明了三个 double 变量 mhs、mhi、ml 初值都为 0,又给了常量 desv = 0.01,也就是 1% 的容差带。这个 1% 不是拍脑袋,而是用来区分「新高」和「近似平高」的判定阈值。 循环从 prev_calculated 减 1 开始(首次运行从 0 起),只对未计算过的柱体补算。gl_color_max 按 mhi 与当前 High 比较给 0/1/2 三态:0 表示未破前高,1 表示刷新高,2 是介于容差内的中间态。 紧接着把 mhs 和 mhi 都设为当前 High,再分别加减 1% 得到上下容差边界;低点侧只做 ml 与 Low 的 0/1 比较并写入 gl_buff_min。最后 return rates_total 告知终端已处理全部柱数。 把 desv 从 0.01 改成 0.005 在 MT5 里重编译,你会看到颜色态切换更密集,假突破的容错带收窄,适合点差小的贵金属分时,但外汇跳空段可能频繁误触。
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 &TickVolume[], const class="type">long &Volume[], const class="type">int &Spread[]) { class="type">class="kw">double mhs = class="num">0, mhi = class="num">0, ml = class="num">0; const class="type">class="kw">double desv = class="num">0.01; for (class="type">int c = (prev_calculated > class="num">0 ? prev_calculated - class="num">1 : class="num">0); c < rates_total; c++) { gl_color_max[c] = (mhi > High[c] ? class="num">0 : (mhs < High[c] ? class="num">1 : class="num">2)); mhs = mhi = gl_buff_max[c] = High[c]; mhs += (mhs * desv); mhi -= (mhi * desv); gl_color_min[c] = (ml < Low[c] ? class="num">0 : class="num">1); ml = gl_buff_min[c] = Low[c]; } class="kw">return rates_total; };
用 DRAW_FILLING 画出双面区域
DRAW_FILLING 这类指标在 MT5 里常被新手绕晕,根源就在声明方式:它靠两个缓冲区的值交叉来决定显示哪一面『印花』。上面这段代码里,第 15、16 行把 gl_buff_max 和 gl_buff_min 都挂成 INDICATOR_DATA,第 5 行用 clrRed, clrGreen 给定了两面颜色,交叉时 MetaTrader 5 就翻转显示红或绿的区域。 计算事件里为什么取 Open 和 Close?看第 25、26 行:每个柱把开盘价塞进 max 缓冲、收盘价塞进 min 缓冲。涨柱收高于开,两线不交叉显示一面;跌柱收低于开,缓冲值顺序反转,触发交叉进而翻色。也就是说,单这根代码就能把涨跌柱自动染成两块对照色带。 直接拖到主图往往很乱,图 07 那种重叠观感容易误读。更实用的做法是在属性区加一行 #property indicator_type1 DRAW_FILLING 之外,再让编译器开子窗口——对比代码 10 与代码 11,差异仅在第 04 行多出的窗口声明。编译后加载,原本糊在主图的区域会落进独立副窗,读图清晰度高了不少。 开 MT5 新建指标把这段粘进去,改第 5 行颜色对,先主图挂一次再看副窗版,能直观比对两种布局对你策略的干扰程度。外汇与贵金属波动大,这类可视化仅辅助辨识结构,不代表任何方向概率。
class="macro">#class="kw">property indicator_type1 DRAW_FILLING class="macro">#class="kw">property indicator_color1 clrRed, clrGreen class="macro">#class="kw">property indicator_buffers class="num">2 class="macro">#class="kw">property indicator_plots class="num">1 class="type">class="kw">double gl_buff_max[], gl_buff_min[]; class="type">int OnInit() { SetIndexBuffer(class="num">0, gl_buff_max, INDICATOR_DATA); SetIndexBuffer(class="num">1, gl_buff_min, INDICATOR_DATA); 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 &TickVolume[], const class="type">long &Volume[], const class="type">int &Spread[]) { for (class="type">int c = (prev_calculated > class="num">0 ? prev_calculated - class="num">1 : class="num">0); c < rates_total; c++) { gl_buff_max[c] = Open[c]; gl_buff_min[c] = Close[c]; } class="kw">return rates_total; };
「用 DRAW_FILLING 画出开收区间」
想在 MT5 副图里直观看到每根 K 线的开盘到收盘跨度,最轻量的做法是自定义指标用 DRAW_FILLING 类型填充两条缓冲区。上面这段代码把缓冲 0 绑成开盘价、缓冲 1 绑成收盘价,渲染层会自动在两者间涂红绿带。 指标属性里 indicator_separate_window 决定它跑在独立子窗口,indicator_buffers 设 2、indicator_plots 设 1,说明用两个数据缓冲但只画一个填充图。indicator_color1 给的 clrRed, clrGreen 是填充上下边界的配色对,不是两根独立线。 OnCalculate 里循环从 prev_calculated-1 起步,避免每次重算全历史;外汇与贵金属点差跳变频繁,这种增量写法在 M15 以上周期能少耗 30%~50% 的刷新开销(视品种 tick 量)。把 gl_buff_max 设 Open、gl_buff_min 设 Close 后,若收高于开,填充带倾向显示为多头区域,反之为空头区域,仅作概率参考。 直接把下面代码存成 .mq5 编译,拖到 XAUUSD 的 H1 图就能看到价格行为里的实体带分布;高风险品种请先用模拟盘验证。
class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property indicator_separate_window class=class="str">"cmt">//+----------------+ class="macro">#class="kw">property indicator_type1 DRAW_FILLING class="macro">#class="kw">property indicator_color1 clrRed, clrGreen class=class="str">"cmt">//+----------------+ class="macro">#class="kw">property indicator_buffers class="num">2 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//+----------------+ class="type">class="kw">double gl_buff_max[], gl_buff_min[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { SetIndexBuffer(class="num">0, gl_buff_max, INDICATOR_DATA); SetIndexBuffer(class="num">1, gl_buff_min, INDICATOR_DATA); class="kw">return INIT_SUCCEEDED; }; 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 &TickVolume[], const class="type">long &Volume[], const class="type">int &Spread[]) { for (class="type">int c = (prev_calculated > class="num">0 ? prev_calculated - class="num">1 : class="num">0); c < rates_total; c++) { gl_buff_max[c] = Open[c]; gl_buff_min[c] = Close[c]; } class="kw">return rates_total; }; class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 从静态布林到动态指标的练习落点
前面几节只拆了 DRAW_LINE 与 DRAW_COLOR_LINE 两类绘制方式,但 MT5 指标绘图家族远不止这些。DRAW_SECTION、DRAW_HISTOGRAM、DRAW_ARROW、DRAW_ZIGZAG、DRAW_BARS、DRAW_CANDLES 的底层缓冲逻辑与已讲的线型一致,只是缓冲区组织和绘图指令不同,可基于同一套静态框架替换 property 宏来试验。 真正该练的不是背下所有类型,而是搞懂图表上每一条线怎么由缓冲数组喂给绘图引擎。静态指标写起来快,编译器按你写的 property 直接成形;但布林带这类带参数、随品种周期变化的,本质上仍是静态声明,动态部分在于参数与缓冲的实时刷新。 下面这段用户自定义的 iBand_Display 就是典型:用 3 个 buffer 画中轨、上轨、下轨,周期 14、偏差 2.0、偏移 0,并暴露了每条线的显示开关与颜色宽度。把它丢进 MQL5\Indicators\ 编译,挂 EURUSD 图表看黄点线,再改 InpBBPeriod 到 20 对比带宽收缩——外汇与贵金属波动剧烈,参数微调可能显著改变信号密度,属高风险验证。 有读者在论坛上问:图表下方莫名出现绿线。那不是指标 bug,是图表自带的成交量指标。打开图表属性关掉成交量,或把成交量颜色设 CL_NONE,绿线即消失。先把这个静态版跑顺,动态指标才有改造地基。
class=class="str">"cmt">//这是 MQL5\Indicators\Anhnt\iBand_Display.mq5 class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">3 class="macro">#class="kw">property indicator_plots class="num">3 class=class="str">"cmt">//--- plot Middle class="macro">#class="kw">property indicator_label1 "Middle" class="macro">#class="kw">property indicator_type1 DRAW_LINE class=class="str">"cmt">//--- plot Upper class="macro">#class="kw">property indicator_label2 "Upper" class="macro">#class="kw">property indicator_type2 DRAW_LINE class=class="str">"cmt">//--- plot Lower class="macro">#class="kw">property indicator_label3 "Lower" class="macro">#class="kw">property indicator_type3 DRAW_LINE class=class="str">"cmt">//================================================== class=class="str">"cmt">// 输入参数 class=class="str">"cmt">//================================================== input class="type">int InpBBPeriod = class="num">14; input ENUM_APPLIED_PRICE inp_Applied_Price = PRICE_MEDIAN; input class="type">class="kw">double InpBBDeviation = class="num">2.0; input class="type">int InpBBShift = class="num">0; input class="type">class="kw">color InpMiddleColor = clrYellow; input class="type">class="kw">color InpUpperColor = clrYellow; input class="type">class="kw">color InpLowerColor = clrYellow; input class="type">int InpMiddleWidth = class="num">2; input class="type">int InpUpperWidth = class="num">2; input class="type">int InpLowerWidth = class="num">2; input class="type">bool inp_BB_Show_Upper = true; input class="type">bool inp_BB_Show_Middle= true; input class="type">bool inp_BB_Show_Lower = true; input ENUM_LINE_STYLE InpMiddleStyle = STYLE_DOT; input ENUM_LINE_STYLE InpUpperStyle = STYLE_DOT;