创建具有图形控制选项的指标·进阶篇
🎛️

创建具有图形控制选项的指标·进阶篇

(2/3)· 还在切换不同 MACD 模板看盘?这篇用 MQL5 向导把多类型指标合成可切换的图形控制系统

含代码示例偏理论 第 2/3 篇
很多人把 MACD 的线型和直方图分开挂好几个窗口,切来切去漏掉信号。把多种变体硬塞进一个指标却不设图形开关,最后连自己都分不清当前看的是哪套逻辑。

「用按钮切换 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 就能跑:

MQL5 / C++
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 策略测试器里验证按钮状态读取是否和图表同步。

MQL5 / C++
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 多色分支,代码里留了入口但未填具体条件,复制后可在该处补自己的规则。

MQL5 / C++
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 末尾,就能看到两种模式切换时柱色如何重算:

MQL5 / C++
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 个缓冲位,缺一个都会报数组越界。

MQL5 / C++
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[],
让小布替你跑这套
这些多类型 MACD 的切换与诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到当前生效的是哪一种变体,你只管读结构。

常见问题

句柄是指标副本的引用,配合 CopyBuffer 才能把内部缓冲区算好的数据拉到自己的数组里,没有句柄就无法读取。
因为复制来的缓冲区索引从图表末尾起算,而价格数组从当前柱起算,统一定义为时序型才能让所有缓冲区索引方向一致。
Elder 变体多了趋势检验线和对移动平均的开关,图形系统里需要额外留控制位决定画不画那条线。
可以,小布的品种页已内置多类型 MACD 诊断,图形开关状态会随品种自动呈现,不用自己写向导。
在绘图属性里给上涨和下跌缓冲区分派不同颜色索引,指标按数值正负自动从对应缓冲区取色绘制。