Работа с массивами реальных тиков в структурах MqlTick

MetaTrader 5 обеспечивает возможность работать не только с историей котировок (баров), но и историей реальных тиков. Из пользовательского интерфейса все исторические данные доступны в диалоге Символы. В нем имеются 3 закладки: Спецификация, Бары и Тики. Когда в древовидном списке символов на первой закладке выделен конкретный элемент, при переходе на закладки Бары и Тики можно запросить котировки в виде баров или тиков, соответственно.

Из MQL-программ история реальных тиков также доступна — с помощью функций CopyTicks и CopyTicksRange.  

int CopyTicks(const string symbol, MqlTick &ticks[], uint flags = COPY_TICKS_ALL, ulong from = 0, uint count = 0)

int CopyTicksRange(const string symbol, MqlTick &ticks[], uint flags = COPY_TICKS_ALL, ulong from = 0, ulong to = 0)

Обе функции запрашивают тики для указанного инструмента symbol в передаваемый по ссылке массив ticks. Структура MqlTick содержит всю информацию об одном тике и описана в MQL5 следующим образом:

struct MqlTick
{
   datetime time;        // время данного обновления цен
   double   bid;         // текущая цена Bid
   double   ask;         // текущая цена Ask
   double   last;        // цена последней сделки (Last)
   ulong    volume;      // объем для цены Last
   long     time_msc;    // время данного обновления цен в миллисекундах
   uint     flags;       // флаги (какие поля структуры изменились)
   double   volume_real// объем для цены Last c повышенной точностью
};

Поле flags предназначено для хранения битовой маски признаков, какие именно поля в структуре тика содержат измененные значения.

Константа

Значение

Описание

TICK_FLAG_BID

2

Изменена цена Bid

TICK_FLAG_ASK

4

Изменена цена Ask

TICK_FLAG_LAST

8

Изменена цена Last

TICK_FLAG_VOLUME

16

Изменен объем

TICK_FLAG_BUY

32

Тик возник в результате сделки на покупку

TICK_FLAG_SELL

64

Тик возник в результате сделки на продажу

Это потребовалось потому, что у каждого тика всегда заполняются все поля, независимо от того, изменились ли данные по сравнению с предыдущим тиком. Это позволяет всегда иметь актуальное состояние цен на любой момент времени без поиска предыдущих значений по тиковой истории. Например, с тиком могла измениться только цена Bid, но в структуре помимо новой цены будут указаны и остальные параметры: предыдущая цена Ask, Last, объем и так далее.

Вместе с тем, следует иметь в виду, что в зависимости от типа инструмента некоторые поля в тиках могут быть всегда нулевыми (и для них никогда не взводятся соответствующие биты маски). В частности, для инструментов Forex, как правило, остаются пустыми поля last, volume, volume_real.  

Приемный массив ticks может быть фиксированного размера или динамический. В фиксированный массив функции скопируют не больше тиков, чем размер массива, невзирая на реальное количество тиков в запрошенном интервале времени (указанном параметрами from/to в функции CopyTicksRange) или в параметре count функции CopyTicks. В массиве ticks наиболее старые тики размещаются под первыми номерами, а наиболее новые — под последними.

В параметрах обеих функций временные отсчеты задаются в виде миллисекунд, прошедших с 01.01.1970 00:00:00. Так в функции CopyTicks диапазон запрашиваемых тиков задается начальным отсчетом from и количеством тиков count, а в функции CopyTicksRange — начальным и конечным отсчетами from и to (обе границы — включительно).

Иными словами, CopyTicksRange предназначена для получения тиков в конкретном интервале, причем их количество заранее не известно. А CopyTicks гарантирует получение не более count тиков, но не позволяет заранее определить, какой временной интервал эти тики покроют.

Хронологический порядок значений from и to в CopyTicksRange неважен: функция в любом случае отдаст тики, начиная от минимального из двух значений и заканчивая максимальным.

Функция CopyTicks расценивает параметр from как левую границу с минимальным временем и отсчитывает от неё count тиков в будущее. Однако существует важное исключение: значение from = 0 (по умолчанию) трактуется как текущий момент времени, и от него тики отсчитываются в прошлое. Это дает возможность всегда получить заданное количество последних тиков. Когда параметр count = 0 (по умолчанию), функция копирует не более 2000 тиков.

Обе функции возвращают количество скопированных тиков либо -1 в случае ошибки. В частности GetLastError может возвращать следующие коды ошибок:

  • ERR_HISTORY_TIMEOUT — время ожидания синхронизации тиков вышло, функция отдала всё что было;
  • ERR_HISTORY_SMALL_BUFFER — статический буфер слишком маленький, отдано столько, сколько поместилось в массив;
  • ERR_NOT_ENOUGH_MEMORY — не удалось выделить нужный объем памяти для получения истории тиков из указанного диапазона в динамический массив.

Параметр flags определяет тип запрашиваемых тиков.

Константа

Значение

Описание

COPY_TICKS_INFO

1

Тики, вызванные изменениями Bid и/или Ask
(TICK_FLAG_BID, TICK_FLAG_ASK)

COPY_TICKS_TRADE

2

Тики с изменениями Last и Volume
(TICK_FLAG_LAST, TICK_FLAG_VOLUME, TICK_FLAG_BUY, TICK_FLAG_SELL)

COPY_TICKS_ALL

3

Все тики

При любом типе запроса в оставшиеся поля структуры MqlTick, не соответствующие флагам, подставляются предыдущие актуальные значения. Например, если запрашивались только информационные тики (COPY_TICKS_INFO), в них все равно будут заполнены остальные поля — так, если изменилась только цена Bid, в поля ask и volume будут записаны последние известные значения. Чтобы узнать, что именно новое содержит тик, необходимо анализировать его поле flags (там будет либо значение TICK_FLAG_BID, либо TICK_FLAG_ASK, либо их комбинация). Если тик имеет нулевые значения цен Bid и Ask, и при этом флаги показывают, что данные цены изменились (flags == TICK_FLAG_BID | TICK_FLAG_ASK), то это говорит об опустошении стакана заявок.

Аналогичным образом, если запрашивались торговые тики (COPY_TICKS_TRADE), в их поля bid и ask будут записаны последние известные значения цен. В данном случае поле flags может иметь комбинацию значений TICK_FLAG_LAST, TICK_FLAG_VOLUME, TICK_FLAG_BUY, TICK_FLAG_SELL.

При запросе COPY_TICKS_ALL отдаются все тики.

Вызов любой из функций CopyTicks/CopyTicksRange проверяет синхронизацию базы тиков, хранящихся на жёстком диске по заданному символу. Если тиков в локальной базе не хватает, то недостающие тики автоматически будут загружены с торгового сервера. При этом будут синхронизированы тики с учетом старейшей даты из параметров запроса и вплоть до текущего момента. После этого все приходящие по данному символу тики будут поступать в тиковую базу и поддерживать её в актуальном синхронизированном состоянии.

Тиковые данные имеют значительно больший размер, чем минутные котировки. При первом запросе истории тиков или запуске тестирования по реальным тикам их скачивание может занять продолжительное время. История тиковых данных хранится в файлах внутреннего TKC-формата в каталоге {каталог_терминала}/bases/{имя_сервера}/ticks/{имя_символа}. Каждый файл содержит информацию за один месяц.

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

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

Напомним, что тики, поступающие в режиме реального времени, транслируются на графики в виде событий: индикаторы получают уведомления о новых тиках в обработчике OnCalculate, а эксперты — в обработчике OnTick. При этом следует иметь в виду, что система не гарантирует доставку всех событий: если за то время, пока программа обрабатывает текущее событие OnCalculate/OnTick, в терминал поступили новые тики, события о них для этой "занятой" программы не формируются и не добавляются в её очередь (см. раздел Обзор функций обработки событий). Более того, в терминал может одновременно прийти несколько тиков, но для каждой MQL-программы будет сгенерировано лишь одно событие — об актуальном состоянии рынка. В связи с этим весьма полезной является функция CopyTicks, которая позволяет запрашивать все тики, пришедшие с момента предыдущей обработки события. Вот как выглядит этот алгоритм в псевдокоде:

void processAllTicks()
{
   static ulong prev = 0;
   if(!prev)
   {
      MqlTick ticks[];
      const int n = CopyTicks(_SymbolticksCOPY_TICKS_ALLprev + 11000000);
      if(n > 0)
      {
         prev = ticks[n - 1].time_msc;
         ... // обработка всех пропущенных тиков
      }
   }
   else
   {
      MqlTick tick;
      SymbolInfoTick(_Symboltick);
      prev = tick.time_msc;
      ... // обработка первого тика
   }

Функция SymbolInfoTick, использованная здесь, заполняет передаваемую по ссылке одиночную структуру MqlTick информацией о последнем тике. Мы изучим её в отдельном разделе.

Обратите внимание, что при вызове CopyTicks к старой метке времени prev добавляется одна миллисекунда. Это гарантирует, что прежний тик не будет обработан повторно. Однако, если в течение одной миллисекунды, соответствующей prev, на самом деле было несколько тиков, данный алгоритм пропустит их. Если требуется охватить абсолютно все тики, нужно запоминать количество имеющихся тиков с временем prev одновременно с обновлением переменной prev. Затем при следующем вызове CopyTicks следует запрашивать тики с момента prev и пропускать (игнорировать в массиве) сохраненное количество "старых" тиков.

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

Для демонстрации функций рассмотрим два примера — по одному на каждую функцию. Для обоих примеров был разработан общий заголовочный файл TickEnum.mqh, в котором вышеприведенные константы для флагов запрашиваемых тиков и флагов состояния тиков сведены в два перечисления.

enum COPY_TICKS
{
   ALL_TICKS = /* -1 */ COPY_TICKS_ALL,    // all ticks
   INFO_TICKS = /* 1 */ COPY_TICKS_INFO,   // info ticks
   TRADE_TICKS = /* 2 */ COPY_TICKS_TRADE// trade ticks
};
 
enum TICK_FLAGS
{
   TF_BID = /* 2 */ TICK_FLAG_BID,
   TF_ASK = /* 4 */ TICK_FLAG_ASK,
   TF_BID_ASK = TICK_FLAG_BID | TICK_FLAG_ASK,
   
   TF_LAST = /* 8 */ TICK_FLAG_LAST,
   TF_BID_LAST = TICK_FLAG_BID | TICK_FLAG_LAST,
   TF_ASK_LAST = TICK_FLAG_ASK | TICK_FLAG_LAST,
   TF_BID_ASK_LAST = TF_BID_ASK | TICK_FLAG_LAST,
   
   TF_VOLUME = /* 16 */ TICK_FLAG_VOLUME,
   TF_LAST_VOLUME = TICK_FLAG_LAST | TICK_FLAG_VOLUME,
   TF_BID_VOLUME = TICK_FLAG_BID | TICK_FLAG_VOLUME,
   TF_BID_ASK_VOLUME = TF_BID_ASK | TICK_FLAG_VOLUME,
   TF_BID_ASK_LAST_VOLUME = TF_BID_ASK | TF_LAST_VOLUME,
   
   TF_BUY = /* 32 */ TICK_FLAG_BUY,
   TF_SELL = /* 64 */ TICK_FLAG_SELL,
   TF_BUY_SELL = TICK_FLAG_BUY | TICK_FLAG_SELL,
   TF_LAST_VOLUME_BUY = TF_LAST_VOLUME | TICK_FLAG_BUY,
   TF_LAST_VOLUME_SELL = TF_LAST_VOLUME | TICK_FLAG_SELL,
   TF_LAST_VOLUME_BUY_SELL = TF_BUY_SELL | TF_LAST_VOLUME,
   ...
};

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

Первый скрипт SeriesTicksStats.mq5 использует функцию CopyTicks, для того чтобы подсчитать количество тиков с различными взведенными флагами на заданную глубину истории.

Во входных параметрах можно задать рабочий символ (по умолчанию, символ графика), количество анализируемых тиков и режим запроса из COPY_TICKS.

input string WorkSymbol = NULL// Symbol (leave empty for current)
input int TickCount = 10000;
input COPY_TICKS TickType = ALL_TICKS;

Статистика встречаемости каждого флага (а точнее, каждого бита в битовой маске) в свойствах тиков собирается в структуре TickFlagStats.

struct TickFlagStats
{
   TICK_FLAGS flag// маска с взведенным битом (одним или несколькими)
   int count;       // количество тиков с таким битом в поле flags
   string legend;   // описание бита
};

В функции OnStart описан массив структур TickFlagStats размером в 8 элементов: 6 из них (с 1 по 6 включительно) используются для соответствующих TICK_FLAG-битов, а два остальных для комбинаций битов (см. далее). С помощью простого цикла в массиве заполняются элементы для отдельных стандартных битов/флагов, а после цикла — две комбинированных маски (в 0-м элементе будут подсчитываться тики с одновременным изменением Bid и Ask, а в 7-м элементе — тики с одновременными сделками Buy и Sell).

void OnStart()
{
   TickFlagStats stats[8] = {};
   for(int k = 1k < 7; ++k)
   {
      stats[k].flag = (TICK_FLAGS)(1 << k);
      stats[k].legend = EnumToString(stats[k].flag);
   }
   stats[0].flag = TF_BID_ASK;  // BID И ASK комбинация
   stats[7].flag = TF_BUY_SELL// BUY И SELL комбинация
   stats[0].legend = "TF_BID_ASK (COMBO)";
   stats[7].legend = "TF_BUY_SELL (COMBO)";
   ...

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

   const int count = CalcTickStats(TickType0TickCountstats);
   PrintFormat("%s stats requested: %d (got: %d) on %s",
      EnumToString(TickType),
      TickCountcountStringLen(WorkSymbol) > 0 ? WorkSymbol : _Symbol);
   ArrayPrint(stats);
}

Наибольший интерес, конечно, представляет сама функция CalcTickStats.

int CalcTickStats(const string symbolconst COPY_TICKS type,
   const datetime startconst int count,
   TickFlagStats &stats[])
{
   MqlTick ticks[];
   ResetLastError();
   const int nf = ArraySize(stats);
   const int nt = CopyTicks(symboltickstypestart * 1000count);
   if(nt > -1 && _LastError == 0)
   {
      PrintFormat("Ticks range: %s'%03d - %s'%03d",
         TimeToString(ticks[0].timeTIME_DATE TIME_SECONDS),
         ticks[0].time_msc % 1000,
         TimeToString(ticks[nt - 1].timeTIME_DATE TIME_SECONDS),
         ticks[nt - 1].time_msc % 1000);
      
      // цикл по тикам
      for(int j = 0j < nt; ++j)
      {
         // цикл по флагам TICK_FLAGs (2 4 8 16 32 64) и комбинациям
         for(int k = 0k < nf; ++k)
         {
            if((ticks[j].flags & stats[k].flag) == stats[k].flag)
            {
               stats[k].count++;
            }
         }
      }
   }
   return nt;
}

Её назначение запросить с помощью CopyTicks тики указанного символа (symbol), конкретного типа (type), начиная с даты start, в количестве count штук. Параметр start, будучи типа datetime, должен пересчитываться в миллисекунды при передаче в CopyTicks. Напомним, что если start = 0 (что имеет место в нашем случае, в функции OnStart), система вернет последние тики, то есть отсчет ведется от текущего времени. Поэтому при каждом вызове скрипта статистика, скорее всего, будет обновляться за счет прихода новых тиков. Исключение могут составить лишь запросы на выходных или малоликвидных инструментах.

Если CopyTicks выполнилась без ошибок, наш код выводит в журнал временной диапазон, покрытый полученными тиками.

Наконец, в цикле мы проходим по всем тикам и подсчитываем количество побитовых совпадений во флагах тиков и масках элементов в подготовленном заранее массиве статистических структур TickFlagStats.

Желательно запускать скрипт на инструментах, где имеется информация о реальных объемах и сделках, чтобы протестировать все режимы из перечисления COPY_TICKS (напомним, они соответствуют константам для параметра flags в CopyTicks: COPY_TICKS_INFO, COPY_TICKS_TRADE и COPY_TICKS_ALL).

Вот пример записей в журнале при запросе статистики для 100000 тиков всех типов (TickType = ALL_TICKS):

Ticks range: 2021.10.11 07:39:53'278 - 2021.10.13 11:51:29'428
ALL_TICKS stats requested: 100000 (got: 100000) on YNDX.MM
    [flag] [count]              [legend]
[0]      6   11323 "TF_BID_ASK (COMBO)" 
[1]      2   26700 "TF_BID"             
[2]      4   33541 "TF_ASK"             
[3]      8   51082 "TF_LAST"            
[4]     16   51082 "TF_VOLUME"          
[5]     32   25654 "TF_BUY"             
[6]     64   28802 "TF_SELL"            
[7]     96    3374 "TF_BUY_SELL (COMBO)"

А вот что получится, если запросить только информационные тики (TickType = INFO_TICKS).

Ticks range: 2021.10.07 07:08:24'692 - 2021.10.13 11:54:01'297
INFO_TICKS stats requested: 100000 (got: 100000) on YNDX.MM
    [flag] [count]              [legend]
[0]      6   23115 "TF_BID_ASK (COMBO)" 
[1]      2   60860 "TF_BID"             
[2]      4   62255 "TF_ASK"             
[3]      8       0 "TF_LAST"            
[4]     16       0 "TF_VOLUME"          
[5]     32       0 "TF_BUY"             
[6]     64       0 "TF_SELL"            
[7]     96       0 "TF_BUY_SELL (COMBO)"

Здесь можно проверить точность расчетов: сумма чисел для "TF_BID" и "TF_ASK" за вычетом совпадений "TF_BID_ASK (COMBO)" дает ровно 100000 (общее количество тиков). Тики с объемами и ценой Last в результат не попали ни разу, как и ожидалось.

Теперь запустим скрипт еще раз — исключительно для торговых тиков (TickType = TRADE_TICKS).

Ticks range: 2021.10.06 20:43:40'024 - 2021.10.13 11:52:40'044
TRADE_TICKS stats requested: 100000 (got: 100000) on YNDX.MM
    [flag] [count]              [legend]
[0]      6       0 "TF_BID_ASK (COMBO)" 
[1]      2       0 "TF_BID"             
[2]      4       0 "TF_ASK"             
[3]      8  100000 "TF_LAST"            
[4]     16  100000 "TF_VOLUME"          
[5]     32   51674 "TF_BUY"             
[6]     64   55634 "TF_SELL"            
[7]     96    7308 "TF_BUY_SELL (COMBO)"

Все тики имели флаги "TF_LAST" и "TF_VOLUME", а смешивание направлений сделок случилось 7308 раз. Опять же сумма показателей "TF_BUY" и "TF_SELL" за вычетом их комбинации совпадает с общим количеством тиков.

Второй скрипт SeriesTicksDeltaVolume.mq5 использует функцию CopyTicksRange для расчета дельт объемов на каждом баре. Как известно, котировки MetaTrader 5 содержат лишь обезличенные объемы, в которых покупки и продажи объединены в одной величине для каждого бара. Однако наличие истории реальных тиков позволяет посчитать отдельно суммы объемов покупок и продаж, а также их разницу. Данные характеристики являются дополнительными важными факторами для принятия торговых решений.

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

input string WorkSymbol = NULL// Symbol (leave empty for current)
input ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT;
input int BarCount = 100;
input COPY_TICKS TickType = INFO_TICKS;

Статистика по каждому бару хранится в структуре DeltaVolumePerBar.

struct DeltaVolumePerBar
{
   datetime time// время бара
   ulong buy;     // чистый объем покупок
   ulong sell;    // чистый объем продаж
   long delta;    // разница объемов
};

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

void OnStart()
{
   DeltaVolumePerBar deltas[];
   ArrayResize(deltasBarCount);
   ZeroMemory(deltas);
   ...

А вот и основной алгоритм.

   for(int i = 0i < BarCount; ++i)
   {
      MqlTick ticks[];
      const datetime next = iTime(WorkSymbolTimeFramei);
      const datetime prev = iTime(WorkSymbolTimeFramei + 1);
      ResetLastError();
      const int n = CopyTicksRange(WorkSymbolticksCOPY_TICKS_ALL,
         prev * 1000next * 1000 - 1);
      if(n > -1 && _LastError == 0)
      {
         ...
      }
   }

В цикле по барам получаем временные рамки для каждого бара: prev и next (0-й неполный бар не обрабатывается). Вызываем CopyTicksRange для этого интервала, не забывая перевести datetime в миллисекунды и вычесть 1 миллисекунду из правой границы, так как это время принадлежит уже следующему бару. В отсутствие ошибок обрабатываем в цикле массив полученных тиков.

         deltas[i].time = prev// запомним время бара
         for(int j = 0j < n; ++j)
         {
            // когда реальные объемы могут быть доступны, берем их из тиков
            if(TickType == TRADE_TICKS)
            {
               // раздельно аккумулируем объемы по сделкам покупки и продажи
               if((ticks[j].flags & TICK_FLAG_BUY) != 0)
               {
                  deltas[i].buy += ticks[j].volume;
               }
               if((ticks[j].flags & TICK_FLAG_SELL) != 0)
               {
                  deltas[i].sell += ticks[j].volume;
               }
            }
            // когда реальных объемов нет, оцениваем их по движению цен вверх/вниз
            else
            if(TickType == INFO_TICKS && j > 0)
            {
               if((ticks[j].flags & (TICK_FLAG_ASK | TICK_FLAG_BID)) != 0)
               {
                  const long d = (long)(((ticks[j].ask + ticks[j].bid)
                               - (ticks[j - 1].ask + ticks[j - 1].bid)) / _Point);
                  if(d > 0deltas[i].buy += d;
                  else deltas[i].sell += -d;
               }
            }
         }
         deltas[i].delta = (long)(deltas[i].buy - deltas[i].sell);

Если в настройках скрипта запрашивался анализ по торговым тикам (TRADE_TICKS), проверяем наличие флагов TICK_FLAG_BUY и TICK_FLAG_SELL, и если хотя бы один из них взведен, учитываем объем из поля volume в соответствующей переменной структуры DeltaVolumePerBar. Данный режим подходит только для биржевых инструментов. Для инструментов Forex объемы и флаги направлений сделок не заполняются, а потому следует изобрести другой подход.

Если в настройках указаны информационные тики (INFO_TICKS), которые есть для всех инструментов, алгоритм строится на следующих эмпирических правилах. Как известно, покупки толкают цену вверх, а продажи — вниз. Поэтому можно предположить, что если средняя цена Ask+Bid двинулась в новом тике вверх относительно предыдущего, то на нем произошла покупка, а если цена двинулась вниз — продажа. Объем можно примерно оценить как количество пройденных пунктов (_Point).

Результаты подсчетов выводятся просто как массив структур с собранной статистикой.

   PrintFormat("Delta volumes per intraday bar\nProcessed %d bars on %s %s %s",
      BarCountStringLen(WorkSymbol) > 0 ? WorkSymbol : _Symbol,
      EnumToString(TimeFrame == PERIOD_CURRENT ? _Period : TimeFrame),
      EnumToString(TickType));
   ArrayPrint(deltas);
}

Ниже показана пара фрагментов журналов для режимов TRADE_TICKS и INFO_TICKS.

Delta volumes per intraday bar
Processed 100 bars on YNDX.MM PERIOD_H1 TRADE_TICKS
                  [time] [buy] [sell] [delta]
[ 0] 2021.10.13 11:00:00  7912  14169   -6257
[ 1] 2021.10.13 10:00:00  8470  11467   -2997
[ 2] 2021.10.13 09:00:00 10830  13047   -2217
[ 3] 2021.10.13 08:00:00 23682  19478    4204
[ 4] 2021.10.13 07:00:00 14538  11600    2938
[ 5] 2021.10.12 20:00:00  2132   4786   -2654
[ 6] 2021.10.12 19:00:00  9173  13775   -4602
[ 7] 2021.10.12 18:00:00  1297   1719    -422
[ 8] 2021.10.12 17:00:00  3803   2995     808
[ 9] 2021.10.12 16:00:00  6743   7045    -302
[10] 2021.10.12 15:00:00 17286  37286  -20000
[11] 2021.10.12 14:00:00 33263  54157  -20894
[12] 2021.10.12 13:00:00 56060  52659    3401
[13] 2021.10.12 12:00:00 12832  10489    2343
[14] 2021.10.12 11:00:00  7530   6092    1438
[15] 2021.10.12 10:00:00  6268  25201  -18933
...

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

Delta volumes per intraday bar
Processed 100 bars on YNDX.MM PERIOD_H1 INFO_TICKS
                  [time]  [buy] [sell] [delta]
[ 0] 2021.10.13 11:00:00   1939   2548    -609
[ 1] 2021.10.13 10:00:00   2222   2400    -178
[ 2] 2021.10.13 09:00:00   2903   2909      -6
[ 3] 2021.10.13 08:00:00   4489   4060     429
[ 4] 2021.10.13 07:00:00   4999   4285     714
[ 5] 2021.10.12 20:00:00   1444   1556    -112
[ 6] 2021.10.12 19:00:00   5464   5867    -403
[ 7] 2021.10.12 18:00:00   2522   2653    -131
[ 8] 2021.10.12 17:00:00   2111   2017      94
[ 9] 2021.10.12 16:00:00   4617   6096   -1479
[10] 2021.10.12 15:00:00   5716   5411     305
[11] 2021.10.12 14:00:00  10044  10866    -822
[12] 2021.10.12 13:00:00  10893  11178    -285
[13] 2021.10.12 12:00:00   2822   2783      39
[14] 2021.10.12 11:00:00   2070   1936     134
[15] 2021.10.12 10:00:00   2053   2303    -250
...

Когда мы научимся создавать индикаторы, то сможем встроить этот алгоритм в один из них (см. пример IndDeltaVolume.mq5 в разделе Ожидание данных и управление видимостью), чтобы наглядно отобразить дельты непосредственно на графике.