В качестве примера рассмотрим создание индикатора, который будет реализовывать форекс стратегию «Impulse keeper» (Ловец импульсов) и показывать на графике сигналы на покупку и продажу.
В данной стратегии применяются четыре индикатора:
Экспоненциальная скользящая средняя с периодом 34 для цены High.
Экспоненциальная скользящая средняя с периодом 34 для цены Low.
Экспоненциальная скользящая средняя с периодом 125 для цены Close.
Parabolic SAR.
Сигналы на покупку и продажу в данной стратегии описываются следующим образом.
Сигнал на покупку: зеленая свеча закрывается выше EMA34 High и EMA34 Low, зеленая свеча выше EMA125 и Parabolic SAR.
Сигнал на продажу: красная свеча закрывается ниже EMA34 Low и EMA34 High, красная свеча ниже EMA125 и Parabolic SAR.
Давайте, реализуем эту стратегию в коде, который будет отображать на графике стрелки вверх и вниз сигналов на покупку и продажу.
Откроем MQL5 редактор и в меню File выберем New. В диалоговом окне MQL Wizard выберем Custom Indicator и нажмем кнопку Далее. Введем имя индикатора Impulse keeper, имя автора и ссылку и нажмем два раза Далее, а затем Готово.
В результате мы получим код индикатора с пустыми функциями OnInit и OnCalculate.
Создание индикатора начнем с определения его свойств.
Количество буферов индикатора определим 8.
2 буфера – данные и цвет, для сигналов на покупку. 2 буфера – данные и цвет, для сигналов на продажу. И 4 буфера промежуточных вычислений для скопированных данных из индикаторов EMA34 Low, EMA34 High, EMA125 и Parabolic SAR:
#property indicator_buffers 8
Определим число графических построений – 2, одно построение для сигналов на покупку и другое построение для сигналов на продажу:
#property indicator_plots 2
Определим цвет и тип для обоих графических построений:
#property indicator_color1 clrGreen, clrBlack
#property indicator_type1 DRAW_COLOR_ARROW
#property indicator_color2 clrRed, clrBlack
#property indicator_type2 DRAW_COLOR_ARROW
Далее определим массивы буферов индикатора и хэндлы используемых индикаторов:
double IKBuyBuffer [];
double ColorIKBuyBuffer [];
double IKSellBuffer [];
double ColorIKSellBuffer [];
double EMA34HBuffer [];
double EMA34LBuffer [];
double EMA125Buffer [];
double PSARBuffer [];
int EMA34HHandle;
int EMA34LHandle;
int EMA125Handle;
int PSARHandle;
В функции OnInit () для первого графического построения определим тип стрелки – стрелка вверх, пустое значение и сдвиг:
int OnInit ()
{
PlotIndexSetInteger (0,PLOT_ARROW,233);
PlotIndexSetDouble (0,PLOT_EMPTY_VALUE,0);
PlotIndexSetInteger (0,PLOT_ARROW_SHIFT, -10);
Для второго графического построения определим тип стрелки – стрелка вниз, пустое значение и сдвиг:
PlotIndexSetInteger (1,PLOT_ARROW,234);
PlotIndexSetDouble (1,PLOT_EMPTY_VALUE,0);
PlotIndexSetInteger (1,PLOT_ARROW_SHIFT,10);
Свяжем массивы с буферами индикатора:
SetIndexBuffer (0,IKBuyBuffer, INDICATOR_DATA);
SetIndexBuffer (1,ColorIKBuyBuffer, INDICATOR_COLOR_INDEX);
SetIndexBuffer (2,IKSellBuffer, INDICATOR_DATA);
SetIndexBuffer (3,ColorIKSellBuffer, INDICATOR_COLOR_INDEX);
SetIndexBuffer (4,EMA34HBuffer, INDICATOR_CALCULATIONS);
SetIndexBuffer (5,EMA34LBuffer, INDICATOR_CALCULATIONS);
SetIndexBuffer (6,EMA125Buffer, INDICATOR_CALCULATIONS);
SetIndexBuffer (7,PSARBuffer, INDICATOR_CALCULATIONS);
Получим хэндлы используемых индикаторов:
EMA34HHandle=iMA (NULL,0,34,0,MODE_EMA, PRICE_HIGH);
EMA34LHandle=iMA (NULL,0,34,0,MODE_EMA, PRICE_LOW);
EMA125Handle=iMA (NULL,0,125,0,MODE_EMA, PRICE_CLOSE);
PSARHandle=iSAR (NULL,0,0.02, 0.2);
В функции OnCalculate () произведем проверку размера доступной истории для расчета используемых индикаторов, определим количество копируемых значений используемых индикаторов и определим стартовую позицию расчета индикатора:
int values_to_copy;
int start;
int calculated=BarsCalculated (EMA34HHandle);
if (calculated <=0)
{
return (0);
}
if (prev_calculated==0 || calculated!=bars_calculated)
{
start=1;
if (calculated> rates_total) values_to_copy=rates_total;
else values_to_copy=calculated;
}
else
{
start=rates_total-1;
values_to_copy=1;
}
Переменную bars_calculated определим как глобальную int bars_calculated=0; в свойствах индикатора.
Далее произведем копирование из буферов используемых индикаторов в массивы буферов нашего индикатора:
if (!FillArrayFromMABuffer (EMA34HBuffer,0,EMA34HHandle, values_to_copy)) return (0); if (!FillArrayFromMABuffer (EMA34LBuffer,0,EMA34LHandle, values_to_copy)) return (0); if (!FillArrayFromMABuffer (EMA125Buffer,0,EMA125Handle, values_to_copy)) return (0);
if (!FillArrayFromPSARBuffer (PSARBuffer, PSARHandle, values_to_copy)) return (0);
Здесь FillArrayFromMABuffer и FillArrayFromPSARBuffer – пользовательские функции, определенные вне функции OnCalculate ():
//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +
bool FillArrayFromPSARBuffer (
double &sar_buffer [], // индикаторный буфер значений Parabolic SAR
int ind_handle, // хэндл индикатора iSAR
int amount // количество копируемых значений
)
{
ResetLastError ();
if (CopyBuffer (ind_handle,0,0,amount, sar_buffer) <0)
{
return (false);
}
return (true);
}
//+ – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – — – +
bool FillArrayFromMABuffer (
double &values [], // индикаторный буфер значений Moving Average
int shift, // смещение
int ind_handle, // хэндл индикатора iMA
int amount // количество копируемых значений
)
{
ResetLastError ();
if (CopyBuffer (ind_handle,0, -shift, amount, values) <0)
{
return (false);
}
return (true);
}
Далее в функции OnCalculate () заполним буфера индикатора данными и цветом:
for (int i=start; i { IKBuyBuffer [i-1] =0; ColorIKBuyBuffer [i-1] =1; IKSellBuffer [i-1] =0; ColorIKSellBuffer [i-1] =1; if (close [i-1]> open [i-1] &&close [i-1]> EMA34HBuffer [i-1] &&close [i-1]> EMA34LBuffer [i-1] &&low [i-1]> EMA125Buffer [i-1] &&low [i-1]> PSARBuffer [i-1] &&EMA125Buffer [i-1] IKBuyBuffer [i-1] =high [i-1]; ColorIKBuyBuffer [i-1] =0; } if (close [i-1] IKSellBuffer [i-1] =low [i-1]; ColorIKSellBuffer [i-1] =0; } } bars_calculated=calculated; // – - return value of prev_calculated for next call return (rates_total); } Здесь мы рассчитываем индикатор на предыдущем баре, так как на текущем баре цена close – это текущая цена тика. Откомпилируем код и присоединим индикатор к графику: Мы увидим, что, в общем и целом, индикатор дает верные сигналы на продажу и покупку, хотя в некоторых случаях он запаздывает и дает ложные сигналы: Как мы видим, происходит это из-за трендовой линии EMA125. Попробуем отвязать ее от текущего периода и попробуем определять тренд, скажем по дневному графику: EMA125Handle=iMA (NULL, PERIOD_D1,125,0,MODE_EMA, PRICE_CLOSE); При этом запаздывание, конечно, сократится, но количество ложных сигналов увеличится: Видимо для улучшения данной стратегии, нужно привлекать дополнительные индикаторы. Попробуем сделать этот же самый индикатор, но не с помощью графических построений, а помещая графические объекты на график символа. В свойствах индикатора теперь не нужно объявлять буфера данных и цвета индикатора и графические серии для них. Оставим только буфера индикатора для промежуточных расчетов и хэндлы используемых индикаторов: #property indicator_chart_window #property indicator_buffers 4 double EMA34HBuffer []; double EMA34LBuffer []; double EMA125Buffer []; double PSARBuffer []; int EMA34HHandle; int EMA34LHandle; int EMA125Handle; int PSARHandle; int bars_calculated=0; В функции OnInit () соответственно оставим только привязку массивов к буферам промежуточных расчетов и получение хэндлов используемых индикаторов: int OnInit () { // – - indicator buffers mapping SetIndexBuffer (0,EMA34HBuffer, INDICATOR_CALCULATIONS); SetIndexBuffer (1,EMA34LBuffer, INDICATOR_CALCULATIONS); SetIndexBuffer (2,EMA125Buffer, INDICATOR_CALCULATIONS); SetIndexBuffer (3,PSARBuffer, INDICATOR_CALCULATIONS); EMA34HHandle=iMA (NULL,0,34,0,MODE_EMA, PRICE_HIGH); EMA34LHandle=iMA (NULL,0,34,0,MODE_EMA, PRICE_LOW); EMA125Handle=iMA (NULL,0,125,0,MODE_EMA, PRICE_CLOSE); PSARHandle=iSAR (NULL,0,0.02, 0.2); // – — return (INIT_SUCCEEDED); } В функции OnCalculate () определим создание объектов на графике символа: 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 values_to_copy; int start; int calculated=BarsCalculated (EMA34HHandle); if (calculated <=0) { return (0); } if (prev_calculated==0 || calculated!=bars_calculated) { start=1; if (calculated> rates_total) values_to_copy=rates_total; else values_to_copy=calculated; } else { start=rates_total-1; values_to_copy=1; } if (!FillArrayFromMABuffer (EMA34HBuffer,0,EMA34HHandle, values_to_copy)) return (0); if (!FillArrayFromMABuffer (EMA34LBuffer,0,EMA34LHandle, values_to_copy)) return (0); if (!FillArrayFromMABuffer (EMA125Buffer,0,EMA125Handle, values_to_copy)) return (0); if (!FillArrayFromPSARBuffer (PSARBuffer, PSARHandle, values_to_copy)) return (0); for (int i=start; i { if (close [i-1]> open [i-1] &&close [i-1]> EMA34HBuffer [i-1] &&close [i-1]> EMA34LBuffer [i-1] &&low [i-1]> EMA125Buffer [i-1] &&low [i-1]> PSARBuffer [i-1] &&EMA125Buffer [i-1] if (!ObjectCreate (0,«Buy»+i, OBJ_ARROW,0,time [i-1],high [i-1])) { return (false); } ObjectSetInteger (0,«Buy»+i, OBJPROP_COLOR, clrGreen); ObjectSetInteger (0,«Buy»+i, OBJPROP_ARROWCODE,233); ObjectSetInteger (0,«Buy»+i, OBJPROP_WIDTH,2); ObjectSetInteger (0,«Buy»+i, OBJPROP_ANCHOR, ANCHOR_UPPER); ObjectSetInteger (0,«Buy»+i, OBJPROP_HIDDEN, true); ObjectSetString (0,«Buy»+i, OBJPROP_TOOLTIP,»»+close [i-1]); } if (close [i-1] if (!ObjectCreate (0,«Sell»+i, OBJ_ARROW,0,time [i-1],low [i-1])) { return (false); } ObjectSetInteger (0,«Sell»+i, OBJPROP_COLOR, clrRed); ObjectSetInteger (0,«Sell»+i, OBJPROP_ARROWCODE,234); ObjectSetInteger (0,«Sell»+i, OBJPROP_WIDTH,2); ObjectSetInteger (0,«Sell»+i, OBJPROP_ANCHOR, ANCHOR_LOWER); ObjectSetInteger (0,«Sell»+i, OBJPROP_HIDDEN, true); ObjectSetString (0,«Sell»+i, OBJPROP_TOOLTIP,»»+close [i-1]); } } bars_calculated=calculated; // – - return value of prev_calculated for next call return (rates_total); } Здесь функцией ObjectCreate создаются объекты стрелка, привязанные ко времени и максимальной или минимальной цене. Функцией ObjectSetInteger со свойством OBJPROP_COLOR определяется цвет стрелки. Функцией ObjectSetInteger со свойством OBJPROP_ARROWCODE определяется направление стрелки вверх или вниз. Функцией ObjectSetInteger со свойством OBJPROP_WIDTH определяется размер объекта. Функцией ObjectSetInteger со свойством OBJPROP_ANCHOR определяется привязка к цене сверху или снизу по центру. Функцией ObjectSetInteger со свойством OBJPROP_HIDDEN – true определяется отсутствие созданных объектов в списке объектов графика символа. Функцией ObjectSetString со свойством OBJPROP_TOOLTIP определяется содержание всплывающей подсказки при наведении указателя на объект. В функции OnDeinit () уберем все добавленные графические объекты: void OnDeinit (const int reason) { ObjectsDeleteAll (0, -1, -1); } Более подробно о создании объектов на графике символа мы поговорим далее.