Русский

Учебник по MQL4  Простые программы на MQL4  Создание пользовательских индикаторов

Создание пользовательских индикаторов


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

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


Устройство пользовательского индикатора


Необходимость буферов


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

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

Стандартом языка MQL4 предусмотрена возможность построения с помощью одного пользовательского индикатора до восьми индикаторных линий. Каждой из индикаторных линий ставится в соответствие один индикаторный массив и один буфер. Каждый из буферов имеет свой индекс. Индекс первого буфера - 0, второго - 1, и т.д., а последнего - 7. На Рис. 115 показано как информация из пользовательского индикатора через буферы передаётся клиентскому терминалу для построения индикаторных линий.


Рис. 115. Передача значений индикаторных массивов через буфер клиентскому терминалу.


Общий порядок построения индикаторных линий состоит в следующем:

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

2. Значения элементов индикаторных массивов через буферы сообщаются клиентскому терминалу.

3. На основе массивов значений, полученных из буферов, клиентский терминал воспроизводит изображение индикаторных линий.


Из чего состоит пользовательский индикатор


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

Пример простого пользовательского индикатора userindicator.mq4

//--------------------------------------------------------------------
// userindicator.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------------
#property indicator_chart_window // Индик. рисуется в основном окне
#property indicator_buffers 2 // Количество буферов
#property indicator_color1 Blue // Цвет первой линии
#property indicator_color2 Red // Цвет второй линии

double Buf_0[],Buf_1[]; // Объявление массивов (под буферы индикатора)
//--------------------------------------------------------------------
int init() // Специальная функция init()
{
SetIndexBuffer(0,Buf_0); // Назначение массива буферу
SetIndexStyle (0,DRAW_LINE,STYLE_SOLID,2);// Стиль линии
SetIndexBuffer(1,Buf_1); // Назначение массива буферу
SetIndexStyle (1,DRAW_LINE,STYLE_DOT,1);// Стиль линии
return; // Выход из спец. ф-ии init()
}
//--------------------------------------------------------------------
int start() // Специальная функция start()
{
int i, // Индекс бара
Counted_bars; // Количество просчитанных баров
//--------------------------------------------------------------------
Counted_bars=IndicatorCounted(); // Количество просчитанных баров
i=Bars-Counted_bars-1; // Индекс первого непосчитанного
while(i>=0) // Цикл по непосчитанным барам
{
Buf_0[i]=High[i]; // Значение 0 буфера на i-ом баре
Buf_1[i]=Low[i]; // Значение 1 буфера на i-ом баре
i--; // Расчёт индекса следующего бара
}
//--------------------------------------------------------------------
return; // Выход из спец. ф-ии start()
}
//--------------------------------------------------------------------

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

Первая директива указывает клиентскому терминалу, в каком окне необходимо отображать индикаторные линии:

#property indicator_chart_window             // Индик. рисуется в основном окне

В MQL4 предусмотрено всего два варианта для отображения индикаторных линий: в основном окне финансового инструмента и в отдельном окне. Основное окно - это то окно, в котором отображается привычный график цены по финансовому инструменту. В данном примере параметр indicator_chart_window в директиве #property предписывает клиентскому терминалу отображать индикаторные линии в основном окне.

В следующей строке указано количество буферов, используемых в индикаторе:

#property indicator_buffers 2                // Количество буферов

В рассматриваемом примере предусматривается построение двух индикаторных линий. Каждой линии ставится в соответствие один буфер, поэтому общее количество буферов - два.

В следующих строках заданы цвета для каждой из индикаторных линий.

#property indicator_color1 Blue              // Цвет первой линии
#property indicator_color2 Red // Цвет второй линии

Параметры indicator_color1 и indicator_color2 указывают на настройку цветов для соответствующего буфера - в данном случае для буферов с индексами 0 (Blue - синий цвет) и 1 (Red - красный цвет). Обратите внимание, литеры 1 и 2 в названиях параметров indicator_color1 и indicator_color2 - это не индексы буферов. Эти литеры являются составной частью названия констант, поставленных в соответствие буферам. Для каждой из этих констант может быть установлен свой цвет по усмотрению пользователя.

В следующей строке объявлены индикаторные массивы:

double Buf_0[],Buf_1[];                      // Объявление массивов(под буферы индикатора)

Индикатор предназначен для построения двух индикаторных линий, поэтому необходимо объявить два глобальных одномерных массива, по одному на каждую линию. Названия индикаторных массивов определяются пользователем по его выбору. В данном случае использованы названия массивов Buf_0[] и Buf_1[], в других случаях возможно использование других названий, например,Line_1[],Alfa[], Integral[] и пр. Необходимость объявления массивов на глобальном уровне вызвана тем, что значения элементов массивов должны сохраняться в период между вызовами на исполнение специальной функции start().

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

Очень важное действие выполняется в строке:

   SetIndexBuffer(0,Buf_0);                  // Назначение массива буферу

С помощью функции SetIndexBuffer() необходимый буфер (в данном случае с индексом 0) ставится в соответствие массиву (в данном случае Buf_0). Это значит, что для построения первой индикаторной линии клиентский терминал будет принимать данные, заключённые в массиве Buf_0, используя для этого нулевой буфер.

В следующей строке определяется стиль линии:

   SetIndexStyle (0,DRAW_LINE,STYLE_SOLID,2);// Стиль линии

Клиентскому терминалу предписывается для нулевого буфера (0) использовать следующие стили отображения: простая линия (DRAW_LINE), сполошная линия (STYLE_SOLID), толщина линии 2.

В последующих двух строках указаны настройки для второй индикаторной линии:

   SetIndexBuffer(1,Buf_1);                 // Назначение массива буферу
SetIndexStyle (1,DRAW_LINE,STYLE_DOT,1); // Стиль линии

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

Вычисление значений элементов индикаторных массивов (здесь внимательно)


Вычисление значений элементов индикаторных массивов выполняется в специальной функции start(). Для того, чтобы правильно понять содержание программного кода функции start() пользовательского индикатора, необходимо обратить внимание на порядок индексирования баров. В разделе Массивы подробно рассматривался метод индексирования массивов-таймсерий. Согласно этому методу индексирование баров начинается с нуля. Нулевым баром считается текущий, ещё не сформировавшийся бар. Ближайший к нему бар имеет индекс 1, следующий - индекс 2 и т.д.

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

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

Ранее упоминалось, что индикаторные линии строятся на основе численной информации, содержащейся в индикаторных массивах. Индикаторный массив содержит информацию о координатах точек, по которым строится индикаторная линия. При этом координатой каждой точки по оси Y является значение элемента индикаторного массива, а координатой по оси Х - значение индекса элемента индикаторного массива. В рассматриваемом примере первая индикаторная линия строится по максимальным значениям баров. На Рис. 116 показана эта индикаторная линия (синего цвета) в окне финансового инструмента, построенная на основе индикаторного массива Buf_0.

Значение индекса
индикаторного
массива Buf_0
Значение элемента
индикаторного
массива Buf_0
0 1.3123
1 1.3124
2 1.3121
3 1.3121
4 1.3123
5 1.3125
6 1.3127
... ...

Рис. 116. Соответствие координат индикаторной линии значениям индикаторного массива.

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

Основополагающее отличие индикаторного массива от обычного массива состоит в том, что:

В момент образования нового бара значения индексов элементов индикаторного массива автоматически изменяются клиентским терминалом, а именно - значение каждого индекса индикаторного массива увеличивается на единицу, а размер индикаторного массива увеличивается на один элемент (с нулевым индексом).

Например, нулевой бар на Рис. 116 (таймфреймH1) имеет время открытия 6:00. В 7:00 в окне финансового инструмента появится новый бар. Бар, открытый в 6:00, автоматически получит индекс 1. Для того, чтобы индикаторная линия на этом баре была отражена правильно, клиентский терминал изменит индекс элемента индикаторного массива, соответствующего бару со временем открытия 6:00. В таблице на Рис. 116 этот элемент записан в первой строке. Одновременно с этим индексы всех элементов индикаторного массива будут увеличены клиентским терминалом на единицу. В частности, индекс элемента массива, соответствующего бару со временем открытия 6:00, получит индекс 1 (ранее он был равен 0). Индикаторный массив будет увеличен на один элемент. Индекс нового, добавленного элемента индикаторного массива будет равен 0, а значением этого элемента должно быть новое значение, отражающее координату индикаторной линии на нулевом баре. Это значение и рассчитывается в специальной функции start() на каждом тике.

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

Для реализации указанной технологии в языке MQL4 имеется очень полезная стандартная функция - IndicatorCounted().

Функция IndicatorCounted()

int IndicatorCounted()

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

Если раньше индикатор никогда не присоединялся к окну финансового инструмента, то при первом исполнении функции start() значение переменной Counted_bars будет равно нулю:

   Counted_bars=IndicatorCounted();  // Количество просчитанных баров

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

   i=Bars-Counted_bars-1;           // Индекс первого непосчитанного

Предположим, что в момент запуска в окне финансового инструмента имеется 300 баров. Эта величина является значением предопределённой переменной Bars. Ранее определённое значение Counted_bars равно 0. В результате получится, что индекс i первого не посчитанного бара (самого старого бара, начиная с которого необходимо начать расчёт) равен 299.

Вычисление значений элементов индикаторных массивов выполняется в цикле while():

   while(i>=0)                      // Цикл по непосчитанным барам
{
Buf_0[i] = High[i]; // Значение 0 буфера на i-ом баре
Buf_1[i] = Low[i]; // Значение 1 буфера на i-ом баре
i--; // Расчёт индекса следующего бара
}

До тех пор, пока значение переменной i находится в пределах значений индексов первого непосчитанного (299) и текущего (0) баров включительно, выполняется расчёт значений элементов индикаторных массивов для обеих индикаторных линий. Обратите внимание, расчёт недостающих значений элементов индикаторных массивов производится в рамках одного (первого) запуска специальной функции start(). По ходу вычислений клиентский терминал запоминает, для каких элементов был произведен расчёт. Последняя итерация в цикле while() выполняется, когда значение переменной i равно нулю, т.е. рассчитываются значения индикаторных массивов для нулевого бара. По окончании цикла специальная функция start() заканчивает своё исполнение и передаёт управление клиентскому терминалу. Клиентский терминал, в свою очередь, отобразит все (в данном случае две) индикаторные линии в соответствии с рассчитанными значениями элементов индикаторных массивов.

На следующем тике специальная функция start() будет снова запущена клиентским терминалом на исполнение. Дальнейшие события будут развиваться в зависимости от ситуации (продолжим рассмотрение примера для 300 баров).

Вариант 1. Новый тик поступил во время развития текущего нулевого бара (наиболее распространённая ситуация).


Рис. 117. Обрабатываемый тик принадлежит текущему бару.


На Рис. 117 показано два тика, поступивших в клиентский терминал в момент времени t 1 и t 2. Рассматриваемая ситуация будет одинаковой для обоих этих тиков. Для примера проследим, как будет исполняться функция start(), запущенная в момент t 2. Во время выполнения функции start() будет исполнена строка:

   Counted_bars=IndicatorCounted(); // Количество просчитанных баров

Функция IndicatorCounted() вернёт значение 299, т.е. с момента последнего вызова функции start() не изменялись предыдущие 299 баров. В результате этого вычисленное значение индекса i будет равно 0 (300-299-1):

   i=Bars-Counted_bars-1;           // Индекс первого непосчитанного

Это значит, что в последующем цикле while() будут рассчитаны значения элементов индикаторных массивов, имеющие нулевой индекс. Иными словами, будет рассчитано новое положение индикаторной линии на нулевом баре. По окончании цикла функция start() закончит работу и передаст управление клиентскому терминалу.

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


Рис. 118. Обрабатываемый тик является первым тиком нового нулевого бара.


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

На Рис. 118 показана ситуация, при которой на последнем тике предыдущего бара (в момент t 2 ) была запущена и благополучно исполнена функция start(). Поэтому, хотя теперь уже первый (с индексом 1) бар, закончившийся в момент t 2, был посчитан индикатором, функция IndicatorCounted() вернет значение, которое было на предыдущем баре, то есть 299:

   Counted_bars=IndicatorCounted(); // Количество просчитанных баров

В следующей строке будет вычислен индекс i, в данном случае для первого тика нового он окажется равным 1 (301-299-1):

   i=Bars-Counted_bars-1;           // Индекс первого непосчитанного

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

Вариант 3. Новый тик является первым тиком нового нулевого бара, но при этом предпоследний тик не обработан (редкая ситуация).


Рис. 119. Не все тики на предыдущем баре были обработаны.


На Рис. 119 показана ситуация, когда специальная функция start() запущена на первом тике нового бара, в момент t 5. Предыдущий раз функция start() запускалась в момент t 2. Тик, поступивший в клиентский терминал в момент t 3 (красная стрелка) не был обработан индикатором. Это произошло потому, что период исполнения функции start() t 2 -t 4 оказался больше интервала времени между тиками t 2 -t 3. Этот факт будет отмечен клиентским терминалом - при исполнении функции start(), запущенной в момент t 5. При вычислении в строке:

   Counted_bars=IndicatorCounted(); // Количество просчитанных баров

функция IndicatorCounted() вернёт значение 299 (!). Это значение соответствует истине - с момента последнего вызова индикатора не было изменено 299 баров из (теперь уже) 301. Поэтому вычисленный индекс первого (самого левого) бара, с которого необходимо начать расчёт значения элементов индикаторных массивов, будет равен 1 (301-299-1):

   i=Bars-Counted_bars-1;           // Индекс первого непосчитанного

Это значит, что при исполнении оператора цикла while() будет выполнено две итерации. На первой из них будут посчитаны значения элементов индикаторных массивов с индексом i =1, а именно, Buf_0[1] и Buf_1[1]. Обратите внимание, к моменту начала вычислений бары и индикаторные массивы уже переиндексированы клиентским терминалом (по причине начала нового бара, в период между запусками на исполнение функции start()). Поэтому вычисления для элементов массивов с индексами 1 будут производиться на основе массивов-таймсерий (максимальных и минимальных значений цены бара) также с индексом 1:

   while(i>=0)                      // Цикл по непосчитанным барам
{
Buf_0[i] = High[i]; // Значение 0 буфера на i-ом баре
Buf_1[i] = Low[i]; // Значение 1 буфера на i-ом баре
i--; // Расчёт индекса следующего бара
}

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

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

Обратите внимание, бар считается непосчитанным, если расчёт значений элементов индикаторного массива не выполнен хотя бы для одного последнего тика на этом баре.

Запустив на исполнение пользовательский индикатор userindicator.mq4, в окне финансового инструмента можно наблюдать две линии - жирную синюю линию, построенную по максимумам баров, и красную пунктирную линию, построенную по минимумам (Рис. 120).


Рис. 120. Две индикаторные линии в окне финансового инструмента, построенные индикатором userindicator.mq4.

Следует заметить, что можно построить пользовательский индикатор, индикаторные линии которого будут совпадать с линиями аналогичного технического индикатора. Это легко выполнить, если в качестве расчётных формул в пользовательском индикаторе будут использоваться те же формулы, что и в техническом. Чтобы это продемонстрировать несколько усовершенствуем программный код, рассмотренный в предыдущем примере. Пусть следующий пользовательский индикатор строит индикаторные линии по средним значениям максимумов и минимумов нескольких последних баров. Выполнить необходимые для этого вычисления очень просто: достаточно найти средние значения элементов массивов-таймсерий. Например, значение индикаторного массива с индексом 3 (т.е. координату индикаторной линии для третьего бара) на основе пяти последних максимумов, вычисляется так:

Buf_0[3] = ( High[3] + High[4] + High[5] + High[6] + High[7] ) / 5

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

Пример простого пользовательского индикатора averagevalue.mq4. Индикаторные линии построены по средним максимальным и минимальным значениям N баров.
//--------------------------------------------------------------------
// averagevalue.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------------
#property indicator_chart_window // Индик. рисуется в основном окне
#property indicator_buffers 2 // Количество буферов
#property indicator_color1 Blue // Цвет первой линии
#property indicator_color2 Red // Цвет второй линии

extern int Aver_Bars=5; // Количество баров для расчёта

double Buf_0[],Buf_1[]; // Объявление индикаторных массивов
//--------------------------------------------------------------------
int init() // Специальная функция init()
{
//--------------------------------------------------------------------
SetIndexBuffer(0,Buf_0); // Назначение массива буферу
SetIndexStyle (0,DRAW_LINE,STYLE_SOLID,2);// Стиль линии
//--------------------------------------------------------------------
SetIndexBuffer(1,Buf_1); // Назначение массива буферу
SetIndexStyle (1,DRAW_LINE,STYLE_DOT,1);// Стиль линии
//--------------------------------------------------------------------
return; // Выход из спец. ф-ии init()
}
//--------------------------------------------------------------------
int start() // Специальная функция start()
{
int i, // Индекс бара
n, // Формальный параметр
Counted_bars; // Количество просчитанных баров
double
Sum_H, // Сумма значений High за период
Sum_L; // Сумма значений Low за период
//--------------------------------------------------------------------
Counted_bars=IndicatorCounted(); // Количество просчитанных баров
i=Bars-Counted_bars-1; // Индекс первого непосчитанного
while(i>=0) // Цикл по непосчитанным барам
{
Sum_H=0; // Обнуление в начале цикла
Sum_L=0; // Обнуление в начале цикла
for(n=i;n<=i+Aver_Bars-1;n++) // Цикл суммирования значений
{
Sum_H=Sum_H + High[n]; // Накопление суммы макс.значений
Sum_L=Sum_L + Low[n]; // Накопление суммы мин. значений
}
Buf_0[i]=Sum_H/Aver_Bars; // Значение 0 буфера на i-ом баре
Buf_1[i]=Sum_L/Aver_Bars; // Значение 1 буфера на i-ом баре

i--; // Расчёт индекса следующего бара
}
//--------------------------------------------------------------------
return; // Выход из спец. ф-ии start()
}
//--------------------------------------------------------------------

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

Используемый здесь метод усреднения применяется также для расчётов в техническом индикаторе Moving Average. Если к окну финансового инструмента прикрепить рассматриваемый пользовательский индикатор averagevalue.mq4 и технический индикатор Moving Average, то можно наблюдать три индикаторных линии. Если для обоих индикаторов установить одинаковый период усреднения, то индикаторная линия технического индикатора совпадёт с одной из линий пользовательского индикатора (для этого также необходимо в настройке технического индикатора указать параметры, представленные на Рис. 121.)


Рис. 121. Совпадение линий технического и пользовательского индикаторов (красная линия).

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

Возможности пользовательских индикаторов


Отображение индикаторных линий в разных окнах


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

Для отображения индикаторных линий в отдельном окне (которое располагается в нижней части окна финансового инструмента) в директиве #property (в начале программы) необходимо указать параметр indicator_separate_window:

#property indicator_separate_window // Индик. рисуется в основном окне

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

Ограничение расчётной истории


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

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

   i=Bars-Counted_bars-1;           // Индекс первого непосчитанного
if (i>History-1) // Если много баров то ..
i=History-1; // ..рассчитывать заданное колич.

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


Пример простого пользовательского индикатора separatewindow.mq4. Индикаторные линии отображаются в отдельном окне.
//--------------------------------------------------------------------
// separatewindow.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------------
#property indicator_separate_window // Отображение в отдельном окне
#property indicator_buffers 1 // Количество буферов
#property indicator_color1 Blue // Цвет первой линии
#property indicator_color2 Red // Цвет второй линии

extern int History =50; // Колич.баров в расчётной истории
extern int Aver_Bars=5; // Количество баров для расчёта

double Buf_0[]; // Объявление индикаторного массива
//--------------------------------------------------------------------
int init() // Специальная функция init()
{
SetIndexBuffer(0,Buf_0); // Назначение массива буферу
SetIndexStyle (0,DRAW_LINE,STYLE_SOLID,2);// Стиль линии
return; // Выход из спец. ф-ии init()
}
//--------------------------------------------------------------------
int start() // Специальная функция start()
{
int i, // Индекс бара
n, // Формальный параметр
Counted_bars; // Количество просчитанных баров
double
Sum_H, // Сумма значений High за период
Sum_L; // Сумма значений Low за период
//--------------------------------------------------------------------
Counted_bars=IndicatorCounted(); // Количество просчитанных баров
i=Bars-Counted_bars-1; // Индекс первого непосчитанного
if (i>History-1) // Если много баров то ..
i=History-1; // ..рассчитывать заданное колич.
while(i>=0) // Цикл по непосчитанным барам
{
Sum_H=0; // Обнуление в начале цикла
Sum_L=0; // Обнуление в начале цикла
for(n=i;n<=i+Aver_Bars-1;n++) // Цикл суммирования значений
{
Sum_H=Sum_H + High[n]; // Накопление суммы макс.значений
Sum_L=Sum_L + Low[n]; // Накопление суммы мин. значений
}
Buf_0[i]=(Sum_H-Sum_L)/Aver_Bars;// Знач. 0 буфера на i-ом баре
i--; // Расчёт индекса следующего бара
}
//--------------------------------------------------------------------
return; // Выход из спец. ф-ии start()
}
//--------------------------------------------------------------------

Аналогичный расчёт индикаторной линии выполняется и в техническом индикаторе AverageTrue Range. На Рис. 122 можно наблюдать индикаторную линию, построенную пользовательским индикатором separatewindow.mq4 в одном отдельном окне и индикаторную линию, построенную техническим индикатором ATR в другом окне. В данном случае наблюдается полное совпадение линий благодаря тому, что для обоих индикаторов выбран один и тот же период усреднения, равный 5. Если в каком-нибудь индикаторе изменить этот параметр, то рисунок соответствующей индикаторной линии изменится.


Рис. 122. Отображение линии пользовательского индикатора в отдельном окне.
Совпадение рисунков линий технического (ATR) и пользовательского (separatewindow.mq4) индикаторов.


Легко заметить также, что линия пользовательского индикатора построена не на всю ширину экрана, а лишь по 50 последним барам, согласно установленному значению внешней переменной History. В случае если трейдеру понадобится использовать больший исторический интервал, он может легко изменить значение внешней переменной через окно настроек пользовательского индикатора.

На Рис. 123 показано окно финансового инструмента, в котором индикаторная линия отражается другим стилем - в виде гистограммы. Для получения такого результата в программном коде separatewindow.mq4 была изменена одна строка - указаны иные значения стиля линии:

   SetIndexStyle (0,DRAW_HISTOGRAM);// Стиль линии

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


Рис. 123. Отображение линии пользовательского индикатора в отдельном окне (гистограмма).
Совпадение рисунков линий технического (ATR) и пользовательского (separatewindow.mq4) индикаторов.


Cмещение индикаторных линий по вертикали и горизонтали


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

Пример простого пользовательского индикатора displacement.mq4. Смещение индикаторных линий по горизонтали и вертикали.
//--------------------------------------------------------------------
// displacement.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------------
#property indicator_chart_window // Индик. рисуется в основном окне
#property indicator_buffers 3 // Количество буферов
#property indicator_color1 Red // Цвет первой линии
#property indicator_color2 Blue // Цвет второй линии
#property indicator_color3 Green // Цвет второй линии

extern int History =500; // Колич.баров в расчётной истории
extern int Aver_Bars=5; // Количество баров для расчёта
extern int Left_Right= 5; // Смещение по горизонтали (баров)
extern int Up_Down =25; // Смещение по вертикали (пунктов)

double Line_0[],Line_1[],Line_2[]; // Объявление массивов данных
//--------------------------------------------------------------------
int init() // Специальная функция init()
{
//--------------------------------------------------------------------
SetIndexBuffer(0,Line_0); // Назначение массива буферу 0
SetIndexStyle (0,DRAW_LINE,STYLE_SOLID,2);// Стиль линии
//--------------------------------------------------------------------
SetIndexBuffer(1,Line_1); // Назначение массива буферу 1
SetIndexStyle (1,DRAW_LINE,STYLE_DOT,1);// Стиль линии
//--------------------------------------------------------------------
SetIndexBuffer(2,Line_2); // Назначение массива буферу 2
SetIndexStyle (2,DRAW_LINE,STYLE_DOT,1);// Стиль линии
//--------------------------------------------------------------------
return; // Выход из спец. ф-ии init()
}
//--------------------------------------------------------------------
int start() // Специальная функция start()
{
int i, // Индекс бара
n, // Формальный параметр (индекс)
k, // Индекс элемента индик. массива
Counted_bars; // Количество просчитанных баров
double
Sum; // Сумма Low и High за период
//--------------------------------------------------------------------
Counted_bars=IndicatorCounted(); // Количество просчитанных баров
i=Bars-Counted_bars-1; // Индекс первого непосчитанного
if (i>History-1) // Если много баров то ..
i=History-1; // ..рассчитывать заданное колич.

while(i>=0) // Цикл по непосчитанным барам
{
Sum=0; // Обнуление в начале цикла
for(n=i;n<=i+Aver_Bars-1;n++) // Цикл суммирования значений
Sum=Sum + High[n]+Low[n]; // Накопление суммы макс.значений
k=i+Left_Right; // Вычисление расчётного индекса
Line_0[k]= Sum/2/Aver_Bars; // Значение 0 буфера на k-ом баре
Line_1[k]= Line_0[k]+Up_Down*Point;// Значение 1 буфера
Line_2[k]= Line_0[k]-Up_Down*Point;// Значение 2 буфера

i--; // Расчёт индекса следующего бара
}
//--------------------------------------------------------------------
return; // Выход из спец. ф-ии start()
}
//--------------------------------------------------------------------

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

extern int Left_Right= 5;              // Смещение по горизонтали (баров)
extern int Up_Down = 25; // Смещение по вертикали (пунктов)

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

  • для смещения линии по горизонтали необходимо присвоить вычисленное значение элементу массива, индекс которого на величину Left_Right больше (для сдвига влево и меньше для сдвига вправо), чем индекс бара, для которого выполнены вычисления;
  • для смещения линии по вертикали необходимо прибавить (для смещения вверх или вычесть для смещения вниз) значение Up_Down*Point к каждому значению индикаторного массива, характеризующего исходное положение линии;

В рассматриваемом примере вычисление индексов выполняется в строке:

      k = i+Left_Right;                // Вычисление расчётного индекса

Здесь i - индекс бара, для которого выполняются вычисления, а k - индекс элемента индикаторного массива. Красная индикаторная линия, отображаемая клиентским терминалом на основе индикаторного массива Line_0[], сдвинута влево на 5 баров (в соответствии с настройками пользователя, см. Рис. 124) относительно того места точек, по которым была бы построена исходная линия. Исходная линия в данном случае представляет среднюю МА с периодом усреднения 5, рассчитанную по формуле (High[i]+Low[i])/2.

      Line_0[k]= Sum2 Aver_Bars;       // Значение 0 буфера на k-ом баре

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

      Line_1[k]= Line_0[k]+Up_Down*Point;// Значение 1 буфера 
Line_2[k]= Line_0[k]-Up_Down*Point;// Значение 2 буфера

Использование здесь индекса k для элементов всех индикаторных массивов позволило выполнить расчёт для элементов массивов Line_1[], Line_2[] на том же баре, для которого имеется соответствующее значение опорного массива Line_0[]. В результате пунктирные линии оказались сдвинутыми относительно красной линии на то значение, которое указано пользователем в окне настроек индикатора, в данном случае, на 30 пунктов (Рис. 124).


Рис. 124. Красная индикаторная линия сдвинута влево на 5 баров.
Пунктирные индикаторные линии отстоят от красной линии на 30 пунктов.

Ограничения пользовательских индикаторов


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

Существует группа функций, которые можно использовать только в пользовательских индикаторах, и запрещено использовать в экспертах и скриптах: IndicatorBuffers(), IndicatorCounted (), IndicatorDigits(), IndicatorShortName(), SetIndexArrow(), SetIndexBuffer(), SetIndexDrawBegin(), SetIndexEmptyValue(), SetIndexLabel(), SetIndexShift(), SetIndexStyle(), SetLevelStyle(), SetLevelValue().

С другой стороны, в индикаторах запрещено использование торговых функций: OrderSend(), OrderClose(), OrderCloseBy(), OrderDelete() и OrderModify(). Это связано с тем, что индикаторы работают в интерфейсном потоке (в отличие от экспертов и скриптов, работающих в собственном потоке).

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

Общая сравнительная характеристика экспертов, скриптов и индикаторов приведена в Таблице 2.