Массивы в MQL и их применение в торговле
ВведениеРынок — это поток данных. Каждый тик, каждый бар, каждое значение индикатора — отдельная величина, но по отдельности они почти бесполезны. Трейдер-разработчик мыслит сериями: последние сто закрытий, значения RSI за час, список сделок за месяц. Чтобы работать с такими сериями, в MQL есть массивы. Это одна из базовых конструкций языка, и от того, насколько свободно вы ей владеете, напрямую зависит, сможете ли вы писать расчёты, которые работают быстро и без ошибок. В этой статье мы разберём массивы в MQL от объявления до практических задач: копирование данных из терминала, работу с буферами индикаторов, ресемплинг, накопление статистики и типичные ошибки, которые чаще всего ломают советники.
Зачем трейдеру-разработчику массивыПредставьте, что нужно посчитать среднюю цену закрытия за последние 20 баров. Можно завести двадцать переменных и сложить их вручную — но такое решение рассыплется при первой же попытке изменить период. Массив решает задачу иначе: он хранит набор однотипных значений под одним именем, а цикл обрабатывает их за несколько строк.
Массивы нужны почти в каждой торговой задаче. Скользящие средние, стандартные отклонения, поиск максимума и минимума, накопление профита по сделкам, построение собственных индикаторов, ресемплинг таймфреймов, поиск последнего сигнала в истории — всё это опирается на массивы. Более того, штатные функции MetaTrader возвращают данные именно в массивы: CopyBuffer заполняет массив значениями индикатора, CopyRates — массивом структур баров. Поэтому без массивов просто не обойтись.
Объявление и инициализацияМассив объявляется как обычная переменная, только после имени в квадратных скобках указывается размер.
// === Статический массив: размер фиксирован при объявлении ===double closes[20];int volumes[50];
Если значения известны заранее, массив можно инициализировать списком:
// === Инициализация списком значений ===int fastPeriods[3] = {5, 10, 20};
Обратите внимание: индексы начинаются с нуля. У массива из трёх элементов допустимы индексы 0, 1 и 2. Это правило — источник множества ошибок, о которых мы поговорим отдельно.
Динамический массив объявляется без указания размера — квадратные скобки остаются пустыми:
// === Динамический массив: размер задаётся во время работы ===double buffer[];
Такой массив не содержит элементов, пока вы не зададите его размер.
Управление размером: ArrayResize, ArraySize, ArrayFreeДля динамических массивов есть три ключевые функции. ArraySize возвращает текущее число элементов, ArrayResize меняет размер, а ArrayFree освобождает память.
// =============================================================================================== //// = Управляем размером динамического массива = //// =============================================================================================== //double buffer[];
ArrayResize(buffer, 0); // очистить: массив без элементовArrayResize(buffer, 100); // выделить память под 100 элементовint size = ArraySize(buffer); // size == 100
// === Освобождаем память, когда массив больше не нужен ===ArrayFree(buffer);
Важная деталь: при увеличении размера старые элементы сохраняются, новые заполняются нулями. Если размер уменьшается, «лишние» элементы отбрасываются. А вот ArrayFree полностью освобождает память — после него ArraySize вернёт ноль.
ArrayResize возвращает новый размер или -1 при ошибке. Если вы часто добавляете элементы по одному, лучше увеличивать размер блоками: многократное перевыделение памяти на каждом тике — прямой путь к просадке производительности.
Направление индексации и ArraySetAsSeriesЭто, пожалуй, самый коварный момент для новичка. В MetaTrader таймсерии (цены, объёмы) по умолчанию проиндексированы так, что индекс 0 соответствует самому свежему бару, а индекс растёт в прошлое. Между тем обычный массив индексируется с начала: индекс 0 — первый, самый старый элемент.
Функция ArraySetAsSeries управляет этим поведением для конкретного массива.
// === Массив как таймсерия: индекс 0 — самый свежий бар ===ArraySetAsSeries(buffer, true);
// === Обычный порядок: индекс 0 — самый старый элемент ===ArraySetAsSeries(buffer, false);
Важно понимать: ArraySetAsSeries не переставляет данные физически, а лишь меняет способ доступа к ним. Если вы скопировали цены в массив и не настроили направление, легко получить расчёт «наоборот»: вместо свежего бара взять самый старый. Правило простое — определитесь с направлением сразу после копирования данных и держитесь его на протяжении всего кода.
Статические и динамические массивыСтатический массив создаётся один раз и живёт всё время работы программы. Его размер фиксирован, зато обращение к элементам происходит быстро и без накладных расходов. Это хороший выбор, когда размер известен заранее: например, массив из двух элементов под цены bid и ask или фиксированный набор периодов для набора индикаторов.
Динамический массив гибок: размер можно менять в процессе работы. Именно такие массивы используют для данных, объём которых заранее неизвестен, — например, для накопления списка сделок или для буферов, размер которых зависит от количества копируемых баров.
// =============================================================================================== //// = Статический массив под фиксированный набор параметров = //// =============================================================================================== //double g_levels[2] = {1.1000, 1.2000};
// === Динамический массив под данные переменной длины ===double g_tradeProfits[];
Выбор между ними — это компромисс между предсказуемостью и гибкостью. Там, где размер известен, статический массив проще и надёжнее; там, где он меняется, без динамического не обойтись.
Многомерные массивыМассивы могут быть многомерными — до четырёх измерений в MQL. Двумерный массив удобно представлять как таблицу: строки и столбцы. Например, для хранения сетки параметров или матрицы корреляций.
// =============================================================================================== //// = Двумерный массив: таблица значений = //// =============================================================================================== //double matrix[3][4]; // 3 строки, 4 столбца
// === Заполняем и читаем элемент по двум индексам ===matrix[0][0] = 1.5;double value = matrix[2][3];
У многомерного динамического массива ArrayResize меняет только первое измерение. Остальные измерения должны быть заданы заранее, иначе компилятор выдаст ошибку. Для расчётов это неудобно, поэтому на практике многомерные массивы чаще применяют для компактных таблиц фиксированного размера, а большие наборы данных хранят в одномерных массивах или структурах.
Буферы индикаторов и CopyBufferИндикаторы в MQL5 построены на буферах — это массивы, которые отображаются на графике. Чтобы получить значения индикатора внутри советника, используют CopyBuffer. Сначала создаётся хэндл индикатора, затем его буфер копируется в массив.
// =============================================================================================== //// = Копируем значения индикатора в массив через CopyBuffer = //// =============================================================================================== //double rsi[];int rsiHandle = iRSI(_Symbol, PERIOD_H1, 14, PRICE_CLOSE);
// === Копируем 100 последних значений нулевого буфера ===ArraySetAsSeries(rsi, true);int copied = CopyBuffer(rsiHandle, 0, 0, 100, rsi);
// === Проверяем, что данные действительно скопированы ===if (copied <= 0) { Print("Не удалось скопировать буфер индикатора"); return; }
Здесь важно проверять результат копирования. Терминал может ещё не успеть рассчитать индикатор, и тогда CopyBuffer вернёт -1 или ноль. Всегда проверяйте возвращаемое значение и убеждайтесь, что данных хватает, прежде чем обращаться к элементам массива.
Цены через CopyRates и MqlRatesЕсли нужны цены открытия, максимума, минимума и закрытия, удобнее копировать сразу целые бары. Для этого есть функция CopyRates и структура MqlRates.
// =============================================================================================== //// = Копируем бары в массив структур MqlRates = //// =============================================================================================== //MqlRates rates[];ArraySetAsSeries(rates, true);int copied = CopyRates(_Symbol, PERIOD_H1, 0, 100, rates);
// === Читаем поля самого свежего бара (индекс 0) ===if (copied > 0) { double high = rates[0].high; double low = rates[0].low; long vol = rates[0].tick_volume; Print("High: ", high, " Low: ", low, " Volume: ", vol); }
Структура MqlRates содержит поля time, open, high, low, close, tick_volume, spread и real_volume. Такой подход удобнее, чем отдельно копировать цены закрытия и максимумы: вы получаете весь бар одним вызовом.
Практические задачиТеперь разберём, как массивы применяются в реальных задачах.
Среднее значениеСамый частый расчёт — среднее арифметическое. Складываем элементы и делим на количество.
// =============================================================================================== //// = Среднее значение массива = //// =============================================================================================== //double Average(const double &data[]) { int n = ArraySize(data); if (n == 0) return 0.0;
double sum = 0.0; for (int i = 0; i < n; i++) sum += data[i];
return sum / n; }
Обратите внимание на передачу массива в функцию через ссылку с модификатором const — так данные не копируются и не изменяются внутри функции.
Максимум и минимумДля поиска экстремумов есть готовые функции ArrayMaximum и ArrayMinimum. Они возвращают индекс, а не значение, поэтому значение нужно взять по индексу.
// =============================================================================================== //// = Индекс и значение максимума массива = //// =============================================================================================== //int maxIndex = ArrayMaximum(closes, 0, ArraySize(closes));double maxValue = closes[maxIndex];
// === Минимум ищется аналогично ===int minIndex = ArrayMinimum(closes, 0, ArraySize(closes));double minValue = closes[minIndex];
Накопление статистики сделокМассивы хорошо подходят для накопления статистики: например, списка профитов по закрытым сделкам. Динамический массив растёт по мере поступления данных.
// =============================================================================================== //// = Добавляем профит сделки в динамический массив = //// =============================================================================================== //double g_profits[];
void AddProfit(double profit) { int size = ArraySize(g_profits); ArrayResize(g_profits, size + 1); g_profits[size] = profit; }
По такому массиву затем легко посчитать сумму, средний профит или долю прибыльных сделок.
Ресемплинг данныхРесемплинг — это пересборка баров одного таймфрейма в другой, например из минутных в часовые. Проходим массив исходных баров и накапливаем значения в новый.
// =============================================================================================== //// = Собираем часовые бары из минутных = //// =============================================================================================== //MqlRates m1[];CopyRates(_Symbol, PERIOD_M1, 0, 60, m1);
double hourlyHigh = m1[0].high;double hourlyLow = m1[0].low;
for (int i = 1; i < ArraySize(m1); i++) { if (m1[i].high > hourlyHigh) hourlyHigh = m1[i].high; if (m1[i].low < hourlyLow) hourlyLow = m1[i].low; }
Это упрощённый пример, но он показывает суть: массив исходных данных сворачивается в агрегаты.
Поиск последнего сигналаЧастая задача — найти самый свежий бар, на котором выполнилось условие. Проходим массив от нулевого индекса (свежие данные) вглубь и останавливаемся на первом совпадении.
// =============================================================================================== //// = Ищем последний бар, где значение превысило порог = //// =============================================================================================== //int FindLastSignal(const double &data[], double threshold) { for (int i = 0; i < ArraySize(data); i++) { if (data[i] > threshold) return i; // первый совпавший индекс — самый свежий } return -1; // сигнал не найден }
Здесь направление индексации критично: поиск от нуля имеет смысл только для массива, настроенного как таймсерия.
Типичные ошибкиВыход за границы массиваОбращение к индексу, которого нет, — самая частая причина сбоя. Если массив содержит 100 элементов, допустимы индексы от 0 до 99. Обращение к data[100] — ошибка.
// === ОПАСНО: индекс выходит за границы ===double data[10];// data[10] = 5.0; // ошибка: последний допустимый индекс — 9
Всегда сверяйтесь с ArraySize, особенно когда данные приходят из терминала и их количество заранее неизвестно.
Путаница направления индексацииВторая по частоте ошибка — перепутать, где «свежий», а где «старый» бар. Если массив настроен как таймсерия, индекс 0 — последний бар; если нет — первый. Смешение этих правил даёт логику «шиворот-навыворот», которую трудно заметить.
Забытый ArrayResizeПопытка записать элемент в динамический массив нулевого размера приведёт к ошибке. Перед записью размер нужно задать.
// === НЕПРАВИЛЬНО: массив пуст ===double buf[];// buf[0] = 1.0; // ошибка
// === ПРАВИЛЬНО: сначала задаём размер ===ArrayResize(buf, 1);buf[0] = 1.0;
Лишние копирования в OnTick и влияние на скоростьOnTick вызывается на каждом тике — иногда десятки раз в секунду. Если копировать сотни баров при каждом вызове, советник начнёт тратить ресурсы на копирование вместо торговой логики. Копируйте данные только при появлении нового бара.
// =============================================================================================== //// = Копируем данные только на новом баре = //// =============================================================================================== //void OnTick() { static datetime lastBar = 0; datetime currentBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
// === Бар не менялся — данные уже актуальны === if (currentBar == lastBar) return;
lastBar = currentBar;
// === ... здесь копирование массивов и логика на новом баре === }
Такая привычка резко снижает нагрузку. Помните: каждое копирование — это выделение памяти и перебор данных, а значит, время. Чем меньше лишних копирований, тем отзывчивее робот.
ЗаключениеМассивы — это фундамент, на котором строится вся обработка рыночных данных в MQL. Мы разобрали объявление и инициализацию, управление размером через ArrayResize, ArraySize и ArrayFree, направление индексации и ArraySetAsSeries, разницу между статическими и динамическими массивами, многомерные массивы, копирование буферов индикаторов и цен, а также практические задачи от среднего значения до ресемплинга. Отдельно остановились на типичных ошибках: выходе за границы, путанице индексации, забытом ArrayResize и лишних копированиях в OnTick.
Освойте эти приёмы — и расчёты в ваших советниках станут и быстрее, и надёжнее. Больше учебных материалов — на https://max.ru/channel_mqlmasters, а короткие практические разборы выходят в Телеграме: https://t.me/mqlmasters
#MQL5 #массивы #программирование #MetaTrader5 #MQLMaster
Владимир, опубликовал запись .
С момента публикации зафиксировано 44 просмотра. Сейчас эту запись просматривают 2 незарегистрированных пользователя.
|
|