Массивы
Значительная часть информации, обрабатываемой прикладными программами, заключена
в массивах.
Понятие массив
Массив - это сгруппированный по месту распределения набор значений однотипных переменных, имеющих общее название.
Различают одномерный и многомерный массивы. Максимально допустимое количество измерений
в массиве - четыре. Допускаются массивы любых типов данных.
Элемент массива - это составная часть массива; индексированная переменная с одноимённым названием,
имеющая некоторое значение.
Рис. 59. Графическое представление массивов целого типа: а) одномерный; b) двумерный;
с) трёхмерный.
Индексирование
Индекс элемента массива - одно или несколько целых значений, указанных в виде константы, переменной или
выражения, перечисленных через запятую и заключённых в квадратные скобки. Индекс
элемента массива однозначно определяет место элемента в массиве. Индекс элемента
массива указывается непосредственно после идентификатора переменной (названия массива)
и является неотъемлемой частью элемента массива. В MQL4 принято индексирование,
начинающееся с нуля.
Допускается также способ указания индексов, при котором каждый из индексов обрамляется
квадратными скобками:
Наиболее близким бытовым аналогом двумерного массива является зрительный зал кинотеатра.
Номер ряда в кинотеатре символизирует значение первого индекса, номер места в ряду
- значение второй индекса, зрители - элементы массива, фамилия зрителя - значение
элемента массива, а билет для входа в кинотеатр (с указанием ряда и места) - метод
доступа к значению элемента массива.
Объявление массива и доступ к элементам массива
Перед тем, как массив будет использоваться в программе, его необходимо объявить.
Массив может быть объявлен так же, как и переменная, - на глобальном и локальном
уровне. Соответственно, значения элементов глобального массива доступны всей программе,
а локального массива - только той функции, в которой этот массив объявлен. Массив
не может быть объявлен на уровне клиентского терминала, поэтому глобальные переменные
клиентского терминала не могут быть объединены в массив. Тип значений элементов
массива может быть любым. Значения всех элементов массива имеют одинаковый тип,
а именно тот, который указан при объявлении массива. При объявлении массива указывается
тип данных, название массива и количество элементов в каждом измерении массива:
Доступ к значениям элементов массива осуществляется поэлементно, т.е. одновременно
можно получить доступ только к одному из элементов. Тип значения элемента массива
в программе не указывается. Получение или изменение значения элемента массива может
осуществляться с помощью операторов присваивания:
Значения элементов массивов, показанных на Рис. 59, следующие:
- для одномерного массива значением элемента Mas[4] является целое число 34;
- для двумерного массива значением элемента Mas[3,7] является целое число 28;
- для трёхмерного массива значением элемента Mas[5,4,1] является целое число 77.
|
Обратите внимание, минимальное значение индекса элемента массива - 0 (ноль), а максимальное
значение - на единицу меньше, чем количество элементов в соответствующем измерении,
указанное при объявлении массива. |
Например, для массива Mas[10][15] элементом с минимальными значениями индексов является
элемент Mas[0,0], а с максимальными значениями индексов - элемент Mas[9,14] .
Осуществлять операции с массивами можно также с помощью стандартных функций. Для
получения подробной информации о работе этих функций обратитесь к справочной документации
на сайте производителя (https://docs.mql4.com/ru) или к разделу "Справка" в
редакторе MetaEditor. Некоторые из этих функций
рассматриваются в дальнейшем изложении.
Инициализация массива
Массив может быть инициализирован только константами соответствующего типа. Одномерные
и многомерные массивы инициализируются одномерной последовательностью констант,
перечисленных через запятую. Последовательность обрамляется фигурными скобками:
int Mas_i[3][4] = { 0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23 };
double Mas_d[2][3] = { 0.1, 0.2, -0.3, -10.2, 1.5, 7.0 };
bool Mas_b[5] = { false, true, false, true, true }
В инициализирующей последовательности допускается пропуск одной или нескольких констант.
В этом случае соответствующие элементы массивов числовых типов инициализируются
нолём, а элементы массивов типа string инициализируются строковым значением ""
(открывающая и закрывающая двойная кавычка без промежутка), т.е. пустой строкой
(не путать с пробелом). Следующая программа выводит на
экран значений массивов, инициализированных последовательностью
с пропусками некоторых значений (скрипт
arrayalert.mq4):
int start()
{
string Mas_s[4] = {"a","b", ,"d"};
int Mas_i[6] = { 0,1,2, ,4,5 };
Alert(Mas_s[0],Mas_s[1],Mas_s[2],Mas_s[3]);
Alert(Mas_i[0],Mas_i[1],Mas_i[2],Mas_i[3],Mas_i[4],Mas_i[5]);
return;
}
Если размер одномерного инициализируемого массива не указан, то он определяется
компилятором, исходя из размера инициализирующей последовательности. Инициализация
массива может быть выполнена также с помощью стандартной функции ArrayInitialize().
Все массивы являются статическими, т.е. имеют вид static, даже если при инициализации
это явно не указано. Это значит, что все элементы массива сохраняют свои значения
в промежутке между вызовами на исполнение функции, в которой объявлен массив (см.
Виды переменных).
Все используемые в MQL4 массивы можно разделить на две группы: пользовательские массивы
(создаваемые по инициативе программиста) и массивы - таймсерии (массивы с предопределёнными
именами и типами данных). Определение размеров пользовательских массивов и значений
их элементов зависит от того, как составлена программа и, в конечном счёте, от
воли программиста. Значения элементов пользовательских массивов сохраняется в программе
в течение всего времени исполнения программы и может быть изменено в результате
вычислений. В то же время изменение значений элементов в массивах-таймсериях не
допускается, а их размеры могут увеличиваться при подкачке истории.
Пользовательские массивы
В разделе Оператор switch рассматривалась
Задача 18. Несколько усложним условие (увеличим количество выводимых
словами пунктов до 100) и решим эту задачу, используя массивы.
|
Задача 25. Составить программу, в которой реализуются следующие условия: если
курс поднялся выше заданного уровня, то выдать сообщение, в котором словами обозначено
превышение над уровнем (до 100 пунктов); в остальных случаях сообщить, что курс
не превышает заданный уровень. |
Решение Задачи 25 с использованием строкового массива может быть таким
(эксперт stringarray.mq4):
extern double Level=1.3200;
string Text[101];
int init()
{
Text[1]="один "; Text[15]="пятнадцать ";
Text[2]="два "; Text[16]="шестнадцать ";
Text[3]="три "; Text[17]="семнадцать ";
Text[4]="четыре "; Text[18]="восемнадцать ";
Text[5]="пять "; Text[19]="девятнадцать ";
Text[6]="шесть "; Text[20]="двадцать ";
Text[7]="семь "; Text[30]="тридцать ";
Text[8]="восемь "; Text[40]="сорок ";
Text[9]="девять "; Text[50]="пятьдесят ";
Text[10]="десять "; Text[60]="шестьдесят";
Text[11]="одиннадцать "; Text[70]="семьдесят ";
Text[12]="двенадцать "; Text[80]="восемьдесят ";
Text[13]="тринадцать "; Text[90]="девяносто";
Text[14]="четырнадцать "; Text[100]= "сто";
for(int i=20; i<=90; i=i+10)
{
for(int j=1; j<=9; j++)
Text[i+j]=Text[i] + Text[j];
}
return;
}
int start()
{
int Delta=NormalizeDouble((Bid-Level)/Point,0);
if (Delta<=0)
{
Alert("Цена ниже уровня");
return;
}
if (Delta<100)
{
Alert("Более ста пунктов");
return;
}
Alert("Плюс ",Text[Delta],"pt.");
return;
}
Для решения Задачи 25 используется строковый массив Text[]. Во время исполнения
программы значения элементов этого массива не изменяются. Массив объявлен
на глобальном уровне (за пределами специальных функций), а первоначальное заполнение
массива осуществляется в специальной функции init(). Таким образом, в специальной
функции start() выполняются только те вычисления, которые необходимо исполнить
на каждом тике.
Некоторой части элементов массива Text[] присваиваются значения строковых констант.
Другой части элементов массива присваиваются значения, вычисленные во вложенных
циклах путём суммирования строк.
for (int i = 20; i<=90; i=i+10)
{
for (int j=1; j<=9; j++)
Text[i+j] = Text[i] + Text[j];
}
Смысл этих вычислений понять легко: для каждого элемента массива с индексом, начиная
с 21 и заканчивая 99 (исключая значения индексов, кратных 10), вычисляются соответствующие
строковые значения. Обратите внимание на значения индексов, указанные в строке:
Text[i+j] = Text[i] + Text[j];
В качестве значений индексов используются переменные (значения
которых в цикле изменяются) и выражение. В зависимости значений переменных i и
j программа будет обращаться к соответствующим элементам массива Text[], суммировать
их значения и присваивать результат элементу массива с индексом, значение которого
также вычисляется ( i+j ) Например, если на некотором этапе вычислений значение
переменной i равно 30, а переменной j равно 7, то название элементов, значения
которых суммируются, соответственно, Text[30] и Text[7], а элемента, которому присваивается
результат, - Text[37]. В качестве значения индекса элемента массива может использоваться
и любая другая переменная целого типа. В данном примере в функции start() используется
название элемента того же массива с индексом Delta, а именно - Text[Delta].
Специальная функция start() имеет простой код. Вычисления ведутся в зависимости от
значения переменной Delta. Если оно оказывается меньше одного или больше ста пунктов,
то после вывода необходимого сообщения исполнение специальной функции start() завершается.
Если же это значение находится в пределах заданного диапазона, то на экран выводится
сообщение, требуемое по условию задачи.
Рис. 60. Вывод на экран искомых значений экспертом
stringarray.mq4.
Обратите внимание на решение Задачи 18. Если бы и Задача 25 была решена тем
же способом, то тело оператора switch содержало бы около 100 строк, - по одной
строке для каждого варианта решения. Такой подход к составлению программ нельзя
считать удовлетворительным. Тем более подобные способы решения оказываются непригодными,
если приходится обрабатывать десятки, а иногда и сотни тысяч значений переменных.
В таких случаях использование массивов оправдано и очень удобно.
Массивы-таймсерии
Массив-таймсерия - это массив с предопределённым названием (Open, Close, High, Low, Volume или Time),
элементы которого содержат значения соответствующих характеристик исторических
баров.
Данные, содержащиеся в массивах-таймсериях, несут очень важную информацию и широко
используются в практике программирования на MQL4. Каждый массив-таймсерия является
одномерным массивом и содержит исторические сведения о какой-то одной характеристике
бара. Каждый бар характеризуется ценой открытия Open[], ценой закрытия
Close[], максимальной
ценой High[], минимальной ценой Low[], объёмом Volume[] и временем открытия Time[].
Например, массив-таймсерия Open[] несёт информацию о цене открытия всех баров, имеющихся
в окне финансового инструмента: значением элемента массива Open[1] является цена
открытия первого бара, Open[2] - цена открытия второго бара и т.д. То же справедливо
и для всех других таймсерий.
Нулевой бар - это текущий бар, который ещё полностью не сформировался. В окне финансового инструмента
нулевой бар отражается в крайней правой позиции.
Отсчёт баров (и поставленных им в соответствие индексов массивов-таймсерий) начинается
с нулевого бара. Значениями элементов всех массивов-таймсерий, имеющих индекс [0],
являются значения, характеризующие нулевой бар. Например, значением элемента Open[0]
является цена открытия нулевого бара. На Рис. 61 показан порядок нумерации баров
и характеристики бара (отражаемые в окне финансового инструмента при наведении
на изображение бара курсора мыши).
Рис. 61. Каждый бар характеризуется набором значений, содержащихся в массивах-таймсериях.
Отсчёт баров начинается с нулевого бара.
Нулевой бар на Рис. 61 имеет следующие характеристики:
Индекс |
Open[] |
Close[] |
High[], |
Low[], |
Time[] |
[0] |
1.2755 |
1.2752 |
1.2755 |
1.2752 |
2006.11.01 14:34 |
По истечении некоторого времени текущий бар сформируется, а в окне финансового инструмента
появится новый бар. Теперь нулевым будет этот новый бар, а тот, который только
что сформировался, станет первым (с индексом 1):
Рис. 62. Бары смещаются с течением времени, но нумерация баров не смещается.
Теперь значения элементов массивов-таймсерий станут следующими:
Индекс |
Open[] |
Close[] |
High[], |
Low[], |
Time[] |
[0] |
1.2751 |
1.2748 |
1.2752 |
1.2748 |
2006.11.01 14:35 |
[1] |
1.2755 |
1.2752 |
1.2755 |
1.2752 |
2006.11.01 14:34 |
В дальнейшем в окне финансового инструмента будут появляться новые бары. При этом
текущий, не сформировавшийся, самый правый бар всегда будет нулевым, ближайший
слева от него будет первым, ближайший следующий - вторым и т.д. Вместе с тем, собственно
бар не изменяет свои характеристики: тот бар, который в представленном примере
был открыт в 14:34 по-прежнему будет характеризоваться временем открытия 14:34,
и все другие его параметры также не изменятся. Однако, индекс этого бара будет
всё время увеличиваться по мере появления новых баров.
Таким образом, важнейшая особенность, касающаяся массивов-таймсерий, состоит в следующем:
|
Значения элементов массивов-таймсерий являются собственными характеристиками бара
и никогда не изменяются (за исключением следующих характеристик нулевого бара:
Close[0], High[0], Low[0], Volume [0]), а индекс бара отражает его углубление в
историю на текущий момент и меняется с течением времени. |
Отдельно нужно заметить, что время открытия бара исчисляется кратно
календарным минутам, секунды при этом не учитываются. Иными словами, если в
период между 14:34 и 14:35 первый тик пришёл в 14 час. 34 мин. 07 сек, то в
минутном таймфрейме образуется новый бар со временем открытия 14:34.
Соответственно, время открытия бара в 15-минутном таймфрейме кратно 15 минутам,
при этом первый бар в течение часового интервала открывается в
n час.00 мин, второй в n:15, третий в n:30 и четвёртый - в n:45.
Чтобы правильно понять роль индексов в массивах-таймсериях, решим следующую
простую задачу:
|
Задача 26.Найти минимальное и максимальное значения цены среди последних n баров.
|
Обратите внимание, решение подобных задач невозможно без обращения к значениям массивов-таймсерий.
Эксперт, определяющий минимальную и максимальную цену среди заданного количества
последних баров, может иметь следующее решение (extremumprice.mq4):
extern int Quant_Bars=30;
int start()
{
int i;
double Minimum=Bid,
Maximum=Bid;
for(i=0;i<=Quant_Bars-1;i++)
{
if (Low[i]< Minimum)
Minimum=Low[i];
if (High[i]> Maximum)
Maximum=High[i];
}
Alert("За последние ",Quant_Bars,
" баров Min= ",Minimum," Max= ",Maximum);
return;
}
В программе extremumprice.mq4 используется простой алгоритм. Количество баров, предназначенных для анализа, задано
во внешней переменной Quant_Bars. В начале программы искомым значениям Minimum
и Maximum присваиваются значения текущей цены. Поиск минимального и максимального
значений выполняется в операторе цикла:
for(i=0;i<=Quant_Bars-1;i++)
{
if (Low[i]< Minimum)
Minimum = Low[i];
if (High[i]> Maximum)
Maximum = High[i];
}
Здесь показательным является интервал значений индексов (целая переменная i) обрабатываемых
элементов массивов-таймсерий Low[i] и High[i]. Обратите внимание на Выражение_1 и
Условие в заголовке оператора цикла:
for(i=0;i<=Quant_Bars-1;i++)
На первой итерации вычисления производятся с нулевыми значениями индексов. Это значит,
что в расчётах на первой итерации анализируются значения нулевого бара. Таким образом
гарантируется, что самые последние значения цены, появившиеся в окне финансового
инструмента, тоже принимаются во внимание. В разделе Предопределённые переменные указано правило, в соответствии с которым значения всех предопределённых переменных,
в том числе и массивов-таймсерий, обновляются в момент запуска на исполнение специальных
функций. Таким образом, ни одно значение цены не может быть пропущено или не учтено.
Последним индексом элементов таймсерий, обрабатываемых в цикле, является индекс,
на 1 меньший, чем количество обрабатываемых баров. В нашем примере указано количество
баров, равное 30. Это значит, что максимальное значение индекса должно быть равно
29. Таким образом, в цикле будут обработаны значения элементов таймсерий с индексами
от 0 до 29, т.е. всего для 30 баров.
Легко понять и смысл вычислений в теле оператора цикла:
if (Low[i]< Minimum)
Minimum = Low[i];
if (High[i]> Maximum)
Maximum = High[i];
Если текущее значение Low[i] (т.е. в период текущей итерации с текущим значением
индекса) оказывается меньше известного минимального значения, то оно и становится
минимальным значением. Аналогично вычисляется и максимальное значение. К моменту
окончания цикла переменные Minimum и Maximum получат искомые значения. В последующих
строках эти значения выводятся на экран.
Запустив эту программу на выполнение можно получить результат, аналогичный следующему:
Рис. 63. Результаты работы эксперта extremumprice.mq4.
Обратите внимание, эксперт может работать бесконечно долго, показывая правильный
результат, причём в программе всё время используются одни и те же значения индексов
(в данном случае от 0 до 29). При этом значения элементов массивов-таймсерий с
ненулевыми индексами будут изменяться в момент появления нового бара, а значения
элементов массивов-таймсерий, характеризующих нулевой бар, могут поменяться на
любом следующем тике (за исключением значений Open[0] и Time[0], которые на нулевом
баре не изменяются).
В некоторых случаях требуется выполнить какие-то действия, начиная с момента, когда
очередной бар только что полностью сформировался. Это бывает важно, например, для
реализации алгоритмов, основанных на свечном анализе. При этом обычно во внимание
принимаются только полностью сформировавшиеся бары.
|
Задача 27.В начале каждого бара 1 раз вывести на экран минимальное и максимальное
значения цены среди последних сформировавшихся n баров. |
Чтобы решить задачу, прежде всего необходимо определить факт начала нового бара,
т.е. выявить первый тик на нулевом баре. Для этой цели существует очень простой
и надёжный способ - анализировать время открытия нулевого бара. Время открытия
нулевого бара - это такая характеристика бара, которая не изменяется в течение
времени его формирования. Новые тики, поступающие в процессе развития нулевого
бара, могут изменить его максимальную цену High[0], минимальную цену Low[0], цену
закрытия Close[0] и объём Volume0]. Но такие характеристики нулевого бара, как
цена открытия Open[0] и время открытия Time[0], не изменяются.
Поэтому достаточно запомнить время открытия нулевого бара и на каждом тике сравнивать
это значение с последним известным временем открытия нулевого бара. Как только
обнаружится несовпадение, это будет означать факт образования нового бара (и завершение
предыдущего). В эксперте newbar.mq4 алгоритм обнаружения нового бара реализован
в виде пользовательской функции:
extern int Quant_Bars=15;
bool New_Bar=false;
int start()
{
double Minimum,
Maximum;
Fun_New_Bar();
if (New_Bar==false)
return;
int Ind_max =ArrayMaximum(High,Quant_Bars,1);
int Ind_min =ArrayMinimum(Low, Quant_Bars,1);
Maximum=High[Ind_max];
Minimum=Low[Ind_min];
Alert("За последние ",Quant_Bars,
" баров Min= ",Minimum," Max= ",Maximum);
return;
}
void Fun_New_Bar()
{
static datetime New_Time=0;
New_Bar=false;
if(New_Time!=Time[0])
{
New_Time=Time[0];
New_Bar=true;
}
}
В программе используется глобальная переменная New_Bar. Полагается, что если её
значение равно true, то это означает, что последний известный тик - это первый
тик нового бара. Если же значение New_Bar равно false, то последний известный тик
появился в период развития текущего нулевого бара.
Флаг - это переменная, значение которой ставится в соответствие каким-либо событиям
или фактам.
Использование флагов в программе очень удобно. Значение флага может быть определено
в одном месте, а использоваться в разных местах. Иногда в программе используется
алгоритм, в котором принимается решение в зависимости от сочетания значений различных
флагов. В эксперте newbar.mq4 в качестве флага используется переменная New_Bar, значение которой находится в
прямой зависимости от факта образования нового бара.
Вычисления, касающиеся выявления факта образования нового бара, сосредоточены в
пользовательской функции Fun_New_Bar(). В первых строках этой функции определена
статическая переменная New_Time (напомним, что статические переменные не теряют
своё значение после окончания исполнения функции). При каждом обращении к функции
значение глобальной переменной New_Bar устанавливается равным false. Собственно
обнаружение нового бара осуществляется в операторе if:
if(New_Time != Time[0])
{
New_Time = Time[0];
New_Bar = true;
}
Если значение переменной New_Time (вычисленное в предыдущей истории), не равно времени
открытия нулевого бара Time[0], то это означает факт образования нового бара. В
этом случае управление передаётся в тело оператора if, где запоминается новое значение
времени открытия нового нулевого бара и переменной New_Bar присваивается значение
true (условно можно сказать, что флаг устанавливается в положение вверх).
При решениях подобных задач важно учитывать специфику использования различных флагов.
В данном случае особенность состоит в том, что значение переменной New_Bar (положение
флага) должно быть обновлено раньше, чем оно будет использоваться в вычислениях
(в данном случае - в специальной функции start()). Поскольку значение переменной
New_Bar определяется в пользовательской функции, значит обращение к ней должно
быть выполнено как можно раньше в программе, а именно, до первых вычислений, в
которых используется переменная New_Bar. Специальная функция start() так и составлена:
обращение к пользовательской функции осуществляется сразу после объявления переменных.
Вычисление искомых значений имеет смысл только в том случае, если специальная функция start()
запущена тиком, на котором и образовался новый бар. Поэтому в функции start(),
сразу после определения факта образования нового бара, производится анализ положения
флага (значения переменной New_Bar):
if (New_Bar == false)
return;
Если последний тик, запустивший на исполнение специальную функцию start(), не образовал
новый бар, то управление передаётся оператору, завершающему исполнение функции
start(). И только в том случае, если новый бар образовался, управление передаётся
в последующие строки для вычисления искомых значений (что и требуется по условию
задачи).
Вычисление максимального и минимального значений осуществляется с помощью стандартных
функций ArrayMaximum() и ArrayMinimum(). Каждая из этих функций возвращает индекс
элемента массива (соответственно максимального и минимального значения) для заданного
интервала индексов. По условию задачи необходимо исследовать только полностью сформировавшиеся
бары, поэтому в качестве граничных значений индексов выбраны значения 1 и Quant_Bars
- заданное количество баров (нулевой бар не сформировался, т.е. в решении данной
задачи не учитывается). Для получения подробной информации о работе этих и других
функций доступа к таймсериям обратитесь к справочной документации на сайте производителя
(https://docs.mql4.com/ru) или к разделу "Справка" в
редакторе MetaEditor.
На Рис. 64 можно проследить, как менялись минимальное и максимальное значения
цены на заданном интервале в период исполнения программы:
Рис. 64. Результаты работы эксперта newbar.mq4.