English 中文 Español Deutsch 日本語 Português
preview
Торговый инструментарий MQL5 (Часть 4): Разработка EX5-библиотеки для управления историей

Торговый инструментарий MQL5 (Часть 4): Разработка EX5-библиотеки для управления историей

MetaTrader 5Примеры |
356 0
Wanateki Solutions LTD
Kelvin Muturi Muigua

Введение

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

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

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

Для начала мы рассмотрим несколько важных вопросов, которые многим программистам MQL5, особенно новичкам в обработке истории сделок в MetaTrader 5, часто сложно понять.


Каков жизненный цикл сделки в MQL5?

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

Рыночный ордер

Прямой рыночный ордер — это запрос в режиме реального времени на покупку или продажу актива по текущей рыночной цене (Ask или Bid). Ранее мы рассмотрели, как обрабатывать эти ордера в первой и второй статьях при разработке библиотеки Positions Manager.

Рыночный ордер

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

Отложенный ордер

Отложенный ордер (BUY STOP, BUY LIMIT, SELL STOP, SELL LIMIT, BUY STOP LIMIT и SELL STOP LIMIT), напротив, представляет собой отложенный ордер, срабатывающий при достижении определенного уровня цены. Подробное руководство по обработке таких ордеров приведено в третьей статье серии, в которой мы разрабатываем библиотеку Pending Orders Manager.

Отложенный ордер

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


Как статус позиции может измениться в течение ее срока действия?

На протяжении всего срока существования позиции ее статус может меняться из-за различных факторов:

  • Частичное закрытие: Если часть позиции закрыта, в торговой истории будет зарегистрирована соответствующая сделка exit (out) deal (сделка выхода).
  • Разворот позиции: Транзакция close by также записывается как exit deal.
  • Полное закрытие: Когда вся позиция закрывается либо вручную, либо автоматически с помощью тейк-профита, стоп-лосса или стоп-аута (при маржин-колле), в торговой истории регистрируется окончательная сделка выхода.

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

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

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


В чем разница между позицией и сделкой в MQL5?

В MQL5 позиция представляет собой активную на данный момент сделку (position), которые вы в настоящее время держите на рынке. Она находится в открытом состоянии. Позиция может быть на покупку или продажу на конкретном символе. С другой стороны, сделка представляет собой завершенную транзакцию — полностью закрытую позицию. Активные открытые позиции и отложенные ордера отображаются в окне "Инструменты" на вкладке "Торговля".

Вкладка "Торговля" окна "Инструменты" в MetaTrader 5

Закрытые позиции , наряду с ордерами и сделками, отображаются на вкладке "История" окна "Инструменты".

Вкладка "История" панели инструментов MetaTrader 5

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

Вкладка "История" панели инструментов MetaTrader 5 - Выбор истории позиций

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


Создание файла исходного кода библиотеки History Manager (.mq5)

Откройте MetaEditor IDE и запустите Мастер MQL, выбрав "Создать" в меню. В Мастере выберите создание нового исходного файла библиотеки, который мы назовем HistoryManager.mq5. Этот файл станет основой для наших базовых функций, которые направлены на управление и анализ истории торговли на счете. При создании новой библиотеки HistoryManager.mq5, сохраните ее в папку Libraries\Toolkit, созданную в первой статье. Сохранив этот новый файл в том же каталоге, что и EX5-библиотеки Positions Manager и Pending Orders Manager, мы сможем поддерживать четкую и последовательную организационную структуру нашего проекта. Такой подход облегчит обнаружение и управление каждым компонентом по мере расширения нашего инструментария.

Каталог инструментов библиотеки Навигатора MetaEditor

Вот как выглядит наш недавно созданный исходный файл HistoryManager.mq5. Начнем с удаления комментариев в разделе My function под директивами property. Директивы свойств copyright и link в вашем файле могут отличаться, но это не повлияет на поведение или производительность кода. Вы можете настроить директивы copyright и link, добавив любую необходимую информацию, однако свойство library менять нельзя.

//+------------------------------------------------------------------+
//|                                               HistoryManager.mq5 |
//|                          Copyright 2024, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
#property library
#property copyright "Copyright 2024, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2) export
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+


Структуры данных, директивы препроцессора и глобальные переменные

Определим следующие компоненты в нашем только что созданном исходном файле библиотеки HistoryManager.mq5:

  • Директивы препроцессора: Помогут сортировать и запрашивать различные типы истории торговли.
  • Структуры данных: Будут хранить исторические данные по ордерам, сделкам, позициям и отложенным ордерам.
  • Глобальные динамические структурные массивы: Будут содержать все соответствующие исторические данные в библиотеке.

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

Директивы препроцессора

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

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

Организуем исторические данные по пяти основным категориям:

  1. История ордеров.
  2. История сделок.
  3. История позиций.
  4. История отложенных ордеров.
  5. Все исторические данные.

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

#define GET_ORDERS_HISTORY_DATA 1001
#define GET_DEALS_HISTORY_DATA 1002
#define GET_POSITIONS_HISTORY_DATA 1003
#define GET_PENDING_ORDERS_HISTORY_DATA 1004
#define GET_ALL_HISTORY_DATA 1005

Структуры данных

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

//- Data structure to store deal properties
struct DealData
  {
   ulong             ticket;
   ulong             magic;
   ENUM_DEAL_ENTRY   entry;
   ENUM_DEAL_TYPE    type;
   ENUM_DEAL_REASON  reason;
   ulong             positionId;
   ulong             order;
   string            symbol;
   string            comment;
   double            volume;
   double            price;
   datetime          time;
   double            tpPrice;
   double            slPrice;
   double            commission;
   double            swap;
   double            profit;
  };

//- Data structure to store order properties
struct OrderData
  {
   datetime                timeSetup;
   datetime                timeDone;
   datetime                expirationTime;
   ulong                   ticket;
   ulong                   magic;
   ENUM_ORDER_REASON       reason;
   ENUM_ORDER_TYPE         type;
   ENUM_ORDER_TYPE_FILLING typeFilling;
   ENUM_ORDER_STATE        state;
   ENUM_ORDER_TYPE_TIME    typeTime;
   ulong                   positionId;
   ulong                   positionById;
   string                  symbol;
   string                  comment;
   double                  volumeInitial;
   double                  priceOpen;
   double                  priceStopLimit;
   double                  tpPrice;
   double                  slPrice;
  };

//- Data structure to store closed position/trade properties
struct PositionData
  {
   ENUM_POSITION_TYPE type;
   ulong              ticket;
   ENUM_ORDER_TYPE    initiatingOrderType;
   ulong              positionId;
   bool               initiatedByPendingOrder;
   ulong              openingOrderTicket;
   ulong              openingDealTicket;
   ulong              closingDealTicket;
   string             symbol;
   double             volume;
   double             openPrice;
   double             closePrice;
   datetime           openTime;
   datetime           closeTime;
   long               duration;
   double             commission;
   double             swap;
   double             profit;
   double             tpPrice;
   double             slPrice;
   int                tpPips;
   int                slPips;
   int                pipProfit;
   double             netProfit;
   ulong              magic;
   string             comment;
  };

//- Data structure to store executed or canceled pending order properties
struct PendingOrderData
  {
   string                  symbol;
   ENUM_ORDER_TYPE         type;
   ENUM_ORDER_STATE        state;
   double                  priceOpen;
   double                  tpPrice;
   double                  slPrice;
   int                     tpPips;
   int                     slPips;
   ulong                   positionId;
   ulong                   ticket;
   datetime                timeSetup;
   datetime                expirationTime;
   datetime                timeDone;
   ENUM_ORDER_TYPE_TIME    typeTime;
   ulong                   magic;
   ENUM_ORDER_REASON       reason;
   ENUM_ORDER_TYPE_FILLING typeFilling;
   string                  comment;
   double                  volumeInitial;
   double                  priceStopLimit;
  };

Глобальные динамические структурные массивы

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

OrderData orderInfo[];
DealData dealInfo[];
PositionData positionInfo[];
PendingOrderData pendingOrderInfo[];


Функция получения исторических данных

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

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

GetHistoryDataFunction() принимает три входных параметра:

  1. Две переменные datetime, fromDateTime и toDateTime, указывающее начало и конец желаемого периода.
  2. Беззнаковое целое число dataToGet, соответствующее одной из предопределенных констант в верхней части файла.

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

bool GetHistoryData(datetime fromDateTime, datetime toDateTime, uint dataToGet)
  {
   return(true);

//-- Our function's code will go here

  }

Первой задачей нашей функции будет проверка корректности указанного диапазона дат. Так как тип данных datetime в MQL5 - по сути, целое число типа long, представляющее формат эпохи Unix (количество секунд, прошедших с 1 января 1970 года, 00:00:00 UTC), мы можем напрямую сравнить эти значения, чтобы убедиться в их правильности. Также обратите внимание, что при запросе исторических данных в MQL5 время рассчитывается на основе времени торгового сервера, а не вашего локального компьютера.

Чтобы проверить диапазон дат, убедимся, что значение fromDateTime меньше toDateTime. Если fromDateTime превышает или равно toDateTime, это указывает на недопустимый период, поскольку дата начала не может быть позже или равной дате окончания. Если указанный период не проходит проверку, возвращаем false и выходим из функции.

if(fromDateTime >= toDateTime)
     {
      //- Invalid time period selected
      Print("Invalid time period provided. Can't load history!");
      return(false);
     }

После проверки дат и периода сбросим кэш ошибок MQL5, чтобы обеспечить точность кодов ошибок в случае возникновения каких-либо проблем. Далее вызовем функцию HistorySelect() в пределах оператора if-else, передав проверенные значения datetime для извлечения истории сделок и ордеров для тестируемого периода. Так как HistorySelect() возвращает булево значение, она вернет true, если успешно найдет историю для обработки или false, если обнаружит ошибку или не сможет получить данные.

ResetLastError();
if(HistorySelect(fromDateTime, toDateTime)) //- History selected ok
  {
//-- Code to process the history data will go here
  }
else //- History selecting failed
  {
   Print("Selecting the history failed. Error code = ", GetLastError());
   return(false);
  }

В части else оператора if-else мы добавили код для регистрации сообщения, указывающего на то, что выбор истории не удался, вместе с кодом ошибки, перед выходом из функции и возвратом булева значения false. В части if мы используем оператор switch для вызова соответствующих функций для обработки загруженных данных истории торговли на основе значения dataToGet.

switch(dataToGet)
  {
   case GET_DEALS_HISTORY_DATA: //- Get and save only the deals history data
      SaveDealsData();
      break;

   case GET_ORDERS_HISTORY_DATA: //- Get and save only the orders history data
      SaveOrdersData();
      break;

   case GET_POSITIONS_HISTORY_DATA: //- Get and save only the positions history data
      SaveDealsData();  //- Needed to generate the positions history data
      SaveOrdersData(); //- Needed to generate the positions history data
      SavePositionsData();
      break;

   case GET_PENDING_ORDERS_HISTORY_DATA: //- Get and save only the pending orders history data
      SaveOrdersData(); //- Needed to generate the pending orders history data
      SavePendingOrdersData();
      break;

   case GET_ALL_HISTORY_DATA: //- Get and save all the history data
      SaveDealsData();
      SaveOrdersData();
      SavePositionsData();
      SavePendingOrdersData();
      break;

   default: //-- Unknown entry
      Print("-----------------------------------------------------------------------------------------");
      Print(__FUNCTION__, ": Can't fetch the historical data you need.");
      Print("*** Please specify the historical data you need in the (dataToGet) parameter.");
      break;
  }

Ниже приведен полный код GetHistoryDataFunction().

bool GetHistoryData(datetime fromDateTime, datetime toDateTime, uint dataToGet)
  {
//- Check if the provided period of dates are valid
   if(fromDateTime >= toDateTime)
     {
      //- Invalid time period selected
      Print("Invalid time period provided. Can't load history!");
      return(false);
     }

//- Reset last error and get the history
   ResetLastError();
   if(HistorySelect(fromDateTime, toDateTime)) //- History selected ok
     {
      //- Get the history data
      switch(dataToGet)
        {
         case GET_DEALS_HISTORY_DATA: //- Get and save only the deals history data
            SaveDealsData();
            break;

         case GET_ORDERS_HISTORY_DATA: //- Get and save only the orders history data
            SaveOrdersData();
            break;

         case GET_POSITIONS_HISTORY_DATA: //- Get and save only the positions history data
            SaveDealsData();  //- Needed to generate the positions history data
            SaveOrdersData(); //- Needed to generate the positions history data
            SavePositionsData();
            break;

         case GET_PENDING_ORDERS_HISTORY_DATA: //- Get and save only the pending orders history data
            SaveOrdersData(); //- Needed to generate the pending orders history data
            SavePendingOrdersData();
            break;

         case GET_ALL_HISTORY_DATA: //- Get and save all the history data
            SaveDealsData();
            SaveOrdersData();
            SavePositionsData();
            SavePendingOrdersData();
            break;

         default: //-- Unknown entry
            Print("-----------------------------------------------------------------------------------------");
            Print(__FUNCTION__, ": Can't fetch the historical data you need.");
            Print("*** Please specify the historical data you need in the (dataToGet) parameter.");
            break;
        }
     }
   else
     {
      Print(__FUNCTION__, ": Selecting the history failed. Error code = ", GetLastError());
      return(false);
     }
   return(true);
  }

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


Функция сохранения данных о сделках

Функция SaveDealsData() будет отвечать за извлечение и сохранение всей истории сделок, доступной в данный момент в кэше истории торговли за различные периоды, которые будут запрошены различными функциями в библиотеке. Он не возвращает никаких данных и не определен как экспортируемый, поскольку вызывается внутри библиотеки, а именно - из функции GetHistoryData(). Функция будет использовать стандартные функции MQL5 HistoryDealGet... для извлечения различных свойств сделки и сохранения их в динамическом массиве структуры данных dealInfo.

Для начала давайте создадим определение, или сигнатуру функции.

void SaveDealsData()
  {

//-- Our function's code will go here

  }

Так как SaveDealsData() вызывается в пределах функции GetHistoryData(), нет необходимости снова вызывать HistorySelect() перед обработкой торговой истории. Первый шаг в функции SaveDealsData() - проверка наличия истории сделок для обработки. Введем ее с помощью функции HistoryDealsTotal(), которая возвращает общее количество сделок, доступных в кэше истории. Для эффективности мы создадим целое число и назовем его totalDeals для хранения общей истории сделок и беззнакового dealTicket long-типа для хранения идентификаторов тикетов сделок.

int totalDeals = HistoryDealsTotal();
ulong dealTicket;

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

if(totalDeals > 0)
  {
//-- Code to process deal goes here
  }
else
  {
   Print(__FUNCTION__, ": No deals available to be processed, totalDeals = ", totalDeals);
  }

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

ArrayResize(dealInfo, totalDeals);

Затем мы пройдемся по сделкам в обратном порядке, начиная с самой последней, используя цикл for. Для каждой сделки мы используем функцию HistoryDealGetTicket() для извлечения уникального тикета. Если извлечение тикетов прошло успешно, мы извлечем и сохраним различные свойства сделки. Мы будем хранить каждое свойство в соответствующем поле в массиве dealInfo по индексу, соответствующему текущей итерации цикла.

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

for(int x = totalDeals - 1; x >= 0; x--)
  {
   ResetLastError();
   dealTicket = HistoryDealGetTicket(x);
   if(dealTicket > 0)
     {
      //- Deal ticket selected ok, we can now save the deals properties
      dealInfo[x].ticket = dealTicket;
      dealInfo[x].entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
      dealInfo[x].type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE);
      dealInfo[x].magic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
      dealInfo[x].positionId = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
      dealInfo[x].order = HistoryDealGetInteger(dealTicket, DEAL_ORDER);
      dealInfo[x].symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
      dealInfo[x].comment = HistoryDealGetString(dealTicket, DEAL_COMMENT);
      dealInfo[x].volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
      dealInfo[x].price = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
      dealInfo[x].time = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
      dealInfo[x].tpPrice = HistoryDealGetDouble(dealTicket, DEAL_TP);
      dealInfo[x].slPrice = HistoryDealGetDouble(dealTicket, DEAL_SL);
      dealInfo[x].commission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
      dealInfo[x].swap = HistoryDealGetDouble(dealTicket, DEAL_SWAP);
      dealInfo[x].reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
      dealInfo[x].profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
     }
   else
     {
      Print(
         __FUNCTION__, " HistoryDealGetTicket(", x, ") failed. (dealTicket = ", dealTicket,
         ") *** Error Code: ", GetLastError()
      );
     }
  }

Ниже приведен полный код функции SaveDealsData().

void SaveDealsData()
  {
//- Get the number of loaded history deals
   int totalDeals = HistoryDealsTotal();
   ulong dealTicket;
//-
//- Check if we have any deals to be worked on
   if(totalDeals > 0)
     {
      //- Resize the dynamic array that stores the deals
      ArrayResize(dealInfo, totalDeals);

      //- Let us loop through the deals and save them one by one
      for(int x = totalDeals - 1; x >= 0; x--)
        {
         ResetLastError();
         dealTicket = HistoryDealGetTicket(x);
         if(dealTicket > 0)
           {
            //- Deal ticket selected ok, we can now save the deals properties
            dealInfo[x].ticket = dealTicket;
            dealInfo[x].entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
            dealInfo[x].type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE);
            dealInfo[x].magic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
            dealInfo[x].positionId = HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
            dealInfo[x].order = HistoryDealGetInteger(dealTicket, DEAL_ORDER);
            dealInfo[x].symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
            dealInfo[x].comment = HistoryDealGetString(dealTicket, DEAL_COMMENT);
            dealInfo[x].volume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
            dealInfo[x].price = HistoryDealGetDouble(dealTicket, DEAL_PRICE);
            dealInfo[x].time = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
            dealInfo[x].tpPrice = HistoryDealGetDouble(dealTicket, DEAL_TP);
            dealInfo[x].slPrice = HistoryDealGetDouble(dealTicket, DEAL_SL);
            dealInfo[x].commission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
            dealInfo[x].swap = HistoryDealGetDouble(dealTicket, DEAL_SWAP);
            dealInfo[x].reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
            dealInfo[x].profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
           }
         else
           {
            Print(
               __FUNCTION__, " HistoryDealGetTicket(", x, ") failed. (dealTicket = ", dealTicket,
               ") *** Error Code: ", GetLastError()
            );
           }
        }
     }
   else
     {
      Print(__FUNCTION__, ": No deals available to be processed, totalDeals = ", totalDeals);
     }
  }


Функция отображения истории сделок

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

Начнем с определения функции PrintDealsHistory(). Функция требует два параметра: fromDateTime и toDateTime, которые представляют время начала и окончания периода, в котором мы хотим выполнить поиск. Функция извлечет сделки, которые были заключены в течение этого периода времени. Обратите внимание, что функция отмечена как export (экспортируемая), то есть ее можно вызывать из других программ или библиотек, что делает ее легко доступной для внешнего использования.

void PrintDealsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//-- Our function's code will go here
  }

Далее вызываем функцию GetHistoryData(), передающую fromDateTime, toDateTime и дополнительную константу GET_DEALS_HISTORY_DATA. Это указывает функции на необходимость извлечения соответствующих торговых данных между указанными начальным и конечным временем. Этот вызов функции гарантирует, что информация о сделке за желаемый период будет извлечена и сохранена в массиве dealInfo.

GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA);

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

int totalDeals = ArraySize(dealInfo);
if(totalDeals <= 0)
  {
   Print("");
   Print(__FUNCTION__, ": No deals history found for the specified period.");
   return; //-- Exit the function
  }

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

Print("");
Print(__FUNCTION__, "-------------------------------------------------------------------------------");
Print(
   "Found a total of ", totalDeals,
   " deals executed between (", fromDateTime, ") and (", toDateTime, ")."
);

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

for(int r = 0; r < totalDeals; r++)
  {
   Print("---------------------------------------------------------------------------------------------------");
   Print("Deal #", (r + 1));
   Print("Symbol: ", dealInfo[r].symbol);
   Print("Time Executed: ", dealInfo[r].time);
   Print("Ticket: ", dealInfo[r].ticket);
   Print("Position ID: ", dealInfo[r].positionId);
   Print("Order Ticket: ", dealInfo[r].order);
   Print("Type: ", EnumToString(dealInfo[r].type));
   Print("Entry: ", EnumToString(dealInfo[r].entry));
   Print("Reason: ", EnumToString(dealInfo[r].reason));
   Print("Volume: ", dealInfo[r].volume);
   Print("Price: ", dealInfo[r].price);
   Print("SL Price: ", dealInfo[r].slPrice);
   Print("TP Price: ", dealInfo[r].tpPrice);
   Print("Swap: ", dealInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Commission: ", dealInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Profit: ", dealInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
   Print("Comment: ", dealInfo[r].comment);
   Print("Magic: ", dealInfo[r].magic);
   Print("");
  }

Ниже приведен полный код функции PrintDealsHistory().

void PrintDealsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the deals history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA);
   int totalDeals = ArraySize(dealInfo);
   if(totalDeals <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No deals history found for the specified period.");
      return; //-- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalDeals,
      " deals executed between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalDeals; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Deal #", (r + 1));
      Print("Symbol: ", dealInfo[r].symbol);
      Print("Time Executed: ", dealInfo[r].time);
      Print("Ticket: ", dealInfo[r].ticket);
      Print("Position ID: ", dealInfo[r].positionId);
      Print("Order Ticket: ", dealInfo[r].order);
      Print("Type: ", EnumToString(dealInfo[r].type));
      Print("Entry: ", EnumToString(dealInfo[r].entry));
      Print("Reason: ", EnumToString(dealInfo[r].reason));
      Print("Volume: ", dealInfo[r].volume);
      Print("Price: ", dealInfo[r].price);
      Print("SL Price: ", dealInfo[r].slPrice);
      Print("TP Price: ", dealInfo[r].tpPrice);
      Print("Swap: ", dealInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Commission: ", dealInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Profit: ", dealInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Comment: ", dealInfo[r].comment);
      Print("Magic: ", dealInfo[r].magic);
      Print("");
     }
  }


Функция сохранения данных ордеров

Функция SaveOrdersData() будет отвечать за извлечение и сохранение исторических данных об ордерах, доступных в кэше истории торговли. функция обрабатывает заказы один за другим, извлекает их ключевые свойства с помощью функций MQL5 HistoryOrderGet... и сохраняет их в динамическом массиве orderInfo. Этот массив затем будет использоваться другими частями библиотеки для анализа и обработки данных по мере необходимости. Эта функция не возвращает никаких данных, не будет определена как экспортируемая, поскольку она используется внутри библиотеки, будет корректно обрабатывать ошибки и регистрировать любые проблемы для отладки.

Начнем с определения сигнатуры функции.

void SaveOrdersData()
  {
//-- Our function's code will go here
  }

Далее мы определяем, сколько исторических ордеров доступно. Это достигается с помощью функции HistoryOrdersTotal(), которая возвращает общее количество исторических ордеров в кэше. Результат сохраняется в переменной totalOrdersHistory. Кроме того, мы объявляем беззнаковую long-переменную orderTicket для хранения тикета каждого ордера по мере его обработки.

int totalOrdersHistory = HistoryOrdersTotal();
ulong orderTicket;

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

if(totalOrdersHistory > 0)
  {
   //-- Code to process orders goes here
  }
else
  {
   Print(__FUNCTION__, ": No order history available to be processed, totalOrdersHistory = ", totalOrdersHistory);
   return;
  }

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

ArrayResize(orderInfo, totalOrdersHistory);

Мы циклически просматриваем ордера в обратном порядке (начиная с самого последнего) с использованием цикла for. Для каждого ордера. Начинаем с получения тикета ордера с помощью функции HistoryOrderGetTicket(). Если извлечение тикета прошло успешно, извлекаем различные свойства ордера, используя функции HistoryOrderGet..., и сохраняем их в соответствующих полях массива orderInfo. Если извлечение тикета не удлось, функция регистрирует сообщение об ошибке вместе с кодом ошибки для отладки.

for(int x = totalOrdersHistory - 1; x >= 0; x--)
  {
   ResetLastError();
   orderTicket = HistoryOrderGetTicket(x);
   if(orderTicket > 0)
     {
      //- Order ticket selected ok, we can now save the order properties
      orderInfo[x].ticket = orderTicket;
      orderInfo[x].timeSetup = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_SETUP);
      orderInfo[x].timeDone = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_DONE);
      orderInfo[x].expirationTime = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_EXPIRATION);
      orderInfo[x].typeTime = (ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_TIME);
      orderInfo[x].magic = HistoryOrderGetInteger(orderTicket, ORDER_MAGIC);
      orderInfo[x].reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(orderTicket, ORDER_REASON);
      orderInfo[x].type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE);
      orderInfo[x].state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(orderTicket, ORDER_STATE);
      orderInfo[x].typeFilling = (ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_FILLING);
      orderInfo[x].positionId = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_ID);
      orderInfo[x].positionById = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_BY_ID);
      orderInfo[x].symbol = HistoryOrderGetString(orderTicket, ORDER_SYMBOL);
      orderInfo[x].comment = HistoryOrderGetString(orderTicket, ORDER_COMMENT);
      orderInfo[x].volumeInitial = HistoryOrderGetDouble(orderTicket, ORDER_VOLUME_INITIAL);
      orderInfo[x].priceOpen = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN);
      orderInfo[x].priceStopLimit = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_STOPLIMIT);
      orderInfo[x].tpPrice = HistoryOrderGetDouble(orderTicket, ORDER_TP);
      orderInfo[x].slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL);
     }
   else
     {
      Print(
         __FUNCTION__, " HistoryOrderGetTicket(", x, ") failed. (orderTicket = ", orderTicket,
         ") *** Error Code: ", GetLastError()
      );
     }
  }

После обработки всех ордеров функция корректно завершает работу. Вот полная реализация функции SaveOrdersData() в коде.

void SaveOrdersData()
  {
//- Get the number of loaded history orders
   int totalOrdersHistory = HistoryOrdersTotal();
   ulong orderTicket;
//-
//- Check if we have any orders in the history to be worked on
   if(totalOrdersHistory > 0)
     {
      //- Resize the dynamic array that stores the history orders
      ArrayResize(orderInfo, totalOrdersHistory);

      //- Let us loop through the order history and save them one by one
      for(int x = totalOrdersHistory - 1; x >= 0; x--)
        {
         ResetLastError();
         orderTicket = HistoryOrderGetTicket(x);
         if(orderTicket > 0)
           {
            //- Order ticket selected ok, we can now save the order properties
            orderInfo[x].ticket = orderTicket;
            orderInfo[x].timeSetup = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_SETUP);
            orderInfo[x].timeDone = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_DONE);
            orderInfo[x].expirationTime = (datetime)HistoryOrderGetInteger(orderTicket, ORDER_TIME_EXPIRATION);
            orderInfo[x].typeTime = (ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_TIME);
            orderInfo[x].magic = HistoryOrderGetInteger(orderTicket, ORDER_MAGIC);
            orderInfo[x].reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(orderTicket, ORDER_REASON);
            orderInfo[x].type = (ENUM_ORDER_TYPE)HistoryOrderGetInteger(orderTicket, ORDER_TYPE);
            orderInfo[x].state = (ENUM_ORDER_STATE)HistoryOrderGetInteger(orderTicket, ORDER_STATE);
            orderInfo[x].typeFilling = (ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(orderTicket, ORDER_TYPE_FILLING);
            orderInfo[x].positionId = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_ID);
            orderInfo[x].positionById = HistoryOrderGetInteger(orderTicket, ORDER_POSITION_BY_ID);
            orderInfo[x].symbol = HistoryOrderGetString(orderTicket, ORDER_SYMBOL);
            orderInfo[x].comment = HistoryOrderGetString(orderTicket, ORDER_COMMENT);
            orderInfo[x].volumeInitial = HistoryOrderGetDouble(orderTicket, ORDER_VOLUME_INITIAL);
            orderInfo[x].priceOpen = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_OPEN);
            orderInfo[x].priceStopLimit = HistoryOrderGetDouble(orderTicket, ORDER_PRICE_STOPLIMIT);
            orderInfo[x].tpPrice = HistoryOrderGetDouble(orderTicket, ORDER_TP);
            orderInfo[x].slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL);
           }
         else
           {
            Print(
               __FUNCTION__, " HistoryOrderGetTicket(", x, ") failed. (orderTicket = ", orderTicket,
               ") *** Error Code: ", GetLastError()
            );
           }
        }
     }
   else
     {
      Print(__FUNCTION__, ": No order history available to be processed, totalOrdersHistory = ", totalOrdersHistory);
     }
  }


Функция отображения истории ордеров

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

void PrintOrdersHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the orders history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_ORDERS_HISTORY_DATA);
   int totalOrders = ArraySize(orderInfo);
   if(totalOrders <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No orders history found for the specified period.");
      return; //-- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalOrders,
      " orders filled or cancelled between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalOrders; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Order #", (r + 1));
      Print("Symbol: ", orderInfo[r].symbol);
      Print("Time Setup: ", orderInfo[r].timeSetup);
      Print("Type: ", EnumToString(orderInfo[r].type));
      Print("Ticket: ", orderInfo[r].ticket);
      Print("Position ID: ", orderInfo[r].positionId);
      Print("State: ", EnumToString(orderInfo[r].state));
      Print("Type Filling: ", EnumToString(orderInfo[r].typeFilling));
      Print("Type Time: ", EnumToString(orderInfo[r].typeTime));
      Print("Reason: ", EnumToString(orderInfo[r].reason));
      Print("Volume Initial: ", orderInfo[r].volumeInitial);
      Print("Price Open: ", orderInfo[r].priceOpen);
      Print("Price Stop Limit: ", orderInfo[r].priceStopLimit);
      Print("SL Price: ", orderInfo[r].slPrice);
      Print("TP Price: ", orderInfo[r].tpPrice);
      Print("Time Done: ", orderInfo[r].timeDone);
      Print("Expiration Time: ", orderInfo[r].expirationTime);
      Print("Comment: ", orderInfo[r].comment);
      Print("Magic: ", orderInfo[r].magic);
      Print("");
     }
  }


Функция сохранения данных позиций

Функция SavePositionsData() организует историю сделок и ордеров для реконструкции жизненного цикла каждой позиции, играя центральную роль в создании истории позиций путем синтеза информации из доступных данных. В документации MQL5 вы заметите отсутствие стандартных функций (таких как HistoryPositionSelect() или HistoryPositionsTotal()) для прямого доступа к историческим данным о местоположении. Поэтому нам нужно создать пользовательскую функцию, которая объединяет данные об ордерах и сделках, используя Position ID как связующий ключ для связи с исходными ордерами.

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

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

void SavePositionsData()
  {
//-- Our function's code will go here
  }

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

int totalDealInfo = ArraySize(dealInfo);
ArrayResize(positionInfo, totalDealInfo);
int totalPositionsFound = 0, posIndex = 0;

Если в массиве dealInfo нет доступных сделок (то есть totalDealInfo == 0), мы выходим из функции раньше времени, поскольку нет данных для обработки.

if(totalDealInfo == 0)
  {
   return;
  }

Далее мы проходим по сделкам в обратном порядке (начиная с самой последней), чтобы мы могли сопоставить сделки выхода с соответствующими им сделками входа. Мы проверяем, является ли текущая сделка сделкой выхода, оценивая ее свойство entry. (dealInfo[x].entry == DEAL_ENTRY_OUT). Крайне важно начинать с поиска сделок выхода, поскольку это подтверждает, что позиция закрыта и больше не активна. Нам нужно регистрировать только закрытые позиции.

for(int x = totalDealInfo - 1; x >= 0; x--)
  {
   if(dealInfo[x].entry == DEAL_ENTRY_OUT)
     {
      // Process exit deal
     }
  }

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

for(int k = ArraySize(dealInfo) - 1; k >= 0; k--)
  {
   if(dealInfo[k].positionId == positionId)
     {
      if(dealInfo[k].entry == DEAL_ENTRY_IN)
        {
         exitDealFound = true;
         totalPositionsFound++;
         posIndex = totalPositionsFound - 1;

         // Save the entry deal data
         positionInfo[posIndex].openingDealTicket = dealInfo[k].ticket;
         positionInfo[posIndex].openTime = dealInfo[k].time;
         positionInfo[posIndex].openPrice = dealInfo[k].price;
         positionInfo[posIndex].volume = dealInfo[k].volume;
         positionInfo[posIndex].magic = dealInfo[k].magic;
         positionInfo[posIndex].comment = dealInfo[k].comment;
        }
     }
  }

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

if(exitDealFound)
  {
   if(dealInfo[x].type == DEAL_TYPE_BUY)
     {
      positionInfo[posIndex].type = POSITION_TYPE_SELL;
     }
   else
     {
      positionInfo[posIndex].type = POSITION_TYPE_BUY;
     }

   positionInfo[posIndex].positionId = dealInfo[x].positionId;
   positionInfo[posIndex].symbol = dealInfo[x].symbol;
   positionInfo[posIndex].profit = dealInfo[x].profit;
   positionInfo[posIndex].closingDealTicket = dealInfo[x].ticket;
   positionInfo[posIndex].closePrice = dealInfo[x].price;
   positionInfo[posIndex].closeTime = dealInfo[x].time;
   positionInfo[posIndex].swap = dealInfo[x].swap;
   positionInfo[posIndex].commission = dealInfo[x].commission;

   positionInfo[posIndex].duration = MathAbs((long)positionInfo[posIndex].closeTime -
                                     (long)positionInfo[posIndex].openTime);
   positionInfo[posIndex].netProfit = positionInfo[posIndex].profit + positionInfo[posIndex].swap -
                                      positionInfo[posIndex].commission;
  }

Для каждой позиции мы рассчитываем значения пипсов для уровней стоп-лосса (SL) и тейк-профита (TP) в зависимости от направления сделки - покупка или продажа. Мы используем значение точки символа для определения количества пипсов.

if(positionInfo[posIndex].type == POSITION_TYPE_BUY)
  {
// Calculate TP and SL pip values for buy position
   if(positionInfo[posIndex].tpPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].tpPips = int((positionInfo[posIndex].tpPrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
     }
   if(positionInfo[posIndex].slPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].slPips = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].slPrice) / symbolPoint);
     }
// Calculate pip profit for buy position
   double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
   positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].closePrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
  }
else
  {
// Calculate TP and SL pip values for sell position
   if(positionInfo[posIndex].tpPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].tpPips = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].tpPrice) / symbolPoint);
     }
   if(positionInfo[posIndex].slPrice > 0)
     {
      double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
      positionInfo[posIndex].slPips = int((positionInfo[posIndex].slPrice -
                                           positionInfo[posIndex].openPrice) / symbolPoint);
     }
// Calculate pip profit for sell position
   double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
   positionInfo[posIndex].pipProfit = int((positionInfo[posIndex].openPrice -
                                           positionInfo[posIndex].closePrice) / symbolPoint);
  }

Наконец, мы просматриваем массив orderInfo для поиска ордера, инициировавшего позицию. Сопоставляем POSITION_ID и проверяем, что ордер в состоянии ORDER_STATE_FILLED. Далее мы сохраняем тикет и тип открывающего ордера, что поможет определить, была ли позиция инициирована отложенным или прямым рыночным ордером.

for(int k = 0; k < ArraySize(orderInfo); k++)
  {
   if(
      orderInfo[k].positionId == positionInfo[posIndex].positionId &&
      orderInfo[k].state == ORDER_STATE_FILLED
   )
     {
      positionInfo[posIndex].openingOrderTicket = orderInfo[k].ticket;
      positionInfo[posIndex].ticket = positionInfo[posIndex].openingOrderTicket;

      //- Determine if the position was initiated by a pending order or direct market entry
      switch(orderInfo[k].type)
        {
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            positionInfo[posIndex].initiatedByPendingOrder = true;
            positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
            break;
         default:
            positionInfo[posIndex].initiatedByPendingOrder = false;
            positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
            break;
        }

      break; //- Exit the orderInfo loop once the required data is found
     }
  }

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

ArrayResize(positionInfo, totalPositionsFound);

Ниже представлена полная реализация функции SavePositionsData() со всеми сегментами кода.

void SavePositionsData()
  {
//- Since every transaction is recorded as a deal, we will begin by scanning the deals and link them
//- to different orders and generate the positions data using the POSITION_ID as the primary and foreign key
   int totalDealInfo = ArraySize(dealInfo);
   ArrayResize(positionInfo, totalDealInfo); //- Resize the position array to match the deals array
   int totalPositionsFound = 0, posIndex = 0;
   if(totalDealInfo == 0) //- Check if we have any deal history available for processing
     {
      return; //- No deal data to process found, we can't go on. exit the function
     }
//- Let us loop through the deals array
   for(int x = totalDealInfo - 1; x >= 0; x--)
     {
      //- First we check if it is an exit deal to close a position
      if(dealInfo[x].entry == DEAL_ENTRY_OUT)
        {
         //- We begin by saving the position id
         ulong positionId = dealInfo[x].positionId;
         bool exitDealFound = false;

         //- Now we check if we have an exit deal from this position and save it's properties
         for(int k = ArraySize(dealInfo) - 1; k >= 0; k--)
           {
            if(dealInfo[k].positionId == positionId)
              {
               if(dealInfo[k].entry == DEAL_ENTRY_IN)
                 {
                  exitDealFound = true;

                  totalPositionsFound++;
                  posIndex = totalPositionsFound - 1;

                  positionInfo[posIndex].openingDealTicket = dealInfo[k].ticket;
                  positionInfo[posIndex].openTime = dealInfo[k].time;
                  positionInfo[posIndex].openPrice = dealInfo[k].price;
                  positionInfo[posIndex].volume = dealInfo[k].volume;
                  positionInfo[posIndex].magic = dealInfo[k].magic;
                  positionInfo[posIndex].comment = dealInfo[k].comment;
                 }
              }
           }

         if(exitDealFound) //- Continue saving the exit deal data
           {
            //- Save the position type
            if(dealInfo[x].type == DEAL_TYPE_BUY)
              {
               //- If the exit deal is a buy, then the position was a sell trade
               positionInfo[posIndex].type = POSITION_TYPE_SELL;
              }
            else
              {
               //- If the exit deal is a sell, then the position was a buy trade
               positionInfo[posIndex].type = POSITION_TYPE_BUY;
              }

            positionInfo[posIndex].positionId = dealInfo[x].positionId;
            positionInfo[posIndex].symbol = dealInfo[x].symbol;
            positionInfo[posIndex].profit = dealInfo[x].profit;
            positionInfo[posIndex].closingDealTicket = dealInfo[x].ticket;
            positionInfo[posIndex].closePrice = dealInfo[x].price;
            positionInfo[posIndex].closeTime = dealInfo[x].time;
            positionInfo[posIndex].swap = dealInfo[x].swap;
            positionInfo[posIndex].commission = dealInfo[x].commission;
            positionInfo[posIndex].tpPrice = dealInfo[x].tpPrice;
            positionInfo[posIndex].tpPips = 0;
            positionInfo[posIndex].slPrice = dealInfo[x].slPrice;
            positionInfo[posIndex].slPips = 0;

            //- Calculate the trade duration in seconds
            positionInfo[posIndex].duration = MathAbs((long)positionInfo[posIndex].closeTime - (long)positionInfo[posIndex].openTime);

            //- Calculate the net profit after swap and commission
            positionInfo[posIndex].netProfit =
               positionInfo[posIndex].profit + positionInfo[posIndex].swap - positionInfo[posIndex].commission;

            //- Get pip values for the position
            if(positionInfo[posIndex].type == POSITION_TYPE_BUY) //- Buy position
              {
               //- Get sl and tp pip values
               if(positionInfo[posIndex].tpPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].tpPips =
                     int((positionInfo[posIndex].tpPrice - positionInfo[posIndex].openPrice) / symbolPoint);
                 }
               if(positionInfo[posIndex].slPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].slPips =
                     int((positionInfo[posIndex].openPrice - positionInfo[posIndex].slPrice) / symbolPoint);
                 }

               //- Get the buy profit in pip value
               double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
               positionInfo[posIndex].pipProfit =
                  int((positionInfo[posIndex].closePrice - positionInfo[posIndex].openPrice) / symbolPoint);
              }
            else //- Sell position
              {
               //- Get sl and tp pip values
               if(positionInfo[posIndex].tpPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].tpPips =
                     int((positionInfo[posIndex].openPrice - positionInfo[posIndex].tpPrice) / symbolPoint);
                 }
               if(positionInfo[posIndex].slPrice > 0)
                 {
                  double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
                  positionInfo[posIndex].slPips =
                     int((positionInfo[posIndex].slPrice - positionInfo[posIndex].openPrice) / symbolPoint);
                 }

               //- Get the sell profit in pip value
               double symbolPoint = SymbolInfoDouble(positionInfo[posIndex].symbol, SYMBOL_POINT);
               positionInfo[posIndex].pipProfit =
                  int((positionInfo[posIndex].openPrice - positionInfo[posIndex].closePrice) / symbolPoint);
              }

            //- Now we scan and get the opening order ticket in the orderInfo array
            for(int k = 0; k < ArraySize(orderInfo); k++) //- Search from the oldest to newest order
              {
               if(
                  orderInfo[k].positionId == positionInfo[posIndex].positionId &&
                  orderInfo[k].state == ORDER_STATE_FILLED
               )
                 {
                  //- Save the order ticket that intiated the position
                  positionInfo[posIndex].openingOrderTicket = orderInfo[k].ticket;
                  positionInfo[posIndex].ticket = positionInfo[posIndex].openingOrderTicket;

                  //- Determine if the position was initiated by a pending order or direct market entry
                  switch(orderInfo[k].type)
                    {
                     //- Pending order entry
                     case ORDER_TYPE_BUY_LIMIT:
                     case ORDER_TYPE_BUY_STOP:
                     case ORDER_TYPE_SELL_LIMIT:
                     case ORDER_TYPE_SELL_STOP:
                     case ORDER_TYPE_BUY_STOP_LIMIT:
                     case ORDER_TYPE_SELL_STOP_LIMIT:
                        positionInfo[posIndex].initiatedByPendingOrder = true;
                        positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
                        break;

                     //- Direct market entry
                     default:
                        positionInfo[posIndex].initiatedByPendingOrder = false;
                        positionInfo[posIndex].initiatingOrderType = orderInfo[k].type;
                        break;
                    }

                  break; //--- We have everything we need, exit the orderInfo loop
                 }
              }
           }
        }
      else //--- Position id not found
        {
         continue;//- skip to the next iteration
        }
     }
//- Resize the positionInfo array and delete all the indexes that have zero values
   ArrayResize(positionInfo, totalPositionsFound);
  }


Функция отображения истории позиций

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

void PrintPositionsHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the deals, orders, positions history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_POSITIONS_HISTORY_DATA);
   int totalPositionsClosed = ArraySize(positionInfo);
   if(totalPositionsClosed <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No position history found for the specified period.");
      return; //- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalPositionsClosed,
      " positions closed between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalPositionsClosed; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Position #", (r + 1));
      Print("Symbol: ", positionInfo[r].symbol);
      Print("Time Open: ", positionInfo[r].openTime);
      Print("Ticket: ", positionInfo[r].ticket);
      Print("Type: ", EnumToString(positionInfo[r].type));
      Print("Volume: ", positionInfo[r].volume);
      Print("0pen Price: ", positionInfo[r].openPrice);
      Print("SL Price: ", positionInfo[r].slPrice, " (slPips: ", positionInfo[r].slPips, ")");
      Print("TP Price: ", positionInfo[r].tpPrice, " (tpPips: ", positionInfo[r].tpPips, ")");
      Print("Close Price: ", positionInfo[r].closePrice);
      Print("Close Time: ", positionInfo[r].closeTime);
      Print("Trade Duration: ", positionInfo[r].duration);
      Print("Swap: ", positionInfo[r].swap, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Commission: ", positionInfo[r].commission, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Profit: ", positionInfo[r].profit, " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("Net profit: ", DoubleToString(positionInfo[r].netProfit, 2), " ", AccountInfoString(ACCOUNT_CURRENCY));
      Print("pipProfit: ", positionInfo[r].pipProfit);
      Print("Initiating Order Type: ", EnumToString(positionInfo[r].initiatingOrderType));
      Print("Initiated By Pending Order: ", positionInfo[r].initiatedByPendingOrder);
      Print("Comment: ", positionInfo[r].comment);
      Print("Magic: ", positionInfo[r].magic);
      Print("");
     }
  }


Функция сохранения данных отложенных ордеров

Функция SavePendingOrdersData() обрабатывает данные из истории ордеров для формирования и сохранения истории отложенных ордеров. Функция сортирует отложенные ордера из истории ордеров, сохраняет ключевые данные и вычисляет конкретные значения, такие как количество пипсов для тейа-профита (TP) и стоп-лосса (SL) levels. Она играет важную роль в отслеживании жизненного цикла отложенных ордеров, помогая формировать точную историю ордеров и дополняя систему данными о том, как был структурирован и исполнен каждый отложенный ордер.

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

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

void SavePendingOrdersData()
  {
//-- Function's code will go here
  }

Далее мы подсчитываем общее количество ордеров массива orderInfo, в котором хранится информация обо всех ордерах. Изменим размер массива pendingOrderInfo для первоначального размещения общего количества ордеров, обеспечивая достаточно места для хранения отсортированных отложенных ордеров.

int totalOrderInfo = ArraySize(orderInfo);
ArrayResize(pendingOrderInfo, totalOrderInfo);
int totalPendingOrdersFound = 0, pendingIndex = 0;

Если нет ордеров для обработки (то есть totalOrderInfo == 0), мы немедленно выходим из функции, поскольку нет данных об отложенных ордерах для обработки.

if(totalOrderInfo == 0)
  {
   return;
  }

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

for(int x = totalOrderInfo - 1; x >= 0; x--)
  {
   if(
      orderInfo[x].type == ORDER_TYPE_BUY_LIMIT || orderInfo[x].type == ORDER_TYPE_BUY_STOP ||
      orderInfo[x].type == ORDER_TYPE_SELL_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP ||
      orderInfo[x].type == ORDER_TYPE_BUY_STOP_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP_LIMIT
   )
     {
      totalPendingOrdersFound++;
      pendingIndex = totalPendingOrdersFound - 1;

      //-- Save the pending order properties into the pendingOrderInfo array

     }

Если ордер отложенный, мы сохраняем его свойства (например, тип, состояние, идентификатор позиции, тикет, символ, время и т.д.) в массиве pendingOrderInfo.

pendingOrderInfo[pendingIndex].type = orderInfo[x].type;
pendingOrderInfo[pendingIndex].state = orderInfo[x].state;
pendingOrderInfo[pendingIndex].positionId = orderInfo[x].positionId;
pendingOrderInfo[pendingIndex].ticket = orderInfo[x].ticket;
pendingOrderInfo[pendingIndex].symbol = orderInfo[x].symbol;
pendingOrderInfo[pendingIndex].timeSetup = orderInfo[x].timeSetup;
pendingOrderInfo[pendingIndex].expirationTime = orderInfo[x].expirationTime;
pendingOrderInfo[pendingIndex].timeDone = orderInfo[x].timeDone;
pendingOrderInfo[pendingIndex].typeTime = orderInfo[x].typeTime;
pendingOrderInfo[pendingIndex].priceOpen = orderInfo[x].priceOpen;
pendingOrderInfo[pendingIndex].tpPrice = orderInfo[x].tpPrice;
pendingOrderInfo[pendingIndex].slPrice = orderInfo[x].slPrice;

Затем мы подсчитаем количество пипсов как для уровней тейк-профита (TP), так и стоп-лосса (SL), еслт они указаны. Для этого мы используем значение точки символа для определения количества пипсов.

if(pendingOrderInfo[pendingIndex].tpPrice > 0)
  {
   double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
   pendingOrderInfo[pendingIndex].tpPips =
      (int)MathAbs((pendingOrderInfo[pendingIndex].tpPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
  }
if(pendingOrderInfo[pendingIndex].slPrice > 0)
  {
   double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
   pendingOrderInfo[pendingIndex].slPips =
      (int)MathAbs((pendingOrderInfo[pendingIndex].slPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
  }

Мы также сохраняем дополнительные свойства, такие как магическое число ордера, причина, тип исполнения, комментарий, начальный объем и цена stop limit.

pendingOrderInfo[pendingIndex].magic = orderInfo[x].magic;
pendingOrderInfo[pendingIndex].reason = orderInfo[x].reason;
pendingOrderInfo[pendingIndex].typeFilling = orderInfo[x].typeFilling;
pendingOrderInfo[pendingIndex].comment = orderInfo[x].comment;
pendingOrderInfo[pendingIndex].volumeInitial = orderInfo[x].volumeInitial;
pendingOrderInfo[pendingIndex].priceStopLimit = orderInfo[x].priceStopLimit;

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

ArrayResize(pendingOrderInfo, totalPendingOrdersFound);

Ниже представлена полная реализация функции SavePendingOrdersData() со всеми сегментами кода.

void SavePendingOrdersData()
  {
//- Let us begin by scanning the orders and link them to different deals
   int totalOrderInfo = ArraySize(orderInfo);
   ArrayResize(pendingOrderInfo, totalOrderInfo);
   int totalPendingOrdersFound = 0, pendingIndex = 0;
   if(totalOrderInfo == 0)
     {
      return; //- No order data to process found, we can't go on. exit the function
     }

   for(int x = totalOrderInfo - 1; x >= 0; x--)
     {
      //- Check if it is a pending order and save its properties
      if(
         orderInfo[x].type == ORDER_TYPE_BUY_LIMIT || orderInfo[x].type == ORDER_TYPE_BUY_STOP ||
         orderInfo[x].type == ORDER_TYPE_SELL_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP ||
         orderInfo[x].type == ORDER_TYPE_BUY_STOP_LIMIT || orderInfo[x].type == ORDER_TYPE_SELL_STOP_LIMIT
      )
        {
         totalPendingOrdersFound++;
         pendingIndex = totalPendingOrdersFound - 1;

         pendingOrderInfo[pendingIndex].type = orderInfo[x].type;
         pendingOrderInfo[pendingIndex].state = orderInfo[x].state;
         pendingOrderInfo[pendingIndex].positionId = orderInfo[x].positionId;
         pendingOrderInfo[pendingIndex].ticket = orderInfo[x].ticket;
         pendingOrderInfo[pendingIndex].symbol = orderInfo[x].symbol;
         pendingOrderInfo[pendingIndex].timeSetup = orderInfo[x].timeSetup;
         pendingOrderInfo[pendingIndex].expirationTime = orderInfo[x].expirationTime;
         pendingOrderInfo[pendingIndex].timeDone = orderInfo[x].timeDone;
         pendingOrderInfo[pendingIndex].typeTime = orderInfo[x].typeTime;
         pendingOrderInfo[pendingIndex].priceOpen = orderInfo[x].priceOpen;
         pendingOrderInfo[pendingIndex].tpPrice = orderInfo[x].tpPrice;
         pendingOrderInfo[pendingIndex].slPrice = orderInfo[x].slPrice;

         if(pendingOrderInfo[pendingIndex].tpPrice > 0)
           {
            double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
            pendingOrderInfo[pendingIndex].tpPips =
               (int)MathAbs((pendingOrderInfo[pendingIndex].tpPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
           }
         if(pendingOrderInfo[pendingIndex].slPrice > 0)
           {
            double symbolPoint = SymbolInfoDouble(pendingOrderInfo[pendingIndex].symbol, SYMBOL_POINT);
            pendingOrderInfo[pendingIndex].slPips =
               (int)MathAbs((pendingOrderInfo[pendingIndex].slPrice - pendingOrderInfo[pendingIndex].priceOpen) / symbolPoint);
           }

         pendingOrderInfo[pendingIndex].magic = orderInfo[x].magic;
         pendingOrderInfo[pendingIndex].reason = orderInfo[x].reason;
         pendingOrderInfo[pendingIndex].typeFilling = orderInfo[x].typeFilling;
         pendingOrderInfo[pendingIndex].comment = orderInfo[x].comment;
         pendingOrderInfo[pendingIndex].volumeInitial = orderInfo[x].volumeInitial;
         pendingOrderInfo[pendingIndex].priceStopLimit = orderInfo[x].priceStopLimit;

        }
     }
//--Resize the pendingOrderInfo array and delete all the indexes that have zero values
   ArrayResize(pendingOrderInfo, totalPendingOrdersFound);
  }


Функция отображения истории отложенных ордеров

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

void PrintPendingOrdersHistory(datetime fromDateTime, datetime toDateTime) export
  {
//- Get and save the pending orders history for the specified period
   GetHistoryData(fromDateTime, toDateTime, GET_PENDING_ORDERS_HISTORY_DATA);
   int totalPendingOrders = ArraySize(pendingOrderInfo);
   if(totalPendingOrders <= 0)
     {
      Print("");
      Print(__FUNCTION__, ": No pending orders history found for the specified period.");
      return; //- Exit the function
     }

   Print("");
   Print(__FUNCTION__, "-------------------------------------------------------------------------------");
   Print(
      "Found a total of ", totalPendingOrders,
      " pending orders filled or cancelled between (", fromDateTime, ") and (", toDateTime, ")."
   );

   for(int r = 0; r < totalPendingOrders; r++)
     {
      Print("---------------------------------------------------------------------------------------------------");
      Print("Pending Order #", (r + 1));
      Print("Symbol: ", pendingOrderInfo[r].symbol);
      Print("Time Setup: ", pendingOrderInfo[r].timeSetup);
      Print("Type: ", EnumToString(pendingOrderInfo[r].type));
      Print("Ticket: ", pendingOrderInfo[r].ticket);
      Print("State: ", EnumToString(pendingOrderInfo[r].state));
      Print("Time Done: ", pendingOrderInfo[r].timeDone);
      Print("Volume Initial: ", pendingOrderInfo[r].volumeInitial);
      Print("Price Open: ", pendingOrderInfo[r].priceOpen);
      Print("SL Price: ", pendingOrderInfo[r].slPrice, " (slPips: ", pendingOrderInfo[r].slPips, ")");
      Print("TP Price: ", pendingOrderInfo[r].tpPrice, " (slPips: ", pendingOrderInfo[r].slPips, ")");
      Print("Expiration Time: ", pendingOrderInfo[r].expirationTime);
      Print("Position ID: ", pendingOrderInfo[r].positionId);
      Print("Price Stop Limit: ", pendingOrderInfo[r].priceStopLimit);
      Print("Type Filling: ", EnumToString(pendingOrderInfo[r].typeFilling));
      Print("Type Time: ", EnumToString(pendingOrderInfo[r].typeTime));
      Print("Reason: ", EnumToString(pendingOrderInfo[r].reason));
      Print("Comment: ", pendingOrderInfo[r].comment);
      Print("Magic: ", pendingOrderInfo[r].magic);
      Print("");
     }
  }


Заключение

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

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

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

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

Чтобы внедрение прошло гладко, мы предоставим полную документацию для EX5-библиотеки History Manager, а также примеры практического использования. Эти примеры покажут, как интегрировать библиотеку в ваши проекты и проводить эффективный торговый анализ. Кроме того, мы включим простые примеры советников и пошаговые демонстрации, которые помогут вам оптимизировать свои торговые стратегии и в полной мере использовать возможности библиотеки.

Файл с исходным кодом HistoryManager.mq5 приложен внизу. Спасибо за внимание и успехов в торговле и программировании на MQL5!

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16528

Прикрепленные файлы |
HistoryManager.mq5 (33.95 KB)
Нейросети в трейдинге: Распутывание структурных компонентов (SCNN) Нейросети в трейдинге: Распутывание структурных компонентов (SCNN)
Предлагаем познакомиться с инновационным фреймворком SCNN, который выводит анализ временных рядов на новый уровень за счёт чёткого разделения данных на долгосрочные, сезонные, краткосрочные и остаточные компоненты. Такой подход значительно повышает точность прогнозирования, позволяя модели адаптироваться к сложной и меняющейся рыночной динамике.
Преодоление ограничений машинного обучения (Часть 1): Нехватка совместимых метрик Преодоление ограничений машинного обучения (Часть 1): Нехватка совместимых метрик
В настоящей статье показано, что часть проблем, с которыми мы сталкиваемся, коренится в слепом следовании «лучшим практикам». Предоставляя читателю простые, основанные на реальном рынке доказательства, мы объясним ему, почему мы должны воздержаться от такого поведения и вместо этого принять передовой опыт, основанный на конкретных областях, если наше сообщество хочет получить хоть какой-то шанс на восстановление скрытого потенциала ИИ.
Квантовая нейросеть на MQL5 (Часть II): Обучаем нейросеть с обратным распространением ошибки на марковских матрицах ALGLIB Квантовая нейросеть на MQL5 (Часть II): Обучаем нейросеть с обратным распространением ошибки на марковских матрицах ALGLIB
В статье представлена инновационная архитектура квантовой нейронной сети для алгоритмической торговли, объединяющая принципы квантовой механики с современными методами машинного обучения. Система включает квантовые эффекты (резонанс, интерференцию, декогеренцию), многоуровневую память различных временных масштабов, марковские цепи с библиотекой ALGLIB и адаптивное управление параметрами. Полная реализация выполнена на MQL5 с использованием встроенных типов matrix/vector, что устраняет барьеры внедрения в MetaTrader 5.
Трейдинг с экономическим календарем MQL5 (Часть 5): Добавление в панель адаптивных элементов управления и кнопок сортировки Трейдинг с экономическим календарем MQL5 (Часть 5): Добавление в панель адаптивных элементов управления и кнопок сортировки
В этой статье мы создадим кнопки для фильтров валютных пар, уровней важности, временных фильтров и функцию отмены для улучшения управления панелью. Кнопки будут запрограммированы на динамическую реакцию на действия пользователя, обеспечивая бесперебойное взаимодействие. Мы также автоматизируем их поведение, чтобы отражать изменения в реальном времени на панели. Это повысит общую функциональность, мобильность и оперативность панели.