Разработка пользовательского канала Дончиана с помощью MQL5

Mohamed Abdelmaaboud | 18 сентября, 2023

Введение

В этой статье мы поговорим об очень важной части торговли - идентификации тренда, а также узнаем о техническом инструменте, который может быть полезен в этом деле, а именно - о канала Дончиана (Дончяна). Это индикатор, следующий за трендом.

Все эти вопросы будут рассмотрены в следующих разделах:


Внимание! Все содержание настоящей статьи предоставляется "как есть", предназначено только для целей обучения и не является торговой рекомендацией. Статья не несет в себе каких-либо гарантий результатов. Все, что вы применяете на практике на основе этой статьи, вы делаете исключительно на свой страх и риск, автор не гарантирует никаких результатов.


Определение канала Дончиана

В этом разделе мы дадим определение каналу Дончиана и рассмотрим принципы его применения. Канал Дончиана был разработан трейдером Ричардом Дончианом. Его основная цель - обнаружить тренд. Это индикатор, следующий за трендом и запаздывающий, поскольку он следует направлениям тренда и движениям цен. Он состоит из трех линий, образующих канал, в котором находится цена. Верхняя линия канала представляет собой самую высокую цену, зафиксированную в течение определенного времени, нижняя линия канала представляет собой самую низкую цену за определенный период времени, а средняя линия представляет собой половину расстояния между верхней и нижней линиями.

Ниже представлен пример канала:

пример индикатора

На графике мы видим линию над ценами, еще одну под ценами и третью между ними. Индикатор содержит или окружает цены верхней и нижней линиями в дополнение к еще одной средней линии, которая также может быть полезна. Индикатор может быть основой для разных стратегий поиска тренда, в частности для поиска прорывов и линий поддержки/сопротивления.

Основной принцип использования индикатора заключается в наблюдении максимумов и минимумов цен в течение определенного времени, чтобы определить тренды или преобладающее направление. Если цена находится выше самого высокого максимума определенного времени, это может быть сигналом на покупку. Если цена находится ниже самого низкого минимума определенного времени, это может быть сигналом на продажу. Итак, после указания определенного периода времени и определения ценовых максимумов и минимумов, мы наблюдаем за ценой до тех пор, пока цены не начнут двигаться в определенном направлении вверх или вниз, и сигналом здесь является пробитие предопределенного максимума или минимума.

Индикатор можно использовать для определения уровней стоп-лосса и тейк-профита. Это очень важно в торговле, поскольку позволяет снизить вероятность ошибок в установке уровней при использовании правильных параметров. Например, минимум канала можно использовать в качестве уровня стоп-лосса для позиции на покупку или уровня тейк-профита для позиции на продажу. Максимум канала можно использовать в качестве стоп-лосса для позиции на продажу или тейк-профита для позиции на покупку.

Рассмотрим способ расчета индикатора:

Итак, нам нужно определить желаемый период времени, в течение которого нам необходимо определить направление, обнаружить ценовые максимум и минимум, построить линии для наблюдения и получить среднюю линию, рассчитав половину расстояния между ценовыми экстремумами. В то время как канал Дончиана отображает самый высокий максимум и самый низкий минимум в течение определенного периода времени, полосы Боллинджера отображают среднее значение за период времени после сложения и вычитания двух стандартных отклонений. Чтобы подробнее узнать о полосах Боллинджера и о создании торговой системы на их основе, прочтите одну из моих предыдущих статей.

Канал рекомендуется использовать совместно с другими техническими индикаторами для получения полноценной картины рынка и лучших результатов.


Пользовательский канал Дончиана

В этом разделе мы рассмотрим метод, который можно использовать для создания собственного канала Дончиана с помощью 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 с тремя параметрами (индекс, буфер в виде динамического массива и метка в виде строки для каждой строки индикатора). В теле функции сделаем следующее:

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 мы выполним следующие шаги для расчета индикатора:

Проверяем, меньше ли times_total, чем введенный пользователем период +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);
  }

Код должен быть скомпилирован без ошибок и предупреждений, тогда мы найдем индикатор в окне "Навигатор" торгового терминала в папке "Индикаторы" и, выполнив его, откроем окно параметров и введем следующее:

входные параметры индикатора

Как мы видим, у нас есть два входных параметра:

После настройки параметров и нажатия ОК индикатор прикрепляется к графику:

индикатор присоединен

Как видим, всё выглядит как надо.


Советник Donchian Channel

Используем пользовательский канал Дончиана в торговой системе, создав советник для генерации сигналов на основе движения или поведения индикатора. Мы можем сделать это, используя два разных метода: первый — написать код индикатора в советнике, второй — использовать функцию iCustom для прикрепления созданного индикатора к советнику. Здесь мы разработаем очень простые системы, чтобы понять идею и то, как мы можем улучшить эти системы на основе второго метода, рассмотренного при создании советника.

Советник Donchian Channel Simple

Создадим первую систему, которая отображает значения индикатора (верхний, средний и нижний каналы) на графике. Программа будет постоянно отслеживать эти значения и выводить их на график в качестве комментария.

Ниже приведены шаги для создания советника:

Создадим входную переменную периода индикатора со значением по умолчанию (20). Пользователь может обновить значение из входных параметров советника. 

input int indPeriod=20; //Period

Создайте целочисленную глобальную переменную donChianChannel.

int donchianChannel;

В части OnInit() обновим donchianChannel, назначив ему функцию iCustom, которая возвращает дескриптор созданного пользовательского индикатора. Параметры такие:

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

В части OnDeinit() используем функцию 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 для всех.

   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);
  }

После успешной компиляции код появится в окне Навигатора в папке "Советники". При запуске советника на графике окно параметров выглядит так:

окно входных параметров dcSimpleEA

После запуска советника на графике мы увидим значения канала Дончиана в виде комментария:

запущенный советник dcSimpleEA и сигнал

На графике имеется искомый сигнал в виде комментария с тремя значениями индикатора (Channel High, Channel Middle и Channel Low). Каждое значение - в отдельной строке.

Если мы сравним значения сигналов советника и значения индикатора, мы увидим, что значения в окне данных такие же, как и значения сигналов советника:

сигнал dcSimpleEA совпадает со значениями индикатора


Если мы хотим, чтобы советник находил сигналы на основе движений и уровней индикатора, его можно улучшить, установив необходимые условия для получения сигналов на покупку и продажу.

Советник Donchian Channel Breakout:

В этой версии советника нам нужно, чтобы программа постоянно проверяла все три значения индикаторов, и если цена (ask) пробьет максимум канала, нам нужно получить сигнал на покупку в качестве комментария на графике. Соответственно, если цена (bid) пробьет минимум канала, нам необходимо получить сигнал на продажу. Во всех остальных случаях ничего получать не надо.

Ниже приведен полный код для создания этого типа торговой системы в виде советника:

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.

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

Условия стратегии:

При покупке

Нам нужна, чтобы программа проверила ask и максимум канала для определения их позиций. Если ask превышает максимум канала, это условие для покупки, и нам нужно, чтобы советник возвращал сигнал на покупку в качестве комментария на графике, как только это условие будет выполнено.

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

При продаже

Нам нужна, чтобы программа проверила bid и минимум канала для определения их позиций. Если bid ниже минимума канала, это условие для продажи, и нам нужно, чтобы советник возвращал сигнал на продажу в качестве комментария на графике, как только это условие будет выполнено.

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

В остальных случаях

Нам нужно, чтобы советник ничего не возвращал, если есть что-то кроме условий покупки или продажи.

else Comment(" ");

После успешной компиляции перетащим советник на нужный график для получения сигналов:

В случае сигнала на покупку

сигнал на покупку dcBreakout

В верхнем левом углу графика мы видим сигнал на покупку после пробития максимума канала вверх.

В случае сигнала на продажу

сигнал на продажу dcBreakout

В верхнем левом углу графика мы видим сигнал на продажу после пробития минимума канала вниз.

В остальных случаях

нет сигнала dcBreakout

Как мы видим, сигнала нет, так как цена движется внутри канала - ниже максимума канала и выше минимума.

Канал Дончиана и прорыв скользящей средней:

Теперь нам нужно немного улучшить советник, фильтруя сигналы путем добавления в условия стратегии скользящей средней. Итак, нам нужно получить сигнал на покупку, когда цена ask прорвется выше максимума канала в случае, если 200-периодная EMA (экспоненциальная скользящая средняя) находится ниже цены ask. В случае сигнала на продажу нам необходимо убедиться, что цена bid пробилась ниже минимума канала и в то же время 200-периодная EMA находится выше цены bid. Во всех остальных случаях мы не должны получать ничего.

Ниже приведен полный код для создания этого типа торговой системы:

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[];

Обновим переменную EMA, используя функцию iMA, чтобы вернуть дескриптор индикатора скользящей средней, и его параметры:

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

Используем функцию ArraySetAsSeries для установки AS_SERIES. Ее параметры:

ArraySetAsSeries(emaArray,true);

Используем функцию для получения данных из буфера скользящей средней

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 для торговли или получения сигналов на основе индикатора. Вы можете улучшить торговую систему (советника), добавляя определенные условия и используя другие технические инструменты.

Надеюсь, статья будет для вас полезной и поможет улучшить ваши торговые результаты. Не применяйте содержимое этой статьи без надлежащего тестирования. Не существует инструмента, подходящего для всех трейдеров. 

По ссылкам ниже вы можете ознакомиться с другими моими статьями. В частности, вы можете найти серию статей о создании торговых систем на основе самых популярных технических индикаторов. Я надеюсь, вы найдете их полезными.