Скачать MetaTrader 5

Торговые события в MetaTrader 5

24 января 2011, 12:03
MetaQuotes Software Corp.
24
3 574

Введение

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

Принятые торговым сервером запросы хранятся в виде ордеров, которые могут быть отложенными или немедленного исполнения по рыночным ценам. Ордера хранятся на сервере до того момента, как они будут либо исполнены, либо отмены. Результатом исполнения ордера является сделка.

Сделка приводит к изменению торговой позиции до данному символу - открытие, закрытие, наращивание, сокращение или переворот позиции. Поэтому, открытая позиция - это всегда результат проведения одной или нескольких сделок. Более подробную информацию можно получить из статьи Ордерa, позиции и сделки в MetaTrader 5.

В этой статье описываются понятия, термины и процессы, которые происходят с момента отсылки запроса и до момента попадания ордера в историю после его обработки.


Прохождение заявки от терминала до торгового сервера

Для проведения торговой операции необходимо  подать заявку в торговую систему. Заявка всегда подается посредством отправки торговому серверу запроса из клиентского терминала. Структура запроса должна быть заполнена правильно и  не зависит от того как осуществляется торговля - вручную или с помощью программы на MQL5.

В случае ручной торговли для совершения торговой операции можно нажатием F9 вызвать диалоговое окно заполнения торгового запроса. При автоматической торговли с помощью MQL5 запросы отправляются с помощью функции OrderSend(). Так как неправильные массовые запросы могут вызвать нежелательную загрузку торгового сервера, то каждый запрос перед его отправкой необходимо проверять на корректность с помощью функции OrderCheck(). Результат проверки запроса помещается в переменную, описываемую структурой MqlTradeCheckresult.

Важно: каждый запрос перед его отправкой торговому серверу предварительно проверяется на корректность в самом клиентском терминале. Заведомо неверные запросы (купить миллион лотов или купить по отрицательной цене) за пределы терминала не проходят. Это сделано для защиты торговых серверов от массовых неправильных запросов в случае ошибки в mql5-программе.

После отправки запроса он поступает на торговый сервер и проходит первичную проверку:

  • достаточность средств для выполнения операций;
  • правильность указанных цен: цены открытия, Stop Loss, Take Profit и т.д.;
  • наличие указанной цены в потоке цен для немедленного исполнения (Instant Execution);
  • отсутствие уровней Stop Loss и Take Profit для исполнения в режиме Market Execution;
  • проверка объема сделки: минимальный и максимальный объем, шаг объема, максимальный объем позиции (SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_MAX, SYMBOL_VOLUME_STEP и SYMBOL_VOLUME_LIMIT);
  • состояние  торгового инструмента: котировальная или торговая сессия, запрещение торговли по данному инструменту, специфичный режим торговли (только закрытие позиций) и т.д.;
  • состояние торгового счета: различные ограничения для определенных типов счетов;
  • другие проверки, в зависимости от запрашиваемой торговой операции.
Некорректный запрос не проходит первичную проверку на стороне сервера и отклоняется. Результат проверки запроса всегда сообщается клиентскому терминалу в виде отсылаемого ответа. Ответ торгового сервера можно получить из переменной типа MqlTradeResult, которая передается вторым параметром в функцию OrderSend() при отправке запроса.

Отправка запросов торговому серверу из клиентского терминала

Если запрос прошел первичную проверку на корректность, то он ставится в очередь запросов на обработку. В результате обработки запроса в базе торгового сервера создается ордер (приказ на совершение торговой операции). Но есть два вида запросов, которые не приводят к появлению ордера:

  1. запрос на изменение позиции (изменение Stop Loss и/или Take Profit);
  2. запрос на модификацию отложенного ордера (уровни цен и время истечения).

Терминал получает сообщение о том, что запрос принят и размещен в торговой подсистеме платформы MetaTrader 5. Принятый запрос сервер помещает в очередь запросов для дальнейшей обработки, результатом обработки может быть:

  • отложенный ордер;
  • ордер немедленного исполнения по рынку;
  • модификация ордера или позиции.

Время жизни запроса на сервере в очереди на обработку ограничено и не превышает трех минут. По истечении этого срока запрос удаляется из очереди запросов.


Отсылка торговых событий торговым сервером в терминал

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

Торговые события генерируются сервером в следующих случаях:

  • изменение в действующих ордерах,
  • изменения в позициях,
  • изменения в сделках,
  • изменения в торговой истории.

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

  1. появление сделки, которая заносится в торговую историю;
  2. перемещение отложенного ордера из разряда действующих в разряд исторических (ордер переносится в историю).

Другой пример множественных торговых событий - совершение на основании одного ордера нескольких сделок, если требуемый для сделки объем не удалось получить из одной встречной заявки. О  каждом торговом событии сервер создает и отправляет сообщение в клиентский терминал. Поэтому функция OnTrade() может быть вызвана несколько раз подряд для казалось бы одного события. Это простой пример процедуры обработки ордера в торговой подсистеме платформы MetaTrader 5.

Пример: в ожидании исполнения имеется отложенный ордер на покупку 10 лотов по EURUSD и в этот момент поступают встречные запросы на продажу - объемами 1, 4 и 5 лотов. Эти  три запроса в сумме дают объем в 10 лотов  и последовательно исполняются, если политика исполнения позволяет совершить покупку по частям.

В результате исполнения 4 запросов сервер проведет на основании имеющихся встречных заявок 3 сделки - объемами 1, 4 и 5 лотов соответственно. Сколько торговых событий будет сгенерировано в этом случае? Первая встречная заявка на продажу одного лота приведет к совершению сделки в 1 лот. Это первое торговое событие Trade (сделка объемом 1 лот). Но отложенный ордер на покупку 10 лотов также изменится - теперь он является ордером на покупку 9 лотов по EURUSD. Изменение объема в отложенном ордере - это второе торговое событие Trade (изменение объема отложенного ордера).

Генерация торговых событий

Для второй сделки на 4 лота будет сгенерировано еще два события Trade, сообщение о каждом из них будет отправлено терминалу, который инициировал первоначальный отложенный ордер на покупку EURUSD объемом 10 лотов.

Последняя сделка на 5 лотом приведет к отправке сообщений о трех торговых событиях:

  1. сделка на 5 лотов,
  2. изменение объема,
  3. перемещение ордера в историю.

В результате проведения сделок в клиентский терминал поступит одно за другим 7 торговых события Trade (предполагается, что связь между терминалом и торговым сервером надежная и все сообщения дойдут без потерь). Эти сообщения должны быть обработаны в советнике функцией OnTrade().

Важно: каждое сообщение о торговом событии Trade может быть результатом одного или нескольких запросов. Каждый запрос может порождать несколько торговых событий. Нельзя полагаться на правило "Один запрос - Одно событие Trade", так как обработка запросов может происходить в несколько этапов и каждая операция может изменять состояние ордеров, позиций и торговой истории.


Обработка ордеров торговым сервером

Все ожидающие исполнения ордера в конечном счете попадут в историю - либо наступят условия для их исполнения либо ордер будет снят. Имеется несколько возможных вариантов прекращения действия ордера:

  • совершение сделки на основании ордера;
  • отклонение ордера дилером;
  • отмена ордера по запросу трейдера (ручной запрос или запрос из программы MQL5);
  • истечение времени жизни ордера, которое определяется либо трейдером при отправке запроса, либо условиями торговли в данной торговой системе;
  • нехватка средств на торговом счете для проведения сделки к тому моменту, когда наступят условия ее исполнения;
  • ордер снят в соответствии с политикой исполнения (снят не полностью выполненный ордер).

Независимо от того, по какой причине действующий ордер переходит в разряд исторических, сообщение о произошедшем изменении будет отправлено клиентскому терминалу. Сообщения о торговом событии отправляются не во все терминалы, которые подключены к торговому серверу, а только в те из них, которые подключены к соответствующему счету.


Общая схема обработки ордера торговым сервером

Важно: принятие торговым сервером запроса к исполнению не означает, что запрашиваемая торговая операция будет обязательно исполнена.  Это означает только то, что запрос прошел проверку на корректность в момент его поступления в торговый сервер.

Именно поэтому в документации по функции OrderSend() сказано:

Возвращаемое значение

В случае успешной базовой проверки запроса функция OrderSend() возвращает  true - это не свидетельствует об успешном выполнении торговой операции. Для получения более подробного описания результата выполнения функции следует анализировать поля структуры MqlTradeResult.


Как обновляется торговля и история в терминале

Сообщения о торговых событиях и об изменениях в торговой истории поступают по независимым каналам. При отправке запроса на покупку функцией OrderSend() можно сразу же узнать тикет ордера, который был создан при успешном результате проверки запроса. Но в то же время сам ордер еще может не появиться в клиентском терминале и попытка выбрать его с помощью функции OrderSelect() окажется неуспешной. 


Все сообщения из торгового сервера поступают в терминал независимо друг от друга


На рисунке показана ситуация,  когда торговый сервер сообщает mql5-программе тикет ордера, но само сообщение о торговом событии Trade (появление нового ордера) еще не поступило. Также еще не поступило сообщение об изменении в списке действующих ордеров.

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


Обработка торговых событий в MQL5

Все операции на торговом сервере и отсылка сообщений о торговых событиях производятся асинхронно. Есть только один гарантированный способ выяснить, что именно изменилось на торговом счета. Этот способ -  запоминать состояние торговли и торговой истории и сравнивать новое состояние с сохраненным.

Алгоритм отслеживания торговых событий в эксперте таков:

  1. объявляем на глобальном уровне счетчики ордеров, позиций и сделок;
  2. определяемся с глубиной торговой истории, которую будем запрашивать в кэш mql5-программы. Чем больший объем истории загружаем в кэш, тем больше загружаются ресурсы терминала и компьютера;
  3. в функции OnInit инициализируем счетчики ордеров, позиций и сделок;
  4. определяем в каких функциях-обработчиках событий мы будем запрашивать торговую историю в кэш;
  5. Там же после загрузки торговой истории путем сравнения запомненного состояния и текущего состояния выясняем что именно произошло с торговым счетом.

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

В эксперте изменения счетчиков можно проверять в функциях OnTrade() и в OnTick()

Напишем по шагам пример программы.

1. Счетчики ордеров, позиций и сделок на глобальном уровне.

int          orders;            // количество действующих ордеров
int          positions;         // количество открытых позиций
int          deals;             // количество сделок в кэше торговой истории
int          history_orders;    // количество ордеров в кэше торговой истории
bool         started=false;     // признак инициализированности счетчиков


2. Глубину торговой истории для загрузки в кэш зададим input-переменной days (загружаем торговую историю за указанное этой переменной количество дней).

input    int days=7;            // глубина торговой истории в днях

//--- зададим на глобальном уровне границы торговой истории
datetime     start;             // дата начала торговой истории в кэше
datetime     end;               // дата конца торговой истории в кэше 


3. Инициализация счетчиков и границ торговой истории. Вынесем инициализацию счетчиков в отдельную функцию InitCounters() для лучшей читаемости кода:

int OnInit()
  {
//---
   end=TimeCurrent();
   start=end-days*PeriodSeconds(PERIOD_D1);
   PrintFormat("Границы загружаемой торговой истории: начало - %s, конец - %s",
               TimeToString(start),TimeToString(end));
   InitCounters();
//---
   return(0);
  }

Функция InitCounters() пытается загрузить в кэш торговую историю, и в случае успеха инициализирует все счетчики. Также при успешной загрузке истории значение глобальной переменной started устанавливается равным true, это означает, что счетчики инициализированы успешно.

//+------------------------------------------------------------------+
//|  инициализация счетчиков позиций, ордеров и сделок               |
//+------------------------------------------------------------------+
void InitCounters()
  {
   ResetLastError();
//--- загрузим историю 
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Не удалось загрузить в кэш историю с %s по %s. Код ошибки: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }
//--- получим текущие значениия
   orders=OrdersTotal();
   positions=PositionsTotal();
   deals=HistoryDealsTotal();
   history_orders=HistoryOrdersTotal();
   started=true;
   Print("Счетчики ордеров,  позиций и сделок успешно инициализированы");
  }


4. В обработчиках OnTick() и OnTrade() производится проверка изменений на торговом счете. Сначала проверяется переменная started -  если ее значение равно true, вызывается функция SimpleTradeProcessor(), в противном случае вызывается функция инициализации счетчиков InitCounters().

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }
//+------------------------------------------------------------------+
//| вызывается при поступлении события Trade                         |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }

5. Функция SimpleTradeProcessor() проверяет изменение количество ордеров, сделок и позиций. После всех проверок вызывается функция CheckStartDateInTradeHistory(), которая при необходимости перемещает ближе к текущему моменту значение start.

//+------------------------------------------------------------------+
//| простейший пример обработки изменений в торговле и истории       |
//+------------------------------------------------------------------+
void SimpleTradeProcessor()
  {
   end=TimeCurrent();
   ResetLastError();
//--- загрузим историю 
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Не удалось загрузить в кэш историю с %s по %s. Код ошибки: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }

//--- получим текущие значение
   int curr_orders=OrdersTotal();
   int curr_positions=PositionsTotal();
   int curr_deals=HistoryDealsTotal();
   int curr_history_orders=HistoryOrdersTotal();

//--- проверим изменения в количестве действующих ордеров
   if(curr_orders!=orders)
     {
      //--- количество действующих ордеров изменилось
      PrintFormat("Изменилось количество ордеров. Было %d, стало %d",
                  orders,curr_orders);
     /*
       еще какие-то действия в связи с изменением ордеров
     */
      //--- обновим значение
      orders=curr_orders;
     }

//--- изменения в количестве открытых позиций
   if(curr_positions!=positions)
     {
      //--- количество открытых позиций изменилось
      PrintFormat("Изменилось количество позиций. Было %d, стало %d",
                  positions,curr_positions);
      /*
       еще какие-то действия в связи с изменением в позициях
      */
      //--- обновим значение
      positions=curr_positions;
     }

//--- изменения в количестве сделок в кэше торговой истории
   if(curr_deals!=deals)
     {
      //--- количество сделок в кэше торговой истории изменилось
      PrintFormat("Изменилось количество сделок. Было %d, стало %d",
                  deals,curr_deals);
      /*
       еще какие-то действия в связи с изменением количества сделок
      */
      //--- обновим значение
      deals=curr_deals;
     }

//--- изменения в количестве исторических ордеров в кэше торговой истории
   if(curr_history_orders!=history_orders)
     {
      //--- количество исторических ордеров в кэше торговой истории изменилось
      PrintFormat("Изменилось количество ордеров в истории. Было %d, стало %d",
                  history_orders,curr_history_orders);
     /*
       еще какие-то действия в связи с изменением количества ордеров в кэше торговой истории
      */
     //--- обновим значение
     history_orders=curr_history_orders;
     }
//--- проверка на необходимость изменения границ торговой истории для запроса в кэш
   CheckStartDateInTradeHistory();
  }

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

//+------------------------------------------------------------------+
//|  изменения начальной даты для запроса торговой истории           |
//+------------------------------------------------------------------+
void CheckStartDateInTradeHistory()
  {
//--- начальный интервал, если бы мы начали работу прямо сейчас
   datetime curr_start=TimeCurrent()-days*PeriodSeconds(PERIOD_D1);
//--- убедимся, что граница начала торговой истории ушла не больше, 
//--- чем на 1 день от задуманной даты
   if(curr_start-start>PeriodSeconds(PERIOD_D1))
     {
      //--- придется подкорректировать дату начала загружаемой в кэш истории 
      start=curr_start;
      PrintFormat("Новая граница начала загружаемой торговой истории: начало => %s",
                  TimeToString(start));

      //--- теперь заново загрузим торговоую историю для поправленного интервала
      HistorySelect(start,end);

      //--- подкорректируем счетчики сделок и ордеров в истории для следующего сравнения
      history_orders=HistoryOrdersTotal();
      deals=HistoryDealsTotal();
     }
  }

Полный код эксперта DemoTradeEventProcessing.mq5 приложен к статье.


Заключение

Все операции в торгово-аналитической платформе MetaTrader 5 производятся асинхронно и отсылка сообщений обо всех изменениях на торговом счете производятся независимо друг от друга. Поэтому не нужно пытаться отслеживать одиночное событие по правилу "Один запрос - Одно торговое событие". Если требуется точно определить что именно изменилось по приходу события Trade, то нужно на каждом вызове обработчика OnTrade анализировать все сделки, позиции и ордера и сравнивать с тем состоянием, что было до его появления.

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (24)
Vladimir Batrudinov
Vladimir Batrudinov | 25 янв 2011 в 16:05
Yedelkin:

Фактически, интересует такой вопрос: о прохождении ордером какой именно стадии (от терминала до сервера) сигнализирует  true у функции OrderSend()? "Базовая проверка" или же "Базовая проверка + принятие (размещение) ордера"? Или же нельзя исключительно по поступлению true сделать нужный вывод?
На мой взгляд выводы только по true делать не стоит, нужно еще код ответа сервера анализировать (причем последний приоритетней, как мне кажется)...
Yedelkin
Yedelkin | 25 янв 2011 в 19:16

Renat:
Конечно после принятия сервером - это же очевидно. Кроме того, функция называется OrderSEND.

ОК, понятно!

Насчёт "очевидности" - так это по большей части для тех, кто не один год плотно работает с системой. Я же споткнулся на вопросе о том, можно ли рассматривать фразу из справочника (для функции  OrderSend() "в случае успешной базовой проверки структур возвращается true") как достаточное условие для возврата true. Теперь получается, что нет, нельзя: это только необходимое условие, но не достаточное.

Yedelkin
Yedelkin | 25 янв 2011 в 19:27
Interesting:
На мой взгляд выводы только по true делать не стоит, нужно еще код ответа сервера анализировать (причем последний приоритетней, как мне кажется)...

Да, у меня за эти несколько часов как пелена спала: среди кодов возврата вообще нет кода для "успешной проверки базовой структуры" (назовём его так), а поэтому true сразу после базовой проверки вернуться никак не может.

Насчёт детальноого анализа кода возврата - да, так и в справочнике рекомендовано. Я просто всё ищу пути для обоснованного уменьшения числа имеющихся проверок.

Trolls
Trolls | 26 янв 2011 в 13:06

Объясните пожалуйста что означает. Ордер поставлен в очередь ?

Следующая ситуация

2011.01.26 12:59:26 Network '716201': connection to MetaQuotes-Demo lost
2011.01.26 12:59:26 Trades '716201' : failed instant buy 1.00 EURUSD at 1.37136 [No connection]
2011.01.26 12:58:40 Trades '716201' : instant buy 1.00 EURUSD at 1.37136
2011.01.26 12:56:17 Network '716201': terminal synchronized with MetaQuotes Software Corp.

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

Что это за очередь ? Где она находиться ? Если ордер уже принят сервером, то почему он не выполнен ? Причина ?

 

Yedelkin
Yedelkin | 23 май 2011 в 20:04

В статье рассказано про асинхронность торговых событий, когда получение тикета  ордера при отправке запроса функцией  OrderSend() и появление ордера в терминале по времени могут не совпадать. Здесь всё понятно. Прошлой осенью народ советовал преодолевать такую асинхронность засыпанием секунды на три. А каково гарантированное время, за которое и значение тикета, и сам ордер появятся в терминале (после принятия ордера сервером)? Я могу подождать и 20 секунд, если надо, - хотелось бы знать, какой промежуток гарантированно обеспечит такую вот "ручную синхронизацию".

Новый индикатор технического анализа Moving Mini-Max и его реализация в MQL5 Новый индикатор технического анализа Moving Mini-Max и его реализация в MQL5

В статье рассматривается новый индикатор технического анализа Moving Mini-Max, разработанный З.К. Силагадзе в 2008 году. Индикатор основан на модели туннельного эффекта, предложенного Г. Гамовым в теории альфа-распада. Описывается реализация индикатора на MQL5 и возможности его использования в торговле.

Мастер MQL5: Как написать свой модуль сопровождения открытых позиций Мастер MQL5: Как написать свой модуль сопровождения открытых позиций

Генератор торговых стратегий Мастера MQL5 значительно упрощает проверку торговых идей. В статье рассказывается о том, как написать и подключить в Мастер MQL5 свой собственный модуль управления открытыми позициями, устанавливающий уровень Stop Loss в безубыток при движении цены в благоприятном направлении, что позволяет защитить прибыль и уменьшить потери. Рассматривается структура и формат описания созданного класса для Мастера MQL5.

Курс Монетки и основанный на нем Индикатор Трендовости Курс Монетки и основанный на нем Индикатор Трендовости

Модели случайных блужданий даётся название "Курс Монетки". Приводятся свойства курса монетки с точки зрения трейдера. Предлагается создать симулятор курса на основе курса монетки с трендом. Для отличия реального курса от курса монетки создан индикатор трендовости. Рассматривается трендовость реального курса.

Реализация индикаторов в виде классов на примере Zigzag и ATR Реализация индикаторов в виде классов на примере Zigzag и ATR

Споры о том, какой способ расчета индикаторов является оптимальным, идут постоянно. Где лучше вычислять значения индикатора - в самом индикаторе или встроить всю логику в код самого эксперта, который его использует? В статье рассматривается один из вариантов переноса кода пользовательского индикатора iCustom непосредственно в код эксперта или скрипта с оптимизацией расчетов и моделированием значения prev_calculated.