Генерация тиков в тестере

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

  • OnTick — обработчик события прихода нового тика;
  • OnTrade — обработчик торгового события;
  • OnTradeTransaction — обработчик торговой транзакции;
  • OnTimer — обработчик сигнала таймера;
  • OnChartEvent — обработчик событий на графике, в том числе пользовательских.

Вместе с тем, внутри тестера основным эквивалентом хода времени является поток тиков, которые содержат не только изменение цены, но и время с точностью до миллисекунд. Поэтому для тестирования экспертов необходимо генерировать тиковые последовательности. В тестере MetaTrader 5 реализовано 4 режима генерации тиков:

  • Реальные тики (если их историю предоставляет брокер);
  • Все тики (эмуляция на основе доступных котировок таймфрейма M1);
  • Цены OHLC с минутных баров (1 Minute OHLC);
  • Только цены открытия (1 тик на бар);

Еще один режим работы — математические вычисления — мы разберем позднее, так как он не связан с котировками и тиками.

Какой бы из 4-х режимов пользователь ни выбрал, терминал осуществляет загрузку доступных исторических данных для тестирования. Если был выбран режим реальных тиков, а их у брокера по данному инструменту нет, то используется режим "Все тики". Качество генерации тиков тестер указывает в своем отчете графически и в процентах (100% — все тики реальные).

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

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

Обращение к дополнительным инструментам происходит и в том случае, когда вычисляется цена кросс-курса при торговых операциях. Например, при тестировании стратегии на EURCHF с валютой депозита в долларах США перед обработкой первой же торговой операции агент тестирования запросит у клиентского терминала историю по EURUSD и USDCHF, хотя в стратегии нет прямого обращения к этим инструментам.

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

Теперь рассмотрим режимы генерации тиков более подробно.

Реальные тики из истории

Тестирование и оптимизация на реальных тиках являются максимально приближенными к реальным условиям. Это — тики с бирж и от поставщиков ликвидности.

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

Тики хранятся в кэше символа в тестере стратегий. Размер кэша — не более 128 000 тиков. При поступлении новых тиков самые старые данные из него выталкиваются. Однако при помощи функции CopyTicks можно получить тики и за пределами кэша (только при тестировании по реальным тикам). В этом случае данные будут запрошены из базы тиков тестера, которая полностью соответствует аналогичной базе клиентского терминала. В эту базу никакие корректировки по минутным барам не вносятся. Поэтому тики в ней могут отличаться от тиков, находящихся в кэше.

Все тики (эмуляция)

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

История котировок по финансовым инструментам передается от торгового сервера в клиентский терминал MetaTrader 5 в виде экономно упакованных блоков минутных баров. Как происходит запрос и построение требуемых таймфреймов мы рассматривали более подробно в разделе Технические особенности организации и хранения таймсерий.

Минимальным элементом ценовой истории является минутный бар, из которого можно получить информацию о четырех значениях цены OHLC: Open, High, Low, Close.

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

Таким образом, для каждого минутного бара нам известны 4 контрольные точки, о которых можно точно сказать, что цена там побывала. Если бар имеет только 4 тика, то для тестирования этой информации достаточно, но обычно тиковый объем больше 4. Значит, необходимо сгенерировать дополнительные контрольные точки для тиков, которые приходили между ценами Open, High, Low и Close. Принцип генерации тиков в режиме "Все тики" описан в документации.

При тестировании в режиме "Все тики" функция OnTick эксперта будет вызываться на каждом сгенерированном тике. Эксперт будет получать время и цены Ask/Bid/Last так же, как и при работе онлайн.

Режим тестирования "Все тики" является самым точным (после режима на основе реальных тиков), но при этом и самым затратным по времени. Для первичной оценки большинства торговых стратегий обычно достаточно использовать один из двух упрощенных режимов тестирования: по ценам OHLC M1 или по открытию баров выбранного таймфрейма.

1 minute OHLC

В режиме "1 minute OHLC" тиковая последовательность строится только по OHLC ценам минутных баров, количество вызовов функции OnTick существенно уменьшается — следовательно, уменьшается и время тестирования. Это очень эффективный, полезный режим, предлагающий компромисс между точностью тестирования и скоростью. Однако с ним нужно быть осторожным, когда речь идет о чужом эксперте.

Отказ от генерации дополнительных промежуточных тиков между ценами Open, High, Low и Close приводит к появлению жесткой детерминированности в развитии цены с того момента, как определена цена Open. Это дает возможность для создания "Грааля тестирования", который показывает красивый восходящий график баланса при тестировании.

Для минутного бара известны 4 цены, из которых первой идет Open, а последней Close. Между ними случились цены High и Low, причем информация о порядке их наступления утрачена, однако мы знаем, что цена High больше или равна цене Open, а цена Low меньше или равна цене Open.
 
Достаточно после поступления цены Open анализировать следующий тик, чтобы определить, что перед нами — High или Low. Если цена ниже цены Open, значит это цена Low — покупаем на этом тике, так как следующий тик будет соответствовать цене High, на котором закрываем покупку и открываем продажу. Следующий тик последний на баре, это цена Close, на нем закрываем продажу.
 
Если после цены пришел тик с ценой больше цены открытия, то последовательность сделок обратная. В таком мошенническом режиме можно торговать на каждом баре. При тестировании такого эксперта на истории все идет идеально, но в онлайне его ждет полный крах.

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

Таким образом, всегда важно после нахождения оптимальных настроек эксперта на грубых режимах тестирования ("1 minute OHLC" и "Только цены открытия") протестировать его в режиме "Все тики", а еще лучше — на реальных тиках.

Только цены открытия

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

Например, производится тестирование советника на EURUSD H1 в режиме "Только цены открытия". В этом случае общее количество тиков (контрольных точек) будет в 4 раза больше количества часовых баров, попавших в тестируемый интервал. Но при этом вызов обработчика OnTick произойдет только на открытии часовых баров. На остальных ("скрытых" от эксперта) тиках происходят проверки, необходимые для корректного тестирования:

  • вычисление маржевых требований;
  • срабатывание Stop Loss и Take Profit;
  • срабатывание отложенных ордеров;
  • удаление отложенных ордеров с истекшим временем.

Если нет открытых позиций или отложенных ордеров, то необходимости в данных проверках на скрытых тиках нет, и прирост скорости может оказаться существенным.

Исключением при генерации тиков в режиме "Только цены открытия" являются периоды W1 и MN1: для этих таймфреймов тики генерируются для цен OHLC каждого дня, а не недели или месяца соответственно.

Данный режим "Только цены открытия" хорошо подходит для тестирования стратегий, которые совершают сделки только на открытии бара и не используют отложенные ордера, а также не используют уровни Stop Loss и Take Profit. Для класса таких стратегий сохраняется вся необходимая точность тестирования.

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

Объявим свое перечисление с обозначениями существующих режимов.

enum TICK_MODEL
{
   TICK_MODEL_UNKNOWN = -1,    /*Unknown (any)*/    // неизвестный/еще не определен
   TICK_MODEL_REAL = 0,        /*Real ticks*/       // лучшее качество
   TICK_MODEL_GENERATED = 1,   /*Generated ticks*/  // хорошее качество
   TICK_MODEL_OHLC_M1 = 2,     /*OHLC M1*/          // приемлемое качество и быстро
   TICK_MODEL_OPEN_PRICES = 3, /*Open prices*/      // худшее качество, но очень быстро
   TICK_MODEL_MATH_CALC = 4,   /*Math calculations*/// без тиков (не определяется)
};

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

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

TICK_MODEL getTickModel()
{
   static TICK_MODEL model = TICK_MODEL_UNKNOWN;
   ...

На первом анализируемом тике модель равна TICK_MODEL_UNKNOWN, и производится попытка получить реальные тики с помощью вызова CopyTicks.

   if(model == TICK_MODEL_UNKNOWN)
   {
      MqlTick ticks[];
      const int n = CopyTicks(_SymbolticksCOPY_TICKS_ALL010);
      if(n == -1)
      {
         switch(_LastError)
         {
         case ERR_NOT_ENOUGH_MEMORY:    // эмуляция тиков
            model = TICK_MODEL_GENERATED;
            break;
            
         case ERR_FUNCTION_NOT_ALLOWED// цены открытия или OHLC
            if(TimeCurrent() != iTime(_Symbol_Period0))
            {
               model = TICK_MODEL_OHLC_M1;
            }
            else if(model == TICK_MODEL_UNKNOWN)
            {
               model = TICK_MODEL_OPEN_PRICES;
            }
            break;
         }
         
         Print(E2S(_LastError));
      }
      else
      {
         model = TICK_MODEL_REAL;
      }
   }
   ...

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

Различить один от другого можно по времени тика. Если оно у тика окажется некратным таймфрейму, значит речь о режиме OHLC. Однако проблема здесь в том, первый тик в обоих режимах может быть выровнен по времени открытия бара. Таким образом, мы получим значение TICK_MODEL_OPEN_PRICES, но оно требует уточнения. Поэтому для окончательного заключения следует проанализировать еще один тик (вызвать на нем функцию еще раз в случае получения перед этим TICK_MODEL_OPEN_PRICES). Внутри функции для этого случая предусмотрена следующая ветвь оператора if.

   else if(model == TICK_MODEL_OPEN_PRICES)
   {
      if(TimeCurrent() != iTime(_Symbol_Period0))
      {
         model = TICK_MODEL_OHLC_M1;
      }
   }
   return model;
}

Работу детектора проверим в простом эксперте TickModel.mq5. Во входном параметре TickCount укажем максимальное количество анализируемых тиков, то есть сколько раз вызвать функцию getTickModel. Мы знаем, что достаточно двух, но для того, чтобы убедиться, что модель впоследствии не меняется, по умолчанию предложено 5 тиков. Также предусмотрим параметр RequireTickModel, который предписывает эксперту завершить работу, если уровень моделирования окажется ниже запрошенного. По умолчанию его значение TICK_MODEL_UNKNOWN, что означает отсутствие ограничения по режиму.

input int TickCount = 5;
input TICK_MODEL RequireTickModel = TICK_MODEL_UNKNOWN;

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

void OnTick()
{
   if(MQLInfoInteger(MQL_TESTER))
   {
      static int count = 0;
      if(count++ < TickCount)
      {
         // выводим информацию о тике для справки
         static MqlTick tick[1];
         SymbolInfoTick(_Symboltick[0]);
         ArrayPrint(tick);
         // определяем и выводим модель (предварительно)
         const TICK_MODEL model = getTickModel();
         PrintFormat("%d %s"countEnumToString(model));
         // если счетчик тиков 2+, заключение окончательное и на его основе действуем
         if(count >= 2)
         {
            if(RequireTickModel != TICK_MODEL_UNKNOWN
            && RequireTickModel < model// качество ниже запрошенного
            {
               PrintFormat("Tick model is incorrect (%s %sis required), terminating",
                  EnumToString(RequireTickModel),
                  (RequireTickModel != TICK_MODEL_REAL ? "or better " : ""));
               ExpertRemove(); // завершаем работу
            }
         }
      }
   }
}

Попробуем запустить эксперт в тестере при различных режимах генерации тиков, выбрав расхожее сочетание EURUSD H1.

Установим в эксперте параметр RequireTickModel в OHLC M1. Если режим тестера "Все тики", получим в журнале соответствующее сообщение, и эксперт продолжит работу.

                 [time]   [bid]   [ask]  [last] [volume]    [time_msc] [flags] [volume_real]

[0] 2022.04.01 00:00:30 1.10656 1.10679 1.10656        0 1648771230000      14       0.00000

NOT_ENOUGH_MEMORY

1 TICK_MODEL_GENERATED

                 [time]   [bid]   [ask]  [last] [volume]    [time_msc] [flags] [volume_real]

[0] 2022.04.01 00:01:00 1.10656 1.10680 1.10656        0 1648771260000      12       0.00000

2 TICK_MODEL_GENERATED

                 [time]   [bid]   [ask]  [last] [volume]    [time_msc] [flags] [volume_real]

[0] 2022.04.01 00:01:30 1.10608 1.10632 1.10608        0 1648771290000      14       0.00000

3 TICK_MODEL_GENERATED

Аналогично подойдут режимы непосредственно OHLC M1 и реальные тики, причем в последнем случае не будет никакого кода ошибки.

                 [time]   [bid]   [ask] [last] [volume]    [time_msc] [flags] [volume_real]

[0] 2022.04.01 00:00:00 1.10656 1.10687 0.0000        0 1648771200122     134       0.00000

1 TICK_MODEL_REAL

                 [time]   [bid]   [ask] [last] [volume]    [time_msc] [flags] [volume_real]

[0] 2022.04.01 00:00:00 1.10656 1.10694 0.0000        0 1648771200417       4       0.00000

2 TICK_MODEL_REAL

                 [time]   [bid]   [ask] [last] [volume]    [time_msc] [flags] [volume_real]

[0] 2022.04.01 00:00:00 1.10656 1.10691 0.0000        0 1648771200816       4       0.00000

3 TICK_MODEL_REAL

Однако если в тестере поменять режим на "Только цены открытия", эксперт остановится после второго тика.

                 [time]   [bid]   [ask]  [last] [volume]    [time_msc] [flags] [volume_real]

[0] 2022.04.01 00:00:00 1.10656 1.10679 1.10656        0 1648771200000      14       0.00000

FUNCTION_NOT_ALLOWED

1 TICK_MODEL_OPEN_PRICES

                 [time]   [bid]   [ask]  [last] [volume]    [time_msc] [flags] [volume_real]

[0] 2022.04.01 01:00:00 1.10660 1.10679 1.10660        0 1648774800000      14       0.00000

2 TICK_MODEL_OPEN_PRICES

Tick model is incorrect (TICK_MODEL_OHLC_M1 or better is required), terminating

ExpertRemove() function called

К сожалению, данный способ требует запуска теста и пары тиков для определения режима. Иными словами, мы не можем остановить тест раньше, вернув ошибку из OnInit. Более того, при запуске оптимизации с неправильным типом генерации тиков мы не сможем остановить оптимизацию, что можно делать только из функции OnTesterInit. Таким образом, тестер будет пытаться выполнить все проходы по время оптимизации, хотя они будут пресекаться в самом начале. Это текущее ограничение платформы.