Creación de un Panel de administración de operaciones en MQL5 (Parte IX): Organización del código (IV): Clase sobre el Panel de gestión de operaciones
Contenido
- Introducción
- Resumen del debate
- Clase del Panel de gestión de operaciones (CTradeManagementPanel)
- Integración con (New_Admin_Panel)
- Pruebas
- Conclusión
Introducción
En nuestras discusiones anteriores introdujimos la organización del código como una estrategia crucial para permitir una expansión fluida y escalable del proyecto del Panel de administración de operaciones, siguiendo el principio de separación de responsabilidades. Este enfoque me ha permitido centrarme en cada subpanel dentro del NewAdminPanel, garantizando que nuestro programa siga siendo modular y esté bien estructurado. Al desarrollar cada función de forma independiente, podemos perfeccionarlas para ofrecer la mejor funcionalidad posible.
Anteriormente, nuestro panel de operaciones tenía capacidades limitadas, pero ahora es más potente que nunca. Ofrece la flexibilidad de ejecutar operaciones rápidas al tiempo que incorpora una gestión de riesgos integrada, todo ello dentro de la misma interfaz. También es posible configurar órdenes pendientes directamente desde el panel, lo que agiliza el proceso de negociación.
Esta actualización aborda dos desafíos importantes:
- Crear programas amplios y bien organizados que sean más fáciles de desarrollar y mantener.
- Garantizar un acceso rápido a las operaciones de trading dentro de un Asesor Experto (EA) todo en uno que integra una interfaz de comunicación, una interfaz de gestión de operaciones y, en un futuro próximo, una interfaz de análisis.
A continuación, ofreceré un resumen de esta discusión, detallando cómo llevaremos a cabo estas mejoras hasta el final.
Resumen del debate
El objetivo principal de nuestros artículos es hacer práctico el uso de MQL5 aplicándolo a diversos proyectos. Hoy exploraremos el desarrollo de una clase de Panel de gestión de operaciones, teniendo en cuenta que, en MQL5, el encabezado de una clase contiene declaraciones de variables similares. En este contexto, todas las funciones de negociación que pretendemos incluir en nuestro panel heredarán de encabezados de clase integrados como CTrade, CDialog, CLabel y CEdit.
Una vez que la clase esté completamente desarrollada, integraremos sus métodos en el programa principal: el NewAdminPanel EA. Nuestra conversación no estaría completa sin compartir los resultados de las pruebas y proporcionar los archivos fuente, lo que les permitirá revisar la implementación, tomar prestadas ideas y experimentar con el código para mejorar sus propios proyectos.
En esta etapa, decidí centralizar la creación del Panel de inicio dentro del programa principal, ya que no aumentaba significativamente la longitud del código. Si bien el enfoque anterior tenía sus ventajas, opté por esta estructura para simplificar el desarrollo reduciendo las dependencias. Mi objetivo es mantener el enfoque del programa principal, utilizando una clase específica para cada función. Los elementos principales de la interfaz ahora se crean directamente dentro del programa principal, lo que da como resultado un diseño más ágil y eficiente. Como resultado, ya no llamamos a los métodos de la clase AdminHomeDialog en NewAdminPanel.
La imagen que aparece a continuación ilustra lo que crearemos al final de nuestra discusión. Pero esto es solo el principio; una vez construido, servirá como una base sólida para futuras mejoras y perfeccionamientos.
Panel de gestión de operaciones (al finalizar la discusión)
Ventajas de este proyecto
Las herramientas que estamos desarrollando aquí ofrecen varias ventajas clave:
- Operaciones rápidas: Ejecute sus operaciones de forma eficiente.
- Gestión de riesgos: Establezca el Stop Loss (SL) y el Take Profit (TP) antes de realizar las órdenes, y planifique sus operaciones con antelación utilizando órdenes pendientes.
- Funciones de comunicación: Envía mensajes a otros operadores a través del Panel de comunicaciones que creamos anteriormente.
- Aprende programación avanzada en MQL5: Obtén conocimientos más profundos sobre la programación orientada a objetos en MQL5.
- Participación de la comunidad: Comparte tus ideas y experiencias en la sección de comentarios.
- Reutilización: El panel CTradeManagementPanel se puede utilizar en otros proyectos.
Clase del Panel de gestión de operaciones (CTradeManagementPanel)
1. Estableciendo las bases con encabezados y macros
Antes de construir nada, necesitamos contar con las herramientas adecuadas. El primer paso consiste en incluir archivos de cabecera que gestionen la ejecución de las operaciones, los elementos de la interfaz de usuario y la gestión de eventos. En lugar de dispersar valores de diseño como el tamaño de los botones y el espaciado a lo largo del código, definimos macros al principio. Esto garantiza la coherencia en el diseño de la interfaz de usuario y facilita el ajuste posterior de las dimensiones sin tener que buscar en varios archivos.
#include <Trade\Trade.mqh> #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> //+------------------------------------------------------------------+ //| Defines for dimensions and spacing | //+------------------------------------------------------------------+ #define BUTTON_WIDTH 100 #define BUTTON_HEIGHT 30 #define DELETE_BUTTON_WIDTH 130 #define EDIT_WIDTH 80 #define EDIT_HEIGHT 20 #define DEFAULT_LABEL_HEIGHT 20 #define SECTION_LABEL_WIDTH 250 #define GAP 10 #define INPUT_LABEL_GAP 3
2. Definición de la clase principal y sus componentes
El núcleo del Panel de operaciones es una clase que gestiona tanto la interfaz como la lógica de ejecución de las operaciones. Al heredar de una clase de diálogo base, adquiere capacidades integradas de gestión de la interfaz de usuario, al tiempo que nos permite centrarnos en personalizar el comportamiento.
Dentro de esta clase, definimos variables miembro importantes. Estos incluyen una referencia al gráfico de operaciones, un objeto de ejecución de operaciones y una colección estructurada de controles de interfaz de usuario. Organizar estos elementos por separado garantiza que la interfaz de usuario permanezca claramente separada de la lógica que gestiona los pedidos.
//+------------------------------------------------------------------+ //| Trade Management Panel class | //+------------------------------------------------------------------+ class CTradeManagementPanel : public CAppDialog { protected: // Store chart and subwindow values for helper functions. long m_chart; int m_subwin; CTrade m_trade; // Trade object for executing trades // Section Labels CLabel m_secQuickLabel; CLabel m_secPendingLabel; CLabel m_secAllOpsLabel; // Section 1: Quick Execution CButton m_buyButton; // Market Buy button CButton m_sellButton; // Market Sell button CEdit m_volumeEdit; // Volume input CLabel m_lotLabel; // "Lot:" label above volume input // TP and SL for market orders—with a label to their right CEdit m_tpEdit; CLabel m_tpRightLabel; // "TP:" label CLabel m_tpErrorLabel; CEdit m_slEdit; CLabel m_slRightLabel; // "SL:" label CLabel m_slErrorLabel; // Section 2: Pending Orders // Column Headers for pending orders CLabel m_pendingPriceHeader; CLabel m_pendingTPHeader; CLabel m_pendingSLHeader; CLabel m_pendingExpHeader; // Buy pending orders CButton m_buyLimitButton; CEdit m_buyLimitPriceEdit; CEdit m_buyLimitTPEdit; CEdit m_buyLimitSLEdit; CEdit m_buyLimitExpEdit; // Expiration input for Buy Limit CButton m_buyStopButton; CEdit m_buyStopPriceEdit; CEdit m_buyStopTPEdit; CEdit m_buyStopSLEdit; CEdit m_buyStopExpEdit; // Expiration input for Buy Stop // Sell pending orders CButton m_sellLimitButton; CEdit m_sellLimitPriceEdit; CEdit m_sellLimitTPEdit; CEdit m_sellLimitSLEdit; CEdit m_sellLimitExpEdit; // Expiration input for Sell Limit CButton m_sellStopButton; CEdit m_sellStopPriceEdit; CEdit m_sellStopTPEdit; CEdit m_sellStopSLEdit; CEdit m_sellStopExpEdit; // Expiration input for Sell Stop // Section 3: All Order Operations CButton m_closeAllButton; CButton m_closeProfitButton; CButton m_closeLossButton; CButton m_closeBuyButton; CButton m_closeSellButton; CButton m_deleteAllOrdersButton; CButton m_deleteLimitOrdersButton; CButton m_deleteStopOrdersButton; CButton m_deleteStopLimitOrdersButton; CButton m_resetButton; //--- Helper: Create a text label using bool CreateLabelEx(CLabel &label, int x, int y, int height, string label_name, string text, color clr) { string unique_name = m_name + label_name; if(!label.Create(m_chart, unique_name, m_subwin, x, y, x, y+height)) return false; if(!Add(label)) return false; if(!label.Text(text)) return false; label.Color(clr); return true; } //--- Helper: Create and add a button control bool CreateButton(CButton &button, string name, int x, int y, int w = BUTTON_WIDTH, int h = BUTTON_HEIGHT, color clr = clrWhite) { if(!button.Create(m_chart, name, m_subwin, x, y, x+w, y+h)) return false; button.Text(name); button.Color(clr); if(!Add(button)) return false; return true; } //--- Helper: Create and add an edit control bool CreateEdit(CEdit &edit, string name, int x, int y, int w = EDIT_WIDTH, int h = EDIT_HEIGHT) { if(!edit.Create(m_chart, name, m_subwin, x, y, x+w, y+h)) return false; if(!Add(edit)) return false; return true; } // Event handler declarations virtual void OnClickBuy(); virtual void OnClickSell(); virtual void OnClickBuyLimit(); virtual void OnClickBuyStop(); virtual void OnClickSellLimit(); virtual void OnClickSellStop(); virtual void OnClickCloseAll(); virtual void OnClickCloseProfit(); virtual void OnClickCloseLoss(); virtual void OnClickCloseBuy(); virtual void OnClickCloseSell(); virtual void OnClickDeleteAllOrders(); virtual void OnClickDeleteLimitOrders(); virtual void OnClickDeleteStopOrders(); virtual void OnClickDeleteStopLimitOrders(); virtual void OnClickReset(); // Validation helpers for market orders bool ValidateBuyParameters(double volume, double tp, double sl, double ask); bool ValidateSellParameters(double volume, double tp, double sl, double bid); public: CTradeManagementPanel(); ~CTradeManagementPanel(); virtual bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2); virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); void Toggle(); };
3. Estructuración de la interfaz de usuario con secciones lógicas
Una interfaz bien organizada marca la diferencia. En lugar de distribuir los botones y los campos de entrada al azar, dividimos el panel en tres secciones principales.
- La primera sección está destinada a órdenes de mercado rápidas, donde los usuarios pueden comprar o vender al instante, establecer el volumen de la operación y configurar los niveles de stop loss y take profit.
- La segunda sección está dedicada a los pedidos pendientes. Aquí, los usuarios pueden especificar precios, plazos de vencimiento y tipos de órdenes, como órdenes de compra limitada o órdenes de venta con límite.
- La tercera sección gestiona acciones masivas, como cerrar todas las operaciones, cerrar solo las rentables o eliminar las órdenes pendientes. Mantener estas funciones agrupadas garantiza que la interfaz siga siendo fácil de navegar.
//+------------------------------------------------------------------+ //| Create the trade management panel | //+------------------------------------------------------------------+ bool CTradeManagementPanel::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) { // Save chart and subwin for later use. m_chart = chart; m_subwin = subwin; if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2)) return false; int curX = GAP; int curY = GAP; // Section 1: Quick Order Execution if(!CreateLabelEx(m_secQuickLabel, curX, curY-10, DEFAULT_LABEL_HEIGHT, "SecQuick", "Quick Order Execution:", clrBlack)) return false; curY += DEFAULT_LABEL_HEIGHT + GAP; // Market order row: if(!CreateButton(m_buyButton, "Buy", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen)) return false; int volX = curX + BUTTON_WIDTH + GAP; if(!CreateLabelEx(m_lotLabel, volX, curY - DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "Lot", "Lot Size:", clrBlack)) return false; if(!CreateEdit(m_volumeEdit, "VolumeEdit", volX, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; double minVolume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); m_volumeEdit.Text(DoubleToString(minVolume, 2)); int sellBtnX = volX + EDIT_WIDTH + GAP; if(!CreateButton(m_sellButton, "Sell", sellBtnX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed)) return false; curY += BUTTON_HEIGHT + GAP; // Next row: TP and SL for market orders if(!CreateEdit(m_tpEdit, "TPEdit", 4*curX+ GAP , curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_tpEdit.Text("0.00000"); if(!CreateLabelEx(m_tpRightLabel, curX + INPUT_LABEL_GAP, curY, DEFAULT_LABEL_HEIGHT, "TP", "TP:", clrBlack)) return false; if(!CreateLabelEx(m_tpErrorLabel, curX, curY + DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "TPError", "", clrBlack)) return false; int slX = 2*EDIT_WIDTH ; if(!CreateEdit(m_slEdit, "SLEdit", slX + 4*curX, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_slEdit.Text("0.00000"); if(!CreateLabelEx(m_slRightLabel, slX , curY, DEFAULT_LABEL_HEIGHT, "SL", "SL:", clrBlack)) return false; if(!CreateLabelEx(m_slErrorLabel, slX, curY + DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "SLError", "", clrBlack)) return false; curY += EDIT_HEIGHT + GAP*2; // Section 2: Pending Orders if(!CreateLabelEx(m_secPendingLabel, curX, curY, DEFAULT_LABEL_HEIGHT, "SecPend", "Pending Orders:", clrBlack)) return false; curY += DEFAULT_LABEL_HEIGHT + GAP; // Column headers for pending orders (each label includes a colon) int headerX = curX + BUTTON_WIDTH + GAP; if(!CreateLabelEx(m_pendingPriceHeader, headerX, curY, DEFAULT_LABEL_HEIGHT, "PendPrice", "Price:", clrBlack)) return false; if(!CreateLabelEx(m_pendingTPHeader, headerX + EDIT_WIDTH + GAP, curY, DEFAULT_LABEL_HEIGHT, "PendTP", "TP:", clrBlack)) return false; if(!CreateLabelEx(m_pendingSLHeader, headerX + 2*(EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, "PendSL", "SL:", clrBlack)) return false; if(!CreateLabelEx(m_pendingExpHeader, headerX + 3*(EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, "PendExp", "Expiration Date:", clrBlack)) return false; curY += DEFAULT_LABEL_HEIGHT + GAP; // Prepare default expiration value as current day end time. datetime now = TimeCurrent(); string exp_default = TimeToString(now, TIME_DATE) + " 23:59:59"; // --- Buy Pending Orders --- // Buy Limit Order row: if(!CreateButton(m_buyLimitButton, "Buy Limit", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue)) return false; int pendingX = curX + BUTTON_WIDTH + GAP; if(!CreateEdit(m_buyLimitPriceEdit, "BuyLimitPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; string askStr = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_ASK), 5); m_buyLimitPriceEdit.Text(askStr); int pending2X = pendingX + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyLimitTPEdit, "BuyLimitTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_buyLimitTPEdit.Text("0.00000"); int pending3X = pending2X + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyLimitSLEdit, "BuyLimitSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_buyLimitSLEdit.Text("0.00000"); int pending4X = pending3X + EDIT_WIDTH + GAP; // Double width for expiration input if(!CreateEdit(m_buyLimitExpEdit, "BuyLimitExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT)) return false; m_buyLimitExpEdit.Text(exp_default); curY += BUTTON_HEIGHT + GAP; // Buy Stop Order row: if(!CreateButton(m_buyStopButton, "Buy Stop", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue)) return false; pendingX = curX + BUTTON_WIDTH + GAP; if(!CreateEdit(m_buyStopPriceEdit, "BuyStopPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_buyStopPriceEdit.Text(askStr); pending2X = pendingX + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyStopTPEdit, "BuyStopTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_buyStopTPEdit.Text("0.00000"); pending3X = pending2X + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyStopSLEdit, "BuyStopSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_buyStopSLEdit.Text("0.00000"); pending4X = pending3X + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyStopExpEdit, "BuyStopExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT)) return false; m_buyStopExpEdit.Text(exp_default); curY += BUTTON_HEIGHT + GAP; // --- Sell Pending Orders --- // Sell Limit Order row: if(!CreateButton(m_sellLimitButton, "Sell Limit", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed)) return false; pendingX = curX + BUTTON_WIDTH + GAP; if(!CreateEdit(m_sellLimitPriceEdit, "SellLimitPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; string bidStr = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_BID), 5); m_sellLimitPriceEdit.Text(bidStr); pending2X = pendingX + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellLimitTPEdit, "SellLimitTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_sellLimitTPEdit.Text("0.00000"); pending3X = pending2X + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellLimitSLEdit, "SellLimitSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_sellLimitSLEdit.Text("0.00000"); pending4X = pending3X + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellLimitExpEdit, "SellLimitExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT)) return false; m_sellLimitExpEdit.Text(exp_default); curY += BUTTON_HEIGHT + GAP; // Sell Stop Order row: if(!CreateButton(m_sellStopButton, "Sell Stop", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed)) return false; pendingX = curX + BUTTON_WIDTH + GAP; if(!CreateEdit(m_sellStopPriceEdit, "SellStopPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_sellStopPriceEdit.Text(bidStr); pending2X = pendingX + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellStopTPEdit, "SellStopTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_sellStopTPEdit.Text("0.00000"); pending3X = pending2X + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellStopSLEdit, "SellStopSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT)) return false; m_sellStopSLEdit.Text("0.00000"); pending4X = pending3X + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellStopExpEdit, "SellStopExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT)) return false; m_sellStopExpEdit.Text(exp_default); curY += BUTTON_HEIGHT + GAP; // Section 3: All Order Operations if(!CreateLabelEx(m_secAllOpsLabel, curX, curY, DEFAULT_LABEL_HEIGHT, "SecAllOps", "All Order Operations:", clrBlack)) return false; curY += DEFAULT_LABEL_HEIGHT + GAP; int deleteX = curX + 2*BUTTON_WIDTH + 2*GAP; int deleteY = curY ; CreateButton(m_closeAllButton, "Close All", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed); CreateButton(m_closeProfitButton, "Close Profit", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen); CreateButton(m_deleteLimitOrdersButton, "Delete Limits", deleteX, curY, DELETE_BUTTON_WIDTH, BUTTON_HEIGHT, clrRed); curY += BUTTON_HEIGHT + GAP; CreateButton(m_closeLossButton, "Close Loss", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed); CreateButton(m_closeBuyButton, "Close Buy", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen); CreateButton(m_deleteStopOrdersButton, "Delete Stops", deleteX, curY , DELETE_BUTTON_WIDTH+10, BUTTON_HEIGHT, clrRed); CreateButton(m_resetButton, "Reset", deleteX+ 2*BUTTON_WIDTH-3*curX, curY, DELETE_BUTTON_WIDTH, 2*BUTTON_HEIGHT, clrRed); curY += BUTTON_HEIGHT + GAP; CreateButton(m_closeSellButton, "Close Sell", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue); CreateButton(m_deleteAllOrdersButton, "Delete All", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed); CreateButton(m_deleteStopLimitOrdersButton, "Delete StopLimits", deleteX, curY , DELETE_BUTTON_WIDTH+10, BUTTON_HEIGHT, clrDarkRed); curY += BUTTON_HEIGHT + GAP; return true; }
4. Automatización de la creación de controles de interfaz de usuario con funciones auxiliares
Colocar manualmente cada botón y campo de entrada haría que el código fuera repetitivo y más difícil de mantener. En su lugar, escribimos funciones auxiliares que se encargan de la creación, el posicionamiento y el estilo de los controles.
Al centralizar esta lógica, mantenemos organizado el código de la interfaz de usuario. Si decidimos cambiar la apariencia de los botones o las etiquetas, solo necesitamos actualizar una función, en lugar de modificar docenas de líneas en todo el código.
//--- Helper: Create a text label using bool CreateLabelEx(CLabel &label, int x, int y, int height, string label_name, string text, color clr) { string unique_name = m_name + label_name; if(!label.Create(m_chart, unique_name, m_subwin, x, y, x, y+height)) return false; if(!Add(label)) return false; if(!label.Text(text)) return false; label.Color(clr); return true; } //--- Helper: Create and add a button control bool CreateButton(CButton &button, string name, int x, int y, int w = BUTTON_WIDTH, int h = BUTTON_HEIGHT, color clr = clrWhite) { if(!button.Create(m_chart, name, m_subwin, x, y, x+w, y+h)) return false; button.Text(name); button.Color(clr); if(!Add(button)) return false; return true; } //--- Helper: Create and add an edit control bool CreateEdit(CEdit &edit, string name, int x, int y, int w = EDIT_WIDTH, int h = EDIT_HEIGHT) { if(!edit.Create(m_chart, name, m_subwin, x, y, x+w, y+h)) return false; if(!Add(edit)) return false; return true; }
5. Gestión de las interacciones del usuario mediante un sistema centralizado de eventos.
Una vez que la interfaz de usuario esté lista, necesitamos una forma de gestionar las interacciones. En lugar de asignar funciones independientes a cada botón, utilizamos un gestor de eventos centralizado.
Este despachador escucha las acciones del usuario, determina qué control desencadenó el evento y llama a la función apropiada. Esto mantiene la organización y el orden en la gestión del evento. Al hacer clic en un botón para realizar una operación, se enviarán los datos de entrada a la lógica de ejecución de la orden, mientras que al hacer clic en un botón de reinicio se borrarán los campos.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ bool CTradeManagementPanel::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK) { if(sparam == m_buyButton.Name()) OnClickBuy(); else if(sparam == m_sellButton.Name()) OnClickSell(); else if(sparam == m_buyLimitButton.Name()) OnClickBuyLimit(); else if(sparam == m_buyStopButton.Name()) OnClickBuyStop(); else if(sparam == m_sellLimitButton.Name()) OnClickSellLimit(); else if(sparam == m_sellStopButton.Name()) OnClickSellStop(); else if(sparam == m_closeAllButton.Name()) OnClickCloseAll(); else if(sparam == m_closeProfitButton.Name()) OnClickCloseProfit(); else if(sparam == m_closeLossButton.Name()) OnClickCloseLoss(); else if(sparam == m_closeBuyButton.Name()) OnClickCloseBuy(); else if(sparam == m_closeSellButton.Name()) OnClickCloseSell(); else if(sparam == m_deleteAllOrdersButton.Name()) OnClickDeleteAllOrders(); else if(sparam == m_deleteLimitOrdersButton.Name()) OnClickDeleteLimitOrders(); else if(sparam == m_deleteStopOrdersButton.Name()) OnClickDeleteStopOrders(); else if(sparam == m_deleteStopLimitOrdersButton.Name()) OnClickDeleteStopLimitOrders(); else if(sparam == m_resetButton.Name()) OnClickReset(); // Handle reset button click } return true; }
6. Validación y ejecución de órdenes de mercado
Antes de ejecutar una operación, el sistema verifica los datos introducidos para evitar errores. Comprueba si el volumen de operaciones se encuentra dentro de los límites aceptables y si los valores de stop loss y take profit están configurados correctamente en relación con el precio de mercado.
Si la entrada es válida, el objeto de ejecución de la operación procesa la orden. De lo contrario, el sistema alerta al usuario e impide que se realice una operación errónea. Este paso de validación es crucial para evitar errores costosos y garantizar que el panel funcione de manera fiable.
//+------------------------------------------------------------------+ //| Validate Buy order parameters (Market orders) | //+------------------------------------------------------------------+ bool CTradeManagementPanel::ValidateBuyParameters(double volume, double tp, double sl, double ask) { bool valid = true; m_tpErrorLabel.Text(""); m_slErrorLabel.Text(""); if(volume <= 0) { m_tpErrorLabel.Text("Invalid volume"); m_tpErrorLabel.Color(clrRed); valid = false; } if(tp != 0 && tp <= ask) { m_tpErrorLabel.Text("Invalid TP"); m_tpErrorLabel.Color(clrRed); valid = false; } if(sl != 0 && sl >= ask) { m_slErrorLabel.Text("Invalid SL"); m_slErrorLabel.Color(clrRed); valid = false; } return valid; } //+------------------------------------------------------------------+ //| Validate Sell order parameters (Market orders) | //+------------------------------------------------------------------+ bool CTradeManagementPanel::ValidateSellParameters(double volume, double tp, double sl, double bid) { bool valid = true; m_tpErrorLabel.Text(""); m_slErrorLabel.Text(""); if(volume <= 0) { m_tpErrorLabel.Text("Invalid volume"); m_tpErrorLabel.Color(clrRed); valid = false; } if(tp != 0 && tp >= bid) { m_tpErrorLabel.Text("Invalid TP"); m_tpErrorLabel.Color(clrRed); valid = false; } if(sl != 0 && sl <= bid) { m_slErrorLabel.Text("Invalid SL"); m_slErrorLabel.Color(clrRed); valid = false; } return valid; }
7. Gestión de pedidos pendientes con parámetros adicionales
Las órdenes pendientes requieren más ajustes que las órdenes de mercado. Los usuarios deben especificar un precio de entrada y un tiempo de vencimiento, por lo que ampliamos nuestras funciones de validación para que puedan gestionar estos datos.
Una función auxiliar interpreta la configuración de caducidad, indicando si el usuario desea un tiempo fijo o la opción "Válido hasta su cancelación". Esta estructura garantiza que los pedidos pendientes cumplan con las normas adecuadas antes de ser enviados.
//+------------------------------------------------------------------+ //| Helper: Parse expiration input | //+------------------------------------------------------------------+ void ParseExpiration(string sExp, ENUM_ORDER_TYPE_TIME &type_time, datetime &expiration) { if(StringCompare(StringToUpper(sExp), "GTC") == 0) { type_time = ORDER_TIME_GTC; expiration = 0; } else { type_time = ORDER_TIME_SPECIFIED; expiration = StringToTime(sExp); } } //+------------------------------------------------------------------+ //| Button click handlers - Pending Orders | //+------------------------------------------------------------------+ void CTradeManagementPanel::OnClickBuyLimit() { double price = StringToDouble(m_buyLimitPriceEdit.Text()); double tp = StringToDouble(m_buyLimitTPEdit.Text()); double sl = StringToDouble(m_buyLimitSLEdit.Text()); double volume = StringToDouble(m_volumeEdit.Text()); string sExp = m_buyLimitExpEdit.Text(); ENUM_ORDER_TYPE_TIME type_time; datetime expiration; ParseExpiration(sExp, type_time, expiration); m_trade.BuyLimit(volume, price, Symbol(), sl, tp, type_time, expiration, ""); } void CTradeManagementPanel::OnClickBuyStop() { double price = StringToDouble(m_buyStopPriceEdit.Text()); double tp = StringToDouble(m_buyStopTPEdit.Text()); double sl = StringToDouble(m_buyStopSLEdit.Text()); double volume = StringToDouble(m_volumeEdit.Text()); string sExp = m_buyStopExpEdit.Text(); ENUM_ORDER_TYPE_TIME type_time; datetime expiration; ParseExpiration(sExp, type_time, expiration); m_trade.BuyStop(volume, price, Symbol(), sl, tp, type_time, expiration, ""); } void CTradeManagementPanel::OnClickSellLimit() { double price = StringToDouble(m_sellLimitPriceEdit.Text()); double tp = StringToDouble(m_sellLimitTPEdit.Text()); double sl = StringToDouble(m_sellLimitSLEdit.Text()); double volume = StringToDouble(m_volumeEdit.Text()); string sExp = m_sellLimitExpEdit.Text(); ENUM_ORDER_TYPE_TIME type_time; datetime expiration; ParseExpiration(sExp, type_time, expiration); m_trade.SellLimit(volume, price, Symbol(), sl, tp, type_time, expiration, ""); } void CTradeManagementPanel::OnClickSellStop() { double price = StringToDouble(m_sellStopPriceEdit.Text()); double tp = StringToDouble(m_sellStopTPEdit.Text()); double sl = StringToDouble(m_sellStopSLEdit.Text()); double volume = StringToDouble(m_volumeEdit.Text()); string sExp = m_sellStopExpEdit.Text(); ENUM_ORDER_TYPE_TIME type_time; datetime expiration; ParseExpiration(sExp, type_time, expiration); m_trade.SellStop(volume, price, Symbol(), sl, tp, type_time, expiration, ""); }
8. Implementación de operaciones de pedidos masivos para una gestión comercial eficiente
Gestionar múltiples operaciones manualmente puede resultar tedioso, por lo que incluimos funciones que permiten a los usuarios aplicar acciones a varias órdenes a la vez.
Los usuarios pueden cerrar todas las operaciones, cerrar solo las que sean rentables o las que generen pérdidas, o eliminar las órdenes pendientes. Los gestores de eventos para estas acciones recorren las posiciones disponibles y aplican la operación solicitada de manera eficiente. Esto hace que gestionar varios pedidos sea mucho más rápido y sencillo.
//+------------------------------------------------------------------+ //| Button click handlers - All Order Operations | //+------------------------------------------------------------------+ void CTradeManagementPanel::OnClickCloseAll() { for(int i = PositionsTotal()-1; i >= 0; i--) m_trade.PositionClose(PositionGetTicket(i)); } void CTradeManagementPanel::OnClickCloseProfit() { for(int i = PositionsTotal()-1; i >= 0; i--) if(PositionGetDouble(POSITION_PROFIT) > 0) m_trade.PositionClose(PositionGetTicket(i)); } void CTradeManagementPanel::OnClickCloseLoss() { for(int i = PositionsTotal()-1; i >= 0; i--) if(PositionGetDouble(POSITION_PROFIT) < 0) m_trade.PositionClose(PositionGetTicket(i)); } void CTradeManagementPanel::OnClickCloseBuy() { for(int i = PositionsTotal()-1; i >= 0; i--) if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) m_trade.PositionClose(PositionGetTicket(i)); } void CTradeManagementPanel::OnClickCloseSell() { for(int i = PositionsTotal()-1; i >= 0; i--) if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) m_trade.PositionClose(PositionGetTicket(i)); } void CTradeManagementPanel::OnClickDeleteAllOrders() { for(int i = OrdersTotal()-1; i >= 0; i--) m_trade.OrderDelete(OrderGetTicket(i)); } void CTradeManagementPanel::OnClickDeleteLimitOrders() { for(int i = OrdersTotal()-1; i >= 0; i--) if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_LIMIT || OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_LIMIT) m_trade.OrderDelete(OrderGetTicket(i)); } void CTradeManagementPanel::OnClickDeleteStopOrders() { for(int i = OrdersTotal()-1; i >= 0; i--) if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP || OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP) m_trade.OrderDelete(OrderGetTicket(i)); } void CTradeManagementPanel::OnClickDeleteStopLimitOrders() { for(int i = OrdersTotal()-1; i >= 0; i--) if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP_LIMIT || OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP_LIMIT) m_trade.OrderDelete(OrderGetTicket(i)); }
9. Agregar controles de reinicio y visibilidad para una mejor usabilidad
Para mejorar la experiencia del usuario, hemos añadido dos funciones pequeñas pero útiles. El primero es un botón de reinicio que borra todos los campos de entrada, devolviendo el panel a su estado predeterminado. Esto resulta útil cuando los usuarios quieren volver a empezar después de realizar cambios.
La segunda función es un interruptor de visibilidad que permite a los usuarios mostrar u ocultar el panel. Esto mantiene el espacio de trabajo ordenado, al tiempo que facilita el acceso al panel cuando sea necesario.
//+------------------------------------------------------------------+ //| Reset all input fields to default values | //+------------------------------------------------------------------+ void CTradeManagementPanel::OnClickReset() { // Reset volume to minimum allowed double minVolume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); m_volumeEdit.Text(DoubleToString(minVolume, 2)); // Reset TP and SL for market orders m_tpEdit.Text("0.00000"); m_slEdit.Text("0.00000"); // Reset pending order prices to current ASK/BID double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); m_buyLimitPriceEdit.Text(DoubleToString(ask, 5)); m_buyStopPriceEdit.Text(DoubleToString(ask, 5)); m_sellLimitPriceEdit.Text(DoubleToString(bid, 5)); m_sellStopPriceEdit.Text(DoubleToString(bid, 5)); // Reset pending order TP/SL to 0 m_buyLimitTPEdit.Text("0.00000"); m_buyLimitSLEdit.Text("0.00000"); m_buyStopTPEdit.Text("0.00000"); m_buyStopSLEdit.Text("0.00000"); m_sellLimitTPEdit.Text("0.00000"); m_sellLimitSLEdit.Text("0.00000"); m_sellStopTPEdit.Text("0.00000"); m_sellStopSLEdit.Text("0.00000"); // Reset expiration dates to current day's end datetime now = TimeCurrent(); string exp_default = TimeToString(now, TIME_DATE) + " 23:59:59"; m_buyLimitExpEdit.Text(exp_default); m_buyStopExpEdit.Text(exp_default); m_sellLimitExpEdit.Text(exp_default); m_sellStopExpEdit.Text(exp_default); // Clear error messages m_tpErrorLabel.Text(""); m_slErrorLabel.Text(""); }
He adjuntado el código fuente completo debajo de este artículo, que puedes descargar. En la siguiente sección, le guiaré paso a paso sobre cómo integrar la clase del Panel de gestión de operaciones con el programa principal.
Integración con (New_Admin_Panel)
1. Incluir el encabezado de gestión vomercial en el sistema New_Admin_Panel
En primer lugar, incorporamos el TradeManagementPanel incluyendo su archivo de cabecera. Esto hace que todo lo que necesitamos para ejecutar y gestionar operaciones esté disponible dentro de nuestro Panel de administración. Sencillo, pero esencial.
#include <TradeManagementPanel.mqh>
2. Configuración de una instancia del Panel de operaciones
Necesitamos que el Panel de operaciones sea accesible en todo el EA, por lo que lo definimos como un puntero global. De esta forma, podemos comprobar si ya está creado e interactuar con él cuando sea necesario.
CTradeManagementPanel *g_tradePanel = NULL; 3. Agregar un botón de gestión de operaciones
Los usuarios necesitan una forma de abrir el Panel de operaciones, por lo que hemos añadido un botón específico dentro del Panel de inicio de administración. Utilizamos una función auxiliar para garantizar la coherencia en el diseño y el estilo. Este botón se encuentra directamente en la interfaz, listo para abrir el Panel de operaciones al hacer clic.
CButton g_tradeMgmtButton;
4. Hacer que el Panel de operaciones sea interactivo
Ahora, aquí es donde la cosa se pone interesante. Hemos creado una función que gestiona el ciclo de vida del Panel de operaciones.
- Si el Panel de operaciones no existe, lo creamos.
- Si ya está ahí, simplemente lo activamos o desactivamos.
void HandleTradeManagement() { if(g_tradePanel == NULL) { g_tradePanel = new CTradeManagementPanel(); if(!g_tradePanel.Create(g_chart_id, "TradeManagementPanel", g_subwin, 310, 20, 310+565, 20+510)) { delete g_tradePanel; g_tradePanel = NULL; Print("Failed to create Trade Management Panel"); return; } } g_tradePanel.Toggle(); ChartRedraw(); }
Con esta configuración, los usuarios obtienen acceso instantáneo a la gestión de operaciones con solo pulsar un botón.
5. Integrando la gestión comercial en los eventos
A continuación, nos aseguramos de que el Panel de operaciones responda a las acciones del usuario. Dentro del controlador de eventos, reenviamos las interacciones al panel si está abierto. Esto garantiza que el panel reaccione en tiempo real, ya sea ejecutando órdenes, ajustando límites de pérdidas o cerrando operaciones.
if(g_tradePanel != NULL && g_tradePanel.IsVisible()) return g_tradePanel.OnEvent(id, lparam, dparam, sparam);
6. Limpieza tras el cierre del EA
Cuando se elimina el EA necesitamos liberar memoria y evitar fugas. Eso significa destruir correctamente el Panel de operaciones.
if(g_tradePanel != NULL) { g_tradePanel.Destroy(reason); delete g_tradePanel; g_tradePanel = NULL; }
No quedan instancias. Sin desperdicio de recursos. Un apagado limpio. El archivo fuente completo del New_Admin_Panel se adjunta a continuación para su descarga. Puedes seguir el mismo procedimiento con algunas modificaciones para integrar el Panel de gestión de operaciones en tus propios proyectos. En la siguiente sección, compartiremos los resultados de las pruebas.
Pruebas
Tras compilar el código correctamente, lo ejecuté en la plataforma MetaTrader 5. A continuación se muestran ilustraciones que ilustran la correcta implementación del Panel de gestión de operaciones desde el Panel de administración. El Panel de inicio (Panel de administración) nos permite activar y desactivar el Panel de gestión de operaciones, lo que nos brinda la flexibilidad de ver el gráfico completo cuando sea necesario. Con el panel de gestión de operaciones activo, podemos operar sus controles directamente en el gráfico mientras seguimos observando la evolución del precio, lo que supone una ventaja para el scalping. Vea la imagen a continuación.

Uso del Panel de Administraciónl
Conclusión
El desarrollo de la clase de Panel de gestión de operaciones supone otro hito, ya que aplicamos la modularización para estructurar nuestro código de forma eficaz, lo que permite reutilizar grandes componentes del programa en diferentes proyectos. Como ya hemos comentado anteriormente, este enfoque ofrece numerosas ventajas. Recorrimos el proceso de desarrollo paso a paso, demostrando cómo se hace, y estoy seguro de que hay notas valiosas que se pueden extraer.
No pretendo ser perfecto en este campo; estoy en constante aprendizaje y mejora. Puede que haya errores que quienes tengan más experiencia puedan identificar, y agradezco los comentarios constructivos para beneficio de todos. Aprecio enormemente las ventajas que ofrece este panel a quienes lo encuentran útil.
También quiero expresar mi agradecimiento a Omega J Msigwa por su contribución al código fuente con el código fuente del Panel informativo, que nos proporcionó información muy útil durante este proceso de desarrollo. No dudes en ampliar y modificar el código adjunto para perfeccionar tus habilidades con MQL5 y crear herramientas aún más potentes.
| Archivo | Descripción |
|---|---|
| TradeManagementPanel.mqh | Código fuente del encabezado de clase que proporciona una interfaz gráfica para ejecutar, gestionar y modificar operaciones de forma eficiente dentro del EA New_Admin_Panel. También se puede integrar con otros asesores expertos. |
| New_Admin_Panel.mq5 | El código fuente del EA New_Admin_Panel actúa como interfaz centralizada para gestionar la ejecución de operaciones, las comunicaciones, los análisis y otras funciones administrativas cuando se compila para ejecutarse dentro de la plataforma de negociación. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17396
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.
Asesor de autoaprendizaje con red neuronal basada en matriz de estados
Redes neuronales en el trading: Generalización de series temporales sin vinculación a datos (Módulos básicos del modelo)
Particularidades del trabajo con números del tipo double en MQL4
Características del Wizard MQL5 que debe conocer (Parte 59): Aprendizaje por refuerzo (DDPG) con patrones de media móvil y oscilador estocástico (II)
- 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