Урок №11: Пишем индикатор

На этом уроке мы напишем простой индикатор, который в дальнейшем будем использовать при разработке советника.

А использовать для получения сигнала мы будем уже существующие — Moving Average и MACD. Алгоритм следующий:

Определять направление сделки (покупка или продажа) мы будем по индикатору Moving Average с периодом 100 на дневном графике(Daily), т.е. если текущая цена выше MA(100), то покупаем, а если ниже — продаём.

А для поиска удачных точек входа воспользуемся индикатором MACD.

EURAUD

вход в покупки– индикатор 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 < 0 && MacdCurrent > SignalCurrent && 
                       MacdPrevious < SignalPrevious && MathAbs(MacdCurrent) > (MACDOpenLevel*Point))
      // Да, есть сигнал на покупку
      return(BUY);

   if (High[i] < ima && MacdCurrent > 0 && MacdCurrent < SignalCurrent && MacdPrevious > 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 < limit; i++)  
   {
         //Проверяем сигнал на вход для текущего бара
         signal = Signal(i-1);
         
         if (signal == BUY)
         {
            Buy[i-1] = low[i-1];
         }
         else   
         if (signal == SELL)
         {
            Sell[i-1] = high[i-1];
         }
   }
   
   return(rates_total);
}

Теперь подробней:

Функция 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 < limit; i++)  
{
    //Проверяем сигнал на вход для текущего бара
    signal = Signal(i-1);
         
    if (signal == BUY)
    {
       Buy[i-1] = low[i-1];
    }
    else   
    if (signal == SELL)
    {
       Sell[i-1] = high[i-1];
    }
}

Именно так и получаются сигнальные стрелки на графике самого индикатора:

Сигналы индикатора

 

И полный текст индикатора:

//+------------------------------------------------------------------+
//|                                                  MyIndicator.mq4 |
//|                                             Copyright 2017, xbms |
//|                                              mailto:xbms@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, xbms"
#property link      "mailto:xbms@mail.ru"
#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 < limit; i++) 
{
//Проверяем сигнал на вход для текущего бара 
signal = Signal(i-1);
if (signal == BUY)
{ 
Buy[i-1] = low[i-1];
}
else
if (signal == SELL)
{
Sell[i-1] = high[i-1];
}
}
return(rates_total); } 
//+------------------------------------------------------------------+ 
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 < 0 && MacdCurrent > SignalCurrent && 
MacdPrevious < SignalPrevious && 
MathAbs(MacdCurrent) > (MACDOpenLevel*Point))
// Да, есть сигнал на покупку
return(BUY);
if (High[i] < ima && MacdCurrent > 0 && MacdCurrent < SignalCurrent &&
MacdPrevious > SignalPrevious && 
MathAbs(MacdCurrent) > (MACDOpenLevel*Point))
// Да, есть сигнал на продажу
return(SELL);
// Сигнала нет   
return(-1);
}
//+------------------------------------------------------------------+

На этом, пожалуй, все. На одном из следующих уроков мы рассмотрим советник на нашем индикаторе.

Исходный текст нашего индикатора доступен для скачивания.

До новых встреч!

Written by 

Один комментарий к “Урок №11: Пишем индикатор”

Добавить комментарий