English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Разнонаправленная торговля и хеджирование позиций в MetaTrader 5 с помощью API HedgeTerminal, часть 2

Разнонаправленная торговля и хеджирование позиций в MetaTrader 5 с помощью API HedgeTerminal, часть 2

MetaTrader 5Торговые системы | 28 января 2015, 08:49
7 019 3
Vasiliy Sokolov
Vasiliy Sokolov

Оглавление


Введение

Данная статья является продолжением статьи "Разнонаправленная торговля и хеджирование позиций в MetaTrader 5 при помощи панели HedgeTerminal, часть 1". Во второй части статьи мы рассмотрим вопросы интеграции ваших экспертов, а также других программ, написанных на MQL5, с библиотекой HedgeTerminalAPI. Эта статья посвящена описанию работы с этой библиотекой. С ее помощью вы сможете создавать разнонаправленных торговых экспертов и работать в комфортном и простом торговом окружении.

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

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


Глава 1. Взаимодействие экспертов с API HedgeTerminal и его панелью

1.1. Установка HedgeTermianlAPI. Первый запуск библиотеки

Установка HedgeTerminalAPI отличается от установки визуальной панели HT тем, что ее нельзя запустить непосредственно, ведь библиотека не может быть запущена в MetaTrader 5 сама по себе. Вместо этого необходимо написать специальный эксперт, который вызовет функцию HedgeTerminalInstall() из этой библиотеки. Она установит специальный заголовочный файл Prototypes.mqh, описывающий функции, доступные в HedgeTerminalAPI .

Чтобы установить библиотеку на свой компьютер, потребуется выполнить три шага:

Шаг 1. Скачайте библиотеку HedgeTerminalAPI на свой компьютер. Расположение библиотеки относительно вашего терминала будет: \MQL5\Experts\Market\HedgeTerminalApi.ex5.

Шаг 2. Создайте новый эксперт в мастере MQL из стандартного шаблона. Мастер MQL сгенерирует следующий исходный код:

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  }

Шаг 3. В получившимся эксперте вам понадобится только одна функция OnInit(), а также директива экспорта, описывающая специальную функцию-инсталлятор HedgeTerminalInstall(), экспортируемую библиотекой HedgeTerminalApi. Эту функцию необходимо непосредственно запустить в системной функции OnInit(). Исходный код, выполняющий эти операции, подчеркнут желтым маркером:

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#import "HedgeTerminalAPI.ex5"
   void HedgeTerminalInstall(void);
#import

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   HedgeTerminalInstall();
   ExpertRemove();   
//---
   return(INIT_SUCCEEDED);
  }

Шаг 4. Далее ваши действия будут зависеть от того, приобрели ли вы библиотеку или нет. Если вы ее приобрели, то сможете запустить этот эксперт в режиме реального времени прямо на графике. В этом случае запустится стандартный инсталлятор для общей линейки продуктов HedgeTerminal. Вы легко сможете пройти его, следуя инструкциям, описанным в разделах 2.1 и 2.2 статьи "Разнонаправленная торговля и хеджирование позиций в MetaTrader 5 с помощью панели HedgeTerminal, часть 1". После прохождения мастера установки все необходимые файлы, включая заголовочный файл и файл с тестовым экспертом, будут установлены на ваш компьютер.

Если вы не приобретали библиотеку, а хотите просто протестировать ее работу, то запуск эксперта в режиме реального времени вам будет недоступен, но вы сможете протестировать API, запустив эксперт в тестере стратегий. В этом случае инсталлятор запускаться не будет. В режиме тестирования HedgeTermianalAPI работает в монопольном режиме, поэтому ему не нужны будут файлы, устанавливаемые в обычном режиме, следовательно, в этом случае ничего больше настраивать не нужно.

После окончания тестирования эксперта или его работы в общей директории терминала появится папка \HedgeTerminal. Обычным путем к общей директории терминалов MetaTrader является c:\Users\<Имя пользователя>\AppData\Roaming\MetaQuotes\Terminal\Common\Files\HedgeTerminal\, где <Имя пользователя> - имя вашего текущего аккаунта на компьютере. К этому моменту в папке \HedgeTerminal уже должны будут располагаться файлы \MQL5\Include\Prototypes.mqh и \MQL5\Experts\Chaos2.mq5. Скопируйте эти файлы в аналогичные директории вашего терминала: файл Prototypes.mqh в  \MetaTrader5\MQL5\Include, а файл Chaos2.mq5 в \MetaTrader5\MQL5\Experts.

Файл Prototypes.mqh - заголовочный файл, включающий описание экспортированных функций из библиотеки HedgeTerminalAPI. Их назначение и описание вы сможете увидеть в комментариях к ним.

Файл Chaos2.mq5 содержит пример советника, описанный в разделе "Пример работы с функцией SendTradeRequest() и структурой HedgeTradeRequest() на примере советника "Chaos II". Он поможет вам наглядно разобраться как работает HedgeTerminalAPI и в том, что нужно сделать для написания советника, использующего технологии виртуализации HedgeTerminal.

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

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int transTotal = TransactionsTotal();
   printf((string)transTotal);
  }

Например, приведенный выше код получает общее количество активных позиций и выводит это количество во вкладку "Эксперты" торгового терминала MetaTrader 5.

Важно понимать, что фактическая инициализация HedgeTerminal происходит в момент первого вызова одной из его функций. Такую инициализацию принято называть отложенной. Поэтому первый вызов одной из его функций может занимать значительное время. Если требуется быстрая реакция в момент первого запуска, необходимо производить инициализацию HT заблаговременно, например, можно вызвать функцию TransactionTotal() в блоке OnInit().

Отложенная инициализация позволяет обойтись без явной инициализации из эксперта. Это существенно упрощает работу с HedgeTerminal и делает ненужным его предварительное конфигурирование.


1.2. Интеграция экспертов с панелью HedgeTerminal

Если у вас есть визуальная панель HedgeTerminal и полнофункциональная версия библиотеки, которую можно запустить в режиме реального времени, вы можете интегрировать свои торговые эксперты с этой панелью так, чтобы все торговые действия, совершаемые ими, отображались и в панели тоже. В целом, интеграция происходит за кулисами, если вы используете функции HedgeTermianalAPI, то совершаемые роботами действия автоматически становятся видны на панели. Однако, вы можете расширить представление, подписав каждую совершенную транзакцию именем эксперта. Для этого в файле Settings.xml снимите комментарий с тэга:

<Column ID="Magic" Name="Magic" Width="100"/>

Этот тэг находится в секциях <Active-Position> и <History-Position>.

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

Чтобы отображать более привычное для человека имя эксперта, а не его номер, внесите соответствующее имя в файл псевдонимов ExpertAliases.xml. Скажем, если вы хотите чтобы вместо магического номера эксперта 123847 отображалось его имя, например "ExPro 1.1" внесите в файл следующий тэг:

<Expert Magic="123847" Name="ExPro 1.1"></Expert>

Если вы все сделали правильно, после перезапуска панели в соответствующей колонке появится имя эксперта, а не его magic:

Рис. 1. Отображение имен экспертов вместо их магических номеров

Рис. 1. Отображение имен экспертов вместо их магических номеров

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


1.3. Общие принципы работы с HedgeTerminalAPI

HedgeTerminal работает не только с разнонаправленными позициями, но также и с другими торговыми типами, например, отложенными ордерами, сделками, брокерскими операциями на счете. С точки зрения HedgeTerminal все эти типы объединяются в единую группу транзакций. Сделка, отложенный ордер, разнонаправленная позиция – все это транзакции. Однако транзакция сама по себе существовать не может. В терминах объектно-ориентированного программирования легче всего представить транзакцию как абстрактный базовый класс, от которого наследуются все возможные торговые сущности, вроде сделок и разнонаправленных позиций. Поэтому все функции HedgeTerminalAPI условно можно разделить на несколько групп:

  1. Функции по перебору и выбору транзакций. Общая сигнатура функций и способы работы с ними почти полностью совпадают с функциями OrderSend() и OrderSelect() в MetaTrader 4;
  2. Функции получения свойств выбранной транзакции. У каждой транзакции свой набор свойств и свои функции выбора этих свойств. Общая сигнатура функций и способы работы с ними напоминают системные функции MetaTrader 5 по доступу к свойствам позиций, сделок и ордеров (вроде OrderGetDouble() или HistoryDealGetInteger());
  3. В HedgeTerminalAPI используется лишь одна торговая функция: SendTradeRequest(). С помощью этой функции можно закрыть разнонаправленную позицию или ее часть. Также с ее помощью можно модифицировать уровни стоп-лосс, тейк-профит или исходящий комментарий. Работа с функцией схожа с работой функции OrderSend() в MetaTrader 5;
  4. Функция для получения общих ошибок GetHedgeError(), функции для подробного анализа торговых действий HedgeTerminal: TotalActionsTask() и GetActionResult(). Также они служат для обнаружения ошибок. Аналогов данным функциям в MetaTrader 4 и MetaTrader 5 нет.

Работа почти со всеми функциями напоминает работу с системными функциями MetaTrader 4 и MetaTrader 5. Как правило, функция на вход принимает какой-либо идентификатор (значение перечисления), и возвращает значение, которое ему соответствует.

Для каждой функции существуют свои перечисления. Общая сигнатура их вызова следующая:

<значение> = Функция(<идентификатор>);

Рассмотрим пример получения уникального идентификатора для позиции. В MetaTrader 5 получение соответствующего свойства выглядело бы так:

ulong id = PositionGetInteger(POSITION_IDENTIFIER);

В HedgeTerminal получение аналогичного идентификатора для разнонаправленной позиции было бы таким:

ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)

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


1.4. Выбор транзакций

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

Самих списков транзакций на сегодняшний день два, это список активных и список исторических транзакций. С каким из двух списков работать, определяется с помощью модификатора ENUM_MODE_TRADES. Он аналогичен модификатору MODE_TRADES в MetaTrader 4.

Общий алгоритм перебора и выбора транзакции следующий:

1: for(int i=TransactionsTotal(MODE_TRADES)-1; i>=0; i--)
2:     {
3:      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))continue;
4:      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
5:      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
6:      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
7:      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN)continue;
8:      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)
9:     }

Код перебирает список активных транзакций в цикле for (строка 1). Прежде чем продолжить работу с транзакцией, ее необходимо выбрать с помощью функции TransactionSelect() (строка 3). Из этих транзакций выбираются только разнонаправленные позиции (строка 4). Если магический номер позиции и ее символ не соответствует магическому номеру текущего эксперта и символу, на котором он запущен, берется следующая позиция (строки 5 и 6). Затем узнается уникальный идентификатор позиции (строка 8).

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

Модификатор POSITION_STATE_FROZEN сигнализирует о том, что позиция "заморожена" для работы и недоступна для изменений. Модификатор POSITION_STATE_ACTIVE показывает, что позиция активна и может быть изменена. Эти модификаторы находятся в перечислении ENUM_HEDGE_POSITION_STATE, которое описывается в соответствующем разделе документации.

Если бы нужно было сделать перебор в списке исторических транзакций, модификатор MODE_TRADES в функциях TransactionTotal() и TransactionSelect() необходимо было бы заменить на MODE_HISTORY.

Одна транзакция в HedgeTerminal может быть вложена в другую. Это сильно отличается от концепции MetaTrader 5, где вложенности никакой нет. Например, историческая разнонаправленная позиция в HedgeTerminal состоит из двух ордеров, каждый из которых включает произвольный набор сделок. На схеме такую вложенность можно было бы изобразить так:

Рис. 2. Вложенность транзакций

Рис. 2. Вложенность транзакций

Вложенность транзакций хорошо прослеживается в визуальной панели HedgeTerminal.

На скриншоте ниже показана развернутая позиция, принадлежащая эксперту MagicEx 1.3, где это хорошо видно:

Рис. 3. Вложенность транзакций в панели HedgeTerminal

Рис. 3. Вложенность транзакций в панели HedgeTerminal

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

Для этого необходимо:

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

Обратите внимание, что после того, как транзакция выбрана, для нее становятся доступны ее определенные свойства. Так, если транзакцией является ордер, после его выбора функцией HedgeOrderSelect() для него возможно узнать количество сделок (HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL)) или средневзвешенную цену входа (HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED)).

Например, давайте узнаем цену сделки #1197610, помеченной красным маркером на скриншоте выше. Эта сделка принадлежит разнонаправленной позиции эксперта MagicEx 1.3.

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

#include <Prototypes.mqh>

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnTick()
  {
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
    {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // Select transaction #i;
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;                 // If transaction is not position - continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;  // If position is not main - continue;
      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID);   // Get id for closed order;
      if(id!=5917888)continue;                                             // If id of position != 5917888 - continue;
      printf("1: -> Select position #"+(string)id);                        // Print position id;
      if(!HedgeOrderSelect(ORDER_SELECTED_CLOSED))continue;                // Select closed order or continue;    
      ulong order_id = HedgeOrderGetInteger(HEDGE_ORDER_ID);               // Get id closed order;
      printf("2: ----> Select order #" + (string)order_id);                // Print id closed order;
      int deals_total = (int)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);// Get deals total in selected order;
      for(int deal_index = deals_total-1; deal_index >= 0; deal_index--)   // Search deal #1197610...
        {
         if(!HedgeDealSelect(deal_index))continue;                         // Select deal by index or continue;
         ulong deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);               // Get id for current deal;
         if(deal_id != 1197610)continue;                                   // Select deal #1197610;
         double price = HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED);     // Get price executed;
         printf("3: --------> Select deal #"+(string)deal_id+              // Print price excecuted;
              ". Executed price = "+DoubleToString(price,0));
        }
     }
  }

После запуска этого кода на вкладке "Эксперты" терминала MetaTrader 5 будет выведено сообщение:

2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      3: --------> Select deal #1197610. Executed price = 4735
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      2: ----> Select order #6389111
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      1: -> Select position #5917888

Как видно, эксперт вначале выбирает позицию #5917888, после чего внутри этой позиции выбирает ее входящий ордер под номером #6389111. Когда ордер выбран, эксперт начинает перебирать сделки, в поисках сделки под номером 1197610. Когда эта сделка находится, он получает цену ее исполнения и выводит эту цену в журнал.


1.5. Получение кодов ошибок с помощью GetHedgeError()

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

Самый простой случай получить ошибку – это забыть выбрать транзакцию функцией TransactionSelect(). В этом случае функция TransactionType() вернет модификатор TRANS_NOT_DEFINED.

Чтобы понять, в чем проблема, необходимо получить модификатор последней ошибки. Он подскажет нам, что транзакция не была выбрана. Код ниже делает это:

for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
  {
   //if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // forgot to select;
   ENUM_TRANS_TYPE type = TransactionType();
   if(type == TRANS_NOT_DEFINED)
   {
      ENUM_HEDGE_ERR error = GetHedgeError();
      printf("Error, transaction type not defined. Reason: " + EnumToString(error));
   }
  }

В результате будет выведено сообщение:

Error, transaction type not defined. Reason: HEDGE_ERR_TRANS_NOTSELECTED

Идентификатор ошибки должен навести нас на мысль, что мы забыли выбрать транзакцию, перед тем как получить ее тип.

Полный перечень возможных ошибок указан в структуре ENUM_HEDGE_ERR.


1.6. Подробный анализ торговых действий и идентификация ошибок при помощи функций TotalActionsTask() и GetActionResult()

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

  1. Отменить отложенный stop-ордер, реализующий стоп-уровень;
  2. Поставить новый отложенный stop-ордер на место предыдущего с новым комментарием.

Если новый стоп-ордер сработает, то его комментарий будет отображен как комментарий, закрывающий позицию, что верно.

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

Для этих целей служат две функции: TotalActionsTask(), которая возвращает общее количество торговых действий (подзадач), которые входят в задачу, и GetActionResult(), она принимает индекс подзадачи и возвращает ее тип и результат ее выполнения. Так как все торговые действия выполняются стандартными средствами MetaTrader 5, то результат их выполнения соответствует коду возврата торгового сервера.

В общем случае алгоритм поиска причины неудачи следующий:

  1. Получить общее количество подзадач в нашей задаче с помощью функции TotalActionsTask();
  2. Сделать перебор всех подзадач в цикле for. Определить тип каждой подзадачи и результат ее завершения.

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

Код ниже приводит пример, как эксперт мог бы узнать причину этого сбоя:

#include <Prototypes.mqh> 

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnTick()
  {
//detect active position
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;
      ENUM_TRANS_TYPE type=TransactionType();
      if(type==TRANS_NOT_DEFINED)
        {
         ENUM_HEDGE_ERR error=GetHedgeError();
         printf("Error, transaction not defined. Reason: "+EnumToString(error));
        }
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
      HedgeTradeRequest request;
      request.action=REQUEST_MODIFY_COMMENT;
      request.exit_comment="My new comment";
      if(!SendTradeRequest(request)) // Is error?
        {
         for(uint action=0; action < TotalActionsTask(); action++)
           {
            ENUM_TARGET_TYPE typeAction;
            int retcode=0;
            GetActionResult(action, typeAction, retcode);
            printf("Action#" + (string)action + ": " + EnumToString(type) +(string)retcode);
           }
        }
     }
  }

После выполнения этого кода будет выведено сообщение:

Action #0 TARGET_DELETE_PENDING_ORDER 10009 (TRADE_RETCODE_PLACED)
Action #1 TARGET_SET_PENDING_ORDER 10015 (TRADE_RETCODE_INVALID_PRICE)

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

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


1.7. Отслеживания статуса исполнения торговой задачи

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

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

Например, если после отправки приказа на закрытие позиции что-то пошло не так, и позиция не была закрыта, то в этом случае нужно получить статус задачи.

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

ENUM_TASK_STATUS status=HedgePositionGetInteger(HEDGE_POSITION_TASK_STATUS);
switch(status)
  {
   case TASK_STATUS_COMPLETE:
      printf("Task complete!");
      break;
   case TASK_STATUS_EXECUTING:
      printf("Task executing. Waiting...");
      Sleep(200);
      break;
   case TASK_STATUS_FAILED:
      printf("Filed executing task. Print logs...");
      for(int i=0; i<TotalActionsTask(); i++)
        {
         ENUM_TARGET_TYPE type;
         uint retcode;
         GetActionResult(i,type,retcode);
         printf("#"+i+" "+EnumToString(type)+" "+retcode);
        }
      break;
   case TASK_STATUS_WAITING:
      printf("task will soon start.");
      break;
  }

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

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

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

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


1.8. Изменение и закрытие разнонаправленных позиций

Изменением и закрытием разнонаправленных позиций занимается функция SendTradeRequest(). Над активными позициями можно совершить всего три действия:

  1. Полностью или частично закрыть позицию;
  2. Изменить уровни стоп-лосс и тейк-профит позиции;
  3. Изменить исходящий комментарий позиции.

Исторические позиции для изменений недоступны. Аналогично функции OrderSend() в MetaTrader 5, функция SendTradeRequest() использует для своей работы предварительно составленный торговый запрос в виде структуры HedgeTraderRequest. Более подробно о работе функции SendTradeRequest() и структуре HedgeTraderRequest написано в соответствующем разделе документации. Пример, показывающий изменение и закрытие позиций, размещен в разделе, описывающим советник "Chaos II".


1.9. Установка свойств HedgeTerminal из эксперта

HedgeTerminal обладает набором свойств, например, частотой обновления или количеством секунд, которое необходимо дожидаться ответа от сервера.

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

Для такого случая существует специальный набор функций HedgePropertySet… В текущей версии доступен лишь один прототип из этого набора:

enum ENUM_HEDGE_PROP_INTEGER
{
   HEDGE_PROP_TIMEOUT,
};

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, int value)

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

bool res = HedgePropertySetInteger(HEDGE_PROP_TIMEOUT, 30);

После установки этого свойства, если ответ от сервера при отправке асинхронного запроса не придет в течении 30 секунд, заблокированная позиция будет разблокирована.


1.10. Синхронный и асинхронный режимы работы

HedgeTerminal и его API совершают торговые действия полностью в асинхронном режиме.

Однако этот режим требует более сложной логики от торговых экспертов. Чтобы скрыть эту сложность, в HedgeTerminalAPI реализован специальный эмулятор синхронной работы, позволяющий экспертам, написанным в обычном синхронном режиме, взаимодействовать с асинхронными алгоритмами HedgeTerminalAPI. Такое взаимодействие проявляется в момент изменения и закрытия разнонаправленных позиций с помощью функции SendTradeRequest(). Данная функция позволяет выполнить торговое задание как в синхронном, так и в асинхронном режиме. По умолчанию все торговые действия выполнятся синхронно через эмулятор синхронной работы. Однако если в торговом запросе (структуре HedgeTradeRequest) будет явно указан флаг asynch_mode = true, торговое задание будет выполнено в асинхронном режиме.

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

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

Синхронный эмулятор вносит незначительные задержки в выполнение торговых приказов. Это связано с тем, что торговому окружению MetaTrader 5 требуется некоторое время, чтобы совершенные торговые действия в нем были отображены. Необходимость доступа к окружению связана прежде всего с тем, что в режиме эмуляции синхронного потока HedgeTermianlAPI недоступны события, приходящие в обработчик OnTradeTransaction().

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


1.11. Пример работы с свойствами разнонаправленных позиций на основе скрипта

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

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

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

//+------------------------------------------------------------------+
//|                                           sample_using_htapi.mq5 |
//|         Copyright 2014, Vasiliy Sokolov, Russia, St.-Petersburg. |
//|                              https://login.mql5.com/ru/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

// Include prototypes function of HedgeTerminalAPI library.
#include <Prototypes.mqh> 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnStart()
  {
   // Search all transaction in list transaction...
   for(int i=TransactionsTotal(); i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))                           // Selecting from active transactions
        {
         ENUM_HEDGE_ERR error=GetHedgeError();                                      // Get reason if selecting has failed
         printf("Error selecting transaction # "+(string)i+". Reason: "+            // Print reason
                EnumToString(error));
         ResetHedgeError();                                                         // Reset error
         continue;                                                                  // Go to next transaction
        }
      // Only for hedge positions
      if(TransactionType()==TRANS_HEDGE_POSITION) 
        {
         // --- Position captions --- //
         ENUM_TRANS_DIRECTION direction=(ENUM_TRANS_DIRECTION)                      // Get direction caption
                              HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
         double price_entry = HedgeOrderGetDouble(HEDGE_ORDER_PRICE_EXECUTED);      // Get volume of positions
         string symbol = HedgePositionGetString(HEDGE_POSITION_SYMBOL);             // Get symbol of position
         // --- Order captions --- //
         if(!HedgeOrderSelect(ORDER_SELECTED_INIT))continue;                        // Selecting init order in position
         double slippage = HedgeOrderGetDouble(HEDGE_ORDER_SLIPPAGE);               // Get some slippage was
         uint deals_total = (uint)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);    // Get deals total
         // --- Deals captions --- //
         double commissions=0.0;
         ulong deal_id=0;
         //Search all deals in list deals...
         for(uint d_index=0; d_index<deals_total; d_index++)                        
           {
            if(!HedgeDealSelect(d_index))continue;                                  // Selecting deal by its index
            deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);                           // Get deal id
            commissions += HedgeDealGetDouble(HEDGE_DEAL_COMMISSION);               // Count commissions
           }
         int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
         printf("Position #" + (string)i + ": DIR " + EnumToString(direction) +     // Print result line
         "; PRICE ENTRY " + DoubleToString(price_entry, digits) + 
         "; INIT SLIPPAGE " + DoubleToString(slippage, 2) + "; LAST DEAL ID " +
         (string)deal_id + "; COMMISSIONS SUM " + DoubleToString(commissions, 2));
        }
     }
  }

1.12. Пример работы с функцией SendTradeRequest() и структурой HedgeTradeRequest на примере советника "Chaos II"

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

Мы не будем следовать полностью его рекомендациям, а несколько упростим схему, исключив из стратегии индикатор Alligator и некоторые другие условия. Выбор именно этой стратегии был продиктован несколькими соображениями. Главное из них заключается в том, что эта стратегия включает сложные композитные тактики ведения позиции. Иногда требуется закрывать часть объема позиции и перенести уровень стоп-лосс в безубыток.

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

Отправной точкой нам послужит разворотный бар. Бычий развортный бар – это бар, цена закрытия которого находится в его верхней трети, а его минимум (Low) самый низкий за N последних баров. Медвежий разворотный бар – это бар, цена закрытия которого находится в его нижней трети, а его максимум (High) самый высокий за N последних баров. Параметр N выбирается произвольно и его можно установить при запуске эксперта. Это отличается от классической стратегии "Chaos 2".

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

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

Первая позиция имеет короткие цели. Для нее устанавливается тейк-профит, прибыль которого в случае срабатывания будет равна абсолютному убытку, полученному от срабатывания стоп-лосса. Например, если длинная позиция была открыта по цене 1.0000, и стоп-лосс для нее находится на уровне 0.9000, то уровень тейк-профита для нее составит 1.0000 + (1.0000 – 0.9000) = 1.1000. Выход из позиции будет осуществляться по стоп-лосу или тейк-профиту.

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

Обратимся к графику, иллюстрирующему данную стратегию:

Рис. 4. Представление разнонаправленных позиций эксперта Chaos 2 на ценовом графике

Рис. 4. Представление разнонаправленных позиций эксперта "Chaos 2" на ценовом графике

Разворотные бары выделены красной рамкой. Период N на данном графике соответствует 2. Выбран наиболее удачный момент для этой стратегии. Короткие позиции показаны голубой пунктирной линией, длинные – зеленой. Как видно, даже в такой достаточно простой стратегии возникают ситуации, когда могут существовать одновременно и длинные и короткие позиции. Обратите внимание на период с 5 по 8 января 2014.

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

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

Код, реализующий эту стратегию, представлен ниже.

В нем специально не используется ООП программирование, и он адаптирован для того, чтобы новички в программировании могли его лучше понять:

//+------------------------------------------------------------------+
//|                                                       Chaos2.mq5 |
//|     Copyright 2014, Vasiliy Sokolov specially for HedgeTerminal. |
//|                                          St.-Petersburg, Russia. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Include files                                                |
//+------------------------------------------------------------------+
#include <Prototypes.mqh>           // Include prototypes function of HedgeTerminalAPI library

//+------------------------------------------------------------------+
//| Input parameters.                                                |
//+------------------------------------------------------------------+
input uint N=2;                     // Period of extermum/minimum
input uint OldPending=3;            // Old pending

//+------------------------------------------------------------------+
//| Private variables of expert advisor.                             |
//+------------------------------------------------------------------+
ulong Magic = 2314;                 // Magic number of expert
datetime lastTime = 0;              // Remembered last time for function DetectNewBar
int hFractals = INVALID_HANDLE;     // Handle of indicator 'Fractals'. See: 'https://www.mql5.com/en/docs/indicators/ifractals'
//+------------------------------------------------------------------+
//| Type of bar by Bill Wiallams strategy.                           |
//+------------------------------------------------------------------+
enum ENUM_BAR_TYPE
  {
   BAR_TYPE_ORDINARY,               // Ordinary bar. 
   BAR_TYPE_BEARISH,                // This bar close in the upper third and it's minimum is lowest at N period
   BAR_TYPE_BULLISH,                // This bar close in the lower third and it's maximum is highest at N period
  };
//+------------------------------------------------------------------+
//| Type of Extremum.                                                |
//+------------------------------------------------------------------+
enum ENUM_TYPE_EXTREMUM
  {
   TYPE_EXTREMUM_HIGHEST,           // Extremum from highest prices
   TYPE_EXTREMUM_LOWEST             // Extremum from lowest prices
  };
//+------------------------------------------------------------------+
//| Type of position.                                                |
//+------------------------------------------------------------------+
enum ENUM_ENTRY_TYPE
  {
   ENTRY_BUY1,                      // Buy position with short target
   ENTRY_BUY2,                      // Buy position with long target
   ENTRY_SELL1,                     // Sell position with short target
   ENTRY_SELL2,                     // Sell position with long target
   ENTRY_BAD_COMMENT                // My position, but wrong comment
  };
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create indicator 'Fractals' ---//
   hFractals=iFractals(Symbol(),NULL);
   if(hFractals==INVALID_HANDLE)
      printf("Warning! Indicator 'Fractals' not does not create. Reason: "+
             (string)GetLastError());
//--- Corection magic by timeframe ---//
   int minPeriod=PeriodSeconds()/60;
   string strMagic=(string)Magic+(string)minPeriod;
   Magic=StringToInteger(strMagic);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete indicator 'Fractals' ---//
   if(hFractals!=INVALID_HANDLE)
      IndicatorRelease(hFractals);
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Run logic only open new bar. ---//
   int totals=SupportPositions();
   if(NewBarDetect()==true)
     {
      MqlRates rates[];
      CopyRates(Symbol(),NULL,1,1,rates);
      MqlRates prevBar=rates[0];
      //--- Set new pendings order ---//
      double closeRate=GetCloseRate(prevBar);
      if(closeRate<=30 && BarIsExtremum(1,N,TYPE_EXTREMUM_HIGHEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BEARISH);
        }
      else if(closeRate>=70 && BarIsExtremum(1,N,TYPE_EXTREMUM_LOWEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BULLISH);
        }
      DeleteOldPendingOrders(OldPending);
     }
//---
  }
//+------------------------------------------------------------------+
//| Analyze open positions and modify it if needed.                  |
//+------------------------------------------------------------------+
int SupportPositions()
  {
//---
   int count=0;
   //--- Analize active positions... ---//
   for(int i=0; i<TransactionsTotal(); i++) // Get total positions.
     {
      //--- Select main active positions ---//
      if(!TransactionSelect(i, SELECT_BY_POS, MODE_TRADES))continue;             // Select active transactions
      if(TransactionType() != TRANS_HEDGE_POSITION)continue;                     // Select hedge positions only
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)                 // Select main positions by magic
      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN) // If position is frozen - continue
         continue;                                                               // Let's try to get access to positions later
      count++;
      //--- What position do we choose?... ---//
      ENUM_ENTRY_TYPE type=IdentifySelectPosition();
      bool modify=false;
      double sl = 0.0;
      double tp = 0.0;
      switch(type)
        {
         case ENTRY_BUY1:
         case ENTRY_SELL1:
           {
            //--- Check sl, tp levels and modify it if need. ---//
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            sl=GetStopLossLevel();
            if(!DoubleEquals(sl,currentStop))
               modify=true;
            tp=GetTakeProfitLevel();
            double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
            double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
            //--- Close by take-profit if price more tp level
            bool isBuyTp=tp<bid && !DoubleEquals(tp,0.0) && type==ENTRY_BUY1;
            bool isSellTp=tp>ask && type==ENTRY_SELL1;
            if(isBuyTp || isSellTp)
              {
               HedgeTradeRequest request;
               request.action=REQUEST_CLOSE_POSITION;
               request.exit_comment="Close by TP from expert";
               request.close_type=CLOSE_AS_TAKE_PROFIT;
               if(!SendTradeRequest(request))
                 {
                  ENUM_HEDGE_ERR error=GetHedgeError();
                  string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
                  printf("Close position by tp failed. Reason: "+EnumToString(error)+" "+logs);
                  if(error==HEDGE_ERR_TASK_FAILED)
                     PrintTaskLog();
                  ResetHedgeError();
                 }
               else break;
              }
            double currentTakeProfit=HedgePositionGetDouble(HEDGE_POSITION_TP);
            if(!DoubleEquals(tp,currentTakeProfit))
               modify=true;
            break;
           }
         case ENTRY_BUY2:
           {
            //--- Check sl level and set modify flag. ---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            if(sl>currentStop)
               modify=true;
            break;
           }
         case ENTRY_SELL2:
           {
            //--- Check sl level and set modify flag. ---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            bool usingSL=HedgePositionGetInteger(HEDGE_POSITION_USING_SL);
            if(sl<currentStop || !usingSL)
               modify=true;
            break;
           }
        }
      //--- if  need modify sl, tp levels - modify it. ---//
      if(modify)
        {
         HedgeTradeRequest request;
         request.action=REQUEST_MODIFY_SLTP;
         request.sl = sl;
         request.tp = tp;
         if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
            request.exit_comment="Exit by T/P level";
         else
            request.exit_comment="Exit by trailing S/L";
         if(!SendTradeRequest(request))
           {
            ENUM_HEDGE_ERR error=GetHedgeError();
            string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
            printf("Modify stop-loss or take-profit failed. Reason: "+EnumToString(error)+" "+logs);
            if(error==HEDGE_ERR_TASK_FAILED)
               PrintTaskLog();
            ResetHedgeError();
           }
         else break;
        }
     }
   return count;
//---
  }
//+------------------------------------------------------------------+
//| Return stop-loss level for selected position.                    |
//| RESULT                                                           |
//|   Stop-loss level                                                |
//+------------------------------------------------------------------+
double GetStopLossLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   double fractals[];
   double sl=0.0;
   MqlRates ReversalBar;

   if(!LoadReversalBar(ReversalBar))
     {
      printf("Reversal bar load failed.");
      return sl;
     }
   //--- What position do we choose?... ---//
   switch(IdentifySelectPosition())
     {
      case ENTRY_SELL2:
        {
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,UPPER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]<sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_ASK);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]>price+freeze)
                     sl=NormalizeDouble(fractals[i]+point,Digits());
                 }
              }
            break;
           }
        }
      case ENTRY_SELL1:
         sl=ReversalBar.high+point;
         break;
      case ENTRY_BUY2:
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,LOWER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]>sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_BID);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]<price-freeze)
                     sl=NormalizeDouble(fractals[i]-point,Digits());
                 }
              }
            break;
           }
      case ENTRY_BUY1:
         sl=ReversalBar.low-point;
     }
   sl=NormalizeDouble(sl,Digits());
   return sl;
//---
  }
//+------------------------------------------------------------------+
//| Return Take-Profit level for selected position.                  |
//| RESULT                                                           |
//|   Take-profit level                                              |
//+------------------------------------------------------------------+
double GetTakeProfitLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   ENUM_ENTRY_TYPE type=IdentifySelectPosition();
   double tp=0.0;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
     {
      if(!HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
         return tp;
      double sl=HedgePositionGetDouble(HEDGE_POSITION_SL);
      double openPrice=HedgePositionGetDouble(HEDGE_POSITION_PRICE_OPEN);
      double deltaStopLoss=MathAbs(NormalizeDouble(openPrice-sl,Digits()));
      if(type==ENTRY_BUY1)
         tp=openPrice+deltaStopLoss;
      if(type==ENTRY_SELL1)
         tp=openPrice-deltaStopLoss;
      return tp;
     }
   else
      return 0.0;
//---
  }
//+------------------------------------------------------------------+
//| Identify what position type is select.                           |
//| RESULT                                                           |
//|   Return type position. See ENUM_ENTRY_TYPE                      |
//+------------------------------------------------------------------+
ENUM_ENTRY_TYPE IdentifySelectPosition()
  {
//---   
   string comment=HedgePositionGetString(HEDGE_POSITION_ENTRY_COMMENT);
   int pos=StringLen(comment)-2;
   string subStr=StringSubstr(comment,pos);
   ENUM_TRANS_DIRECTION posDir=(ENUM_TRANS_DIRECTION)HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
   if(subStr=="#0")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY1;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL1;
     }
   else if(subStr=="#1")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY2;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL2;
     }
   return ENTRY_BAD_COMMENT;
//---
  }
//+------------------------------------------------------------------+
//| Set pending orders under or over bar by index_bar.               |
//| INPUT PARAMETERS                                                 |
//|   index_bar - index of bar.                                      |
//|   barType - type of bar. See enum ENUM_BAR_TYPE.                 |
//| RESULT                                                           |
//|   True if new order successfully set, othewise false.            | 
//+------------------------------------------------------------------+
bool SetNewPendingOrder(int index_bar,ENUM_BAR_TYPE barType)
  {
//---
   MqlRates rates[1];
   CopyRates(Symbol(),NULL,index_bar,1,rates);
   MqlTradeRequest request={0};
   request.volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   double vol=request.volume;
   request.symbol = Symbol();
   request.action = TRADE_ACTION_PENDING;
   request.type_filling=ORDER_FILLING_FOK;
   request.type_time=ORDER_TIME_GTC;
   request.magic=Magic;
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   string comment="";
   if(barType==BAR_TYPE_BEARISH)
     {
      request.price=rates[0].low-point;
      comment="Entry sell by bearish bar";
      request.type=ORDER_TYPE_SELL_STOP;
     }
   else if(barType==BAR_TYPE_BULLISH)
     {
      request.price=rates[0].high+point;
      comment="Entry buy by bullish bar";
      request.type=ORDER_TYPE_BUY_STOP;
     }
   MqlTradeResult result={0};
//--- Send pending order twice...
   for(int i=0; i<2; i++)
     {
      request.comment=comment+" #"+(string)i;       // Detect order by comment;
      if(!OrderSend(request,result))
        {
         printf("Trade error #"+(string)result.retcode+" "+
                result.comment);
         return false;
        }
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| Delete old pending orders. If pending order set older that       |
//| n_bars ago pending orders will be removed.                       |
//| INPUT PARAMETERS                                                 |
//|   period - count bar.                                            |
//+------------------------------------------------------------------+
void DeleteOldPendingOrders(int n_bars)
  {
//---
   for(int i=0; i<OrdersTotal(); i++)
     {
      ulong ticket = OrderGetTicket(i);            // Get ticket of order by index.
      if(!OrderSelect(ticket))                     // Continue if not selected.
         continue;
      if(Magic!=OrderGetInteger(ORDER_MAGIC))      // Continue if magic is not main.
         continue;
      if(OrderGetString(ORDER_SYMBOL)!=Symbol())   // Continue if symbol is not main.
         continue;
      //--- Count time elipsed ---//
      datetime timeSetup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);
      int secElapsed=(int)(TimeCurrent()-timeSetup);
      //--- delete old pending order ---//
      if(secElapsed>=PeriodSeconds() *n_bars)
        {
         MqlTradeRequest request={0};
         MqlTradeResult result={0};
         request.action= TRADE_ACTION_REMOVE;
         request.order = ticket;
         if(!OrderSend(request,result))
            printf("Delete pending order failed. Reason #"+(string)result.retcode+" "+result.comment);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| Detect new bar.                                                  |
//+------------------------------------------------------------------+
bool NewBarDetect(void)
  {
//---
   datetime timeArray[1];
   CopyTime(Symbol(),NULL,0,1,timeArray);
   if(lastTime!=timeArray[0])
     {
      lastTime=timeArray[0];
      return true;
     }
   return false;
//---
  }
//+------------------------------------------------------------------+
//| Get close rate. Type bar defined in trade chaos strategy  |
//| and equal enum 'ENUM_TYPE_BAR'.                                  |
//| INPUT PARAMETERS                                                 |
//|   index - index of bars series. for example:                     |
//|   '0' - is current bar. 1 - previous bar.                        |
//| RESULT                                                           |
//|   Type of ENUM_TYPE_BAR.                                         | 
//+------------------------------------------------------------------+
double GetCloseRate(const MqlRates &bar)
  {
//---
   double highLowDelta = bar.high-bar.low;      // Calculate diaposon bar.
   double lowCloseDelta = bar.close - bar.low;  // Calculate Close - Low delta.
   double percentClose=0.0;
   if(!DoubleEquals(lowCloseDelta, 0.0))                    // Division by zero protected.   
      percentClose = lowCloseDelta/highLowDelta*100.0;      // Calculate percent 'lowCloseDelta' of 'highLowDelta'.
   return percentClose;
//---
  }
//+------------------------------------------------------------------+
//| If bar by index is extremum - return true, otherwise             |
//| return false.                                                    |
//| INPUT PARAMETERS                                                 |
//|   index - index of bar.                                          |
//|   period - Number of bars prior to the extremum.                 |
//|   type - Type of extremum. See ENUM_TYPE_EXTREMUM TYPE enum.     |
//| RESULT                                                           |
//|   True - if bar is extremum, otherwise false.                    | 
//+------------------------------------------------------------------+
bool BarIsExtremum(const int index,const int period,ENUM_TYPE_EXTREMUM type)
  {
//--- Copy rates --- //
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,index,N+1,rates);
//--- Search extremum --- //
   for(int i=1; i<ArraySize(rates); i++)
     {
      //--- Reset comment if you want include volume analize. ---//
      //if(rates[0].tick_volume<rates[i].tick_volume)
      //   return false;
      if(type==TYPE_EXTREMUM_HIGHEST && 
         rates[0].high<rates[i].high)
         return false;
      if(type==TYPE_EXTREMUM_LOWEST && 
         rates[0].low>rates[i].low)
         return false;
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| Print current error and reset it.                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> Action #"+(string)i+"; "+EnumToString(type)+"; RETCODE: "+(string)retcode);
     }
//---
  }
//+------------------------------------------------------------------+
//| Load reversal bar. The current position must be selected.        |
//| OUTPUT PARAMETERS                                                |
//|   bar - MqlRates bar.
//+------------------------------------------------------------------+  
bool LoadReversalBar(MqlRates &bar)
  {
//---
   datetime time=(datetime)(HedgePositionGetInteger(HEDGE_POSITION_ENTRY_TIME_SETUP_MSC)/1000+1);
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,time,2,rates);
   int size=ArraySize(rates);
   if(size==0)return false;
   bar=rates[size-1];
   return true;
//---   
  }
//+------------------------------------------------------------------+
//| Compares two double numbers.                                     |
//| RESULT                                                           |
//|   True if two double numbers equal, otherwise false.             |
//+------------------------------------------------------------------+
bool DoubleEquals(const double a,const double b)
  {
//---
   return(fabs(a-b)<=16*DBL_EPSILON*fmax(fabs(a),fabs(b)));
//---
  }

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

К сожалению, эти уровни в момент установки отложенных ордеров установить еще нельзя, ведь реальных позиций не существует. Установкой этих уровней занимается функция SupportPositions(). Для работы ей необходимо знать, для какой позиции необходимо выставить тейк-профит, а какую необходимо тралить вслед за новыми фракталами. Этим определением занимается функция IdentifySelectPosition(). Она, в свою очередь, анализирует инициирующий комментарий позиции, и если он содержит подстроку "#1", для него устанавливается короткая цель, а если подстроку "#2", его стоп тралится.

В случае модификации открытой разнонаправленной позиции или в случае ее закрытия создается соответствующий торговый запрос, а затем он отправляется на выполнение функции SendTradeRequest():

...
if(modify)
  {
   HedgeTradeRequest request;
   request.action=REQUEST_MODIFY_SLTP;
   request.sl = sl;
   request.tp = tp;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
      request.exit_comment="Exit by T/P level";
   else
      request.exit_comment="Exit by trailing S/L";
   if(!SendTradeRequest(request))
     {
      ENUM_HEDGE_ERR error=GetHedgeError();
      string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
      printf("Modify stop-loss or take-profit failed. Reason: "+EnumToString(error)+" "+logs);
      if(error==HEDGE_ERR_TASK_FAILED)
         PrintTaskLog();
      ResetHedgeError();
     }
   else break;
  }
...

Обратите внимание на анализ ошибок от этой функции.

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

Когда приказ не выполнен, бессмысленно анализировать лог его выполнения, достаточно получить код ошибки.

Однако если запрос составлен верно, но приказ все равно по каким-то причинам не был выполнен, будет получена ошибка HEDGE_ERR_TASK_FAILED. В этом случае необходимо проанализировать лог выполнения приказа с помощью перебора лога. Перебором лога занимается специальная функция PrintTaskLog():

//+------------------------------------------------------------------+
//| Print current error and reset it.                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> Action #"+(string)i+"; "+EnumToString(type)+"; RETCODE: "+(string)retcode);
     }
//---
  }

Благодаря ее сообщениям становиться возможным понять причину сбоя и исправить ее.

В заключении проиллюстрируем отображение эксперта Chaos2 и его позиций в HedgeTerminal в режиме реального времени. Эксперт запущен на минутном (М1) графике:

Рис. 5. Представление разнонаправленных позиций эксперта Chaos 2 в панели HedgeTerminal

Рис. 5. Представление разнонаправленных позиций эксперта "Chaos 2" в панели HedgeTerminal

Как видно, разнонаправленные позиции даже одного эксперта прекрасно уживаются друг с другом.


1.13. О так называемых "дублирующих символах" и виртуализации со стороны брокера

Сразу после появления MetaTrader 5 некоторые брокеры стали предоставлять так называемые дублирующие инструменты. Их котировки идентичны оригинальным инструментам, однако на конце, как правило, они имеют постфикс "_m" или "_1". Они были введены для того, чтобы трейдеры могли вести разнонаправленные позиции по фактически одинаковому инструменту.

Однако для алготрейдеров, использующих роботов в своей торговле, такие символы практически бесполезны. И вот почему. Представим, что нам потребовалось бы написать эксперт "Chaos II" без библиотеки HedgeTerminalAPI. В наличии у нас имелись бы несколько дублирующих инструментов. Как бы мы это сделали? Допустим, все продажи открывали на одном инструменте, например на EURUSD, а все покупки на другом, например EURUSD_m1.

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

На скриншоте выше показаны три позиции на продажу, но их может быть и больше. Уровни защитных остановок у этих позиций разные, поэтому объеденять их в одну нетто-позицию нельзя. Выход – открывать каждую позицию на новом дублирующим инструменте. Но таких инструментов может и не хватить, ведь только для одного робота нужны 6 дублей (по три на каждое направление сделки), а для двух роботов, запущенных на разных таймфреймах, их уже 12.

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

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

  • За каждый дублирующий инструмент вы платите из своего кармана в виде начисленных вам на счет отрицательных свопов. Ведь свопы при локе или частичном локе всегда отрицательны, а такое перекрытие позиций и происходит, когда вы занимаете две разнонаправленных позиции на двух разных инструментах.
  • Не все брокеры предоставляют дублирующие инструменты. Стратегия, написанная для брокера, предоставляющего дублирующие инструменты, не будет работать у брокера с одним инструментом. Если названия инструментов различаются, это тоже потенциальный источник проблем.
  • Создание дублирующего инструмента возможно не всегда. На прозрачных рынках со строгим регулированием, сделки, совершенные по инструменту, являются финансовым документом. На таких рынках нетто-позиция является стандартом де-факто. Создание своих символов на них трудно представить. Например, на бирже MOEX никогда не появится брокер, предоставляющий дублирующие инструменты. На рынках с менее строгим регулированием брокер может создавать для своего клиента любые инструменты.
  • Дублирующие инструменты неэффективны при торговле роботами. Причины их неэффективности были раскрыты выше на примере эксперта Chaos 2.

Дублирующий инструмент по своей сути является виртуализацией на стороне брокера. HedgeTerminal использует виртуализацию на стороне клиента.

И в первом, и во втором случае используется виртуализация как таковая. Она изменяет фактическое представление обязательств трейдера. При виртуализации одна позиция может превратиться в две. Когда она происходит на стороне клиента, нет никаких проблем, ведь клиент может представлять себе все что угодно. Однако когда такую виртуализацию дает брокер, то у регулирующих и лицензирующих организаций могут возникнуть вопросы по поводу того, как предоставляемая информация соотносится с фактической. Вторая сложность состоит в том, что придется иметь практически два API в одном: один набор функций и модификаторов для работы в netto-режиме, другой – для работы в разнонаправленном режиме.

Многие алготрейдеры нашли свой способ связывания сделок в единую позицию. Многие из этих способов работают и про них даже написаны статьи. Однако виртуализация позиций в реальности более сложная процедура, чем может показаться. В HedgeTerminal алгоритмы, так или иначе связанные с виртуализацией позиций, занимают около 20 000 строк исходного кода. А ведь функционал, реализованный в HedgeTerminal, является лишь базовым. Создавать подобный объем кода в своем эксперте лишь для сопровождения разнонаправленных позиций неоправданно ресурсоемкое занятие.


Глава 2. Документация к API HedgeTerminal

2.1. Функции для выбора транзакций

Функция TransactionsTotal()

Функция возвращает общее количество транзакций в списке транзакций. Функция является основой для организации перебора доступных транзакций (см. пример в разделе 1.4 и разделе 1.11 данной статьи).

int TransactionsTotal(ENUM_MODE_TRADES pool = MODE_TRADES);

Параметры

  • [in] pool=MODE_TRADES  – Указывает идентификатор источника данных для выбора. Может быть одним из значений перечисления ENUM_MODE_TRADES.

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

Возвращает общее количество транзакций в списке транзакций.


Функция TransactionType()

Функция возвращает тип выбранной транзакции.

ENUM_TRANS_TYPE TransactionType(void);

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

Тип возвращаемого значения. Значение может быть одним из значений перечисления ENUM_TRANS_TYPE.

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция TransactionSelect()

Функция выбирает транзакцию для дальнейшей работы с ней. Функция позволяет выбрать транзакцию по индексу или уникальному идентификатору транзакции в списке транзакций.

bool TransactionSelect(int index,
     ENUM_MODE_SELECT select = SELECT_BY_POS,
     ENUM_MODE_TRADES pool=MODE_TRADES
     );

Параметры

  • [in] index – Порядковый номер ордера в списке ордеров либо уникальный идентификатор транзакции в зависимости от параметра select.
  • [in] select=SELECT_BY_POS – Идентификатор типа параметра 'index'. Значение может быть одним из значений перечисления ENUM_MODE_SELECT.
  • [in] pool=MODE_TRADES – Указывает идентификатор источника данных для выбора. Может быть одним из значений перечисления ENUM_MODE_TRADES.

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

Возвращает true при успешном выборе транзакции и false в случае ошибки. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetHedgeError().

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".

Примечание

В случае выбора транзакции по ее индексу сложность операции соответствует O(1). В случае выбора транзакции по ее уникальному идентификатору, сложность операции асимптотически стремится к O(log2(n)).


Функция HedgeOrderSelect()

Функция выбирает один из ордеров, входящих в разнонаправленную позицию. Разнонаправленная позиция, в которую входит требуемый ордер, предварительно должна быть выбрана с помощью функции TransactionSelect().

bool HedgeOrderSelect(ENUM_HEDGE_ORDER_SELECTED_TYPE type);

Параметры

  • [in] type – Идентификатор ордера, который должен быть выбран. Значение может быть одним из значений перечисления ENUM_HEDGE_ORDER_SELECTED_TYPE.

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

Возвращает true при успешном выборе ордера и false в случае ошибки. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetHedgeError().

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция HedgeDealSelect()

Функция выбирает одну из сделок, исполнивших ордер. Ордер, в которую входит требуемая сделка, должен быть предварительно выбран функцией HedgeOrderSelect().

bool HedgeDealSelect(int index);

Параметры

  • [in] index – Индекс сделки, которая должна быть выбрана в списке исполнивших ордер сделок. Общее количество сделок, входящих в ордер, можно узнать, запросив соответствующее свойство ордера с помощью функции HedgeOrderGetInteger(), используя в качестве параметра модификатор ENUM_HEDGE_ORDER_PROP_INTEGER, равный значению HEDGE_ORDER_DEALS_TOTAL.

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

Возвращает true при успешном выборе сделки и false в случае ошибки. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetHedgeError().

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


2.2. Функции для получения свойств выбранной транзакции

Функция HedgePositionGetInteger()

Функция возвращает свойство выбранной разнонаправленной позиции. Свойство может быть типа int, long, datetime или bool, в зависимости от типа запрашиваемого свойства. Разнонаправленная позиция должна быть предварительно выбрана функцией TransactionSelect().

ulong HedgePositionGetInteger(ENUM_HEDGE_POSITION_PROP_INTEGER property);

Параметры

  • [in] property – Идентификатор свойства разнонаправленной позиции. Значение может быть одним из значений перечисления ENUM_HEDGE_DEAL_PROP_INTEGER.

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

Значение типа ulong. Для дальнейшей работы с ним необходимо его явное приведение к типу запрашиваемого свойства.

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция HedgePositionGetDouble()

Функция возвращает свойство выбранной разнонаправленной позиции. Возвращаемое свойство имеет тип double. Тип свойства указывается с помощью перечисления ENUM_HEDGE_POSITION_PROP_DOUBLE. Разнонаправленная позиция должна быть предварительно выбрана функцией TransactionSelect().

ulong HedgePositionGetDouble(ENUM_HEDGE_POSITION_PROP_DOUBLE property);

Параметры

  • [in] property – Идентификатор свойства разнонаправленной позиции. Значение может быть одним из значений перечисления ENUM_HEDGE_DEAL_PROP_DOUBLE.

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

Значение типа double.

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция HedgePositionGetString()

Функция возвращает свойство выбранной разнонаправленной позиции. Свойство имеет тип string. Тип свойства указывается с помощью перечисления ENUM_HEDGE_POSITION_PROP_STRING. Разнонаправленная позиция должна быть предварительно выбрана функцией TransactionSelect().

ulong HedgePositionGetString(ENUM_HEDGE_POSITION_PROP_STRING property);

Параметры

  • [in] property – Идентификатор свойства разнонаправленной позиции. Значение может быть одним из значений перечисления ENUM_HEDGE_POSITION_PROP_STRING.

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

Значение типа string.

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция HedgeOrderGetInteger()

Функция возвращает свойство выбранного ордера, входящего в разнонаправленную позицию. Свойство может быть типа int, long, datetime или bool. Тип свойства указывается с помощью перечисления ENUM_HEDGE_ORDER_PROP_INTEGER. Ордер должен быть предварительно выбран функцией HedgeOrderSelect().

ulong HedgeOrderGetInteger(ENUM_HEDGE_ORDER_PROP_INTEGER property);

Параметры

  • [in] property – Идентификатор свойства ордера, входящего в разнонаправленную позицию. Значение может быть одним из значений перечисления ENUM_HEDGE_ORDER_PROP_INTEGER.

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

Значение типа ulong. Для дальнейшей работы с ним, необходимо его явное приведение к типу запрашиваемого свойства.

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция HedgeOrderGetDouble()

Функция возвращает свойство выбранного ордера, входящего в разнонаправленную позицию. Запрашиваемое свойство имеет тип double. Тип свойства указывается с помощью перечисления ENUM_HEDGE_ORDER_PROP_DOUBLE. Ордер должен быть предварительно выбран функцией HedgeOrderSelect().

double HedgeOrderGetDouble(ENUM_HEDGE_ORDER_PROP_DOUBLE property);

Параметры

  • [in] property – Идентификатор свойства ордера, входящего в разнонаправленную позицию. Значение может быть одним из значений перечисления ENUM_HEDGE_ORDER_PROP_DOUBLE.

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

Значение типа double.

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция HedgeDealGetInteger()

Функция возвращает свойство выбранной сделки, входящей в исполненный ордер. Свойство может быть типа int, long, datetime или bool. Тип свойства указывается с помощью перечисления ENUM_HEDGE_DEAL_PROP_INTEGER. Сделка должна быть предварительно выбрана функцией HedgeDealSelect().

ulong HedgeOrderGetInteger(ENUM_HEDGE_DEAL_PROP_INTEGER property);

Параметры

  • [in] property – Идентификатор свойства выбранной сделки, входящей в исполненный ордер. Значение может быть одним из значений перечисления ENUM_HEDGE_DEAL_PROP_INTEGER.

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

Значение типа ulong. Для дальнейшей работы с ним необходимо его явное приведение к типу запрашиваемого свойства.

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция HedgeDealGetDouble()

Функция возвращает свойство выбранной сделки, входящей в исполненный ордер. Свойство может быть типа double. Тип свойства указывается с помощью перечисления ENUM_HEDGE_DEAL_PROP_DOUBLE. Сделка должна быть предварительно выбрана функцией HedgeDealSelect().

ulong HedgeOrderGetDouble(ENUM_HEDGE_DEAL_PROP_DOUBLE property);

Параметры

  • [in] property – Идентификатор свойства выбранной сделки входящей в исполненный ордер. Значение может быть одним из значений перечисления ENUM_HEDGE_DEAL_PROP_DOUBLE.

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

Значение типа double.

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


2.3. Функции для установки и получения свойств HedgeTerminal из экспертов

Функция HedgePropertySetInteger()

Функция устанавливает одно из свойств HedgeTerminal. Свойство может быть типа int, long, datetime или bool. Тип свойства указывается с помощью перечисления ENUM_HEDGE_PROP_INTEGER.

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, long value);

Параметры

  • [in] property – Идентификатор свойства, которое необходимо установить для HedgeTerminal. Значение может быть одним из значений перечисления ENUM_HEDGE_PROP_INTEGER.

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

Значение типа bool. Если свойство было установлено успешно, функция возвращает true, в противном случае возвращает false.

Пример использования

В примере с помощью функции устанавливается время блокировки позиции при отправки асинхронного запроса. Если ответ от сервера при отправке асинхронного запроса не придет в течении 30 секунд, заблокированная позиция будет разблокирована.

void SetTimeOut()
  {
   bool res=HedgePropertySetInteger(HEDGE_PROP_TIMEOUT,30);
   if(res)
      printf("The property is set successfully");
   else
      printf("Property is not set");
  }

Функция HedgePropertyGetInteger()

Функция возвращает одно из свойств HedgeTerminal. Свойство может быть типа int, long, datetime или bool. Тип свойства указывается с помощью перечисления ENUM_HEDGE_PROP_INTEGER.

long HedgePropertyGetInteger(ENUM_HEDGE_PROP_INTEGER property);

Параметры

  • [in] property – Идентификатор свойства, которое необходимо получить от HedgeTerminal. Значение может быть одним из значений перечисления ENUM_HEDGE_PROP_INTEGER.

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

Значение типа long.

Пример использования

Функция получает время блокировки позиции при отправки асинхронного запроса и выводит его в терминал:

void GetTimeOut()
  {
   int seconds=HedgePropertyGetInteger(HEDGE_PROP_TIMEOUT);
   printf("Timeout is "+(string) seconds);
  }

2.4. Функции для получения кодов ошибок и работы с ними

Функция GetHedgeError()

Функция возвращает идентификатор ошибки, которая была получена при совершении последнего действия. Идентификатор ошибки соответствует перечислению ENUM_HEDGE_ERR.

ENUM_HEDGE_ERR GetHedgeError(void);

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

Идентификатор ошибки. Значение может быть одним из значений перечисления ENUM_HEDGE_ERR.

Примечание

Функция GetHedgeError() после своего вызова не обнуляет идентификатор ошибки. Для принудительного сброса идентификатора ошибки необходимо использовать функцию ResetHedgeError().

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция ResetHedgeError()

Функция сбрасывает идентификатор последней полученной ошибки. После ее вызова идентификатор ENUM_HEDGE_ERR, возвращаемый функцией GetHedgeError(), будет равен HEDGE_ERR_NOT_ERROR.

void ResetHedgeError(void);

Пример использования

Пример использования функции см. в разделе 1.11 данной статьи: "Пример работы с свойствами разнонаправленных позиций на основе скрипта".


Функция TotalActionsTask()

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

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

uint TotalActionsTask(void);

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

Возвращает общее количество подзадач входящих в задачу.

Пример использования

Пример использования см. в разделе 1.6 данной статьи: "Подробный анализ торговых действий и идентификация ошибок при помощи функций TotalActionsTask и GetActionResult".


Функция GetActionResult()

Функция принимает индекс подзадачи входящий в задачу (см. функцию TotalActionTask()). Возвращает через ссылочные параметры тип подзадачи и результат ее выполнения. Тип подзадачи определен перечислением ENUM_TARGET_TYPE. Результат выполнения подзадачи соответствует кодам возврата торгового сервера MetaTrader 5.

void GetActionResult(uint index, ENUM_TARGET_TYPE& target_type, uint& retcode);

Параметры

  • [in] index – Порядковый номер подзадачи в списке подзадач.
  • [out] target_type – Тип подзадачи. Значение может быть одним из значений перечисления ENUM_TARGET_TYPE.
  • [out] retcode – Код возврата торгового сервера, полученный при выполнении подзадачи.

Пример использования

Пример использования см. в разделе 1.6 данной статьи: "Подробный анализ торговых действий и идентификация ошибок при помощи функций TotalActionsTask и GetActionResult".


2.5. Совершение торговых действий

Функция SendTradeRequest()

Функция отправляет запрос на изменение выбранной разнонаправленной позиции в HedgeTerminalAPI. Результатом исполнения функции является одно из трех действий:

  1. Закрытие позиции или части ее объема;
  2. Модификация уровней стоп-лосс и тейк-профит уровней;
  3. Модификация исходящего комментария.

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

bool SendTradeRequest(HedgeTradeRequest& request);

Параметры

[in] request – Структура запроса на изменение разнонаправленной позиции. Описание структуры и значение полей см. в описании структуры HedgeTradeRequest.

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

Возвращает true, если запрос на изменение позиции был выполнен удачно. Возвращает false в противном случае. В случае неудачного выполнения запроса для обнаружения сбоя и его причины используйте функции TotalActionsTask() и GetActionResult().

Примечание

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


Структура торгового запроса HedgeTradeRequest()

Закрытие и модификация разнонаправленных позиций в HedgeTerminal осуществляется через вызов функции SendTradeRequest(), в качестве аргумента которой используется торговый запрос. Запрос представлен специальной предопределенной структурой HedgeTradeRequest, которая содержит все поля, необходимые для закрытия или модификации выбранной позиции:

struct HedgeTradeRequest
  {
   ENUM_REQUEST_TYPE action;             // type of action
   double            volume;             // volume of position
   ENUM_CLOSE_TYPE   close_type;         // Marker of closing order
   double            sl;                 // stop-loss level
   double            tp;                 // take-profit level
   string            exit_comment;       // outgoing comment
   uint              retcode;            // last retcode in executed operation
   bool              asynch_mode;        // true if the closure is performed asynchronously, otherwise false
   ulong             deviation;          // deviation in step price
                     HedgeTradeRequest() // default params
     {
      action=REQUEST_CLOSE_POSITION;
      asynch_mode=false;
      volume=0.0;
      sl = 0.0;
      tp = 0.0;
      retcode=0;
      deviation=3;
     }
  };

Описание полей

ПолеОписание
 action Тип действия с позицией, который требуется совершить. Значение может быть одним из значений перечисления ENUM_REQUEST_TYPE
 volume Объем, который требуется закрыть. Может быть меньше объема текущей активной позиции. Если объем равен нулю, активная позиция будет закрыта полностью.
 sl Уровень стоп-лосс, который требуется установить для активной позиции.
 tp Уровень тейк-профит, который требуется установить для активной позиции.
 exit_comment  Исходящий комментарий для активной позиции.
 retcode Код результата последней выполненной операции.
 asynch_mode true, если используется асинхронный режим отправки запроса, в противном случае false.
 deviation Предельное отклонение от используемой цены.


2.6. Перечисления для работы с функциями по выбору транзакций

ENUM_TRANS_TYPE

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

Перечисление ENUM_TRANS_TYPE содержит тип каждой выбранной транзакции. Это перечисление возвращает функция TransactionType(). Поля перечисления и их описание представлены ниже:

ПолеОписание
 TRANS_NOT_DEFINED Транзакция не выбрана функцией TransactionSelect() или ее тип неопреден.
 TRANS_HEDGE_POSITION Транзакция является разнонаправленной позицией.
 TRANS_BROKERAGE_DEAL  Транзакция является брокерской сделкой (операцией на счете). Например, зачисление денежных средств или коррекция.
 TRANS_PENDING_ORDER Транзакция является отложенным ордером.
 TRANS_SWAP_POS Транзакция является свопом, начисленным на нетто-позицию.


ENUM_MODE_SELECT

Перечисление определяет тип параметра index, задаваемого в функции TransactionSelect().

ПолеОписание
 SELECT_BY_POS В параметре index передается порядковый номер транзакции в списке.
 SELECT_BY_TICKET В параметре index передается номер тикета.


ENUM_MODE_TRADES

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

ПолеОписание
 MODE_TRADES Транзакция выбирается среди активных транзакций.
 MODE_HISTORY Транзакция выбирается среди исторических транзакций.

2.7. Перечисления для работы с функциями по получению свойств транзакций

Перечисление ENUM_TRANS_DIRECTION

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

Это рыночное направление определено перечислением ENUM_TRANS_DIRECTION. Его поля и их описание представлены ниже.

ПолеОписание
 TRANS_NDEF Направление транзакции неопределенно. Например, брокерские сделки на счете не имеют рыночного направления и помечаются этим модификатором.
 TRANS_LONG Указывает, что торговая транзакция (ордер или разнонаправленная позиция) открыта на покупку.
 TRANS_SHORT  Указывает, что торговая транзакция (ордер или разнонаправленная позиция) открыта на продажу.


Перечисление ENUM_HEDGE_POSITION_STATUS

Перечисление содержит статус разнонаправленной позиции.

ПолеОписание
 HEDGE_POSITION_ACTIVE  Активная позиция. Активные позиции отображаются на вкладке Active панели HedgeTerminal.
 HEDGE_POSITION_HISTORY  Историческая позиция. Исторические позиции отображаются на вкладке History панели HedgeTerminal.


Перечисление ENUM_HEDGE_POSITION_STATE

Перечисление содержит состояние разнонаправленной позиции.

ПолеОписание
 POSITION_STATE_ACTIVE Выбранная позиция активна и ее можно изменить с помощью торгового запроса HedgeTradeRequest.
 POSITION_STATE_FROZEN  Выбранная позиция заблокирована и недоступна для изменений. При получении этого  модификатора следует дождаться разблокировки позиции.


Перечисление ENUM_HEDGE_POSITION_PROP_INTEGER

Перечисление задает тип свойства, возвращаемого функцией HedgePositionGetInteger().

ПолеОписание
 HEDGE_POSITION_ENTRY_TIME_SETUP_MSC Время установки ордера в миллисекундах с 01.01.1970, инициирующего  разнонаправленную позицию.
 HEDGE_POSITION_ENTRY_TIME_EXECUTED_MSC  Время исполнения ордера в миллисекундах с 01.01.1970,  инициирующего разнонаправленную позицию (время открытия позиции).
 HEDGE_POSITION_EXIT_TIME_SETUP_MSC Время установки ордера в миллисекундах с 01.01.1970, закрывающего  разнонаправленную позицию.
 HEDGE_POSITION_EXIT_TIME_EXECUTED_MSC Время исполнения ордера в миллисекундах с 01.01.1970 закрывающего  разнонаправленную позицию (время закрытия позиции).
 HEDGE_POSITION_TYPE Тип разнонаправленной позиции. Равен типу инициирующего ордера.  Содержит одно из значений системного перечисления ENUM_ORDER_TYPE.
 HEDGE_POSITION_DIRECTION Направление позиции. Определено перечислением ENUM_TRANS_DIRECTION.
 HEDGE_POSITION_MAGIC Магический номер эксперта, которому принадлежит выбранная позиция.  Нулевое значение указывает что позиция была открыта вручную.
 HEDGE_POSITION_CLOSE_TYPE Маркер ордера, закрывающего позицию. Определен перечислением ENUM_CLOSE_TYPE.
 HEDGE_POSITION_ID Идентификатор позиции. Равен идентификатору инициирующего ордера.
 HEDGE_POSITION_ENTRY_ORDER_ID Идентификатор инициирующего ордера.
 HEDGE_POSITION_EXIT_ORDER_ID Идентификатор закрывающего ордера для исторической позиции.
 HEDGE_POSITION_STATUS Статус позиции. Определяется перечислением  ENUM_HEDGE_POSITION_STATUS.
 HEDGE_POSITION_STATE Состояние позиции. Определяется перечислением ENUM_HEDGE_POSITION_STATE
 HEDGE_POSITION_USING_SL Флаг использования стоп-лосс уровня. Если стоп-лосс используется, то функция  HedgePositionGetInteger() вернет true, в противном случае false.
 HEDGE_POSITION_USING_TP Флаг использования тейк-профит уровня. Если тейк-профит  используется, HedgePositionGetInteger() вернет true, в противном случае false.
 HEDGE_POSITION_TASK_STATUS Статус задачи, которая выполняется для выбранной позиции. Позиция  может находится в процессе изменения. Для отслеживания изменений  этой позиции используется этот модификатор. Статус позиции определен перечислением ENUM_TASK_STATUS.
 HEDGE_POSITION_ACTIONS_TOTAL Возвращает общее количество подзадач, запущенных для изменения  данной позиции.

 

Перечисление ENUM_HEDGE_POSITION_PROP_DOUBLE

Перечисление задает тип свойства, возвращаемого функцией HedgePositionGetDouble().

ПолеОписание
 HEDGE_POSITION_VOLUME Объем разнонаправленной позиции.
 HEDGE_POSITION_PRICE_OPEN Средневзвешенная цена открытия позиции.
 HEDGE_POSITION_PRICE_CLOSED Средневзвешенная цена закрытия позиции.
 HEDGE_POSITION_PRICE_CURRENT Текущая цена активной позиции. Для исторической позиции этот модификатор вернет цену закрытия позиции.
 HEDGE_POSITION_SL Уровень стоп-лосс. Если стоп-лосс не используется равен нулю.
 HEDGE_POSITION_TP Уровень тейк-профит. Если тейк-профит не используется равен нулю.
 HEDGE_POSITION_COMMISSION Размер комиссии, который был заплачен для позиции.
 HEDGE_POSITION_SLIPPAGE Размер проскальзывания в пунктах.
 HEDGE_POSITION_PROFIT_CURRENCY  Размер прибыли или убытка данной позиции. Прибыль указывается в валюте депозита.
 HEDGE_POSITION_PROFIT_POINTS Размер прибыли или убытка данной позиции. Прибыль указывается в пунктах финансового инструмента, по которому открыта позиция.

Примечание

Размер проскальзывания HEDGE_POSITION_SLIPPAGE рассчитывается как разница в пунктах между лучшей сделкой входа в позицию и средневзвешенной ценой входа.

 

Перечисление ENUM_HEDGE_POSITION_PROP_STRING

Перечисление задает тип свойства, возвращаемого функцией HedgePositionGetString().

ПолеОписание
 HEDGE_POSITION_SYMBOL Символ, по которому открыта текущая позиция.
 HEDGE_POSITION_ENTRY_COMMENT Входящий комментарий позиции.
 HEDGE_POSITION_EXIT_COMMENT Исходящий комментарий позиции.

 

Перечисление ENUM_HEDGE_ORDER_STATUS

Перечисление содержит тип ордера.

ПолеОписание
 HEDGE_ORDER_PENDING  Ордер является отложенным и доступен на вкладке "Торговля" в MetaTrader 5.
 HEDGE_ORDER_HISTORY Ордер является историческим и доступен в истории ордеров MetaTrader 5.

Перечисление ENUM_HEDGE_ORDER_SELECTED_TYPE

Перечисление определяет тип выбираемого ордера функцией HedgeOrderSelect().

ПолеЗначение
 ORDER_SELECTED_INIT Ордер, инициирующий разнонаправленную позицию.
 ORDER_SELECTED_CLOSED  Ордер, закрывающий разнонаправленную позицию.
 ORDER_SELECTED_SL Ордер, выполняющий функцию уровня стоп-лосс.


Перечисление ENUM_HEDGE_ORDER_PROP_INTEGER

Перечисление задает тип свойства, возвращаемого функцией HedgeOrderGetInteger().

ПолеОписание
 HEDGE_ORDER_ID Уникальный идентификатор ордера.
 HEDGE_ORDER_STATUS Статус ордера. Значение может быть одним из значений перечисления ENUM_HEDGE_ORDER_STATUS.
 HEDGE_ORDER_DEALS_TOTAL Общее количество сделок, исполневших ордер. У отложенных ордеров значение равно нулю.
 HEDGE_ORDER_TIME_SETUP_MSC Время установки отложенного ордера в миллисекундах, прошедших с 01.01.1970.
 HEDGE_ORDER_TIME_EXECUTED_MSC Время исполнения исполненного ордера в миллисекундах, прошедших с 01.01.1970.
 HEDGE_ORDER_TIME_CANCELED_MSC Время отмены исполненного ордера в миллисекундах, прошедших с 01.01.1970.

Примечание

Время исполнения ордера HEDGE_ORDER_TIME_EXECUTED_MSC соответствует времени его последней сделки. 


Перечисление ENUM_HEDGE_ORDER_PROP_DOUBLE

Перечисление задает тип свойства, возвращаемого функцией HedgeOrderGetDouble().

ПолеОписание
 HEDGE_ORDER_VOLUME_SETUP Объем ордера, который был установлен при его установке.
 HEDGE_ORDER_VOLUME_EXECUTED Выполненный объем ордера. Если ордер отложенный, выполненный объем будет равен нулю.
 HEDGE_ORDER_VOLUME_REJECTED Объем ордера, который не удалось выполнить. Равен разнице между объемом при установке и выполненным объемом.
 HEDGE_ORDER_PRICE_SETUP Цена установки ордера.
 HEDGE_ORDER_PRICE_EXECUTED Средневзвешенная цена исполнения ордера.
 HEDGE_ORDER_COMMISSION Размер комиссионных, которые были уплачены брокеру за исполнение ордера. Указывается в валюте депозита.
 HEDGE_ORDER_SLIPPAGE Проскальзывание ордера.

Примечание

Размер проскальзывания HEDGE_ORDER_SLIPPAGE рассчитывается как разница в пунктах между лучшей исполненной сделкой и средневзвешенной ценой входа ордера.


Перечисление ENUM_HEDGE_DEAL_PROP_INTEGER

Перечисление задает тип свойства, возвращаемого функцией HedgeDealGetInteger().

ПолеОписание
 HEDGE_DEAL_ID Уникальный идентификатор сделки.
 HEDGE_DEAL_TIME_EXECUTED_MSC Время исполнения сделки в миллисекундах прошедших с 01.01.1970


Перечисление ENUM_HEDGE_DEAL_PROP_DOUBLE

Перечисление задает тип свойства, возвращаемого функцией HedgeDealGetDouble().

ПолеОписание
 HEDGE_DEAL_VOLUME_EXECUTED Объем сделки.
 HEDGE_DEAL_PRICE_EXECUTED Цена исполнения сделки.
 HEDGE_DEAL_COMMISSION Размер комиссионных, которые были уплачены брокеру за исполнение сделки.  Указывается в валюте депозита.

 

2.8. Перечисления для установки и получения свойств HedgeTerminal

Перечисление ENUM_HEDGE_PROP_INTEGER

Перечисление задает тип свойства, которое надо получить или установить у HedgeTerminal.

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


2.9. Перечисления для работы с функциями по работе с кодами ошибок

Перечисление ENUM_TASK_STATUS

Каждая разнонаправленная позиция может находится в процессе изменения. Изменением позиции занимается торговая задача.

Каждая запущенная торговая задача имеет свой статус исполнения, определенный в перечислении ENUM_TASK_STATUS. Его поля и их описание представлены ниже:

ПолеОписание
 TASK_STATUS_WAITING Текущей задачи нет, либо задача находится в процессе ожидания.
 TASK_STATUS_EXECUTING Торговая задача выполняемая для позиции находится в процессе выполнения.
 TASK_STATUS_COMPLETE Торговая задача для позиции завершена удачно.
 TASK_STATUS_FAILED Торговая задача для позиции завершена сбоем.


Перечисление ENUM_HEDGE_ERR

Перечисление содержит идентификатор ошибки, которую может вернуть функция GetHedgeError().

ПолеОписание
 HEDGE_ERR_NOT_ERROR Ошибка отсутствует.
 HEDGE_ERR_TASK_FAILED Задача для выбранной позиции завершилась неудачей.
 HEDGE_ERR_TRANS_NOTFIND Транзакция не найдена.
 HEDGE_ERR_WRONG_INDEX Неправильно указан индекс.
 HEDGE_ERR_WRONG_VOLUME Неправильно указан объем.
 HEDGE_ERR_TRANS_NOTSELECTED  Транзакция не была предварительно выбрана функцией TransactionSelect().
 HEDGE_ERR_WRONG_PARAMETER Неправильный один из передаваемых параметров.
 HEDGE_ERR_POS_FROZEN Разнонаправленная позиция находится в процессе изменения и недоступна для новых  изменений. Ожидайте разблокировки позиции.
 HEDGE_ERR_POS_NO_CHANGES Торговый запрос не содержит изменений.

 

Перечисление ENUM_TARGET_TYPE

Перечисление определяет тип подзадачи выбранной функцией GetActionResult().

ПолеОписание
 TARGET_NDEF Подзадача неопределенна.
 TARGET_CREATE_TASK Подзадача находится в процессе создания. Этот тип используется во внутренней  логике работы HedgeTerminalAPI.
 TARGET_DELETE_PENDING_ORDER Удаление отложенного ордера.
 TARGET_SET_PENDING_ORDER Установка отложенного ордера.
 TARGET_MODIFY_PENDING_ORDER  Модификация цены отложенного ордера.
 TARGET_TRADE_BY_MARKET Совершение торговых действий.


2.10. Перечисления для работы с функциями по работе с кодами ошибок

Перечисление ENUM_REQUEST_TYPE

Перечисление описывает действие, совершаемое с разнонаправленной позицией HedgeTerminal.

ПолеОписание
 REQUEST_CLOSE_POSITION Закрывает позицию. Если в поле volume структуры HedgeTradeRequest указан объем меньше текущего, будет закрыта только часть позиции. В этом случае часть закрытой позиции будет соответствовать значению поля volume.
 REQUEST_MODIFY_SLTP Устанавливает или изменяет существующие уровни стоп-лосс и тейк-профит.
 REQUEST_MODIFY_COMMENT Модифицирует исходящий комментарий активной позиции.


Перечисление ENUM_CLOSE_TYPE

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

  • Достижение позицией предельного уровня убытка или стоп лосс уровня;
  • Достижение позицией уровня прибыли или тейк профит уровня;
  • Закрытие позиции по рынку. Уровни стоп лосс и тейк профит не были установлены или достигнуты.
ПолеОписание
 CLOSE_AS_MARKET Указывает, что позиция закрыта по рынку. Уровни стоп лосс и тейк профит не были  установлены или достигнуты.
 CLOSE_AS_STOP_LOSS Указывает, что позиция закрыта по причине достижения стоп лосс уровня.
 CLOSE_AS_TAKE_PROFIT  Указывает, что позиция закрыта по причине достижения тейк профит уровня.

 

Глава 3. Основы асинхронных торговых операций

Тема асинхронных операций сложна и требует отдельной развернутой статьи. Однако ввиду того, что HedgeTerminal активно использует асинхронные операции, будет уместно вкратце рассказать об принципах организации советников, использующих этот тип отправки торговых запросов. К тому же, материалов по данной теме на текущий момент практически нет.

3.1. Организация и схема отправки синхронного торгового приказа

В MetaTrader 5 есть две функции отправки торговых приказов на сервер:

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

Причина, по которой запрос не прошел проверку, будет также указана в MqlTradeResult.

Схема выполнения потока пользовательской MQL5-программы при использовании функции OrderSend() показана ниже:

Рис. 6. Схема организации и схема отправки синхронного торгового запроса

Рис. 6. Схема организации и схема отправки синхронного торгового запроса.

На схеме видно, что поток MQL5-программы неотделим от общего системного потока по отправке запроса на сервер и совершения торговых операций на бирже.

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


3.2. Организация и схема отправки асинхронного торгового приказа

Иначе устроена функция OrderSendAsync(). Как и OrderSend(), она также принимает на вход торговый запрос MqlTradeRequest и также возвращает флаг, свидетельствующий о результате ее работы.

Однако, в отличии от первого примера, она не дожидается выполнения запроса торговым сервером и возвращает значения, полученные лишь от модуля базовой проверки значений торгового запроса (Basic verification inside the terminal). Схема выполнения пользовательского потока при использовании функции OrderSendAsync() показана ниже:

Рис. 7. Схема организации и схема отправки асинхронного торгового запроса.

Рис. 7. Схема организации и схема отправки асинхронного торгового запроса.

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

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

Напишем простой тестовый эксперт, иллюстрирующий сказанное выше:

//+------------------------------------------------------------------+
//|                                                    AsynchExp.mq5 |
//|                           Copyright 2014, Vasiliy Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
input bool UsingAsynchMode=true;
bool sendFlag=false;
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(sendFlag)return;
   printf("Formation of order and send to the server...");
   MqlTradeRequest request={0};
   request.magic=12345;
   request.symbol = Symbol();
   request.volume = 0.1;
   request.type=ORDER_TYPE_BUY;
   request.comment= "asynch test";
   request.action = TRADE_ACTION_DEAL;
   request.type_filling=ORDER_FILLING_FOK;
   MqlTradeResult result;
   uint tiks= GetTickCount();
   bool res = false;
   if(UsingAsynchMode)
      res=OrderSendAsync(request,result);
   else
      res=OrderSend(request,result);
   uint delta=GetTickCount()-tiks;
   if(OrderSendAsync(request,result))
     {
      printf("The order has been successfully"+
             "sent to the server.");
     }
  else
     {
     printf("The order is not shipped."+
             " Reason: "+(string)result.retcode);
     }
   printf("Time to send a trade request: "+(string)delta);
   sendFlag=true;
//---
  }

Убедимся, что эксперт работает. Запустим его с параметром UsingAsynchMode = false.

В этом случае им будет открыта длинная позиция объемом 0.1 лотом. Торговый запрос будет проведен синхронно с помощью функции OrderSend(). Лог после его запуска будет примерно следующим:

2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   Time to send a trade request: 94
2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   The order has been successfullysent to the server.
2014.11.06 17:49:28.345 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

Видно, что торговый запрос выполнился за 94 миллисекунды. Такое время говорит нам о том, что запрос прошел базовую проверку и был отправлен на сервер, после чего исполнился.

Теперь изменим код эксперта, изменив объем транзакции на максимально возможное значение DBL_MAX:

request.volume = DBL_MAX;

Очевидно, что это значение лежит за пределами реальных диапазонов. Попробуем выполнить этот запрос в синхронном режиме:

2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   The order is not shipped. Reason: 10014
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

Отправка запроса завершилось неудачей. Причина неудачи - ошибка 10014 (неправильный объем в запросе). Запрос не прошел базовую проверку и даже не был отправлен на сервер, о чем свидетельствует время выполнения запроса: 0 миллисекунд.

Снова изменим запрос. На этот раз выставим достаточно большой, но не экстремальный объем – 15 лотов. Для счета в 1000$, на котором тестируется этот эксперт, это слишком большой объем. Такая позиция открыта на нем быть не может.

Посмотрим, что вернет функция OrderSend():

2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   Time to send a trade request: 78
2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   The order is not shipped. Reason: 10019
2014.11.06 17:59:22.550 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

На этот раз ошибка другая: 10019 (Нет достаточных денежных средств для выполнения запроса, что безусловно верно). Обратите внимание на время выполнения запроса: 79 миллисекунд. Оно свидетельствует о том, что запрос был отправлен на сервер и именно сервер вернул ошибку, сигнализирующую о нехватке денежных средств.

Теперь отправим этот же запрос с объемом 15 лотов с помощью функции OrderSendAsync(). Как и в случае с OrderSend(), позиция не будет открыта, но давайте проанализируем лог:

2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   The order has been successfully sent to the server.
2014.11.06 18:03:58.104 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

Лог говорит нам, что ошибки нет! Так как ошибку 10019 диагностирует торговый сервер, то при асинхронной отправке ордера она недоступна текущему потоку. Возвращаемое значение лишь говорит о том, что запрос прошел базовую проверку. Для того чтобы получить фактическую ошибку 10019, нам необходимо проанализировать результат в новом пользовательском потоке в системной функции OnTradeTransaction(), которую необходимо добавить в наш эксперт:

void  OnTradeTransaction(const MqlTradeTransaction    &trans,
                         const MqlTradeRequest        &request,
                         const MqlTradeResult         &result)
  {
   uint delta = GetTickCount() - tiks;
   printf("Server answer: " + (string)result.retcode + "; Time: " + (string)delta);
  }

Запустим эксперт снова и выведем логи:

2014.11.06 18:17:00.943 AsynchExp (AUDCAD,H1)   Server answer: 10019; Time: 94
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   The order has been successfully sent to the server.
2014.11.06 18:17:00.851 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

Ошибка 10019 была получена, но не сразу после отправки, а уже в новом пользовательском потоке, запущенном в OnTradeTransaction().


3.3. Скорость выполнения асинхронных приказов

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

Оно базируется на том наблюдении, что функция OrderSendAsync() завершает свою работу, как правило, менее чем за 1 миллисекунду с момента своего запуска. В реальности, как было показано выше, фактическое время на совершение торговой транзакции необходимо измерять в момент прихода ответа от сервера внутри функций OnTradeTransaction() или OnTrade(). Такое измерение дает нам реальную скорость, которая при одиночном приказе равна скорости синхронного исполнения. Реальные преимущества во времени исполнения становятся заметны при групповой отправке транзакций. Ситуаций, когда требуется отправить сразу несколько запросов, по крайней мере, три:

  • Требуемое время между двумя последовательными запросами настолько мало, что нет возможности проверять результат выполнения запроса перед отправкой следующего. Следующий запрос отправляется в надежде на то, что предыдущий был выполнен. Подобные тактики применяются в HFT-торговле;
  • Требуется одновременно открыть сразу несколько позиций по нескольким инструментам. Например, для арбитражных стратегий и сложных композитных синтетических позиций требуется одновременное открытие позиций по разным инструментам по текущим ценам. Постепенное формирование позиций при таких тактиках нежелательно;
  • Требуется как можно быстрее завершить поток и перейти в режим ожидания следующих событий и команд пользователя. При многопоточных и инфраструктурных решениях это требование важно. В основном именно по этой причине HedgeTerminal использует асинхронные приказы. Если бы HT использовал синхронную отправку приказов, он постоянно подвисал бы на 1-2 секунды каждый раз, когда пользователь бы закрывал или изменял позицию, а это непримемлемо;

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

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

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

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

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

Количество заявокВремя, миллисекунд
5050
100180
2002100
5009000
100023000

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

 

Глава 4. Основы многопоточного программирования в среде MetaTrader

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

Именно к таким экспертам относится HedgeTerminal. Чтобы каждый эксперт, использующий библиотеку HedgeTerminalAPI, знал о действиях других экспертов, HT организует обмен данными через многопоточное чтение и запись файла ActivePositions.xml. Это нетривиальное и редко используемое решение в среде MQL-программистов. Поэтому мы создадим многопоточный эксперт, алгоритм работы которого близко напоминает алгоритмы HedgeTerminal. Это поможет лучше разобраться в многопоточном программировании и тем самым лучше понять, как работает HedgeTerminal.


4.1. Многопоточное программирование на примере сборщика котировок UnitedExchangeQuotes

Изучать основы многопоточного программирования мы будем на конкретном примере: напишем сборщик котировок от разных провайдеров (брокеров).

Идея в следующем: допустим, у нас есть 6-7 брокеров, предоставляющих котировки по одному и тому же инструменту. Естественно, котировки у них могут незначительно различаться. Однако анализ этих различий открывает путь к арбитражным стратегиям. Кроме того, сравнение котировок в динамике поможет выявить лучшего и худшего поставщика. Так, если брокер постоянно дает лучшие цены, чем цены других брокеров, то возможно, торговать лучше идти именно к нему. Мы не будем задаваться практической ценностью полученных результатов, а лишь опишем механизм, с помощью которого эти результаты можно получить.

Вот скриншот эксперта, который мы должны написать к концу этой главы:

Рис. 8. Внешний вид сборщика котировок UnitedExhangesQuotes.

Рис. 8. Внешний вид сборщика котировок UnitedExhangesQuotes.

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

Каждая строка – это брокер, котирующий инструмент (в данном случае EURUSD). Ask и Bid, соответственно, это лучшее предложение и спрос этого брокера. На скриншоте видно, что котировки незначительно отличаются друг от друга. Разница между предложением текущего и другого брокера отображена в графе D-ASK (Delta Ask). Аналогично, разница между спросом отображена в графе D-BID (Delta Bid). Например, в момент скриншота видно, что самый выгодный Ask у провайдера "Alpari Limited", а самый дорогой у "Банк ВТБ 24".

У MQL-программ нет доступа к окружению других терминалов MetaTrader. Иными словами, если программа запущена на одном терминале, получить данные другого она не сможет. Однако, все программы MQL могут обмениваться данными через файлы, находящиеся в общем каталоге всех терминалов MetaTrader. Если какая-то программа запишет информацию, например, текущую котировку в файл, то MQL-программа из другого терминала сможет ее прочитать. Других средств без привлечения внешних DLL в MQL нет. Поэтому мы будем использовать этот способ.

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

Чтобы избавиться от подобных ошибок, или, по крайней мере, минимизировать вероятность их появления, мы разработаем четкий план.

Во-первых, всю информацию мы будем хранить в XML-формате. Этот формат пришел на смену более старым и неуклюжим ini-файлам. Кроме того, XML позволяет гибко развертывать из своих узлов сложные структуры данных, например, классы. Во-вторых, определимся с общим алгоритмом чтения и записи. Очевидно, что основных операций будет две: чтение данных и запись данных. В момент чтения и записи одной из MQL-программ, другие программы не могут получить доступ к этому файлу. Тем самым, ситуация, когда одна программа читает данные, а вторая их изменяет, невозможна. Однако, нужно учитывать, что получить доступ к данным будет возможно не всегда.

Создадим специальный класс CQuoteList, который будет содержать алгоритмы доступа к XML, а также данные по всем котировкам из этого файла.

Одна из функций этого класса будет называться TryGetHandle(), она будет пытаться получить доступ к файлу и возвращать его хэндл в случае удачи. Вот ее реализация:

int CQuoteList::TryGetHandle(void)
{
   int attempts = 10;
   int handle = INVALID_HANDLE;
   // We try to open 'attemps' times
   for(att = 0; att < attempts; att++)
   {
      handle = FileOpen("Quotes.xml", FILE_WRITE|FILE_READ|FILE_BIN|FILE_COMMON);
      if(handle == INVALID_HANDLE)
      {
         Sleep(15);
         continue;
      }
      break;
   }
   return handle;
}

Из ее устройства видно, что она делает несколько попыток открыть файл в комбинированном режиме чтения/записи. По умолчанию количество попыток равно десяти.

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

После того, как файл открыт, его дескриптор предается функции LoadQuotes(). Полный листинг этой функции, как и сам класс CQuoteList, доступен во вложении к статье. Мы же не будем полностью приводить содержимое функции, а лишь укажем на общую последовательность ее действий:

  1. С помощью функции TryGetHandle() открывается файл на чтение и запись;
  2. С помощью библиотеки XML Parser XML-документ загружается в память эксперта;
  3. На основе загруженного XML-документа формируется новый массив котировок, хранящий необходимую информацию;
  4. В созданном массиве находится котировка, принадлежащая текущему эксперту. Ее значения обновляются;
  5. Массив котировок преобразуется обратно в XML-документ. Содержимое открытого XML-файла заменяется этим XML-документом;
  6. XML-файл котировок закрывается.

Видно, что функция LoadQuotes() выполняет большую работу, однако в подавляющем большинстве случаев для ее работы требуется менее 1 миллисекунды.

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

Когда котировки загружены и находятся внутри класса, с ними можно работать так, как с другими данными в MetaTrader 5. Для этого в классе CQuotesList реализован специальный программный интерфейс, написанный в стиле MetaTrader 5.

Вызов функций и отрисовка данных происходит внутри блока OnTick(). Вот его содержимое:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
      return;
   if(!QuotesList.LoadQuotes())    
      return;   
   PrintQuote quote = {0};
   Panel.DrawAccess(QuotesList.CountAccess());
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   string brokerName = AccountInfoString(ACCOUNT_COMPANY);
   for(int i = 0; i < QuotesList.BrokersTotal(); i++)
   {
      if(!QuotesList.BrokerSelect(i))
         continue;
      if(!QuotesList.SymbolSelect(Symbol()))
         continue;
      quote.ask = QuotesList.QuoteInfoDouble(QUOTE_ASK);
      quote.bid = QuotesList.QuoteInfoDouble(QUOTE_BID);
      quote.delta_ask = ask - quote.ask;
      quote.delta_bid = quote.bid - bid;
      quote.broker_name = QuotesList.BrokerName();
      quote.index = i;
      Panel.DrawBroker(quote);
   }
  }

Примечательно, что исходный код примера без изменений одинаково работает как в MetaTrader 4, так и в MetaTrader 5!

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

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

 

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

Основными преимуществами являются:

  1. Гибкость. Можно хранить и загружать любые данные, даже целые классы;
  2. Относительная скорость. Время на полный цикл чтения и перезаписи почти всегда в пределах 1 миллисекунды, чего вполне достаточно на фоне относительно медленных торговых операций, на выполнение которых уходит 80 – 150 миллисекунд, а иногда даже больше;
  3. Базируется на штатных средствах языка MQL5 без вызова сторонних DLL.

Основным недостатком такого решения - это серьезная нагрузка на систему хранения данных. Когда котировка одна, а брокеров два, операций перезаписи файла относительно немного, однако при плотном потоке котировок и большом количестве брокеров/инструментов, количество перезаписей становится очень большим. Менее чем за час демонстрационный пример произвел более 90 000 перезаписей файла Quotes.xml. Эту статистику можно посмотреть в самом верху панели эксперта: "I/O Rewrite" показывает общее число перезаписей файла, "fps" показывает скорость между двумя последними перезаписями, а "Avrg" отображает среднюю скорость перезаписей в секунду.

Если вы используете для хранения файлов SSD или классический HDD накопитель, это будет негативно сказываться на его долголетии. Поэтому лучше при такой организации обмена данными использовать виртуальный RAM диск.

HedgeTerminal, в отличии от представленного примера, экономно обращается с файлом ActivePositions.xml, записывая в него лишь значимые изменения позиций, недоступные через глобальный контекст. Операций чтения/перезаписи в нем на порядки меньше чем в примере. Поэтому при его использовании специальные средства типа RAM дисков не требуются.


4.2. Области применения многопоточного взаимодействия между экспертами

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

  • Копировщики сделок. Любой копировщик сделок предполагает одновременный запуск как минимум двух экспертов, один из которых будет транслировать сделки, а другой их повторять. В этом случае необходимо организовать трансляцию и примем сделок через многопоточные операции чтения/записи общего файла данных;
  • Организация глобального обмена данными между экспертами, глобальные переменные. Штатные глобальные переменные в MetaTrader 5 доступны экспертам лишь на уровне одного терминала. Глобальная переменная, определенная в одном терминале, недоступна в другом. Однако, с помощью общих данных можно создать сложные глобальные переменные, которые могли бы быть доступны на уровне всех терминалов даже разных версий;
  • Арбитражные стратегии. Анализаторы котировок поставщиков ликвидности. Если разница между ценами различных провайдеров будет существенной, становиться возможным создать прибыльные арбитражные стратегии. Также благодаря анализаторам котировок можно накапливать статистику по лучшим ценам и объективно выявлять лучшего поставщика ликвидности.


Описание прикрепленных файлов

Кратко опишем файлы, прикрепленные к статье, а также процедуру их компиляции.

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

Этот файл необходимо разместить в папке C:\Program Files\MetaTrader 5\MQL5\Include, где "C:\Program Files\MetaTrader 5\" - название каталога, в котором установлен ваш терминал MetaTrader 5. После того как файл скопирован в нужный каталог, вы сможете ссылаться на него в своей MQL-программе. Это необходимо будет делать всякий раз, когда вам понадобится использовать библиотеку HedgeTerminalAPI в своей работе. Чтобы сослаться на файл Prototypes.mqh, необходимо в коде написать специальную директиву включения этого файла в свою программу:

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //...
   // Здесь находится содержание вашей программы.
   //...
  }

Эта директива в примере выше выделена желтым маркером и называется "#include <Ptototypes.mqh>". Теперь скрипт, приведенный выше, может ссылаться на функции библиотеки и использовать их функционал в своей работе.

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

Chaos2.mqh - исходный код торгового эксперта Chaos2, работа которого подробно описывается в разделе 1.12: "Пример работы с функцией SendTradeRequest и структурой HedgeTradeRequest на примере советника "Chaos II". Чтобы успешно скомпилировать этот код, необходимо чтобы файл прототипов функций Prototypes.mqh находился в соответствующей директории \Include, а библиотека HedgeTerminalAPI была размещена по адресу: C:\Program Files\MetaTrader 5\MQL5\Market\hedgeterminalapi.ex5. Где "C:\Program Files\MetaTrader 5\" - название каталога (папка данных терминала), в котором установлен ваш терминал MetaTrader 5.

Исходный код UnitedExchangeQuotes - специальный zip-архив (unitedexchangequotes.zip), содержащий одноименный проект, подробно описанный в главе 4: "Основы многопоточного программирования в среде MetaTrader". Этот архив содержит следующие файлы:

  • UnitedExchangeQuotes.mq5 - центральный файл одноименного советника. Должен находится в папке экспертов: \MetaTrader 5\MQL5\Experts. Этот файл необходимо непосредственно скомпилировать в редакторе MetaEditor.
  • MultiThreadXML.mqh - основной файл, содержащий алгоритмы многопоточного доступа к XML-файлу. Он организует передачу информации между независимыми потоками. Должен находиться в папке \MetaTrader 5\MQL5\Include. Алгоритмы в этом файле базируются на специальной библиотеке, разработанной пользователем ya-sha и доступной в CodeBase. Однако для работы в многопоточном режиме потребовалась ее незначительная модификация. Это модифицированная версия и находится в данном вложении. Она состоит из следующих файлов:
    • XmlBase.mqh;
    • XmlDocument.mqh;
    • XmlAttribute.mqh;
    • XmlElement.mqh.
    Эти файлы также необходимо расположить в папке \Include.
  • Panel.mqh - содержит класс панели, представленный в примере. Этот файл необходимо разместить в той же директории, что и файл UnitedEchangesQuotes.mqh, т.е. в директории \Experts.

Все файлы в данном архиве содержат относительные пути. Например, файл UnitedExchangeQuotes.mq5 располагается в папке \MQL5\Experts. Это значит, что его необходимо разместить в одноименном подкаталоге папки данных терминала MetaTrader 5, например в C:\Program Files\MetaTrader 5\MQL5\Experts\UnitedExchangeQuotes.mq5.


Заключение

Мы подробно рассмотрели вопросы работы с программным интерфейсом HedgeTerminal.

Было показано, что принципы работы с этой библиотекой весьма сильно напоминают принципы работы с API MetaTrader 4. Точно также как и в нем, перед началом работы с транзакцией (аналога понятия "ордера" в МetaТrader 4), ее необходимо выбрать с помощью функции TransactionSelect(). Транзакцией в Hedge Terminal, как правило, является разнонаправленная позиция. После того, как позиция выбрана, можно получить ее свойства или совершить над ней торговое действие, например, установить стоп-лосс или просто закрыть. Этот алгоритм действий практически идентичен алгоритму работы с ордерами в MetaTrader 4.

Помимо основной информации о количестве разнонаправленных позиций и их свойствах, HedgeTerminal позволяет получить доступ к значениям, которые напрямую в MetaTrader 5 недоступны и требуют сложного аналитического расчета. Например, можно узнать величину проскальзывания каждой разнонаправленной позиции, лишь запросив одно из ее свойств. Можно узнать количество сделок, которое принадлежит выбранной позиции. При этом все расчеты и необходимое сведение сделок выполняются "за кулисами" в момент запуска HedgeTerminal. Это удобно, т.к. торговому эксперту не требуется ничего рассчитывать. Вся необходимая информация уже рассчитана и доступна через простой и понятный API.

Благодаря тому, что API и панель HedgeTerminal имеют единые алгоритмы, становиться возможным единое представление данных. Следовательно, можно управлять экспертами из панели HedgeTerminal, а изменения, сделанные экспертом, будут отображаться в самой панели.


Прикрепленные файлы |
Prototypes.mqh (15.3 KB)
Chaos2.mq5 (23.56 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Kira27
Kira27 | 7 февр. 2021 в 15:36

Странно что эта статья не нашла отклик)

Kira27
Kira27 | 7 февр. 2021 в 15:37

Спасибо за неё!!!   Она круто написана!!!

Kira27
Kira27 | 7 февр. 2021 в 17:10
Bongioanni:
Смысл в пятом метатрейдере, если есть четвертый? В чём его преимущество?

А классы?  В 4 же их нельзя программировать?

Рецепты MQL5 - ОСО-ордера Рецепты MQL5 - ОСО-ордера
В торговле трейдер использует различные механизмы и взаимосвязи, в том числе и между ордерами. В данной статье предлагается решение по обработке ОСО-ордеров. При этом широко задействованы классы Стандартной библиотеки, а также создаются новые типы данных.
Разнонаправленная торговля и хеджирование позиций в MetaTrader 5 с помощью панели HedgeTerminal, часть 1 Разнонаправленная торговля и хеджирование позиций в MetaTrader 5 с помощью панели HedgeTerminal, часть 1
Статья описывает новый подход в вопросах хеджирования позиций и ставит точку в спорах между пользователями платформ MetaTrader 4 и MetaTrader 5 в этом вопросе. На примере простых схем и диаграмм, общедоступным языком рассказывается об алгоритмах, которые делают такое хеджирование надежным. Статья посвящена описанию новой панели - HedgeTerminal, которая, по сути, является полноценным торговым терминалом внутри самого терминала MetaTrader 5. С ее помощью, благодаря предлагаемой виртуализации торговли, можно управлять своими торговыми позициями так, как это принято в MetaTrader 4.
Изучаем класс CCanvas. Реализация прозрачности графических объектов Изучаем класс CCanvas. Реализация прозрачности графических объектов
Надоела угловатая графика скользящих средних? Вы хотите рисовать в терминале что-то более красивое, чем простой прямоугольник с заливкой? Рисовать красиво в терминале можно. Для этого есть класс для создания пользовательской графики - CCanvas. С помощью этого класса можно реализовать прозрачность, смешивать цвета и получать иллюзию прозрачности при помощи наложения и смешивания цвета.
Программируем режимы работы советника с помощью ООП Программируем режимы работы советника с помощью ООП
В статье рассматривается идея мультирежимного программирования торговых роботов на MQL5. Используется объектно-ориентированный подход для реализации каждого из режимов. Приводится пример иерархии режимных классов и пример классов для тестирования. Предполагается, что мультирежимное программирование торговых роботов полностью учитывает особенности каждого режима работы MQL5-советника. Для идентификации режимов создаются функции и перечисление.