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);
}