Creación de un Panel de administración de operaciones en MQL5 (Parte IX): Organización del código (I)
Introducción
El código extenso puede ser difícil de seguir, especialmente cuando carece de una organización adecuada. Esto a menudo conduce al abandono de proyectos. ¿Pero eso significa que tengo que abandonar el proyecto del Panel de administración de operaciones ahora que ha crecido con múltiples paneles integrados? ¡Por supuesto que no! En cambio, necesitamos estrategias para que siga funcionando sin problemas.
Esto nos lleva al tema de hoy, en el que exploraremos cómo la organización del código puede mejorar el desarrollo de algoritmos en MQL5. El éxito de MQL5 y otros proyectos a gran escala a menudo se puede atribuir a su enfoque estructurado, que les permite gestionar y mantener de manera eficiente amplias bases de código.
Sin la documentación y la estructura adecuadas, mantener un código se convierte en un reto, lo que dificulta aún más las modificaciones futuras.
En este debate, exploraremos soluciones prácticas a estos retos, centrándonos en estructurar el panel de administración comercial para una escalabilidad a largo plazo. La organización del código no es solo una cuestión de comodidad, sino que es un factor fundamental para escribir programas eficientes y fáciles de mantener. Al adoptar estas prácticas recomendadas, podemos garantizar que los proyectos MQL5 sigan siendo robustos, compartibles y escalables, lo que permite a los desarrolladores individuales crear y mantener aplicaciones complejas.
Antes de profundizar más, permítanme desglosar los puntos clave de este debate:
- Resumen del debate
- Comprensión de la organización del código
- Implementación en el Panel de Administración (Admin Panel EA)
- Resultados y pruebas
- Un ejemplo integrado de un código bien organizado
- Conclusión
Resumen del debate
En la discusión anterior de esta serie, fuimos testigos de una importante expansión de nuestro programa al introducir paneles más especializados en el Panel de administración, transformándolo en un panel de control esencial para cualquier operador. Con estas incorporaciones, ahora contamos con cuatro paneles: el Panel de inicio de administración, el Panel de comunicaciones, el Panel de gestión comercial y el Panel de análisis. El código ha crecido considerablemente, esbozando la estructura principal, pero aún queda mucho por hacer para mejorar la funcionalidad de cada característica.
Mientras pensaba en dar el siguiente paso y añadir aún más funciones, me di cuenta de la importancia de revisar todo el código para organizarlo mejor. Fue entonces cuando surgió la idea de este tema. En lugar de limitarme a presentar un programa terminado, consideré que sería valioso recorrer contigo el proceso de perfeccionamiento y organización del código. En la siguiente sección, revelaremos más información sobre la organización del código basada en mi investigación.
Creo que al final de este debate, alguien habrá adquirido los conocimientos necesarios para responder a estas preguntas:
- ¿Cómo desarrollar programas de gran envergadura?
- ¿Cómo puedo hacer que otros comprendan mi extenso programa?
Comprensión de la organización del código
Según diversas fuentes, la organización del código se refiere a la práctica de estructurar y ordenar el código de manera que se mejore la legibilidad, la mantenibilidad y la escalabilidad. Un código bien organizado facilita a los desarrolladores la comprensión, la depuración y la ampliación de sus programas.
Como mencionó una vez el ingeniero de software Zhaojun Zhang, «La organización del código es como el orden de tu casa: no es necesario ordenarla todos los días, y puedes seguir viviendo en tu casa independientemente de lo desordenada que esté, siempre y cuando puedas tolerarlo». Solo te persigue cuando necesitas desesperadamente encontrar algo que no has tocado en mucho tiempo o cuando quieres invitar a tus amigos a una cena elegante.
Creo que esta analogía deja claro que la organización del código es importante para tu propio flujo de trabajo, y también para otras personas que puedan trabajar con tu código. Analicemos estos conceptos clave de organización del código (legibilidad, mantenibilidad y escalabilidad) y exploremos su importancia, particularmente en el contexto del desarrollo de algoritmos en MQL5.
1. Legibilidad:
La legibilidad se refiere a la facilidad con la que alguien puede comprender la lógica y la estructura del código. En el contexto de MQL5, esto es especialmente crucial porque el código podría ser trabajado por varios desarrolladores, e incluso si trabajas solo, querrás revisar o depurar tu propio código en algún momento, como mencioné anteriormente.
Características principales:
- Nombres de variables claros: Utilice nombres significativos para variables, funciones y clases. En lugar de utilizar nombres vagos como a, b o temp, elige nombres descriptivos que transmitan el propósito, como movingAveragePeriod o signalStrength.
- Comentarios: Los buenos comentarios explican por qué existen ciertos bloques de código, no solo qué hacen. Esto es esencial para documentar la intención del algoritmo.
- Formato consistente: La sangría y el interlineado ayudan a dividir el código en bloques legibles. Por ejemplo, utilice una sangría consistente para bucles, condicionales y funciones.
- Código modular: Dividir el código en funciones o clases pequeñas y autocontenidas que se encargan de tareas específicas (como calcular una media móvil o comprobar una condición de negociación) mejora la legibilidad.
- Depuración rápida: Un código legible facilita la detección y corrección de errores.
- Colaboración: Si tu código es limpio y comprensible, es mucho más fácil para otros desarrolladores colaborar o ayudarte a solucionar problemas.
- Incorporación más rápida: Al retomar un proyecto, un código legible garantiza que no pierda tiempo volviendo a comprender su propio trabajo.
2. Mantenibilidad:
La mantenibilidad es la facilidad con la que se puede modificar o ampliar el código a lo largo del tiempo, especialmente cuando se añaden nuevas funciones o se corrigen errores. En el trading algorítmico, como en MQL5, donde las estrategias a menudo evolucionan, la mantenibilidad es fundamental para el éxito a largo plazo.
Características principales:
- Modularidad: Al utilizar funciones o clases para compartimentar diferentes tareas (por ejemplo, una función para gestionar la ejecución de operaciones, otra para calcular indicadores), se crean partes aisladas del sistema. Se pueden realizar cambios en un área sin afectar a otras.
- Separación de responsabilidades: Cada parte del código debe tener una sola responsabilidad. Por ejemplo, la lógica para realizar operaciones debe ser independiente de la lógica para evaluar las condiciones del mercado.
- Uso de bibliotecas y funciones integradas: En lugar de reinventar la rueda, aproveche las funciones y bibliotecas integradas de MQL5 para tareas comunes como promedios móviles o colocación de órdenes, lo que puede reducir la complejidad y los errores.
- Control de versiones: utilice sistemas de control de versiones (por ejemplo, Git y MQL5 Storage) para realizar un seguimiento de los cambios, de modo que pueda revertirlos si una modificación introduce errores o comportamientos inesperados.
Beneficios:
- Modificaciones futuras: A medida que las estrategias evolucionan, mantener una base de código bien estructurada permite a los desarrolladores implementar nuevas funciones o realizar ajustes con un esfuerzo mínimo.
- Corrección de errores: Cuando se detectan errores, el código mantenible permite resolver los problemas rápidamente sin afectar a otras partes del sistema.
- Eficiencia: Los desarrolladores dedican menos tiempo a averiguar cómo funciona el código, lo que se traduce en actualizaciones más rápidas y menos errores.
3. Escalabilidad:
La escalabilidad se refiere a la capacidad del código para gestionar cantidades crecientes de trabajo o adaptarse a requisitos funcionales y de datos cada vez mayores. A medida que las estrategias comerciales se vuelven más complejas y requieren un mayor volumen de datos, la escalabilidad se convierte en un factor fundamental para garantizar un funcionamiento fluido.
Características principales:
- Algoritmos eficientes: En el trading algorítmico, es posible que tenga que procesar grandes volúmenes de datos históricos, ejecutar muchas operaciones o analizar múltiples activos simultáneamente. Optimizar tus algoritmos para mejorar la velocidad y el uso de la memoria es fundamental.
- Estructuras de datos: Elegir las estructuras de datos adecuadas, como matrices, listas o mapas, ayuda a gestionar de forma eficiente conjuntos de datos más grandes. MQL5 proporciona datos sobre Estructuras, clases e interfaces, que pueden aprovecharse para ampliar su estrategia.
- Procesamiento paralelo: MQL5 admite multithreading (multiprocesamiento), lo que le permite ejecutar varias tareas en paralelo. Esto resulta especialmente útil en estrategias de negociación complejas o en pruebas retrospectivas, donde se pueden gestionar simultáneamente diferentes tareas (como el análisis de mercado y la ejecución de órdenes).
- Operaciones asíncronas: Para tareas que no necesitan bloquear la ejecución de otras partes del algoritmo (por ejemplo, la obtención de datos de API externas), el uso de operaciones asíncronas ayuda a mantener la capacidad de respuesta del sistema.
Beneficios:
- Gestión de grandes volúmenes de datos: El código escalable puede procesar conjuntos más grandes de datos de mercado o incorporar activos adicionales sin una degradación significativa del rendimiento.
- Apoyo al crecimiento: Si el algoritmo necesita adaptarse a características adicionales (como operar con múltiples pares, aplicar modelos de aprendizaje automático o gestionar un mayor riesgo), el código escalable proporciona la flexibilidad necesaria para crecer sin necesidad de realizar grandes modificaciones.
- Rendimiento en tiempo real: En un entorno de negociación en vivo, la escalabilidad garantiza que su algoritmo pueda gestionar flujos de datos en tiempo real y ejecuciones de órdenes sin retrasos.
En MQL5, la legibilidad, la facilidad de mantenimiento y la escalabilidad a menudo se superponen y se refuerzan mutuamente. Por ejemplo, una función legible y modular es más fácil de mantener cuando necesita ajustes. Del mismo modo, el código escalable tiende a ser más modular, lo que también mejora su legibilidad y facilidad de mantenimiento. Al desarrollar algoritmos de negociación, este equilibrio garantiza que el código funcione bien ahora y pueda adaptarse o ampliarse a medida que evolucionen las estrategias de negociación o aumenten las exigencias de rendimiento con más datos.
Por ejemplo, en este desarrollo, comenzamos con un Panel de comunicaciones en la Parte 1. A medida que el proyecto evolucionaba, integramos a la perfección nuevos paneles con diferentes especializaciones sin alterar la lógica central. Esto demuestra la escalabilidad, pero aún hay conceptos clave que deben tenerse en cuenta para mejorar la reutilización de las características existentes en el código.
Implementación en el Panel de Administración (Admin Panel EA)
Haremos referencia al código del artículo anterior a medida que aplicamos mejoras en la organización del código. Los enfoques para estructurar el código pueden variar: algunos desarrolladores prefieren organizarlo a medida que lo crean, mientras que otros pueden optar por evaluarlo y perfeccionarlo posteriormente. Independientemente del enfoque, una evaluación rápida ayuda a determinar si el código cumple con los estándares esenciales. Como mencioné anteriormente en la cita de Zhaojun Zhang, no es obligatorio tener un código bien organizado. Algunos desarrolladores se sienten cómodos con el código desorganizado siempre y cuando funcione. Sin embargo, esto suele plantear retos importantes, especialmente cuando se amplían los proyectos. Un código mal estructurado dificulta su mantenimiento, ampliación y depuración, lo que limita el crecimiento a largo plazo. Por eso recomiendo encarecidamente seguir las mejores prácticas en la organización del código. Exploremos más en la siguiente sección.
Identificación de problemas de código
Al revisar el código fuente de Admin Panel V1.24, decidí crear un resumen de los componentes que me facilitara comprenderlo rápidamente e identificar problemas. Por lo general, como desarrollador original, conozco los componentes de mi programa, pero el único reto es organizarlo y acortarlo sin perder legibilidad. A continuación, describo nueve aspectos principales que nos permiten comprender la idea del programa, y luego compartiré más información sobre los temas que abordaré.
1. Elementos de la interfaz de usuario y variables globales
// Panels CDialog adminHomePanel, tradeManagementPanel, communicationsPanel, analyticsPanel; // Authentication UI CDialog authentication, twoFactorAuth; CEdit passwordInputBox, twoFACodeInput; CButton loginButton, closeAuthButton, twoFALoginButton, close2FAButton; // Trade Management UI (12+ buttons) CButton buyButton, sellButton, closeAllButton, closeProfitButton, ...; // Communications UI CEdit inputBox; CButton sendButton, clearButton, quickMessageButtons[8];
2. Sistema de autenticación:
- Contraseña codificada (Password = "2024")
- Flujo de trabajo básico de 2FA
- Contador de intentos de inicio de sesión (failedAttempts)
- Diálogos de autenticación: ShowAuthenticationPrompt() y ShowTwoFactorAuthPrompt()
3. Funciones de gestión comercial:
- Funciones de cierre de posición
- Funciones de eliminación de pedidos
- Ejecución de operaciones
4. Características de comunicación:
- Integración con Telegram mediante SendMessageToTelegram()
- Botones de mensajes rápidos (8 mensajes predefinidos)
- Cuadro de entrada de mensajes con contador de caracteres
5. Panel de análisis:
- Visualización de gráfico circular (CreateAnalyticsPanel())
- Análisis del historial de operaciones (GetTradeData())
- Clases de gráficos personalizados: CCustomPieChart y CAnalyticsChart
6. Estructura de manejo de eventos:
- OnChartEvent() monolítico con:
- Comprueba los clics en los botones
- Lógica mixta de interfaz de usuario/comercio/autenticación
- Llamadas directas a funciones sin enrutamiento
7. Componentes de seguridad:
- Almacenamiento de contraseñas en texto plano
- Implementación básica de la autenticación de dos factores (2FA)
- No se utiliza cifrado para las credenciales de Telegram
- Sin gestión de sesiones
8. Inicialización/Limpieza:
- OnInit() con creación secuencial de interfaz de usuario
- OnDeinit() con destrucción del panel
- Ningún sistema de gestión de recursos
9. Manejo de errores:
- Instrucciones Print() básicas para errores
- No existen mecanismos de recuperación de errores
- No se permiten reversiones de transacciones
- Validación limitada para operaciones comerciales
10. Formato, sangría y espacios:
- Este aspecto está bien cubierto por MetaEditor
- Nuestro código es legible, salvo por otros aspectos como el código repetitivo, que abordaremos en la siguiente sección
Tras analizar el código, aquí presentamos una lista de problemas organizativos que requieren atención:
- Estructura monolítica: Todas las funciones en un solo archivo
- Acoplamiento estrecho: Lógica de interfaz de usuario mezclada con lógica de negocio
- Patrones repetitivos: Código similar para la creación de botones/paneles
- Riesgos de seguridad: Credenciales codificadas, sin cifrado
- Escalabilidad limitada: Sin arquitectura modular
- Convenciones de nomenclatura inconsistentes, principalmente en los elementos de la interfaz de usuario y las variables globales
Reorganizando el código
Tras este paso, el programa debe seguir siendo capaz de ejecutarse y mantener su funcionalidad original. Basándonos en evaluaciones anteriores, a continuación analizaremos algunos aspectos como una forma de mejorar la organización de nuestro código.
1. Estructura monolítica:
Esta situación nos resulta problemática, ya que hace que el código sea innecesariamente largo. Podemos resolver esto dividiendo el código en componentes modulares. Esto implica desarrollar archivos separados para diferentes funcionalidades, haciéndolos reutilizables y manteniendo el código principal limpio y manejable. Las declaraciones e implementaciones residirán fuera del archivo principal y se incluirán según sea necesario.
Para mantener la claridad y evitar sobrecargar este artículo con demasiada información, he reservado la discusión detallada para el próximo artículo. Sin embargo, aquí hay un breve ejemplo: podríamos crear un archivo de inclusión para la autenticación. Vea el código a continuación:
class AuthManager { private: string m_password; int m_failedAttempts; public: AuthManager(string password) : m_password(password), m_failedAttempts(0) {} bool Authenticate(string input) { if(input == m_password) { m_failedAttempts = 0; return true; } m_failedAttempts++; return false; } bool Requires2FA() const { return m_failedAttempts >= 3; } };
Este archivo se incluirá posteriormente en el código de nuestro Panel de administración principal, como se muestra a continuación:
#include <AuthManager.mqh>
2. Acoplamiento estrecho:
En esta implementación, abordamos el problema de mezclar controladores de interfaz de usuario con lógica de negociación. Esto puede mejorarse desacoplándolos mediante interfaces. Para lograrlo, podemos crear un archivo de encabezado o una clase específicos, basados en la clase integrada CTrade.
Para una mejor organización, crearé un archivo de encabezado TradeManager para gestionar la lógica relacionada con el comercio por separado, lo que lo hará reutilizable y más fácil de gestionar. Al incluir esta clase personalizada y separar adecuadamente la lógica de las operaciones de la lógica de la interfaz de usuario, mejoramos la mantenibilidad y la legibilidad del código.
#include<TradeManager.mqh> 3. Patrones de código repetidos
El problema aquí es la duplicación del código de creación de la interfaz de usuario, especialmente para paneles y botones. Podemos resolver esto creando funciones auxiliares de interfaz de usuario, lo que agilizará el proceso de creación de interfaces y sus elementos.
A continuación se muestra un ejemplo de una función auxiliar para la creación de botones:
//+------------------------------------------------------------------+ //| Generic Button Creation Helper | //+------------------------------------------------------------------+ bool CreateButton(CButton &button, CDialog &panel, const string name, const string text, int x1, int y1, int x2, int y2) { if(!button.Create(ChartID(), name, 0, x1, y1, x2, y2)) { Print("Failed to create button: ", name); return false; } button.Text(text); panel.Add(button); return true; }
Los botones restantes se pueden crear utilizando este enfoque, eliminando el código redundante y asegurando una implementación más estructurada y fácil de mantener. A continuación se muestra un ejemplo para la creación de un botón de acceso al Panel de gestión comercial. La mayor parte de la implementación se incluye en el código final organizado que se encuentra en la sección de resultados.
CreateButton(TradeMgmtAccessButton, AdminHomePanel, "TradeMgmtAccessButton", "Trade Management Panel", 10, 20, 250, 60)
4. Riesgos de seguridad:
Para simplificar, seguimos utilizando contraseñas codificadas, pero ya hemos tratado este aspecto en la Parte VII. Esto se puede resolver utilizando una configuración cifrada.
5. Nombres inconsistentes:
En algunos momentos, utilicé abreviaturas para los nombres para reducir la longitud del texto. Sin embargo, esto puede generar dificultades a la hora de colaborar con otros. La mejor manera de abordar esto es aplicando convenciones de nomenclatura coherentes.
Por ejemplo, en el fragmento de código siguiente, utilicé una "t" minúscula en lugar de una "T" mayúscula y una abreviatura de "management", lo que puede generar confusión entre otros desarrolladores que desconozcan la intención del autor. Además, el nombre de la función del botón de tema es demasiado extenso y podría ser más conciso para una mejor legibilidad. Vea el ejemplo a continuación que ilustra estos problemas:
CButton tradeMgmtAccessButton; // Inconsistent void OnToggleThemeButtonClick(); // Verbose
Aquí está el código resuelto:
CButton TradeManagementAccessButton; // PascalCase void HandleThemeToggle(); // Action-oriented
Resultados y pruebas
Tras una cuidadosa aplicación de la solución que hemos analizado, aquí presentamos nuestro código final resuelto. En este fragmento, hemos eliminado la funcionalidad del tema para poder crear un archivo de encabezado dedicado al tema por separado. Esta medida tiene como objetivo solucionar los problemas asociados con la extensión de las clases integradas para la funcionalidad relacionada con el tema.
//+------------------------------------------------------------------+ //| Admin Panel.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property description "A secure and responsive, communications, trade management and analytics Panel" #property version "1.25" //Essential header files included #include <Trade\Trade.mqh> #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> #include <Canvas\Charts\PieChart.mqh> #include <Canvas\Charts\ChartCanvas.mqh> // Input parameters for quick messages input string QuickMessage1 = "Updates"; input string QuickMessage2 = "Close all"; input string QuickMessage3 = "In deep profits"; input string QuickMessage4 = "Hold position"; input string QuickMessage5 = "Swing Entry"; input string QuickMessage6 = "Scalp Entry"; input string QuickMessage7 = "Book profit"; input string QuickMessage8 = "Invalid Signal"; input string InputChatId = "YOUR_CHAT_ID"; // Telegram chat ID for notifications input string InputBotToken = "YOUR_BOT_TOKEN"; // Telegram bot token // Security Configuration const string TwoFactorAuthChatId = "REPLACE_WITH_YOUR_CHAT_ID"; // 2FA notification channel const string TwoFactorAuthBotToken = "REPLACE_WITH_YOUR_BOT_TOKEN"; // 2FA bot credentials const string DefaultPassword = "2024"; // Default access password // Global UI Components CDialog AdminHomePanel, TradeManagementPanel, CommunicationsPanel, AnalyticsPanel; CButton HomeButtonComm, HomeButtonTrade, SendButton, ClearButton; CButton ChangeFontButton, ToggleThemeButton, LoginButton, CloseAuthButton; CButton TwoFactorAuthLoginButton, CloseTwoFactorAuthButton, MinimizeCommsButton; CButton CloseCommsButton, TradeMgmtAccessButton, CommunicationsPanelAccessButton; CButton AnalyticsPanelAccessButton, ShowAllButton, QuickMessageButtons[8]; CEdit InputBox, PasswordInputBox, TwoFactorAuthCodeInput; CLabel CharCounter, PasswordPromptLabel, FeedbackLabel; CLabel TwoFactorAuthPromptLabel, TwoFactorAuthFeedbackLabel; // Trade Execution Components CButton BuyButton, SellButton, CloseAllButton, CloseProfitButton; CButton CloseLossButton, CloseBuyButton, CloseSellButton; CButton DeleteAllOrdersButton, DeleteLimitOrdersButton; CButton DeleteStopOrdersButton, DeleteStopLimitOrdersButton; // Security State Management int FailedAttempts = 0; // Track consecutive failed login attempts bool IsTrustedUser = false; // Flag for verified users string ActiveTwoFactorAuthCode = ""; // Generated 2FA verification code // Trade Execution Constants const double DefaultLotSize = 1.0; // Standard trade volume const double DefaultSlippage = 3; // Allowed price deviation const double DefaultStopLoss = 0; // Default risk management const double DefaultTakeProfit = 0; // Default profit target //+------------------------------------------------------------------+ //| Program Initialization | //+------------------------------------------------------------------+ int OnInit() { if(!InitializeAuthenticationDialog() || !InitializeAdminHomePanel() || !InitializeTradeManagementPanel() || !InitializeCommunicationsPanel()) { Print("Initialization failed"); return INIT_FAILED; } AdminHomePanel.Hide(); TradeManagementPanel.Hide(); CommunicationsPanel.Hide(); AnalyticsPanel.Hide(); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Trade Management Functions | //+------------------------------------------------------------------+ CTrade TradeExecutor; // Centralized trade execution handler // Executes a market order with predefined parameters // name="orderType">Type of order (ORDER_TYPE_BUY/ORDER_TYPE_SELL) // <returns>True if order execution succeeded</returns> bool ExecuteMarketOrder(int orderType) { double executionPrice = (orderType == ORDER_TYPE_BUY) ? SymbolInfoDouble(Symbol(), SYMBOL_ASK) : SymbolInfoDouble(Symbol(), SYMBOL_BID); if(executionPrice <= 0) { Print("Price retrieval failed. Error: ", GetLastError()); return false; } bool orderResult = (orderType == ORDER_TYPE_BUY) ? TradeExecutor.Buy(DefaultLotSize, Symbol(), executionPrice, DefaultSlippage, DefaultStopLoss, DefaultTakeProfit) : TradeExecutor.Sell(DefaultLotSize, Symbol(), executionPrice, DefaultSlippage, DefaultStopLoss, DefaultTakeProfit); if(orderResult) { Print(orderType == ORDER_TYPE_BUY ? "Buy" : "Sell", " order executed successfully"); } else { Print("Order execution failed. Error: ", GetLastError()); } return orderResult; } // Closes positions based on specified criteria // name="closureCondition" // 0=All, 1=Profitable, -1=Losing, 2=Buy, 3=Sell bool ClosePositions(int closureCondition) { CPositionInfo positionInfo; for(int i = PositionsTotal()-1; i >= 0; i--) { if(positionInfo.SelectByIndex(i) && (closureCondition == 0 || (closureCondition == 1 && positionInfo.Profit() > 0) || (closureCondition == -1 && positionInfo.Profit() < 0) || (closureCondition == 2 && positionInfo.Type() == POSITION_TYPE_BUY) || (closureCondition == 3 && positionInfo.Type() == POSITION_TYPE_SELL))) { TradeExecutor.PositionClose(positionInfo.Ticket()); } } return true; } //+------------------------------------------------------------------+ //| Authentication Management | //+------------------------------------------------------------------+ CDialog AuthenticationDialog, TwoFactorAuthDialog; /// Initializes the primary authentication dialog bool InitializeAuthenticationDialog() { if(!AuthenticationDialog.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300)) return false; // Create dialog components if(!PasswordInputBox.Create(ChartID(), "PasswordInput", 0, 20, 70, 260, 95) || !PasswordPromptLabel.Create(ChartID(), "PasswordPrompt", 0, 20, 20, 260, 40) || !FeedbackLabel.Create(ChartID(), "AuthFeedback", 0, 20, 140, 380, 160) || !LoginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 140) || !CloseAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 140)) { Print("Authentication component creation failed"); return false; } // Configure component properties PasswordPromptLabel.Text("Enter Administrator Password:"); FeedbackLabel.Text(""); FeedbackLabel.Color(clrRed); LoginButton.Text("Login"); CloseAuthButton.Text("Cancel"); // Assemble dialog AuthenticationDialog.Add(PasswordInputBox); AuthenticationDialog.Add(PasswordPromptLabel); AuthenticationDialog.Add(FeedbackLabel); AuthenticationDialog.Add(LoginButton); AuthenticationDialog.Add(CloseAuthButton); AuthenticationDialog.Show(); return true; } /// Generates a 6-digit 2FA code and sends via Telegram void HandleTwoFactorAuthentication() { ActiveTwoFactorAuthCode = StringFormat("%06d", MathRand() % 1000000); SendMessageToTelegram("Your verification code: " + ActiveTwoFactorAuthCode, TwoFactorAuthChatId, TwoFactorAuthBotToken); } //+------------------------------------------------------------------+ //| Panel Initialization Functions | //+------------------------------------------------------------------+ bool InitializeAdminHomePanel() { if(!AdminHomePanel.Create(ChartID(), "Admin Home Panel", 0, 30, 80, 335, 350)) return false; return CreateButton(TradeMgmtAccessButton, AdminHomePanel, "TradeMgmtAccessButton", "Trade Management Panel", 10, 20, 250, 60) && CreateButton(CommunicationsPanelAccessButton, AdminHomePanel, "CommunicationsPanelAccessButton", "Communications Panel", 10, 70, 250, 110) && CreateButton(AnalyticsPanelAccessButton, AdminHomePanel, "AnalyticsPanelAccessButton", "Analytics Panel", 10, 120, 250, 160) && CreateButton(ShowAllButton, AdminHomePanel, "ShowAllButton", "Show All 💥", 10, 170, 250, 210); } bool InitializeTradeManagementPanel() { if (!TradeManagementPanel.Create(ChartID(), "Trade Management Panel", 0, 500, 30, 1280, 170)) { Print("Failed to create Trade Management Panel."); return false; } CreateButton(HomeButtonTrade, TradeManagementPanel, "HomeButtonTrade", "Home 🏠", 20, 10, 120, 30) && CreateButton(BuyButton, TradeManagementPanel, "BuyButton", "Buy", 130, 5, 210, 40) && CreateButton(SellButton, TradeManagementPanel, "SellButton", "Sell", 220, 5, 320, 40) && CreateButton(CloseAllButton, TradeManagementPanel, "CloseAllButton", "Close All", 130, 50, 230, 70) && CreateButton(CloseProfitButton, TradeManagementPanel, "CloseProfitButton", "Close Profitable", 240, 50, 380, 70) && CreateButton(CloseLossButton, TradeManagementPanel, "CloseLossButton", "Close Losing", 390, 50, 510, 70) && CreateButton(CloseBuyButton, TradeManagementPanel, "CloseBuyButton", "Close Buys", 520, 50, 620, 70) && CreateButton(CloseSellButton, TradeManagementPanel, "CloseSellButton", "Close Sells", 630, 50, 730, 70) && CreateButton(DeleteAllOrdersButton, TradeManagementPanel, "DeleteAllOrdersButton", "Delete All Orders", 40, 50, 180, 70) && CreateButton(DeleteLimitOrdersButton, TradeManagementPanel, "DeleteLimitOrdersButton", "Delete Limits", 190, 50, 300, 70) && CreateButton(DeleteStopOrdersButton, TradeManagementPanel, "DeleteStopOrdersButton", "Delete Stops", 310, 50, 435, 70) && CreateButton(DeleteStopLimitOrdersButton, TradeManagementPanel, "DeleteStopLimitOrdersButton", "Delete Stop Limits", 440, 50, 580, 70); return true; } //+------------------------------------------------------------------+ //| Two-Factor Authentication Dialog | //+------------------------------------------------------------------+ bool InitializeTwoFactorAuthDialog() { if(!TwoFactorAuthDialog.Create(ChartID(), "Two-Factor Authentication", 0, 100, 100, 500, 300)) return false; if(!TwoFactorAuthCodeInput.Create(ChartID(), "TwoFACodeInput", 0, 20, 70, 260, 95) || !TwoFactorAuthPromptLabel.Create(ChartID(), "TwoFAPromptLabel", 0, 20, 20, 380, 40) || !TwoFactorAuthFeedbackLabel.Create(ChartID(), "TwoFAFeedbackLabel", 0, 20, 140, 380, 160) || !TwoFactorAuthLoginButton.Create(ChartID(), "TwoFALoginButton", 0, 20, 120, 100, 140) || !CloseTwoFactorAuthButton.Create(ChartID(), "Close2FAButton", 0, 120, 120, 200, 140)) { return false; } TwoFactorAuthPromptLabel.Text("Enter verification code sent to Telegram:"); TwoFactorAuthFeedbackLabel.Text(""); TwoFactorAuthFeedbackLabel.Color(clrRed); TwoFactorAuthLoginButton.Text("Verify"); CloseTwoFactorAuthButton.Text("Cancel"); TwoFactorAuthDialog.Add(TwoFactorAuthCodeInput); TwoFactorAuthDialog.Add(TwoFactorAuthPromptLabel); TwoFactorAuthDialog.Add(TwoFactorAuthFeedbackLabel); TwoFactorAuthDialog.Add(TwoFactorAuthLoginButton); TwoFactorAuthDialog.Add(CloseTwoFactorAuthButton); return true; } //+------------------------------------------------------------------+ //| Telegram Integration | //+------------------------------------------------------------------+ bool SendMessageToTelegram(string message, string chatId, string botToken) { string url = "https://api.telegram.org/bot" + botToken + "/sendMessage"; string headers; char postData[], result[]; string requestData = "{\"chat_id\":\"" + chatId + "\",\"text\":\"" + message + "\"}"; StringToCharArray(requestData, postData, 0, StringLen(requestData)); int response = WebRequest("POST", url, headers, 5000, postData, result, headers); if(response == 200) { Print("Telegram notification sent successfully"); return true; } Print("Failed to send Telegram notification. Error: ", GetLastError()); return false; } //+------------------------------------------------------------------+ //| Generic Button Creation Helper | //+------------------------------------------------------------------+ bool CreateButton(CButton &button, CDialog &panel, const string name, const string text, int x1, int y1, int x2, int y2) { if(!button.Create(ChartID(), name, 0, x1, y1, x2, y2)) { Print("Failed to create button: ", name); return false; } button.Text(text); panel.Add(button); return true; } //+------------------------------------------------------------------+ //| Enhanced Event Handling | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK || id == CHARTEVENT_OBJECT_ENDEDIT) { if(sparam == "InputBox") { int length = StringLen(InputBox.Text()); CharCounter.Text(IntegerToString(length) + "/4096"); } else if(id == CHARTEVENT_OBJECT_CLICK) { // Authentication event handling if(sparam == "LoginButton") { string enteredPassword = PasswordInputBox.Text(); if(enteredPassword == DefaultPassword) { FailedAttempts = 0; IsTrustedUser = true; AuthenticationDialog.Destroy(); AdminHomePanel.Show(); } else { if(++FailedAttempts >= 3) { HandleTwoFactorAuthentication(); AuthenticationDialog.Destroy(); InitializeTwoFactorAuthDialog(); } else { FeedbackLabel.Text("Invalid credentials. Attempts remaining: " + IntegerToString(3 - FailedAttempts)); PasswordInputBox.Text(""); } } } if(sparam == "AnalyticsPanelAccessButton") { OnAnalyticsButtonClick(); AdminHomePanel.Hide(); if(!InitializeAnalyticsPanel()) { Print("Failed to initialize Analytics Panel"); return; } // Communications Handling if(sparam == "SendButton") { if(SendMessageToTelegram(InputBox.Text(), InputChatId, InputBotToken)) InputBox.Text(""); } else if(sparam == "ClearButton") { InputBox.Text(""); CharCounter.Text("0/4096"); } else if(StringFind(sparam, "QuickMsgBtn") != -1) { int index = (int)StringToInteger(StringSubstr(sparam, 11)) - 1; if(index >= 0 && index < 8) SendMessageToTelegram(QuickMessageButtons[index].Text(), InputChatId, InputBotToken); } // Trade execution handlers else if(sparam == "BuyButton") ExecuteMarketOrder(ORDER_TYPE_BUY); else if(sparam == "SellButton") ExecuteMarketOrder(ORDER_TYPE_SELL); // Panel Navigation if(sparam == "TradeMgmtAccessButton") { TradeManagementPanel.Show(); AdminHomePanel.Hide(); } else if(sparam == "CommunicationsPanelAccessButton") { CommunicationsPanel.Show(); AdminHomePanel.Hide(); } else if(sparam == "AnalyticsPanelAccessButton") { OnAnalyticsButtonClick(); AdminHomePanel.Hide(); } else if(sparam == "ShowAllButton") { TradeManagementPanel.Show(); CommunicationsPanel.Show(); AnalyticsPanel.Show(); AdminHomePanel.Hide(); } else if(sparam == "HomeButtonTrade") { AdminHomePanel.Show(); TradeManagementPanel.Hide(); } else if(sparam == "HomeButtonComm") { AdminHomePanel.Show(); CommunicationsPanel.Hide(); } } } } } //+------------------------------------------------------------------+ //| Communications Management | //+------------------------------------------------------------------+ bool InitializeCommunicationsPanel() { if(!CommunicationsPanel.Create(ChartID(), "Communications Panel", 0, 20, 150, 490, 650)) return false; // Create main components if(!InputBox.Create(ChartID(), "InputBox", 0, 5, 25, 460, 95) || !CharCounter.Create(ChartID(), "CharCounter", 0, 380, 5, 460, 25)) return false; // Create control buttons with corrected variable names const bool buttonsCreated = CreateButton(SendButton, CommunicationsPanel, "SendButton", "Send", 350, 95, 460, 125) && CreateButton(ClearButton, CommunicationsPanel, "ClearButton", "Clear", 235, 95, 345, 125) && CreateButton(ChangeFontButton, CommunicationsPanel, "ChangeFontButton", "Font<>", 95, 95, 230, 115) && CreateButton(ToggleThemeButton, CommunicationsPanel, "ToggleThemeButton", "Theme<>", 5, 95, 90, 115); CommunicationsPanel.Add(InputBox); CommunicationsPanel.Add(CharCounter); CommunicationsPanel.Add(SendButton); CommunicationsPanel.Add(ClearButton); CommunicationsPanel.Add(ChangeFontButton); CommunicationsPanel.Add(ToggleThemeButton); return buttonsCreated && CreateQuickMessageButtons(); } bool CreateQuickMessageButtons() { string quickMessages[] = {QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8}; const int startX = 5, startY = 160, width = 222, height = 65, spacing = 5; for(int i = 0; i < 8; i++) { const int xPos = startX + (i % 2) * (width + spacing); const int yPos = startY + (i / 2) * (height + spacing); if(!QuickMessageButtons[i].Create(ChartID(), "QuickMsgBtn" + IntegerToString(i+1), 0, xPos, yPos, xPos + width, yPos + height)) return false; QuickMessageButtons[i].Text(quickMessages[i]); CommunicationsPanel.Add(QuickMessageButtons[i]); } return true; } //+------------------------------------------------------------------+ //| Data for Pie Chart | //+------------------------------------------------------------------+ void GetTradeData(int &wins, int &losses, int &forexTrades, int &stockTrades, int &futuresTrades) { wins = 0; losses = 0; forexTrades = 0; stockTrades = 0; futuresTrades = 0; if (!HistorySelect(0, TimeCurrent())) { Print("Failed to select trade history."); return; } int totalDeals = HistoryDealsTotal(); for (int i = 0; i < totalDeals; i++) { ulong dealTicket = HistoryDealGetTicket(i); if (dealTicket > 0) { double profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); if (profit > 0) wins++; else if (profit < 0) losses++; string symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL); if (SymbolInfoInteger(symbol, SYMBOL_SELECT)) { if (StringFind(symbol, ".") == -1) forexTrades++; else { string groupName; if (SymbolInfoString(symbol, SYMBOL_PATH, groupName)) { if (StringFind(groupName, "Stocks") != -1) stockTrades++; else if (StringFind(groupName, "Futures") != -1) futuresTrades++; } } } } } } //+------------------------------------------------------------------+ //| Custom Pie Chart Class | //+------------------------------------------------------------------+ class CCustomPieChart : public CPieChart { public: void DrawPieSegment(double fi3, double fi4, int idx, CPoint &p[], const uint clr) { DrawPie(fi3, fi4, idx, p, clr); // Expose protected method } }; //+------------------------------------------------------------------+ //| Analytics Chart Class | //+------------------------------------------------------------------+ class CAnalyticsChart : public CWnd { private: CCustomPieChart pieChart; // Declare pieChart as a member of this class public: bool CreatePieChart(string label, int x, int y, int width, int height) { if (!pieChart.CreateBitmapLabel(label, x, y, width, height)) { Print("Error creating Pie Chart: ", label); return false; } return true; } void SetPieChartData(const double &values[], const string &labels[], const uint &colors[]) { pieChart.SeriesSet(values, labels, colors); pieChart.ShowPercent(); } void DrawPieChart(const double &values[], const uint &colors[], int x0, int y0, int radius) { double total = 0; int seriesCount = ArraySize(values); if (seriesCount == 0) { Print("No data for pie chart."); return; } for (int i = 0; i < seriesCount; i++) total += values[i]; double currentAngle = 0.0; // Resize the points array CPoint points[]; ArrayResize(points, seriesCount + 1); for (int i = 0; i < seriesCount; i++) { double segmentValue = values[i] / total * 360.0; double nextAngle = currentAngle + segmentValue; // Define points for the pie slice points[i].x = x0 + (int)(radius * cos(currentAngle * M_PI / 180.0)); points[i].y = y0 - (int)(radius * sin(currentAngle * M_PI / 180.0)); pieChart.DrawPieSegment(currentAngle, nextAngle, i, points, colors[i]); currentAngle = nextAngle; } // Define the last point to close the pie points[seriesCount].x = x0 + (int)(radius * cos(0)); // Back to starting point points[seriesCount].y = y0 - (int)(radius * sin(0)); } }; //+------------------------------------------------------------------+ //| Initialize Analytics Panel | //+------------------------------------------------------------------+ bool InitializeAnalyticsPanel() { if (!AnalyticsPanel.Create(ChartID(), "Analytics Panel",0, 500, 450, 1285, 750)) { Print("Failed to create Analytics Panel"); return false; } int wins, losses, forexTrades, stockTrades, futuresTrades; GetTradeData(wins, losses, forexTrades, stockTrades, futuresTrades); CAnalyticsChart winLossChart, tradeTypeChart; // Win vs Loss Pie Chart if (!winLossChart.CreatePieChart("Win vs. Loss", 690, 480, 250, 250)) { Print("Error creating Win/Loss Pie Chart"); return false; } double winLossValues[] = {wins, losses}; string winLossLabels[] = {"Wins", "Losses"}; uint winLossColors[] = {clrGreen, clrRed}; winLossChart.SetPieChartData(winLossValues, winLossLabels, winLossColors); winLossChart.DrawPieChart(winLossValues, winLossColors, 150, 150, 140); AnalyticsPanel.Add(winLossChart); // Trade Type Pie Chart if (!tradeTypeChart.CreatePieChart("Trade Type", 950, 480, 250, 250)) { Print("Error creating Trade Type Pie Chart"); return false; } double tradeTypeValues[] = {forexTrades, stockTrades, futuresTrades}; string tradeTypeLabels[] = {"Forex", "Stocks", "Futures"}; uint tradeTypeColors[] = {clrBlue, clrOrange, clrYellow}; tradeTypeChart.SetPieChartData(tradeTypeValues, tradeTypeLabels, tradeTypeColors); tradeTypeChart.DrawPieChart(tradeTypeValues, tradeTypeColors, 500, 150, 140); AnalyticsPanel.Add(tradeTypeChart); return true; } //+------------------------------------------------------------------+ //| Analytics Button Click Handler | //+------------------------------------------------------------------+ void OnAnalyticsButtonClick() { // Clear any previous pie charts because we're redrawing them ObjectDelete(0, "Win vs. Loss Pie Chart"); ObjectDelete(0, "Trade Type Distribution"); // Update the analytics panel with fresh data AnalyticsPanel.Destroy(); InitializeAnalyticsPanel(); AnalyticsPanel.Show(); } //+------------------------------------------------------------------+ //| Cleanup Operations | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Release all UI components AuthenticationDialog.Destroy(); TwoFactorAuthDialog.Destroy(); AdminHomePanel.Destroy(); AnalyticsPanel.Destroy(); }
En el proceso de organización del código, la transición del código original (v1.24) a la versión actualizada (v1.25) refleja varias mejoras clave:
1. Modularidad y estructura mejoradas: El código actualizado introduce una agrupación más lógica de las funcionalidades dentro de las secciones. Por ejemplo, en v1.24, gran parte de la inicialización y gestión de la interfaz de usuario estaba dispersa o no estaba claramente separada. La nueva versión organiza el código en secciones bien definidas, como «Trade Management Functions», «Authentication Management» y «Panel Initialization Functions». Esta segregación hace que el código sea más legible y fácil de mantener. Ahora cada sección comienza con un encabezado claro que indica qué funcionalidad se está implementando, lo que facilita la navegación rápida por el código base.
2. Separación de responsabilidades: En la v1.24, funciones como ShowAuthenticationPrompt y ShowTwoFactorAuthPrompt se mezclaban con declaraciones globales y carecían de una separación clara de la lógica de inicialización. El código actualizado en la versión 1.25 separa estas cuestiones de forma más eficaz. Las funciones de inicialización como InitializeAuthenticationDialog e InitializeTwoFactorAuthDialog ahora se distinguen de los controladores de eventos o las funciones de utilidad, lo que reduce la complejidad en cada segmento. Esta separación ayuda a comprender el ciclo de vida de los diferentes componentes de la EA, desde la inicialización hasta el manejo de las interacciones. Además, la introducción de clases específicas para gestionar análisis (CAnalyticsChart y CCustomPieChart) encapsula la compleja lógica de dibujo de gráficos, lo que promueve la reutilización y mantiene un principio de responsabilidad única para cada clase o función.
Una vez completada correctamente la compilación, el Panel de administración se inicia correctamente y solicita una verificación de seguridad como paso inicial. Después de introducir las credenciales correctas, el sistema concede acceso al Panel de inicio de administración.
A modo de referencia, la contraseña predeterminada es «2024», tal y como se especifica en el código. A continuación se muestra una imagen que muestra el lanzamiento del Asesor Experto (Expert Advisor, EA) en el gráfico:

Añadir el Panel de administración al gráfico desde el nuevo código fuente.
Un ejemplo integrado de un código bien organizado
Antes de escribir este artículo, encontré una implementación integrada de la clase CDialog en MQL5. Esto sirvió como ejemplo motivador, inspirando esfuerzos para crear código más legible y reutilizable. El ejemplo es un Asesor Experto de Controles, ubicado en la carpeta Experts dentro de Examples. Véase la imagen a continuación.
Localización de los controles del EA en MetaTrader 5
La aplicación de ejemplo es muy receptiva e incluye numerosas funciones de interfaz. Véase la imagen a continuación.

Agregar controles al gráfico
Para ver el código fuente, abra MetaEditor, vaya a la carpeta Experts y localice el código fuente de Controls en la carpeta Examples, como se muestra a continuación.
Acceso al código fuente de los controles en MetaEditor
Esto sirve como código principal del programa, con gran parte de la lógica de la interfaz de usuario distribuida en CControlsDialog, que aprovecha la clase CDialog para simplificar la creación de la interfaz. El código fuente es compacto, legible y escalable.//+------------------------------------------------------------------+ //| Controls.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "ControlsDialog.mqh" //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CControlsDialog ExtDialog; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create application dialog if(!ExtDialog.Create(0,"Controls",0,20,20,360,324)) return(INIT_FAILED); //--- run application ExtDialog.Run(); //--- succeed return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy dialog ExtDialog.Destroy(reason); } //+------------------------------------------------------------------+ //| Expert chart event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event ID const long& lparam, // event parameter of the long type const double& dparam, // event parameter of the double type const string& sparam) // event parameter of the string type { ExtDialog.ChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Este ejemplo muestra cómo organizar el código de forma profesional mediante una arquitectura modular y principios de diseño basados en las mejores prácticas. El código separa claramente las preocupaciones al delegar toda la lógica de la interfaz de usuario a la clase CControlsDialog (definida en el módulo ControlsDialog.mqh incluido), mientras que el archivo principal se centra exclusivamente en la gestión del ciclo de vida de la aplicación.
Este enfoque modular encapsula los detalles de implementación, exponiendo solo interfaces estandarizadas como Create(), Run() y Destroy() para la inicialización, ejecución y limpieza. Reenviando los eventos del gráfico directamente al componente de diálogo a través de ExtDialog. ChartEvent(), la arquitectura desacopla el manejo de eventos de la lógica central de la aplicación, lo que garantiza la reutilización y la capacidad de prueba.
La estructura cumple con altos estándares gracias a un diseño minimalista del archivo principal, que no contiene declaraciones de interfaz de usuario, y aplica límites estrictos a los componentes, lo que permite realizar modificaciones seguras y facilitar la colaboración en equipo. Este patrón ejemplifica el desarrollo escalable de MQL5, en el que módulos discretos gestionan responsabilidades específicas, lo que reduce la carga cognitiva y favorece la facilidad de mantenimiento mediante contratos de interfaz claros y una gestión sistemática de los recursos.
Conclusión
Nos hemos embarcado en un viaje hacia una organización del código más estructurada y de nivel empresarial. Se han realizado mejoras significativas al abordar las convenciones de nomenclatura inconsistentes, mejorar los comentarios, perfeccionar el manejo de errores y agrupar lógicamente las funcionalidades relacionadas. Estos cambios han dado lugar a una reducción del tamaño del archivo principal, una clara separación de responsabilidades, la creación de componentes reutilizables y el establecimiento de convenciones de nomenclatura coherentes.
Estos cambios organizativos dan como resultado un código base más fácil de navegar y más escalable, lo que permite realizar actualizaciones y adiciones más fácilmente en áreas específicas de funcionalidad sin afectar a otras. Este enfoque estructurado también facilita la realización de pruebas y la depuración, ya que permite aislar las diferentes partes del sistema.
Nuestros próximos pasos consisten en modularizar aún más nuestro programa para garantizar que sus componentes puedan reutilizarse fácilmente en otros proyectos de Expert Advisor (EA) e indicadores. Este esfuerzo continuo beneficiará en última instancia a toda la comunidad comercial. Aunque hemos sentado unas bases sólidas, aún hay varios aspectos que merecen un debate y un análisis más profundos, que exploraremos con mayor detalle en nuestro próximo artículo.
Estoy seguro de que con esta guía podremos desarrollar código limpio, legible y escalable. De esta manera, mejoramos nuestros propios proyectos y también atraemos a otros desarrolladores, contribuyendo a crear una gran biblioteca de código para su reutilización futura. Este esfuerzo colectivo mejora la eficiencia de nuestra comunidad y fomenta la innovación.
Sus comentarios y opiniones son bienvenidos en la sección siguiente.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16539
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.
Dominando los registros (Parte 5): Optimizar el controlador con caché y rotación
Ingeniería de características con Python y MQL5 (Parte III): El ángulo del precio (2) Coordenadas polares
Pruebas de robustez en asesores expertos
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 11): EA de señales Heikin Ashi
- 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