- Направление индексации в массивах и таймсериях
- Организация доступа к данным
- SeriesInfoInteger
- Bars
- BarsCalculated
- IndicatorCreate
- IndicatorParameters
- IndicatorRelease
- CopyBuffer
- CopyRates
- CopySeries
- CopyTime
- CopyOpen
- CopyHigh
- CopyLow
- CopyClose
- CopyTickVolume
- CopyRealVolume
- CopySpread
- CopyTicks
- CopyTicksRange
- iBars
- iBarShift
- iClose
- iHigh
- iHighest
- iLow
- iLowest
- iOpen
- iTime
- iTickVolume
- iRealVolume
- iVolume
- iSpread
Организация доступа к данным
В этом разделе рассматриваются вопросы, связанные с получением, хранением и запросами ценовых данных (таймсерий).
Получение данных от торгового сервера
Прежде чем ценовые данные будут доступны в терминале MetaTrader 5, их необходимо получить и обработать. Для получения данных требуется подключение к торговому серверу MetaTrader 5. Данные поступают с сервера по запросу терминала в виде экономно упакованных блоков минутных баров.
Механизм обращения к серверу за данными не зависит от того, каким образом был инициирован запрос — пользователем при навигации по графику или программным способом на языке MQL5.
Хранение промежуточных данных
Полученные с сервера данные автоматически распаковываются и сохраняются в специальном промежуточном формате HCC. Данные по каждому символу пишутся в отдельную папку каталог_терминала\bases\имя_сервера\history\имя_символа. Например, данные по символу EURUSD с торгового сервера MetaQuotes-Demo будут находиться в папке каталаг_терминала\bases\MetaQuotes-Demo\history\EURUSD\.
Данные записываются в файлы с расширением .hcc, каждый файл хранит данные минутных баров за год. Например, файл 2009.hcc в папке EURUSD содержит минутные бары по символу EURUSD за 2009 год. Эти файлы используются для подготовки ценовых данных по всем таймфреймам и не предназначены для прямого доступа.
Получение данных нужного таймфрейма из промежуточных данных
Служебные файлы в формате HCC исполняют роль источника данных для построения ценовых данных по запрошенным таймфреймам в формате HC. Данные в формате HC являются таймсериями, максимально подготовленными для быстрого доступа. Они создаются только по запросу графика или mql5-программы в объеме, не превышающем значения параметра "Max bars in charts", и сохраняются для дальнейшего использования в файлах с расширением hc.
Для экономии ресурсов данные по таймфрейму загружаются и хранятся в оперативной памяти только по необходимости, при длительном отсутствии обращений к данным происходит выгрузка их из оперативной памяти с сохранением в файл. Для каждого таймфрейма данные подготавливаются независимо от наличия уже готовых данных для других таймфреймов. Правила формирования и доступности данных одинаковы для всех таймфреймов. Т.е. не смотря на то, что единицей хранения данных в формате HCC является минутный бар, наличие данных в формате HCC не означает наличие и доступность в том же объеме данных таймфрейма М1 в формате HC.
Получение новых данных с сервера вызывает автоматическое обновление используемых ценовых данных в формате HC по всем таймфреймам и перерасчет всех индикаторов, которые явно используют их в качестве входных данных для расчета.
Параметр "Max bars in chart"
Параметр "Max bars in charts" ограничивает доступное для графиков, индикаторов и mql5-программ количество баров в формате HC. Это ограничение действует для данных всех таймфреймов, и предназначено в первую очередь для экономии ресурсов.
Устанавливая большие значения данного параметра, следует помнить, что при наличии достаточно глубокой истории ценовых данных для младших таймфреймов расход памяти на хранение таймсерий и буферов индикаторов может составить сотни мегабайт и достигнуть ограничения оперативной памяти для программы клиентского терминала (2Гб для 32-битных приложений MS Windows).
Изменение параметра "Max bars in charts" вступает в силу после перезапуска клиентского терминала. Само по себе изменение данного параметра не вызывает ни автоматического обращения к серверу за дополнительными данными, ни формирования дополнительных баров таймсерий. Запрос дополнительных ценовых данных у сервера и обновление таймсерий с учетом нового ограничения произойдет либо в случае прокрутки графика в область недостающих данных, либо в случае запроса недостающих данных из mql5-программы.
Объем запрашиваемых у сервера данных соответствует требуемому количеству баров данного таймфрейма с учетом значения параметра "Max bars in charts". Ограничение, задаваемое параметром, не является жестким, и в некоторых случаях количество доступных баров по таймфрейму может быть незначительно больше текущего значения параметра.
Доступность данных
Наличие данных в формате HCC или даже в готовом для использования формате HC не всегда означает безусловную доступность этих данных для отображения на графике или для использования в mql5-программах.
При доступе к ценовым данным или к значениям индикаторов из mql5-программ следует помнить, что не гарантируется их доступность в определенный момент времени, либо с определенного момента времени. Это связано с тем, что в целях экономии ресурсов в MetaTrader 5 не хранится полная копия требуемых данных для mql5-программы, а дается прямой доступ к базе данных терминала.
Ценовая история по всем таймфреймам строится из общих данных формата HCC и любое обновление данных с сервера приводит к обновлению данных по всем таймфреймам и пересчету индикаторов. Вследствие этого, в доступе к данным может быть отказано даже в том случае, если эти данные были доступны мгновение назад.
Синхронизация данных терминала и данных сервера #
Поскольку mql5-программа может обратиться к данным по любому символу и таймфрейму, то есть вероятность, что данные требуемой таймсерии еще не сформированы в терминале или требуемые ценовые данные не синхронизированы с торговым сервером. В этом случае время ожидания готовности данных сложно прогнозировать.
Алгоритмы с использованием циклов ожидания готовности данных являются не лучшим решением. Единственное исключение в данном случае — скрипты, так как у них нет другого выбора алгоритма в виду отсутствия обработки событий. Для пользовательских индикаторов подобные алгоритмы, как и любые другие циклы ожидания, категорически не рекомендуются, так как приводят к остановке расчета всех индикаторов и другой обработки ценовых данных по данному символу.
Для экспертов и пользовательских индикаторов лучше использовать событийную модель обработки. Если при обработке события OnTick() или OnCalculate() не удалось получить все необходимые данные требуемой таймсерии, то следует выйти из обработчика события, рассчитывая на появление доступа к данным при следующем вызове обработчика.
Пример скрипта для закачки истории
Рассмотрим пример скрипта, который выполняет запрос на получение истории с торгового сервера по указанному инструменту. Скрипт предназначен для запуска на графике требуемого инструмента, таймфрейм значения не имеет, так как, как уже было сказано выше, ценовые данные приходят с торгового сервера в виде упакованных минутных данных, из которых в последствие и строится любая предопределенная таймсерия.
Оформим все действия по получению данных в виде отдельной функции CheckLoadHistory(symbol, timeframe, start_date):
int CheckLoadHistory(string symbol,ENUM_TIMEFRAMES period,datetime start_date)
|
Функция CheckLoadHistory() задумана как универсальная, которую могут вызвать из любой программы (эксперт, скрипт или индикатор), и поэтому требует три входных параметра: имя символа, период и начальную дату, с которой нам требуется ценовая история.
Вставим в код функции все необходимые проверки, прежде чем мы потребуем недостающую историю. Первым делом необходимо убедиться, что имя символа и значение периода являются корректными:
if(symbol==NULL || symbol=="") symbol=Symbol();
|
Далее — убедимся, что указанный символ доступен в окне MarketWatch, то есть, история по данному символу будет доступна при запросе к торговому серверу. Если его там нет - добавим символ в окно самостоятельно с помощью функции SymbolSelect().
if(!SymbolInfoInteger(symbol,SYMBOL_SELECT))
|
Теперь необходимо получить начальную дату по уже имеющейся истории для указанной пары символ/период. Возможно, что значение входного параметра startdate, переданного функции CheckLoadHistory() попадает в интервал уже доступной истории, и тогда никакого запроса к торговому серверу не потребуется. Для получения самой первой даты по символу-периоду на данный момент предназначена функция SeriesInfoInteger() с модификатором SERIES_FIRSTDATE.
SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date);
|
Следующая важная проверка — проверка типа программы, из которой вызывается функция. Напомним, что отправка запроса на обновление таймсерии с тем же периодом, что и у индикатора, вызывающего обновление, крайне нежелательна. Нежелательность запроса данных по тому же символу-периоду, что и у индикатора обусловлена тем, что обновление исторических данных производится в том же потоке, в котором работает индикатор. Поэтому велика вероятность клинча. Для проверки используем функцию MQL5InfoInteger() с модификатором MQL5_PROGRAM_TYPE.
if(MQL5InfoInteger(MQL5_PROGRAM_TYPE)==PROGRAM_INDICATOR && Period()==period && Symbol()==symbol)
|
Если мы успешно прошли все проверки, то сделаем последнюю попытку обойтись без обращения к торговому серверу. Сначала узнаем начальную дату, для которой доступны минутные данные в формате HCC. Запросим это значение функцией SeriesInfoInteger() с модификатором SERIES_TERMINAL_FIRSTDATE и опять сравним со значением параметра start_date.
if(SeriesInfoInteger(symbol,PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_date))
|
Если после всех проверок поток выполнения по-прежнему находится в теле функции CheckLoadHistory(), то значит, есть необходимость запросить недостающие ценовые данные с торгового сервера. Для начала мы узнаем значение "Max bars in chart" с помощью функции TerminalInfoInteger():
int max_bars=TerminalInfoInteger(TERMINAL_MAXBARS); |
Оно нам понадобится для того, чтобы не запрашивать лишние данные. Затем выясним самую первую дату в истории по символу на торговом сервере (независимо от периода) посредством уже знакомой функции SeriesInfoInteger() с модификатором SERIES_SERVER_FIRSTDATE.
datetime first_server_date=0;
|
Так как запрос является асинхронной операцией, то функция вызывается в цикле с небольшой задержкой в 5 миллисекунд до тех пор, пока переменная first_server_date не получит значение либо выполнение цикла не будет прервано пользователем (IsStopped() в этом случае вернет значение true). Укажем корректное значение начальной даты, начиная с которой мы запрашиваем у торгового сервера ценовые данные.
if(first_server_date>start_date) start_date=first_server_date;
|
Если вдруг начальная дата first_server_date на сервере окажется меньше, чем начальная дата first_date по символу в формате HCC , то в журнал будет выведено соответствующее сообщение.
Теперь мы готовы сделать запрос к торговому серверу за недостающими ценовыми данными. Запрос сделаем в виде цикла и начнем заполнять его тело:
while(!IsStopped())
|
Первые три пункта реализуются уже знакомыми приемами.
while(!IsStopped())
|
Остался последний четвертый пункт — непосредственный запрос истории. Мы не можем прямо обратиться к серверу, но любая Copy-функция при нехватке истории в формате HCC терминал автоматически инициирует посылку такого запроса от терминала к торговому серверу. Так как время самой первой начальной даты в переменной first_date является самым простым и естественным критерием для оценки степени выполнения запроса, то самым простым будет использовать функцию CopyTime().
При вызове функций, осуществляющих копирование любых данных из таймсерий, необходимо иметь в виду то, что параметр start (номер бара, с которого начинать копирование ценовых данных) всегда должен быть в пределах доступной истории терминала. Если у нас имеется только 100 баров, то не имеет смысла пытаться скопировать 300 баров, начиная с бара с индексом 500. Такой запрос будет воспринят как ошибочный и не будет обработан, т.е. никакая история с торгового сервера подгружена не будет.
Именно поэтому мы будем копировать по 100 баров, начиная с бара с индексом bars. Это обеспечит плавную подкачку истории с торгового сервера, при этом реально будет подгружено чуть более запрошенных 100 баров, сервер отдает историю с запасом.
int copied=CopyTime(symbol,period,bars,100,times); |
После операции копирования необходимо проанализировать количество скопированных элементов, если попытка оказалась неудачной, то значение переменной copied будет равно нулю и значение счетчика fail_cnt будет увеличено на 1. После 100 неудачных попыток работа функции будет прервана.
int fail_cnt=0;
|
Таким образом, в функции не только организована правильная обработка текущей ситуации в каждый момент выполнения, но и еще возвращается код завершения, который мы можем обработать после вызова функции CheckLoadHistory() для получения дополнительную информацию. Например, таким образом:
int res=CheckLoadHistory(InpLoadedSymbol,InpLoadedPeriod,InpStartDate);
|
Полный код функции приведен в примере скрипта, демонстрирующего правильный способ организации доступа к любым данным с обработкой результат запроса.
Код:
//+------------------------------------------------------------------+
|