English 日本語
preview
Erstellen eines Handelsadministrator-Panels in MQL5 (Teil IX): Code Organisation (I)

Erstellen eines Handelsadministrator-Panels in MQL5 (Teil IX): Code Organisation (I)

MetaTrader 5Beispiele | 16 Juni 2025, 13:55
21 0
Clemence Benjamin
Clemence Benjamin

Einführung

Langer Code kann schwer zu verstehen sein, vor allem, wenn er nicht gut organisiert ist. Dies führt häufig dazu, dass Projekte aufgegeben werden. Aber bedeutet das, dass ich das Trading-Admin-Panel-Projekt aufgeben muss, jetzt wo es mit mehreren integrierten Panels gewachsen ist? Auf keinen Fall! Stattdessen brauchen wir Strategien, um den reibungslosen Ablauf zu gewährleisten.

Das bringt uns zur heutigen Diskussion, in der wir untersuchen, wie die Codeorganisation die Algorithmenentwicklung in MQL5 verbessern kann. Der Erfolg von MQL5 und anderen Großprojekten lässt sich häufig auf ihren strukturierten Ansatz zurückführen, der es ihnen ermöglicht, umfangreiche Codebasen effizient zu verwalten und zu pflegen.

Ohne ordnungsgemäße Dokumentation und Struktur wird die Pflege eines Codes zu einer Herausforderung, die künftige Änderungen noch schwieriger macht.

In dieser Diskussion werden wir praktische Lösungen für diese Herausforderungen untersuchen und uns dabei auf die Strukturierung des Handels Admin Panels für langfristige Skalierbarkeit konzentrieren. Die Organisation des Codes ist nicht nur eine Annehmlichkeit, sondern ein entscheidender Faktor beim Schreiben effizienter, wartbarer Programme. Durch die Übernahme dieser Best Practices können wir sicherstellen, dass MQL5-Projekte robust, gemeinsam nutzbar und skalierbar bleiben, sodass einzelne Entwickler komplexe Anwendungen erstellen und aufrechterhalten können.

Bevor wir näher darauf eingehen, möchte ich die wichtigsten Punkte dieser Diskussion erläutern:

  1. Überblick über die Diskussion
  2. Organisation des Codes verstehen.
  3. Implementierung auf der Administrationsoberfläche (EA)
    1. Identifizierung von Code-Problemen.
    2. Umstrukturierung des Codes.
  4. Ergebnisse und Tests
  5. Ein integriertes Beispiel für einen gut organisierten Code
  6. Schlussfolgerung


Überblick über die Diskussion

In der vorangegangenen Diskussion dieser Serie haben wir eine bedeutende Erweiterung unseres Programms erlebt, als wir weitere spezialisierte Panels in das Admin-Panel einführten, die es zu einem unverzichtbaren Dashboard für jeden Händler machten. Mit diesen Ergänzungen haben wir nun vier Panels: das Admin-Home-Panel, das Kommunikations-Panel, das Handelsmanagement-Panel und das Analyse-Panel. Der Code ist beträchtlich gewachsen und hat die Hauptstruktur umrissen, aber es gibt noch viel zu tun, um die Funktionalität der einzelnen Funktionen zu verbessern.

Als ich darüber nachdachte, den nächsten Schritt zu tun und noch mehr Funktionen hinzuzufügen, wurde mir klar, wie wichtig es war, den gesamten Code zu überarbeiten, um ihn besser zu organisieren. So entstand die Idee zu diesem Thema. Anstatt einfach ein fertiges Programm zu präsentieren, hielt ich es für sinnvoll, den Prozess der Verfeinerung und Organisation des Codes mit Ihnen durchzugehen. Im nächsten Abschnitt werden wir auf der Grundlage meiner Recherchen mehr über die Organisation des Codes herausfinden.

Ich denke, dass am Ende dieser Diskussion jemand Kenntnisse erworben haben muss, um diese Fragen zu beantworten:

  1. Wie entwickelt man große Programme?
  2. Wie kann ich mein großes Programm anderen verständlich machen?


Verständnis der Codeorganisation

Verschiedenen Quellen zufolge bezieht sich der Begriff Codeorganisation auf die Praxis der Strukturierung und Anordnung von Code in einer Weise, die die Lesbarkeit, Wartbarkeit und Skalierbarkeit verbessert. Gut organisierter Code erleichtert Entwicklern das Verständnis, die Fehlersuche und die Erweiterung ihrer Programme.

Wie der Software-Ingenieur Zhaojun Zhang einmal sagte: „Die Organisation von Code ist wie die Ordnung in Ihrem Haus: Sie müssen es nicht jeden Tag aufräumen, und Sie können immer noch in Ihrem Haus leben, egal wie unordentlich es ist, solange Sie es tolerieren können. Es verfolgt dich nur, wenn du dringend etwas finden musst, das du schon lange nicht mehr angefasst hast, oder wenn du Gäste zu einem schicken Abendessen einladen willst.“

Ich glaube, diese Analogie macht deutlich, dass die Organisation des Codes nicht nur für Ihren eigenen Arbeitsablauf wichtig ist, sondern auch für andere, die mit Ihrem Code arbeiten könnten. Lassen Sie uns diese Schlüsselkonzepte der Codeorganisation - Lesbarkeit, Wartbarkeit und Skalierbarkeit - aufschlüsseln und ihre Bedeutung untersuchen, insbesondere im Kontext der Algorithmenentwicklung in MQL5.

1. Lesbarkeit:

Die Lesbarkeit bezieht sich darauf, wie leicht jemand die Logik und Struktur des Codes verstehen kann. Im Zusammenhang mit MQL5 ist dies besonders wichtig, da der Code möglicherweise von mehreren Entwicklern bearbeitet wird, und selbst wenn Sie alleine arbeiten, werden Sie, wie bereits erwähnt, irgendwann Ihren eigenen Code überarbeiten oder debuggen wollen.

Wesentliche Merkmale:

  • Klare Variablenbenennung: Verwenden Sie aussagekräftige Namen für Variablen, Funktionen und Klassen. Anstatt vage Namen wie a, b oder temp zu verwenden, wählen Sie beschreibende Namen, die den Zweck vermitteln, wie movingAveragePeriod oder signalStrength.
  • Kommentare: Gute Kommentare erklären, warum es bestimmte Codeblöcke gibt, und nicht nur, was sie tun. Dies ist wichtig, um die Absicht des Algorithmus zu dokumentieren.
  • Konsistente Formatierung: Einrückung und Zeilenabstand helfen, den Code in lesbare Blöcke aufzuteilen. Verwenden Sie beispielsweise eine einheitliche Einrückung für Schleifen, Konditionale und Funktionen.
  • Modularer Code: Die Aufteilung des Codes in kleine, in sich geschlossene Funktionen oder Klassen, die jeweils eine bestimmte Aufgabe erledigen (wie die Berechnung eines gleitenden Durchschnitts oder die Überprüfung einer Handelsbedingung), verbessert die Lesbarkeit.
Vorteile:

  • Schnelles Debugging: Lesbarer Code macht es einfacher, Fehler zu erkennen und zu korrigieren.
  • Kollaboration: Wenn Ihr Code sauber und verständlich ist, ist es für andere Entwickler viel einfacher, mit Ihnen zusammenzuarbeiten oder Ihnen bei der Fehlerbehebung zu helfen.
  • Schnelleres Onboarding: Wenn Sie ein Projekt überarbeiten, sorgt lesbarer Code dafür, dass Sie keine Zeit damit verschwenden, Ihre eigene Arbeit erneut zu verstehen.

2. Wartbarkeit:

Unter Wartbarkeit versteht man die Leichtigkeit, mit der Code im Laufe der Zeit geändert oder erweitert werden kann, insbesondere wenn neue Funktionen hinzugefügt oder Fehler behoben werden müssen. Beim algorithmischen Handel, wie bei MQL5, wo sich die Strategien oft weiterentwickeln, ist die Wartbarkeit entscheidend für den langfristigen Erfolg.

Wesentliche Merkmale:

  • Modularität: Durch die Verwendung von Funktionen oder Klassen zur Aufteilung verschiedener Aufgaben (z. B. eine Funktion zur Abwicklung des Handels, eine andere zur Berechnung von Indikatoren) schaffen Sie isolierte Teile des Systems. Änderungen können in einem Bereich vorgenommen werden, ohne dass dies Auswirkungen auf andere Bereiche hat.
  • Trennung der Belange: Jeder Teil des Codes sollte eine Zuständigkeit haben. So sollte beispielsweise die Logik für die Platzierung von Geschäften von der Logik für die Bewertung der Marktbedingungen getrennt sein.
  • Verwendung von Bibliotheken und eingebauten Funktionen: Anstatt das Rad neu zu erfinden, nutzen Sie die in MQL5 integrierten Funktionen und Bibliotheken für gängige Aufgaben wie gleitende Durchschnitte oder Orderplatzierung, die Komplexität und Fehler reduzieren können.
  • Versionskontrolle: Verwenden Sie Versionskontrollsysteme (e.g., Git und MQL5 Storage), um Änderungen zu verfolgen, damit Sie diese rückgängig machen können, wenn eine Änderung zu Fehlern oder unerwartetem Verhalten führt.

Vorteile:

  • Künftige Änderungen: Wenn sich die Strategien weiterentwickeln, ermöglicht die Beibehaltung einer gut strukturierten Codebasis den Entwicklern, mit minimalem Aufwand neue Funktionen zu implementieren oder Anpassungen vorzunehmen.
  • Fehlerbehebungen: Wenn Fehler entdeckt werden, können Sie mit wartbarem Code Probleme schnell beheben, ohne andere Teile des Systems zu beeinträchtigen.
  • Effizienz: Die Entwickler verbringen weniger Zeit damit, herauszufinden, wie der Code funktioniert, was zu schnelleren Aktualisierungen und weniger Fehlern führt.

3. Skalierbarkeit:

Skalierbarkeit bezieht sich auf die Fähigkeit des Codes, wachsende Arbeitsmengen zu bewältigen oder wachsende Daten/Funktionsanforderungen zu erfüllen. Da die Handelsstrategien immer komplexer und datenintensiver werden, ist die Skalierbarkeit für einen reibungslosen Betrieb unerlässlich.

Wesentliche Merkmale:

  • Effiziente Algorithmen: Beim algorithmischen Handel müssen Sie unter Umständen große Mengen historischer Daten verarbeiten, viele Handelsgeschäfte ausführen oder mehrere Vermögenswerte gleichzeitig analysieren. Die Optimierung Ihrer Algorithmen im Hinblick auf Geschwindigkeit und Speichernutzung ist von entscheidender Bedeutung.
  • Daten-Strukturen: Die Auswahl geeigneter Datenstrukturen wie Arrays, Listen oder Maps hilft bei der effizienten Verwaltung größerer Datenmengen. MQL5 bietet Datenstrukturen wie Array und Struct, die zur Skalierung Ihrer Strategie genutzt werden können.
  • Parallele Verarbeitung: MQL5 unterstützt Multithreading, sodass Sie mehrere Aufgaben parallel ausführen können. Dies ist besonders nützlich bei komplexen Handelsstrategien oder Backtests, bei denen verschiedene Aufgaben (wie Marktanalyse und Auftragsausführung) gleichzeitig erledigt werden können.
  • Asynchrone Operationen: Bei Aufgaben, die die Ausführung anderer Teile des Algorithmus nicht blockieren müssen (z. B. das Abrufen von Daten aus externen APIs), trägt die Verwendung asynchroner Operationen dazu bei, dass das System reaktionsfähig bleibt.

Vorteile:

  • Umgang mit Bigger Data: Ein skalierbarer Code kann größere Mengen an Marktdaten verarbeiten oder zusätzliche Vermögenswerte ohne nennenswerte Leistungseinbußen einbeziehen.
  • Unterstützung für Wachstum: Wenn der Algorithmus zusätzliche Funktionen aufnehmen muss (z. B. den Handel mit mehreren Paaren, die Anwendung von Modellen des maschinellen Lernens oder ein verbessertes Risikomanagement), bietet skalierbarer Code die Flexibilität, ohne größere Überarbeitungen zu wachsen.
  • Leistung in Echtzeit: In einer Live-Handelsumgebung stellt die Skalierbarkeit sicher, dass Ihr Algorithmus Echtzeit-Datenfeeds und Auftragsausführungen ohne Verzögerung verarbeiten kann.

In MQL5 überschneiden sich Lesbarkeit, Wartbarkeit und Skalierbarkeit oft und verstärken sich gegenseitig. Eine lesbare und modulare Funktion ist beispielsweise leichter zu pflegen, wenn sie angepasst werden muss. Ebenso ist skalierbarer Code in der Regel modularer, was auch seine Lesbarkeit und Wartbarkeit verbessert. Bei der Entwicklung von Handelsalgorithmen sorgt dieses Gleichgewicht dafür, dass der Code jetzt gut funktioniert und angepasst oder erweitert werden kann, wenn sich die Handelsstrategien weiterentwickeln oder die Leistungsanforderungen mit mehr Daten steigen.

Bei dieser Entwicklung haben wir zum Beispiel in Teil 1 mit einem Kommunikationspanel begonnen. Im weiteren Verlauf des Projekts haben wir nahtlos neue Panels mit unterschiedlichen Spezialisierungen integriert, ohne die Kernlogik zu unterbrechen. Dies zeigt die Skalierbarkeit, aber es gibt immer noch Schlüsselkonzepte, die die Wiederverwendbarkeit der bestehenden Funktionen im Code verbessern.


Implementierung auf der Administrationsoberfläche (EA)

Wir werden auf den Code aus dem vorherigen Artikel verweisen, wenn wir Verbesserungen der Codeorganisation anwenden. Die Ansätze zur Strukturierung des Codes können unterschiedlich sein - manche Entwickler bevorzugen es, den Code während der Erstellung zu organisieren, während andere ihn erst im Nachhinein bewerten und verfeinern wollen. Unabhängig von der Vorgehensweise hilft eine schnelle Bewertung, um festzustellen, ob der Code den wesentlichen Standards entspricht.
Wie bereits das Zitat von Zhaojun Zhang besagt, ist ein gut organisierter Code nicht zwingend erforderlich. Manche Entwickler sind mit unorganisiertem Code zufrieden, solange er läuft. Dies führt jedoch häufig zu erheblichen Herausforderungen, insbesondere bei der Skalierung von Projekten. Schlecht strukturierter Code erschwert die Wartung, Erweiterung und Fehlersuche und schränkt das langfristige Wachstum ein. Deshalb empfehle ich nachdrücklich bewährte Praktiken bei der Codeorganisation. Im nächsten Abschnitt werden wir mehr darüber erfahren.


Identifizierung von Code-Problemen

Als ich den Quellcode des Admin Panel V1.24 durchging, beschloss ich, eine Zusammenfassung der Komponenten zu erstellen, die es mir erleichtert, sie schnell zu verstehen und Probleme zu erkennen. Im Allgemeinen kenne ich als ursprünglicher Entwickler die Bestandteile meines Programms, aber die einzige Herausforderung besteht darin, es zu organisieren und zu kürzen, während es lesbar bleibt. Im Folgenden habe ich etwa neun Hauptbestandteile skizziert, die es uns ermöglichen, die Idee des Programms zu erfassen, und dann werde ich auf die Themen eingehen, die ich ansprechen werde.

1. UI-Elemente und globale Variablen

// 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. Authentifizierungssystem:

  •  Ein fest-kodiertes Passwort(Password = "2024")
  •  Grundlegender 2FA-Arbeitsablauf
  •  Zähler für Anmeldeversuche (failedAttempts)
  • Authentifizierungsdialoge:  ShowAuthenticationPrompt() und ShowTwoFactorAuthPrompt()

3. Funktionen der Handelsverwaltung:

  • Funktionen zum Schließen von Positionen
  • Funktionen zum Löschen von Aufträgen
  • Ausführung des Handels

 4. Kommunikationsmerkmale:

  •  Telegram-Integration über SendMessageToTelegram()
  •  Tasten für Sofortnachrichten (8 vordefinierte Nachrichten)
  •  Nachrichteneingabefeld mit Zeichenzähler

 5. Analytik-Panel:

  • Kreisdiagramm-Visualisierung (CreateAnalyticsPanel())
  • Analyse der Handelsgeschichte (GetTradeData())
  • Nutzerdefinierte Chart-Klassen:  CCustomPieChart und CAnalyticsChart

 6. Struktur der Ereignisbehandlung:

  • Monolithisches OnChartEvent() mit:
    •   Prüft, ob eine Schaltfläche angeklickt wurde
    •    Gemischte UI/Handels-/Authentifizierungslogik
    •    Direkte Funktionsaufrufe ohne Routing.

 7. Sicherheitskomponenten:

  • Speicherung von Klartext-Passwörtern
  • Grundlegende 2FA-Implementierung
  • Keine Verschlüsselung für Telegram-Anmeldeinformationen
  • Keine Sitzungsverwaltung

 8. Initialisierung/Bereinigung:

  • OnInit() mit sequenzieller UI-Erstellung
  • OnDeinit() mit Panel-Abbau
  • Kein System zur Ressourcenverwaltung

 9. Fehlerbehandlung:

  • Einfache Print()-Anweisungen für Fehler
  • Keine Mechanismen zur Fehlerbehebung
  • Keine Transaktions-Rollbacks
  • Begrenzte Validierung für Handelsgeschäfte

10. Formatierung, Einrückung und Leerzeichen:

  • Dieser Aspekt wird durch den MetaEditor gut abgedeckt.
  • Unser Code ist lesbar, abgesehen von anderen Dingen wie sich wiederholendem Code, den wir im nächsten Abschnitt behandeln werden

Nachdem wir uns den Code angesehen haben, finden Sie hier eine Reihe von organisatorischen Problemen, die beachtet werden müssen:

  • Monolithische Struktur - Alle Funktionen in einer einzigen Datei
  • Enge Kopplung - UI-Logik gemischt mit Geschäftslogik
  • Sich wiederholende Muster - Ähnlicher Code für die Erstellung von Schaltflächen/Panels
  • Sicherheitsrisiken - Festcodierte Anmeldedaten, keine Verschlüsselung
  • Begrenzte Skalierbarkeit - Keine modulare Architektur
  • Inkonsistente Namenskonventionen vor allem im Bereich (UI-Elemente und globale Variablen)


Umstrukturierung des Codes

Nach diesem Schritt muss das Programm noch lauffähig sein und seine ursprüngliche Funktionalität beibehalten. Auf der Grundlage früherer Bewertungen werden wir im Folgenden einige Aspekte erörtern, um unsere Codeorganisation zu verbessern.

1. Monolithische Struktur:

Diese Situation ist eine Herausforderung für uns, da sie den Code unnötig lang macht. Wir können dieses Problem lösen, indem wir den Code in modulare Komponenten aufteilen. Dies beinhaltet die Entwicklung separater Dateien für verschiedene Funktionen, um sie wiederverwendbar zu machen, während der Hauptcode sauber und überschaubar bleibt. Die Deklarationen und Implementierungen befinden sich außerhalb der Hauptdatei und werden bei Bedarf eingefügt.

Um die Übersichtlichkeit zu wahren und diesen Artikel nicht mit zu vielen Informationen zu überfrachten, habe ich mir die ausführliche Diskussion für den nächsten Artikel aufgehoben. Hier ein kurzes Beispiel: Wir könnten eine Include-Datei für die Authentifizierung erstellen. Siehe den nachstehenden Code:

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;
    }
};

Diese Datei wird dann, wie unten gezeigt, in unseren Hauptcode für das Admin-Panel aufgenommen:

#include <AuthManager.mqh>

2. Enge Kopplung:

In dieser Implementierung befassen wir uns mit dem Problem der Vermischung von Nutzeroberflächen-Handlern mit der Handelslogik. Dies kann durch Entkopplung über Schnittstellen verbessert werden. Um dies zu erreichen, können wir eine eigene Header-Datei oder Klasse erstellen, die auf der integrierten Klasse CTrade.

Zur besseren Organisation werde ich eine Header-Datei für TradeManageerstellen, um die handelsbezogene Logik separat zu behandeln, damit sie wiederverwendbar und leichter zu verwalten ist. Durch die Einbindung dieser nutzerdefinierten Klasse und die korrekte Trennung der Handelslogik von der Logik der Nutzeroberfläche wird der Code besser wartbar und lesbar.

#include<TradeManager.mqh>

3. Wiederholte Code-Muster

Das Problem hierbei ist die Duplizierung des Codes für die Erstellung der Nutzeroberfläche, insbesondere für Bedienfelder und Schaltflächen. Wir können dieses Problem lösen, indem wir UI-Helper-Funktionen erstellen, die den Prozess der Erstellung von Schnittstellen und deren Elementen rationalisieren.

Nachfolgend finden Sie ein Beispiel für eine Hilfsfunktion zur Erstellung von Schaltflächen:

//+------------------------------------------------------------------+
//| 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;
}

Die übrigen Schaltflächen können mit diesem Ansatz erstellt werden, wodurch redundanter Code vermieden und eine strukturiertere und wartungsfreundlichere Implementierung gewährleistet wird. Nachfolgend finden Sie ein Beispiel für die Erstellung einer Schaltfläche für den Zugriff auf das Handelsmanagement-Panel. Der größte Teil der Implementierung ist in dem endgültigen organisierten Code enthalten, der im Abschnitt Ergebnisse zu finden ist.

CreateButton(TradeMgmtAccessButton, AdminHomePanel, "TradeMgmtAccessButton", "Trade Management Panel", 10, 20, 250, 60)

4. Sicherheitsrisiken:

Der Einfachheit halber haben wir weiterhin hart kodierte Passwörter verwendet, aber diesen Aspekt haben wir bereits in Teil (VII) behandelt. Diese Probleme können durch die Verwendung einer verschlüsselten Konfiguration behoben werden.

5. Inkonsistente Namensgebung:

An einigen Stellen habe ich die Namen abgekürzt, um die Textlänge zu verringern. Dies kann jedoch bei der Zusammenarbeit mit anderen zu Problemen führen. Dies lässt sich am besten durch die Durchsetzung einheitlicher Namenskonventionen erreichen.

Im folgenden Codeausschnitt habe ich beispielsweise ein kleines „t“ anstelle eines großen „T“ und eine Abkürzung für „Management“ verwendet, was bei anderen Entwicklern, die die Absicht des Autors nicht kennen, zu Verwirrung führen kann. Außerdem ist der Funktionsname für die Themenschaltfläche übermäßig lang und könnte zur besseren Lesbarkeit prägnanter sein. Das folgende Beispiel veranschaulicht diese Probleme:

CButton tradeMgmtAccessButton;  // Inconsistent
void OnToggleThemeButtonClick(); // Verbose

Hier ist der bessere Code:

CButton TradeManagementAccessButton;      //  PascalCase
void HandleThemeToggle();        // Action-oriented

 

Ergebnisse und Tests

Nach einer sorgfältigen Anwendung der Lösung, die wir besprochen haben, ist hier unser endgültiger Code. In diesem Teil haben wir die Funktionalität des Themas entfernt, sodass wir eine separate, dem Thema gewidmete Header-Datei erstellen werden. Damit sollen Probleme im Zusammenhang mit der Erweiterung der integrierten Klassen für themenbezogene Funktionen behoben werden. 

//+------------------------------------------------------------------+
//|                                             Admin Panel.mq5      |
//|                           Copyright 2024, Clemence Benjamin      |
//|        https://www.mql5.com/de/users/billionaire2024/seller      |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/de/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();
}   

Bei der Organisation des Codes spiegelt der Übergang vom ursprünglichen Code (v1.24) zur aktualisierten Version (v1.25) mehrere wichtige Verbesserungen wider:

1. Verbesserte Modularität und Struktur: Der aktualisierte Code führt eine logischere Gruppierung der Funktionalitäten innerhalb der Abschnitte ein. In der Version 1.24 war beispielsweise ein Großteil der UI-Initialisierung und -Verwaltung verstreut oder nicht klar getrennt. Die neue Version gliedert den Code in klar definierte Abschnitte wie „Trade Management Functions“, „Authentication Management“ und „Panel Initialization Functions“. Durch diese Trennung wird der Code besser lesbar und leichter zu pflegen. Jeder Abschnitt beginnt nun mit einer klaren Überschrift, die angibt, welche Funktionen implementiert werden, was eine schnelle Navigation durch die Codebasis ermöglicht.

2. Trennung der Belange: In v1.24 wurden Funktionen wie ShowAuthenticationPrompt und ShowTwoFactorAuthPrompt mit globalen Deklarationen vermischt und waren nicht klar von der Initialisierungslogik getrennt. Der aktualisierte Code in v1.25 trennt diese Belange effektiver. Initialisierungsfunktionen wie InitializeAuthenticationDialog und InitializeTwoFactorAuthDialog sind jetzt von der Ereignisbehandlung und den Hilfsfunktionen getrennt, was die Komplexität in jedem Segment reduziert. Diese Trennung hilft, den Lebenszyklus der verschiedenen Komponenten des EA zu verstehen, von der Initialisierung bis zur Interaktionsbehandlung. Darüber hinaus kapselt die Einführung spezifischer Klassen für die Handhabung von Analysen (CAnalyticsChart und CCustomPieChart) komplexe Chartzeichnungslogik, fördert die Wiederverwendung und die Beibehaltung eines einzigen Verantwortungsprinzips für jede Klasse oder Funktion.

Nach erfolgreicher Kompilierung wird das Admin Panel erfolgreich gestartet und fordert im ersten Schritt zur Sicherheitsüberprüfung auf. Nach Eingabe der korrekten Anmeldedaten gewährt das System Zugriff auf das Admin Home Panel.

Als Referenz ist das Standardpasswort auf „2024“ eingestellt, wie im Code angegeben. Das folgende Bild zeigt den Start des Expert Advisor (EA) im Chart:

Starten des Admin Panels aus neu organisiertem Code

Hinzufügen des Admin-Panels zum Chart aus dem neuen Quellcode



Ein integriertes Beispiel für einen gut organisierten Code

Bevor ich diesen Artikel schrieb, stieß ich auf eine integrierte Implementierung der Klasse Dailog in MQL5. Dies diente als motivierendes Beispiel, das dazu anregte, lesbaren und wiederverwendbaren Code zu erstellen. Das Beispiel ist ein Controls Expert Advisor, der sich im Ordner Experts unter Examples befindet. Siehe das Bild unten.

Das Beispiel Controls EA

Auffinden des Controls EA in MetaHändler 5

Die Beispielanwendung ist sehr reaktionsschnell und verfügt über zahlreiche Schnittstellenfunktionen. Siehe das Bild unten.

Hinzufügen von Controls zum Chart

Hinzufügen von Controls zum Chart

Um den Quellcode anzuzeigen, öffnen Sie MetaEditor, navigieren Sie zum Ordner Experts und suchen Sie den Quellcode von Controls im Ordner Examples, wie unten gezeigt.

Zugriff auf die Quelle von Controls im MetaEditor

Zugriff auf die Quelle von Controls im MetaEditor

Dies dient als Hauptcode für das Programm, wobei ein Großteil der UI-Logik in CControlsDialog verteilt ist, das die Klasse Dialog nutzt, um die Erstellung der Schnittstelle zu vereinfachen. Der Quellcode ist kompakt, lesbar und skalierbar.
//+------------------------------------------------------------------+
//|                                                     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);
  }
//+------------------------------------------------------------------+

Dieses Beispiel zeigt eine professionelle Codeorganisation durch eine modulare Architektur und Best-Practice-Designprinzipien. Der Code trennt die Bereiche sauber voneinander, indem er die gesamte UI-Logik an die Klasse CControlsDialog (definiert im enthaltenen Modul ControlsDialog.mqh) delegiert, während sich die Hauptdatei ausschließlich auf die Verwaltung des Anwendungslebenszyklus konzentriert.

Dieser modulare Ansatz kapselt Implementierungsdetails und stellt nur standardisierte Schnittstellen wie Create(), Run() und Destroy() für Initialisierung, Ausführung und Bereinigung zur Verfügung. Durch die Weiterleitung von Chartereignissen direkt an die Dialogkomponente über ExtDialog. ChartEvent() entkoppelt die Architektur die Ereignisbehandlung von der Kernanwendungslogik und gewährleistet Wiederverwendbarkeit und Testbarkeit.

Die Struktur entspricht hohen Standards durch ein minimalistisches Design der Hauptdatei, das keine UI-Deklarationen enthält, und setzt strenge Komponentengrenzen durch, was sichere Änderungen und die Zusammenarbeit im Team ermöglicht. Dieses Muster veranschaulicht die skalierbare MQL5-Entwicklung, bei der einzelne Module bestimmte Aufgaben übernehmen, wodurch die kognitive Belastung verringert und gleichzeitig die Wartbarkeit durch klare Schnittstellenverträge und systematisches Ressourcenmanagement gefördert wird.


Schlussfolgerung

Wir haben uns auf den Weg gemacht, eine besser strukturierte und unternehmensgerechte Code-Organisation zu schaffen. Wesentliche Verbesserungen wurden durch die Beseitigung inkonsistenter Namenskonventionen, die Verbesserung der Kommentare, die Verfeinerung der Fehlerbehandlung und die logische Gruppierung verwandter Funktionen erzielt. Diese Änderungen haben zu einer Verringerung der Größe der Hauptdatei, einer klaren Trennung der Bereiche, der Schaffung von wiederverwendbaren Komponenten und der Einführung einheitlicher Namenskonventionen geführt.

Diese organisatorischen Änderungen führen zu einer Codebasis, die einfacher zu navigieren und besser skalierbar ist, sodass Aktualisierungen und Ergänzungen zu bestimmten Funktionsbereichen einfacher sind, ohne andere Bereiche zu beeinträchtigen. Dieser strukturierte Ansatz erleichtert auch das Testen und Debuggen, da verschiedene Teile des Systems isoliert werden.

Unsere nächsten Schritte bestehen darin, unser Programm weiter zu modularisieren, um sicherzustellen, dass seine Komponenten leicht in anderen Expert Advisor (EA) und Indikatorprojekten wiederverwendet werden können. Diese fortlaufenden Bemühungen werden letztlich der gesamten Handelsgemeinschaft zugute kommen. Wir haben zwar eine solide Grundlage geschaffen, doch gibt es noch einige Aspekte, die eine eingehendere Erörterung und Analyse verdienen und die wir in unserem nächsten Artikel genauer untersuchen werden.

Ich bin zuversichtlich, dass wir mit diesem Leitfaden sauberen, lesbaren und skalierbaren Code entwickeln können. Auf diese Weise verbessern wir unsere eigenen Projekte, ziehen andere Entwickler an und tragen zum Aufbau einer großen Code-Bibliothek für die künftige Wiederverwendung bei. Diese gemeinsame Anstrengung steigert die Effizienz unserer Gemeinschaft und fördert die Innovation.

Wir freuen uns über Ihre Kommentare und Ihr Feedback in dem unten stehenden Abschnitt.

Zurück zur Einleitung

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16539

Beigefügte Dateien |
Einführung in MQL5 (Teil 12): Ein Anfängerleitfaden für das Erstellen nutzerdefinierter Indikatoren Einführung in MQL5 (Teil 12): Ein Anfängerleitfaden für das Erstellen nutzerdefinierter Indikatoren
Erfahren Sie, wie Sie einen nutzerdefinierten Indikator in MQL5 erstellen können. Mit einem projektbezogenen Ansatz. Dieser einsteigerfreundliche Leitfaden behandelt Indikatorpuffer, Eigenschaften und Trendvisualisierung und ermöglicht es Ihnen, Schritt für Schritt zu lernen.
Robustheitstests für Expert Advisors Robustheitstests für Expert Advisors
Bei der Entwicklung von Strategien sind viele komplizierte Details zu berücksichtigen, von denen viele für Anfänger nicht besonders interessant sind. Infolgedessen mussten viele Händler, mich eingeschlossen, diese Lektionen auf die harte Tour lernen. Dieser Artikel basiert auf meinen Beobachtungen von häufigen Fallstricken, die den meisten Anfängern bei der Entwicklung von Strategien auf MQL5 begegnen. Es wird eine Reihe von Tipps, Tricks und Beispielen bieten, die dabei helfen, die Untauglichkeit eines EA zu erkennen und die Robustheit unserer eigenen EAs auf einfache Weise zu testen. Ziel ist es, die Leser aufzuklären und ihnen zu helfen, zukünftige Betrügereien beim Kauf von EAs zu vermeiden und Fehler bei der eigenen Strategieentwicklung zu verhindern.
Datenwissenschaft und ML (Teil 33): Pandas Dataframe in MQL5, Vereinfachung der Datensammlung für ML-Nutzung Datenwissenschaft und ML (Teil 33): Pandas Dataframe in MQL5, Vereinfachung der Datensammlung für ML-Nutzung
Bei der Arbeit mit maschinellen Lernmodellen ist es wichtig, die Konsistenz der für Training, Validierung und Tests verwendeten Daten sicherzustellen. In diesem Artikel werden wir unsere eigene Version der Pandas-Bibliothek in MQL5 erstellen, um einen einheitlichen Ansatz für den Umgang mit maschinellen Lerndaten zu gewährleisten und sicherzustellen, dass innerhalb und außerhalb von MQL5, wo der Großteil des Trainings stattfindet, dieselben Daten verwendet werden.
Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 13): RSI-Sentinel-Tool Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 13): RSI-Sentinel-Tool
Die Kursentwicklung kann durch die Identifizierung von Divergenzen effektiv analysiert werden, wobei technische Indikatoren wie der RSI wichtige Bestätigungssignale liefern. Im folgenden Artikel erläutern wir, wie eine automatisierte RSI-Divergenzanalyse Trendfortsetzungen und -umkehrungen erkennen kann und damit wertvolle Einblicke in die Marktstimmung bietet.