Скачать MetaTrader 5

Рецепты MQL5 - Программируем скользящие каналы

10 марта 2016, 12:49
Dennis Kirichenko
8
5 023

Введение

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

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



1. Основы построения равноудалённого канала

Для начала поработаем со схемами, которые будут основой для программирования равноудалённого канала. Здесь я порекомендовал бы обратиться к справке по инструменту технического анализа «Равноудалённый канал».   

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

При отрисовке канала не используются ни лучи влево, ни лучи вправо, если не сказано иное.

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

Рис.1 Первый тип набора точек, схема

Рис.1 Первый тип набора точек, схема

На ценовом графике первый тип выглядит следующим образом (Рис.2).

Рис.2 Первый тип набора точек, ценовой график

Рис.2 Первый тип набора точек, ценовой график

Второй тип — случай, когда на графике последовательно появляются максимум, минимум и снова минимум (Рис.3).

Рис.3 Второй тип набора точек, схема

Рис.3 Второй тип набора точек, схема

Локальный максимум, появляющийся вначале, станет в итоге третьей точкой. Следующая за ним пара минимумов образует основную линию.

Третий тип строится по схеме «минимум-минимум-максимум». В этом случае основная линия ждёт, когда будет сформирован локальный максимум (Рис.4).

Рис.4 Третий тип набора точек, схема

Рис.4 Третий тип набора точек, схема

Два последних типа — скорее частные случаи.  

Четвёртый вариант получается, когда третья и первая точки совпадают по времени построения. (Рис.5).

Рис.5 Четвёртый тип набора точек, схема

Рис.5 Четвёртый тип набора точек, схема

И пятый тип – случай, когда совпадают временные координаты второй и третьей точек (Рис.6).

Рис.6 Пятый тип набора точек, схема

Рис.6 Пятый тип набора точек, схема


С пятью такими типами равноудалённых каналов нам и предстоит работать. В следующем разделе постараемся запрограммировать точки, по которым строятся линии канала.



2. Вспомогательные типы данных

Чаще всего точки, которые берутся для отображения трендовых линий канала, — это фракталы. Тогда такая точка является одновременно и фракталом, и основой для проведения прямой линии.

Попробуем обобщить и закодировать фрактальные точки с помощью ООП-инструментария.


2.1 Класс фрактальной точки

Функционал этого класса — отвечать за точку, входящую в число тех, по которым строится равноудалённый канал.

Назовём указанный класс CFractalPoint и, в лучших традициях языка MQL5, привяжем его отношением наследования к классу-интерфейсу CObject.

//+------------------------------------------------------------------+
//| Класс фрактальной точки                                          |
//+------------------------------------------------------------------+
class CFractalPoint : public CObject
  {
   //--- === Data members === --- 
private:
   datetime          m_date;           // дата и время
   double            m_value;          // значение
   ENUM_EXTREMUM_TYPE m_extreme_type;  // тип экстремума
   int               m_idx;            // индекс (от 0 до 2)

   //--- === Methods === --- 
public:
   //--- конструктор/деструктор
   void              CFractalPoint(void);
   void              CFractalPoint(datetime _date,double _value,
                                   ENUM_EXTREMUM_TYPE _extreme_type,int _idx);
   void             ~CFractalPoint(void){};
   //--- get-методы
   datetime          Date(void) const {return m_date;};
   double            Value(void) const {return m_value;};
   ENUM_EXTREMUM_TYPE FractalType(void) const {return m_extreme_type;};
   int               Index(void) const {return m_idx;};
   //--- set-методы
   void              Date(const datetime _date) {m_date=_date;};
   void              Value(const double _value) {m_value=_value;};
   void              FractalType(const ENUM_EXTREMUM_TYPE extreme_type) {m_extreme_type=extreme_type;};
   void              Index(const int _bar_idx){m_idx=_bar_idx;};
   //--- сервис
   void              Copy(const CFractalPoint &_source_frac);
   void              Print(void);
  };
//+------------------------------------------------------------------+

Класс имеет 4 члена для передачи данных:

  1. m_date — временная координата точки на графике;
  2. m_value — ценовая координата точки на графике;
  3. m_extreme_type –  тип экстремума;
  4. m_idx – индекс.

За тип экстремума будет отвечать перечисление ENUM_EXTREMUM_TYPE:

//+------------------------------------------------------------------+
//| Тип экстремума                                                   |
//+------------------------------------------------------------------+
enum ENUM_EXTREMUM_TYPE
  {
   EXTREMUM_TYPE_MIN=0, // минимум
   EXTREMUM_TYPE_MAX=1, // максимум
  };

Основная задача методов класса CFractalPoint — обеспечивать получение или обновление значений приватных членов, перечисленных выше.

К примеру, создадим программным образом фрактальную точку для указанной на Рис.7 свечи от 26.01.2016 08:00 на графике EURUSD, H4. Фрактал образовался на максимуме свечи по цене 1,08742.

Рис.7 Пример фрактала

Рис.7 Пример фрактала

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

//--- данные для фрактальной точки
   datetime pnt_date=D'26.01.2016 08:00';
   double pnt_val=1.08742;
   ENUM_EXTREMUM_TYPE pnt_type=EXTREMUM_TYPE_MAX;
   int pnt_idx=0;
//--- создание фрактальной точки
   CFractalPoint myFracPoint(pnt_date,pnt_val,pnt_type,pnt_idx);
   myFracPoint.Print();

В журнале получим такую распечатку:

---=== Данные фрактальной точки ===---
Дата: 2016.01.26 08:00
Цена: 1.08742
Тип: EXTREMUM_TYPE_MAX
Индекс: 0

Такая запись означает, что фрактальная точка была найдена на баре от 26 января 2016 по цене 1,08742. Этот фрактал является локальным максимумом. Нулевой индекс указывает, что в наборе аналогичных точек данная будет первой.


2.2 Класс набора фрактальных точек

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

Данный класс будет включён в состав советника, а не индикатора, поэтому каналы будут относиться не к буферам индикатора, а к графическим объектам типа  CChartObjectChannel.

Класс CFractalSet — потомок класса Стандартной библиотеки CArrayObj. Я выбрал защищенный тип наследования, чтобы сделать интерфейс класса узкоспециализированным.

//+------------------------------------------------------------------+
//| Класс набора фрактальных точек                                   |
//+------------------------------------------------------------------+
class CFractalSet : protected CArrayObj
  {
   //--- === Data members === --- 
private:
   ENUM_SET_TYPE     m_set_type;           // тип набора точек
   int               m_fractal_num;        // фиксированное число точек
   int               m_fractals_ha;        // хэндл фрактального индикатора 
   CisNewBar         m_new_bar;            // объект нового бара
   CArrayObj         m_channels_arr;       // объект массива указателей
   color             m_channel_colors[4];  // цвета каналов
   bool              m_is_init;            // флаг инициализации
   //--- настройки канала
   int               m_prev_frac_num;      // предыдущих фракталов
   int               m_bars_beside;        // баров слева/справа от фрактала
   int               m_bars_between;       // число промежуточных баров  
   bool              m_to_delete_prev;     // удалять предыдущие каналы?
   bool              m_is_alt;             // альтернативный индикатор фракталов?
   ENUM_RELEVANT_EXTREMUM m_rel_frac;      // актуальная точка
   bool              m_is_array;           // отрисовывать луч?
   int               m_line_wid;           // толщина линии
   bool              m_to_log;             // вести журнал?

   //--- === Methods === --- 
public:
   //--- конструктор/деструктор
   void              CFractalSet(void);
   void              CFractalSet(const CFractalSet &_src_frac_set);
   void             ~CFractalSet(void){};
   //---
   void              operator=(const CFractalSet &_src_frac_set);
   //--- обработчики
   bool              Init(
                          int _prev_frac_num,
                          int _bars_beside,
                          int _bars_between=0,
                          bool _to_delete_prev=true,
                          bool _is_alt=false,
                          ENUM_RELEVANT_EXTREMUM _rel_frac=RELEVANT_EXTREMUM_PREV,
                          bool _is_arr=false,
                          int _line_wid=3,
                          bool _to_log=true
                          );
   void              Deinit(void);
   void              Process(void);
   //--- сервис
   CChartObjectChannel *GetChannelByIdx(const int _ch_idx);
   int               ChannelsTotal(void) const {return m_channels_arr.Total();};

private:
   int               AddFrac(const int _buff_len);
   int               CheckSet(const SFracData &_fractals[]);
   ENUM_SET_TYPE     GetTypeOfSet(void) const {return m_set_type;};
   void              SetTypeOfSet(const ENUM_SET_TYPE _set_type) {m_set_type=_set_type;};
   bool              PlotChannel(void);
   bool              Crop(const uint _num_to_crop);
   void              BubbleSort(void);
  };
//+------------------------------------------------------------------+


Перечислим члены для этого класса. 

  1. m_set_type – тип набора точек. Ниже рассмотрим перечисление, отвечающее за классификацию наборов;
  2. m_fractal_num – фиксированное число точек, попадающих в набор;
  3. m_fractals_ha – хэндл фрактального индикатора;
  4. m_new_bar – объект нового бара;
  5. m_channels_arr – объект массива указателей;
  6. m_channel_colors[4] — массив цветов для отображения каналов;
  7. m_is_init — флаг инициализации.
    Затем идёт блок членов, отвечающих за настройки канала.
  8. m_prev_frac_num — число предыдущих фракталов, используемых для построения самого первого канала. Если точек будет 3, то канал построится сразу после инициализации;  
  9. m_bars_beside — число баров слева/справа от фрактала. Если указать, например, 5, то всего для поиска фрактала будут использоваться 11 баров;
  10. m_bars_between — число промежуточных баров. Т.е. это своего рода минимум баров, которые должны присутствовать между соседними фрактальными точками;
  11. m_to_delete_prev — разрешение на удаление  предыдущих  каналов;
  12. m_is_alt — флаг использования альтернативного индикатора фракталов;
  13. m_rel_frac — выбор актуальной точки. Если промежуточных баров было недостаточно, то тип этой точки укажет, какой из баров нужно пропустить;
  14. m_is_array — флаг отрисовки луча;
  15. m_line_wid — толщина линии;
  16. m_to_log — флаг логирования.

Перечисление, которое обрабатывает типы набора точек, представлено так:

//+------------------------------------------------------------------+
//| Тип набора экстремальных точек                                   |
//+------------------------------------------------------------------+
enum ENUM_SET_TYPE
  {
   SET_TYPE_NONE=0,     // не задан
   SET_TYPE_MINMAX=1,   // мин-макс-мин
   SET_TYPE_MAXMIN=2,   // макс-мин-макс                       
  };

В этом примере значение SET_TYPE_MAXMIN соответствует такой очерёдности фрактальных точек: максимальная, потом минимальная, и снова максимальная (Рис.8).

Рис.2 Набор типа «макс-мин-макс»

Рис.8 Набор типа «макс-мин-макс»

Оговорюсь сразу, что очерёдность точек соблюдать получается не всегда. Бывает так, что за одним минимумом следует второй. В качестве примера можно вспомнить третий тип набора точек, описанный в первом разделе (Рис.4). В любом случае, будем считать, что набор полноценный, если в нём есть либо пара минимумов и максимум, либо пара максимумов и минимум.

Перечисление, которое обрабатывает типы актуальной точки, имеет такой вид:

//+------------------------------------------------------------------+
//| Тип актуальной точки                                             |
//+------------------------------------------------------------------+
enum ENUM_RELEVANT_EXTREMUM
  {
   RELEVANT_EXTREMUM_PREV=0, // предыдущая
   RELEVANT_EXTREMUM_LAST=1, // последняя
  };

Перейдем к методам. Сначала перечислим обработчики.

  1. Init() – проводит инициализацию набора. Метод отвечает за корректное начало работы объекта, представляющего набор фрактальных точек.
  2. Deinit() - проводит деинициализацию набора.
  3. Process() – контролирует ценовой поток. По сути, именно этот метод идентифицирует точки и отображает канал.

Сервисные методы:

  1. AddFrac() — добавляет в набор фрактальные точки.
  2. CheckSet() проверяет текущее состояние набора.
  3. PlotChannel() – отрисовывает равноудалённый канал.
  4. Crop() – подрезает набор.
  5. BubbleSort() — сортирует точки в наборе по времени их появления.

2.3 Дополнительные возможности построения канала

Ещё раз напомню, что при построении канала и обращении к его свойствам использовался класс Стандартной библиотеки CChartObjectChannel. Рассмотрим некоторые моменты, алгоритмическая реализация которых может сделать систему автоматического построения каналов более гибкой.


2.3.1 Синхронизация линий

Удобнее всего визуально оценивать график с каналами в тот момент, когда обе линии канала начинаются на одном и том же баре. Формально такому подходу соответствует четвёртый тип канала (Рис.5). Естественно, что каналы могут относиться и к другим типам. Поэтому в методе CFractalSet::PlotChannel() ценовые и временные координаты фрактальных точек модифицируются, чтобы тип канала стал четвёртым. При этом важно (это реализовано), чтобы сохранялся наклон канала и его ширина.

Рассмотрим следующий равноудалённый канал на ценовом графике (Рис.9).

Рис.9 Равноудалённый канал по исходным точкам

Рис.9 Равноудалённый канал по исходным точкам

Оговорюсь сразу, что он строился вручную. Здесь есть такие фрактальные точки:

  1. $1.05189 от 2015.12.03 (минимум);
  2. $1.07106 от 2016.01.05 (минимум);
  3. $1.10594 от 2016.01.05 (максимум).

Если отобразить подобный канал с помощью класса CFractalSet, то получим такую картину (Рис.10).


Рис.10 Равноудалённый канал по расчётным точкам

Рис.10 Равноудалённый канал по расчётным точкам

Различия, пусть и незначительные, заключаются в том, что на Рис.10 построение канала идёт по расчётным точкам. Рассчитывается ценовое и временное значения второй и третьей точек. Последняя точка должна совпадать  по координате времени с первой.

Задачу отрисовки канала по расчётным точкам я разбиваю на 2 части.

Первая часть касается временных координат — определяются начало и конец канала. В указанном методе есть такой блок кода:

//--- 1) координаты времени
//--- начало канала
int first_date_idx=ArrayMinimum(times);
if(first_date_idx<0)
  {
   Print("Ошибка получения координаты времени!");
   m_channels_arr.Delete(m_channels_arr.Total()-1);
   return false;
  }
datetime first_point_date=times[first_date_idx];
//--- конец канала
datetime dates[];
if(CopyTime(_Symbol,_Period,0,1,dates)!=1)
  {
   Print("Ошибка получения времени последнего бара!");
   m_channels_arr.Delete(m_channels_arr.Total()-1);
   return false;
  }
datetime last_point_date=dates[0];

Таким образом, все точки будут иметь такие значения координат времени:

//--- итоговые координаты времени 
times[0]=times[2]=first_point_date;
times[1]=last_point_date;

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

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

//--- 2) координаты цен
//--- 2.1 наклон линии
//--- баров между 1-ой и 2-ой точками
datetime bars_dates[];
int bars_between=CopyTime(_Symbol,_Period,
                          times[0],times[1],bars_dates
                          );
if(bars_between<2)
  {
   Print("Ошибка получения числа баров между точками!");
   m_channels_arr.Delete(m_channels_arr.Total()-1);
   return false;
  }
bars_between-=1;
//--- общий дифференциал
double price_differential=MathAbs(prices[0]-prices[1]);
//--- ценовая скорость (ценовое изменение на 1-ом баре)
double price_speed=price_differential/bars_between;
//--- направление канала
bool is_up=(prices[0]<prices[1]);


Теперь можно обновить ценовые координаты точек. Здесь важно знать, какая точка была образована раньше. Кроме того, нужно знать, куда направлен канал — вверх или вниз:

//--- 2.2 новая цена 1-й или 3-ей точки  
if(times[0]!=times[2])
  {
   datetime start,end;
   start=times[0];
   end=times[2];
//--- если 3-я точка раньше чем 1-я
   bool is_3_point_earlier=false;
   if(times[2]<times[0])
     {
      start=times[2];
      end=times[0];
      is_3_point_earlier=true;
     }
//--- баров между 1-ой и 3-ей точками
   int bars_between_1_3=CopyTime(_Symbol,_Period,
                                 start,end,bars_dates
                                 );
   if(bars_between_1_3<2)
     {
      Print("Ошибка получения числа баров между точками!");
      m_channels_arr.Delete(m_channels_arr.Total()-1);
      return false;
     }
   bars_between_1_3-=1;

//--- если восходящий канал
   if(is_up)
     {
      //--- если 3-я точка была раньше
      if(is_3_point_earlier)
         prices[0]-=(bars_between_1_3*price_speed);
      else
         prices[2]-=(bars_between_1_3*price_speed);
     }
//--- или если нисходящий канал
   else
     {
      //--- если 3-я точка была раньше
      if(is_3_point_earlier)
         prices[0]+=(bars_between_1_3*price_speed);
      else
         prices[2]+=(bars_between_1_3*price_speed);
     }
  }
В нашем примере раньше образовалась первая точка, значит, нужно обновить цену третьей.

И в завершение обновляем координаты второй точки:

//--- 2.3 новая цена 2-ой точки 
if(times[1]<last_point_date)
  {
   datetime dates_for_last_bar[];
//--- баров между 2-ой точкой и последним баром
   bars_between=CopyTime(_Symbol,_Period,times[1],last_point_date,dates_for_last_bar);
   if(bars_between<2)
     {
      Print("Ошибка получения числа баров между точками!");
      m_channels_arr.Delete(m_channels_arr.Total()-1);
      return false;
     }
   bars_between-=1;
//--- если восходящий канал
   if(is_up)
      prices[1]+=(bars_between*price_speed);
//--- или если нисходящий канал
   else
      prices[1]-=(bars_between*price_speed);
  }

Тогда получим:

  1. $1.05189 от 2015.12.03 (минимум);
  2. $1.10575 от 2016.02.26 (расчётное значение);
  3. $1.09864 от 2015.12.03 (расчётное значение).

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


2.3.2 Учёт прошлых фрактальных точек

В класс CFractalSet добавлена опция обращения в историю и поиск там фрактальных точек по заданным параметрам. Такая возможность используется только при инициализации экземпляра класса. Вспомним, что за число "точек из прошлого" (их может быть от 0 до 3) отвечает член m_prev_frac_num.

Рассмотрим пример (Рис.11). Допустим, что сразу же при инициализации советника TestChannelEA нужно найти несколько фрактальных точек на графике. Такими могут быть фракталы, обозначенные соответствующими цифрами.

Рис.9 Фрактальные точки при инициализации

Рис.11 Фрактальные точки при инициализации

Если возьмём все три точки, то построим канал (Рис.12).

Рис.10 Первый канал, построенный при инициализации

Рис.12 Первый канал, построенный при инициализации


В журнале видим запись:

2016.02.25 15:49:23.248 TestChannelEA (EURUSD.e,H4)     Добавлено предыдущих фракталов: 3

Несложно заметить, что точки добавляются в набор справа налево. А канал строится по точкам, которые должны собираться слева направо. Приватный метод сортировки  CFractalSet::BubbleSort() как раз и позволяет упорядочить точки до отрисовки самого канала.

Блок кода, отвечающий за набор точек при инициализации в методе CFractalSet::Init(), представлен следующим образом:

//--- если добавить предыдущие фрактальные точки
if(m_prev_frac_num>0)
  {
//--- 1) Загрузка истории [start]
   bool synchronized=false;
//--- счетчик цикла
   int attempts=0;
//--- 10 попыток дождаться синхронизации
   while(attempts<10)
     {
      if(SeriesInfoInteger(_Symbol,0,SERIES_SYNCHRONIZED))
        {
         synchronized=true;
         //--- есть синхронизация, выходим
         break;
        }
      //--- увеличим счетчик
      attempts++;
      //--- подождем 50 миллисекунд до следующей итерации
      Sleep(50);
     }
//---
   if(!synchronized)
     {
      Print("Не удалось получить количество баров на ",_Symbol);
      return false;
     }
   int curr_bars_num=Bars(_Symbol,_Period);
   if(curr_bars_num>0)
     {
      PrintFormat("Количество баров в истории терминала по символу-периоду на данный момент: %d",
                  curr_bars_num);
     }
//--- 1) Загрузка истории [end]

//--- 2) Рассчитанные данные для запрашиваемого индикатора [start]
   double Ups[];
   int i,copied=CopyBuffer(m_fractals_ha,0,0,curr_bars_num,Ups);
   if(copied<=0)
     {
      Sleep(50);
      for(i=0;i<100;i++)
        {
         if(BarsCalculated(m_fractals_ha)>0)
            break;
         Sleep(50);
        }
      copied=CopyBuffer(m_fractals_ha,0,0,curr_bars_num,Ups);
      if(copied<=0)
        {
         Print("Не удалось скопировать верхние фракталы. Error = ",GetLastError(),
               "i=",i,"    copied= ",copied);
         return false;
        }
      else
        {
         if(m_to_log)
            Print("Удалось скопировать верхние фракталы.",
                  " i = ",i,"    copied = ",copied);
        }
     }
   else
     {
      if(m_to_log)
         Print("Удалось скопировать верхние фракталы. ArraySize = ",ArraySize(Ups));
     }
//--- 2) Рассчитанные данные для запрашиваемого индикатора [end]

//--- 3) Добавление фрактальных точек [start]
   int prev_fracs_num=AddFrac(curr_bars_num-1);
   if(m_to_log)
      if(prev_fracs_num>0)
         PrintFormat("Добавлено предыдущих фракталов: %d",prev_fracs_num);
//--- если можно тобразить канал
   if(prev_fracs_num==3)
      if(!this.PlotChannel())
         Print("Не удалось отобразить канал!");
//--- 3) Добавление фрактальных точек [end]
  }

Его можно разделить на 3 подблока:

  1. загрузка истории котировок;
  2. обсчёт данных фрактального индикатора;
  3. добавление фрактальных точек в набор.

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

2.3.3 Учёт баров между соседними фрактальными точками

На предыдущих графиках используемые фрактальные точки (первая и вторая, а также третья и четвёртая) расположены рядом друг с другом. Можно добавить своего рода фильтр, который будет отсеивать ближайшие точки. Таким может выступать член  m_bars_between - число промежуточных баров между соседними точками. Если задать это число равным 1, то вторая точка не попадёт в набор, и ее место займет та, которая на данный момент третья.

Рис.11 Первый канал с учётом промежуточных баров

 Рис.13 Первый канал с учётом промежуточных баров

Построим канал при условии, что между соседними фрактальными точками будет как минимум 1 бар (Рис.13). Получается, что нужно пропустить точку, следующую за первой, и точку, следующую за второй. Они выделены жёлтыми ярлычками.

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

2016.02.25 16:11:48.037 TestChannelEA (EURUSD.e,H4)     Предыдущая точка будет пропущена: 2016.02.24 12:00
2016.02.25 16:11:48.037 TestChannelEA (EURUSD.e,H4)     Промежуточных баров недостаточно. Будет пропущена одна точка.

Тогда искомый канал станет узким и, скорее всего, не особенно функциональным с точки зрения трейдера.

Что касается кода, то проверка на допустимое число промежуточных баров осуществляется в теле приватного метода CFractalSet::CheckSet().

//--- если проверять число баров между последней и текущей точками
if(m_bars_between>0)
  {
   curr_fractal_num=this.Total();
   if(curr_fractal_num>0)
     {
      CFractalPoint *ptr_prev_frac=this.At(curr_fractal_num-1);
      if(CheckPointer(ptr_prev_frac)!=POINTER_DYNAMIC)
        {
         Print("Ошибка получения объекта фрактальной точки из набора!");
         return -1;
        }
      datetime time1,time2;
      time1=ptr_prev_frac.Date();
      time2=ptr_temp_frac.Date();
      //--- баров между точками
      datetime bars_dates[];
      int bars_between=CopyTime(_Symbol,_Period,
                                time1,time2,bars_dates
                                );
      if(bars_between<0)
        {
         Print("Ошибка получения данных времени открытия баров!");
         return -1;
        }
      bars_between-=2;
      //--- если на разных барах
      if(bars_between>=0)
         //--- если промежуточных баров недостаточно 
         if(bars_between<m_bars_between)
           {
            bool to_delete_frac=false;
            if(m_to_log)
               Print("Промежуточных баров недостаточно. Будет пропущена одна точка.");

            // ...

           }
     }
  }

Переменная bars_between принимает число баров между двумя соседними фрактальными точками. Если её значение меньше допустимого, то точка пропускается. Какая именно — текущая либо предыдущая — мы узнаем из следующего раздела.

2.3.4 Выбор актуальной фрактальной точки

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

Рис.12 Первый канал с учётом промежуточных баров и предыдущей актуальной точки

Рис.14 Первый канал с учётом промежуточных баров и предыдущей актуальной точки

Например, для первой пропущенной точки в журнале получим такую запись:

2016.02.25 16:46:06.212 TestChannelEA (EURUSD.e,H4)     Текущая точка будет пропущена: 2016.02.24 16:00
2016.02.25 16:46:06.212 TestChannelEA (EURUSD.e,H4)     Промежуточных баров недостаточно. Будет пропущена одна точка.

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

Если заглянем в код (а это всё тот же блок кода в теле приватного метода CFractalSet::CheckSet()), то увидим, что на поведение метода влияют два фактора: выбранный тип актуальной точки и флаг инициализации.

//--- если промежуточных баров недостаточно 
if(bars_between<m_bars_between)
  {
   bool to_delete_frac=false;
   if(m_to_log)
      Print("Промежуточных баров недостаточно. Будет пропущена одна точка.");
//--- если актуальна предыдущая точка
   if(m_rel_frac==RELEVANT_EXTREMUM_PREV)
     {
      datetime curr_frac_date=time2;
      //--- если была инициализация
      if(m_is_init)
        {
         continue;
        }
      //--- если инициализации не было
      else
        {
         //--- удалить текущую точку
         to_delete_frac=true;
         curr_frac_date=time1;
        }
      if(m_to_log)
        {
         PrintFormat("Текущая точка будет пропущена: %s",
                     TimeToString(curr_frac_date));
        }
     }
//--- если актуальна последняя точка
   else
     {
      datetime curr_frac_date=time1;
      //--- если была инициализация
      if(m_is_init)
        {
         //--- удалить предыдущую точку
         to_delete_frac=true;
        }
      //--- если инициализации не было
      else
        {
         curr_frac_date=time2;
        }
      if(m_to_log)
         PrintFormat("Предыдущая точка будет пропущена: %s",
                     TimeToString(curr_frac_date));
      if(curr_frac_date==time2)
         continue;

     }
//--- если удалить точку
   if(to_delete_frac)
     {
      if(!this.Delete(curr_fractal_num-1))
        {
         Print("Ошибка удаления последней точки в наборе!");
         return -1;
        }
     }
  }

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

3. Автоматическое построение скользящих каналов

Чтобы протестировать отрисовку каналов, была создана версия советника под названием ChannelsPlotter. Результат работы советника представлен на Рис.15. Очевидно, что на основе обычных фракталов и при отсутствии явного тренда на рынке каналы начинают "мельтешить". Поэтому была добавлена возможность использовать альтернативный индикатор фракталов, где задаётся любое другое число соседних с экстремумом баров. Сам индикатор X-bars Fractals был заимствован из базы исходных кодов.

Рис.9 Скользящие каналы по обычным фракталам

Рис.15 Скользящие каналы по обычным фракталам

Если запустить советник с выбором альтернативного индикатора фракталов, то удовлетворительный результат дает увеличение в нём числа баров, образующих группу для определения экстремума. Так, если будем искать фрактал в группе из 23 баров, то картинка может быть такой, как на Рис.16.

Рис.10 Скользящие каналы по альтернативным фракталам
Рис.16 Скользящие каналы по альтернативным фракталам


Таким образом, чем меньше соседних баров участвуют в определении фрактала, тем больше будет "канального" шума на ценовом графике.



Заключение

В этой статье я постарался представить способ программирования системы равноудалённых каналов. Были рассмотрены некоторые нюансы построения каналов. За основу была взята идея Виктора Баришпольца. В следующей статье мы рассмотрим торговые сигналы, генерируемые скользящими каналами.

Расположение файлов:

На мой взгляд, удобнее всего создать и хранить файлы в папке проекта. Например, путь может быть таким: <каталог_данных>\MQL5\Projects\ChannelsPlotter. Не забудьте откомпилировать альтернативный фрактальный индикатор X-bars_Fractals. Исходный файл индикатора должен находиться в папке индикаторов <каталог_данных>\MQL5\Indicators.
Прикрепленные файлы |
CFractalPoint.mqh (41.23 KB)
CisNewBar.mqh (7.64 KB)
Andrey F. Zelinsky
Andrey F. Zelinsky | 10 мар 2016 в 16:36
Dennis Kirichenko:

Из описания стратегии Баришпольца:

...поэтому их и называют скользящие... 

интересно, а нескользящие каналы есть? или Баришпольц просто поумничал с терминологией?
Dennis Kirichenko
Dennis Kirichenko | 10 мар 2016 в 16:42
Andrey F. Zelinsky:
интересно, а нескользящие каналы есть? или Баришпольц просто поумничал с терминологией?
Хороший вопрос... если делать отрисовку каналов таким образом, что у каналов не будет общих точек, то наверное это и будут нескользящие :-))
Dmitry Fedoseev
Dmitry Fedoseev | 10 мар 2016 в 20:50
Andrey F. Zelinsky:
интересно, а нескользящие каналы есть? или Баришпольц просто поумничал с терминологией?

Интересней как же должен выглядеть скользящий канал. Если по аналогии со средней - средняя это точка. Значит скользящая средняя - это много точек. Канал это две линии, значит скользящий канал это много пар линий. Хотя, опять же по аналогии со средней, канал, это две точки, значит скользящий канал это две линии (типа Болинджера). С третье же стороны, скользящим каналом можно назвать канал из двух линий, который автоматически перемещается и перерисовывается по мере появления новых баров. Незнаю кому как, мне больше третий вариант нравится. В чем же скользявость у скользящих средних не совсем понятно. 

Sergey Pavlov
Sergey Pavlov | 11 мар 2016 в 04:05
Dmitry Fedoseev:

Интересней как же должен выглядеть скользящий канал. Если по аналогии со средней - средняя это точка. Значит скользящая средняя - это много точек. Канал это две линии, значит скользящий канал это много пар линий. Хотя, опять же по аналогии со средней, канал, это две точки, значит скользящий канал это две линии (типа Болинджера). С третье же стороны, скользящим каналом можно назвать канал из двух линий, который автоматически перемещается и перерисовывается по мере появления новых баров. Незнаю кому как, мне больше третий вариант нравится. В чем же скользявость у скользящих средних не совсем понятно. 

Не хотелось бы уходить от темы статьи рассуждением о "скользящих", но скользить можно по плавной поверхности (линии, каналу), а не по ступенькам "против шерсти".

===

Автору за статью спасибо. 

Alexander
Alexander | 14 май 2016 в 19:26
 В конце статьи автор обещал  "В следующей статье мы рассмотрим торговые сигналы, генерируемые скользящими каналами." А будет следующая статья?

Графические интерфейсы II: Элемент "Главное меню" (Глава 4) Графические интерфейсы II: Элемент "Главное меню" (Глава 4)

Это завершающая глава второй части серии о графических интерфейсах. В ней мы рассмотрим создание такого элемента управления, как «Главное меню». Будет продемонстрирован процесс его разработки и настройка обработчиков классов библиотеки для правильной реакции на действия пользователя. Также мы рассмотрим, как подключить к пунктам главного меню контекстные меню. Кроме того, затронем тему блокировки неактивных на текущий момент элементов.

Графические интерфейсы II: Настройка обработчиков событий библиотеки (Глава 3) Графические интерфейсы II: Настройка обработчиков событий библиотеки (Глава 3)

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

Графические интерфейсы III: Простые и многофункциональные кнопки (Глава 1) Графические интерфейсы III: Простые и многофункциональные кнопки (Глава 1)

Приступим к рассмотрению такого элемента управления, как кнопка. Будут показаны примеры нескольких классов для создания простой кнопки, кнопок с расширенным функционалом («кнопка с картинкой» и «сдвоенная кнопка»), а также кнопок, которые взаимосвязаны между собой (группы кнопок и «радио-кнопки»). Кроме этого, внесём некоторые дополнения в уже существующие классы элементов управления, чтобы расширить их возможности.

В MetaTrader 5 добавлена хеджинговая система учета позиций В MetaTrader 5 добавлена хеджинговая система учета позиций

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