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

Mohamed Abdelmaaboud | 14 十二月, 2023

概述

在本文中,我们将讨论交易中一个非常重要的概念,即趋势识别,故我们要学习能提供帮助的技术工具,它就是唐奇安(Donchian)通道指标。 该指标追踪趋势,与我们所见相同。

我们将涵盖以下主题的所有内容:


免责声明:所有提供的信息均“如原状”,仅用于教学目的,未准备用于交易目的或建议。 该信息不保证任何形式的结果。 如果您选择在您的任何交易账户上使用这些素材,您将自行承担风险,并且您将是唯一的责任人。


唐奇安(Donchian)通道定义

在这部分中,我们将辨认唐奇按通道指标,并了解其背后的主要概念,以便能有效运用。 唐奇安通道是由 Richard Donchian 交易员开发的,主要目标是识别趋势,这意味着它也是一款趋势跟踪和滞后指标,因为它追踪趋势方向和价格走势。 它由三条线组成,形成一个包围价格的通道。 通道的上边线代表特定时间内记录的最高价格,通道的下边线代表特定时间段的最低价格,中线代表上下边线之间距离的一半。

下图是唐奇安通道图表的示例:

指标示例

正如我们在上一张图表中所见,价格上方有一条线,价格下方有另一条线,它们之间还有第三条线。 该指标包含或围绕价格的上、下线,以及另一很实用的中线,是由什么所造成。 在交易中这种形式的指标都能发挥作用,例如,基于不同策略趋势识别、突破、以及支撑位和阻力位识别。

此指标依据的主要概念是观察特定时间内价格的高点和低点,从而辨别趋势或我们可以偏向的方向。 如果价格高于特定时间的最高点,则表明存在看涨动力,这也许是买入信号。 如果价格低于特定时间的最低低点,则表明存在看跌动力,这也许是卖出信号。 故此,一旦我们指定了特定的时间段,并判定了最高和最低价格,那么我们就要观察它们,直到价格在特定方往上或往下移动,并且正在突破检测到的最高点或最低点之处则为信号。

该指标还可判定止损和止盈价位,这在交易中非常重要和实用,因为它能消除和减少设置错误的价位,因为指标的边线位置非常清晰,它代表重要的价位,如果我们所用参数正确,尤为如此。 由此,例如,通道的低点可以作为多头持仓的止损价位、或空头持仓的止盈价位。 但通道的高点可以作为空头持仓的止损、或多头持仓的止盈目标。

根据我们提到的内容,我们将提供计算该指标的方式,这与以下简单步骤相同:

因此,我们需要判定期望的时间区间,来检测其间的方向,检测最高价格和最低价格,并在它们旁边绘制一条线进行观察,通过计算最高和最低价位间距的一半得到中线。 值得一提的是,唐奇安通道和布林带之间存在差异,因为正如我们提到的,唐奇安通道绘制的是特定时间段内的最高高点和最低低点,但布林带绘制的是时间段内平均值相加和相减两个标准差值。 如果您需要更多信息,可以参阅我之前有关布林带的文章,去了解有关它以及如何基于它创建交易系统的更多信息。

正如我们辨别出的那样,该指标的概念非常重要,尤其是对于那些趋势跟踪交易者,因为它有助于清晰地识别趋势和方向,这令基于该指标操作和测试不同策略时非常有用,故其在我们交易系统中也许是一款非常优秀的工具。 重点是,如果我们将此指标与其它含义的技术指标配合使用,就能获得更好的见解和结果。


自定义唐奇安通道

在本主题中,我将分享一种利用 MQL5 创建自定义唐奇安通道指标的方法。 我们将创建的指标其形式为上边线、下边线和中线,如以下步骤所示。 

按照以下说明和数值创建参数:

#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 3

创建两个输入,一个为周期,另一个为指标线的颜色,如下所示:

input int indPeriod=20; //Period
input color indColor=clrBlue; //Color

创建如以下内容的全局变量:

double upperBuff[];
double lowerBuff[];
double middleBuff[];
double upperLine,lowerLine,middleLine;
int start, bar;

创建指标的自定义函数,使用 void 表示无任何返回,函数名为 indInit,且有三个参数(index、buffer 是动态数组、以及字符串 label 作为指标线标签)。 在函数的主体中,我们将执行以下操作:

void indInit(int index, double &buffer[],string label)
  {
   SetIndexBuffer(index,buffer,INDICATOR_DATA);
   PlotIndexSetInteger(index,PLOT_DRAW_TYPE,DRAW_LINE);
   PlotIndexSetInteger(index,PLOT_LINE_WIDTH,2);
   PlotIndexSetInteger(index,PLOT_DRAW_BEGIN,indPeriod-1);
   PlotIndexSetInteger(index,PLOT_SHIFT,1);
   PlotIndexSetInteger(index,PLOT_LINE_COLOR,indColor);
   PlotIndexSetString(index,PLOT_LABEL,label);
   PlotIndexSetDouble(index,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }

之后,在 OnInit() 主体中,我们将调用自定义函数三次,设置指标的三条线,它将与以下内容相同:

   indInit(0,upperBuff,"Donchian Channel");
   indInit(1,lowerBuff,"Donchian Channel");
   indInit(2,middleBuff,"Middle Donchian");

此处调用 IndicatorSetString 函数设置指标的标签文本

IndicatorSetString(INDICATOR_SHORTNAME,"Donchian ("+IntegerToString(indPeriod)+")");

在 OnCalculate 部分,我们将执行以下步骤来计算指标:

检查 rates_totalis 是否小于用户的输入周期+1,如果是这样,我们需要程序返回零。

   if(rates_total<indPeriod+1)
     {
      return 0;
     }

使用三元运算符 "?:" 为 start 变量赋值 — 如果 start=prev_calculated==0 为 true,则运算符将取 indPeriod 设置,如果为 false,运算符将取 prev_calculated-1 设置。

start=prev_calculated==0? indPeriod: prev_calculated-1;

创建一个 for 循环来计算指标,表达式 1 将是(bar=start),表达式 2 将是(bar < rate_total),表达式 3 将是(bar ++)将柱线递增一根。 for 循环的运算如下相同:

   for(bar=start;bar<rates_total;bar++)
   {
      upperLine=high[ArrayMaximum(high,bar-indPeriod+1,indPeriod)];
      lowerLine=low[ArrayMinimum(low,bar-indPeriod+1,indPeriod)];
      middleLine=(upperLine+lowerLine)/2;
      
      upperBuff[bar]=upperLine-(upperLine-lowerLine);
      lowerBuff[bar]=lowerLine+(upperLine-lowerLine);
      middleBuff[bar]=middleLine;

   }

下面是一个模块的完整代码:

#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 3
input int indPeriod=20; //Period
input color indColor=clrBlue; //Color
double upperBuff[];
double lowerBuff[];
double middleBuff[];
double upperLine,lowerLine,middleLine;
int start, bar;
void indInit(int index, double &buffer[],string label)
  {
   SetIndexBuffer(index,buffer,INDICATOR_DATA);
   PlotIndexSetInteger(index,PLOT_DRAW_TYPE,DRAW_LINE);
   PlotIndexSetInteger(index,PLOT_LINE_WIDTH,2);
   PlotIndexSetInteger(index,PLOT_DRAW_BEGIN,indPeriod-1);
   PlotIndexSetInteger(index,PLOT_SHIFT,1);
   PlotIndexSetInteger(index,PLOT_LINE_COLOR,indColor);
   PlotIndexSetString(index,PLOT_LABEL,label);
   PlotIndexSetDouble(index,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }
int OnInit()
  {
   indInit(0,upperBuff,"Donchian Channel");
   indInit(1,lowerBuff,"Donchian Channel");
   indInit(2,middleBuff,"Middle Donchian");
   IndicatorSetString(INDICATOR_SHORTNAME,"Donchian ("+IntegerToString(indPeriod)+")");

   return(INIT_SUCCEEDED);
  }
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(rates_total<indPeriod+1)
     {
      return 0;
     }
   start=prev_calculated==0? indPeriod: prev_calculated-1;
   for(bar=start;bar<rates_total;bar++)
   {
      upperLine=high[ArrayMaximum(high,bar-indPeriod+1,indPeriod)];
      lowerLine=low[ArrayMinimum(low,bar-indPeriod+1,indPeriod)];
      middleLine=(upperLine+lowerLine)/2;
      
      upperBuff[bar]=upperLine-(upperLine-lowerLine);
      lowerBuff[bar]=lowerLine+(upperLine-lowerLine);
      middleBuff[bar]=middleLine;
   }
   return(rates_total);
  }

编译此段代码后,必须没有错误或警告,然后我们将从交易终端导航窗口里的 Indicator 文件夹中找到该指标,执行它,我们可以找到该窗口,并输入如下内容:

指标输入

正如我们在上图中看到的,我们有两项输入:

根据我们的偏好确定这两项输入,并按确定后,我们可以找到加载到图表上的指标,如下所示

指标加载

正如我们在上图中看到的,我们有三条指标线,两条线从上方和下方围绕价格形成一个通道,一条中线代表通道的距离一半。


唐奇安通道 EA

在这部分中,我们需在交易系统中用到这个自定义的唐奇安通道指标,即创建一个智能系统,其可基于指标的走势或行为生成信号。 我们可以采用两种不同的方法做到这一点:第一种是在 EA 中按指标概念编写代码,或者第二种方法是调用 iCustom 函数将您已创建的指标附加到 EA。 在此,我们设计的是非常简单的系统,只是为了理解概念、以及学习如何基于已创建指标改进系统,故采用第二种方法。

以下是这些 EA 的有关信息:

唐奇按通道简单 EA

我们将开始创建第一个系统,该系统可在图表上返回含有指标值(通道高点、通道中间值、和通道低点)的注释。 因此,我们需要程序持续检查和监控这些数值,并将它们输出到图表上作为注释。

以下是创建该类型 EA 的步骤:

创建指标输入变量,周期采用默认值(20),但用户可以在 EA 输入中更改它。 

input int indPeriod=20; //Period

创建一个整数型全局变量 donChianChannel。

int donchianChannel;

在 OnInit() 部分中,我们调用 iCustom 函数返回创建的自定义唐奇安通道指标的句柄,并将其赋值给 donchianChannel。 其参数为:

donchianChannel=iCustom(_Symbol,PERIOD_CURRENT,"My Files\\Donchian_Channel\\Donchian_Channel",indPeriod);

在 OnDeinit() 部分,当删除 EA 时,我们调用 Print 函数在智能系统栏返回一条消息,其中包含 “Donchian Channel EA Removed”。

Print("Donchian Channel EA Removed");

在 OnTick() 部分,我们将创建三个数组 channelBuff、channelBuff1 和 middleBuff。

double channelBuff[],channelBuff1[], middleBuff[];

调用 CopyBuffer 函数获取自定义唐奇安通道指标每个缓冲区的数据。 其参数:

   CopyBuffer(donchianChannel,0,0,3,channelBuff);
   CopyBuffer(donchianChannel,1,0,3,channelBuff1);
   CopyBuffer(donchianChannel,2,0,3,middleBuff);

所有双精度变量创建完毕,定义每条线的当前值。

   double channelHigh=channelBuff1[0];
   double channelMiddle=middleBuff[0];
   double channelLow=channelBuff[0];

调用 Comment 函数在图表上返回包含三个值的注释,每个值位于单独的行中。

Comment("Channel High: ",channelHigh,"\nChannel Middle: ",channelMiddle,"\nChannel Low: ",channelLow);

下面是一个模块的完整代码

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

编译此代码后,若无错误或警告,我们可从导航器窗口的 “Expert Advisor” 文件夹下找到它。 在所需图表执行它,我们可以找到与以下内容相同的输入窗口:

dcSimpleEA 输入窗口

执行后,我们发现 EA 加载到图表上,另有唐奇安通道指标的价位作为注释,如下所示:

dcSimpleEA 加载及信号

正如我们所看到的,我们在图表上有所需的信号,它是包含指标三个值(通道高位、通道中值和通道低位)的注释,每个数值都在单独一行中。

出于确认的缘故,我们可以取 EA 的信号值和指标的数值进行比较,我们可从下图发现所插入的指标,且在数据窗口中它的值与 EA 的信号值相同,如下图所示:

dcSimpleEA 信号与指标相同


如果我们想改进这个 EA,并基于指标的走势和价位找到信号,这就是我们将尝试通过以下交易系统(EA)要做的事情,尝试根据指标的概念,为接收买入和卖出信号设置条件。

唐奇安通道突破 EA

在这个版本的 EA 中,我们需要程序持续检查指标的所有三个数值,如果价格(要价)突破通道高点,我们需要接收买入信号作为图表上的注释。 另一种情况是,如果价格(出价)突破通道低点,我们也需要在图表上收到卖出信号作为注释。 如果还有别的东西,我们需要忽略它们。

以下是创建此类型交易系统(EA)的完整代码:

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

此代码中的差异与下相同:

定义要价和出价,创建属性值(ask,bid)的双精度变量,调用 SymbolInfoDouble 函数返回属性(ask,bid)值。

   double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);

策略条件:

买入情况

我们需要程序检查要价和通道高点,以便判定它们的位置。 如果要价大于通道高点,这是买入条件,一旦满足此条件,我们需要 EA 在图表上返回买入信号作为注释。

   if(ask>channelHigh)
     {
      Comment("Buy Signal");
     }

卖出情况

我们需要程序检查出价和通道低点,以便判定它们的位置。 如果出价低于通道低位,这是卖出条件,一旦满足此条件,我们需要 EA 在图表上返回卖出信号作为注释。

     else if(bid<channelLow)
     {
      Comment("Sell Signal");
     }

无情况

如果除了买入或卖出条件之外,我们不需要 EA 返回任何内容。

else Comment(" ");

编译这段代码,且无错误或警告之后,我们可以将它拖放到所需的图表上来执行它,从而获得基于策略的信号,与以下示例相同:

买入信号情况

dcBreakout 买入信号

正如我们在上一张图表的左上角看到的,在向上突破通道高点后,我们有一个买入信号。

卖出信号情况

dcBreakout 卖出信号

正如我们在上一张图表的左上角看到的,在向下突破通道低点后,我们有一个卖出信号。

无情况

dcBreakout 无信号

正如我们所看到的,当价格在通道内移动时,没有信号,这意味着价格低于通道高点,高于通道低点。

唐奇安通道和 MA 突破:

现在,我们需要将移动平均线添加到策略条件当中,进行信号过滤,从而稍微改进 EA,这就是我们将在这个交易系统中所做的。 由此,若 200-周期 EMA(指数移动平均线)低于要价,当价格(要价)突破通道高点时,我们需要收到买入信号。 在卖出信号的情况下,我们需要确保出价突破通道低点,同时 200-周期 EMA 高于出价。 如果发生其它任何情况,我们需要忽略它们。

以下是创建此类型交易系统的完整代码:

input int indPeriod=20; //Period
input int maPeriod=200; //Moving Average Period
int donchianChannel;
int EMA;
double emaArray[];
int OnInit()
  {
   donchianChannel=iCustom(_Symbol,PERIOD_CURRENT,"My Files\\Donchian_Channel\\Donchian_Channel",indPeriod);
   EMA = iMA(_Symbol,_Period,maPeriod,0,MODE_EMA,PRICE_CLOSE);
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
  {
   Print("Donchian Channel EA Removed");
  }
void OnTick()
  {
   double channelBuff[],channelBuff1[], middleBuff[];
   CopyBuffer(donchianChannel,0,0,3,channelBuff);
   CopyBuffer(donchianChannel,1,0,3,channelBuff1);
   CopyBuffer(donchianChannel,2,0,3,middleBuff);
   ArraySetAsSeries(emaArray,true);
   CopyBuffer(EMA,0,0,3,emaArray);
   double channelHigh=channelBuff1[0];
   double channelMiddle=middleBuff[0];
   double channelLow=channelBuff[0];
   double EMAValue=NormalizeDouble(emaArray[0],_Digits);
   double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   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(" ");
  }

此代码中的差异与下相同:

创建另一个整数形输入变量 maPeriod,默认值(200),但用户可以根据自己的喜好进行更改

input int maPeriod=200; //Moving Average Period

创建 EMA 的全局整数型变量

int EMA;

创建一个 emaArray[] 数组

double emaArray[];

调用 iMA 函数返回移动平均线指标的句柄,并赋值到 EMA 变量,其参数为:

EMA = iMA(_Symbol,_Period,maPeriod,0,MODE_EMA,PRICE_CLOSE);

调用 ArraySetAsSeries 函数为数组设置排序 AS_SERIES,其参数为:

ArraySetAsSeries(emaArray,true);

调用 CopyBuffer 函数从移动平均线的缓冲区复制数据

CopyBuffer(EMA,0,0,3,emaArray);

定义 EMA 值,并对其进行常规化

double EMAValue=NormalizeDouble(emaArray[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(" ");

编译此段代码,无错误或警告的话,我们可以找到该交易系统的信号,如以相同

买入信号情况

dc & EMABreakout 买入信号

正如我们在前面的例子中看到的,我们在图表上的注释中有买入信号和这个信号的条件,即满足价格高于通道高点,以及 200-EMA。

卖出信号情况

dc & EMABreakout 卖出信号

正如我们在前面的例子中看到的,我们在图表上的注释中有卖出信号和该信号的条件,即满足价格低于通道低点,以及 200-EMA。

无信号情况

dc & EMABreakout 无信号

正如我们所看到的,图表上没有信号,表明不满足条件,因为价格高于通道低点、且低于通道高点,即使我们有一个卖出设置,即价格低于 200-EMA。


结束语

正如我们通过本文提到的主题所学到的那样,我们辨别唐奇安通道指标在多大程度上可以作为一个实用且有价值的工具,尤其在我们自己将其创建为自定义指标之后,另又基于指标的概念创建交易系统之后。 假设您能够根据您的喜好创建自定义唐奇安通道指标,并作为您的优秀交易工具;此外,能够调用 iCustom 函数创建一个交易系统来交易,或获取基于该指标的信号;不仅如此,您还应该能够改进此交易系统(EA),添加基于其它技术工具的特定条件来增强其结果,并获得更好的见解。

我希望您发现这篇文章对您的交易之旅有用,可获得更好的结果和见解,或者至少发现这篇文章能为您提供有关任何其它相关概念的良好见解。 我不会忘记再次确认,您在真实帐户中使用本文的内容之前一定要进行测试,以确保它对您有利可图,因为没有适合所有人的工具。 

如果您发现这篇文章很有用,并且想阅读我的更多文章,您可以据文后的发表链接去阅读我的其它文章,您可以找到一系列有关如何基于最流行的技术指标创建交易系统的文章,并希望您发现它们都很有用。