Скачать MetaTrader 5

Построение каналов - взгляд изнутри и снаружи

30 ноября 2010, 16:30
Dmitriy Skub
13
5 513

Введение

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

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

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

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


Задание экстремумов

Фактически, положение канала на графике определяется, как минимум, тремя экстремумами. Если давать определение экстремума то можно принять следующее: это максимальное или минимальное значение функции на заданном множестве. Точка, в которой достигается экстремум, называется точкой экстремума. Соответственно, если достигается минимум — точка экстремума называется точкой минимума, а если максимум — точкой максимума.

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

Для целей построения каналов нас интересуют именно локальные экстремумы. Не вдаваясь в математические формулы, проиллюстрируем это понятие графически. На приведенном ниже рис.1 отмечены три локальных экстремума красными ценовыми метками. Прямоугольными точками отмечены два максимума и один минимум:

Рисунок 1. Примеры локальных экстремумов

Рисунок 1. Примеры локальных экстремумов

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

Поскольку мы не ставим задачу сделать автоматический построитель канала, то задаваться положение экстремумов будет как показано на рис.1, - положением их на оси времени и цены. Наиболее подходят для этой цели ценовые метки - специальные графические объекты, имеющиеся в терминале MetaTrader 5. Ценовая метка имеет свойства-координаты цена и время, что позволяет однозначно идентифицировать точку экстремума на графике.


Объект для хранения экстремума - класс TExtremum

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

class TExtremum : public CObject
{
private:
  datetime  extr_datetime;              // дата/время в точке экстремума
  double    extr_price;                 // цена в точке экстремума
        
protected:
  virtual int  Compare(const CObject* _node, int _mode = 0) const;

public:
  void      TExtremum();               // конструктор
  void      ~TExtremum();              // деструктор
  void      SetExtremum(datetime _time, double _price);  // изменить дату/время и цену в точке экстремума
  void      SetDateTime(datetime _time);                 // изменить дату/время в точке экстремума
  void      SetPrice(double _price);  // изменить цену в точке экстремума

public:
  datetime  GetDateTime() const;      // получить дату/время в точке экстремума
  double    GetPrice() const;         // получить цену в точке экстремума

public:
  virtual bool  SaveExtremum(string _dt_name, string _p_name);  // сохранить экстремум
  virtual bool  LoadExtremum(string _dt_name, string _p_name);  // считать экстремум
  virtual bool  DeleteExtremum(string _dt_name, string _p_name);// удалить экстремум
};

Большинство методов тривиальны и не стоят того, чтобы заострять внимание на их реализации. Момент, на котором можно остановиться - метод TExtremum::Compare. Этот метод декларирован в классе CObject и используется при сортировке внутри списка. Мы его реализовали следующим образом:

//---------------------------------------------------------------------
//  Сравнение двух экстремумов по времени:
//---------------------------------------------------------------------
int TExtremum::Compare(const CObject* _node, int _mode = 0) const
{
  datetime  temp = ((TExtremum* )_node).GetDateTime();
  datetime  curr = GetDateTime();
  if(curr > temp)
  {
    return(_mode > 0 ? 1 : -1);
  }
  else if(curr < temp)
  {
    return(_mode > 0 ? -1 : 1);
  }

  return(0);
}

Здесь параметр _mode задает направление сортировки. Если он больше нуля, то прямая сортировка (сортировка по возрастанию), в противном случае - обратная (сортировка по убыванию).

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

//---------------------------------------------------------------------
//  Сохранить экстремум (дату/время):
//---------------------------------------------------------------------
bool TExtremum::SaveExtremum(string _dt_name, string _p_name)
{
  datetime  dt_result = GlobalVariableSet(_dt_name, (double)extr_datetime);
  datetime  p_result = GlobalVariableSet(_p_name, (double) extr_price);
  if(dt_result != 0 && p_result != 0)
  {
    return(true);
  }

  return(false);
}

//---------------------------------------------------------------------
//  Считать экстремум (дату/время):
//---------------------------------------------------------------------
bool TExtremum::LoadExtremum(string _dt_name, string _p_name)
{
  double  dt_temp, p_temp;
  bool    result = GlobalVariableGet(_dt_name, dt_temp);
  result &= GlobalVariableGet(_p_name, p_temp);
  if(result != false)
  {
    extr_datetime = (datetime)dt_temp;
    extr_price = p_temp;
    return(true);
  }

  return(false);
}

Два метода чтения/записи  в глобальные переменные TExtremum::LoadExtremum и TExtremum::SaveExtremum в случае успеха возвращают истину.


Манипуляция списком экстремумов - класс TExtremumList

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

Описание данного класса представлено ниже:

class TExtremumList : public CList
{
private:
  string              channel_prefix;     // имя канала ( префикс )
  ENUM_TIMEFRAMES      chart_timeframe;    // тайм-фрейм текущий
  string              chart_symbol;       // рабочий символ на графике

protected:
  string    MakeDTimeName(int _nmb);     // получить имя для сохранения/чтения даты/времени экстремума
  string    MakePriceName(int _nmb);     // получить имя для сохранения/чтения цены экстремума

public:
  void      TExtremumList();             // конструктор
  void     ~TExtremumList();             // деструктор
  void     SetChannelParams(string _pref, string _symbol = NULL, ENUM_TIMEFRAMES _curr_tf = PERIOD_CURRENT);
  void     AddExtremum(datetime _time, double  _price);
  void     DeleteAllExtremum();
  void     SaveExtremumList();
  void     LoadExtremumList();
  int      FindExtremum(datetime _dt);  // поиск экстремума по заданному времени

public:
  datetime GetDateTime(int _index);
  double   GetPrice(int _index);
};

Основной метод класса - TExtremumList::AddExtremum. Предназначен для добавления нового экстремума в список. После добавления в список выполняется сортировка экстремумов в списке по времени в точке экстремума. Текст этого метода представлен ниже:

void TExtremumList::AddExtremum(datetime _time, double  _price)
{
//  Создадим экстремум:
  TExtremum*    extr = new TExtremum();
  extr.SetExtremum(_time, _price);

//  Добавим его в список:
  Add(extr);

//  Отсортируем:
  Sort(1);
}

Здесь используются методы базового класса: CList::Add - для добавления нового элемента в список и CList::Sort - для сортировки элементов в списке. В методе CList::Sort используется метод TExtremum::Compare.

Рассмотрим метод поиска экстремума с заданным временем в списке TExtremumList::FindExtremum. Текст метода приведен ниже:

int TExtremumList::FindExtremum(datetime _dt)
{
  int           k = 0;
  TExtremum*    extr = (TExtremum*)(GetFirstNode());
  while(extr != NULL)
  {
    if(extr.GetDateTime() == _dt)
    {
      return(k);
    }
    extr = (TExtremum*)(GetNextNode());
  }
  return(-1);                     // экстремум не найден
}

Здесь используются методы базового класса: CList::GetFirstNode - для получения первого элемента списка (если список пустой, то возвращается нулевой указатель) и CList::GetNextNode - для получения следующего элемента списка (если следующего элемента нет - список закончился, то возвращается нулевой указатель.

Примечание:

Во внутренних данных класса списка CList есть указатель на текущий элемент. Этот указатель изменяется при вызове методов перемещения по списку (CList::GetFirstNode, CList::GetNextNode, CList::GetPrevNode и др.). Если ни один из таких методов еще не вызывался, то указатель на текущий элемент указывает на первый.

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

Методы TExtremum::MakeDTimeName и TExtremum::MakePriceName - вспомогательные. Предназначены для получения имен глобальных переменных, которые используются при сохранении и считывании экстремумов. Методы имеет следующую реализацию:

string TExtremumList::MakeDTimeName(int _nmb)
{
  string    name;
  StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_DTime_Extr", _nmb);
  return(name);
}

string TExtremumList::MakePriceName( int _nmb )
{
  string    name;
  StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_Price_DTime_Extr", _nmb);
  return(name);
}

Пример получаемого имени: "MainChannel_EURUSD_5_DTime_Extr1". Такое имя соответствует временной точке экстремума для канала MainChannel (условное название), инструмента EURUSD, таймфрейма 5М и номера экстремума 1. Номер экстремуму присваивается в порядке возрастания его времени, начиная с единицы. Фактически, это индекс в отсортированном по возрастанию списке со сдвигом на единицу.

Пример сохраненных в терминале значений трех экстремумов представлен на картинке ниже:

Сохраненные экстремумы в глобальных переменных

Рисунок 2. Экстремумы, сохраненные в глобальных переменных

Описанные выше классы приложены к статье в файле ExtremumClasses.mqh.


Индикатор для задания экстремумов в ручном режиме - ExtremumHandSet

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

Для начала, представим визуально то, что мы хотим получить на экране:

Рисунок 3. Индикатор для задания экстремумов

Рисунок 3. Индикатор для задания экстремумов

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

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

В начале подключим необходимые нам библиотеки:

//---------------------------------------------------------------------
//  Подключаемые библиотеки:
//---------------------------------------------------------------------
#include  <TextDisplay.mqh>
#include  <ExtremumClasses.mqh>

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

Затем добавим задаваемые параметры индикатора (здесь описаны только основные):

input string  PrefixString = "MainChannel";
//---------------------------------------------------------------------
input color   ExtremumPointColor = Yellow;
//---------------------------------------------------------------------
input bool    ShowInfo = true;

Первый параметр PrefixString задает префикс, который используется для составления имени глобальных переменных при записи/чтении экстремума. Это также дает возможность использования нескольких таких индикаторов на одном графике. Надо лишь задать для них разные префиксы.

Параметр ExtremumPointColor задает цвет левых ценовых меток, которые определяют положение экстремумов. Ценовые метки обязательно должны быть заданного цвета. Это соответствие проверяется в индикаторе. Метки с отличающимися параметрами игнорируются.

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

Далее создадим объекты для вывода информации и манипуляции экстремумами:

TableDisplay    TitlesDisplay;    // вывод информации на экран
//---------------------------------------------------------------------
TExtremumList*  PrevExtr_List;    // список предыдущих экстремумов
TExtremumList*  CurrExtr_List;    // список текущих экстремумов
TExtremumList*  NewExtr_List;     // список новых экстремумов

Эти объекты инициализируем следующим образом:

PrevExtr_List = new TExtremumList();
PrevExtr_List.SetChannelParams(PrefixString, Symbol(), Period());
PrevExtr_List.LoadExtremumList();

CurrExtr_List = PrevExtr_List;

NewExtr_List = new TExtremumList();
NewExtr_List.SetChannelParams(PrefixString, Symbol(), Period());

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

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

В список NewExtr_List мы будем заносить новые найденные на графике экстремумы.

Рассмотрим основные функции, использующиеся в индикаторе. Первая функция FindExtremumPoints используется для считывания и проверки параметров ценовых меток, задающих положение экстремумов:

bool FindExtremumPoints(long _chart_id)
{
  string  name;

//  1. Ищем общее число оьъектов с заданными свойствами и записываем их в список:
  int total_objects = ObjectsTotal(_chart_id, -1, OBJ_ARROW_LEFT_PRICE);
  if(total_objects <= 0)
  {
    return(false);
  }

  NewExtr_List.Clear();
  for(int i = 0; i < total_objects; i++)
  {
    name = ObjectName(_chart_id, i, -1, OBJ_ARROW_LEFT_PRICE);

    if( IsGraphicObjectGood(_chart_id, name, OBJ_ARROW_LEFT_PRICE, ExtremumPointColor) == true)
    {
      NewExtr_List.AddExtremum(ObjectGetInteger( _chart_id, name, OBJPROP_TIME),
                               ObjectGetDouble(_chart_id, name, OBJPROP_PRICE));
    }
  }

//  2. Если найдено ровно три экстремума, то можно пытаться строить канал:
  if(NewExtr_List.Total() == 3)
  {

//  Сохраним список новых экстремумов:
    NewExtr_List.SaveExtremumList();
    return(true);
  }

  NewExtr_List.Clear();
  return(false);
}

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

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

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

//---------------------------------------------------------------------
//  Проверка, переместились ли экстремумы на экране:
//---------------------------------------------------------------------
bool CheakExtremumMoving()
{
  if(FindExtremumLines(0) == true)
  {
    int  count = NewExtr_List.Total();
    int  index;
    for(int i = 0; i < count; i++)
    {
      index = CurrExtr_List.FindExtremum(NewExtr_List.GetDateTime(i));

//  Если найден новый экстремум:
      if(index == -1)
      {
        PrevExtr_List = CurrExtr_List;
        CurrExtr_List = NewExtr_List;
        return(true);
      }
    }
    CurrExtr_List = PrevExtr_List;
  }

  return(false);
}

Мы рассмотрели способ задания точек экстремумов в ручном режиме. У нас есть готовый индикатор, позволяющий контролировать этот процесс и записывающий точки в глобальные переменные. Полный текст индикатора находится в файле ExtremumHandSet.mq5. Теперь можно приступить к основной части - отрисовке канала.


Построение канала - немного теории

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

Построение канала по трем точкам экстремумов

Рисунок 4. Построение канала по трем точкам экстремумов

Как мы знаем из геометрии, через две точки можно провести только одну прямую. На рис.4 это прямая красного цвета. Она проходит через две точки с координатами (T1, P1) и (T2, P2) , они отмечены буквами A и B. Уравнение этой прямой будет выглядеть так:

(1)   P(t) = P1 + (t - T1)*(P2 - P1) / (T2 - T1), где P(t) - цена, вычисленная в момент времени t.


Через точку С (третий экстремум) нужно провести еще одну прямую, параллельную первой. На рис.3 это прямая зеленого цвета. Поскольку точки T1 и T2 для обеих прямых одинаковые, то нам нужно найти значения P1' и P2' (см. рис. 4).

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

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

Если пойти еще дальше и считать, что точка А на рис.4 всегда лежит на нулевой координате по оси времени (на нулевом номере бара), то наше уравнение еще упростится. Итак, T1=0, T3=B3,Т2=В2. Здесь В3 и В2 - номера бара относительно точки Т1, т.е. нулевой точки. Понятно, что данное допущение не повлияет на наклон прямой. Тогда мы получим следующее уравнение прямой, проходящей через точки А и В:

(2)   P(n) = P1 + n * (P2-P1) / B2, где P(n) - цена, вычисленная для номера бара n.


Итак, мы знаем значения P1, P2, P3 и B2, B3. Нужно найти значения P1' и P2'. Составив систему двух уравнений и решив ее, получим следующие формулы для нахождения неизвестных:

(3)   P1' = P3 - B3 * (P2 - P1) / B2

(4)   P2' = P2 - P1 + P1'


Найдя сначала значение P1' и подставив его в формулу (4), мы получим значение P2'. Теперь у нас есть теоретические основы для построения канала. Приступим к реализации.


Построение границы канала - класс TChannelBorderObject

Данный класс является наследником стандартного класса CChartObjectTrend. Его назначение - хранение всех параметров, относящихся к границе канала, отрисовка/удаление линии границы и  управление графическими свойствами этой линии.

Определение данного класса приведено ниже:

class TChannelBorderObject : public CChartObjectTrend
{
//  Общие параметры границы:
private:
  bool             is_created;       // создан ли графический объект на экране
  long             chart_id;         // идентификатор окна
  int              window;           // идентификатор под-окна

//  Параметры линии границы:
private:
  string           border_name;      // название линии границы
  color            border_color;     // цвет линии границы
  int              border_width;     // толщина линии границы
  ENUM_LINE_STYLE   border_style;     // стиль линии границы

//  Координаты границы:
private:
  datetime         point_left;       // время для левой точки (T1)
  datetime         point_right;      // время для правой точки (T2)
  double           price_left;       // цена для левой точки (P1)
  double           price_right;      // цена для правой точки (P2)

public:
  void     TChannelBorderObject();  // конструктор
  void    ~TChannelBorderObject();  // деструктор
  bool     IsCreated();             // проверить, создана ли линия

//  Создание/удаление линии:
public:
  bool     CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, 
                           double _p_left, double _p_right, color _color, int _width, ENUM_LINE_STYLE _style);
  bool     CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, 
                           double _p_left, double _p_right);
  bool     CreateBorder(datetime _t_left, datetime _t_right, double _p_left, double _p_right);
  bool     RemoveBorder();          // удалить линию с графика

//  Задание параметров линии:
public:
  void     SetCommonParams(long _chart_id, int _window, string _name);
  bool     SetBorderParams(color _color, int _width, ENUM_LINE_STYLE _style);
  bool     SetBorderColor(color _color);
  bool     SetBorderWidth(int _width);
  bool     SetBorderStyle(ENUM_LINE_STYLE _style);

//  Получение значения на линии:
  double   GetPrice(datetime _dt); // получить значение цены на линии границы в заданной точке
};

Особых пояснений данный класс не требует.

Остановимся только на методе получения цены границы в заданной временной точке:

//---------------------------------------------------------------------
//  Получить значение цены на линии границы в заданной точке:
//---------------------------------------------------------------------
double TChannelBorderObject::GetPrice(datetime _dt)
{
//  Если графический объект создан:
  if(is_created == true)
  {
    return(ObjectGetValueByTime( chart_id, border_name, _dt));
  }
  return(0.0);
}

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


Отрисовка канала - класс TSlideChannelObject

Данный класс является наследником стандартного класса CList. Его назначение следующее:

  • хранение объектов класса TChannelBorderObject и проведение различных манипуляций с ними;
  • расчет точек для построения необходимых линий, составляющих канал;
  • хранение и модификация параметров канала;
  • получение информационно-расчетных значений, описывающих построенный канал (его высоту, значение цены на границах и др.);

Код, описывающий данный класс, довольно велик по размеру, поэтому мы не будем приводить его полностью. Желающие могут посмотреть приложенный к статье файл SlideChannelClasses.mqh. Остановимся подробней на некоторых основных моментах.

Прежде всего, это получение значений B2 и B3 в точках T2 и T3, соответственно (см. рис.4). Используется следующий код:

//  Получим относительные смещения в барах для точек экстремумов:
  total_bars = Bars(symbol, time_frame);     // количество всех баров в истории
  if(total_bars == 0)
  {
    return(false);                           // канал построить не возможно
  }
  double  B2 = Bars(symbol, time_frame, point_left, point_right);
  double  B3 = Bars(symbol, time_frame, point_left, point_middle);

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

Если же функция вернула ненулевое значение, то можно получать значения В2 и В3. Это делается той же функцией Bars, но в другом варианте вызова. Мы задаем временные границы и получаем число баров в этих границах. Поскольку левая граница у нас одинаковая, то мы и получаем смещение в барах для точек Т2 и Т3. Для точки Т1 смещение всегда нулевое.

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


Рассмотрим основные моменты расчета. Весь расчет находится в методе TSlideChannelObject::CalcChannel.

//  Коэффициент наклона прямой:
  koeff_A = (price_right - price_left) / B2;

//  Значение цены на прямой АВ в точке Т3:
  double  P3_AB = price_left + B3 * koeff_A;

// Определим тип канала - 2MAX_1MIN или 1MAX_2MIN:
  if(P3_AB > price_middle)              // 2MAX_1MIN
  {
    channel_type = CHANNEL_2MAX_1MIN;

    left_prices[BORDER_UP_INDEX] = price_left;
    right_prices[BORDER_UP_INDEX] = price_right;
        
    left_prices[BORDER_DN_INDEX] = price_middle - B3 * koeff_A;
    right_prices[BORDER_DN_INDEX] = left_prices[BORDER_DN_INDEX] + (price_right - price_left);
  }
  else if(P3_AB < price_middle)         // 1MAX_2MIN
  {
    channel_type = CHANNEL_1MAX_2MIN;

    left_prices[BORDER_DN_INDEX] = price_left;
    right_prices[BORDER_DN_INDEX] = price_right;
        
    left_prices[BORDER_UP_INDEX] = price_middle - B3 * koeff_A;
    right_prices[BORDER_UP_INDEX] = left_prices[BORDER_UP_INDEX] + (price_right - price_left);
  }
  else
  {
    return( false );                      // канал построить не возможно ( все экстремумы на одной прямой )
  }

Здесь left_prices и right_prices - это массивы, в которых хранятся ценовые координаты всех девяти линий канала. Временные координаты всех линий канала одинаковы и нам известны заранее.

Сначала определяем коэффициент наклона прямой (см. формулу (2)) koeff_A. Затем рассчитываем значение цены на прямой АВ в точке Т3 (см. рис. 4). Делается это для того, чтобы определить какой тип канала задан для отрисовки - по двум максимумам и одному минимуму или по двум минимумам и одному максимуму. Мы проверяем, какая точка находится выше по оси цен - точка С или точка с координатами (P3', T3). В зависимости от их расположения мы и относим канал к одному из двух типов.

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

  left_prices[BORDER_MD_INDEX] = (left_prices[BORDER_DN_INDEX] + left_prices[BORDER_UP_INDEX ]) / 2.0;
  right_prices[BORDER_MD_INDEX] = (right_prices[BORDER_DN_INDEX] + right_prices[BORDER_UP_INDEX]) / 2.0;

Просто берем среднее значение координат верхней и нижней границ канала.


Индикатор для построения канала по заданным экстремумам - SlideChannel

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

Рисунок 5. Пример построенного по экстремумам канала

Рисунок 5. Пример построенного по экстремумам канала

Здесь также выводится информация по построенному каналу - ширина канала и расстояние в пунктах от текущей цены до границ канала и до средней линии.

Подключим необходимые библиотеки:

#include  <TextDisplay.mqh>
#include  <SlideChannelClasses.mqh>

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

Затем добавим задаваемые параметры индикатора (здесь описаны только основные):

input string          PrefixString = "MainChannel";
//---------------------------------------------------------------------
input ENUM_TIMEFRAMES  ExtremumTimeFrame = PERIOD_CURRENT;
//---------------------------------------------------------------------
input bool            ShowInfo = true;

Первый параметр PrefixString, как и в индикаторе ExtremumHandSet, задает префикс, который используется для составления имени глобальных переменных при чтении экстремума. Это также дает возможность использования нескольких таких индикаторов на одном графике. Надо лишь задать для них разные префиксы.

Параметр ExtremumTimeFrame задает тот таймфрейм, с которого будут считываться точки экстремумов из глобальных переменных. Это очень полезный параметр. Он позволяет строить синхронные каналы на разных таймфреймах. Задав, например, экстремумы на H1, мы можем построить точно такой же канал на M5. Для этого просто добавляем на график М5 наш индикатор для построения канала и он будет синхронно отображать все изменения.

Параметр ShowInfo управляет выводом на экран текстовой информации о текущих параметрах построенного канала.

Далее, создадим объекты для вывода информации и построения канала:

TableDisplay         ChannalDisplay;  // вывод общей информации по каналу на экран
TableDisplay         BordersDisplay;  // вывод информации по границам канала на экран
//---------------------------------------------------------------------
TSlideChannelObject  Channel;         // построение канала

Объект для построения канала инициализируется следующим образом:

  Channel.CreateChannel(PrefixString, 0, 0, Symbol(), period_current, curr_left_point, curr_middle_point, 
                        curr_right_point, curr_left_price, curr_middle_price, curr_right_price);
  Channel.SetBorderWidth(BorderWidth );
  Channel.SetmiddleWidth(middleLineWidth);
  Channel.SetUpBorderColor(UpBorderColor);
  Channel.SetDnBorderColor(DnBorderColor);
  Channel.SetmiddleColor(middleLineColor );
  Channel.ShowBorderZone(ShowBorderPercentageLines);
  Channel.BorderZonePercentage( PercentageZoneSize);
  Channel.Showmiddle(ShowmiddleLine);
  Channel.ShowmiddleZone( ShowmiddlePercentageLines);
  Channel.middleZonePercentage(PercentagemiddleZoneSize);

Здесь мы сначала создаем канал, вызывая метод TSlideChannelObject::CreateChannel, затем задаем необходимые параметры линий канала. Последовательность задания не имеет значения, можно сделать наоборот - сначала задать параметры линий, затем создать сам канал.

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

Рассмотрим основные функции, использующиеся в индикаторе. Первая функция GetExtremums - используется для считывания положения экстремумов и обновления канала, в соответствие со считанными значениями:

void GetExtremums()
{
  double  temp;
  string  name;

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr1");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_left_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr2");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_middle_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr3");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_right_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr1");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_left_price = temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol( ), "_", period_current, "_Price_Extr2");
  if( GlobalVariableGet(name, temp) != false )
  {
    curr_middle_price = temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr3");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_right_price = temp;
  }

//  Обновим положение канала:
  Channel.SetExtremums(curr_left_point, curr_middle_point, curr_right_point, 
                       curr_left_price, curr_middle_price, curr_right_price);
}

Для обновления канала на экране используется метод TSlideChannelObject::SetExtremums. Данный метод пересчитывает координаты линий канала и перерисовывает канал на экране.

Пример синхронного построения канала на разных таймфреймах можно увидеть в следующем видео-ролике:


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

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

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


Заключение

Мы рассмотрели полный цикл - от задания положения канала на экране до построения. Все оказалось не так уж сложно, особенно при использовании стандартных классов и ОПП.

Возникает вопрос: как можно использовать каналы для работы на рынке. Это, прежде всего, технический анализ текущего состояния инструмента. Затем, уже после анализа, - принятие решения. Большим подспорьем в этом могут стать каналы.

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

Прикрепленные файлы |
extremumhandset.mq5 (11.84 KB)
slidechannel.mq5 (13.62 KB)
extremumclasses.mqh (12.72 KB)
textdisplay.mqh (15.18 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (13)
Dmitriy Skub
Dmitriy Skub | 17 ноя 2012 в 07:56
vspexp:
хз
?Не понял. Картинку могешь привести?
Sergey Petruk
Sergey Petruk | 17 ноя 2012 в 09:27
ВИДЕО
Dmitriy Skub
Dmitriy Skub | 17 ноя 2012 в 10:27
vspexp:
ВИДЕО

Сергей, отличное видео!))

Теперь повтори тоже самое, только сначала прочитав написанное выше и ниже (статью читать уже не предлагаю)):

Сами они не будут отображаться - их надо нанести вручную три штуки - желтого цвета (если задано по умолчанию).

Sergey Petruk
Sergey Petruk | 17 ноя 2012 в 10:38

получилось.

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

буду практиковаться, спасибо.

Dmitriy Skub
Dmitriy Skub | 17 ноя 2012 в 10:47
vspexp:

получилось.

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

буду практиковаться, спасибо.

Да я понял уже, просто для меня это было само собой понятно)

Интересно, также, экстремумы задавать (и канал ставить) на крупном ТФ (Н1) и открыть еще мелкий ТФ (М5 - надо, при этом, задать в его настройках ExtremumTimeFrame ТФ Н1) .

Тогда на мелком ТФ будет хорошо видно как цена отбивает/пробивает границу канала (обычно с техническим возвратом). Если граница реальная, конечно.

Параллельные вычисления в MetaTrader 5 штатными средствами Параллельные вычисления в MetaTrader 5 штатными средствами

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

Димитар Манов: "На Чемпионате я боюсь только исключительных обстоятельств" (ATC 2010) Димитар Манов: "На Чемпионате я боюсь только исключительных обстоятельств" (ATC 2010)

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

Томаш Таузовски: "Мне остается лишь молиться о появлении убыточных позиций" (ATC 2010) Томаш Таузовски: "Мне остается лишь молиться о появлении убыточных позиций" (ATC 2010)

Томаш Таузовски (ttauzo) - ветеран первой десятки Чемпионата Automated Trading Championship 2010. Его эксперт уже седьмую неделю находится между пятым и седьмым местом. И это неудивительно: по мнению текущего лидера Чемпионата Бориса Одинцова ttauzo - один из самых стабильных экспертов, участвующих в соревновании.

Создание мульти-экспертов на основе торговых моделей Создание мульти-экспертов на основе торговых моделей

Использование объектно-ориентированного подхода в MQL5 значительно упрощает создание мультивалютных/мультисистемных/мультитаймфреймовых экспертов. Только представьте, ваш один единственный эксперт торгует сразу по нескольким десяткам торговых стратегий, сразу на всех доступных инструментах и сразу на всех возможных таймфреймах! К тому же этот эксперт прекрасно тестируется в тестере, а для всех стратегий, входящих в его состав, действует одна или сразу несколько систем управления капиталом.