Сравнение, сортировка и поиск в массивах

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

int ArrayCompare(const void &array1[], const void &array2[], int start1 = 0, int start2 = 0, int count = WHOLE_ARRAY)

Функция возвращает результат сравнения двух массивов встроенных типов или структур с полями встроенных типов за исключением строк. Массивы объектов классов не поддерживаются. Также нельзя сравнивать массивы структур, в которых есть динамические массивы, объекты классов или указатели.

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

Массивы могут быть фиксированными или динамическими, а также многомерными. Многомерные массивы в процессе сравнения представляются как эквивалентные одномерные массивы (например, для двумерных массивов элементы второго ряда идут следом за элементами первого, элементы третьего ряда — за вторым и так далее). В связи с этим параметры start1, start2 и count обозначают для многомерных массивов сквозную нумерацию элементов, а не индекс по первому измерению.

Пользуясь разными смещениями start1 и start2, можно сравнивать разные части одного и того же массива.

Массивы сравниваются поэлементно, до первого расхождения или по достижении конца одного из массивов. Соотношение между двумя элементами (находящимися на одинаковых позициях в обоих массивах) зависит от типа: для чисел используются операторы '>', '<', '==', а для строк — функция StringCompare. Структуры сравниваются побайтово, то есть это эквивалентно выполнению для каждой пары элементов следующего кода:

uchar bytes1[], bytes2[];
StructToCharArray(array1[i], bytes1);
StructToCharArray(array2[i], bytes2);
int cmp = ArrayCompare(bytes1bytes2);

На основании соотношения первых различающихся элементов получается результат сравнения массивов array1 и array2 целиком. Если отличий не найдено и длина массивов равна, то массивы считаются одинаковыми. Если длина отличается, то более длинный массив считается больше.

Функция возвращает -1 — если array1 "меньше" array2, +1 — если array1 "больше" array2, 0 если они "равны".

В случае ошибки результат равен -2.

Рассмотрим некоторые примеры в скрипте ArrayCompare.mq5.

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

struct Dummy
{
   int x;
   int y;
   
   Dummy()
   {
      x = rand() / 10000;
      y = rand() / 5000;
   }
};

Поля класса заполняются случайными числами (при каждом запуске скрипта будем получать новые значения).

В функции OnStart опишем небольшой массив структур и сравним последовательные элементы друг с другом (как сдвигающиеся соседние фрагменты массива длиной 1 элемент).

#define LIMIT 10
 
void OnStart()
{
   Dummy a1[LIMIT];
   ArrayPrint(a1);
   
   // попарное сравнение соседних элементов
   // -1: [i] < [i + 1]
   // +1: [i] > [i + 1]
   for(int i = 0i < LIMIT - 1; ++i)
   {
      PRT(ArrayCompare(a1a1ii + 11));
   }
   ...

Ниже приведены результаты для одного из вариантов массива (для удобства анализа колонка с признаками "больше"(+1)/"меньше"(-1) добавлена непосредственно справа от содержимого массива):

       [x] [y]   // результат
   [0]   0   3   // -1
   [1]   2   4   // +1
   [2]   2   3   // +1
   [3]   1   6   // +1
   [4]   0   6   // -1
   [5]   2   0   // +1
   [6]   0   4   // -1
   [7]   2   5   // +1
   [8]   0   5   // -1
   [9]   3   6

Сравнение двух половин массива между собой дает -1:

   // сравнение первой и второй половины
   PRT(ArrayCompare(a1a10LIMIT / 2LIMIT / 2)); // -1

Далее проведем сравнение массивов строк с предопределенными данными.

   string s[] = {"abc","456","$"};
   string s0[][3] = {{"abc","456","$"}};
   string s1[][3] = {{"abc","456",""}};
   string s2[][3] = {{"abc","456"}}; // последний элемент опущен: он равен null
   string s3[][2] = {{"abc","456"}};
   string s4[][2] = {{"aBc","456"}};
   
   PRT(ArrayCompare(s0s));  // s0 == s, 1D и 2D массивы содержат одинаковые данные
   PRT(ArrayCompare(s0s1)); // s0 > s1, т.к. "$" > ""
   PRT(ArrayCompare(s1s2)); // s1 > s2, т.к. "" > null
   PRT(ArrayCompare(s2s3)); // s2 > s3, из-за разной длины: [3] > [2]
   PRT(ArrayCompare(s3s4)); // s3 < s4, т.к. "abc" < "aBc"

Наконец, проверим соотношение фрагментов массивов:

   PRT(ArrayCompare(s0s1111)); // вторые элементы (с индексом 1) равны
   PRT(ArrayCompare(s1s2002)); // два первых элемента равны

 

bool ArraySort(void &array[])

Функция сортирует числовой массив (в том числе, возможно многомерный) по первому измерению. Порядок сортировки — по возрастанию. Если требуется сортировка по убыванию, примените к результирующему массиву функцию ArrayReverse или обходите его в обратном порядке.

Функция не поддерживает массивы строк, структур или классов.

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

Если для массива установлено свойство "таймсерии" (временного ряда), то индексация элементов в нем ведется в обратном порядке (см. подробности в разделе Направление индексации массивов как в таймсерии), и это оказывает "внешний" разворотный эффект на порядок сортировки: при прямом обходе такого массива вы получите убывающие значения. На физическом уровне массив всегда сортируется по возрастанию значений, и именно так и хранится.

В скрипте ArraySort.mq5 генерируется двумерный массив размером 10 на 3 и затем сортируется с помощью ArraySort:

#define LIMIT 10
#define SUBLIMIT 3
   
void OnStart()
{
   // генерируем случайные данные
   int array[][SUBLIMIT];
   ArrayResize(arrayLIMIT);
   for(int i = 0i < LIMIT; ++i)
   {
      for(int j = 0j < SUBLIMIT; ++j)
      {
         array[i][j] = rand();
      }
   }
   
   Print("Before sort");
   ArrayPrint(array);    // исходный массив
   
   PRTS(ArraySort(array));
   
   Print("After sort");
   ArrayPrint(array);    // упорядоченный массив
   ...
}

По выводу в журнал легко убедиться, что первая колонка отсортирована по возрастанию (конкретные числа будут меняться из-за случайной генерации):

Before sort
      [,0]  [,1]  [,2]
[0,]  8955  2836 20011
[1,]  2860  6153 25032
[2,] 16314  4036 20406
[3,] 30366 10462 19364
[4,] 27506  5527 21671
[5,]  4207  7649 28701
[6,]  4838   638 32392
[7,] 29158 18824 13536
[8,] 17869 23835 12323
[9,] 18079  1310 29114
ArraySort(array)=true / status:0
After sort
      [,0]  [,1]  [,2]
[0,]  2860  6153 25032
[1,]  4207  7649 28701
[2,]  4838   638 32392
[3,]  8955  2836 20011
[4,] 16314  4036 20406
[5,] 17869 23835 12323
[6,] 18079  1310 29114
[7,] 27506  5527 21671
[8,] 29158 18824 13536
[9,] 30366 10462 19364

Значения в следующих колонках переместились синхронно с "ведущими" показателями в первой колонке. Иными словами, перестановке подвергаются ряды целиком, несмотря на то, что критерием сортировки выступает только первая колонка.

Что делать, если требуется сортировать двумерный массив по колонке, отличной от первой? Можно написать алгоритм самостоятельно. Один из вариантов включен в файл ArraySort.mq5 как шаблонная функция:

template<typename T>
bool ArraySort(T &array[][], const int column)
{
   if(!ArrayIsDynamic(array)) return false;
   
   if(column == 0)
   {
      return ArraySort(array); // стандартная функция
   }
   
   const int n = ArrayRange(array0);
   const int m = ArrayRange(array1);
   
   T temp[][2];
   
   ArrayResize(tempn);
   for(int i = 0i < n; ++i)
   {
      temp[i][0] = array[i][column];
      temp[i][1] = i;
   }
   
   if(!ArraySort(temp)) return false;
   
   ArrayResize(arrayn * 2);
   for(int i = ni < n * 2; ++i)
   {
      ArrayCopy(arrayarrayi * m, (int)(temp[i - n][1] + 0.1) * mm);
      /* equivalent
      for(int j = 0; j < m; ++j)
      {
         array[i][j] = array[(int)(temp[i - n][1] + 0.1)][j];
      }
      */
   }
   
   return ArrayRemove(array0n);
}

Данная функция работает только с динамическими массивами, поскольку размер array удваивается для сборки промежуточных результатов во второй половине массива, а в завершении первая половина (исходная) удаляется с помощью ArrayRemove. Именно поэтому исходный тестовый массив в функции OnStart распределялся через ArrayResize.

С принципом сортировки предлагается разобраться самостоятельно (или перевернуть пару страниц).

Для массивов с большим числом измерений (например, array[][][]) потребуется реализовать что-то аналогичное.

Теперь вспомним, что в предыдущем разделе мы поднимали вопрос о том, чтобы отсортировать массив структур по произвольному полю. Как мы знаем, стандартная ArraySort этого не умеет. Попробуем придумать "обходной маневр". За основу возьмем класс из файла ArraySwapSimple.mq5 из предыдущего раздела. Скопируем его в ArrayWorker.mq5 и кое-что добавим.

В методе Worker::process предусмотрим вызов вспомогательного метода сортировки arrayStructSort, причем сортируемое поле будем задавать по номеру (как это возможно, расскажем чуть ниже):

   ...
   bool process(const int mode)
   {
      ...
      switch(mode)
      {
      ...
      case -1:
         ArrayReverse(array);
         break;
      default// сортировка по полю номер 'mode'
         arrayStructSort(mode);
         break;
      }
      return true;
   }
   
private:
   bool arrayStructSort(const int field)
   {
      ...
   }

Теперь становится понятно, почему все предыдущие режимы (значения параметра mode) в методе process были отрицательными: нулевое и положительные значения зарезервированы для сортировки — они соответствуют номеру "колонки".

Идея сортировки массива структур взята из сортировки двумерного массива. Необходимо лишь каким-то образом отобразить одиночную структуру на одномерный массив (олицетворяющий ряд двумерного массива). Для этого сперва нужно определиться, какого типа должен быть массив.

Поскольку класс Worker уже является шаблонным, логично добавить еще один параметр в шаблон, чтобы тип массива можно было гибко задавать.

template<typename T,typename R>
class Worker
{
   T array[];
   ...

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

   union Overlay
   {
      T r;
      R d[sizeof(T) / sizeof(R)];
   };

В этом объединении тип структуры совмещен с массивом типа R, причем его размер автоматически рассчитывается компилятором на основе соотношения размеров двух типов T и R.

Внутри метода arrayStructSort теперь можно частично продублировать код из сортировки двумерного массива.

   bool arrayStructSort(const int field)
   {
      const int n = ArraySize(array);
      
      R temp[][2];
      Overlay overlay;
      
      ArrayResize(tempn);
      for(int i = 0i < n; ++i)
      {
         overlay.r = array[i];
         temp[i][0] = overlay.d[field];
         temp[i][1] = i;
      }
      ...

Вместо массива с исходными структурами мы подготавливаем массив temp[][2] типа R, расширяем его до количества записей в array, и в цикле записываем по 0-му индексу каждого ряда "отображение" требуемого поля field из структуры, а по 1-му индексу — исходный индекс этого элемента.

"Отображение" основывается на том, что поля в структурах обычно каким-то образом выровнены, поскольку используют стандартные типы. Поэтому при правильно подобранном типе R можно обеспечить полное или частичное попадание полей в элементы массива в "оверлее".

Например, в стандартной структуре MqlRates первые 6 полей имеют размер 8 байтов и потому корректно накладываются на массив double или long (это кандидаты на шаблонный тип R).

struct MqlRates 

   datetime time
   double   open
   double   high
   double   low
   double   close
   long     tick_volume
   int      spread
   long     real_volume
};

С двумя последними полями дело обстоит сложнее. Если до поля spread еще можно добраться, используя тип int в качестве R, то поле real_volume оказывается по смещению, некратному его собственному размеру (из-за поля типа int, то есть 4 байта, перед ним). Это издержки конкретного метода. Его можно улучшить или изобрести другой.

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

      ...
      if(!ArraySort(temp)) return false;
      T result[];
      
      ArrayResize(resultn);
      for(int i = 0i < n; ++i)
      {
         result[i] = array[(int)(temp[i][1] + 0.1)];
      }
      
      return ArraySwap(resultarray);
   }

Перед выходом из функции мы снова используем ArraySwap, чтобы экономным способом подменить содержимое внутриобъектного массива array на новое и упорядоченное — полученное в локальном массиве result.

Проверим класс Worker в действии: в функции OnStart определим массив структур MqlRates и запросим у терминала несколько тысяч записей.

#define LIMIT 5000
 
void OnStart()
{
   MqlRates rates[];
   int n = CopyRates(_Symbol_Period0LIMITrates);
   ...

Функция CopyRates будет описана в отдельном разделе. Пока нам достаточно знать, что она заполняет переданный массив rates котировками символа и таймфрейма текущего графика, на котором запущен скрипт. В макросе LIMIT указано количество запрашиваемых баров: необходимо удостовериться, что это значение не больше, чем настройка вашего терминала по количеству баров в каждом окне.

Для обработки полученных данных создадим объект worker с типами T=MqlRates и R=double:

Worker<MqlRates,doubleworker(rates);

Запуск сортировки можно осуществить инструкцией вида:

worker.process(offsetof(MqlRatesopen) / sizeof(double));

Здесь используется оператор offsetof, чтобы узнать байтовое смещение поля open внутри структуры. Далее оно делится на размер double и получается правильный номер "колонки" для сортировки по цене open. Результат сортировки можно прочитать поэлементно или получить весь массив целиком:

Print(worker[i].open);
...
worker.get(rates);
ArrayPrint(rates);

Обратите внимание, что получение массива методом get перемещает его из внутреннего массива array во внешний (переданный в качестве аргумента) с помощью ArraySwap. Поэтому после этого вызовы worker.process() бесполезны: в объекте worker уже нет данных.

Для упрощения запуска сортировки по разным полям была реализована вспомогательная функция sort:

void sort(Worker<MqlRates,double> &workerconst int offsetconst string title)
{
   Print(title);
   worker.process(offset);
   Print("First struct");
   StructPrint(worker[0]);
   Print("Last struct");
   StructPrint(worker[worker.size() - 1]);
}

Она выводит в журнал некий заголовок и первый и последний элементы отсортированного массива. С её помощью тестирование в OnStart для трех полей выглядит следующим образом:

void OnStart()

{

   ...

   Worker<MqlRates,doubleworker(rates);

   sort(workeroffsetof(MqlRatesopen) / sizeof(double), "Sorting by open price...");

   sort(workeroffsetof(MqlRatestick_volume) / sizeof(double), "Sorting by tick volume...");

   sort(workeroffsetof(MqlRatestime) / sizeof(double), "Sorting by time...");

}

К сожалению, стандартная функция Print не поддерживает печать одиночных структур, и в MQL5 нет встроенной функции StructPrint. Поэтому нам пришлось самим её написать, базируясь на ArrayPrint: фактически достаточно положить структуру в массив размером 1.

template<typename S>
void StructPrint(const S &s)
{
   S temp[1];
   temp[0] = s;
   ArrayPrint(temp);
}

В результате запуска скрипта можем получить примерно следующее (зависит от настроек терминала, а именно на каком символе/таймфрейме выполняется):

Sorting by open price...
First struct
                 [time]  [open]  [high]   [low] [close] [tick_volume] [spread] [real_volume]
[0] 2021.07.21 10:30:00 1.17557 1.17584 1.17519 1.17561          1073        0             0
Last struct
                 [time]  [open]  [high]   [low] [close] [tick_volume] [spread] [real_volume]
[0] 2021.05.25 15:15:00 1.22641 1.22664 1.22592 1.22618           852        0             0
Sorting by tick volume...
First struct
                 [time]  [open]  [high]   [low] [close] [tick_volume] [spread] [real_volume]
[0] 2021.05.24 00:00:00 1.21776 1.21811 1.21764 1.21794            52       20             0
Last struct
                 [time]  [open]  [high]   [low] [close] [tick_volume] [spread] [real_volume]
[0] 2021.06.16 21:30:00 1.20436 1.20489 1.20149 1.20154          4817        0             0
Sorting by time...
First struct
                 [time]  [open]  [high]   [low] [close] [tick_volume] [spread] [real_volume]
[0] 2021.05.14 16:15:00 1.21305 1.21411 1.21289 1.21333           888        0             0
Last struct
                 [time]  [open]  [high]   [low] [close] [tick_volume] [spread] [real_volume]
[0] 2021.07.27 22:45:00 1.18197 1.18227 1.18191 1.18225           382        0             0

Здесь представлены данные для EURUSD,M15.

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

Если же сложности с выравниванием полей структуры или скептическое отношение к самому подходу с "отображением" структуры в массив вынуждают отказаться от данного метода (и тем самым, от функции ArraySort), остается проверенный способ "сделай всё сам".

Существует большое количество алгоритмов сортировки, которые легко адаптировать под MQL5. Один из варианто быстрой сортировки представлен в прилагаемом к книге файле QuickSortStructT.mqh. Это усовершенствованная версия QuickSortT.mqh, который мы использовали в разделе Сравнение строк. В нем метод Compare шаблонного класса QuickSortStructT сделан чисто виртуальным и должен быть переопределен в классе наследнике, чтобы возвращать аналог операции сравнения '>' для требуемого типа и его полей. Для удобства пользователей в заголовочном файле создан макрос:

#define SORT_STRUCT(T,A,F)                                           \
{                                                                    \
   class InternalSort : public QuickSortStructT<T>                   \
   {                                                                 \
      virtual bool Compare(const T &aconst T &boverride          \
      {                                                              \
         return a.##F > b.##F;                                       \
      }                                                              \
   } sort;                                                           \
   sort.QuickSort(A);                                                \
}

С помощью него для сортировки массива структур по заданном полю достаточно написать одну инструкцию. Например:

   MqlRates rates[];
   CopyRates(_Symbol_Period010000rates);
   SORT_STRUCT(MqlRatesrateshigh);

Здесь выполняется сортировка массива rates типа MqlRates по цене high.

int ArrayBsearch(const type &array[], type value)

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

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

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

В случае ошибки выдается -1.

Для многомерных массивов поиск ограничивается первым измерением.

В скрипте ArraySearch.mq5 приведены примеры использования функции ArrayBsearch.

void OnStart()
{
   int array[] = {151117232337};
     // индексы   0  1   2   3   4   5   6
   int data[][2] = {{13}, {32}, {510}, {1410}, {218}};
     // индексы      0       1       2         3         4
   int empty[];
   ...

Для трех предопределенных массивов (один из них пустой) выполняются следующие инструкции:

   PRTS(ArrayBsearch(array, -1)); // 0
   PRTS(ArrayBsearch(array11)); // 2
   PRTS(ArrayBsearch(array12)); // 2
   PRTS(ArrayBsearch(array15)); // 3
   PRTS(ArrayBsearch(array23)); // 4
   PRTS(ArrayBsearch(array50)); // 6
   
   PRTS(ArrayBsearch(data7));   // 2
   PRTS(ArrayBsearch(data9));   // 2
   PRTS(ArrayBsearch(data10));  // 3
   PRTS(ArrayBsearch(data11));  // 3
   PRTS(ArrayBsearch(data14));  // 3
   
   PRTS(ArrayBsearch(empty0));  // -1, 5053, ERR_ZEROSIZE_ARRAY
   ...

Далее во вспомогательной функции populateSortedArray производится заполнение массива numbers случайными значениями, причем массив постоянно поддерживается в отсортированном состоянии с помощью ArrayBsearch.

void populateSortedArray(const int limit)
{
   double numbers[];  // массив для заполнения
   double element[1]; // новое значение для вставки
   
   ArrayResize(numbers0limit); // выделяем память заранее
   
   for(int i = 0i < limit; ++i)
   {
      // генерируем случайное число
      element[0] = NormalizeDouble(rand() * 1.0 / 327673);
      // находим, где его место в массиве
      int cursor = ArrayBsearch(numberselement[0]);
      if(cursor == -1)
      {
         if(_LastError == 5053// пустой массив
         {
            ArrayInsert(numberselement0);
         }
         else break// ошибка
      }
      else
      if(numbers[cursor] > element[0]) // вставка в позицию 'cursor'
      {
         ArrayInsert(numberselementcursor);
      }
      else // (numbers[cursor] <= value) // вставка после 'cursor'
      {
         ArrayInsert(numberselementcursor + 1);
      }
   }
   ArrayPrint(numbers3);
}

Каждое новое значение попадает вначале в одноэлементный массив element, потому что так его проще вставлять в результирующий массив numbers с помощью функции ArrayInsert.

ArrayBsearch позволяет определить, в какое место следует вставить новое значение.

Результат работы функции выводится в журнал:

void OnStart()
{
   ...
   populateSortedArray(80);
   /*
     пример (будет отличаться при каждом запуске из-за рандомизации)
   [ 0] 0.050 0.065 0.071 0.106 0.119 0.131 0.145 0.148 0.154 0.159
        0.184 0.185 0.200 0.204 0.213 0.216 0.220 0.224 0.236 0.238
   [20] 0.244 0.259 0.267 0.274 0.282 0.293 0.313 0.334 0.346 0.366
        0.386 0.431 0.449 0.461 0.465 0.468 0.520 0.533 0.536 0.541
   [40] 0.597 0.600 0.607 0.612 0.613 0.617 0.621 0.623 0.631 0.634
        0.646 0.658 0.662 0.664 0.670 0.670 0.675 0.686 0.693 0.694
   [60] 0.725 0.739 0.759 0.762 0.768 0.783 0.791 0.791 0.791 0.799
        0.838 0.850 0.854 0.874 0.897 0.912 0.920 0.934 0.944 0.992
   */

 

int ArrayMaximum(const type &array[], int start = 0, int count = WHOLE_ARRAY)

int ArrayMinimum(const type &array[], int start = 0, int count = WHOLE_ARRAY)

Функции ArrayMaximum и ArrayMinimum ищут в числовом массиве элементы с максимальным и минимальным значением, соответственно. Диапазон индексов для поиска задается параметрами start и count: со значениями по умолчанию выполняется поиск по всему массиву.

Функция возвращает позицию найденного элемента.

Если для массива установлено свойство "серийности" ("таймсерии", временного ряда), индексация элементов в нем ведется в обратном порядке, и это сказывается на результате данной функции (см. пример). Встроенные функции для работы со свойством "серийности" рассматриваются в следующем разделе. Более подробно о "серийных" массивах будет рассказано в главах про таймсерии и индикаторы.

В многомерных массивах поиск ведется по первому измерению.

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

Пример использования функций приведен в файле ArrayMaxMin.mq5.

#define LIMIT 10
   
void OnStart()
{
   // генерируем случайные данные
   int array[];
   ArrayResize(arrayLIMIT);
   for(int i = 0i < LIMIT; ++i)
   {
      array[i] = rand();
   }
   
   ArrayPrint(array);
   // по умолчанию новый массив не является таймсерией
   PRTS(ArrayMaximum(array));
   PRTS(ArrayMinimum(array));
   // включаем свойство "серийности"
   PRTS(ArraySetAsSeries(arraytrue));
   PRTS(ArrayMaximum(array));
   PRTS(ArrayMinimum(array));
}

Скрипт выведет в журнал примерно следующее (из-за случайной генерации данных каждый запуск будет отличаться):

22242 5909 21570 5850 18026 24740 10852 2631 24549 14635
ArrayMaximum(array)=5 / status:0
ArrayMinimum(array)=7 / status:0
ArraySetAsSeries(array,true)=true / status:0
ArrayMaximum(array)=4 / status:0
ArrayMinimum(array)=2 / status:0