如何利用 MQL5 创建自定义唐奇安(Donchian)通道指标·进阶篇
手搓唐奇安通道的三缓冲结构
想在 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 编译后会看到通道上下颠倒,动手前先把这两行对调才符合常规视觉。
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 也在出价下方时,系统倾向不报卖出,过滤掉一部分假突破。
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);才算完整。
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 的索引使用和双层过滤条件。
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 下有效。上真仓前,务必用策略测试器按你自己的品种和周期验过——没有人人适用的圣杯,只有你测过亏得起的那套。