
Kit de herramientas de negociación MQL5 (Parte 4): Desarrollo de una biblioteca EX5 para la gestión del historial
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.
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.
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.
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».
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.
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ú.
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.
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:
- Historial de pedidos.
- Historial de transacciones.
- Historial de posiciones.
- Historial de pedidos pendientes.
- 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:
- Dos variables datetime, fromDateTime y toDateTime, que especifican el inicio y el final del período deseado.
- 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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso