Процитируем справочник:
Событие Deinit генерируется для экспертов и индикаторов в следующих случаях:
– перед переинициализацией в связи со сменой символа или периода графика, к которому прикреплена mql5-программа;
– перед переинициализацией в связи со сменой входных параметров;
– перед выгрузкой mql5-программы.
Так как функция OnDeinit () вызывается при деинициализации, то ее основное предназначение, это освобождение занимаемых ресурсов.
Под освобождением занимаемых ресурсов для индикатора подразумевается очищение графика символа от дополнительных графических объектов.
То есть помимо диаграммы индикатора, мы можем присоединять к графику символа различные объекты – линии, графические фигуры треугольник, прямоугольник и эллипс, знаки, подписи и др. Об этом мы поговорим позже.
Соответственно при деинициализации индикатора было бы неплохо все это убрать с графика символа.
Первым делом здесь используется функция Comment, которая выводит комментарий, определенный пользователем, в левый верхний угол графика:
void Comment (
argument, // первое значение
…// последующие значения
);
Для очистки от комментариев используются пустые комментарии:
Comment (»»);
Далее используется функция ObjectDelete, которая удаляет объект с указанным именем с указанного графика:
bool ObjectDelete (
long chart_id, // chart identifier
string name // object name
);
Позже мы продемонстрируем применение этих функций.
Функция OnCalculate () вызывается клиентским терминалом при поступлении нового тика по символу, для которого рассчитывается индикатор.
Хотя функция OnCalculate () имеет два вида – для индикатора, который может быть рассчитан на основе только одной из ценовых таймсерий:
int OnCalculate (const int rates_total, // размер массива price []
const int prev_calculated, // обработано баров на предыдущем вызове
const int begin, // откуда начинаются значимые данные
const double& price [] // массив для расчета
);
и для индикатора, который рассчитывается с использованием нескольких ценовых таймсерий:
int OnCalculate (const int rates_total, // размер входных таймсерий
const int prev_calculated, // обработано баров на предыдущем вызове
const datetime& time [], // Time
const double& open [], // Open
const double& high [], // High
const double& low [], // Low
const double& close [], // Close
const long& tick_volume [], // Tick Volume
const long& volume [], // Real Volume
const int& spread [] // Spread
);
Здесь мы будем пользоваться полной версией функции OnCalculate () как наиболее гибкой и предоставляющей наибольшие возможности.
Единственное, что мы должны отметить об усеченной функции OnCalculate (), это то, что она имеет опцию использования в качестве массива price [] рассчитанного буфера другого индикатора.
Продемонстрируем это на примере индикатора MACD и индикатора Custom Moving Average, который использует как раз усеченную функцию OnCalculate ().
Присоединим сначала индикатор MACD к графику символа, а затем перетащим индикатор MA в окно индикатора MACD:
Затем опять перетащим индикатор Custom Moving Average в окно индикатора MACD, при этом снова откроется окно параметров индикатора Custom Moving Average:
В списке выбора, что использовать в качестве массива price [], будут пункты First Indicator’s Data и Previous Indicator’s Data.
Здесь пункт First Indicator’s Data означает, что в качестве массива price [] будет использоваться массив ExtMacdBuffer буфера индикатора MACD, а пункт Previous Indicator’s Data означает, что в качестве массива price [] будет использоваться массив ExtLineBuffer буфера индикатора MA.
Если в функцию OnCalculate индикатора Custom Moving Average добавить:
Print («begin», begin);
То при выборе First Indicator’s Data будет выводиться:
А при выборе Previous Indicator’s Data будет выводиться:
В первом случае, begin=0, так как для буфера ExtMacdBuffer индикатора MACD функция PlotIndexSetInteger с параметром PLOT_DRAW_BEGIN не вызывается. А во втором случае, begin=12, так как для буфера ExtLineBuffer индикатора MA вызывается функция PlotIndexSetInteger:
PlotIndexSetInteger (0,PLOT_DRAW_BEGIN, InpMAPeriod-1+begin);
Тут говорится о том, что массив буфера ExtLineBuffer индикатора MA заполняется, начиная с InpMAPeriod-1 бара, соответственно значение переменной begin функции OnCalculate индикатора Custom Moving Average будет также равно InpMAPeriod-1.
Вернемся к полной версии функции OnCalculate ().
Как правило, код функции OnCalculate () проектируется таким образом, чтобы при загрузке индикатора и первом вызове функции OnCalculate (), буфера индикатора были рассчитаны на основе всей загруженной ценовой истории, а при последующем поступлении нового тика и вызове функции OnCalculate (), рассчитывалось бы только одно новое значение, которое добавляется в конец массива буфера индикатора.
Но в начале кода функции OnCalculate () нужно конечно проверить, достаточный ли размер ценовой истории был загружен при загрузке индикатора.
Для этого проверяется значение переменной rates_total – размер входных таймсерий.
Как правило, в качестве порогового значения для rates_total принимается значение периода индикатора, например для индикатора ADX:
// – - checking for bars count
if (rates_total <ExtADXPeriod)
return (0);
Если же в расчете буфера индикатора участвует хэндл другого индикатора, тогда проверяется количество рассчитанных данных для запрашиваемого индикатора:
// – - узнаем количество рассчитанных значений в индикаторе
int calculated=BarsCalculated (handle);
if (calculated <=0)
{
PrintFormat («BarsCalculated () вернул %d, код ошибки %d», calculated, GetLastError ());
return (0);
}
После проверки первоначальной загруженной истории для расчетов, вычисляется размер данных, которые необходимо рассчитать в этом вызове функции 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 calculated=BarsCalculated (handle);
if (calculated <=0)
{
PrintFormat («BarsCalculated () вернул %d, код ошибки %d», calculated, GetLastError ());
return (0);
}
// – - если это первый запуск вычислений нашего индикатора или изменилось количество значений в индикаторе
// – - или если необходимо рассчитать индикатор для двух или более баров (значит что-то изменилось в истории)
if (prev_calculated==0 || calculated!=bars_calculated || rates_total> prev_calculated+1)
{
// – - если массив больше, чем значений в индикаторе на паре symbol/period, то копируем не все
// – - в противном случае копировать будем меньше, чем размер индикаторных буферов
if (calculated> rates_total) values_to_copy=rates_total;
else values_to_copy=calculated;
}
else
{
// – - значит наш индикатор рассчитывается не в первый раз и с момента последнего вызова OnCalculate ())
// – - для расчета добавилось не более одного бара
values_to_copy= (rates_total-prev_calculated) +1;
}
// – - запомним количество значений в индикаторе
bars_calculated=calculated;
// – - вернем значение prev_calculated для следующего вызова
return (rates_total);
}
Здесь переменная values_to_copy – количество рассчитываемых значений в вызове функции OnCalculate ().
Переменная prev_calculated – сколько было обработано баров функцией OnCalculate () при предыдущем вызове.
Таким образом, при загрузке индикатора prev_calculated=0, а при каждом следующем поступлении нового тика prev_calculated= rates_total.
Переменная prev_calculated также обнуляется терминалом, если вдруг изменилось значение переменной rates_total.
Переменная bars_calculated – предыдущее количество рассчитанных данных для запрашиваемого индикатора, на основе которого рассчитывается данный индикатор.
Таким образом, первая проверка здесь:
prev_calculated==0 – индикатор только что загрузился или изменилась ценовая история.
calculated!=bars_calculated – изменилось количество рассчитанных данных для запрашиваемого индикатора.
rates_total> prev_calculated+1 – необходимо рассчитать индикатор для двух или более баров (значит, что-то изменилось в истории).
Последнее условие вступает в противоречие с утверждением справочника:
Если с момента последнего вызова функции OnCalculate () ценовые данные были изменены (подкачана более глубокая история или были заполнены пропуски истории), то значение входного параметра prev_calculated будет установлено в нулевое значение самим терминалом.
Если изменилась история, тогда сработает проверка prev_calculated==0 и проверка последнего условия будет излишней.
Теперь, если срабатывает первое или второе условие, тогда количество рассчитываемых значений – это размер входных таймсерий или количество рассчитанных данных для запрашиваемого индикатора, на основе которого рассчитывается данный индикатор, что из них меньше.
Если же первое или второе условие не срабатывают, тогда количество рассчитываемых значений:
values_to_copy= (rates_total-prev_calculated) +1;
Опять же, тут есть излишний код, так как, судя по справочнику, переменная prev_calculated может принимать значение либо 0, либо rates_total.
Поэтому, values_to_copy=1.
Таким образом, при поступлении нового тика, будет рассчитываться только одно значение индикатора для этого нового тика.
Рассмотрим другую реализацию вычисления размера данных, которые необходимо рассчитать в вызове функции OnCalculate ().
Для индикатора MACD это реализовано следующим образом:
// – - we can copy not all data
int to_copy;
if (prev_calculated> rates_total || prev_calculated <0) to_copy=rates_total;
else
{
to_copy=rates_total-prev_calculated;
if (prev_calculated> 0) to_copy++;
}
Опять же, судя по справочнику, здесь будет работать только код:
to_copy=rates_total-prev_calculated;
if (prev_calculated> 0) to_copy++;
Т.е. при загрузке индикатора to_copy=rates_total, а затем to_copy=1.
После вычисления размера данных, которые необходимо рассчитать в вызове функции OnCalculate (), производится их вычисление и заполнение ими буферов индикатора.
Если индикатор рассчитывается на основе хэндла другого индикатора, тогда производится копирование из буферов используемого индикатора в динамические массивы данного индикатора.
Вот как это реализовано для используемого индикатора ADX:
// – - заполняем часть массива ADXBuffer значениями из индикаторного буфера под индексом 0
if (CopyBuffer (ind_handle,0,0,amount, adx_values) <0)
{
// – - если копирование не удалось, сообщим код ошибки
PrintFormat («Не удалось скопировать данные из индикатора iADX, код ошибки %d», GetLastError ());
// – - завершим с нулевым результатом – это означает, что индикатор будет считаться нерассчитанным
return (false);
}
// – - заполняем часть массива DI_plusBuffer значениями из индикаторного буфера под индексом 1
if (CopyBuffer (ind_handle,1,0,amount, DIplus_values) <0)
{
// – - если копирование не удалось, сообщим код ошибки
PrintFormat («Не удалось скопировать данные из индикатора iADX, код ошибки %d», GetLastError ());
// – - завершим с нулевым результатом – это означает, что индикатор будет считаться нерассчитанным
return (false);
}
// – - заполняем часть массива DI_plusBuffer значениями из индикаторного буфера под индексом 2
if (CopyBuffer (ind_handle,2,0,amount, DIminus_values) <0)
{
// – - если копирование не удалось, сообщим код ошибки
PrintFormat («Не удалось скопировать данные из индикатора iADX, код ошибки %d», GetLastError ());
// – - завершим с нулевым результатом – это означает, что индикатор будет считаться нерассчитанным
return (false);
}
Здесь ind_handle – это хэндл индикатора ADX, второй параметр – индекс буфера используемого индикатора, из которого производится копирование, третий параметр – стартовая позиция, откуда начинается копирование. Здесь мы помним, что копирование идет от конца к началу, и поэтому нулевая стартовая позиция – это самые свежие данные. Четвертый параметр – это наш размер данных, которые необходимо рассчитать в вызове функции OnCalculate (), и последний параметр – это обычно динамический массив, привязанный к буферу индикатора, куда производится копирование.
Тут есть вопрос, как связать второй параметр функции CopyBuffer с индексом буфера используемого индикатора.
Это определяется вызовом функции SetIndexBuffer в используемом индикаторе. Например, для индикатора ADX:
SetIndexBuffer (0,ExtADXBuffer);
SetIndexBuffer (1,ExtPDIBuffer);
SetIndexBuffer (2,ExtNDIBuffer);
Отсюда нулевой индекс связан с буфером самого индикатора ADX, 1 индекс связан с буфером индикатора направленности +DI, 2 индекс связан с буфером индикатора направленности —DI.
Таким образом, для связывания второго параметра функции CopyBuffer с индексом буфера используемого индикатора, нужно знать код используемого индикатора.
Также для заполнения буфера индикатора значениями, может использоваться цикл, например:
for (int i=start; i <rates_total &&!IsStopped ();i++)
{
}
Здесь start – это стартовая позиция, с которой начинается заполнение буфера индикатора.
При значении prev_calculated=0, значение start это, как правило, 0, при значении prev_calculated= rates_total, зачение start=prev_calculated-1.
Если же перед реализацией цикла с помощью функции ArraySetAsSeries поменять порядок доступа к массивам буферов индикатора и цен, тогда цикл примет вид:
for (int i=start; i> =0;i – ) {
}
Где start=rates_total-1, если prev_calculated=0, и start=0, если prev_calculated= rates_total.
В качестве примера рассмотрим создание индикатора, который будет реализовывать форекс стратегию «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 <rates_total &&!IsStopped ();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] <EMA34LBuffer [i-1] &&EMA125Buffer [i-1] <EMA34HBuffer [i-1]) {
IKBuyBuffer [i-1] =high [i-1];
ColorIKBuyBuffer [i-1] =0;
}
if (close [i-1] <open [i-1] &&close [i-1] <EMA34HBuffer [i-1] &&close [i-1] <EMA34LBuffer [i-1] &&high [i-1] <EMA125Buffer [i-1] &&high [i-1] <PSARBuffer [i-1] &&EMA125Buffer [i-1]> EMA34LBuffer [i-1] &&EMA125Buffer [i-1]> EMA34HBuffer [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 <rates_total &&!IsStopped ();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] <EMA34LBuffer [i-1] &&EMA125Buffer [i-1] <EMA34HBuffer [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] <open [i-1] &&close [i-1] <EMA34HBuffer [i-1] &&close [i-1] <EMA34LBuffer [i-1] &&high [i-1] <EMA125Buffer [i-1] &&high [i-1] <PSARBuffer [i-1] &&EMA125Buffer [i-1]> EMA34LBuffer [i-1] &&EMA125Buffer [i-1]> EMA34HBuffer [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);
}
Более подробно о создании объектов на графике символа мы поговорим далее.