English Русский Deutsch 日本語
preview
Kit de herramientas de negociación MQL5 (Parte 4): Desarrollo de una biblioteca EX5 para la gestión del historial

Kit de herramientas de negociación MQL5 (Parte 4): Desarrollo de una biblioteca EX5 para la gestión del historial

MetaTrader 5Ejemplos |
55 0
Wanateki Solutions LTD
Kelvin Muturi Muigua

Introducción

En esta interesante serie de artículos, hemos desarrollado dos bibliotecas EX5 completas: PositionsManager.ex5, que procesa y gestiona posiciones, y PendingOrdersManager.ex5, que gestiona órdenes pendientes. Además, creamos ejemplos prácticos, algunos de ellos con interfaces gráficas de usuario, para demostrar la implementación eficaz de estas bibliotecas.

En este artículo, presentaremos otra biblioteca EX5 esencial diseñada para recuperar y procesar el historial de órdenes completadas, operaciones y transacciones de posiciones. Además, desarrollaremos módulos analíticos para generar informes comerciales que evalúen el rendimiento de los sistemas de negociación, los asesores expertos o símbolos específicos basados en diferentes criterios flexibles.

Este artículo sirve como guía práctica para desarrolladores principiantes de MQL5 que puedan encontrar difícil trabajar con posiciones, órdenes e historiales de operaciones. También será un recurso valioso para cualquier programador de MQL5 que busque una biblioteca para optimizar y mejorar la eficiencia en el manejo del historial de operaciones.

Para empezar, abordaremos algunas cuestiones fundamentales que a muchos programadores de MQL5, especialmente a los que se inician en el procesamiento de historiales de operaciones en MetaTrader 5, les resulta difícil comprender.


¿Cuál es el ciclo de vida de una transacción comercial en MQL5?

En MQL5, el ciclo de vida de una transacción comercial comienza con la ejecución de una orden. Esta orden se puede clasificar en dos tipos principales: una orden de mercado directa o una orden pendiente.

Entrada directa de órdenes de mercado

Una orden de mercado directa es una solicitud en tiempo real para comprar o vender un activo al precio actual de mercado (Ask o Bid). Anteriormente, en los artículos primero y segundo, explicamos cómo procesar estos pedidos mientras desarrollábamos la biblioteca Positions Manager.

Orden de entrada directa en el mercado

Una orden de mercado directa se ejecuta inmediatamente, lo que la hace ideal para estrategias de negociación manuales y automatizadas. Una vez ejecutada, la orden pasa a ser una posición abierta activa y se le asigna un número de ticket único y un identificador de posición (POSITION_ID) independiente, lo que resulta más fiable para el seguimiento y la gestión de las distintas etapas de la posición a lo largo de su ciclo de vida.

Entrada de orden pendiente

Por el contrario, una orden pendiente (BUY STOP, BUY LIMIT, SELL STOP, SELL LIMIT, BUY STOP LIMIT y SELL STOP LIMIT) es una orden diferida que se activa cuando se alcanza un nivel de precio específico. En el tercer artículo de esta serie, donde desarrollamos la biblioteca Pending Orders Manager, se ofrece una guía detallada sobre el procesamiento de este tipo de órdenes.

Entrada de orden pendiente

Hasta que el precio de mercado se alinee con el precio de activación de la orden pendiente predefinido, la orden pendiente permanece inactiva. Una vez activada, se convierte en una orden de mercado y se ejecuta, recibiendo un número de ticket único y un identificador de posición (POSITION_ID) similar a una orden de mercado directa.


¿Cómo puede cambiar el estado de una posición durante su vigencia?

A lo largo de la vigencia de una posición, su estado puede cambiar debido a diversos factores:

  • Cierre parcial: si se cierra una parte de la posición, se registra la correspondiente operación de salida (cierre) en el historial de operaciones.
  • Reversión de posición: Una reversión de posición, como una transacción «cerrada por», también se registra como una operación de salida.
  • Cierre total: cuando se cierra toda la posición, ya sea manualmente o automáticamente mediante un evento Take Profit, Stop Loss o Stop Out debido a una llamada de margen, se registra una operación de salida final en el historial de operaciones.

Es fundamental comprender el ciclo de vida de una operación comercial en MQL5. Cada operación comienza como una orden enviada al servidor de operaciones, ya sea una solicitud para abrir una orden pendiente, ejecutar una orden directa de mercado para comprar o vender, o cerrar parcialmente una posición existente. Independientemente del tipo, todas las operaciones comerciales se registran primero como órdenes.

Si la orden se ejecuta correctamente, pasa a la siguiente fase y se guarda en la base de datos del historial como una operación. Mediante las diversas propiedades y funciones disponibles para órdenes y operaciones, puede rastrear cada operación hasta su orden original y vincularla a su posición correspondiente. Esto crea un registro claro y sistemático del ciclo de vida de una operación.

Este enfoque de «migas de pan» le permite realizar un seguimiento del origen, la progresión y el resultado de cada operación o transacción en el entorno MQL5. Proporciona un registro de auditoría detallado, que incluye la orden que inició la transacción, la hora exacta de ejecución, cualquier modificación realizada durante el proceso y el resultado final de la operación (posición). Este seguimiento no solo mejora la transparencia, sino que también le permitirá, como programador de MQL5, desarrollar algoritmos para analizar estrategias de trading, identificar áreas de mejora y optimizar el rendimiento.


¿Cuál es la diferencia entre una posición y una operación en MQL5?

En MQL5, una posición representa una operación activa en curso (posición) que usted mantiene actualmente en el mercado. Se encuentra en un estado abierto que refleja una posición de compra o venta sobre un símbolo específico. Por otro lado, una operación se refiere a una transacción completada, es decir, cuando una posición se ha cerrado completamente. Las posiciones abiertas activas y las órdenes pendientes se muestran en la ventana Caja de herramientas de MetaTrader 5, en la pestaña «Operaciones».

MetaTrader 5 Caja de herramientas Pestaña Operaciones

Las posiciones cerradas (positions), junto con las órdenes (orders) y las operaciones (deals), se muestran en la pestaña «Historial» de la ventana Caja de herramientas.

Pestaña Historial de la Caja de herramientas de MetaTrader 5

Para acceder al historial completo de posiciones, puede utilizar las opciones del menú de la plataforma y seleccionar la opción «Posiciones». También se puede acceder al historial de pedidos y transacciones utilizando las mismas opciones del menú.

MetaTrader 5 Caja de herramientas Pestaña Historial - Historial de posiciones Selección

La distinción entre posiciones y operaciones puede resultar confusa para los programadores principiantes de MQL5, especialmente cuando se utilizan las funciones históricas estándar de la plataforma. Este artículo, junto con el código detallado de la biblioteca que estamos a punto de crear, le proporcionará una comprensión clara de cómo se clasifican y se realiza el seguimiento de las posiciones y las operaciones en MQL5. Si dispones de poco tiempo y necesitas una biblioteca de historial lista para usar, solo tienes que seguir la completa documentación del siguiente artículo sobre cómo implementarla directamente en tu proyecto.


Crear el archivo de código fuente de la biblioteca del administrador de historial (.mq5)

Para empezar, abra su MetaEditor IDE y acceda al MQL Wizard seleccionando Nuevo” en el menú. En el asistente, elija crear un nuevo archivo fuente Biblioteca, al que llamaremos HistoryManager.mq5. Este archivo será la base de nuestras funciones principales, dedicadas a gestionar y analizar el historial de operaciones de las cuentas. Al crear la nueva biblioteca HistoryManager.mq5, guárdela en la carpeta Libraries\Toolkit que creamos en el primer artículo. Al almacenar este nuevo archivo en el mismo directorio que las bibliotecas EX5 Positions Manager y Pending Orders Manager, mantenemos una estructura organizativa clara y coherente para nuestro proyecto. Este enfoque facilitará la localización y gestión de cada componente a medida que se amplíe nuestro conjunto de herramientas.

Directorio del kit de herramientas de la biblioteca del navegador MetaEditor

Así es como se ve nuestro archivo fuente HistoryManager.mq5 recién creado. Comience eliminando los comentarios «Mi función» que se encuentran debajo de las directivas propiedad. Las directivas de propiedad Copyright y Link de tu archivo pueden diferir, pero esto no afectará al comportamiento ni al rendimiento del código. Puede personalizar las directivas Copyright y Link con la información que prefiera, pero asegúrese de que la directiva de propiedad Library permanezca sin cambios.

//+------------------------------------------------------------------+
//|                                               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);
//   }
//+------------------------------------------------------------------+


Estructuras de datos, directivas del preprocesador y variables globales

En nuestro archivo fuente de biblioteca HistoryManager.mq5 recién creado, comenzaremos definiendo los siguientes componentes:

  • Directivas del preprocesador: Estas ayudarán a clasificar y consultar diversos tipos de historiales comerciales.
  • Estructuras de datos: almacenarán datos históricos de órdenes, operaciones, posiciones y órdenes pendientes.
  • Matrices de estructura dinámica global: Contendrán todos los datos históricos relevantes de la biblioteca.

Definir estos componentes en el ámbito global garantiza que sean accesibles en toda la biblioteca y que puedan ser utilizados por todos los diferentes módulos o funciones que la componen.

Directivas del preprocesador

Dado que nuestra biblioteca de gestión del historial gestionará diversos tipos de solicitudes, es fundamental diseñarla de manera que solo recupere los datos históricos específicos necesarios para cada solicitud. Este enfoque modular y específico mejorará el rendimiento de nuestra biblioteca, al tiempo que mantendrá la flexibilidad para diversos casos de uso.

Para lograrlo, definiremos constantes enteras que sirvan como identificadores para tipos específicos de datos históricos. Estas constantes permitirán a la biblioteca centrarse únicamente en los datos necesarios, lo que garantizará un consumo mínimo de recursos y un procesamiento más rápido.

Organizaremos los datos históricos en cinco categorías principales:

  1. Historial de pedidos.
  2. Historial de transacciones.
  3. Historial de posiciones.
  4. Historial de pedidos pendientes.
  5. Todos los datos históricos.

Al utilizar estas constantes, las funciones dentro de la biblioteca pueden especificar el tipo de historial que desean procesar. La función principal de obtención del historial consultará y devolverá únicamente los datos solicitados, ahorrando tiempo y recursos computacionales. Comencemos por definir estas constantes enteras colocándolas directamente debajo de las últimas directivas #property de nuestro código.

#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

Estructuras de datos

Nuestra biblioteca EX5 almacenará varios datos históricos en estructuras de datos declaradas globalmente. Estas estructuras mantendrán de manera eficiente el historial de transacciones, órdenes, posiciones y órdenes pendientes cada vez que se consulten.

//- 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;
  };

Matrices de estructura dinámica global

Las declaraciones finales en el ámbito global consistirán en las matrices de estructuras de datos dinámicas de las estructuras que definimos anteriormente. Estas matrices servirán como almacenamiento principal para todos los datos principales administrados por nuestra biblioteca.

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


Función para obtener datos del historial

La función GetHistoryDataFunction() será el núcleo de nuestra biblioteca EX5, constituyendo la columna vertebral de su funcionalidad. La mayoría de las demás funciones de la biblioteca dependerán de ella para recuperar el historial de operaciones en función de los períodos y tipos de historial especificados. Dado que esta función está destinada únicamente al uso interno, no se definirá como exportable.

Esta función está diseñada para recuperar los datos históricos solicitados para un período y un tipo de historial determinados. Es una función de tipo bool , lo que significa que devolverá true si se recupera correctamente el historial y false si la operación falla.

La función GetHistoryDataFunction() acepta tres parámetros de entrada:

  1. Dos variables datetime, fromDateTime y toDateTime, que especifican el inicio y el final del período deseado.
  2. Un entero sin signo, dataToGet, que corresponde a una de las constantes predefinidas en la parte superior del archivo.

Al combinar estos datos, la función puede consultar y procesar de manera eficiente los datos históricos necesarios. Comencemos por definir la función.

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

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

  }

La primera tarea de nuestra función será verificar si el intervalo de fechas proporcionado es válido. Dado que el tipo de datos datetime en MQL5 es esencialmente un entero largo que representa el tiempo en el formato Unix epoch (es decir, el número de segundos transcurridos desde el 1 de enero de 1970, 00:00:00 UTC), podemos comparar directamente estos valores para garantizar su corrección. Además, tenga en cuenta que al solicitar datos históricos en MQL5, la hora se basa en la hora del servidor de operaciones, no en la de su equipo local.

Para validar el intervalo de fechas, comprobaremos que el valor fromDateTime sea inferior al valor toDateTime . Para validar el intervalo de fechas, comprobaremos que el valor fromDateTime sea inferior al valor toDateTime. Si el período proporcionado no supera la validación, devolveremos false y saldremos de la función.

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

Una vez verificadas las fechas y el periodo, restableceremos la caché de errores de MQL5 para garantizar la precisión de los códigos de error en caso de que surja algún problema. A continuación, llamaremos a la función HistorySelect() dentro de una instrucción if-else, pasando los valores datetime validados para recuperar el historial de operaciones y órdenes para el período especificado. Dado que HistorySelect() devuelve un valor booleano, devolverá true si encuentra correctamente el historial que debe procesar, o false si encuentra un error o no consigue recuperar los datos.

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);
  }

Dentro de la parte else de la instrucción if else, hemos añadido código para registrar un mensaje que indica que la selección del historial ha fallado, junto con el código de error, antes de salir de la función y devolver un valor booleano de false. En la parte if, utilizaremos una instrucción switchpara llamar a las funciones adecuadas para procesar los datos del historial de operaciones cargados, basándonos en el valor de 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;
  }

Aquí está la función completa GetHistoryDataFunction() con todos los segmentos de código incluidos.

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);
  }

Si guarda e intenta compilar nuestro archivo de código fuente en esta etapa, encontrará numerosos errores y advertencias de compilación. Esto se debe a que muchas de las funciones a las que se hace referencia en el código aún no se han creado. Dado que aún nos encontramos en las primeras fases de desarrollo de nuestra biblioteca EX5, una vez que se hayan implementado todas las funciones que faltan, el archivo de la biblioteca EX5 se compilará sin errores ni advertencias.


Función Guardar datos de ofertas

La función SaveDealsData() se encargará de recuperar y guardar todo el historial de operaciones disponible actualmente en la caché del historial de operaciones para los diferentes periodos que solicitarán las diferentes funciones de la biblioteca. No devolverá ningún dato y no se define como exportable porque se llama internamente dentro de la biblioteca, concretamente desde la función GetHistoryData(). Esta función utilizará las funciones estándar HistoryDealGet... de MQL5 para obtener las distintas propiedades de la operación y almacenarlas en la matriz de estructura de datos dinámica dealInfo .

En primer lugar, comencemos por crear la definición o firma de la función.

void SaveDealsData()
  {

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

  }

Dado que SaveDealsData() se llama dentro de la función GetHistoryData(), no es necesario volver a llamar a HistorySelect() antes de procesar el historial de operaciones. El primer paso en la función SaveDealsData() será comprobar si hay algún historial de transacciones que procesar. Lo conseguiremos utilizando la función HistoryDealsTotal(), que devuelve el número total de transacciones disponibles en la caché del historial. Para mayor eficiencia, crearemos un entero y lo llamaremos totalDeals para almacenar el historial total de transacciones y un entero sin signo llamado dealTicket para almacenar los identificadores de los tickets de transacción.

int totalDeals = HistoryDealsTotal();
ulong dealTicket;

Si no hay ofertas disponibles o no se encuentran (totalDeals es 0 o menos), registraremos un mensaje para indicarlo y, a continuación, saldremos de la función antes de tiempo para evitar un procesamiento innecesario.

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

Si existe un historial de transacciones, el siguiente paso será preparar una matriz para almacenar los datos obtenidos. Utilizaremos la matriz dinámica dealInfo para esta tarea y comenzaremos por cambiar su tamaño para que coincida con el número total de transacciones utilizando la función ArrayResize(), asegurándonos de que tenga capacidad suficiente para almacenar todas las propiedades relevantes de las transacciones.

ArrayResize(dealInfo, totalDeals);

A continuación, iteraremos a través de las transacciones en orden inverso, comenzando por la más reciente, utilizando un bucle «for». Para cada transacción, utilizaremos la función HistoryDealGetTicket() para recuperar el ticket único asociado a la transacción. Si la recuperación del ticket se realiza correctamente, obtendremos y guardaremos las distintas propiedades de la oferta. Almacenaremos cada propiedad en su campo correspondiente en la matriz dealInfo en el índice correspondiente a la iteración actual del bucle.

Si la función HistoryDealGetTicket() no consigue recuperar un ticket válido para ninguna operación, registraremos un mensaje de error, incluyendo el código de error, con fines de depuración. Esto garantizará la transparencia en caso de que surjan problemas inesperados durante el proceso de recuperación de la propiedad.

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()
      );
     }
  }

Aquí está la función completa SaveDealsData() con todos los segmentos de código al completo.

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);
     }
  }


Imprimir historial de operaciones

La función PrintDealsHistory() está diseñada para recuperar y mostrar los datos históricos de las transacciones durante un período determinado. Esta función será útil en situaciones en las que necesite examinar una variedad de datos comerciales dentro de un período de tiempo determinado. No devuelve ningún dato, sino que envía la información de la transacción al registro de MetaTrader 5 para su revisión. Esta función se puede llamar externamente para proporcionar a los usuarios información sobre operaciones pasadas utilizando la función GetHistoryData() para obtener los datos relevantes.

Comenzamos definiendo la función PrintDealsHistory(). La función requiere dos parámetros, fromDateTime y toDateTime, que representan las horas de inicio y finalización del período que queremos buscar. La función recuperará las operaciones que se hayan ejecutado dentro de este intervalo de tiempo. Observe que la función está marcada como export, lo que significa que se puede llamar desde otros programas o bibliotecas, lo que la hace fácilmente disponible para uso externo.

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

A continuación, llamamos a la función GetHistoryData(), pasando fromDateTime, toDateTime y una constante adicional GET_DEALS_HISTORY_DATA. Esto le dice a la función que extraiga los datos comerciales relevantes entre las horas de inicio y finalización especificadas. Esta llamada a la función garantiza que la información de la transacción correspondiente al período deseado se recupere y se almacene en la matriz dealInfo .

GetHistoryData(fromDateTime, toDateTime, GET_DEALS_HISTORY_DATA);

Una vez obtenidos los datos del acuerdo, debemos comprobar si hay algún dato disponible. Utilizamos la función ArraySize() para obtener el número total de operaciones almacenadas en la matriz dealInfo . Si no se encuentran ofertas (es decir, totalDeals es 0), registramos un mensaje para informar al usuario y salimos de la función. Si no hay ofertas que mostrar, la función finaliza antes de tiempo, lo que ahorra tiempo y evita operaciones innecesarias.

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

Si se encuentran datos del acuerdo, procedemos a imprimir los detalles. El primer paso es imprimir un mensaje resumen que muestre el número total de operaciones encontradas y el intervalo de fechas en el que se ejecutaron.

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

A continuación, utilizamos un bucle for para iterar sobre todas las transacciones de la matriz dealInfo. Para cada operación, imprimimos los detalles relevantes, como el símbolo de la operación, el número de ticket, el ID de la posición, el tipo de entrada, el precio, los niveles de stop loss (SL) y take profit (TP), el swap, la comisión, el beneficio y mucho más. Los detalles de cada transacción se imprimen de forma clara con etiquetas descriptivas, lo que facilita al usuario la comprensión del historial de transacciones.

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("");
  }

Aquí está la función completa PrintDealsHistory() con todos los segmentos de código integrados.

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("");
     }
  }


Función para guardar datos de órdenes

The SaveOrdersData() function will be responsible for retrieving and storing the historical order data available in the trading history cache. Esta función procesa las órdenes una por una, extrae sus propiedades clave utilizando las funciones HistoryOrderGet... de MQL5 y las almacena en una matriz dinámica llamada orderInfo. Esta matriz será utilizada por otras partes de la biblioteca para analizar y manipular los datos según sea necesario. Esta función no devolverá ningún dato, no se definirá como exportable ya que se utiliza internamente dentro de la biblioteca, gestionará los errores con elegancia y registrará cualquier problema con fines de depuración.

Comencemos por definir la firma de la función.

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

A continuación, determinamos cuántos pedidos históricos hay disponibles. Esto se consigue utilizando la función HistoryOrdersTotal(), que devuelve el recuento total de pedidos históricos en la caché. El resultado se almacena en una variable denominada totalOrdersHistory. Además, declaramos una variable larga sin signo, orderTicket, para almacenar el ticket de cada pedido a medida que los procesamos.

int totalOrdersHistory = HistoryOrdersTotal();
ulong orderTicket;

Si no hay pedidos históricos (totalOrdersHistory <= 0), la función registra un mensaje indicándolo y se cierra antes de tiempo para evitar un procesamiento innecesario.

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

Cuando hay órdenes históricas disponibles, preparamos la matriz orderInfo para almacenar los datos recuperados. Esto se hace cambiando el tamaño de la matriz con la función ArrayResize() para que coincida con el número total de órdenes históricas.

ArrayResize(orderInfo, totalOrdersHistory);

Recorremos los pedidos en orden inverso (empezando por el más reciente) utilizando un bucle «for». Por cada pedido. Comenzamos recuperando el ticket del pedido utilizando la función HistoryOrderGetTicket(). Si la recuperación del ticket se realiza correctamente, extraeremos las distintas propiedades del pedido utilizando las funciones HistoryOrderGet... y las almacenaremos en los campos correspondientes de la matriz orderInfo . Si la recuperación del ticket falla, la función registra un mensaje de error junto con el código de error para la depuración.

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()
      );
     }
  }

Después de procesar todos los pedidos, la función se cierra correctamente. Aquí está la implementación completa de la función SaveOrdersData(), con todos los segmentos de código incluidos.

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);
     }
  }


Función imprimir historial de órdenes

La función PrintOrdersHistory() proporciona una característica esencial para mostrar los detalles del historial de pedidos dentro de un período específico. Consulta los datos guardados previamente en la matriz orderInfo e imprime todos los detalles relevantes de los pedidos. Esta función se define como exportación porque está pensada para que puedan acceder a ella los módulos externos o las aplicaciones MQL5 que utilizan esta biblioteca. Sigue un enfoque similar al de la función PrintDealsHistory(). Aquí está la implementación completa de la función PrintOrdersHistory(), con comentarios explicativos para ayudarle a comprender mejor cómo funciona cada parte del código.

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("");
     }
  }


Función para guardar datos de posiciones

La función SavePositionsData() organiza las operaciones y el historial de órdenes para reconstruir el ciclo de vida de cada posición, desempeñando un papel fundamental en la creación del historial de posiciones al sintetizar la información de los datos disponibles. En la documentación de MQL5, observará que no hay funciones estándar (como HistoryPositionSelect() o HistoryPositionsTotal()) para acceder directamente a los datos históricos de posiciones. Por lo tanto, necesitamos crear una función personalizada que combine los datos de los pedidos y las transacciones, utilizando el ID de posición como clave de conexión para vincular las transacciones con sus pedidos originales.

Comenzaremos por examinar las operaciones para identificar todas las operaciones de salida, que indican que se ha cerrado una posición. A partir de ahí, volveremos a la entrada correspondiente operación de entrada para recopilar detalles sobre la apertura de la posición. Por último, utilizaremos el historial de órdenes para enriquecer la información del historial de posiciones con contexto adicional, como el tipo de orden original o si la posición se inició mediante una orden pendiente. Este proceso paso a paso garantizará que el ciclo de vida de cada puesto, desde su apertura hasta su cierre, se reconstruya con precisión, proporcionando un registro de auditoría sencillo.

Comencemos por definir la firma de la función. Dado que esta función solo será utilizada internamente por los módulos centrales de la biblioteca EX5, no será exportable.

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

A continuación, calcularemos el número total de transacciones en la matriz dealInfo, que contiene todos los datos de las transacciones. Después, cambiaremos el tamaño de la matriz positionInfo , que utilizaremos para guardar todos los datos del historial de posiciones y la prepararemos para que se adapte al número previsto de posiciones.

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

Si no hay ofertas disponibles en la matriz dealInfo (es decir, totalDealInfo == 0), salimos de la función antes de tiempo, ya que no hay datos que procesar.

if(totalDealInfo == 0)
  {
   return;
  }

A continuación, recorremos las operaciones en orden inverso (empezando por la operación más reciente) para asegurarnos de que podemos asignar las operaciones de salida a sus operaciones de entrada correspondientes. Comprobamos si la operación actual es una operación de salida evaluando su propiedad de entrada. (dealInfo[x].entry == DEAL_ENTRY_OUT). Es fundamental comenzar buscando operaciones de salida, ya que esto confirma que una posición se ha cerrado y ya no está activa. Solo queremos registrar posiciones históricas cerradas, no las activas.

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

Si se encuentra una operación de salida, buscamos su correspondiente operación de entrada haciendo coincidir el POSITION_ID. Cuando se encuentra una operación de entrada, comenzamos a guardar su información relevante en la matriz 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;
        }
     }
  }

Una vez que la operación de salida se ha emparejado con una operación de entrada, procedemos a guardar las propiedades de la operación de salida, como el precio de cierre, la hora de cierre, el beneficio, el swap y la comisión. También calculamos la duración de la operación y el beneficio neto teniendo en cuenta el swap y la comisión.

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;
  }

Para cada posición, calculamos los valores pip para los niveles stop loss (SL) y take profit (TP), dependiendo de si la posición es una compra o una venta. Usamos el valor de punto del símbolo para determinar el número de pips.

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);
  }

Por último, revisamos la matriz orderInfo para encontrar la orden que inició la posición. Comparamos el POSITION_ID y nos aseguramos de que el pedido se encuentre en el estado ORDER_STATE_FILLED. Una vez encontrada, almacenamos el ticket y el tipo de la orden de apertura, lo que ayudará a determinar si la posición se inició mediante una orden pendiente o una entrada directa en el mercado.

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
     }
  }

Por último, para limpiar la matriz positionInfo, cambiamos su tamaño para eliminar cualquier elemento vacío o sin usar después de que se hayan procesado todas las posiciones.

ArrayResize(positionInfo, totalPositionsFound);

Aquí está la implementación completa de la función SavePositionsData(), con todos los segmentos de código incluidos.

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);
  }


Función para imprimir el historial de posiciones

La función PrintPositionsHistory() está diseñada para mostrar un historial detallado de las posiciones cerradas dentro de un periodo de tiempo específico. Accede a los datos guardados previamente en la matriz positionInfo e imprime los detalles relevantes para cada posición. Esta función es exportable, lo que la hace accesible a módulos externos o aplicaciones MQL5 que utilizan esta biblioteca. Su implementación seguirá una estructura similar a la de otras funciones de impresión que hemos desarrollado. Aquí está la implementación completa, con comentarios detallados para mayor claridad.

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("");
     }
  }


Función para guardar los datos de órdenes pendientes

La función SavePendingOrdersData() procesa los datos del historial de pedidos para generar y guardar el historial de pedidos pendientes. Esta función básicamente filtra las órdenes pendientes del historial de órdenes, almacena los detalles clave y calcula valores específicos, como el número de pips para los niveles de take profit (TP) y stop loss (SL). Desempeña un papel crucial en el seguimiento del ciclo de vida de los pedidos pendientes, ayudando a generar un historial de pedidos preciso y ampliando el sistema con datos sobre cómo se estructuró y ejecutó cada pedido pendiente.

Actualmente, MQL5 no dispone de funciones estándar como HistoryPendingOrderSelect() o HistoryPendingOrdersTotal() para acceder directamente a los datos históricos de órdenes pendientes. Como resultado, debemos crear una función personalizada para escanear el historial de los pedidos y crear una fuente de datos que contenga todos los pedidos completados o cancelados pendientes dentro de un periodo histórico determinado.

Comencemos por definir la firma de la función. Dado que esta función solo será utilizada internamente por los módulos centrales de la biblioteca EX5, no será exportable.

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

A continuación, calculamos el número total de pedidos en la matriz orderInfo , que contiene los detalles de todos los pedidos. Cambiaremos el tamaño de la matriz pendingOrderInfo para acomodar el número total de pedidos inicialmente, asegurando que haya suficiente espacio para almacenar los pedidos pendientes filtrados.

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

Si no hay órdenes que procesar (es decir, totalOrderInfo == 0), salimos inmediatamente de la función, ya que no hay datos de órdenes pendientes que gestionar.

if(totalOrderInfo == 0)
  {
   return;
  }

Ahora, recorremos los pedidos en orden inverso para asegurarnos de que procesamos primero los pedidos más recientes. Dentro del bucle, comprobamos si el pedido actual es un pedido pendiente evaluando su tipo. El historial de órdenes guardadas incluirá las órdenes pendientes (como límites de compra, stops de venta, etc.) que se ejecutaron (completaron) y se convirtieron en posiciones, o que se cancelaron sin convertirse en posiciones.

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

     }

Si la orden es una orden pendiente, guardamos sus propiedades (por ejemplo, tipo, estado, ID de posición, ticket, símbolo, hora y más) en la matriz 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;

A continuación, calculamos el número de pips tanto para los niveles de take profit (TP) como de stop loss (SL), si se han especificado. Para ello, utilizamos el valor del símbolo punto para determinar el número de pips.

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);
  }

También guardamos propiedades adicionales como el número mágico de la orden, el motivo, el tipo de ejecución, el comentario, el volumen inicial y el precio límite de stop.

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;

Una vez que hemos procesado todos los pedidos, redimensionamos la matriz pendingOrderInfo para eliminar cualquier elemento vacío o sin usar, asegurándonos de que la matriz solo contenga los datos relevantes de los pedidos pendientes.

ArrayResize(pendingOrderInfo, totalPendingOrdersFound);

Aquí está la implementación completa de la función SavePendingOrdersData(), con todos los segmentos de código incluidos.

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);
  }


Función para imprimir el historial de órdenes pendientes

La función PrintPendingOrdersHistory() está diseñada para mostrar un historial detallado de las órdenes pendientes ejecutadas o canceladas dentro de un periodo de tiempo especificado. Accede a los datos guardados previamente en la matriz pendingOrderInfo e imprime los detalles relevantes de cada pedido pendiente. Esta función es exportable, lo que la hace accesible a módulos externos o aplicaciones MQL5 que utilicen esta biblioteca EX5. Su implementación seguirá una estructura similar a la de otras funciones de impresión que hemos desarrollado. Aquí está la implementación completa, con comentarios detallados para mayor claridad.

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("");
     }
  }


Conclusión

En este artículo, hemos explorado cómo utilizar MQL5 para recuperar datos del historial de transacciones para órdenes y operaciones. Ha aprendido a aprovechar estos datos para generar el historial de posiciones cerradas y órdenes pendientes, junto con un registro de auditoría que realiza un seguimiento del ciclo de vida de cada posición cerrada. Esto incluye su origen, cómo se cerró y otros detalles valiosos, como beneficio neto, beneficio por pip, valor por pip para stop loss y take profit, duración de la operación y más.

También desarrollamos las funciones principales de la biblioteca History Manager EX5 , lo que nos permite consultar, guardar y clasificar diferentes tipos de datos históricos. Estas funciones fundamentales forman parte del motor de la biblioteca que gestiona su funcionamiento interno. Sin embargo, aún queda mucho por hacer. La mayoría de las funciones que hemos creado en este artículo son preparatorias y sientan las bases para una biblioteca más orientada al usuario.

En el próximo artículo, ampliaremos la biblioteca History Manager EX5 introduciendo funciones exportables diseñadas para clasificar y analizar datos históricos basándose en los requisitos comunes de los usuarios. Por ejemplo, podrá recuperar las propiedades de las posiciones cerradas más recientemente, analizar las últimas órdenes pendientes ejecutadas o canceladas, comprobar la última posición cerrada para un símbolo específico, calcular las ganancias cerradas del día actual y determinar las ganancias semanales en pips, entre otras funcionalidades.

Además, incluiremos módulos avanzados de clasificación y análisis para generar informes comerciales detallados similares a los que produce el Probador de estrategias de MetaTrader 5. Estos informes analizarán datos históricos reales de operaciones, ofreciendo información sobre el rendimiento de un asesor experto o una estrategia de trading. También podrá filtrar y ordenar estos datos mediante programación según parámetros como símbolos o números mágicos.

Para que la implementación sea fluida, proporcionaremos documentación completa sobre la biblioteca History Manager EX5 , junto con ejemplos prácticos de uso. Estos ejemplos le mostrarán cómo integrar la biblioteca en sus proyectos y realizar análisis comerciales eficaces. Además, incluiremos ejemplos sencillos de Asesores Expertos y demostraciones paso a paso para ayudarle a optimizar sus estrategias comerciales y aprovechar al máximo las capacidades de la biblioteca.

Puede encontrar el archivo de código fuente adjunto HistoryManager.mq5 al final de este artículo. ¡Gracias por seguirme y le deseo mucho éxito en su viaje en el trading y la programación MQL5!

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16528

Archivos adjuntos |
HistoryManager.mq5 (33.95 KB)
Introducción a MQL5 (Parte 10): Guía de trabajo con indicadores incorporados en MQL5 para principiantes Introducción a MQL5 (Parte 10): Guía de trabajo con indicadores incorporados en MQL5 para principiantes
Este artículo describe cómo trabajar con indicadores incorporados en MQL5, con especial atención en la creación de un asesor experto basado en el indicador RSI utilizando un enfoque de proyecto. Hoy aprenderá a obtener y utilizar los valores RSI, a gestionar las fluctuaciones de liquidez y a mejorar la visualización de las transacciones mediante objetos gráficos. Además, el artículo abordará otros aspectos importantes: el riesgo como porcentaje del depósito, los ratios riesgo/rentabilidad y la modificación del riesgo sobre la marcha para proteger los beneficios.
Creación de barras 3D basadas en el tiempo, el precio y el volumen Creación de barras 3D basadas en el tiempo, el precio y el volumen
Qué son los gráficos de precios multidimensionales en 3D y cómo se crean. Cómo las barras 3D predicen las inversiones de precios, y cómo Python y MetaTrader 5 permiten construir estas barras volumétricas en tiempo real.
ADAM poblacional (Estimación Adaptativa de Momentos) ADAM poblacional (Estimación Adaptativa de Momentos)
Este artículo presenta la transformación del conocido y popular método de optimización ADAM basado en gradientes en un algoritmo basado en poblaciones y su modificación con la introducción de individuos híbridos. El nuevo enfoque permite crear agentes que combinen elementos de soluciones exitosas mediante una distribución de probabilidades. Una innovación clave es la generación de poblaciones híbridas que acumulan de forma adaptativa la información de las soluciones más prometedoras, mejorando la eficacia de la búsqueda en espacios multidimensionales complejos.
Operar con el Calendario Económico MQL5 (Parte 5): Mejorar el panel de control con controles adaptables y botones de filtro Operar con el Calendario Económico MQL5 (Parte 5): Mejorar el panel de control con controles adaptables y botones de filtro
En este artículo, creamos botones para filtros de pares de divisas, niveles de importancia, filtros de tiempo y una opción de cancelación para mejorar el control del panel. Estos botones están programados para responder dinámicamente a las acciones del usuario, lo que permite una interacción fluida. También automatizamos su comportamiento para reflejar los cambios en tiempo real en el panel de control. Esto mejora la funcionalidad general, la movilidad y la capacidad de respuesta del panel.