创建具有图形控制选项的指标·进阶篇
(2/3)· 还在切换不同 MACD 模板看盘?这篇用 MQL5 向导把多类型指标合成可切换的图形控制系统
「用按钮切换 OsMA 与 MACD 绘制」
这套指标做了 5 种显示变型,本步先落地其中两项:只画 oSMA 直方图(项目3)、只画 MACD 线(项目4)。核心思路是在图表右上角放两个不可选中按钮,用按钮的 OBJPROP_STATE 去控制对应缓冲区的 DRAW_NONE 开关。 按钮被意外删除或图表重排导致订单号平移时,代码里用固定对象名 "ShowMACD" / "ShowOsMA" 重建,能自动归位,不会丢控制。两个按钮 YDISTANCE 分别设 5 和 30,XSIZE 70、YSIZE 20,字号 8,视觉上紧凑且不挡 K 线。 为支持 Elder 冲量检查,新增一个 EldersMA 数组,缓冲区总数 +1,新缓冲定义为内部计算用(非绘制)。EMA 值通过复制函数填入,索引规则和图表其他缓冲区一致——从末尾向前排。
- 色 OsMA 靠颜色索引实现:线中指定颜色索引,0 为第一色、1 为第二色,依次类推。冲量系统条件写好后并进 OsMA 绘制判断,同时加一段防按钮闪烁的条件,并删掉改按钮文本的代码,避免状态切换时界面抖动。
下面这段是按钮创建与 MACD 线受控不绘制的骨架,直接抄进 MT5 自定义指标 OnInit 就能跑:
ObjectCreate(class="num">0,"ShowMACD",OBJ_BUTTON,ChartWindowFind(),class="num">100,class="num">100); class=class="str">"cmt">//创建按钮 ObjectSetInteger(class="num">0,"ShowMACD",OBJPROP_XDISTANCE,class="num">75); class=class="str">"cmt">//分配坐标 ObjectSetInteger(class="num">0,"ShowMACD",OBJPROP_YDISTANCE,class="num">5); ObjectSetInteger(class="num">0,"ShowMACD",OBJPROP_CORNER,CORNER_RIGHT_UPPER); class=class="str">"cmt">// 以及一个标定点 ObjectSetString(class="num">0,"ShowMACD",OBJPROP_TEXT,"ShowMACD"); class=class="str">"cmt">// 按钮标签 ObjectSetInteger(class="num">0,"ShowMACD",OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(class="num">0,"ShowMACD",OBJPROP_XSIZE,class="num">70); class=class="str">"cmt">//按钮大小 ObjectSetInteger(class="num">0,"ShowMACD",OBJPROP_YSIZE,class="num">20); ObjectSetInteger(class="num">0,"ShowMACD",OBJPROP_SELECTABLE,false); class=class="str">"cmt">// 使它可选 ObjectCreate(class="num">0,"ShowOsMA",OBJ_BUTTON,ChartWindowFind(),class="num">100,class="num">100); ObjectSetInteger(class="num">0,"ShowOsMA",OBJPROP_XDISTANCE,class="num">75); ObjectSetInteger(class="num">0,"ShowOsMA",OBJPROP_YDISTANCE,class="num">30); ObjectSetInteger(class="num">0,"ShowOsMA",OBJPROP_CORNER,CORNER_RIGHT_UPPER); ObjectSetString(class="num">0,"ShowOsMA",OBJPROP_TEXT,"Show OsMA"); ObjectSetInteger(class="num">0,"ShowOsMA",OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(class="num">0,"ShowOsMA",OBJPROP_XSIZE,class="num">70); ObjectSetInteger(class="num">0,"ShowOsMA",OBJPROP_YSIZE,class="num">20); ObjectSetInteger(class="num">0,"ShowOsMA",OBJPROP_SELECTABLE,false); SetIndexBuffer(class="num">0,MACDlineBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,SignallineBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">2,HistogramBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">3,HistogramColors,INDICATOR_COLOR_INDEX); if(ObjectGetInteger(class="num">0,"ShowMACD",OBJPROP_STATE)!=class="num">1) { PlotIndexSetInteger(class="num">0,PLOT_DRAW_TYPE,DRAW_NONE); class=class="str">"cmt">// 索引为0的缓存不绘制
◍ 用按钮切换指标绘制样式
在自定义指标里,绘制哪条缓冲、画成什么形态,完全可以用图表按钮的 OBJPROP_STATE 实时切换。上面这段逻辑里,索引0和1的缓冲默认走 DRAW_LINE,当对应开关未命中时改走 DRAW_NONE,等于把两条线藏起来;索引2则在关闭时隐藏、开启时变成 DRAW_COLOR_HISTOGRAM 彩色柱状。 按钮本身用 OBJ_BUTTON 创建,挂在 CORNER_RIGHT_LOWER(右下角),XDISTANCE 75 / YDISTANCE 分别取 50 和 25,两个按钮错开 25 像素纵向距离,尺寸都是 70×20、字号 8,且 OBJPROP_SELECTABLE 设为 false 避免误选。 缓冲数量后来补到 4 和 5,其中索引4用 SetIndexBuffer 绑给 EldersiEMA 做计算缓冲(INDICATOR_CALCULATIONS),再通过 CopyBuffer 从 iMA 的句柄把 EMA 数据一次性搬进来,rates_total 根 K 线全量复制。外汇与贵金属波动剧烈,这类切换逻辑只改显示层、不动信号计算,实盘前务必在 MT5 策略测试器里验证按钮状态读取是否和图表同步。
PlotIndexSetInteger(class="num">1,PLOT_DRAW_TYPE,DRAW_NONE); class=class="str">"cmt">// 索引为1的缓存也不绘制 } else { PlotIndexSetInteger(class="num">0,PLOT_DRAW_TYPE,DRAW_LINE); class=class="str">"cmt">// 索引为0的缓存绘制成线 PlotIndexSetInteger(class="num">1,PLOT_DRAW_TYPE,DRAW_LINE); class=class="str">"cmt">// 索引为1的缓存也绘制成线 } if(ObjectGetInteger(class="num">0,"ShowOsMA",OBJPROP_STATE)!=class="num">1) { class=class="str">"cmt">//索引为2的缓存不绘制 PlotIndexSetInteger(class="num">2,PLOT_DRAW_TYPE,DRAW_NONE); } else { class=class="str">"cmt">//索引为2的缓存绘制成一个彩色直方图 PlotIndexSetInteger(class="num">2,PLOT_DRAW_TYPE,DRAW_COLOR_HISTOGRAM); } ObjectCreate(class="num">0,"2color",OBJ_BUTTON,ChartWindowFind(),class="num">100,class="num">100); ObjectSetInteger(class="num">0,"2color",OBJPROP_XDISTANCE,class="num">75); ObjectSetInteger(class="num">0,"2color",OBJPROP_YDISTANCE,class="num">50); ObjectSetInteger(class="num">0,"2color",OBJPROP_CORNER,CORNER_RIGHT_LOWER); ObjectSetInteger(class="num">0,"2color",OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(class="num">0,"2color",OBJPROP_XSIZE,class="num">70); ObjectSetInteger(class="num">0,"2color",OBJPROP_YSIZE,class="num">20); ObjectSetInteger(class="num">0,"2color",OBJPROP_SELECTABLE,false); ObjectSetString(class="num">0,"2color",OBJPROP_TEXT,"MultiColor"); ObjectCreate(class="num">0,"Impulse",OBJ_BUTTON,ChartWindowFind(),class="num">100,class="num">100); ObjectSetInteger(class="num">0,"Impulse",OBJPROP_XDISTANCE,class="num">75); ObjectSetInteger(class="num">0,"Impulse",OBJPROP_YDISTANCE,class="num">25); ObjectSetInteger(class="num">0,"Impulse",OBJPROP_CORNER,CORNER_RIGHT_LOWER); ObjectSetInteger(class="num">0,"Impulse",OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(class="num">0,"Impulse",OBJPROP_XSIZE,class="num">70); ObjectSetInteger(class="num">0,"Impulse",OBJPROP_YSIZE,class="num">20); ObjectSetInteger(class="num">0,"Impulse",OBJPROP_SELECTABLE,false); class="macro">#class="kw">property indicator_buffers class="num">4 class="macro">#class="kw">property indicator_buffers class="num">5 class="type">class="kw">double EldersiEMA[]; SetIndexBuffer(class="num">4,EldersiEMA,INDICATOR_CALCULATIONS); class=class="str">"cmt">// you can do all in single line CopyBuffer(iMA(NULL,class="num">0,EldersEMA,class="num">0,MODE_EMA,PRICE_CLOSE),class="num">0,class="num">0,rates_total,EldersiEMA); if (ObjectGetInteger(class="num">0,"2color",OBJPROP_STATE)) { for(class="type">int i=class="num">1;i<rates_total;i++)
直方图着色逻辑的三种切换路径
OSMA 直方图的颜色不是写死的,而是由按钮状态在运行时实时决定。基础模式下只比前后两根 HistogramBuffer:上升标 0(DeepSkyBlue),下降标 1(Red),对应 #property indicator_color3 里的前两色。 点开 Impulse 按钮后,判定升级为「直方图方向 + MACD线方向」双条件:两者同升标 0,同降标 1,其余摩擦情况标 2(Green)。这把单纯动量变成了动量+趋势的双重过滤,欧元兑美元 15 分钟图上假突破信号倾向减少。 若切到 Elder's 模式,则把 MACD 线换成 EMA 线(EldersiEMA)做同向校验,逻辑结构完全一致,只是参照系从振荡器换成平滑均线。外汇与贵金属波动剧烈,这类颜色切换仅作视觉辅助,实际进出场仍须结合风控,高风险品种勿单独依赖。 2color 按钮独立控制:开启时退回最简单的双色逻辑;关闭时走 MultiColor 多色分支,代码里留了入口但未填具体条件,复制后可在该处补自己的规则。
if(ObjectGetInteger(class="num">0,"Impulse",OBJPROP_STATE)) { ObjectSetString(class="num">0,"Impulse",OBJPROP_TEXT,"Impulse"); for(class="type">int i=class="num">1;i<rates_total;i++) { if((HistogramBuffer[i]>HistogramBuffer[i-class="num">1]) && (MACDlineBuffer[i]>MACDlineBuffer[i-class="num">1])) HistogramColors[i]=class="num">0; else { if((HistogramBuffer[i]<HistogramBuffer[i-class="num">1]) && (MACDlineBuffer[i]<MACDlineBuffer[i-class="num">1])) HistogramColors[i]=class="num">1; else HistogramColors[i]=class="num">2; } } } else { ObjectSetString(class="num">0,"Impulse",OBJPROP_TEXT,"Elder&class="macro">#x27;s"); for(class="type">int i=class="num">1;i<rates_total;i++) { if((HistogramBuffer[i]>HistogramBuffer[i-class="num">1]) && (EldersiEMA[i]>EldersiEMA[i-class="num">1])) HistogramColors[i]=class="num">0; else { if((HistogramBuffer[i]<HistogramBuffer[i-class="num">1]) && (EldersiEMA[i]<EldersiEMA[i-class="num">1])) HistogramColors[i]=class="num">1; else HistogramColors[i]=class="num">2; } } } if(ObjectGetInteger(class="num">0,"2color",OBJPROP_STATE)) { for(class="type">int i=class="num">1;i<rates_total;i++) { if(HistogramBuffer[i] > HistogramBuffer[i-class="num">1]) HistogramColors[i]=class="num">0; if(HistogramBuffer[i] < HistogramBuffer[i-class="num">1]) HistogramColors[i]=class="num">1; } } else { ObjectSetString(class="num">0,"2color",OBJPROP_TEXT,"MultiColor"); if(ObjectGetInteger(class="num">0,"Impulse",OBJPROP_STATE)) {
「双模式下的柱线着色分支」
这段逻辑处理 MACD 指标在两种显示模式下的直方图配色:Impulse 模式与 Elder's 模式。两种模式都遍历从 1 到 rates_total 的每根 K 线,依据直方图与参考线的同向/反向变动决定颜色索引 0、1、2。 Impulse 模式下,若 HistogramBuffer[i] 与 HistogramBuffer[i-1] 同增、且 MACDlineBuffer[i] 大于前一根,则标 0(多头动能);若两者同减则标 1(空头动能);其余情况标 2(分歧)。Elder's 模式把参考线换成 EldersiEMA,判断逻辑完全一致,只是对比基准从 MACD 主线变为指数平滑后的 Elder 值。 末尾的对象文本切换值得注意:当按钮 "2color" 处于按下状态,标签写为 "2ColorMACD",否则为 "MultiColor";"Impulse" 按钮同理,按下显示 "Impulse",弹起显示 "Elder's"。外汇与贵金属市场波动剧烈,这类多色 MACD 仅作动能参考,实际信号可能失效,请务必在 MT5 策略测试器内用自己的品种验证。 直接把下面这段拷进指标 OnCalculate 末尾,就能看到两种模式切换时柱色如何重算:
ObjectSetString(class="num">0,"Impulse",OBJPROP_TEXT,"Impulse"); for(class="type">int i=class="num">1;i<rates_total;i++) { if((HistogramBuffer[i]>HistogramBuffer[i-class="num">1]) && (MACDlineBuffer[i]>MACDlineBuffer[i-class="num">1])) HistogramColors[i]=class="num">0; else { if((HistogramBuffer[i]<HistogramBuffer[i-class="num">1]) && (MACDlineBuffer[i]<MACDlineBuffer[i-class="num">1])) HistogramColors[i]=class="num">1; else HistogramColors[i]=class="num">2; } } else { ObjectSetString(class="num">0,"Impulse",OBJPROP_TEXT,"Elder&class="macro">#x27;s"); for(class="type">int i=class="num">1;i<rates_total;i++) { if((HistogramBuffer[i]>HistogramBuffer[i-class="num">1]) && (EldersiEMA[i]>EldersiEMA[i-class="num">1])) HistogramColors[i]=class="num">0; else { if((HistogramBuffer[i]<HistogramBuffer[i-class="num">1]) && (EldersiEMA[i]<EldersiEMA[i-class="num">1])) HistogramColors[i]=class="num">1; else HistogramColors[i]=class="num">2; } } } if (ObjectGetInteger(class="num">0,"2color",OBJPROP_STATE)) ObjectSetString(class="num">0,"2color",OBJPROP_TEXT,"2ColorMACD"); else ObjectSetString(class="num">0,"2color",OBJPROP_TEXT,"MultiColor"); if (ObjectGetInteger(class="num">0,"Impulse",OBJPROP_STATE)) ObjectSetString(class="num">0,"Impulse",OBJPROP_TEXT,"Impulse"); else ObjectSetString(class="num">0,"Impulse",OBJPROP_TEXT,"Elder&class="macro">#x27;s")
◍ 把三缓冲区和 Elder 平滑塞进一个指标
想在 MT5 里把 MACD 主线、信号线、彩色直方图外加一条 13 期 Elder EMA 合并成一个窗口,核心是先靠 #property 把三个绘制层定义清楚。第一层绿色实线宽 1 画 MACDline,第二层红色实线宽 1 画 Signalline,第三层用 DRAW_COLOR_HISTOGRAM、宽 2、配色 DeepSkyBlue/Red/Green 画 Histogram,颜色索引单独走一个缓冲。 输入参数直接给默认组合:Fast=12、Slow=26、Signal=9 是经典 MACD 节奏,EldersEMA=13 则是比尔·威廉姆斯那套惯性过滤的常用周期,外汇与贵金属波动下这组合可能比单看 MACD 少一些假交叉。 OnInit 里只做缓冲映射不计算结果:索引 0~2 用 INDICATOR_DATA 接三条可见线,索引 3 用 INDICATOR_COLOR_INDEX 接直方图配色,索引 4 用 INDICATOR_CALCULATIONS 接 Elder EMA 内部计算缓冲,不显示。开 MT5 新建指标把这段贴进 OnInit,编译后子窗口就会预留出 5 个缓冲位,缺一个都会报数组越界。
class="macro">#class="kw">property indicator_label1 "MACDline" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 Green class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//---- 绘制信号线 class="macro">#class="kw">property indicator_label2 "Signalline" class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color2 Red class="macro">#class="kw">property indicator_style2 STYLE_SOLID class="macro">#class="kw">property indicator_width2 class="num">1 class=class="str">"cmt">//---- 绘制直方图 class="macro">#class="kw">property indicator_label3 "Histogram" class="macro">#class="kw">property indicator_type3 DRAW_COLOR_HISTOGRAM class="macro">#class="kw">property indicator_color3 DeepSkyBlue,Red,Green class="macro">#class="kw">property indicator_style3 STYLE_SOLID class="macro">#class="kw">property indicator_width3 class="num">2 class=class="str">"cmt">//--- 输入参数 class="kw">input class="type">int Fast=class="num">12; class="kw">input class="type">int Slow=class="num">26; class="kw">input class="type">int Signal=class="num">9; class="kw">input class="type">int EldersEMA=class="num">13; class=class="str">"cmt">//--- 指标缓存 class="type">class="kw">double MACDlineBuffer[]; class="type">class="kw">double SignallineBuffer[]; class="type">class="kw">double HistogramBuffer[]; class="type">class="kw">double HistogramColors[]; class="type">class="kw">double EldersiEMA[]; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓存映射 SetIndexBuffer(class="num">0,MACDlineBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,SignallineBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">2,HistogramBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">3,HistogramColors,INDICATOR_COLOR_INDEX); SetIndexBuffer(class="num">4,EldersiEMA,INDICATOR_CALCULATIONS); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 自定义指标迭代函数 | 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[],