Функции для чтения свойств действующих ордеров

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

Целочисленные свойства можно прочитать с помощью функции OrderGetInteger, имеющей 2 формы: первая возвращает непосредственно значение свойства, вторая — логический признак успеха (true) или ошибки (false), а значением свойства заполняется переданный по ссылке второй параметр.

long OrderGetInteger(ENUM_ORDER_PROPERTY_INTEGER property)

bool OrderGetInteger(ENUM_ORDER_PROPERTY_INTEGER property, long &value)

Обе функции позволяют получить запрошенное свойство ордера совместимого с целым типа (datetime, long/ulong или перечисление). Хотя в прототипе упоминается long, с технической точки зрения значение хранится как 8-байтовая ячейка, которую можно приводить к совместимым типам без какой-либо конвертации внутреннего представления, в  частности, к ulong, который используется для всех тикетов.

Аналогичная пара функций предназначена для свойств вещественного типа double.

double OrderGetDouble(ENUM_ORDER_PROPERTY_DOUBLE property)

bool OrderGetDouble(ENUM_ORDER_PROPERTY_DOUBLE property, double &value)

Наконец, строковые свойства доступны посредством пары функций OrderGetString.

string OrderGetString(ENUM_ORDER_PROPERTY_STRING property)

bool OrderGetString(ENUM_ORDER_PROPERTY_STRING property, string &value)

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

Напомним, что перед вызовом любой из предыдущих функций необходимо предварительно выделить ордер с помощью OrderSelect или OrderGetTicket.

Для чтения всех свойств конкретного ордера разработаем класс OrderMonitor (OrderMonitor.mqh), действующий по такому же принципу, как уже знакомые нам мониторы символов (SymbolMonitor.mqh) или торгового счета (AccountMonitor.mqh).

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

Несколько забегая вперед, скажем, что сделки и позиции имеют такую же группировку свойств по трём основным типам значений, и для них нам также потребуется реализовать мониторы. В связи с этим имеет смысл выделить общий алгоритм в базовый абстрактный класс MonitorInterface (TradeBaseMonitor.mqh). Он является шаблоном, три параметра которого предназначены для указания типов конкретных перечислений: для целочисленных (I), вещественных (D) и строковых (S) групп свойств.

#include <MQL5Book/EnumToArray.mqh>
   
template<typename I,typename D,typename S>
class MonitorInterface
{
protected:
   bool ready;
public:
   MonitorInterface(): ready(false) { }
   
   bool isReady() const
   {
      return ready;
   }
   ...

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

Несколько чисто виртуальных методов декларируют доступ к свойствам соответствующих типов.

   virtual long get(const I propertyconst = 0;
   virtual double get(const D propertyconst = 0;
   virtual string get(const S propertyconst = 0;
   virtual long get(const int propertyconst longconst = 0;
   virtual double get(const int propertyconst doubleconst = 0;
   virtual string get(const int propertyconst stringconst = 0;
   ...

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

Такой подход позволяет получать свойства, руководствуясь различной исходной информацией, доступной в вызывающем коде. Далее мы создадим классы на основе OrderMonitor (а также будущих DealMonitor и PositionMonitor) для отбора объектов по набору произвольных условий, и там все эти методы будут востребованы.

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

   virtual string stringify(const long vconst I propertyconst = 0;
   
   virtual string stringify(const I propertyconst
   {
      return stringify(get(property), property);
   }
   
   virtual string stringify(const D propertyconst string format = NULLconst
   {
      if(format == NULLreturn (string)get(property);
      return StringFormat(formatget(property));
   }
   
   virtual string stringify(const S propertyconst
   {
      return get(property);
   }
   ...

Единственный из этих методов, который не получил реализации, это первый вариант stringify для типа long. Это связано с тем, что в группе целочисленных свойств, как мы видели в предыдущем разделе, перемешаны свойства фактически разных прикладных типов — дата и время, перечисления, целые числа. Поэтому их преобразование в понятные строки смогут предоставить только производные классы. Данная ситуация является общей для всех торговых сущностей — не только ордеров, но и сделок, и позиций, свойства которых мы рассмотрим позднее.

Когда в целочисленном свойстве содержится элемент некоего перечисления (например, ENUM_ORDER_TYPE, ORDER_TYPE_FILLING и т.д.), для его преобразования в строку следует применять функцию EnumToString. Эту задачу решает вспомогательный метод enumstr — скоро мы увидим его широкое использования в классах конкретных мониторов, начиная с OrderMonitor через пару абзацев.

   template<typename E>
   static string enumstr(const long v)
   {
      return EnumToString((E)v);
   }

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

   template<typename E>
   void list2log() const
   {
      E e = (E)0// подавляем предупреждение 'possible use of uninitialized variable'
      int array[];
      const int n = EnumToArray(earray0USHORT_MAX);
      Print(typename(E), " Count="n);
      for(int i = 0i < n; ++i)
      {
         e = (E)array[i];
         PrintFormat("% 3d %s=%s"iEnumToString(e), stringify(e));
      }
   }

Наконец, для упрощения вывода в журнал свойств всех трех групп предоставлен метод print, вызывающий list2log трижды — для каждой группы свойств.

   virtual void print() const
   {
      if(!readyreturn;
      
      Print(typename(this));
      list2log<I>();
      list2log<D>();
      list2log<S>();
   }

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

class OrderMonitorInterface:
   public MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,
   ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
{
public:
   // описание свойств согласно подтипам
   virtual string stringify(const long v,
      const ENUM_ORDER_PROPERTY_INTEGER propertyconst override
   {
      switch(property)
      {
         case ORDER_TYPE:
            return enumstr<ENUM_ORDER_TYPE>(v);
         case ORDER_STATE:
            return enumstr<ENUM_ORDER_STATE>(v);
         case ORDER_TYPE_FILLING:
            return enumstr<ENUM_ORDER_TYPE_FILLING>(v);
         case ORDER_TYPE_TIME:
            return enumstr<ENUM_ORDER_TYPE_TIME>(v);
         case ORDER_REASON:
            return enumstr<ENUM_ORDER_REASON>(v);
         
         case ORDER_TIME_SETUP:
         case ORDER_TIME_EXPIRATION:
         case ORDER_TIME_DONE:
            return TimeToString(vTIME_DATE TIME_SECONDS);
         
         case ORDER_TIME_SETUP_MSC:
         case ORDER_TIME_DONE_MSC:
            return STR_TIME_MSC(v);
      }
      
      return (string)v;
   }
};

Макрос STR_TIME_MSC для отображения времени с учетом миллисекунд определен следующим образом:

#define STR_TIME_MSC(T) (TimeToString((T) / 1000TIME_DATE | TIME_SECONDS) \
    + StringFormat("'%03d", (T) % 1000))

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

class OrderMonitorpublic OrderMonitorInterface
{
public:
   const ulong ticket;
   OrderMonitor(const long t): ticket(t)
   {
      if(!OrderSelect(ticket))
      {
         PrintFormat("Error: OrderSelect(%lld) failed: %s",
            ticketE2S(_LastError));
      }
      else
      {
         ready = true;
      }
   }
   ...

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

   virtual long get(const ENUM_ORDER_PROPERTY_INTEGER propertyconst override
   {
      return OrderGetInteger(property);
   }
   
   virtual double get(const ENUM_ORDER_PROPERTY_DOUBLE propertyconst override
   {
      return OrderGetDouble(property);
   }
   
   virtual string get(const ENUM_ORDER_PROPERTY_STRING propertyconst override
   {
      return OrderGetString(property);
   }
   
   virtual long get(const int propertyconst longconst override
   {
      return OrderGetInteger((ENUM_ORDER_PROPERTY_INTEGER)property);
   }
   
   virtual double get(const int propertyconst doubleconst override
   {
      return OrderGetDouble((ENUM_ORDER_PROPERTY_DOUBLE)property);
   }
   
   virtual string get(const int propertyconst string)  const override
   {
      return OrderGetString((ENUM_ORDER_PROPERTY_STRING)property);
   }
};

Данный фрагмент кода приведен с некоторыми сокращениями: из него убраны операторы для работы с ордерами в истории. После того как мы познакомимся с этим аспектом в последующих разделах, OrderMonitor будет показан в полном виде.

Важно отметить, что объект монитора не хранит в себе копии свойств. Поэтому доступ к методам get должен осуществляться сразу после создания объекта и, соответственно, вызова OrderSelect. Для чтения свойств в более поздний период потребуется вновь выделить ордер во внутреннем кеше MQL-программы, например, вызвав метод refresh.

   void refresh()
   {
      ready = OrderSelect(ticket);
   }

Протестируем работу OrderMonitor, добавив его в эксперт MarketOrderSend.mq5. Новая версия с именем MarketOrderSendMonitor.mq5 подключает файл OrderMonitor.mqh директивой #include, а в теле функции OnTimer (в блоке успешного подтверждения открытия позиции по ордеру) создает объект монитора и вызывает его метод print.

#include <MQL5Book/OrderMonitor.mqh>
...
void OnTimer()
{
   ...
   const ulong order = (wantToBuy ?
      request.buy(volumePrice) :
      request.sell(volumePrice));
   if(order != 0)
   {
      Print("OK Order: #="order);
      if(request.completed())
      {
         Print("OK Position: P="request.result.position);
         
         OrderMonitor m(order);
         m.print();
         ...
      }
   }
}

В журнале мы должны увидеть новые строки, содержащие все свойства ордера.

OK Order: #=1287846602
Waiting for position for deal D=1270417032
OK Position: P=1287846602
MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER, »
   » ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
ENUM_ORDER_PROPERTY_INTEGER Count=14
  0 ORDER_TIME_SETUP=2022.03.21 13:28:59
  1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00
  2 ORDER_TIME_DONE=2022.03.21 13:28:59
  3 ORDER_TYPE=ORDER_TYPE_BUY
  4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK
  5 ORDER_TYPE_TIME=ORDER_TIME_GTC
  6 ORDER_STATE=ORDER_STATE_FILLED
  7 ORDER_MAGIC=1234567890
  8 ORDER_POSITION_ID=1287846602
  9 ORDER_TIME_SETUP_MSC=2022.03.21 13:28:59'572
 10 ORDER_TIME_DONE_MSC=2022.03.21 13:28:59'572
 11 ORDER_POSITION_BY_ID=0
 12 ORDER_TICKET=1287846602
 13 ORDER_REASON=ORDER_REASON_EXPERT
ENUM_ORDER_PROPERTY_DOUBLE Count=7
  0 ORDER_VOLUME_INITIAL=0.01
  1 ORDER_VOLUME_CURRENT=0.0
  2 ORDER_PRICE_OPEN=1.10275
  3 ORDER_PRICE_CURRENT=1.10275
  4 ORDER_PRICE_STOPLIMIT=0.0
  5 ORDER_SL=0.0
  6 ORDER_TP=0.0
ENUM_ORDER_PROPERTY_STRING Count=3
  0 ORDER_SYMBOL=EURUSD
  1 ORDER_COMMENT=
  2 ORDER_EXTERNAL_ID=
TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, »
   » @ 1.10275, P=1287846602, M=1234567890
DONE, D=1270417032, #=1287846602, V=0.01, @ 1.10275, Bid=1.10275, Ask=1.10275, »
   » Request executed, Req=3

В четвертой строке начинается вывод из метода print, который включает полное название объекта монитора MonitorInterface вместе с типами-параметрами (в данном случае, тройка ENUM_ORDER_PROPERTY) и далее все свойства конкретного ордера.

Однако печать свойств — не самое интересное действие, которое может обеспечить монитор. Гораздо более востребованной в экспертах является задача отбора ордеров по условиям (значениям произвольных свойств). Используя монитор как вспомогательный инструмент, создадим механизм фильтрации ордеров, по аналогии с тем, что мы делали для символов: SymbolFilter.mqh.