
Cómo crear un panel interactivo MQL5 utilizando la clase Controls (Parte 2): Añadir capacidad de respuesta a los botones
Introducción
En nuestro artículo anterior, configuramos con éxito los componentes principales de nuestro panel de control en MetaQuotes Language 5 (MQL5). En esa etapa, los botones y las etiquetas que montamos permanecían estáticos, proporcionando una estructura básica pero inactiva. Ahora es el momento de ir más allá de un mero marco visual. En la siguiente parte, nos centraremos en hacer que el panel sea realmente interactivo. Daremos vida a los componentes añadiendo la capacidad de responder a las entradas y clics de los usuarios, convirtiendo nuestro panel de control en una herramienta dinámica preparada para la interacción comercial en tiempo real.
En este artículo, que es la segunda parte, exploraremos cómo automatizar la funcionalidad de los botones creados en la primera parte, asegurándonos de que reaccionen cuando se pulsen y se editen. Aprenderemos a configurar eventos que activan acciones específicas, lo que permite al usuario interactuar con el panel de forma significativa. Cubriremos temas clave, incluidos:
- Ilustración de los elementos a automatizar: descripción detallada de los componentes que ganarán funcionalidad.
- Automatizar las interacciones de la GUI en MQL5: implementar el código necesario para garantizar que los botones respondan a las entradas y clics del usuario de manera efectiva.
- Conclusión: Resumen de los avances logrados en la creación del panel de control interactivo.
¡Profundicemos en estos temas para mejorar nuestra interfaz comercial!
Ilustración de elementos a automatizar
Nos concentraremos en la automatización de los botones que creamos en la primera parte de nuestro panel MQL5. Cada botón tiene una función específica y queremos asegurarnos de que reaccionen intuitivamente a los comandos del usuario. Esta reacción es esencial porque, a diferencia de un programa que se ejecuta en segundo plano, un panel comercial debe ser fácil de usar y accesible. Primero, tenemos el botón en la esquina superior derecha del panel, que está diseñado para cerrar toda la interfaz. Por lo tanto, si el entorno de negociación está abierto en el gráfico de MetaTrader 5, debería ser posible cerrar el panel de la misma manera que se cerraría una aplicación.
Mientras el botón de comercio esté activo, colocaremos botones que ejecutarán ciertas operaciones comerciales. Entre ellas se incluyen «Open Buy», «Sell», «Sell Stop», «Sell Limit», «Buy Stop» y «Buy Limit». Estos botones permitirán realizar pedidos rápidamente y facilitarán reacciones inmediatas al mercado en constante cambio. También automatizaremos los botones de cierre que prácticamente gestionarán las operaciones cuando el botón de cierre esté activo. Entre ellas se incluyen «Close All», «Close All Profit Trades» y, entre muchas otras, una que casi nos da vergüenza mencionar: «Close All Pending Orders». Cuando haces clic en un botón, hará lo que dice que hará.
Por último, automatizaremos el botón de información que, al presionarlo, despliega una interfaz de botones que detalla la información de la cuenta del usuario y sus antecedentes. Esperamos que esto ayude a mantener a los comerciantes informados sobre los detalles pertinentes relacionados con sus cuentas, ayudándolos así a tomar mejores decisiones. El objetivo de todo esto es crear un panel de trading responsivo, que facilite el tipo de operaciones que un trader necesita hacer y que también, de alguna pequeña manera, intente involucrar al usuario más que el panel anterior.
Para ayudar a comprender fácilmente estos procesos y componentes de automatización, a continuación se incluye una descripción detallada de ellos presentando el hito anterior.
Con la idea de lo que vamos a hacer, comencemos la automatización de inmediato. Si aún no lo ha leído, consulte el artículo anterior en el que creamos el ensamblado estático de los elementos de la interfaz gráfica de usuario para seguir el hilo de nuestra explicación. Vamos a hacerlo.
Automatización de las interacciones de la GUI en MQL5
Iremos de procesos simples a complejos para que nuestra estructura quede ordenada cronológicamente. De esta manera, actualizaremos la información de la cuenta con cada cambio de tick o cotización de precio. Para lograrlo, necesitaremos el controlador de eventos OnTick, una función integrada en MQL5 que normalmente se activa cuando cambian las cotizaciones de precios. La función es un tipo de datos vacío, lo que significa que maneja las ejecuciones directamente y no tiene que devolver ninguna salida. Su función debería parecerse a la que se muestra a continuación.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ ... } //+------------------------------------------------------------------+
Este es el controlador de eventos que es responsable de las actualizaciones de precios y, por lo tanto, el corazón de nuestra lógica. Agregaremos la lógica de control a esta función como se muestra a continuación:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Start of the OnTick function, called on every price tick //--- Check if the background color of the INFO button is yellow if (obj_Btn_INFO.ColorBackground() == clrYellow) { //--- Update the account equity display on the panel obj_Btn_ACC_EQUITY.Text(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2)); //--- Update the account balance display on the panel obj_Btn_ACC_BALANCE.Text(DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2)); //--- Update the server trade time display on the panel obj_Btn_TIME.Text(TimeToString(TimeTradeServer(), TIME_DATE | TIME_SECONDS)); } //--- End of the OnTick function } //+------------------------------------------------------------------+
La primera acción que realizamos dentro de la función OnTick es comprobar el color de fondo del botón de información («obj_Btn_INFO»). Si el fondo del botón está en amarillo nos indica que hemos activado el modo de visualización de información. Al cumplirse esta condición, actualizaremos varias visualizaciones relacionadas con la cuenta en el panel. En concreto, actualizamos la visualización del saldo de la cuenta obteniendo el saldo actual de la cuenta mediante la función AccountInfoDouble y pasando la propiedad «ACCOUNT_EQUITY» como parámetro de entrada, y le damos formato con dos decimales mediante la función DoubleToString. A continuación, asignamos este valor al texto del botón «obj_Btn_ACC_EQUITY», lo que nos garantiza tener a nuestro alcance la información más reciente sobre el patrimonio neto.
A continuación, actualizamos de forma similar la visualización del saldo de la cuenta recuperando el saldo de la cuenta mediante la propiedad del parámetro «ACCOUNT_BALANCE», formateándolo con dos decimales y estableciéndolo como el texto del botón «obj_Btn_ACC_BALANCE». Por último, actualizamos la visualización del tiempo de negociación del servidor obteniendo el tiempo de negociación actual del servidor mediante la función TimeTradeServer, formateándolo para incluir tanto la fecha como los segundos y actualizando el texto del botón «obj_Btn_TIME» con este valor. Esto garantiza que siempre estemos al tanto del último momento comercial, lo cual es esencial para la toma de decisiones oportuna en escenarios comerciales. Estos son los resultados.
Desde la visualización, podemos ver que el campo de tiempo se actualiza correspondientemente, lo cual es un éxito. Ahora, el primer componente de automatización está listo. Eso fue fácil, ¿verdad? Luego procedemos a los demás componentes de nuestro panel GUI. La automatización de los elementos restantes se realizará dentro del controlador de funciones OnChartEvent, por lo que vamos a analizar más detenidamente sus parámetros de entrada y sus funciones.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... }
El propósito de la función es manejar cambios en el gráfico realizados por un usuario o un programa MQL5. De esta forma, las interacciones que realizará el usuario como mover el mouse, editar los campos de los botones y hacer clic en etiquetas y botones serán capturadas y manejadas por este controlador de eventos. Desglosemos sus argumentos para una mayor interpretación:
- id: Este parámetro representa el ID del evento y corresponde a uno de los 11 tipos de eventos predefinidos. Estos incluyen eventos como pulsaciones de teclas, movimientos del mouse, creación de objetos, cambios de gráficos y eventos personalizados. Para eventos personalizados, puede utilizar los ID desde CHARTEVENT_CUSTOM hasta CHARTEVENT_CUSTOM_LAST. Los 11 tipos de eventos son los que se muestran a continuación;
- lparam: Parámetro de evento de tipo largo (long). Su valor depende del evento específico que se esté gestionando. Por ejemplo, podría representar un código de tecla durante un evento de pulsación de tecla.
- dparam: Un parámetro de evento de tipo doble (double). Al igual que lparam, su valor varía en función del tipo de evento. Por ejemplo, durante un evento de movimiento del ratón, podría transmitir la posición del cursor del ratón.
- sparam: Un parámetro de evento de tipo cadena (string). Una vez más, su significado depende del evento. Por ejemplo, durante la creación de un objeto, podría contener el nombre del objeto recién creado.
Para mostrar esto de forma más clara, dentro de la función, vamos a imprimir los cuatro argumentos del diario.
// Print the 4 function parameters Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);
Esta función imprimirá el ID del evento del gráfico, su valor de evento de tipo largo, su valor de evento de tipo doble y su valor de tipo cadena. Echemos un vistazo al siguiente GIF para facilitar la consulta.
A partir del GIF proporcionado, todo debería estar claro ahora. Ahora pasamos a capturar eventos de clic en gráficos en los elementos del panel GUI. Por lo tanto, nuestro ID será CHARTEVENT_OBJECT_CLICK.
//Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); if (id==CHARTEVENT_OBJECT_CLICK){ //--- }
Primero comentamos la línea de código anterior porque no queremos llenar nuestro diario con información irrelevante. Las dos barras (//) utilizadas se denominan comentarios de una sola línea y comentan un código desde su inicio y continúan hasta el final de la línea, de ahí su nombre de «comentario de una sola línea». Los comentarios son particularmente ignorados por la computadora durante la ejecución. Usamos la declaración if para comprobar si hubo un clic en el objeto. Esto se logra equiparando el ID del evento del gráfico con las enumeraciones de clics del objeto. Si hacemos clic en un objeto, imprimamos los argumentos y veamos qué obtenemos. Se utiliza el siguiente código.
if (id==CHARTEVENT_OBJECT_CLICK){ Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam); ... }
En la función de impresión, solo hemos cambiado «LPARAM» por «LP» y «DPARAM» por «DP» para poder centrarnos únicamente en el ID del evento del gráfico y el nombre del objeto en el que se ha hecho clic. A partir de ahí, obtendremos el ID del objeto y tomaremos medidas si es necesario. A continuación se muestra una ilustración de la lógica:
El primer botón que automatizamos es el botón de comercio.
if (sparam==obj_Btn_TRADE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name()); // Reset the pressed states of all buttons to ensure only one button appears pressed at a time obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Change the background color of the Trade button to yellow to indicate it is active obj_Btn_TRADE.ColorBackground(clrYellow); // Set the background color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBackground(clrSilver); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to yellow to match its background obj_Btn_TRADE.ColorBorder(clrYellow); // Set the border color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBorder(clrSilver); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Close section if it exists destroySection_Close(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Trade section, bringing it to the forefront createSection_Trade(); }
Aquí gestionamos la interacción cuando se hace clic en el botón «Trade» comprobando si el parámetro de cadena coincide con el nombre del botón «Trade» utilizando la función «Name». En primer lugar, utilizamos la función Print para registrar el nombre del objeto en el que se ha hecho clic, lo que ayuda a depurar el código al confirmar qué botón se ha pulsado. A continuación, restablecemos el estado pulsado de todos los botones relevantes —«obj_Btn_TRADE», «obj_Btn_CLOSE» y «obj_Btn_INFO»— llamando a la función «Pressed» para cada botón y pasando un indicador falso. Esto garantiza que solo se pueda pulsar visualmente un botón a la vez. Después de restablecer, indicamos visualmente que el botón «Trade» está activo cambiando su color de fondo a amarillo mediante la función «ColorBackground», mientras que los colores de fondo de los botones «Close» e «Info» se establecen en plateado para indicar que están inactivos. Del mismo modo, actualizamos los colores de los bordes utilizando la función «ColorBorder»: amarillo para el botón «Trade» y plateado para los demás.
A continuación, limpiamos la interfaz llamando a las funciones «destroySection_Close» y «destroySection_Information» para eliminar cualquier contenido mostrado en las secciones «Cerrar» o «Información». Por último, invocamos la función «createSection_Trade» para crear y mostrar dinámicamente la sección de operaciones, garantizando que el usuario tenga acceso a la interfaz de operaciones. Hacemos algo similar con el botón «Close».
// Check if the clicked object is the Close button else if (sparam==obj_Btn_CLOSE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade button to silver, indicating it's inactive obj_Btn_TRADE.ColorBackground(clrSilver); // Change the background color of the Close button to yellow to indicate it is active obj_Btn_CLOSE.ColorBackground(clrYellow); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to silver obj_Btn_TRADE.ColorBorder(clrSilver); // Set the border color of the Close button to yellow obj_Btn_CLOSE.ColorBorder(clrYellow); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Close section, bringing it to the forefront createSection_Close(); }
Aquí, seguimos casi exactamente la misma lógica que con el botón «Trade». Gestionamos el caso en el que se hace clic en el botón «Close» comprobando si el parámetro de cadena coincide con el nombre del botón «Close» utilizando la función «Name». Primero registramos el nombre del botón pulsado con la función Print con fines de depuración. Esto nos ayuda a confirmar que se hizo clic en el botón correcto. A continuación, restablecemos los estados pulsados de los botones «obj_Btn_TRADE», «obj_Btn_CLOSE» y «obj_Btn_INFO» utilizando la función «Pressed». Esto garantiza que ninguno de los botones permanezca presionado visualmente después de hacer clic. A continuación, actualizamos la interfaz cambiando el color de fondo del botón «Close» a amarillo mediante la función «ColorBackground», lo que indica que está activo, mientras que el color de fondo de los botones «Trade» e «Info» se establece en plateado para mostrar que están inactivos.
Del mismo modo, ajustamos los colores del borde de los botones utilizando la función «ColorBorder», haciendo que el borde del botón «Close» sea amarillo para que coincida con su fondo, mientras que los bordes de los botones «Trade» e «Info» se establecen en color plateado para indicar inactividad. Para limpiar, llamamos a las funciones «destroySection_Trade» y «destroySection_Information» para eliminar cualquier contenido existente de las secciones de comercio o información. Por último, llamamos a la función «createSection_Close» para generar y mostrar dinámicamente la interfaz relacionada con el cierre de posiciones u órdenes, lo que garantiza que el usuario pueda interactuar con las opciones de cierre adecuadas en el panel. El botón de información también adopta la misma lógica. El fragmento de código es el siguiente:
// Check if the clicked object is the Information button else if (sparam==obj_Btn_INFO.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_INFO.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade and Close buttons to silver, indicating they are inactive obj_Btn_TRADE.ColorBackground(clrSilver); obj_Btn_CLOSE.ColorBackground(clrSilver); // Change the background color of the Info button to yellow to indicate it is active obj_Btn_INFO.ColorBackground(clrYellow); // Set the border color of the Trade and Close buttons to silver obj_Btn_TRADE.ColorBorder(clrSilver); obj_Btn_CLOSE.ColorBorder(clrSilver); // Set the border color of the Info button to yellow obj_Btn_INFO.ColorBorder(clrYellow); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Close section if it exists destroySection_Close(); // Create the Information section, bringing it to the forefront createSection_Information(); }
Cuando compilamos y ejecutamos el programa, obtenemos el siguiente resultado en la interacción con el botón de control.
Podemos ver que fue un éxito. Ahora queremos destruir el panel cuando se haga clic en el botón de Windows.
// Check if the clicked object is the exit button (X button) else if (sparam==obj_Btn_X.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_X.Name()); // Call functions to destroy all sections, effectively closing the entire panel destroySection_Trade(); destroySection_Close(); destroySection_Information(); // Call a function to destroy the main panel itself destroySection_Main_Panel(); }
Aquí, gestionamos el caso en el que hacemos clic en el botón de salida comprobando si el parámetro de cadena coincide con el nombre del botón de salida utilizando la función «Name». Primero registramos el nombre del objeto en el que se ha hecho clic con fines de depuración mediante la función Print, lo que ayuda a confirmar que se ha hecho clic en el botón de salida.
A continuación, procedemos a cerrar todo el panel llamando a varias funciones. En primer lugar, llamamos a «destroySection_Trade», «destroySection_Close» y «destroySection_Information» para eliminar cualquier sección relacionada con las operaciones, el cierre de posiciones o la visualización de información de la cuenta, si es que están actualmente visibles. Estas funciones garantizan que todas las secciones interactivas del panel se borren correctamente. Por último, llamamos a la función «destroySection_Main_Panel», que se encarga de destruir el panel principal. Esto elimina eficazmente todos los elementos del panel de la interfaz, cerrando el panel por completo. Aquí hay un ejemplo.
Ahora tenemos que gestionar las operaciones bursátiles cuando se pulsa en los botones correspondientes. Para hacerlo fácilmente, necesitamos incluir una instancia de clase que ayude en el proceso. Por lo tanto, incluimos una instancia comercial utilizando #include al principio del código fuente. Esto nos da acceso a la clase «CTrade», que utilizamos para crear un objeto de operación. Esto es crucial, ya que lo necesitamos para realizar las operaciones comerciales.
#include <Trade/Trade.mqh>
CTrade obj_Trade;
El preprocesador sustituirá la línea #include <Trade/Trade.mqh> por el contenido del archivo Trade.mqh. Los corchetes angulares indican que el archivo Trade.mqh se tomará del directorio estándar (normalmente es directorio_de_instalación_del_terminal\MQL5\Include). El directorio actual no se incluye en la búsqueda. La línea se puede colocar en cualquier parte del programa, pero normalmente todas las inclusiones se colocan al principio del código fuente, para mejorar la estructura del código y facilitar la consulta. La declaración del objeto obj_Trade de la clase CTrade nos permitirá acceder fácilmente a los métodos contenidos en esa clase, gracias a los desarrolladores de MQL5.
Después de incluir la biblioteca comercial, podemos comenzar con la lógica para abrir una posición de venta cuando se hace clic en el botón de venta. Ésta es la lógica que debemos adoptar.
else if (sparam==obj_Btn_SELL.Name()){ //--- Check if the Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Bid; //--- Set the entry price for selling to the current bid price double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the sell order }
Aquí, una vez que confirmamos que se ha pulsado el botón de venta, registramos el evento de pulsación del botón utilizando la función Print con fines de depuración, que muestra el nombre del botón pulsado. A continuación, procedemos a recopilar los datos comerciales esenciales. En primer lugar, recuperamos y normalizamos los precios actuales de «venta (ask)» y «compra (bid)» del símbolo utilizando la función SymbolInfoDouble, que obtiene los precios de mercado, y la función NormalizeDouble, que garantiza que estos precios tengan el formato adecuado según el número de decimales definido por _Digits.
A continuación, obtenemos el tamaño del lote del campo de entrada convirtiendo el texto del campo «Lots» en un número mediante la función StringToDouble. El precio de entrada se fija en el precio de «compra (bid)» actual, ya que una orden de venta utiliza este precio para su ejecución. También calculamos los valores de stop loss y take profit basándonos en la información introducida por el usuario. El stop loss se calcula sumando el valor definido por el usuario (del campo de entrada «SL») al precio «ask», ajustado por el incremento mínimo del precio del símbolo, _Point. Del mismo modo, el take profit se calcula restando el valor definido por el usuario (del campo de entrada «TP») del precio «ask».
Por último, registramos los detalles de la orden, como el tamaño del lote, el precio de entrada, el stop loss y el take profit, utilizando la función Print. La orden de venta se ejecuta llamando a la función «Sell» desde el objeto «obj_Trade», pasando los parámetros necesarios: el tamaño del lote, el símbolo, el precio de entrada, el stop loss y el take profit. Para la posición de compra, se utiliza una lógica similar a la siguiente.
else if (sparam==obj_Btn_BUY.Name()){ //--- Check if the Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Ask; //--- Set the entry price for buying to the current ask price double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the buy order }
Cuando compilamos y probamos el progreso, obtenemos el siguiente resultado.
Para abrir las órdenes de límite y stop, se utiliza una lógica similar. Sin embargo, dado que estos no utilizan directamente las cotizaciones actuales del mercado, tendremos que configurar e incorporar un algoritmo adicional para las comprobaciones de seguridad. Comencemos con el botón de venta stop.
else if (sparam==obj_Btn_SELLSTOP.Name()){ //--- Check if the Sell Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid - stopslevel*_Point; //--- Calculate the valid price for placing a sell stop order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell stop order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell stop order } }
Aquí, gestionamos la lógica de automatización para cuando se pulsa el botón «Sell Stop» comprobando si el parámetro de cadena variable coincide con el nombre del botón «Sell Stop» utilizando la función «Name». Una vez confirmado esto, registramos el evento utilizando la función Print como de costumbre para mostrar el nombre del botón pulsado con fines de depuración. Comenzamos recuperando y normalizando los precios de «compra» y «venta» utilizando la función SymbolInfoDouble para obtener los precios de mercado y NormalizeDouble para formatearlos según la precisión decimal del símbolo definida por «_Digits».
A continuación, obtenemos el «precio definido por el usuario» del campo de entrada «Price» y lo convertimos de texto a valor numérico utilizando la función StringToDouble. Además, recuperamos el nivel mínimo de stop para el símbolo, necesario para validar las órdenes stop, utilizando SymbolInfoInteger con el parámetro «SYMBOL_TRADE_STOPS_LEVEL». Calculamos el precio válido restando el nivel de stop (convertido a puntos) del precio de «compra (bid)».
A continuación, comprobamos si el «precio definido por el usuario» es válido comparándolo con el precio válido calculado. Si el precio del usuario supera el rango válido, registramos un mensaje de error indicando un «precio de parada no válido». Sin embargo, si el precio definido por el usuario es válido, procedemos con los siguientes pasos. Recuperamos el tamaño del lote del campo de entrada utilizando StringToDouble para convertir el texto en un número. El precio de entrada se establece en el precio definido por el usuario. A continuación, calculamos el stop loss sumando el valor de stop loss definido por el usuario (del campo «SL») al precio de entrada y ajustándolo en función del incremento de precio del símbolo _Point. Del mismo modo, el take profit se calcula restando el valor de take profit definido por el usuario (del campo «TP») del precio de entrada.
Por último, registramos los detalles del pedido, incluyendo el tamaño del lote, el «precio de entrada», el «stop loss» y el «take profit» utilizando la función Print. La orden de venta stop se ejecuta llamando a la función «Sell Stop» desde el objeto «obj_Trade», pasando los parámetros relevantes, como el tamaño del lote, el precio de entrada, el símbolo, el stop loss y el take profit, y colocando la orden de venta stop según las instrucciones del usuario. Para ejecutar las demás órdenes, utilizamos un enfoque similar.
else if (sparam==obj_Btn_BUYSTOP.Name()){ //--- Check if the Buy Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask + stopslevel*_Point; //--- Calculate the valid price for placing a buy stop order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy stop order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy stop order } } else if (sparam==obj_Btn_SELLLIMIT.Name()){ //--- Check if the Sell Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid + stopslevel*_Point; //--- Calculate the valid price for placing a sell limit order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell limit order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell limit order } } else if (sparam==obj_Btn_BUYLIMIT.Name()){ //--- Check if the Buy Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask - stopslevel*_Point; //--- Calculate the valid price for placing a buy limit order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy limit order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy limit order } }
Cuando probamos los botones, obtenemos los siguientes datos de salida.
Fue todo un éxito. Ahora pasamos a cerrar posiciones y eliminar órdenes pendientes. Comencemos con la lógica para cerrar todas las posiciones abiertas en el mercado.
else if (sparam==obj_Btn_CLOSE_ALL.Name()){ //--- Check if the Close All button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol obj_Trade.PositionClose(pos_ticket); //--- Close the position } } } } }
Aquí solo nos ocupamos de la lógica para automatizar el botón «Close All», que cerrará todas las posiciones activas cuando se haga clic en él. Comenzamos comprobando si se ha pulsado el botón y registramos el evento imprimiendo el nombre del botón pulsado en el registro con fines de depuración utilizando la función Print. A continuación, iniciamos un bucle for que recorre todas las posiciones abiertas en la cuenta utilizando la función PositionsTotal, que devuelve el número total de posiciones abiertas. El bucle comienza desde la última posición de la lista (representada por «PositionsTotal() - 1») y funciona hacia atrás, disminuyendo «i» después de cada iteración para garantizar que se tratan todas las posiciones.
Para cada iteración, utilizamos la función PositionGetTicket para recuperar el número de ticket de la posición actual. El número de ticket identifica de forma única cada posición. A continuación, comprobamos si el ticket es válido asegurándonos de que sea mayor que 0. Si es válido, utilizamos la función PositionSelectByTicket para seleccionar la posición asociada a ese ticket.
Una vez seleccionada la posición, comprobamos además si la posición coincide con el símbolo para el que gestionamos las operaciones utilizando la función PositionGetString con el parámetro «POSITION_SYMBOL» y comparándola con _Symbol, que representa el símbolo de negociación actual. Si la posición coincide con el símbolo, llamamos a la función «PositionClose» desde el objeto «obj_Trade» y pasamos el número de ticket como parámetro para cerrar la posición. Este proceso continúa hasta que se cierran todas las posiciones que coinciden con el símbolo de negociación. Para cerrar todas las posiciones de venta, utilizaremos la misma función, pero le añadiremos una lógica de control adicional de la siguiente manera.
else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ //--- Check if the Close All Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position obj_Trade.PositionClose(pos_ticket); //--- Close the sell position } } } } } }
Este es un enfoque similar al anterior que utilizamos para cerrar todas las posiciones. Por lo tanto, hemos resaltado la sección adicional para centrarnos en la lógica adicional utilizada para cerrar solo las posiciones de venta cuando se hace clic en el botón «Close All Sell». Después de recorrer todas las posiciones, recuperamos el tipo de posición para cada posición utilizando la función PositionGetInteger con la constante POSITION_TYPE. Este valor se convierte entonces en la enumeración ENUM_POSITION_TYPE para facilitar su comprensión, un proceso denominado conversión de tipos. Una vez que tenemos el tipo de posición, comprobamos específicamente si la posición actual es una posición de venta comparándola con la propiedad POSITION_TYPE_SELL. Si la condición se evalúa como verdadera, lo que significa que la posición es efectivamente una venta, procedemos a cerrarla utilizando la función «PositionClose» del objeto «obj_Trade». Hacemos lo mismo al cerrar las posiciones de compra.
else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ //--- Check if the Close All Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position obj_Trade.PositionClose(pos_ticket); //--- Close the buy position } } } } } }
Al cerrar todas las posiciones de venta que están en pérdidas, aún necesitamos ampliar el enfoque actual para incorporar las ganancias de la posición seleccionada, que luego podemos utilizar para hacer comparaciones y determinar si son ganadoras o perdedoras. Esta es la lógica.
else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ //--- Check if the Close Loss Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing sell position } } } } } } }
Aquí, después de recorrer todas las posiciones y asegurarnos de que la posición actual es de tipo venta, introducimos una comprobación de las ganancias/pérdidas de cada posición. La función PositionGetDouble recupera la ganancia/pérdida actual de la posición seleccionada, utilizando la constante POSITION_PROFIT para determinar si la posición está en ganancia o pérdida. A continuación, añadimos una instrucción condicional para comprobar si el valor de ganancia/pérdida es inferior a cero, lo que indicaría que la posición está en pérdidas. Si se cumple esta condición, procedemos a cerrar la posición de venta perdedora llamando a la función «PositionClose». Esta lógica adicional, que hemos resaltado en amarillo, garantiza que solo se cerrarán las posiciones de venta que actualmente están en pérdidas. Utilizamos un enfoque similar para los demás botones.
else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ //--- Check if the Close Loss Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ //--- Check if the Close Profit Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ //--- Check if the Close Profit Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ //--- Check if the Close All Loss button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ //--- Check if the Close All Profit button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable position } } } } } }
Una vez que hayamos terminado con la lógica de las posiciones, podemos pasar a la lógica de las órdenes pendientes. Aquí eliminamos todos los pedidos pendientes. Esto se consigue mediante la siguiente lógica.
else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ //--- Check if the Close Pending button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); //--- Log the button click event for (int i = OrdersTotal() -1; i >= 0; i--){ //--- Loop through all pending orders ulong order_ticket = OrderGetTicket(i); //--- Get the ticket of the order if (order_ticket > 0){ //--- Check if the order ticket is valid if (OrderSelect(order_ticket)){ //--- Select the order by ticket if (OrderGetString(ORDER_SYMBOL)==_Symbol){ //--- Check if the order matches the symbol obj_Trade.OrderDelete(order_ticket); //--- Delete the pending order } } } } }
Aquí nos centramos en la funcionalidad de cerrar órdenes pendientes cuando se hace clic en el botón «Close Pending». Empezamos por comprobar si el parámetro de cadena coincide con el nombre del botón "Close Pending", lo que significa que el botón se ha activado. Al confirmar el clic en el botón, registramos la acción con fines de depuración utilizando la función Print, que muestra el nombre del botón en el que se ha hecho clic. A continuación, entramos en un bucle que recorre todos los pedidos pendientes utilizando la función OrdersTotal para obtener el número total de pedidos y decrementando el índice en orden inverso para evitar omitir ningún pedido durante la eliminación. Para cada pedido, recuperamos el número de ticket del pedido utilizando la función OrderGetTicket. A continuación, comprobamos si el billete es válido asegurándonos de que sea mayor que cero. Si es válido, procedemos a seleccionar el pedido utilizando la función OrderSelect.
Una vez seleccionada la orden, verificamos si se corresponde con el símbolo especificado en _Symbol llamando a la función OrderGetString con la constante «ORDER_SYMBOL». Si el pedido coincide, invocamos la función OrderDelete para eliminar el pedido pendiente asociado al ticket recuperado. Todo este proceso permite cerrar de manera eficiente todas las órdenes pendientes vinculadas al símbolo de negociación especificado cuando se activa el botón, lo que garantiza la intención del usuario de gestionar sus órdenes de manera eficaz. Después de todo eso, solo tenemos que actualizar el gráfico utilizando la función ChartRedraw como se muestra a continuación para asegurarnos de que los cambios se reflejen en el gráfico.
ChartRedraw(0);
Eso es todo lo que necesitamos para automatizar el panel. Cuando ejecutamos el programa y probamos la interfaz de cierre, obtenemos el siguiente resultado.
La lógica del controlador de eventos OnChartEvent que hemos incorporado al sistema para gestionar los eventos de clic en los gráficos es la siguiente:
//+------------------------------------------------------------------+ //| Handling chart events | //+------------------------------------------------------------------+ void OnChartEvent( const int id, // Event ID indicating the type of event (e.g., mouse click, timer, etc.) const long& lparam, // Long type parameter associated with the event, usually containing data like mouse coordinates or object IDs const double& dparam, // Double type parameter associated with the event, used for floating-point values related to the event const string& sparam // String type parameter associated with the event, typically the name of the object that triggered the event ){ // Print the 4 function parameters //Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); // Check if the event is a click on a chart object if (id == CHARTEVENT_OBJECT_CLICK){ //Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam); // Check if the clicked object is the Trade button if (sparam==obj_Btn_TRADE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name()); // Reset the pressed states of all buttons to ensure only one button appears pressed at a time obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Change the background color of the Trade button to yellow to indicate it is active obj_Btn_TRADE.ColorBackground(clrYellow); // Set the background color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBackground(clrSilver); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to yellow to match its background obj_Btn_TRADE.ColorBorder(clrYellow); // Set the border color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBorder(clrSilver); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Close section if it exists destroySection_Close(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Trade section, bringing it to the forefront createSection_Trade(); } // Check if the clicked object is the Close button else if (sparam==obj_Btn_CLOSE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade button to silver, indicating it's inactive obj_Btn_TRADE.ColorBackground(clrSilver); // Change the background color of the Close button to yellow to indicate it is active obj_Btn_CLOSE.ColorBackground(clrYellow); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to silver obj_Btn_TRADE.ColorBorder(clrSilver); // Set the border color of the Close button to yellow obj_Btn_CLOSE.ColorBorder(clrYellow); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Close section, bringing it to the forefront createSection_Close(); } // Check if the clicked object is the Information button else if (sparam==obj_Btn_INFO.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_INFO.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade and Close buttons to silver, indicating they are inactive obj_Btn_TRADE.ColorBackground(clrSilver); obj_Btn_CLOSE.ColorBackground(clrSilver); // Change the background color of the Info button to yellow to indicate it is active obj_Btn_INFO.ColorBackground(clrYellow); // Set the border color of the Trade and Close buttons to silver obj_Btn_TRADE.ColorBorder(clrSilver); obj_Btn_CLOSE.ColorBorder(clrSilver); // Set the border color of the Info button to yellow obj_Btn_INFO.ColorBorder(clrYellow); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Close section if it exists destroySection_Close(); // Create the Information section, bringing it to the forefront createSection_Information(); } // Check if the clicked object is the exit button (X button) else if (sparam==obj_Btn_X.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_X.Name()); // Call functions to destroy all sections, effectively closing the entire panel destroySection_Trade(); destroySection_Close(); destroySection_Information(); // Call a function to destroy the main panel itself destroySection_Main_Panel(); } else if (sparam==obj_Btn_SELL.Name()){ //--- Check if the Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Bid; //--- Set the entry price for selling to the current bid price double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the sell order } else if (sparam==obj_Btn_BUY.Name()){ //--- Check if the Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Ask; //--- Set the entry price for buying to the current ask price double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the buy order } else if (sparam==obj_Btn_SELLSTOP.Name()){ //--- Check if the Sell Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid - stopslevel*_Point; //--- Calculate the valid price for placing a sell stop order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell stop order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell stop order } } else if (sparam==obj_Btn_BUYSTOP.Name()){ //--- Check if the Buy Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask + stopslevel*_Point; //--- Calculate the valid price for placing a buy stop order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy stop order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy stop order } } else if (sparam==obj_Btn_SELLLIMIT.Name()){ //--- Check if the Sell Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid + stopslevel*_Point; //--- Calculate the valid price for placing a sell limit order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell limit order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell limit order } } else if (sparam==obj_Btn_BUYLIMIT.Name()){ //--- Check if the Buy Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask - stopslevel*_Point; //--- Calculate the valid price for placing a buy limit order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy limit order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy limit order } } else if (sparam==obj_Btn_CLOSE_ALL.Name()){ //--- Check if the Close All button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol obj_Trade.PositionClose(pos_ticket); //--- Close the position } } } } } else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ //--- Check if the Close All Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position obj_Trade.PositionClose(pos_ticket); //--- Close the sell position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ //--- Check if the Close All Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position obj_Trade.PositionClose(pos_ticket); //--- Close the buy position } } } } } } else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ //--- Check if the Close Loss Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ //--- Check if the Close Loss Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ //--- Check if the Close Profit Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ //--- Check if the Close Profit Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ //--- Check if the Close All Loss button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ //--- Check if the Close All Profit button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable position } } } } } } else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ //--- Check if the Close Pending button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); //--- Log the button click event for (int i = OrdersTotal() -1; i >= 0; i--){ //--- Loop through all pending orders ulong order_ticket = OrderGetTicket(i); //--- Get the ticket of the order if (order_ticket > 0){ //--- Check if the order ticket is valid if (OrderSelect(order_ticket)){ //--- Select the order by ticket if (OrderGetString(ORDER_SYMBOL)==_Symbol){ //--- Check if the order matches the symbol obj_Trade.OrderDelete(order_ticket); //--- Delete the pending order } } } } } } ChartRedraw(0); }
En resumen, esto es lo que hemos logrado.
¡Esto fue fantástico! Hemos logrado dar vida a nuestro panel, haciéndolo totalmente interactivo y receptivo. Ahora cuenta con clics de botones, actualizaciones de datos en tiempo real y capacidad de respuesta a estados activos, lo que mejora la experiencia general del usuario y la funcionalidad de nuestra interfaz de negociación.
Conclusión
En conclusión, las mejoras que hemos implementado en nuestro panel GUI en MetaQuotes Language 5 (MQL5) elevan significativamente su interactividad y funcionalidad, creando una experiencia de usuario más atractiva. Con la incorporación de actualizaciones de datos en vivo y clics de botones responsivos, ahora podemos interactuar con el panel de manera fluida e intuitiva. Estas características no solo facilitan la ejecución de órdenes de compra y venta, sino que también garantizan que tengamos acceso inmediato a la información de la cuenta comercial en tiempo real, lo que les permite tomar decisiones comerciales rápidas e informadas a medida que cambian las condiciones del mercado.
Además, la automatización de varios componentes, como la gestión de posiciones y la visualización de la información de la cuenta, agrega una capa de conveniencia y eficiencia al proceso comercial. Al permitir a los usuarios cerrar posiciones y órdenes con solo un clic y ofrecer opciones personalizables, el panel GUI se convierte en una herramienta poderosa para los traders modernos. Esta transformación fomenta un espacio de trabajo más limpio y organizado que permite una mejor concentración y productividad. Confiamos en que este artículo haya proporcionado información valiosa para mejorar los paneles GUI de MQL5 y esperamos que las explicaciones le resulten claras e informativas. ¡Feliz trading!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16146





- 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
¿Has leído siquiera el artículo?
El panel es bonito y funcional. Gracias.
Bienvenido
y hay planes para (minimizar/desmontar) el panel!? y estaría bien implementar el movimiento de la ventana del panel en el gráfico!
Impresionante panel. Gran artículo. El artículo no sólo describe cómo crear un panel que funcione perfectamente para un trader, sino que la información al respecto se presenta de forma tan clara que puede ser utilizada por principiantes como guía. Y la guía no es sólo para la creación de un panel, sino también una guía sobre cómo escribir correctamente y de manera competente los códigos para MQL5. Artículo muy valioso e informativo. ¡¡¡Gracias al autor, Allan Munene Mutiiria!!!
Saludos,
B.V. Dolgikh