Направление индексации массивов как в таймсерии

В силу прикладной трейдерской специфики MQL5 привносит в работу с массивами дополнительные нюансы. Один из них заключается в том, что элементы массивов могут содержать данные, соответствующие отсчетам во времени. К их числу относятся, например, массивы с котировками финансовых инструментов, их тиков, показаниям технических индикаторов. Хронологический порядок данных означает, что новые элементы постоянно добавляются в конец массива и их индексы увеличиваются.

Однако, с точки зрения торговли удобнее вести отсчет от настоящего в прошлое. Тогда элемент под номером 0 всегда содержит самое свежее, актуальное значение, элемент под номером 1 — предыдущее значение, и так далее.

MQL5 позволяет выбирать и на лету переключать направление индексации массивов. Массив с нумерацией от настоящего в прошлое называется временным рядом или таймсерией. Если же увеличение индексов происходит от прошлого к настоящему — это обычный массив. В таймсериях время уменьшается с ростом индексов. В обычных массивах время увеличивается, как в реальной жизни.

Важно отметить, что массив, в принципе, не обязан содержать величины, связанные со временем, чтобы для него можно было переключить порядок адресации. Просто эта возможность наиболее востребована и, собственно, появилась для работы с историческими данными.

Данный атрибут массива никак не влияет на размещение данных в памяти. Меняется лишь порядок нумерации. В частности, мы могли бы сами реализовать его аналог на MQL5 за счет обхода массива в цикле "задом наперед". Но MQL5 предоставляет готовые функции, чтобы скрыть всю эту рутину от прикладных программистов.

Таймсерией можно сделать любой одномерный динамический массив, описанный в MQL-программе, а также внешние массивы, передаваемые в MQL-программу из ядра MetaTrader 5 как параметры служебных функций. Например, специальный тип MQL-программ — индикаторы — получает массивы с ценовыми данными текущего графика в обработчике события OnCalculate. Мы изучим все особенности прикладного использования таймсерий позднее, в пятой Части книги.

Массивы, определенные в MQL-программе, по умолчанию не являются таймсерией.

А пока познакомимся с набором функций для определения и изменения атрибута "серийности" массива, а также его "принадлежности" терминалу. Общий скрипт с примерами ArrayAsSeries.mq5 будет приведен после описания.

bool ArrayIsSeries(const void &array[])

Функция возвращает признак того, является ли указанный массив "настоящей" таймсерией, то есть управляется и предоставляется самим терминалом. Изменить эту характеристику массива нельзя. Подобные массивы доступны MQL-программе в режиме "только чтение".

В документации MQL5 термины "таймсерия" и "серийность" используются для описания и обратной индексации массива, и того факта, что массив может "принадлежать" терминалу (он выделяет под него память и наполняет данными). В книге мы постараемся избегать этой двусмысленности и называть "таймсериями" или временными рядами массивы с обратной индексацией. А массивы терминала так и будут — собственными массивами терминала.

Индексацию любого собственного массива терминала можно менять по своему усмотрению, переключая его в режим таймсерии или обратно в стандартный. Это делается с помощью функции ArraySetAsSeries, которая применима не только к собственными, но и пользовательским динамическим массивам (см. ниже).

bool ArrayGetAsSeries(const void &array[])

Функция возвращает признак того, включен ли для указанного массива режим индексации "как в таймсерии", то есть с увеличением индексов в направлении от настоящего в прошлое. Изменить направление индексации можно с помощью функции ArraySetAsSeries.

Направление индексации влияет на значения, возвращаемые функциями ArrayBsearch, ArrayMaximum, ArrayMinimum (см. раздел Сравнение, сортировка и поиск в массивах).

bool ArraySetAsSeries(const void &array[], bool as_series)

Функция устанавливает направление индексации в массиве согласно параметру as_series: значение true означает обратный порядок нумерации элементов "как в таймсерии", false — обычный порядок.

Функция возвращает true при успешной установки атрибута или false в случае ошибки.

Поддерживаются массивы любых типов, но менять направление индексации запрещено у многомерных массивов и массивов фиксированного размера.

В скрипте ArrayAsSeries.mq5 описано несколько небольших массивов для экспериментов с участием вышеописанных функций.

#define LIMIT 10
 
template<typename T>
void indexArray(T &array[])
{
   for(int i = 0i < ArraySize(array); ++i)
   {
      array[i] = (T)(i + 1);
   }
}
 
class Dummy
{
   int data[];
};
 
void OnStart()
{
   double array2D[][2];
   double fixed[LIMIT];
   double dynamic[];
   MqlRates rates[];
   Dummy dummies[];
   
   ArrayResize(dynamicLIMIT); // выделяем память
   // заполняем пару массивов номерами: 1, 2, 3,...
   indexArray(fixed);
   indexArray(dynamic);
   ...

Это двумерный массив array2D, фиксированный и динамический массив — все типа double, а также массивы структур и объектов класса. Массивы fixed и dynamic заполняются для наглядности последовательными целыми числами (используется вспомогательная функция indexArray). Для остальных типов массивов мы лишь проверим применимость "серийного" режима, поскольку суть эффекта обращения индексации станет понятна на примере заполненных массивов.

Сперва убедимся, что ни один из массивов не является собственным массивом терминала:

   PRTS(ArrayIsSeries(array2D)); // false
   PRTS(ArrayIsSeries(fixed));   // false
   PRTS(ArrayIsSeries(dynamic)); // false
   PRTS(ArrayIsSeries(rates));   // false

Все вызовы ArrayIsSeries возвращают false, поскольку все массивы определены нами в MQL-программе. Мы увидим значение true у массивов-параметров функции OnCalculate в индикаторах (в пятой Части).

Далее проверим начальное направление индексации массивов:

   PRTS(ArrayGetAsSeries(array2D)); // false, не может быть true
   PRTS(ArrayGetAsSeries(fixed));   // false
   PRTS(ArrayGetAsSeries(dynamic)); // false
   PRTS(ArrayGetAsSeries(rates));   // false
   PRTS(ArrayGetAsSeries(dummies)); // false

И опять получим везде false.

Выведем массивы fixed и dynamic в журнал, чтобы видеть исходный порядок элементов.

   ArrayPrint(fixed, 1);
   ArrayPrint(dynamic, 1);
   /*
       1.0  2.0  3.0  4.0  5.0  6.0  7.0  8.0  9.0 10.0
       1.0  2.0  3.0  4.0  5.0  6.0  7.0  8.0  9.0 10.0
   */

Теперь пробуем изменить порядок индексации:

   // ошибка: parameter conversion not allowed
   // PRTS(ArraySetAsSeries(array2D, true));
 
   // предупреждение: cannot be used for static allocated array
   PRTS(ArraySetAsSeries(fixedtrue));   // false
 
   // далее все штатно
   PRTS(ArraySetAsSeries(dynamictrue)); // true
   PRTS(ArraySetAsSeries(ratestrue));   // true
   PRTS(ArraySetAsSeries(dummiestrue)); // true

Инструкция для массива array2D вызывает ошибку компиляции, и потому закомментирована.

Инструкция для массива fixed выдает предупреждение компилятора о невозможности её применения к массиву постоянного размера. На стадии выполнения все 3 последних инструкции вернули признак успеха (true). Посмотрим, как поменялись атрибуты у массивов:

   // проверки на атрибуты:
   // во-первых, стали ли они собственными для терминала
   PRTS(ArrayIsSeries(fixed));            // false
   PRTS(ArrayIsSeries(dynamic));          // false
   PRTS(ArrayIsSeries(rates));            // false
   PRTS(ArrayIsSeries(dummies));          // false
   
   // во-вторых, направление индексации
   PRTS(ArrayGetAsSeries(fixed));         // false
   PRTS(ArrayGetAsSeries(dynamic));       // true
   PRTS(ArrayGetAsSeries(rates));         // true
   PRTS(ArrayGetAsSeries(dummies));       // true

Как и ожидалось, массивы не превратились в собственные массивы терминала. Однако три массива из четырех поменяли индексацию на режим таймсерий, включая массив структур и объектов. Для демонстрации результата в журнал снова выводятся массивы fixed и dynamic.

   ArrayPrint(fixed, 1);    // без изменений
   ArrayPrint(dynamic, 1);  // обратный порядок
   /*
       1.0  2.0  3.0  4.0  5.0  6.0  7.0  8.0  9.0 10.0
      10.0  9.0  8.0  7.0  6.0  5.0  4.0  3.0  2.0  1.0
   */

Поскольку к массиву постоянного размера режим не применился, он остался без изменений. Массив dynamic отображается теперь в обратном порядке.

Если перевести массив в режим обратной индексации, изменить его размер, а затем вернуть прежнюю индексацию, то добавленные элементы окажутся вставленными в начало массива.