На этом уроке мы напишем простой индикатор, который в дальнейшем будем использовать при разработке советника.
А использовать для получения сигнала мы будем уже существующие — Moving Average и MACD. Алгоритм следующий:
Определять направление сделки (покупка или продажа) мы будем по индикатору Moving Average с периодом 100 на дневном графике(Daily), т.е. если текущая цена выше MA(100), то покупаем, а если ниже — продаём.
А для поиска удачных точек входа воспользуемся индикатором MACD.

вход в покупки– индикатор MACD ниже нуля, идет снизу вверх, а его сверху вниз пересекает сигнальная линия.

вход в продажи – индикатор MACD выше нуля, идет cверху вниз, а его снизу вверх пересекает сигнальная линия.

Итак, приступим.
Запускаем MetaEditor и вызываем мастер создания программ.
Отмечаем в списке выбора «Пользовательский индикатор»:

Заполняем пока только поле «Имя», остальное без изменений и нажимаем кнопку «Далее»:

На следующей форме мастера оставляем как есть и нажимаем кнопку «Далее»:

Тут также ничего не меняем и нажимаем «Готово»:

Итак, шаблон нашего будущего индикатора готов:

В основе пользовательских индикаторов, является передача значений индикаторных массивов клиентскому терминалу (для построения индикаторных линий) через буферы обмена.
Буфер — область памяти, содержащая численные значения индикаторного массива.
Стандартом языка MQL4 предусмотрена возможность построения с помощью одного пользовательского индикатора до восьми индикаторных линий. Каждой из индикаторных линий ставится в соответствие один индикаторный массив и один буфер. Каждый из буферов имеет свой индекс. Индекс первого буфера — 0, второго — 1, и т.д., а последнего — 7.
Нам необходимо два буфера для хранения и отображения на графике сигналов для покупок и для продаж, поэтому определим их:
#property indicator_buffers 2 // Сообщаем о том, что у нас будет два буфера #property indicator_color1 Lime // Цвет стрелки для покупок #property indicator_color2 Red // Цвет стрелки для продаж double Buy[]; // Буфер для покупок double Sell[]; // Буфер для продаж
а кроме того, давайте определим два имени «BUY» и «SELL», присвоив им значения «0» и «1» соответственно.
В дальнейшем это облегчит нам восприятие исходного текста индикатора:
#define BUY 0 #define SELL 1
Далее в функции OnInit():
//+------------------------------------------------------------------+
//| Custom indicator initialization function | //+------------------------------------------------------------------+
int OnInit()
{
// Установим связь наших массивов с буферами индикатора
SetIndexBuffer (0, Buy);
SetIndexBuffer (1, Sell);
// Устанавливаем нулевые значения для индикатора, при которых не будет сигнальных стрелок
SetIndexEmptyValue (0, 0);
SetIndexEmptyValue (1, 0);
//Определяем стиль отображения индикаторных линий - стрелка
SetIndexStyle (0, DRAW_ARROW);
SetIndexStyle (1, DRAW_ARROW);
// Установим значки "стрелки" для буферов
SetIndexArrow(0, 233); // Стрелка "вверх" для покупок
SetIndexArrow(1, 234); // Стрелка "вниз" для продаж
//Определяем разрядность значений индикаторных линий - приравниваем разрядности фин. инструмента
IndicatorDigits (Digits);
//Строка с кратким названием индикатора выводится в сплывающей подсказке при наведении указателя мыши на стрелку
IndicatorShortName ("Мой первый индикатор");
//Устанавливаем текст описания стрелок индикатора для отображения информации в всплывающей подсказке.
SetIndexLabel(0, "Покупаем");
SetIndexLabel(1, "Продаём");
return(INIT_SUCCEEDED);
}
Рассмотрим подробней.
С помощью функции SetIndexBuffer() необходимый буфер (рассмотрим случай с индексом 0) ставится в соответствие массиву (в нашем случае «Buy»). Это значит, что для построения первой стрелки клиентский терминал будет принимать данные, заключённые в массиве «Buy», используя для этого нулевой буфер и соответственно для буфера «Sell» — первый буфер.
Функция SetIndexEmptyValue() устанавливает значения «по умолчанию», т.е. те, при которых индикатор не будет ничего показывать на графике, в нашем случае это ноль.
Функция SetIndexStyle() устанавливает новый тип, стиль, ширину и цвет для указанной линии индикатора. Для нашего же случае это стрелки (DRAW_ARROW).
Функция SetIndexArrow() — устанавливает значок для линии индикаторов, имеющей стиль DRAW_ARROW. В качестве параметров в функцию передаётся номер буфера и код символа из шрифта Wingdings.
Функция IndicatorDigits — определяет формат точности (количество знаков после десятичной точки) для визуализации значений индикатора.
Функция IndicatorShortName — устанавливает «короткое» имя пользовательского индикатора для отображения в подокне индикатора.
Функция SetIndexLabel — устанавливает текст описания линии индикатора для отображения информации в окне.
С этим разобрались и далее я предлагаю написать функцию, которая будет определять наличие сигнала на определённом баре. Таким образом в качестве параметра функции мы будем передавать индекс расчитываемого бара.
int Signal(int i)
{
// Определим переменные типа "double" в которых будем хранить показания индикатора MACD.
// Для текущего бара
double MacdCurrent = iMACD(NULL, 0, fast_ema_period, slow_ema_period, signal_period, PRICE_CLOSE, MODE_MAIN, i);
// Для предыдущего бара
double MacdPrevious = iMACD(NULL, 0, fast_ema_period, slow_ema_period, signal_period, PRICE_CLOSE, MODE_MAIN, i+1);
// И для сигнальной линии индикатора
// Для текущего бара
double SignalCurrent = iMACD(NULL, 0, fast_ema_period, slow_ema_period, signal_period, PRICE_CLOSE, MODE_SIGNAL, i);
// Для предыдущего бара
double SignalPrevious = iMACD(NULL, 0, fast_ema_period, slow_ema_period, signal_period, PRICE_CLOSE, MODE_SIGNAL, i+1);
// Снимем показания индикатора Moving Average на дневном периоде
double ima = iMA (NULL, PERIOD_D1, MA_Period, 0, MODE_SMA, PRICE_CLOSE, i);
// А теперь проверим всловия для наличия сигналов
// есть сигнал на покупку?
if (Low[i] > ima && MacdCurrent SignalCurrent &&
MacdPrevious (MACDOpenLevel*Point))
// Да, есть сигнал на покупку
return(BUY);
if (High[i] 0 && MacdCurrent SignalPrevious &&
MathAbs(MacdCurrent) > (MACDOpenLevel*Point))
// Да, есть сигнал на продажу
return(SELL);
// Сигнала нет
return(-1);
}
При получении показаний с индикатора в качестве параметров я намеренно указал переменные (fast_ema_period, slow_ema_period, signal_period и т.д.), а не вписал жёсткие значения. Это позволит нам гибко подбирать параметры при тестировании будущего советника. Следовательно эти параметры необходимо определить и сделать их внешними:
extern int MA_Period = 100; // extern double MACDOpenLevel = 5; extern int fast_ema_period = 8; // период быстрой средней extern int slow_ema_period = 16; // период медленной средней extern int signal_period = 9; // период сигнальной линии
Дополнительно в этом блоке определена переменная MACDOpenLevel. Она нам понадобится для фильтрации от ложных сигналов.
Собственно, остаётся написать небольшой код в функции OnCalculate():
//+------------------------------------------------------------------+
//| Custom indicator iteration function | //+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
// Вычислим кол-во не измененных баров с последнего вызова индикатора
int counted_bars = IndicatorCounted();
int limit, signal;
//Пересчитаем последний посчитанный бар
if (counted_bars>0)
counted_bars-- ;
//Определяем число баров, которые следует пересчитать
limit=Bars-counted_bars;
// Чтобы индикатор "не перерисовывался" расчет ведем с последнего закрытого бара(он имеет индекс равный 1)
// нулевой бар является текущим, т.е. он изменяется с каждым новым тиком до тех пор пока не будет закрыт
for(int i = 2; i
Теперь подробней:
Функция IndicatorCounted() -возвращает количество баров, не измененных после последнего вызова индикатора.
Если раньше индикатор не был присоединён к окну финансового инструмента, то при первом исполнении функции OnCalculate значение переменной counted_bars будет равно нулю:
// Вычислим кол-во не измененных баров с последнего вызова индикатора
int counted_bars = IndicatorCounted();
Это означает, что индикаторный массив не содержит ни одного элемента с ранее определённым значением, поэтому есть необходимость пересчитать весь индикаторный массив от начала до конца. Расчёт массива производится в направлении от самого старого бара к нулевому. Индекс самого старого бара, начиная с которого необходимо производить вычисления, рассчитывается так:
//Определяем число баров, которые следует пересчитать
limit=Bars-counted_bars;
Предположим, что в момент запуска в окне финансового инструмента имеется 200 баров. Эта величина является значением предопределённой переменной Bars. Ранее определённое значение counted_bars равно 0. В результате получится, что индекс i первого не посчитанного бара (самого старого бара, начиная с которого необходимо начать расчёт) равен 199.
Далее в цикле мы проверяем значения индикатора через функцию Signal() и при наличии сигнала записываем в буфер индикатора значение цены текущего пересчитанного бара:
for(int i = 2; i
Именно так и получаются сигнальные стрелки на графике самого индикатора:

И полный текст индикатора:
//+------------------------------------------------------------------+
//| MyIndicator.mq4 | //| xbms | //| mailto:[email protected] | //+------------------------------------------------------------------+
#property copyright " xbms"
#property link "mailto:[email protected]"
#property version "1.00"
#property strict
#property indicator_chart_window // Индик. рисуется в основном окне
#property indicator_buffers 2 // Сообщаем о том, что у нас будет два буфера
#property indicator_color1 Lime // Цвет стрелки для покупок
#property indicator_color2 Red // Цвет стрелки для продаж
double Buy[]; // Буфер для покупок
double Sell[]; // Буфер для продаж
#define BUY 0
#define SELL 1
extern int MA_Period = 100;
extern double MACDOpenLevel = 5;
extern int fast_ema_period = 8; // период быстрой средней
extern int slow_ema_period = 16; // период медленной средней
extern int signal_period = 9; // период сигнальной линии
//+------------------------------------------------------------------+
//| Custom indicator initialization function | //+------------------------------------------------------------------+
int OnInit()
{
// Установим связь наших массивов с буферами индикатора
SetIndexBuffer (0, Buy);
SetIndexBuffer (1, Sell);
// Устанавливаем нулевые значения для индикатора, при которых не будет сигнальных стрелок
SetIndexEmptyValue (0, 0);
SetIndexEmptyValue (1, 0);
//Определяем стиль отображения индикаторных линий - стрелка
SetIndexStyle (0, DRAW_ARROW);
SetIndexStyle (1, DRAW_ARROW);
// Установим значки "стрелки" для буферов
SetIndexArrow(0, 233); // Стрелка "вверх" для покупок
SetIndexArrow(1, 234); // Стрелка "вниз" для продаж
//Определяем разрядность значений индикаторных линий - приравниваем разрядности фин. инструмента
IndicatorDigits (Digits);
//Строка с кратким названием индикатора выводится в сплывающей подсказке при наведении указателя мыши на стрелку
IndicatorShortName ("Мой первый индикатор");
//Устанавливаем текст описания стрелок индикатора для отображения информации в всплывающей подсказке.
SetIndexLabel(0, "Покупаем");
SetIndexLabel(1, "Продаём");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function | //+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
// Вычислим кол-во не измененных баров с последнего вызова индикатора
int counted_bars = IndicatorCounted();
int limit, signal;
//Пересчитаем последний посчитанный бар
if (counted_bars>0)
counted_bars-- ;
//Определяем число баров, которые следует пересчитать
limit=Bars-counted_bars;
// Чтобы индикатор "не перерисовывался" расчет ведем с последнего закрытого бара(он имеет индекс равный 1)
// нулевой бар является текущим, т.е. он изменяется с каждым новым тиком до тех пор пока не будет закрыт
for(int i = 2; i ima && MacdCurrent SignalCurrent &&
MacdPrevious (MACDOpenLevel*Point))
// Да, есть сигнал на покупку
return(BUY);
if (High[i] 0 && MacdCurrent SignalPrevious &&
MathAbs(MacdCurrent) > (MACDOpenLevel*Point))
// Да, есть сигнал на продажу
return(SELL);
// Сигнала нет
return(-1);
}
//+------------------------------------------------------------------+
На этом, пожалуй, все. На одном из следующих уроков мы рассмотрим советник на нашем индикаторе.
Исходный текст нашего индикатора доступен для скачивания.
До новых встреч!
Один комментарий к “Урок №11: Пишем индикатор”