English 中文 Español Deutsch 日本語 Português
Индикатор NRTR и торговые модули на его основе для Мастера MQL5

Индикатор NRTR и торговые модули на его основе для Мастера MQL5

MetaTrader 5Торговые системы | 27 октября 2017, 09:31
8 245 7
Dmitrii Troshin
Dmitrii Troshin

Введение

Рассмотрим индикатор, который строит динамический ценовой канал. На основе этого канала создаётся торговый советник. Известно, что подобные системы хорошо работают на трендах, но дают много ложных сигналов на боковых движениях. Поэтому для улучшения результата требуются дополнительные трендовые индикаторы. Выбор такого индикатора — непростая задача, потому что часто он зависит от конкретных условий рынка. Поэтому хорошее решение — найти способ быстро подключить любой выбранный индикатор к готовой торговой системе.

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

Индикатор NRTR

Индикатор NRTR — Nick Rypock Trailing Reverse — индикатор, идея которого предложена Константином Копыркиным. Интересная информация: за названием Nick Rypock скрывается фамилия "Копыркин", написанная наоборот. 

Но вернемся к индикатору. Он представляет собой динамический ценовой канал. Автор иллюстрирует его основную идею следующим рисунком:

NRTR

Торговая система на основе NRTR относится к классу пробойных. Сигналы: на покупку — превышение ценами предыдущего максимума за определённый период; на продажу — падение цены ниже минимума. Часто в подобных системах во время смены тренда в его характеристиках некоторое время учитываются цены предыдущего тренда, прошлые максимумы и минимумы. Чтобы этого избежать, в нашей системе период расчетов определяется динамически.

Сам автор определил NRTR как трендовый индикатор прорыва динамического ценового канала.

Он работает так: если тренд возрастающий, то линия индикатора (канал) находится на определенном уровне ниже максимума цены на заданном промежутке времени. Линия падающего тренда находится над ценами, на постоянном удалении от минимума за заданный период.

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

На рисунке показано, как индикатор сначала следует на определённом расстоянии за трендом. Затем он находится на фиксированном расстоянии от локальных максимумов H1 и H2. Локальный максимум H3 меньше предыдущего и не участвует в расчёте.

Затем в точке L3 цена пробивает канал. Это сигнал к продаже. Значение в точке L3 становится новым минимумом. С этого же момента начинается отсчёт времени, т.е. все предыдущие цены просто отбрасываются и никак не участвуют в расчётах. По мере развития тренда минимум обновляется до L3-L4-L5. Растёт и период динамического ценового канала, до тех пор, пока тренд не поменяется, или период не достигнет заданного максимального значения.

Ширина канала рассчитывается в процентах от величины экстремума или, как вариант, зависит от волатильности цен. В данной статье реализованы оба подхода.

Сигналом на покупку/продажу служит пробой ценой линии канала. Если цена пробивает линию поддержки, даётся сигнал на покупку. Если пробита линия сопротивления, поступает сигнал на продажу.

Нам предстоит перевести описание работы индикатора на язык MQL5. Приступим.

Пишем индикатор: от простого к сложному

Прежде всего, определимся с поведением индикатора. Он будет строиться по ценам закрытия close. Понятно, что построение по историческим данным будет интерпретироваться однозначно. Но что если цена пробивает линию поддержки/сопротивления на несформировавшейся свече? В данной реализации тренд не меняется и сигнал не подаётся, пока свеча не сформировалась. С одной стороны, мы можем потерять часть движения. Если, например, движение началось с огромной свечи, которая пробила канал, то в сделку мы войдем только на следующей. С другой стороны, так мы защищаемся от многочисленных ложных пробоев.

NB: у этого индикатора множество вариаций, поэтому здесь описан лишь оригинальный вариант, описанный автором.

В CodeBase можно найти реализацию этого индикатора, в которой период является динамическим только отчасти. Он обнуляется при смене тренда, но дальше теоретически может расти неограниченно. То есть, линия поддержки рассчитывается как MathMax() от предыдущего значения и текущей цены close. При такой реализации поддержка может только расти, а сопротивление — только падать. В оригинале предполагалось, что ранние значения вне заданного периода утрачивают ценность и отбрасываются. Поэтому здесь max/min ищется через ArrayMaximum/Minimum(close,i,dynamic_period). При таком подходе линии поддержки/сопротивления могут и расти, и падать. Следовательно, возможны случаи, например, при маленьких динамических периодах, когда на "медленном" боковом движении линия поддержки довольно глубоко "сдрейфует" вниз. Но такие плавные длительные тренды — редкость, а идеальных методов не существует. И, как уже сказано выше, основной наш принцип — придерживаться первоначального замысла автора.

Следующий момент — таймсерии. В MQL5 ценовые массивы (close) по умолчанию имеют значение ArraySetAsSeries = false. Для тех, кто работал в MQL4, привычней было, что ценовые массивы имели флаг таймсерии, и Close[0] была цена закрытия самого правого бара (самый левый мы, как правило, не видим). Понимаю, что это вопрос привычки, но в данной статье ArraySetAsSeries(close,true).

Теперь перейдем к исполнению. У нас будет четыре индикаторных буфера: два — для линий поддержки/сопротивления и два — для сигналов на покупку/продажу.

#property indicator_chart_window
#property indicator_buffers 4 
#property indicator_plots   4

//Indicators lienes style
#property indicator_type1  DRAW_LINE
#property indicator_color1 Green
#property indicator_style1 STYLE_DASH

#property indicator_type2  DRAW_LINE
#property indicator_color2 Red
#property indicator_style2 STYLE_DASH

#property indicator_type3  DRAW_ARROW
#property indicator_color3 Green

#property indicator_type4  DRAW_ARROW
#property indicator_color4 Red

Объявим индикаторные буферы и внешние параметры индикатора

input int    period =12;      //динамический период
input double percent =0.2;    //процент отступа 

double Buff_Up[],Buff_Dn[];  
double Sign_Up[],Sign_Dn[];

Сигналы будут изображаться стрелками. Остальные параметры "по вкусу" зададим в функции OnInit(). У меня DRAW_ARROW заданы параметром 236,238 из символов шрифта Wingdings. Параметры для сигнала "вверх", например:

   SetIndexBuffer(2,Sign_Up,INDICATOR_DATA);
   PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0.0);
   PlotIndexSetInteger(2,PLOT_ARROW,236);
   PlotIndexSetInteger(2,PLOT_LINE_WIDTH,1);
   ArraySetAsSeries(Sign_Up,true);

В начале расчётов в функции OnCalculate() идут стандартные проверки на достаточность данных и проверка на первый старт расчёта индикатора.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
  
  int start =0;                                           //точка расчёта
  
  int trend =0;                                           //значение тренда вверх 1, вниз -1
  static int trend_prev =0;
  
  double value =0;                                        //значения индикатора  
  static double value_prev =0;
  
  int dyn_period =1;                                     //значения периода                                    
  static int curr_period =1;
  
  
  double maxmin =0;                                       //техническая переменная для расчётов
  
  ArraySetAsSeries(close,true);
  
  if(rates_total<period) return(0);
  
     if(prev_calculated==0)                              // проверка на первый старт расчета индикатора
  {
      start=rates_total-1;                               // стартовый номер для расчета всех баров
      trend_prev =1;
      value=close[start]*(1-0.01*percent);      
  }
  
  else
     {
      start=rates_total-prev_calculated;                  // стартовый номер для расчета новых баров
     }

trend =trend_prev;
value =value_prev;
dyn_period =curr_period;

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

Теперь напишем основной цикл расчётов с учётом замечаний, описанных выше.

trend =trend_prev;
value=value_prev;
dyn_period =curr_period;    
//-------------------------------------------------------------------+
//                        Основной цикл расчёта   
//-------------------------------------------------------------------+  
for(int i=start;i>=0;i--)
{
    Buff_Up[i] =0.0;
    Buff_Dn[i] =0.0;
    Sign_Up[i] =0.0;
    Sign_Dn[i] =0.0;
    
    if(curr_period>period) curr_period=period;
    if(dyn_period>period) dyn_period=period;
    
 //if trend ascending   
    if(trend>0)
    {
    maxmin =close[ArrayMaximum(close,i,dyn_period)];
    value =maxmin*(1-percent*0.01);
    
    if(close[i]<value)
      {
      maxmin =close[i];
      value =maxmin*(1+percent*0.01);
      trend =-1;
      dyn_period =1;
      }
    }
  
//  if trend descending
    else
    {
    maxmin =close[ArrayMinimum(close,i,dyn_period)];
    value =maxmin*(1+percent*0.01);
    if(close[i]>value)
      {
      maxmin =close[i];
      value =maxmin*(1-percent*0.01);
      trend =1;
      dyn_period =1;
      }
    }  
 // trend changes 
  
      if(trend>0) Buff_Up[i] =value;
      if(trend<0) Buff_Dn[i] =value;

      if(trend_prev<0  &&  trend>0) 
      {
      Sign_Up[i] =value;
      Buff_Up[i] =0.0;
      }
      if(trend_prev>0 && trend<0)
      {
      Sign_Dn[i] =value;
      Buff_Dn[i] =0.0;
      }

  dyn_period++;
  
  if(i)
  {
  trend_prev =trend;
  value_prev =value;
  if(dyn_period==2)curr_period =2;
  else curr_period++;
  }

}

В цикле динамический период ограничивается заданным значением. Находятся новые значения поддержки/сопротивления, осуществляется проверка на смену тренда, если канал пробит ценой закрытия. Последний оператор if() служит проверкой завершённости бара. Только в том случае, если бар сформирован, меняются значения trend_prev, value_prev,а следовательно, может быть подан сигнал на покупку/продажу. Здесь же может "сбрасываться" динамический период.

Полный код индикатора находится в прикреплённом файле NRTR.mq5.

Посмотрим работу индикатора, поместив на график два NRTR с различными параметрами: у первого — период 12 и ширина 0.1%; у второго — период 120 и ширина 0.2%.


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

Волатильность и NRTR

В предыдущем подходе используется фиксированное процентное отклонение ценового канала. Было бы логичней, если бы при увеличении волатильности коридор расширялся, а при уменьшении — сужался. Для оценки волатильности на рынке обычно используется индикатор ATR (average true range). Значение ATR может служить для задания ширины коридора. Его можно вычислять самостоятельно, а можно взять готовый технический индикатор, который уже есть в стандартной поставке.

Чтобы привязать ширину канала к волатильности, просто заменим процентное отклонение значением индикатора ATR. При этом сам коэффициент оставим для масштабирования. Пусть по умолчанию он будет равен 1. Для индикатора ATR объявим еще один индикаторный буфер double Buff_ATR[]. Параметр процента заменим на параметр коэффициента K =1. Для получения значений ATR создадим указатель на него:

handle_atr =iATR(_Symbol,PERIOD_CURRENT,period);

Период ATR может отличаться от имеющегося динамического периода, но вполне логично, если они будут просто совпадать, и количество параметров останется прежним.

Приведу код только вновь добавленных строк.

#property indicator_buffers 5 
#property indicator_plots   4
.............................
input double K =1;            //коэффициент масштаба
double Buff_ATR[];
int handle_atr;
.............................
SetIndexBuffer(4,Buff_ATR,INDICATOR_CALCULATIONS);
ArraySetAsSeries(Buff_ATR,true);
         
handle_atr =iATR(_Symbol,PERIOD_CURRENT,period);
.....................................................
int OnCalculate(){
.....................................................
  if(CopyBuffer(handle_atr,0,0,start+1,Buff_ATR)==-1)
  {
  return(0);
  Print("Не удалось скопировать данные в буфер ATR");
  }
.....................................................

//if trend ascending  
  if(trend>=0)
  {
  maxmin =close[ArrayMaximum(close,i,dyn_period)];
  value =maxmin-K*Buff_ATR[i];
  
  if(close[i]<value)
   {
   maxmin =close[i];
   value =maxmin+K*Buff_ATR[i];
   trend =-1;
   dyn_period =1;
   }
  }
 
}

Значения линий канала находятся как value = maxmin(+-)K*Buff_ATR[i], соответственно. Полный код индикатора — в прикреплённом файле NRTRvolatile.mq5.

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


Из рисунка видно, что при низкой волатильности и небольших значениях ATR линия NRTRvolatile практически "прилипает" к ценовому графику. Затем, при увеличении волатильности, она отходит от него.

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

Торговый модуль для Мастера MQL5

Часто подобные модули удобно писать методом copy/paste, используя уже готовые. Но в данном случае проще начать с самого начала, чем объяснять, где и что надо поправить или заменить.

Опишем общую структуру всех модулей.

  • Дескриптор модуля
  • Параметры торговли и функции для их инициализации
  • Проверка входных параметров
  • Привязка выбранного индикатора к данному модулю
  • Описание торговой стратегии

Прежде всего, в папке с сигналами лучше создать отдельную папку для собственных, "самописных" сигналов. Например, Include\Expert\MySignals. Щелкаем правой кнопкой мыши по выбранной папке и в контекстном меню выбираем пункт "Новый файл". Появится мастер MQL5. В меню выберем "Новый класс". Назовём его NRTRsignal. Все сигналы наследуются от базового класса CExpertSignal, укажем это в мастере.


В сгенерированный мастером код добавим местоположение базового класса CExpertSignal: #include "..\ExpertSignal.mqh"

//+------------------------------------------------------------------+
//|                                                   SignalNRTR.mqh |
//|                                                       Orangetree |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Orangetree"
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "..\ExpertSignal.mqh"                  // класс CExpertSignal 
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class SignalNRTR : public CExpertSignal
  {
private:

public:
                     SignalNRTR();
                    ~SignalNRTR();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
SignalNRTR::SignalNRTR()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
SignalNRTR::~SignalNRTR()
  {
  }
//+------------------------------------------------------------------+

Начало положено.

Теперь, чтобы Мастер MQL5 мог опознать наш код как модуль сигналов, создадим дескриптор нашего модуля по образу и подобию других сигналов.

// wizard description start
//+------------------------------------------------------------------+
//| Description of the class                                         |
//| Title=Signals of indicator 'NRTR'                                |
//| Type=SignalAdvanced                                              |
//| Name=NRTR                                                        |
//| ShortName=NRTR                                                   |
//| Class=SignalNRTR                                                 |
//| Page=????                                                        |
//| Parameter=PeriodDyn,int,12,Период динамического канала           |
//| Parameter=PercentDev,double,0.1,Ширина коридора в процентах      |
//+------------------------------------------------------------------+
// wizard description end
//+------------------------------------------------------------------+
//| Class SignalNRTR.                                                |
//| Purpose: Class of generator of trade signals based on            |
//|          the 'NRTR' indicator.                                   |
//| Is derived from the CExpertSignal class.                         |
//+------------------------------------------------------------------+

Начинается дескриптор со слов "wizard description start" и заканчивается словами " wizard description end". Внутри содержится название модуля и внешние параметры. Как только мы откомпилируем модуль вместе с дескриптором, в меню Мастера — Новый файл/Советник(сгенерировать)/Общие параметры/Параметры сигналов для советников/Добавить — появится наш модуль.

Модуль

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

Названия методов инициализации внешних параметров должны совпадать с названиями внешних параметров, приведенных в дескрипторе.

class SignalNRTR : public CExpertSignal
  {
protected:
   int m_period_dyn;                                   //Период канала
   double m_percent_dev;           //Ширина канала в процентах от цены
 
public:
                     SignalNRTR();
                    ~SignalNRTR();
   //--- methods of setting adjustable parameters
   void              PeriodDyn(int value)                 { m_period_dyn=value;}
   void              PercentDev(double value)             { m_percent_dev=value;}
   
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
SignalNRTR::SignalNRTR() : m_period_dyn(12),
                           m_percent_dev(0.1)
  {
  //--- initialization of protected data
   m_used_series=USE_SERIES_OPEN+USE_SERIES_HIGH+USE_SERIES_LOW+USE_SERIES_CLOSE;
  }

Члены класса инициализируются с помощью списка инициализации. Сгенерированное Мастером "private" можно заменить на "protected", но это необязательно.

Для проверки правильности ввода параметров в классе CExpertBase предназначен метод virtual bool ValidationSettings().

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

//+------------------------------------------------------------------+
//| метод  провереряет входные параметры                              |
//+------------------------------------------------------------------+
bool SignalNRTR:: ValidationSettings()
  {
   // вызываем метод базового класса
   if(!CExpertSignal::ValidationSettings())  return(false);
   
   // период должен быть больше 1
   if(m_period_dyn<2)
   {
   Print("Период должен быть больше 1");
   return false;
   }
   // Ширина коридора должна быть положительной
   if(m_percent_dev<=0)
   {
   Print("Ширина коридора должна быть положительной");
   return false;
   }
   
   return true;
  }

Обратите внимание на особенность: сначала вызывается метод базового класса.

Чтобы подключить к нашему модулю конкретный индикатор, используем метод InitIndicators(). Снова создаём прототип этого метода в нашем классе: virtual bool InitIndicators(CIndicators *indicators), а затем делаем его описание. Для этого существуют стандартные процедуры проверки указателя на индикатор и работает инициализация индикаторов и таймсерий в дополнительных фильтрах.

//+------------------------------------------------------------------+
//| Create indicators.                                               |
//+------------------------------------------------------------------+
bool SignalNRTR::InitIndicators(CIndicators *indicators)
   {
//--- check pointer
   if(indicators==NULL)
      return(false);
//--- initialization of indicators and timeseries of additional filters
   if(!CExpertSignal::InitIndicators(indicators))
      return(false);  
//--- create and initialize NRTR indicator
if(!InitNRTR(indicators))
      return(false);
//--- ok
   return(true);
   }

В строке InitNRTR(indicators) создаём и инициализируем наш индикатор. Надо добавить ещё прототип и описание функции InitNRTR(indicators).

//+------------------------------------------------------------------+
//| Create NRTR indicators.                                          |
//+------------------------------------------------------------------+  
bool SignalNRTR::InitNRTR(CIndicators *indicators)
   {
//--- check pointer
   if(indicators==NULL)
      return(false);
//--- add object to collection
   if(!indicators.Add(GetPointer(m_nrtr)))
     {
      printf(__FUNCTION__+": error adding object");
      return(false);
     }
//--- задание параметров NRTR
   MqlParam parameters[3];
//---
   parameters[0].type=TYPE_STRING;
   parameters[0].string_value="Orangetree\\NRTR.ex5";
   parameters[1].type=TYPE_INT;
   parameters[1].integer_value=m_period_dyn;      // период
   parameters[2].type=TYPE_DOUBLE;
   parameters[2].double_value=m_percent_dev;      // ширина канала
//--- initialize object
   if(!m_nrtr.Create(m_symbol.Name(),m_period,IND_CUSTOM,3,parameters))
     {
      printf(__FUNCTION__+": error initializing object");
      return(false);
     }
//--- ok
   return(true);   
   }

Индикатор создаётся с помощью структуры MqlParam и метода Create().

Все предварительные построения выполнены. Теперь необходимо написать алгоритм торговли. В этом нам помогут методы LongCondition() и ShortCondition(). Добавим методы для получения сигнала от индикатора.

   //--- methods of getting data
   double            UpSignal(int index)                   { return(m_nrtr.GetData(2,index));}
   double            DnSignal(int index)                   { return(m_nrtr.GetData(3,index));}

Функция GetData() получает значение буфера индикатора по его индексу и индексу бара. Сам наш индикатор уже содержит сигнал о смене тренда. Поэтому и условия для открытия позиции простые. Покупаем, если индикатор даёт сигнал "вверх" и продаём по сигналу "вниз" .

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int SignalNRTR::LongCondition(void)
   {
   int idx   =StartIndex();
   if(UpSignal(idx))
      return 100;
   else return 0;
   }
//+------------------------------------------------------------------+
//| "Voting" that price will fall.                                   |
//+------------------------------------------------------------------+
int SignalNRTR::ShortCondition(void)
   {
   int idx   =StartIndex();
   if(DnSignal(idx))
      return 100;
   else return 0;
   }

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

В описании функции virtual int StartIndex() говорится следующее: "Если флаг анализа текущего бара установлен, возвращается 0 (анализ будет производиться с текущего бара). Если флаг анализа текущего бара сброшен, возвращается 1 (анализ будет производиться с последнего сформировавшегося бара)." В нашей торговой системе используются только сигналы от сформировавшихся баров. Функция StartIndex() по умолчанию возвращает единицу — как раз в соответствии с нашей стратегией. В полученном затем на основе модуля эксперте это будет отражено в параметре Expert_EveryTick со значением false.

На этом создание модуля торговых сигналов завершено.

Для проверки правильности работы будем использовать тестер стратегий.


Заодно оптимизируем торговые параметры. Оптимизация проводилась за последний месяц 25.09.17 — 18.10.17 на паре EURUSD, таймфрейм H1. Оптимизировались только параметры индикатора: период и ширина канала. Стоп-лосс и тейк-профит = 0.


На рисунке показан результат для периода 48 и ширины канала 0.25%.

Комбинации NRTR + разные индикаторы тренда

Допустим, мы хотим подтверждения сигналов нашего индикатора. Так как он хорошо работает на тренде, то логично добавить в систему еще и индикатор тренда. В этой статье готовые модули сигналов Мастера MQL5. Поэтому продолжим работать по этой методике: будем добавлять к нашему эксперту трендовый индикатор через модули сигналов.

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

Все ступени создания модуля разобраны в предыдущем разделе. Значит, с этого момента удобнее использовать метод "copy/paste" и оценить его преимущества. Мы просто берем любой готовый модуль, и заменяем все необходимые строки.

Допустим, в разделе "Трендовые" мы выбрали ADX, который входит в коллекцию технических индикаторов. Для ссылки на него можно использовать специально зарезервированный для него указатель CiADX, а не указатель на пользовательский индикатор CiCustom. Соответственно, через метод Create его можно создать немного по-другому, при этом путь к нему можно не указывать.

protected:
   CiADX m_adx;                                    // object-indicator
   int m_period_adx;                               //Период ADX
....................... другой код.....................................

//--- initialize object
   if(!m_adx.Create(m_symbol.Name(),m_period,m_period_adx))
     {
      printf(__FUNCTION__+": error initializing object");
      return(false);
     }

У этого индикатора всего один параметр — период. Это надо отразить в дескрипторе модуля и в функциях установки параметров. Еще надо добавить функции получения значений буферов ADX.

//| Parameter=PeriodADX,int,14,Период индикатора ADX 
.....................................................

//--- methods of setting adjustable parameters
void              PeriodADX(int value)                { m_period_adx=value;}
.....................................................

//--- methods of getting data
double            MainADX(int index)                   { return(m_adx.Main(index));}
double            ValueIDPlus(int index)               { return(m_adx.Plus(index));}
double            ValueIDMinus(int index)              { return(m_adx.Minus(index));}

Одним словом, проходим все необходимые шаги, описанные в разделе создания торгового модуля. Везде, где надо, заменяем NRTR на ADX. Торговые условия просто берём из описания индикатора ADX, не проверяя их справедливость. В классическом варианте это:

  • Купить, если +DI >-DI и ADX растёт.
  • Продать, если +DI <-DI и ADX растёт.
//+------------------------------------------------------------------+
//| "Voting" that trend is "Down".                                   |
//+------------------------------------------------------------------+
int SignalADX::LongCondition(void)
   {
   int idx   =StartIndex();
   if(ValueIDPlus(idx)>ValueIDMinus(idx)&&MainADX(idx)>MainADX(idx+1))
      return (100);
   else
      return (0);
   }
//+------------------------------------------------------------------+
//| "Voting" that trend is "UP".                                    |
//+------------------------------------------------------------------+
int SignalADX::ShortCondition(void)
   {
   int idx   =StartIndex();
   if(ValueIDPlus(idx)<ValueIDMinus(idx)&&MainADX(idx)>MainADX(idx+1))
      return (100);
   else
      return (0);
   }

Это основной принцип. Мы даём сигнал на покупку, если считаем что индикатор показывает растущий тренд, и сигнал на продажу — если тренд падающий. Для удобства вес сигнала равен 100.

Теперь снова открываем мастер MQL5. При создании эксперта выбираем два торговых сигнала — SignalNTRTR и ADXTrendSignal. При наличии нескольких сигналов находится их среднее значение. Поэтому обоим сигналам присвоим весовые коэффициенты, равные единице. Соответственно, пороговое значение для открытия зададим 100. Все остальные параметры, кроме периода и ширины канала, обнулим. Включаем тестер стратегий и убеждаемся,что всё работает правильно.


Заключение

Подведём итоги. Мы рассмотрели трендовый индикатор прорыва динамического ценового канала NRTR. Разработаны две его версии: с фиксированным процентным отклонением линий тренда от экстремумов цены и с отклонением, зависящим от волатильности цен на рынке.

Оба варианта индикатора приложены к статье. На основе NRTR написан модуль торговых сигналов и с помощью Мастера MQL5 сгенерирован торговый советник.

Для примера использования NRTR вместе с индикаторами тренда по методике, изложенной выше, создан модуль для индикатора ADX. В мастере MQL5 сгенерирован тестовый советник на основе NRTR + ADX. Оптимизация комбинаций NRTR + трендовый индикатор не проводилась по причине того, что выбор трендового индикатора — вопрос личного предпочтения и является темой для отдельной статьи. Данный подход в своей основе имеет модульную, комбинационную философию.

При работе с прикрепленными файлами следует помнить, что пути к индикаторам и торговым сигналам нужно указывать в соответствии с тем, где установлен модуль или индикатор: например, в файле SignalNRTR, как у меня:

   parameters[0].string_value="NRTR.ex5";

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

Файлы:

#ИмяТипОписание
1NRTR.mq5ИндикаторИсходник рассматриваемого в статье индикатора
2NRTRvolatile.mq5ИндикаторИсходник индикатора, учитывающего волатильность цен
3SignalNRTR.mqhТорговый модульМодуль торговых сигналов. Используется для генерации советников в мастере MQL5
 ADXTrendSignal.mqhТорговый модуль Тестовый модуль трендового индикатора

В папке MQL5.zip файлы расположены в соответствии с директориями в MetaEditor.

Прикрепленные файлы |
NRTR.mq5 (11.02 KB)
NRTRvolatile.mq5 (11.87 KB)
SignalNRTR.mqh (14.17 KB)
ADXTrendSignal.mqh (12.52 KB)
MQL5.zip (8.58 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (7)
Boris Egorov
Boris Egorov | 8 нояб. 2017 в 15:44
Dmitrii Troshin:

В буферах 2,3 находятся сигналы они обновляются только в момент смены тренда.

Поддержка/Сопротивление находятся в буферах 0,1 - именно их мы видим в качестве линий

CopyBuffer() - имеет три перегрузки, но ни одна не имеет трех параметров

Вариант кода для получения значений буферов

Это скрипт


да стало понятней

насчет CopyBuffer - она из библиотеки совместимости с mql4 , точнее отсюда initmql4__1.mqh

double CopyBufferMQL4(int handle,int index,int shift)
  {
   double buf[];
   switch(index)
     {
      case 0: if(CopyBuffer(handle,0,shift,1,buf)>0)
         return(buf[0]); break;
      case 1: if(CopyBuffer(handle,1,shift,1,buf)>0)
         return(buf[0]); break;
      case 2: if(CopyBuffer(handle,2,shift,1,buf)>0)
         return(buf[0]); break;
      case 3: if(CopyBuffer(handle,3,shift,1,buf)>0)
         return(buf[0]); break;
      case 4: if(CopyBuffer(handle,4,shift,1,buf)>0)
         return(buf[0]); break;
      default: break;
     }
   return(EMPTY_VALUE);
  }
Boris Egorov
Boris Egorov | 8 нояб. 2017 в 15:56

наверно так

//+------------------------------------------------------------------+
//| NRTRvolatile                                                     |
//+------------------------------------------------------------------+
//iBufferNumber
//0
//1 
//2 - signal up
//3 - signal down
double indNRTRvolatile(string sSymbol=NULL,
                int tf=PERIOD_M5,
                int period=12,                  //динамический период
                double K=1,                       //коэффициент масштаба
                int iBufferNumber=0,
                int iShift=0)
{
   ENUM_TIMEFRAMES timeframe=TFMigrate(tf);
   double handle=iCustom(sSymbol,timeframe,"NRTRvolatile",period,K);
   
   if(handle<0)
     {
      Print("Объект NRTRvolatile не создан: Ошибка ",GetLastError());
      return(-1);
     }
   else
      return(CopyBufferMQL4(handle,iBufferNumber,iShift));

}

Boris Egorov
Boris Egorov | 10 янв. 2018 в 11:13

Можно ли проапгрейдить NRTRvolatile так, чтобы при добавлении стрелки выскакивало окошко с сообщением?

Dmitrii Troshin
Dmitrii Troshin | 10 янв. 2018 в 18:56
gedd:

Можно ли проапгрейдить NRTRvolatile так, чтобы при добавлении стрелки выскакивало окошко с сообщением?


Возможно в функциях LongCondition и ShortCondition 

int SignalNRTR::LongCondition(void)
   {
   int idx   =StartIndex();
   if(UpSignal(idx))
   { 
   Alert"Text";   
   return 100;
   }
   else return 0;
   }

добавить что-то вроде Alert"Text". Вот не знаю будет ли работать. Дело в том что у меня на компьютере уже нет этого кода. Всё что было в прошлом году я отставил в прошлом:) Поэтому попробовать не могу. Да и в голове ещё НГ звенит :)

Dmitrii Troshin
Dmitrii Troshin | 11 янв. 2018 в 11:43

В самом индикаторе где-то здесь

 // trend changes 
  
      if(trend>0) Buff_Up[i]=value;
      if(trend<0) Buff_Dn[i]=value;

      if(trend_prev<0  &&  trend>0) 
      {
      Alert("Text");
      Sign_Up[i]=value;
      Buff_Up[i]=0.0;
      }
      if(trend_prev>0 && trend<0)
      {
      Alert("Text");
      Sign_Dn[i]=value;
      Buff_Dn[i]=0.0;
      }
Оценка риска в последовательности сделок с одним активом. Продолжение Оценка риска в последовательности сделок с одним активом. Продолжение
Статья развивает идеи, предложенные в предыдущей части и продолжает их рассмотрение. Описаны вопросы распределения доходностей, построения и изучения статистических закономерностей.
R-квадрат как оценка качества кривой баланса стратегии R-квадрат как оценка качества кривой баланса стратегии
Статья описывает построение пользовательского критерия оптимизации R-квадрат. По этому критерию можно оценить качество кривой баланса стратегии и выбрать наиболее равномерно растущие и стабильные стратегии. Материал описывает принципы его построения и статистические методы, используемые для оценки свойств и качества этой метрики.
Кроссплатформенный торговый советник: Классы CExpertAdvisor и CExpertAdvisors Кроссплатформенный торговый советник: Классы CExpertAdvisor и CExpertAdvisors
В заключительной статье серии о кроссплатформенном торговом советнике речь пойдет о классах CExpertAdvisor и CExpertAdvisors, которые служат контейнерами для всех ранее описанных компонентов эксперта. Также рассмотрена реализация отслеживания новых баров и сохранения данных.
Кроссплатформенный торговый советник: Пользовательские стопы, Безубыток и Трейлинг Кроссплатформенный торговый советник: Пользовательские стопы, Безубыток и Трейлинг
В статье обсуждается установка пользовательских стоп-уровней в кроссплатформенном советнике. Также описан тесно связанный с ними метод, который помогает задать изменение стоп-уровней с течением времени.