
Построение каналов - взгляд изнутри и снаружи
Введение
Наверное не будет преувеличением сказать, что после скользящих средних каналы - самый популярный инструмент для анализа рыночной ситуации и принятия торговых решений. В первой статье цикла, посвященного каналам, мы рассмотрим математические основы и практическую реализацию индикатора, строящего канал, заданный тремя экстремумами на экране терминала.
На первый взгляд, построение канала является простой задачей - ведь в основе его лежит уравнение прямой, изучаемое в начальных классах средней школы. Но при практической реализации в терминале возникает масса вопросов, ответить на которые в лоб не получается.
Как лучше реализовать задание экстремумов и как отслеживать перемещение экстремумов? Что делать и как отрисовать канал, если средняя часть его попала на пропущенные бары? Если левый экстремум канала пришелся на пятницу, а правый на понедельник и между ними выходные с отсутствием баров в эти дни? Как получить значения на границах канала в текущий момент времени?
На эти и некоторые другие вопросы отвечает первая статья в цикле статей о каналах. Также предлагается реализация построителя канала по заданным трем экстремумам с использованием стандартных классов и объектно-ориентированного подхода. Реализуем мы построитель канала в виде индикатора.
Задание экстремумов
Фактически, положение канала на графике определяется, как минимум, тремя экстремумами. Если давать определение экстремума то можно принять следующее: это максимальное или минимальное значение функции на заданном множестве. Точка, в которой достигается экстремум, называется точкой экстремума. Соответственно, если достигается минимум — точка экстремума называется точкой минимума, а если максимум — точкой максимума.
В математическом анализе выделяют также понятие локальный экстремум (соответственно минимум или максимум). В точках максимума (минимума) значение функции больше (соответственно, меньше) всех соседних ее значений. Определение взято из Википедии.
Для целей построения каналов нас интересуют именно локальные экстремумы. Не вдаваясь в математические формулы, проиллюстрируем это понятие графически. На приведенном ниже рис.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. Индикатор для задания экстремумов
С помощью левых ценовых меток мы задаем положение экстремумов на временной и ценовой осях графика. Индикатор должен определять положение этих меток на графике, отображать на экране временные точки экстремумов и сохранять их в глобальных переменных терминала в описанном выше формате. Также индикатор должен отслеживать перемещения ценовых меток по графику и корректировать считанные временные точки экстремумов.
Отслеживание перемещений ценовых меток на графике будем по таймеру с интервалом раз в секунду. Это позволит данной системе быть не зависимой от поступления котировок и рабочих/не рабочих дней.
В начале подключим необходимые нам библиотеки:
//--------------------------------------------------------------------- // Подключаемые библиотеки: //--------------------------------------------------------------------- #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. Пример построенного по экстремумам канала
Здесь также выводится информация по построенному каналу - ширина канала и расстояние в пунктах от текущей цены до границ канала и до средней линии.
Подключим необходимые библиотеки:
#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 совпадающим с таймфреймом того графика, на котором задаются точки экстремумов.
Таков результат разделения функции задания опорных точек канала и функции построения канала на экране терминала.
Заключение
Мы рассмотрели полный цикл - от задания положения канала на экране до построения. Все оказалось не так уж сложно, особенно при использовании стандартных классов и ОПП.
Возникает вопрос: как можно использовать каналы для работы на рынке. Это, прежде всего, технический анализ текущего состояния инструмента. Затем, уже после анализа, - принятие решения. Большим подспорьем в этом могут стать каналы.
Вполне возможно разработать полуавтоматический советник, обрабатывающий границы канала на предмет входа в позицию и закрытия позиции. Он может работать как на пробой границы, так и на отбой от нее. Это будет предметом следующей статьи - Приемы работы с каналом - отбой и пробой.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
хз
ВИДЕО
Сергей, отличное видео!))
Теперь повтори тоже самое, только сначала прочитав написанное выше и ниже (статью читать уже не предлагаю)):
Сами они не будут отображаться - их надо нанести вручную три штуки - желтого цвета (если задано по умолчанию).
получилось.
но надо было написать "для тех кто в танке"- уточнить, что ценовым меткам нужно изменить цвет на желтый, т.к. у меня по умолчанию в терминале красный... и догадался - вот только с 5 пинка.
буду практиковаться, спасибо.
получилось.
но надо было написать "для тех кто в танке"- уточнить, что ценовым меткам нужно изменить цвет на желтый, т.к. у меня по умолчанию в терминале красный... и догадался - вот только с 5 пинка.
буду практиковаться, спасибо.
Да я понял уже, просто для меня это было само собой понятно)
Интересно, также, экстремумы задавать (и канал ставить) на крупном ТФ (Н1) и открыть еще мелкий ТФ (М5 - надо, при этом, задать в его настройках ExtremumTimeFrame ТФ Н1) .
Тогда на мелком ТФ будет хорошо видно как цена отбивает/пробивает границу канала (обычно с техническим возвратом). Если граница реальная, конечно.