
Creating a Trading Administrator Panel in MQL5 (Part IX): Code Organization (I)
Introduction
Lengthy code can be difficult to follow, especially when it lacks proper organization. This often leads to abandoned projects. But does that mean I have to abandon the Trading Admin Panel project now that it has grown with multiple integrated panels? Absolutely not! Instead, we need strategies to keep it running smoothly.
That brings us to today's discussion, where we explore how code organization can enhance algorithm development in MQL5. The success of MQL5 and other large-scale projects can often be attributed to their structured approach—allowing them to manage and maintain extensive codebases efficiently.
Without proper documentation and structure, maintaining a code becomes a challenge, making future modifications even difficult.
In this discussion, we will explore practical solutions to these challenges, focusing on structuring the Trading Admin Panel for long-term scalability. Code organization is not just a convenience; it is a critical factor in writing efficient, maintainable programs. By adopting these best practices, we can ensure that MQL5 projects remain robust, shareable, and scalable—allowing individual developers to build and sustain complex applications.
Before we dive deeper, let me break down the key points of this discussion:
- Discussion Overview
- Understanding Code Organization.
- Implementation on Admin Panel (EA)
- Results and Testing
- One in-built example of a well-organized code
- Conclusion
Discussion Overview
In the previous discussion of this series, we witnessed significant expansion of our program as we introduced more specialized panels to the Admin Panel, transforming it into an essential dashboard for any trader. With these additions, we now have four panels: the Admin Home Panel, Communications Panel, Trade Management Panel, and Analytics Panel. The code has grown considerably, outlining the main structure, but there is still much to be done to enhance the functionality of each feature.
As I considered taking the next step and adding even more features, I realized the importance of revisiting the entire code to organize it better. This is when the idea for this topic came about. Rather than simply presenting a finished program, I felt it would be valuable to walk through the process of refining and organizing the code with you. In the next section, we will uncover more on code organization based on my research.
I think by the end of this discussion, someone must have acquired knowledge to answer these questions:
- How to develop large programs?
- How to make others understand my large program?
Understanding Code Organization
According to various sources, code organization refers to the practice of structuring and arranging code in a way that enhances readability, maintainability, and scalability. Well-organized code makes it easier for developers to understand, debug, and extend their programs.
As software engineer Zhaojun Zhang once mentioned, "The organization of code is like the tidiness of your house: you don’t need to tidy it up every day, and you can still live in your house regardless of how messy it is, as long as you can tolerate it. It only haunts you when you desperately need to find something you haven’t touched in a while or when you want to invite guests over for a fancy dinner."
I believe this analogy makes it clear that code organization is important for your own workflow, and also for others who may work with your code. Let’s break down these key concepts of code organization—readability, maintainability, and scalability—and explore their significance, particularly in the context of algorithm development in MQL5.
1. Readability:
Readability refers to how easily someone can understand the logic and structure of the code. In the context of MQL5, this is especially crucial because the code might be worked on by multiple developers, and even if you’re working solo, you’ll want to revisit or debug your own code at some point, as I mentioned earlier.
Key Features:
- Clear Variable Naming: Use meaningful names for variables, functions, and classes. Instead of using vague names like a, b, or temp, choose descriptive ones that convey the purpose, such as movingAveragePeriod or signalStrength.
- Commenting: Good comments explain why certain blocks of code exist, not just what they do. This is essential for documenting the algorithm’s intent.
- Consistent Formatting: Indentation and line spacing help break the code into readable blocks. For instance, use consistent indentation for loops, conditionals, and functions.
- Modular Code: Breaking the code into small, self-contained functions or classes that each handle a specific task (like calculating a moving average or checking for a trade condition) improves readability.
- Quick Debugging: Readable code makes it easier to spot bugs and correct them.
- Collaboration: If your code is clean and understandable, it’s much easier for other developers to collaborate or help you troubleshoot issues.
- Faster Onboarding: When revisiting a project, readable code ensures you don’t waste time re-understanding your own work.
2. Maintainability:
Maintainability is the ease with which code can be modified or extended over time, especially when new features are added or bugs need to be fixed. In algorithmic trading, like in MQL5, where strategies often evolve, maintainability is critical to long-term success.
Key Features:
- Modularity: By using functions or classes to compartmentalize different tasks (e.g., one function to handle trade execution, another to calculate indicators), you create isolated parts of the system. Changes can be made in one area without impacting others.
- Separation of Concerns: Each part of the code should have one responsibility. For example, the logic for placing trades should be separate from the logic for evaluating market conditions.
- Use of Libraries and Built-In Functions: Instead of reinventing the wheel, leverage MQL5’s built-in functions and libraries for common tasks like moving averages or order placement, which can reduce complexity and errors.
- Version Control: Use version control systems (e.g., Git and MQL5 Storage) to track changes, so you can roll back if a modification introduces bugs or unexpected behaviors.
Benefits:
- Future Modifications: As strategies evolve, maintaining a well-structured codebase allows developers to implement new features or make adjustments with minimal effort.
- Bug Fixes: When bugs are detected, maintainable code allows you to address problems quickly without disrupting other parts of the system.
- Efficiency: Developers spend less time figuring out how the code works, leading to faster updates and fewer mistakes.
3. Scalability:
Scalability refers to the capability of the code to handle increasing amounts of work or accommodate growing data/functional requirements. As trading strategies become more complex and data intensive, scalability becomes vital for smooth operation.
Key Features:
- Efficient Algorithms: In algorithmic trading, you may need to process large volumes of historical data, execute many trades, or analyze multiple assets simultaneously. Optimizing your algorithms for speed and memory usage is crucial.
- Data Structures: Choosing appropriate data structures, such as arrays, lists, or maps, helps in managing larger datasets efficiently. MQL5 provides data structures like Array and Struct which can be leveraged to scale up your strategy.
- Parallel Processing: MQL5 supports multithreading, allowing you to run multiple tasks in parallel. This is particularly useful in complex trading strategies or back-testing where different tasks (like market analysis and order execution) can be handled simultaneously.
- Asynchronous Operations: For tasks that don’t need to block the execution of other parts of the algorithm (e.g., fetching data from external APIs), using asynchronous operations helps keep the system responsive.
Benefits:
- Handling Bigger Data: Scalable code can process larger sets of market data or incorporate additional assets without significant degradation in performance.
- Support for Growth: If the algorithm needs to accommodate additional features (like trading multiple pairs, applying machine learning models, or handling increased risk management), scalable code provides the flexibility to grow without major overhauls.
- Real-Time Performance: In a live trading environment, scalability ensures that your algorithm can handle real-time data feeds and order executions without lag.
In MQL5, readability, maintainability, and scalability often overlap and reinforce each other. For instance, a readable and modular function is easier to maintain when it needs adjustments. Similarly, scalable code tends to be more modular, which also enhances its readability and maintainability. While developing trading algorithms, this balance ensures that the code performs well now, and can be adapted or expanded as trading strategies evolve, or as performance demands increase with more data.
For example, in this development, we started with a Communications Panel in Part 1. As the project evolved, we seamlessly integrated new panels with different specializations without disrupting the core logic. This demonstrates scalability, but there are still key concepts to consider enhancing the reusability of the existing features in the code.
Implementation on Admin Panel (EA)
We will refer to code from the previous article as we apply code organization improvements. Approaches to structuring code may vary—some developers prefer organizing it as they build, while others may choose to evaluate and refine it afterward. Regardless of the approach, a quick assessment helps determine whether the code meets essential standards. As I mentioned earlier in Zhaojun Zhang’s quote, a well-organized code isn’t obligatory. Some developers are comfortable with disorganized code as long as it runs. However, this often leads to significant challenges, especially when scaling projects. Poorly structured code makes it harder to maintain, extend, and debug, limiting long-term growth. That’s why I strongly encourage best practices in code organization. Let's explore more in the next section.
Identifying code issues
Going through Admin Panel V1.24 source code, I decided to create a summary of the components that makes it easier for me to quickly comprehend it and identify issues. Generally, as the original developer, I know the components of my program, but the only challenge is on having it organized and shortening it while keeping it readable. So below I outlined about nine major pieces that allow us to grab the idea of the program, and then I will share further on the issues that I will address.
1. UI Elements and Global Variables
// 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. Authentication System:
- Hardcoded password (Password = "2024")
- Basic 2FA workflow
- Login attempt counter (failedAttempts)
- Authentication dialogs: ShowAuthenticationPrompt() and ShowTwoFactorAuthPrompt()
3. Trade Management Functions:
- Position closing functions
- Order deletion functions
- Trade execution
4. Communications Features:
- Telegram integration via SendMessageToTelegram()
- Quick message buttons (8 predefined messages)
- Message input box with character counter
5. Analytics Panel:
- Pie chart visualization (CreateAnalyticsPanel())
- Trade history analysis (GetTradeData())
- Custom chart classes: CCustomPieChart and CAnalyticsChart
6. Event Handling Structure:
- Monolithic OnChartEvent() with:
- Checks for button clicks
- Mixed UI/trade/authentication logic
- Direct function calls without routing.
7. Security Components:
- Plaintext password storage
- Basic 2FA implementation
- No encryption for Telegram credentials
- No session management
8. Initialization/Cleanup:
- OnInit() with sequential UI creation
- OnDeinit() with panel destruction
- No resource management system
9. Error Handling:
- Basic Print() statements for errors
- No error recovery mechanisms
- No transaction rollbacks
- Limited validation for trade operations
10. Formatting, indentation, and spaces:
- This aspect is well covered by the MetaEditor.
- Our code is readable except for other things like repetitive code that we will address in the next section
After looking at the code, here is a collection of organizational issues that need attention:
- Monolithic Structure - All functionality in a single file
- Tight Coupling - UI logic mixed with business logic
- Repetitive Patterns - Similar code for button/panel creation
- Security Risks - Hardcoded credentials, no encryption
- Limited Scalability - No modular architecture
- Inconsistent naming conventions mostly in the (UI Elements and Global Variables)
Reorganizing the code
After this step, the program must still be able to run and maintain its original functionality. Based on previous evaluations, we will discuss some aspects below as a way to improve our code organization.
1. Monolithic Structure:
This situation is challenging for us, as it makes the code unnecessarily long. We can resolve this by splitting the code into modular components. This involves developing separate files for different functionalities, making them reusable while keeping the main code clean and manageable. The declarations and implementations will reside outside the main file and be included as needed.
To maintain clarity and avoid overloading this article with too much information, I have preserved the detailed discussion for the next article. However, here’s a brief example: we could create an include file for authentication. See the code below:
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; } };
This file will then be included in our main Admin Panel code as shown below:
#include <AuthManager.mqh>
2. Tight Coupling:
In this implementation, we address the issue of mixing user interface handlers with trade logic. This can be improved by decoupling them using interfaces. To achieve this, we can create a dedicated header file or class, based on the built-in CTradeclass.
For better organization, I will create a TradeManager header file to handle trade-related logic separately, making it reusable and easier to manage. By including this custom class and properly separating trade logic from user interface logic, we improve code maintainability and readability.
#include<TradeManager.mqh>
3. Repeated Code Patterns
The issue here is the duplication of UI creation code, particularly for panels and buttons. We can resolve this by creating UI helper functions, which will streamline the process of building interfaces and their elements.
Below is an example of a helper function for button creation:
//+------------------------------------------------------------------+ //| 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; }
The remaining buttons can be created using this approach, eliminating redundant code and ensuring a more structured and maintainable implementation. Below is an example for the creation of a Trade Management Panel access button. Most of the implementation is included in the final organized code found in the results section.
CreateButton(TradeMgmtAccessButton, AdminHomePanel, "TradeMgmtAccessButton", "Trade Management Panel", 10, 20, 250, 60)
4. Security Risks:
For simplicity, we continued using hardcoded passwords, but we already covered this aspect in Part (VII). These can be resolved by using encrypted configuration.
5. Inconsistent Naming:
At certain points, I used shorthand for names to reduce text length. However, this can create challenges when collaborating with others. The best way to address this is by enforcing consistent naming conventions.
For example, in the code snippet below, I used a lowercase "t" instead of an uppercase "T" and shorthand for "management," which can lead to confusion for other developers not knowing the author's intention. Additionally, the function name for the theme button is overly wordy and could be more concise for better readability. See the example below illustrating these issues:
CButton tradeMgmtAccessButton; // Inconsistent void OnToggleThemeButtonClick(); // Verbose
Here's the code resolved:
CButton TradeManagementAccessButton; // PascalCase void HandleThemeToggle(); // Action-oriented
Results and Testing
After a careful application of the solution, we discussed here is our final-resolved code. In this piece, we removed theme functionality so that we will build a separate theme dedicated header file. The move is to cater for problems associated with the extending in-built classes for theme related functionality.
//+------------------------------------------------------------------+ //| 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(); }
In the process of code organization, the transition from the original code (v1.24) to the updated version (v1.25) reflects several key improvements:
1. Enhanced Modularity and Structure: The updated code introduces a more logical grouping of functionalities within sections. For instance, in v1.24, much of the UI initialization and management was scattered or not clearly separated. The new version organizes the code into well-defined sections like "Trade Management Functions", "Authentication Management", and "Panel Initialization Functions". This segregation makes the code more readable and easier to maintain. Each section now begins with a clear header, indicating what functionality is being implemented, which aids in quickly navigating through the codebase.
2. Separation of Concerns: In v1.24, functions like ShowAuthenticationPrompt and ShowTwoFactorAuthPrompt were mixed with global declarations and lacked clear separation from initialization logic. The updated code in v1.25 separates these concerns more effectively. Initialization functions like InitializeAuthenticationDialog and InitializeTwoFactorAuthDialog are now distinct from event handlers or utility functions, reducing complexity in each segment. This separation helps in understanding the lifecycle of different components of the EA, from initialization to interaction handling. Additionally, the introduction of specific classes for handling analytics (CAnalyticsChart and CCustomPieChart) encapsulates complex chart drawing logic, promoting reuse and maintaining a single responsibility principle for each class or function.
Upon successful compilation, the Admin Panel launches successfully and prompts for security verification as the initial step. After entering the correct credentials, the system grants access to the Admin Home Panel.
For reference, the default password is set to "2024", as specified in the code. Below is an image showcasing the Expert Advisor (EA) launch on the chart:
Adding the Admin Panel to the chart from the new source code
One in-built example of a well-organized code
Before writing this article, I came across a built-in implementation of Dailog class in MQL5. This served as a motivating example, inspiring efforts to create more readable and reusable code. The example is a Controls Expert Advisor, located in the Experts folder under Examples. See the image below.
Locating the Controls EA in MetaTrader 5
The example application is highly responsive and includes numerous interface features. See the image below.
Adding Controls to the Chart
To view the source code, open MetaEditor, navigate to the Experts folder, and locate the Controls source code in the Examples folder, as shown below.
Accessing Controls Source in MetaEditor
This serves as the main code for the program, with much of the UI logic distributed in CControlsDialog, which leverages the Dialog class to simplify interface creation. The source code is compact, readable, and scalable.//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
This example demonstrates professional code organization through a modular architecture and best practice design principles. The code separates concerns cleanly by delegating all UI logic to the CControlsDialog class (defined in the included ControlsDialog.mqh module), while the main file focuses solely on application lifecycle management.
This modular approach encapsulates implementation details, exposing only standardized interfaces like Create(), Run(), and Destroy() for initialization, execution, and cleanup. By forwarding chart events directly to the dialog component via ExtDialog. ChartEvent(), the architecture decouples event handling from core application logic, ensuring reusability and testability.
The structure adheres to high standards through minimalistic main file design—containing no UI declarations—and enforces strict component boundaries, enabling safe modifications and team collaboration. This pattern exemplifies scalable MQL5 development, where discrete modules manage specific responsibilities, reducing cognitive load while promoting maintainability through clear interface contracts and systematic resource management.
Conclusion
We have embarked on our journey towards achieving a more structured and enterprise-grade code organization. Significant improvements have been made by addressing inconsistent naming conventions, enhancing comments, refining error handling, and logically grouping related functionalities. These changes have resulted in a reduced main file size, clear separation of concerns, creation of reusable components, and establishment of consistent naming conventions.
These organizational changes result in a codebase that's easier to navigate and more scalable, allowing for easier updates and additions to specific areas of functionality without affecting others. This structured approach also facilitates better testing and debugging by isolating different parts of the system.
Our next steps involve further modularizing our program to ensure its components can be easily reused in other Expert Advisor (EA) and indicator projects. This ongoing effort will ultimately benefit the entire trading community. While we have laid a strong foundation, there are still several aspects that warrant deeper discussion and analysis, which we will explore in greater detail in our next article.
I am confident that with this guide, we can develop clean, readable, and scalable code. By doing so, we improve our own projects and also attract other developers and contribute to building a large code library for future reuse. This collective effort enhances our community's efficiency and fosters innovation.
Your comments and feedback are welcome in the section below.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use