После рассмотрения встроенных в MetaTrader массивов для хранения и работы с историческими данными, перейдём к вопросу взаимодействия пользовательской программы с исполняющей средой MetaTrader’а, а именно - к рассмотрению массивов для отрисовки индикаторов.
В прошлой статье мы, рассматривая исходный код остова индикатора, приготовленного с помощью мастера создания советников, обратили внимание на наличие глобального массива ExtMapBuffer1. Вернёмся к более детальнму рассмотрению этой темы.
По аналогии с рассмотренными выше массивами исторических данных, данные для отрисовки пользовательских индикаторов тоже хранятся во внутренних массивах, выделенных MetaTrader’ом. Как мы уже знаем из прошлой статьи, таких массивов может быть выделено до восьми. Но заранее распределять память в MetaTrader’е под эти массивы было бы не слишком экономно, ведь не каждый индикатор будет отрисовывать все восемь графиков. Это означает, что при запуске индикатора, MetaTrader должен быть явно проинформирован, какое количество массивов он должен выделить под данные индикатора и, что не менее важно, какое количество массивов MetaTrader будет учитывать, отрисовывая индикатор на графике.
Программист может сообщить эту информацию через свойство индикатора indicator_buffers. Далее, для наглядности, будем считать, что мы установили это свойство равным трём.
Но само по себе объявление количества доступных индикатору массивов ничего не даст. Даже объявление трёх переменных, которые должны содержать данные индикатора, ничем не поможет. Объявленные массивы необходимо связать с выделенной под них памятью для того, чтобы сделать их действительно актуальными при отрисовке индикатора испольняющей средой MetaTradder’а.
Связывание пользовательской программы с этими массивами происходит с помощью вызова функции SetIndexBuffer. Первым параметром передаётся номер выделенного массива, вторым - имя переменной, объявленной как массив без элементов.
На рисунке показана ситуация, которая случится после единственного вызова этой функции, как показано ниже:
SetIndexBuffer(0,ExtMapBuffer1);
Тем самым осуществляется связывание переменной ExtMapBuffer1 с первым (с номером "0") массивом, выделенным исполняющей средой. Заметим, что в соответствии со свойством индикатора indicator_buffers равным трём, два выделенных массива останутся не связанными.
Если мы попробуем обратиться к элементам переменной массивов ExtMapBuffer2 и ExtMapBuffer3, не связанных с распределённой MetatRader’ом памятью, ничего не получится. Все наши усилия не будут видны на экране терминала. Как вы уже, наверное, догадались, эти массивы тоже надо связать с распределённой MetaTrader’ом памятью, используя следующие вызовы:
SetIndexBuffer(1,ExtMapBuffer2);
SetIndexBuffer(2,ExtMapBuffer3);
Теперь давайте разберёмся, как правильно заполнять массивы, доступные нам посредством связанных с ними
переменных нашей программы. Обратимся к остову индикатора, который был подготовлен для нас мастером создания советников, и рассмотрим подробнее функцию start().
int start()
{
int counted_bars=IndicatorCounted();
//---- TODO: add your code here
//----
return(0);
}
Видно, что мастер добавил строку, в которой создаётся и инициализируется целочисленная переменная counted_bars. После вызова функции IndicatorCounted(), переменная будет содержать количество баров, для которых уже подсчитаны значения создаваемого индикатора.
Можно было бы обойтись и без неё, но если мы видим, что мастер так настойчиво предлагает нам пользоваться таким способом работы с данными, то мы не видем пока особого смысла пренебрегать этими рекомендациями.
Если бы мы решили не использовать IndicatorCounted() для выяснения того, для каких баров уже подсчитан индикатор, то нам пришлось бы изобрести свой собственный способ делать это, либо можно было бы каждый раз, когда исполняющей средой MetaTrader’а вызывается функция start(), пересчитывать значения индикатора на всех барах.
Последний способ может сильно нагрузить компьютер вычислениями, и в случае, когда к ценовым графикам прикреплено большое количество индикаторов, игнорирующих вызов функции IndicatorCounted(), мы ощутили бы некоторый дискомфорт из-за замедленной реакции терминала на наши действия.
Для того, чтобы избежать проблем с чрезмерной вычислительной нагрузкой, предлагается при каждом вызове исполняющей средой выяснять, какое количество баров уже было учтено при вычислении значений индикатора на предыдущих вызовах функции start(). Это и делается в первой же строке кода нашей функции start().
После этого, обычно проверяется, не произошло ли при вызове MQL-функции IndicatorCounted() какой-либо ошибки. Если counted_bars меньше, чем ноль, то ошибка произошла, и не имеет смысла дальше продолжать данную итерацию вычисления индикатора.
В случае ошибки мы возвращаемся из функции start() при помощи директивы return. Указывая в качестве параметра директивы return отрицательное значение мы, тем самым, возвращаем это число в исполняющую среду терминала, давая возможность MetaTrader’у понять, что вычисления не произведены или произведены неверно ввиду какой-либо ошибки. Если ошибки не произошло, то можно продолжать вычисления и дальнейший алгоритм работы большинства индикаторов совпадает:
. если количество учтённых баров больше нуля, то отнимаем от него единицу для того, чтобы на этой итерации на всякий случай ещё раз вычислилось значение индикатора, полученное при предыдущем вызове функции start();
. вычисляем количество баров, на которых требуется вычислить значения индикатора (с учётом пересчитываемого бара);
. в цикле выполняем вычисления значения индикатора на требуемых барах и заносим полученные значения в массивы, выделенные исполняющей средой и связанные с некоторыми переменными нашей программы на этапе её инициализации.
Всё вместе это выглядит приблизительно так, как приведено в следующем примере, снабжённом подробными коментариями:
int start()
{
int limit = 0;
// получаем количество учтённых
// на предыдущих итерациях баров
int counted_bars=IndicatorCounted();
// проверяем, возникла ли ошибка
if(counted_bars<0) return(-1);
// последний учтённый бар на
// всякий случай будем пересчитывать
if(counted_bars>0) counted_bars--;
// сколько баров нужно просчитать
// на этот раз?
limit=Bars-counted_bars;
// выполняем основной цикл
// вычисления значений индикатора
for(int i=0; i<limit; i++) {
// вычисляем значения индикатора
// на i-том баре и заносим его
// в связанный со специальной
// памятью MetaTrader.а массив.
// Например, ExtMapBuffer1
}
// возвращаем 0, как код удачного
// завершения вычислений
return(0);
}
На этом мы заканчиваем очередной этап изучения MQL 4, а читателям, до выпуска следующего номера журнала, рекомендуем разобрать исходный код поставляемых с MetaTrader’ом примеров индикаторов с учётом изложенного материала.
Александр Иванов