如何利用 MQL5 创建自定义唐奇安(Donchian)通道指标·进阶篇
📘

如何利用 MQL5 创建自定义唐奇安(Donchian)通道指标·进阶篇

第 2/2 篇

手搓唐奇安通道的三缓冲结构

想在 MT5 里自己写一套唐奇安通道,先得声明 3 个指标缓冲区和 3 个绘图层:上轨、下轨、中轨各占一个 plot。默认周期参数 indPeriod=20,意味着通道回看 20 根 K 线的高低点,外汇与贵金属波动剧烈时这段代码仍可能给出假突破信号,属高风险用法。 初始化函数 indInit 把缓冲绑定到索引,并统一设成线型、宽度 2、左移 1 根(PLOT_SHIFT=1)、起始绘制位置为 indPeriod-1。注意 PLOT_DRAW_BEGIN 设成 19,前 19 根 bar 不会出图,这是为了避免样本不足导致的畸形通道。 OnCalculate 里有个关键判断:rates_total < indPeriod+1 直接 return 0,也就是最少要 21 根历史才能算。循环从 start 开始,用 ArrayMaximum 取近 20 根最高价、ArrayMinimum 取近 20 根最低价,中轨就是两者均值。 上轨缓冲被写成 upperLine-(upperLine-lowerLine),实际等于 lowerLine;下轨缓冲写成 lowerLine+(upperLine-lowerLine),实际等于 upperLine。这相当于把上下轨画反了,你复制去 MT5 编译后会看到通道上下颠倒,动手前先把这两行对调才符合常规视觉。

MQL5 / C++
class="macro">#class="kw">property indicator_buffers class="num">3
class="macro">#class="kw">property indicator_plots class="num">3
input class="type">int indPeriod=class="num">20; class=class="str">"cmt">//Period
input class="type">color indColor=clrBlue; class=class="str">"cmt">//Color
class="type">class="kw">double upperBuff[];
class="type">class="kw">double lowerBuff[];
class="type">class="kw">double middleBuff[];
class="type">class="kw">double upperLine,lowerLine,middleLine;
class="type">int start, bar;
class="type">void indInit(class="type">int index, class="type">class="kw">double &buffer[],class="type">class="kw">string label)
  {
   SetIndexBuffer(index,buffer,INDICATOR_DATA);
   PlotIndexSetInteger(index,PLOT_DRAW_TYPE,DRAW_LINE);
   PlotIndexSetInteger(index,PLOT_LINE_WIDTH,class="num">2);
   PlotIndexSetInteger(index,PLOT_DRAW_BEGIN,indPeriod-class="num">1);
   PlotIndexSetInteger(index,PLOT_SHIFT,class="num">1);
   PlotIndexSetInteger(index,PLOT_LINE_COLOR,indColor);
   PlotIndexSetString(index,PLOT_LABEL,label);
   PlotIndexSetDouble(index,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }
class="type">int OnInit()
  {
   indInit(class="num">0,upperBuff,"Donchian Channel");
   indInit(class="num">1,lowerBuff,"Donchian Channel");
   indInit(class="num">2,middleBuff,"Middle Donchian");
   IndicatorSetString(INDICATOR_SHORTNAME,"Donchian("+IntegerToString(indPeriod)+")");
   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 &tick_volume[],
                const class="type">long &volume[],
                const class="type">int &spread[])
  {
   if(rates_total<indPeriod+class="num">1)
     {
      class="kw">return class="num">0;
     }
   start=prev_calculated==class="num">0? indPeriod: prev_calculated-class="num">1;
   for(bar=start;bar<rates_total;bar++)
   {
     upperLine=high[ArrayMaximum(high,bar-indPeriod+class="num">1,indPeriod)];
     lowerLine=low[ArrayMinimum(low,bar-indPeriod+class="num">1,indPeriod)];
     middleLine=(upperLine+lowerLine)/class="num">2;
     
     upperBuff[bar]=upperLine-(upperLine-lowerLine);
     lowerBuff[bar]=lowerLine+(upperLine-lowerLine);
     middleBuff[bar]=middleLine;
   }
   class="kw">return(rates_total);
  }

「用 iCustom 把唐奇安通道接进 EA」

想在 MT5 里让 EA 直接读你写好的唐奇安通道指标,最省事的办法不是重写算法,而是用 iCustom 把现成指标挂进去。下面这套思路只做信号注释和突破提示,不自动下单,刚好用来验证指标和价格的联动关系,外汇和贵金属波动大,拿去跑之前先在模拟盘确认逻辑。 先建输入变量 indPeriod 默认 20,用户能在 EA 参数里改;再声明全局句柄 donchianChannel。OnInit 里调用 iCustom(_Symbol, PERIOD_CURRENT, "My Files\\Donchian_Channel\\Donchian_Channel", indPeriod) 拿到指标句柄,OnDeinit 里 Print 一句 "Donchian Channel EA Removed" 做卸载提示。 OnTick 中声明 channelBuff、channelBuff1、middleBuff 三个数组,分别 CopyBuffer 取缓冲区 0/1/2 各 3 根数据,对应通道低、高、中。取 [0] 赋值给 channelLow、channelHigh、channelMiddle,再用 Comment 把三行数值打印到图表左上。编译无报错后从导航器拖到图表,就能看到注释值和数据窗口里的指标数值完全一致。 突破版 EA 加了两个双精度变量 ask、bid,用 SymbolInfoDouble 取实时报价。条件很直白:ask 大于 channelHigh 就注释“买入信号”,bid 小于 channelLow 就注释“卖出信号”,价格在通道内则什么都不显示。实测中向上刺破高点那一瞬左上角出买入词,向下破低点出卖出词,其余时间空白。 再叠一层过滤:加 input int maPeriod=200,用 iMA(_Symbol, _Period, maPeriod, 0, MODE_EMA, PRICE_CLOSE) 取 200 EMA 句柄,CopyBuffer 后判方向。买入需 ask>channelHigh 且 ask>EMA,卖出需 bid<channelLow 且 bid>EMA。这样在价格低于通道低点但 EMA 也在出价下方时,系统倾向不报卖出,过滤掉一部分假突破。

MQL5 / C++
input class="type">int indPeriod=class="num">20; class=class="str">"cmt">//Period
class="type">int donchianChannel;
donchianChannel=iCustom(_Symbol,PERIOD_CURRENT,"My Files\Donchian_Channel\Donchian_Channel",indPeriod);
Print("Donchian Channel EA Removed");
class="type">class="kw">double channelBuff[],channelBuff1[], middleBuff[];
   CopyBuffer(donchianChannel,class="num">0,class="num">0,class="num">3,channelBuff);
   CopyBuffer(donchianChannel,class="num">1,class="num">0,class="num">3,channelBuff1);
   CopyBuffer(donchianChannel,class="num">2,class="num">0,class="num">3,middleBuff);
   class="type">class="kw">double channelHigh=channelBuff1[class="num">0];
   class="type">class="kw">double channelMiddle=middleBuff[class="num">0];
   class="type">class="kw">double channelLow=channelBuff[class="num">0];
Comment("Channel High: ",channelHigh,"\nChannel Middle: ",channelMiddle,"\nChannel Low: ",channelLow);
input class="type">int indPeriod=class="num">20; class=class="str">"cmt">//Period
class="type">int donchianChannel;
class="type">int OnInit()
  {
  donchianChannel=iCustom(_Symbol,PERIOD_CURRENT,"My Files\Donchian_Channel\Donchian_Channel",indPeriod);
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnDeinit(const class="type">int reason)
  {
  Print("Donchian Channel EA Removed");
  }
class="type">void OnTick()
  {
  class="type">class="kw">double channelBuff[],channelBuff1[], middleBuff[];

◍ 把唐奇安通道读进EA再做突破判定

想在MT5里用唐奇安通道做自动突破提示,核心是先通过iCustom把自定义指标句柄拿到,再用CopyBuffer把上轨、中轨、下轨的数值搬进数组。下面这段代码在OnInit里加载了位于My Files\Donchian_Channel\Donchian_Channel的自定义通道,周期参数indPeriod默认20,也就是看最近20根K线的高低区间。 OnTick里每次报价会取3根缓冲数据,但真正用的是索引0的当前值:channelBuff1[0]是上轨、channelBuff[0]是下轨、middleBuff[0]是中轨。当ask大于上轨时屏幕提示Buy Signal,bid小于下轨时提示Sell Signal,否则清屏。 外汇与贵金属杠杆高,通道突破在震荡市会连续假突破,信号仅代表价格触及边界,不代表趋势必然延续。把indPeriod从20改成50,能明显过滤掉部分毛刺,但也会慢半拍,建议开MT5用EURUSD的M15实测两种参数的提示频率差异。 顺带一提,原文里还挂了一个EMA句柄和maPeriod=200的输入,但这段OnInit只初始化了通道,EMA的iMA调用被截断了,复制代码时别以为能直接编译,得自己补一行EMA=iMA(_Symbol,PERIOD_CURRENT,maPeriod,0,MODE_EMA,PRICE_CLOSE);才算完整。

MQL5 / C++
input class="type">int indPeriod=class="num">20; class=class="str">"cmt">//Period
class="type">int donchianChannel;
class="type">int OnInit()
  {
   donchianChannel=iCustom(_Symbol,PERIOD_CURRENT,"My Files\\Donchian_Channel\\Donchian_Channel",indPeriod);
   class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnDeinit(const class="type">int reason)
  {
   Print("Donchian Channel EA Removed");
  }
class="type">void OnTick()
  {
   class="type">class="kw">double channelBuff[],channelBuff1[], middleBuff[];
   CopyBuffer(donchianChannel,class="num">0,class="num">0,class="num">3,channelBuff);
   CopyBuffer(donchianChannel,class="num">1,class="num">0,class="num">3,channelBuff1);
   CopyBuffer(donchianChannel,class="num">2,class="num">0,class="num">3,middleBuff);
   class="type">class="kw">double channelHigh=channelBuff1[class="num">0];
   class="type">class="kw">double channelMiddle=middleBuff[class="num">0];
   class="type">class="kw">double channelLow=channelBuff[class="num">0];
   class="type">class="kw">double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   class="type">class="kw">double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
   if(ask>channelHigh)
     {
      Comment("Buy Signal");
     }
     else if(bid<channelLow)
     {
      Comment("Sell Signal");
     }
     else Comment(" ");
  }

唐奇安通道叠加EMA的触发逻辑

这段 EA 片段把唐奇安通道(上轨、中轨、下轨)与一条可调周期的指数均线揉在一起,在每笔报价来临时重新取数。通道句柄用 CopyBuffer 拉取最近 3 根 K 线的缓冲,索引 0 为实时值;EMA 默认周期写在 input 里,样例给的是 200,属于偏长期趋势过滤。 OnTick 里先抓 ask / bid,再比大小:当 ask 同时高于通道上轨和 EMA 值,终端右下角弹「Buy Signal」;当 bid 同时低于通道下轨和 EMA 值,弹「Sell Signal」。两条都不满足就清屏留空,避免旧信号干扰。 需要注意,这里只是把条件打印出来,并未下单。外汇与贵金属杠杆高,信号出现后价格可能假突破,实盘前请在 MT5 策略测试器用 200 EMA 跑至少 3 个月 tick 数据验证触发频率与滑点表现。 下面把核心取值与判断代码拆开看,重点在 CopyBuffer 的索引使用和双层过滤条件。

MQL5 / C++
EMA = iMA(_Symbol,_Period,maPeriod,class="num">0,MODE_EMA,PRICE_CLOSE);
class="kw">return(INIT_SUCCEEDED);
}
class="type">void OnDeinit(const class="type">int reason)
  {
   Print("Donchian Channel EA Removed");
  }
class="type">void OnTick()
  {
   class="type">class="kw">double channelBuff[],channelBuff1[], middleBuff[];
   CopyBuffer(donchianChannel,class="num">0,class="num">0,class="num">3,channelBuff);
   CopyBuffer(donchianChannel,class="num">1,class="num">0,class="num">3,channelBuff1);
   CopyBuffer(donchianChannel,class="num">2,class="num">0,class="num">3,middleBuff);
   ArraySetAsSeries(emaArray,true);
   CopyBuffer(EMA,class="num">0,class="num">0,class="num">3,emaArray);
   class="type">class="kw">double channelHigh=channelBuff1[class="num">0];
   class="type">class="kw">double channelMiddle=middleBuff[class="num">0];
   class="type">class="kw">double channelLow=channelBuff[class="num">0];
   class="type">class="kw">double EMAValue=NormalizeDouble(emaArray[class="num">0],_Digits);
   class="type">class="kw">double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   class="type">class="kw">double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
   if(ask>channelHigh&&ask>EMAValue)
     {
      Comment("Buy Signal","\nAsk above Channel High","\nAsk above(",maPeriod,") EMA");
     }
   else if(bid<channelLow&&bid<EMAValue)
     {
      Comment("Sell Signal","\nBid below Channel Low","\nBid Below(",maPeriod,") EMA");
     }
   else Comment(" ");
  }
input class="type">int maPeriod=class="num">200; class=class="str">"cmt">//Moving Average Period
class="type">int EMA;
class="type">class="kw">double emaArray[];
EMA = iMA(_Symbol,_Period,maPeriod,class="num">0,MODE_EMA,PRICE_CLOSE);
ArraySetAsSeries(emaArray,true);
CopyBuffer(EMA,class="num">0,class="num">0,class="num">3,emaArray);
class="type">class="kw">double EMAValue=NormalizeDouble(emaArray[class="num">0],_Digits);
   if(ask>channelHigh&&ask>EMAValue)
     {
      Comment("Buy Signal","\nAsk above Channel High","\nAsk above(",maPeriod,") EMA");
     }
   else if(bid<channelLow&&bid<EMAValue)
     {
      Comment("Sell Signal","\nBid below Channel Low","\nBid Below(",maPeriod,") EMA");
     }
else Comment(" ");

「一点提醒」

自定义唐奇安通道指标写完后,真正的杠杆在 iCustom 调用:把通道突破信号接进 EA,再叠一层别的工具过滤,回测里往往能看到胜率或回撤的边际变化。本文附的 dcSimpleEA.mq5 仅 0.74 KB,dcBreakout.mq5 也才 0.91 KB,足够你直接丢进 MT5 改两行参数跑一遍。 外汇和贵金属杠杆高、滑点跳空频繁,任何基于通道的突破系统都只在特定波动 regime 下有效。上真仓前,务必用策略测试器按你自己的品种和周期验过——没有人人适用的圣杯,只有你测过亏得起的那套。

常见问题

上轨、下轨和中线分开缓冲,绘制和EA读取才不会互相覆盖;中线可留空只画上下轨,但缓冲结构建议先建好。
用iCustom取上轨缓冲值,当收盘价大于上轨且前一根未突破,才视为有效突破,避免影线假刺穿。
可以,小布能按你设的通道周期盯品种页,出现上下轨突破时直接推提醒,不用自己挂EA盯盘。
偏向过滤:价格在中周期EMA上方只做上破多单,下方只做下破空单,降低震荡市反向信号。
外汇贵金属高风险,可把周期提到55根减少信号频率,或加最小通道宽度过滤,实盘前先历史回看验证。