На этом уроке мы напишем простой индикатор, который в дальнейшем будем использовать при разработке советника.
А использовать для получения сигнала мы будем уже существующие — 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 < 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); } //+------------------------------------------------------------------+
На этом, пожалуй, все. На одном из следующих уроков мы рассмотрим советник на нашем индикаторе.
Исходный текст нашего индикатора доступен для скачивания.
До новых встреч!
Один комментарий к “Урок №11: Пишем индикатор”