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

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

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

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

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

Для целочисленных и совместимых типов (datetime, перечисления) свойств выделена функция HistoryOrderGetInteger.

long HistoryOrderGetInteger(ulong ticket, ENUM_ORDER_PROPERTY_INTEGER property)

bool HistoryOrderGetInteger(ulong ticket, ENUM_ORDER_PROPERTY_INTEGER property,
  long &value)

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

Для вещественных свойств предназначена функция HistoryOrderGetDouble.

double HistoryOrderGetDouble(ulong ticket, ENUM_ORDER_PROPERTY_DOUBLE property)

bool HistoryOrderGetDouble(ulong ticket, ENUM_ORDER_PROPERTY_DOUBLE property,
  double &value)

Наконец строковые свойства можно прочитать с помощью HistoryOrderGetString.

string HistoryOrderGetString(ulong ticket, ENUM_ORDER_PROPERTY_STRING property)

bool HistoryOrderGetString(ulong ticket, ENUM_ORDER_PROPERTY_STRING property,
  string &value)

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

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

Вызов функции ResetLastError в успешной ветке if нужен для сброса ошибки, которую могла взвести функция OrderSelect (если ордер в истории).

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

Для чтения свойств в get-методах мы теперь должны вызывать разные встроенные функции, в зависимости от значения переменной history.

   virtual long get(const ENUM_ORDER_PROPERTY_INTEGER propertyconst override

   {

      return history ? HistoryOrderGetInteger(ticketproperty) : OrderGetInteger(property);

   }

   

   virtual double get(const ENUM_ORDER_PROPERTY_DOUBLE propertyconst override

   {

      return history ? HistoryOrderGetDouble(ticketproperty) : OrderGetDouble(property);

   }

   

   virtual string get(const ENUM_ORDER_PROPERTY_STRING propertyconst override

   {

      return history ? HistoryOrderGetString(ticketproperty) : OrderGetString(property);

   }

   ...

Основное предназначение класса OrderMonitor — снабжать данными другие аналитические классы. Напомним, что объекты OrderMonitor используются для фильтрации активных ордеров в классе OrderFilter, и нам потребуется аналогичный класс для выборки ордеров по произвольным условиям на истории — HistoryOrderFilter.

Напишем этот класс в том же файле — OrderFilter.mqh. Вполне логично в нем применены две новых функции для работы на истории: HistoryOrdersTotal и HistoryOrderGetTicket.

class HistoryOrderFilterpublic TradeFilter<OrderMonitor,
   ENUM_ORDER_PROPERTY_INTEGER,
   ENUM_ORDER_PROPERTY_DOUBLE,
   ENUM_ORDER_PROPERTY_STRING>
{
protected:
   virtual int total() const override
   {
      return HistoryOrdersTotal();
   }
   virtual ulong get(const int iconst override
   {
      return HistoryOrderGetTicket(i);
   }
};

Этот простой код наследуется от шаблонного класса TradeFilter, куда в качестве первого параметра шаблона передается класс OrderMonitor для чтения свойств соответствующих объектов (мы видели аналог для позиций, и скоро создадим для сделок).

Здесь и кроется проблема с конструктором OrderMonitor. Как мы узнали в разделе Выборка ордеров и сделок из истории, для анализа счета мы должны предварительно настроить контекст с помощью одной из функций, таких как HistorySelect. Поэтому здесь в исходном коде HistoryOrderFilter предполагается, что MQL-программа уже выделила нужный фрагмент истории. Однако в новом, промежуточном варианте конструктора OrderMonitor используется вызов HistoryOrderSelect для проверки существования тикета на истории. Между тем эта функция сбрасывает предыдущий контекст исторических ордеров и выбирает единственный ордер.

Поэтому нам требуется вспомогательный метод historyOrderSelectWeak, который мог бы проверить тикет "мягким" способом, не нарушая существующий контекст. Для этого достаточно проверить равенство свойства ORDER_TICKET с самим переданным тикетом t: (HistoryOrderGetInteger(t, ORDER_TICKET) == t). Если такой тикет уже отобран (доступен), проверка завершится успешно, и монитору не нужно манипулировать историей.

class OrderMonitorpublic OrderMonitorInterface
{
   bool historyOrderSelectWeak(const ulong tconst
   {
      return (((HistoryOrderGetInteger(tORDER_TICKET) == t) ||
         (HistorySelect(0LONG_MAX) && (HistoryOrderGetInteger(tORDER_TICKET) == t))));
   }
   bool history;
   
public:
   const ulong ticket;
   OrderMonitor(const long t): ticket(t), history(!OrderSelect(t))
   {
      if(history && !historyOrderSelectWeak(ticket))
      {
         PrintFormat("Error: OrderSelect(%lld) failed: %s"ticketE2S(_LastError));
      }
      else
      {
         ResetLastError();
         ready = true;
      }
   }

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