- Символы и таймфреймы
- Технические особенности организации и хранения таймсерий
- Получение характеристик массивов котировок
- Количество доступных баров (Bars/iBars)
- Поиск индекса бара по времени (iBarShift)
- Обзор Copy-функций для получения массивов котировок
- Получение котировок в виде массива структур MqlRates
- Раздельный запрос массивов цен, объемов, спредов, времени
- Чтение цены, объема, спреда и времени по индексу бара
- Поиск максимального и минимального значения в таймсерии
- Работа с массивами реальных тиков в структурах MqlTick
Обзор Copy-функций для получения массивов котировок
MQL5 API содержит несколько функций для чтения котировочных таймсерий в массивы. Их имена приведены в следующей таблице.
Функция |
Действие |
---|---|
CopyRates |
Получение истории котировок в массив структур MqlRates |
CopyTime |
Получение истории времен открытия баров в массив типа datetime |
CopyOpen |
Получение истории цен открытия баров в массив типа double |
CopyHigh |
Получение истории максимальных цен баров в массив типа double |
CopyLow |
Получение истории минимальных цен баров в массив типа double |
CopyClose |
Получение истории цен закрытия баров в массив типа double |
CopyTickVolume |
Получение истории тиковых объемов в массив типа long |
CopyRealVolume |
Получение истории биржевых объемов в массив типа long |
CopySpread |
Получение истории спредов в массив типа int |
Все функции принимают в качестве двух первых параметров имя требуемого символа и периода, что можно условно представить следующим псевдокодом:
int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, ...) |
Также все функции имеют по три варианта прототипа, отличающихся способом задания запрашиваемого диапазона:
- начальный индекс бара и количество баров — Copy***(..., int offset, int count, ...);
- время начала диапазона и количество баров — Copy***(..., datetime start, int count, ...);
- время начала и окончания диапазона — Copy***(..., datetime start, datetime stop, ...).
При этом в нотации параметров подразумевается, что запрашиваемые данные имеют направление индексации, как в таймсерии, то есть в позиции offset с индексом 0 хранятся данные текущего незавершенного бара, и увеличение индексов соответствует продвижению вглубь ценовой истории. Из-за этого, в частности, для второго варианта указанное количество баров count будет отсчитываться в прошлое от начала диапазона offset, то есть в сторону уменьшения времени.
Дополнительную гибкость предоставляет третий вариант: в нем не важно, в каком порядке задать стартовую и финишную даты (start/stop), — функции в любом случае вернут данные в диапазоне от меньшей даты до большей. Подходящие бары отбираются по такому принципу, чтобы их время открытия находилось между временными отсчетами start/stop или было равно одному из них, т.е. диапазон [start;stop] рассматривается с включением границ.
Какой именно вариант функции выбрать, определяет разработчик, руководствуясь тем, что для него важнее — получить гарантированное количество элементов (например, для алгоритмов машинного обучения) или покрытие конкретного интервала дат (например, с заранее определенным единообразным поведением рынка).
Напомним, что точность представления времени в типе datetime составляет 1 секунду. Значения start/stop не обязаны быть округленными до размера периода. Например, диапазон от 14:59 до 16:01 позволит выделить на таймфрейме H1 два бара для 15:00 и 16:00. Вырожденный диапазон с равными и округленными метками, например, 15:00 в котировках H1, соответствует одному бару.
Вы можете запрашивать бары на дневном таймфрейме даже при наличии в параметрах start/stop ненулевых значений часов/минут/секунд (несмотря на то, что на таймфрейме D1 метки баров имеют время 00:00). При этом в результат попадут только те бары D1, что имеют время открытия после минимального из start/stop и до максимального из start/stop (равенство с метками дневных баров в данном случае невозможно, поскольку в искомом времени присутствуют часы/минуты/секунды). Например, между отсечками D'2021.09.01 12:00' и D'2021.09.03 07:00' находятся два времени открытия баров D1 — D'2021.09.02' и D'2021.09.03'. Именно эти бары и попадут в результат. Бар D'2021.09.01' имеет время открытия 00:00 раньше, чем начало диапазона, и потому отбрасывается. Бар D'2021.09.03' включен в результат, несмотря на то, что в диапазон попало только 7 утренних часов из этого дня. С другой стороны, запрос нескольких часов внутри дня, например, между D'2021.09.01 12:00' и D'2021.09.01 15:00' не охватит ни одного дневного бара (время открытия бара D'2021.09.01' не попадает в этот диапазон), и потому приемный массив будет пуст.
Единственное отличие всех функций из таблицы заключается в типе массива, принимающего данные, — он передается последним параметром по ссылке. Например, функция CopyRates помещает запрошенные данные в массив структур MqlRates, а функция CopyTime — время открытия баров в массив типа datetime, и так далее.
Таким образом, общие прототипы функций можно представить так:
int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, int offset, int count, type &result[])
int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, datetime start, int count, type &result[])
int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, datetime start, datetime stop, type &result[])
Здесь type соответствует любому из типов MqlRates, datetime, double, long или int, в зависимости от конкретной функции.
Функции возвращают количество скопированных элементов в массив либо -1 в случае ошибки. В частности, мы получим результат -1, если в запрашиваемом интервале нет никаких данных на сервере, или интервал находится за пределами максимального количества баров на графике (TerminalInfoInteger(TERMINAL_MAXBARS)).
Важно отметить, что в приемном массиве полученные данные всегда физически размещаются в хронологическом порядке, от прошлого к будущему. Таким образом, если для приемного массива используется стандартная индексация (то есть к нему не применялась функция ArraySetAsSeries), то элемент с 0-м индексом будет самым старым, а последний элемент — самым новым. Если же для массива была выполнена инструкция ArraySetAsSeries(result, true), то нумерация будет вестись в обратном порядке, как в таймсерии: 0-й элемент окажется самым новым в диапазоне, а последний — самым старым. Это иллюстрирует следующий рисунок.
Таймсерии терминала и приемный массив
В случае успеха в массив-получатель будет скопировано указанное количество элементов из собственной (внутренней) таймсерии терминала. При запросе данных по диапазону дат (start/stop), количество элементов в результирующем массиве определится опосредованно, исходя из содержимого истории в этом диапазоне. Поэтому для копирования заранее неизвестного количества значений рекомендуется использовать динамические массивы: функции копирования самостоятельно распределяют необходимый размер массивов-приемников (размер может быть как увеличен, так и уменьшен).
Если необходимо копировать известное количество элементов или делать это часто, например, при каждом вызове OnTick в экспертах или OnCalculate в индикаторах, то лучше использовать статически распределенные массивы. Дело в том, что операции распределения памяти под динамические массивы требуют дополнительного времени и могут сказаться на быстродействии, в особенности при тестировании и оптимизации.
Доступ к таймсериям осуществляется по-разному для MQL-программ разных типов, если запрашиваемые данные еще не готовы. Так в пользовательских индикаторах Copy-функции сразу же возвращают ошибку, поскольку индикаторы выполняются в общем интерфейсном потоке терминала и не могут ожидать получения данных (предполагается, что индикаторы запросят данные при следующих вызовах их обработчиков событий, и таймсерии к тому моменту уже будут закачаны и построены). Кроме того, в главе, посвященной индикаторам, мы узнаем, что для доступа к котировкам "родного" графика, на котором индикатор и размещен, ему не нужно использовать Copy-функции, потому что все временные ряды автоматически передаются через параметры-массивы обработчика OnCalculate.
При доступе из экспертов и скриптов производится несколько попыток получения данных с небольшой паузой (с ожиданием внутри функции), дающей время для загрузки и расчета недостающих таймсерий. Функция вернет то количество данных, которые будут готовы к моменту истечения этого таймаута, но загрузка истории будет продолжаться, и при следующем аналогичном запросе функция вернет уже больше данных.
В любом случае следует быть готовым, что Copy-функция вернет вместо данных ошибку (причин достаточно: нарушение связи, отсутствие запрошенных данных, загруженность процессора, если параллельно запрашивается много новых таймсерий): проанализируйте в коде причину проблемы (_LastError) и повторите попытку позже, откорректируйте параметры или сообщите пользователю.
Наличие символа в Обзоре рынка не является необходимым условием для запроса таймсерий с помощью Copy-функий, однако для символов, включенных в данное окно, запросы, как правило, выполняются быстрее, поскольку некоторые данные уже скачаны с сервера и, вероятно, посчитаны для востребованных периодов. О том, как добавлять символы в Обзор рынка программным способом, мы узнаем в разделе Редактирование списка Обзора рынка.
Для пояснения принципов работы функций на практике рассмотрим скрипт SeriesCopy.mq5. Он содержит несколько вызовов функции CopyTime, что позволяет наглядно увидеть, как соотносятся временные метки и номера баров.
В скрипте определен динамический массив times для приема данных. Все запросы делаются для символа "EURUSD" и таймфрейма H1.
void OnStart()
|
Для начала делается запрос 10 баров, начиная с 5 сентября 2021, в прошлое. Поскольку этот день воскресенье, предыдущие бары были в пятницу 3-го числа (см. лог ниже).
PRTF(CopyTime("EURUSD", PERIOD_H1, D'2021.09.05', 10, times)); // 10 / ok
|
Вывод массива производится по умолчанию в хронологическом порядке (несмотря на то, что параметры функции задаются в обратной системе координат: как в таймсерии). Поменяем порядок индексации в приемном массиве и выведем его еще раз.
PRTF(ArraySetAsSeries(times, true)); // true / ok
|
Для следующих экспериментов восстановим обычный порядок.
PRTF(ArraySetAsSeries(times, false)); // true / ok |
Теперь запросим неопределенное количество баров между двумя временными отсчетами (количество неизвестно, потому что в диапазоне могут оказаться праздники, например). Сделаем это двумя способами: в первом случае укажем диапазон от будущего в прошлое, а во втором — из прошлого в будущее. Оба результата совпадут.
// FROM TO
|
Распечатка массивов позволяет убедиться в их идентичности. Вернемся к режиму индексации как в таймсерии и обсудим еще один нюанс.
PRTF(ArraySetAsSeries(times, true)); // true / ok
|
Хотя две временных метки отстоят друг от друга на 24 часа, что предполагает получение в массиве 25 элементов (вспоминаем, что начало и конец обрабатываются включительно), результат содержит только 4 бара. Дело в том, что 5-е сентября приходится на воскресенье, и потому из всего диапазона торговля велась только в утренние часы 6-го числа.
Также обратите внимание, что приемный массив был автоматически уменьшен в размере с 10 до 4 элементов.
Наконец, запросим 10 баров, начиная с 100-го бара (полученные результаты будут зависеть от вашего текущего времени и доступной истории).
PRTF(CopyTime("EURUSD", PERIOD_H1, 100, 10, times)); // 10 / ok
|
Из-за индексации как в таймсерии, массив выведен в обратном хронологическом порядке.