English 日本語
preview
MQL5-Handelswerkzeuge (Teil 7): Informatives Dashboard für Multi-Symbol-Positionen und Kontoüberwachung

MQL5-Handelswerkzeuge (Teil 7): Informatives Dashboard für Multi-Symbol-Positionen und Kontoüberwachung

MetaTrader 5Handelssysteme |
120 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem letzten Artikel (Teil 6) haben wir ein dynamisches holografisches Dashboard in MetaQuotes Language 5 (MQL5) zur Überwachung von Symbolen und Zeitrahmen entwickelt, das RSI, Volatilitätswarnungen und interaktive Kontrollen mit Pulsanimationen enthält. In Teil 7 erstellen wir ein Informations-Dashboard, das Multi-Symbol-Positionen, Gesamthandel, Lots, Gewinne, ausstehende Aufträge, Swaps, Provisionen und Kontometriken wie Kontostand und Kapital mit sortierbaren Spalten und kommegetrennte Werte (CSV)-Export für einen umfassenden Überblick verfolgt. Wir werden die folgenden Themen behandeln:

  1. Verstehen der Architektur des Informations-Dashboards
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende haben Sie ein leistungsstarkes MQL5-Dashboard für die Positions- und Kontoüberwachung, das Sie anpassen können – legen Sie los!


Verstehen der Architektur des Informations-Dashboards

Wir entwickeln ein Informations-Dashboard, das einen zentralen Überblick über unsere Positionen in verschiedenen Symbolen und wichtige Kontokennzahlen bietet, sodass es einfacher ist, die Performance zu verfolgen, ohne den Bildschirm wechseln zu müssen. Diese Architektur ist von entscheidender Bedeutung, da sie verstreute Handelsdaten in einem sortierbaren Raster mit Echtzeit-Gesamtwerten und Exportoptionen organisiert und so dazu beiträgt, Probleme wie einen übermäßigen Drawdown oder unausgewogene Positionen schnell zu erkennen.

Wir erreichen dies, indem wir Positionsdetails wie Käufe, Verkäufe, Losgröße und Gewinne für jedes Symbol sammeln und gleichzeitig Kontostand, Kapital und freie Marge anzeigen, alles mit interaktiver Sortierung und einem subtilen visuellen Effekt für das Engagement. Wir planen, eine Schleife durch die Symbole zu ziehen, um Daten zu sammeln und zu summieren und sicherzustellen, dass das Dashboard leichtgewichtig und reaktionsschnell für Live-Handelsumgebungen ist. Sehen Sie sich die Visualisierung unten an, und dann können wir uns an die Umsetzung machen!

PLAN DER ARCHITEKTUR


Implementation in MQL5

Um das Programm in MQL5 zu erstellen, müssen wir die Programm-Metadaten definieren und dann einige Eingaben festlegen, die es uns ermöglichen, die Funktionsweise des Programms leicht zu ändern, ohne direkt in den Code einzugreifen, sowie die Dashboard-Objekte zu definieren.

//+------------------------------------------------------------------+
//|                                     Informational Dashboard.mq5  |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

// Input parameters
input int UpdateIntervalMs = 100; // Update interval (milliseconds, min 10ms)
input long MagicNumber = -1; // Magic number (-1 for all positions and orders)

// Defines for object names
#define PREFIX "DASH_"                   //--- Prefix for all dashboard objects
#define HEADER "HEADER_"                 //--- Prefix for header labels
#define SYMB "SYMB_"                     //--- Prefix for symbol labels
#define DATA "DATA_"                     //--- Prefix for data labels
#define HEADER_PANEL "HEADER_PANEL"      //--- Name for header panel
#define ACCOUNT_PANEL "ACCOUNT_PANEL"    //--- Name for account panel
#define FOOTER_PANEL "FOOTER_PANEL"      //--- Name for footer panel
#define FOOTER_TEXT "FOOTER_TEXT"        //--- Name for footer text label
#define FOOTER_DATA "FOOTER_DATA_"       //--- Prefix for footer data labels
#define PANEL "PANEL"                    //--- Name for main panel
#define ACC_TEXT "ACC_TEXT_"             //--- Prefix for account text labels
#define ACC_DATA "ACC_DATA_"             //--- Prefix for account data labels

Hier richten wir die Eingabeparameter ein und definieren Konstanten für Objektnamen in unserem Informational Dashboard in MQL5, was die Anpassung und organisierte Namensgebung für die Nutzeroberfläche (UI) Elemente ermöglicht. Wir definieren „UpdateIntervalMs“ als 100 Millisekunden (mit einem Minimum von 10ms), um die Aktualisierungsrate des Dashboards zu steuern und zeitnahe Aktualisierungen zu gewährleisten, ohne das System zu überlasten. Die „MagicNumber“ wird auf -1 gesetzt, um alle Positionen und Aufträge zu überwachen, oder auf einen bestimmten Wert, um für eine gezielte Überwachung nach der magischen EA-Nummer zu filtern.

Wir verwenden Defines für die einheitliche Benennung von Objekten: “PREFIX“ als „DASH_“ für alle Dashboard-Objekte, „HEADER“ für Header-Beschriftungen, „SYMB_“ für Symbol-Beschriftungen, „DATA_“ für Daten-Beschriftungen, „HEADER_PANEL“ für das Header-Panel, „ACCOUNT_PANEL“ für den Kontobereich, „FOOTER_PANEL“ für die Fußzeile, „FOOTER_TEXT“ für Fußzeilentext, „FOOTER_DATA_“ für Datenpräfixe in der Fußzeile, „PANEL“ für die Haupttafel, „ACC_TEXT_“ für Kontotextpräfixe und „ACC_DATA_“ für Kontodatenpräfixe. Diese Definitionen vereinfachen die Objektverwaltung und machen den Code besser lesbar. Als Nächstes müssen wir einige Strukturen erstellen, die unsere Informationsdaten und globalen Variablen enthalten, die wir während der gesamten Implementierung verwenden werden.

// Dashboard settings
struct DashboardSettings {               //--- Structure for dashboard settings
   int panel_x;                          //--- X-coordinate of panel
   int panel_y;                          //--- Y-coordinate of panel
   int row_height;                       //--- Height of each row
   int font_size;                        //--- Font size for labels
   string font;                          //--- Font type for labels
   color bg_color;                       //--- Background color of main panel
   color border_color;                   //--- Border color of panels
   color header_color;                   //--- Default color for header text
   color text_color;                     //--- Default color for text
   color section_bg_color;               //--- Background color for header/footer panels
   int zorder_panel;                     //--- Z-order for main panel
   int zorder_subpanel;                  //--- Z-order for sub-panels
   int zorder_labels;                    //--- Z-order for labels
   int label_y_offset;                   //--- Y-offset for label positioning
   int label_x_offset;                   //--- X-offset for label positioning
   int header_x_distances[9];            //--- X-distances for header labels (9 columns)
   color header_shades[12];              //--- Array of header color shades for glow effect
} settings = {                           //--- Initialize settings with default values
   20,                                   //--- Set panel_x to 20 pixels
   20,                                   //--- Set panel_y to 20 pixels
   24,                                   //--- Set row_height to 24 pixels
   11,                                   //--- Set font_size to 11
   "Calibri Bold",                       //--- Set font to Calibri Bold
   C'240,240,240',                       //--- Set bg_color to light gray
   clrBlack,                             //--- Set border_color to black
   C'0,50,70',                           //--- Set header_color to dark teal
   clrBlack,                             //--- Set text_color to black
   C'200,220,230',                       //--- Set section_bg_color to light blue-gray
   100,                                  //--- Set zorder_panel to 100
   101,                                  //--- Set zorder_subpanel to 101
   102,                                  //--- Set zorder_labels to 102
   3,                                    //--- Set label_y_offset to 3 pixels
   25,                                   //--- Set label_x_offset to 25 pixels
   {10, 120, 170, 220, 280, 330, 400, 470, 530}, //--- X-distances for 9 columns
   {C'0,0,0', C'255,0,0', C'0,255,0', C'0,0,255', C'255,255,0', C'0,255,255', 
    C'255,0,255', C'255,255,255', C'255,0,255', C'0,255,255', C'255,255,0', C'0,0,255'}
};

// Data structure for symbol information
struct SymbolData {                      //--- Structure for symbol data
   string name;                          //--- Symbol name
   int buys;                             //--- Number of buy positions
   int sells;                            //--- Number of sell positions
   int trades;                           //--- Total number of trades
   double lots;                          //--- Total lots
   double profit;                        //--- Total profit
   int pending;                          //--- Number of pending orders
   double swaps;                         //--- Total swap
   double comm;                          //--- Total commission
   string buys_str;                      //--- String representation of buys
   string sells_str;                     //--- String representation of sells
   string trades_str;                    //--- String representation of trades
   string lots_str;                      //--- String representation of lots
   string profit_str;                    //--- String representation of profit
   string pending_str;                   //--- String representation of pending
   string swaps_str;                     //--- String representation of swap
   string comm_str;                      //--- String representation of commission
};

// Global variables
SymbolData symbol_data[];                //--- Array to store symbol data
long totalBuys = 0;                      //--- Total buy positions across symbols
long totalSells = 0;                     //--- Total sell positions across symbols
long totalTrades = 0;                    //--- Total trades across symbols
double totalLots = 0.0;                  //--- Total lots across symbols
double totalProfit = 0.0;                //--- Total profit across symbols
long totalPending = 0;                   //--- Total pending across symbols
double totalSwap = 0.0;                  //--- Total swap across symbols
double totalComm = 0.0;                  //--- Total commission across symbols
string headers[] = {"Symbol", "Buy P", "Sell P", "Trades", "Lots", "Profit", "Pending", "Swap", "Comm"}; //--- Header labels
int column_widths[] = {140, 50, 50, 50, 60, 90, 50, 60, 60}; //--- Widths for each column
color data_default_colors[] = {clrRed, clrGreen, clrDarkGray, clrOrange, clrGray, clrBlue, clrPurple, clrBrown};
int sort_column = 3;                     //--- Initial sort column (trades) 
bool sort_ascending = false;             //--- Sort direction (false for descending to show active first)
int glow_index = 0;                      //--- Current index for header glow effect
bool glow_direction = true;              //--- Glow direction (true for forward)
int glow_counter = 0;                    //--- Counter for glow timing
const int GLOW_INTERVAL_MS = 500;        //--- Glow cycle interval (500ms)
string total_buys_str = "";              //--- String for total buys display
string total_sells_str = "";             //--- String for total sells display
string total_trades_str = "";            //--- String for total trades display
string total_lots_str = "";              //--- String for total lots display
string total_profit_str = "";            //--- String for total profit display
string total_pending_str = "";           //--- String for total pending display
string total_swap_str = "";              //--- String for total swap display
string total_comm_str = "";              //--- String for total comm display
string account_items[] = {"Balance", "Equity", "Free Margin"}; //--- Account items
string acc_bal_str = "";                 //--- Strings for account data
string acc_eq_str = "";
string acc_free_str = "";
int prev_num_symbols = 0;                //--- Previous number of active symbols for dynamic resizing

Um die Nutzeroberfläche und die Datenverwaltung zu konfigurieren, definieren wir die Struktur „DashboardSettings“, um die Layout-Einstellungen zu speichern, indem wir „panel_x“ und „panel_y“ auf 20 Pixel für die Positionierung, „row_height“ auf 24 Pixel für den Zeilenabstand, „font_size“ auf 11 für den Text, „font“ als „Calibri Bold“ für den Stil, „bg_color“ als hellgrau für das Hauptpanel, „border_color“ als schwarz für die Panelumrisse, „header_color“ als dunkles Türkis für Kopfzeilen, „text_color“ als Schwarz für allgemeinen Text, „section_bg_color“ als helles Blaugrau für Kopf- und Fußzeilenfelder, „zorder_panel“ bei 100, „zorder_subpanel“ bei 101 und „zorder_labels“ bei 102 für die Überlagerung, „label_y_offset“ bei 3 und „label_x_offset“ bei 25 für die Ausrichtung der Beschriftungen, „header_x_distances“ für neun Spaltenpositionen und „header_shades“ mit 12 Farben für den Glüheffekt.

Wir erstellen die Struktur „SymbolData“, um Daten pro Symbol zu speichern, einschließlich „name“ für das Symbol, „buys“, „sells“, „trades“, „pending“ für die Anzahl, und „lots“, „profit“, „swaps“, „comm“ für die Werte, mit entsprechenden String-Feldern wie „buys_str“ für die Anzeige. Wir deklarieren folgende globale Variablen: „symbol_data“ Array für Symboldaten, „totalBuys“, „totalSells“, „totalTrades“, „totalPending“ als „long“ initialisiert auf Null, „totalLots“, „totalProfit“, „totalSwap“, „totalComm“ als „doubles“ auf Null initialisiert, „headers“ Array für Spaltenbezeichnungen, „column_widths“ für Spaltengrößen, „data_default_colors“ für spaltenspezifische Farben, „sort_column“ bei 3 für Standard-Sortierung nach Handelsgeschäften, „sort_ascending“ als false für absteigende Reihenfolge, „glow_index“ und „glow_counter“ bei 0 mit „glow_direction“ als true und „GLOW_INTERVAL_MS“ bei 500ms für das Kopfzeilen-Glühen, String-Variablen wie „total_buys_str“ für die Anzeige der Gesamtzahl, „account_items“ für die Kennzeichnung des Kontostands, des Kapitals und der freien Marge, ihre String-Repräsentationen wie „acc_bal_str“ und „prev_num_symbols“ bei 0 für die dynamische Größenänderung.

Diese Komponenten bilden das Layout des Dashboards und den Datenrahmen für die Positionsverfolgung in Echtzeit. Wir können nun einige Hilfsfunktionen definieren, die uns helfen, das Programm modularer zu gestalten. Wir beginnen mit den Kennzeichnungen, da wir mit diesen häufig zu tun haben werden.

//+------------------------------------------------------------------+
//| Create label function                                            |
//+------------------------------------------------------------------+
bool createLABEL(string objName, string txt, int xD, int yD, color clrTxt, int fontSize, string font, int anchor, bool selectable = false) {
   if(!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label object
      Print(__FUNCTION__, ": Failed to create label '", objName, "'. Error code = ", GetLastError()); //--- Log creation failure
      return(false);                     //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-coordinate
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-coordinate
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner alignment
   ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font type
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, selectable); //--- Set selectable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, selectable); //--- Set selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Set not selected
   ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor); //--- Set anchor point
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, settings.zorder_labels); //--- Set z-order
   ObjectSetString(0, objName, OBJPROP_TOOLTIP, selectable ? "Click to sort" : "Position data"); //--- Set tooltip
   ChartRedraw(0);                       //--- Redraw chart
   return(true);                         //--- Return success
}

//+------------------------------------------------------------------+
//| Update label function                                            |
//+------------------------------------------------------------------+
bool updateLABEL(string objName, string txt, color clrTxt) {
   int found = ObjectFind(0, objName);   //--- Find object
   if(found < 0) {                       //--- Check if object not found
      Print(__FUNCTION__, ": Failed to find label '", objName, "'. Error code = ", GetLastError()); //--- Log error
      return(false);                     //--- Return failure
   }
   string current_txt = ObjectGetString(0, objName, OBJPROP_TEXT); //--- Get current text
   if(current_txt != txt) {              //--- Check if text changed
      ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Update text
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Update color
      return(true);                      //--- Indicate redraw needed
   }
   return(false);                        //--- No update needed
}

Wir implementieren die Funktion „createLABEL“, um Textbeschriftungen für das Dashboard zu erzeugen, wobei wir die Parameter „objName“, „txt“, „xD“, „yD“, „clrTxt“, „fontSize“, „font“, „anchor“ und „selectable“ verwenden. Wir erstellen das Kennzeichen mit der Funktion ObjectCreate als OBJ_LABEL, protokollieren Fehler mit Print und geben false zurück, wenn sie nicht erfolgreich waren, und setzen dann die Eigenschaften mit der Funktion ObjectSetInteger für die Eigenschaften OBJPROP_XDISTANCE, „OBJPROP_YDISTANCE“, „OBJPROP_CORNER“ als „CORNER_LEFT_UPPER“, „OBJPROP_FONTSIZE“, „OBJPROP_COLOR“, „OBJPROP_BACK“ als „false“, „OBJPROP_STATE“ und „OBJPROP_SELECTABLE“ basierend auf „selectable“, „OBJPROP_SELECTED“ als false, „OBJPROP_ANCHOR“, und OBJPROP_ZORDER aus „settings.zorder_labels“, und ObjectSetString für „OBJPROP_TEXT“ und OBJPROP_FONT, mit einem Tooltip über „OBJPROP_TOOLTIP“ für Sortierung oder Daten. Wir zeichnen den Chart mit ChartRedraw neu und geben true zurück.

Die Funktion „updateLABEL“ aktualisiert vorhandene Beschriftungen, indem sie ObjectFind auf „objName“ überprüft, protokolliert und false zurückgibt, wenn sie nicht gefunden wird. Wenn der „current_txt“ aus „ObjectGetString“ von „txt“ abweicht, werden „OBJPROP_TEXT“ und „OBJPROP_COLOR“ mit „ObjectSetString“ und „ObjectSetInteger“ aktualisiert, wobei „true“ zurückgegeben wird, um anzuzeigen, dass eine Neuzeichnung erforderlich ist, und „false“, wenn nicht. Diese Funktionen ermöglichen eine flexible Erstellung von Kennzeichen und effiziente Aktualisierungen für die Anzeige auf dem Armaturenbrett. Anschließend können wir die anderen Hilfsfunktionen zum Sammeln aller benötigten Informationen erstellen.

//+------------------------------------------------------------------+
//| Count total positions for a symbol                               |
//+------------------------------------------------------------------+
string countPositionsTotal(string symbol) {
   int totalPositions = 0;               //--- Initialize position counter
   int count_Total_Pos = PositionsTotal(); //--- Get total positions
   for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i); //--- Get position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Check if position selected
         if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) totalPositions++; //--- Check symbol and magic
      }
   }
   return IntegerToString(totalPositions); //--- Return total as string
}

//+------------------------------------------------------------------+
//| Count buy or sell positions for a symbol                         |
//+------------------------------------------------------------------+
string countPositions(string symbol, ENUM_POSITION_TYPE pos_type) {
   int totalPositions = 0;               //--- Initialize position counter
   int count_Total_Pos = PositionsTotal(); //--- Get total positions
   for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i); //--- Get position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Check if position selected
         if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_TYPE) == pos_type && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Check symbol, type, magic
            totalPositions++;            //--- Increment counter
         }
      }
   }
   return IntegerToString(totalPositions); //--- Return total as string
}

//+------------------------------------------------------------------+
//| Count pending orders for a symbol                                |
//+------------------------------------------------------------------+
string countOrders(string symbol) {
   int total = 0;                        //--- Initialize counter
   int tot = OrdersTotal();              //--- Get total orders
   for(int i = tot - 1; i >= 0; i--) {   //--- Iterate through orders
      ulong ticket = OrderGetTicket(i);  //--- Get order ticket
      if(ticket > 0 && OrderSelect(ticket)) { //--- Check if order selected
         if(OrderGetString(ORDER_SYMBOL) == symbol && (MagicNumber < 0 || OrderGetInteger(ORDER_MAGIC) == MagicNumber)) total++; //--- Check symbol and magic
      }
   }
   return IntegerToString(total);        //--- Return total as string
}

//+------------------------------------------------------------------+
//| Sum double property for positions of a symbol                    |
//+------------------------------------------------------------------+
string sumPositionDouble(string symbol, ENUM_POSITION_PROPERTY_DOUBLE prop) {
   double total = 0.0;                   //--- Initialize total
   int count_Total_Pos = PositionsTotal(); //--- Get total positions
   for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i); //--- Get position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Check if position selected
         if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Check symbol and magic
            total += PositionGetDouble(prop); //--- Add property value
         }
      }
   }
   return DoubleToString(total, 2);      //--- Return total as string
}

//+------------------------------------------------------------------+
//| Sum commission for positions of a symbol from history            |
//+------------------------------------------------------------------+
double sumPositionCommission(string symbol) {
   double total_comm = 0.0;              //--- Initialize total commission
   int pos_total = PositionsTotal();     //--- Get total positions
   for(int p = 0; p < pos_total; p++) {  //--- Iterate through positions
      ulong ticket = PositionGetTicket(p); //--- Get position ticket
      if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Check if selected
         if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Check symbol and magic
            long pos_id = PositionGetInteger(POSITION_IDENTIFIER); //--- Get position ID
            if(HistorySelectByPosition(pos_id)) { //--- Select history by position
               int deals_total = HistoryDealsTotal(); //--- Get total deals
               for(int d = 0; d < deals_total; d++) { //--- Iterate through deals
                  ulong deal_ticket = HistoryDealGetTicket(d); //--- Get deal ticket
                  if(deal_ticket > 0) {    //--- Check valid
                     total_comm += HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); //--- Add commission
                  }
               }
            }
         }
      }
   }
   return total_comm;                    //--- Return total commission
}

//+------------------------------------------------------------------+
//| Collect active symbols with positions or orders                  |
//+------------------------------------------------------------------+
void CollectActiveSymbols() {
   string symbols_temp[];
   int added = 0;
   // Collect from positions
   int pos_total = PositionsTotal();
   for(int i = 0; i < pos_total; i++) {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      PositionSelectByTicket(ticket);
      if(MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
         string sym = PositionGetString(POSITION_SYMBOL);
         bool found = false;
         for(int k = 0; k < added; k++) {
            if(symbols_temp[k] == sym) {
               found = true;
               break;
            }
         }
         if(!found) {
            ArrayResize(symbols_temp, added + 1);
            symbols_temp[added] = sym;
            added++;
         }
      }
   }
   // Collect from orders
   int ord_total = OrdersTotal();
   for(int i = 0; i < ord_total; i++) {
      ulong ticket = OrderGetTicket(i);
      if(ticket == 0) continue;
      bool isSelected = OrderSelect(ticket);
      if(MagicNumber < 0 || OrderGetInteger(ORDER_MAGIC) == MagicNumber) {
         string sym = OrderGetString(ORDER_SYMBOL);
         bool found = false;
         for(int k = 0; k < added; k++) {
            if(symbols_temp[k] == sym) {
               found = true;
               break;
            }
         }
         if(!found) {
            ArrayResize(symbols_temp, added + 1);
            symbols_temp[added] = sym;
            added++;
         }
      }
   }
   // Set symbol_data
   ArrayResize(symbol_data, added);
   for(int i = 0; i < added; i++) {
      symbol_data[i].name = symbols_temp[i];
      symbol_data[i].buys = 0;
      symbol_data[i].sells = 0;
      symbol_data[i].trades = 0;
      symbol_data[i].lots = 0.0;
      symbol_data[i].profit = 0.0;
      symbol_data[i].pending = 0;
      symbol_data[i].swaps = 0.0;
      symbol_data[i].comm = 0.0;
      symbol_data[i].buys_str = "0";
      symbol_data[i].sells_str = "0";
      symbol_data[i].trades_str = "0";
      symbol_data[i].lots_str = "0.00";
      symbol_data[i].profit_str = "0.00";
      symbol_data[i].pending_str = "0";
      symbol_data[i].swaps_str = "0.00";
      symbol_data[i].comm_str = "0.00";
   }
}

Hier implementieren wir Utility-Funktionen, um Handelsdaten zu sammeln und zusammenzufassen und eine genaue Positions- und Auftragsverfolgung über Symbole hinweg zu gewährleisten. Die Funktion „countpositionstotal“ zählt alle Positionen für ein bestimmtes „Symbol“, wobei sie eine Schleife durch „PositionsTotal“ durchläuft, jedes „Ticket“ mit „PositionGetTicket“ und „PositionSelectByTicket“ auswählt und „totalPositions“ erhöht, wenn das Symbol übereinstimmt und „MagicNumber“ -1 ist oder über „PositionGetInteger“ mit POSITION_MAGIC übereinstimmt. Sie gibt den Zählerstand mit der Funktion IntegerToString als Zeichenkette zurück.

Die Funktion „countPositions“ zählt die Kauf- oder Verkaufspositionen für ein „symbol“ und „pos_type“, wobei sie die Positionen in ähnlicher Weise durchläuft, POSITION_TYPE mit „pos_type“ vergleicht und die Anzahl als String zurückgibt. Die Funktion „countOrders“ zählt die schwebende Aufträge für ein „Symbol“, indem sie eine Schleife durch „OrdersTotal“ zieht, das „Ticket“ mit „OrderGetTicket“ und „OrderSelect“ auswählt, „total“ erhöht, wenn das Symbol und die „MagicNumber“ übereinstimmen, und die Anzahl als Zeichenkette zurückgibt. Die Funktion „sumPositionDouble“ summiert eine Double-Eigenschaft wie Volumen, Gewinn oder Swap für ein „Symbol“, indem sie durch die Positionen iteriert, die Werte von PositionGetDouble für die angegebene „prop“ addiert, wenn die Bedingungen übereinstimmen, und die Summe mit DoubleToString auf zwei Dezimalstellen formatiert zurückgibt.

Die Funktion „sumPositionCommission“ berechnet die Gesamtprovision für ein „Symbol“ aus der Historie der Deals, indem sie eine Schleife durch die Positionen zieht, „pos_id“ mit „PositionGetInteger“ auswählt, verwendet HistorySelectByPosition, um die Deals zu erhalten, addiert „DEAL_COMMISSION“ mit „HistoryDealGetDouble“ für jedes gültige „deal_ticket“ aus HistoryDealGetTicket und gibt die Summe zurück.

Die Funktion „CollectActiveSymbols“ sammelt Symbole mit aktiven Positionen oder Aufträgen in „symbols_temp“, iteriert durch „PositionsTotal“ und „OrdersTotal“, prüft die „MagicNumber“ und fügt einzelne Symbole mit der Funktion ArrayResize hinzu. Es passt die Größe von „symbol_data“ an und initialisiert Felder wie „name“, counts und strings auf Null oder Standardwerte. Diese Funktionen ermöglichen es dem Dashboard, präzise Handelsdaten effizient zu erfassen und anzuzeigen. Bis zu diesem Punkt haben wir alle notwendigen Funktionen, um unser Dashboard zu initialisieren. Lassen Sie uns mit der Erstellung des Dashboards in OnInit fortfahren, damit wir unsere Upgrades weiter verfolgen können.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Collect active symbols first
   CollectActiveSymbols();
   int num_rows = ArraySize(symbol_data);
   // Calculate dimensions
   int num_columns = ArraySize(headers);    //--- Get number of columns
   int column_width_sum = 0;                //--- Initialize sum of column widths
   for(int i = 0; i < num_columns; i++)     //--- Iterate through columns
      column_width_sum += column_widths[i]; //--- Add column width to sum
   int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculate panel width

   // Create main panel in foreground
   string panel_name = PREFIX + PANEL;                                                   //--- Define main panel name
   ObjectCreate(0, panel_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);                            //--- Create main panel
   ObjectSetInteger(0, panel_name, OBJPROP_CORNER, CORNER_LEFT_UPPER);                   //--- Set panel corner
   ObjectSetInteger(0, panel_name, OBJPROP_XDISTANCE, settings.panel_x);                 //--- Set panel x-coordinate
   ObjectSetInteger(0, panel_name, OBJPROP_YDISTANCE, settings.panel_y);                 //--- Set panel y-coordinate
   ObjectSetInteger(0, panel_name, OBJPROP_XSIZE, panel_width);                          //--- Set panel width
   ObjectSetInteger(0, panel_name, OBJPROP_YSIZE, (num_rows + 3) * settings.row_height); //--- Set panel height
   ObjectSetInteger(0, panel_name, OBJPROP_BGCOLOR, settings.bg_color);                  //--- Set background color
   ObjectSetInteger(0, panel_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);                    //--- Set border type
   ObjectSetInteger(0, panel_name, OBJPROP_BORDER_COLOR, settings.border_color);         //--- Set border color
   ObjectSetInteger(0, panel_name, OBJPROP_BACK, false);                                 //--- Set panel to foreground
   ObjectSetInteger(0, panel_name, OBJPROP_ZORDER, settings.zorder_panel);               //--- Set z-order

   // Create header panel
   string header_panel = PREFIX + HEADER_PANEL;                                          //--- Define header panel name
   ObjectCreate(0, header_panel, OBJ_RECTANGLE_LABEL, 0, 0, 0);                          //--- Create header panel
   ObjectSetInteger(0, header_panel, OBJPROP_CORNER, CORNER_LEFT_UPPER);                 //--- Set header panel corner
   ObjectSetInteger(0, header_panel, OBJPROP_XDISTANCE, settings.panel_x);               //--- Set header panel x-coordinate
   ObjectSetInteger(0, header_panel, OBJPROP_YDISTANCE, settings.panel_y);               //--- Set header panel y-coordinate
   ObjectSetInteger(0, header_panel, OBJPROP_XSIZE, panel_width);                        //--- Set header panel width
   ObjectSetInteger(0, header_panel, OBJPROP_YSIZE, settings.row_height);                //--- Set header panel height
   ObjectSetInteger(0, header_panel, OBJPROP_BGCOLOR, settings.section_bg_color);        //--- Set header panel background color
   ObjectSetInteger(0, header_panel, OBJPROP_BORDER_TYPE, BORDER_FLAT);                  //--- Set header panel border type
   ObjectSetInteger(0, header_panel, OBJPROP_BORDER_COLOR, settings.border_color);       //--- Set border color
   ObjectSetInteger(0, header_panel, OBJPROP_ZORDER, settings.zorder_subpanel);          //--- Set header panel z-order

   return(INIT_SUCCEEDED);               //--- Return initialization success
}

In OnInit initialisieren wir die Logik für die Einrichtung der Nutzeroberfläche, die die Grundlage für die Positions- und Kontoüberwachung bildet. Wir beginnen mit dem Aufruf der Funktion „CollectActiveSymbols“, um das Array „symbol_data“ mit aktiven Symbolen zu füllen, und setzen „num_rows“ mit der Funktion ArraySize auf seine Größe. Wir berechnen „num_columns“ aus dem Array „headers“ und berechnen „column_width_sum“, indem wir „column_widths“ mit einer for-Schleife durchlaufen und jede Breite summieren. Die „panel_width“ wird mit MathMax unter Verwendung der letzten „header_x_distances“ plus der entsprechenden „column_widths“ und „column_width_sum“ ermittelt, wobei 20 und „settings.label_x_offset“ zum Auffüllen hinzugefügt werden.

Wir erstellen das Hauptpanel mit ObjectCreate als OBJ_RECTANGLE_LABEL namens „PREFIX + PANEL“, setzen „OBJPROP_CORNER“ auf „CORNER_LEFT_UPPER“, „OBJPROP_XDISTANCE“ und „OBJPROP_YDISTANCE“ aus „settings.panel_x“ und „settings.panel_y“, „OBJPROP_XSIZE“ auf „panel_width“, „OBJPROP_YSIZE“ auf „(num_rows + 3) * settings.row_height“, „OBJPROP_BGCOLOR“ zu „settings.bg_color“, „OBJPROP_BORDER_TYPE“ zu „BORDER_FLAT“, „OBJPROP_BORDER_COLOR“ zu „settings.border_color“, „OBJPROP_BACK“ auf false und OBJPROP_ZORDER auf „settings.zorder_panel“. Für die Kopfzeile verwenden wir einen ähnlichen Ansatz und geben „INIT_SUCCEEDED“ zurück, um die erfolgreiche Initialisierung anzuzeigen. Damit sind die Hauptpanels des Dashboards für die Anzeige von Daten festgelegt, und nach der Kompilierung ergibt sich das folgende Ergebnis.

HAUPTTEIL UND HEADER-PANEL

Nachdem die Grundlage geschaffen wurde, können wir nun die anderen Unterpanels und Beschriftungen erstellen. Um dies zu erreichen, verwenden wir die folgende Logik.

// Create headers with manual X-distances
int header_y = settings.panel_y + 8 + settings.label_y_offset; //--- Calculate header y-coordinate
for(int i = 0; i < num_columns; i++) {                         //--- Iterate through headers
   string header_name = PREFIX + HEADER + IntegerToString(i);  //--- Define header label name
   int header_x = settings.panel_x + settings.header_x_distances[i] + settings.label_x_offset; //--- Calculate header x-coordinate
   createLABEL(header_name, headers[i], header_x, header_y, settings.header_color, 12, settings.font, ANCHOR_LEFT, true); //--- Create header label
}

// Create symbol labels and data labels
int first_row_y = header_y + settings.row_height;                                       //--- Calculate y-coordinate for first row
int symbol_x = settings.panel_x + 10 + settings.label_x_offset;                         //--- Set x-coordinate for symbol labels
for(int i = 0; i < num_rows; i++) {                                                     //--- Iterate through symbols
   string symbol_name = PREFIX + SYMB + IntegerToString(i);                             //--- Define symbol label name
   createLABEL(symbol_name, symbol_data[i].name, symbol_x, first_row_y + i * settings.row_height + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT); //--- Create symbol label
   int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset;   //--- Set initial x-offset for data labels
   for(int j = 0; j < num_columns - 1; j++) {                                           //--- Iterate through data columns
      string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Define data label name
      color initial_color = data_default_colors[j];                                     //--- Set initial color
      string initial_txt = (j <= 2 || j == 5) ? "0" : "0.00";                           //--- Set initial text
      createLABEL(data_name, initial_txt, x_offset, first_row_y + i * settings.row_height + settings.label_y_offset, initial_color, settings.font_size, settings.font, ANCHOR_RIGHT); //--- Create data label
      x_offset += column_widths[j + 1];                                                 //--- Update x-offset
   }
}

// Create footer panel at the bottom
int footer_y = settings.panel_y + (num_rows + 3) * settings.row_height - settings.row_height - 5; //--- Calculate footer y-coordinate
string footer_panel = PREFIX + FOOTER_PANEL;                                    //--- Define footer panel name
ObjectCreate(0, footer_panel, OBJ_RECTANGLE_LABEL, 0, 0, 0);                    //--- Create footer panel
ObjectSetInteger(0, footer_panel, OBJPROP_CORNER, CORNER_LEFT_UPPER);           //--- Set footer panel corner
ObjectSetInteger(0, footer_panel, OBJPROP_XDISTANCE, settings.panel_x);         //--- Set footer panel x-coordinate
ObjectSetInteger(0, footer_panel, OBJPROP_YDISTANCE, footer_y);                 //--- Set footer panel y-coordinate
ObjectSetInteger(0, footer_panel, OBJPROP_XSIZE, panel_width);                  //--- Set footer panel width
ObjectSetInteger(0, footer_panel, OBJPROP_YSIZE, settings.row_height + 5);      //--- Set footer panel height
ObjectSetInteger(0, footer_panel, OBJPROP_BGCOLOR, settings.section_bg_color);  //--- Set footer panel background color
ObjectSetInteger(0, footer_panel, OBJPROP_BORDER_TYPE, BORDER_FLAT);            //--- Set footer panel border type
ObjectSetInteger(0, footer_panel, OBJPROP_BORDER_COLOR, settings.border_color); //--- Set border color
ObjectSetInteger(0, footer_panel, OBJPROP_ZORDER, settings.zorder_subpanel);    //--- Set footer panel z-order

// Create footer text and data
int footer_text_x = settings.panel_x + 10 + settings.label_x_offset;               //--- Set x-coordinate for footer text
createLABEL(PREFIX + FOOTER_TEXT, "Total:", footer_text_x, footer_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT); //--- Create footer text label
int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Set initial x-offset for footer data
for(int j = 0; j < num_columns - 1; j++) {                                         //--- Iterate through footer data columns
   string footer_data_name = PREFIX + FOOTER_DATA + IntegerToString(j);            //--- Define footer data label name
   color footer_color = data_default_colors[j];                                    //--- Set footer data color
   string initial_txt = (j <= 2 || j == 5) ? "0" : "0.00";                         //--- Set initial text
   createLABEL(footer_data_name, initial_txt, x_offset, footer_y + 8 + settings.label_y_offset, footer_color, settings.font_size, settings.font, ANCHOR_RIGHT); //--- Create footer data label
   x_offset += column_widths[j + 1];                                               //--- Update x-offset
}

Wir fahren mit dem Aufbau des Informations-Dashboards fort, indem wir die UI-Elemente Kopfzeile, Symbol, Daten und Fußzeile innerhalb der Funktion OnInit erstellen und die visuelle Struktur für die Anzeige von Handelsdaten einrichten. Für die Kopfzeile berechnen wir „header_y“ als „settings.panel_y + 8 + settings.label_y_offset“ und durchlaufen „num_columns“ mit einer for-Schleife, wobei jedes Header-Label mit „createLABEL“ unter Verwendung von „PREFIX + HEADER + IntegerToString(i)“ als Header-Name für Eindeutigkeit, „headers[i]“ als Text, „header_x“ berechnet aus „settings.panel_x + settings.header_x_distances[i] + settings.label_x_offset“, „settings.header_color“, Schriftgröße 12, „settings.font“, „ANCHOR_LEFT“ und wählbare „true“ für die Sortierinteraktion, da wir die Sortierfunktion später aktivieren müssen.

Für Symbole und Daten wird „first_row_y“ als „header_y + settings.row_height“ und „symbol_x“ als „settings.panel_x + 10 + settings.label_x_offset“ festgelegt. Wir durchlaufen „num_rows“ mit einer for-Schleife und erstellen Symbol-Kennzeichnungen mit „createLABEL“ unter Verwendung von „PREFIX + SYMB + IntegerToString(i)“, „symbol_data[i].name“, „symbol_x“ und „first_row_y + i * settings.row_height + settings.label_y_offset“ in „settings.text_color“. Für jede Zeile wird eine Schleife durch die Datenspalten „num_columns – 1“ gezogen, wobei die Beschriftungen mit „createLABEL“ unter Verwendung von „PREFIX + DATA + IntegerToString(i) + '_' + IntegerToString(j)“, dem Anfangstext „0“ oder „0.00“ je nach Spalte, „x_offset“ beginnend bei „settings.panel_x + 10 + column_widths[0] + settings.label_x_offset“ und inkrementierend um „column_widths[j + 1]“, „data_default_colors[j]“ und „ANCHOR_RIGHT“.

Für die Fußzeile berechnen wir „footer_y“ als „settings.panel_y + (num_rows + 3) * settings.row_height – settings.row_height – 5“ und erstellen das untere Panel mit „ObjectCreate“ als OBJ_RECTANGLE_LABEL mit dem Namen „PREFIX + FOOTER_PANEL“, wobei „OBJPROP_CORNER“ auf „CORNER_LEFT_UPPER“, OBJPROP_XDISTANCE auf „settings.panel_x“, „OBJPROP_YDISTANCE“ auf „footer_y“, „OBJPROP_XSIZE“ auf „panel_width“, „OBJPROP_YSIZE“ auf „settings.row_height + 5“, „OBJPROP_BGCOLOR“ auf „settings.section_bg_color“, „OBJPROP_BORDER_TYPE“ auf „BORDER_FLAT“, „OBJPROP_BORDER_COLOR“ auf „settings.border_color“, und „OBJPROP_ZORDER“ auf „settings.zorder_subpanel“.

Wir erstellen den Fußzeilentext mit „createLABEL“ unter Verwendung von „PREFIX + FOOTER_TEXT“, „Total:“, „footer_text_x“ bei „settings.panel_x + 10 + settings.label_x_offset“, und Schleife durch „num_columns – 1“ zur Erstellung von Fußzeilenbeschriftungen mit „createLABEL“ unter Verwendung von „PREFIX + FOOTER_DATA + IntegerToString(j)“, Anfangstext, „x_offset“, aktualisiert durch „column_widths[j + 1]“, und „data_default_colors[j]“ aus dem Array. Wenn wir kompilieren, erhalten wir das folgende Ergebnis.

AUSGEFÜLLTES DATENMETRIK-PANEL

Nachdem wir nun das Hauptpanel mit Daten gefüllt haben, gehen wir zum Panel für die Kontometrik über, das sich unterhalb des Hauptpanels befinden sollte, um die Kontodaten dynamisch anzuzeigen.

// Create account panel below footer
int account_panel_y = footer_y + settings.row_height + 10;                            //--- Calculate account panel y-coordinate
string account_panel_name = PREFIX + ACCOUNT_PANEL;                                   //--- Define account panel name
ObjectCreate(0, account_panel_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);                    //--- Create account panel
ObjectSetInteger(0, account_panel_name, OBJPROP_CORNER, CORNER_LEFT_UPPER);           //--- Set corner
ObjectSetInteger(0, account_panel_name, OBJPROP_XDISTANCE, settings.panel_x);         //--- Set x-coordinate
ObjectSetInteger(0, account_panel_name, OBJPROP_YDISTANCE, account_panel_y);          //--- Set y-coordinate
ObjectSetInteger(0, account_panel_name, OBJPROP_XSIZE, panel_width);                  //--- Set width
ObjectSetInteger(0, account_panel_name, OBJPROP_YSIZE, settings.row_height);          //--- Set height
ObjectSetInteger(0, account_panel_name, OBJPROP_BGCOLOR, settings.section_bg_color);  //--- Set background color
ObjectSetInteger(0, account_panel_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);            //--- Set border type
ObjectSetInteger(0, account_panel_name, OBJPROP_BORDER_COLOR, settings.border_color); //--- Set border color
ObjectSetInteger(0, account_panel_name, OBJPROP_ZORDER, settings.zorder_subpanel);    //--- Set z-order

// Create account text and data labels
int acc_x = settings.panel_x + 10 + settings.label_x_offset;                          //--- Set base x for account labels
int acc_data_offset = 160;                                                            //--- Increased offset for data labels to avoid overlap
int acc_spacing = (panel_width - 45) / ArraySize(account_items);                      //--- Adjusted spacing to fit
for(int k = 0; k < ArraySize(account_items); k++) {                                   //--- Iterate through account items
   string acc_text_name = PREFIX + ACC_TEXT + IntegerToString(k);                     //--- Define text label name
   int text_x = acc_x + k * acc_spacing;                                              //--- Calculate text x
   createLABEL(acc_text_name, account_items[k] + ":", text_x, account_panel_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT); //--- Create text label
   string acc_data_name = PREFIX + ACC_DATA + IntegerToString(k);                     //--- Define data label name
   int data_x = text_x + acc_data_offset;                                             //--- Calculate data x
   createLABEL(acc_data_name, "0.00", data_x, account_panel_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_RIGHT); //--- Create data label
}

Hier erstellen wir das Kontopanel und seine Beschriftungen zur Anzeige von Kontometriken und vervollständigen damit das UI-Setup innerhalb der Funktion OnInit. Wir berechnen „account_panel_y“ als „footer_y + settings.row_height + 10“ und erstellen das Panel mit ObjectCreate als „OBJ_RECTANGLE_LABEL“ mit dem Namen „PREFIX + ACCOUNT_PANEL“, setzen „OBJPROP_CORNER“ auf „CORNER_LEFT_UPPER“, „OBJPROP_XDISTANCE“ auf „settings.panel_x“, „OBJPROP_YDISTANCE“ auf „account_panel_y“, „OBJPROP_XSIZE“ auf „panel_width“, „OBJPROP_YSIZE“ auf „settings.row_height“, „OBJPROP_BGCOLOR“ auf „settings.section_bg_color“, „OBJPROP_BORDER_TYPE“ auf „BORDER_FLAT“, OBJPROP_BORDER_COLOR auf „settings.border_color“, und „OBJPROP_ZORDER“ auf „settings.zorder_subpanel“.

Für Kontobeschriftungen setzen wir „acc_x“ auf „settings.panel_x + 10 + settings.label_x_offset“, „acc_data_offset“ auf 160 und „acc_spacing“ auf „(panel_width – 45) / ArraySize(account_items)“ für gleichmäßige Abstände und folgen einem ähnlichen Format wie die Hauptlogik zur Erstellung des Panels. Bei dieser Einstellung werden Kontostand, Kapital und freier Spielraum in einem übersichtlichen, ausgerichteten Feld unterhalb der Fußzeile angezeigt. Sehen Sie sich das unten an.

PANEL FÜR KONTOWERTE

Aus dem Bild ist ersichtlich, dass der Bereich Kontometrien erstellt wurde. Jetzt geht es darum, das Panel zu aktualisieren und reaktionsfähig zu machen. Lassen Sie uns eine Funktion zur Aktualisierung des Dashboards erstellen.

//+------------------------------------------------------------------+
//| Sort dashboard by selected column                                |
//+------------------------------------------------------------------+
void SortDashboard() {
   int n = ArraySize(symbol_data);       //--- Get number of symbols
   for(int i = 0; i < n - 1; i++) {     //--- Iterate through symbols
      for(int j = 0; j < n - i - 1; j++) { //--- Compare adjacent symbols
         bool swap = false;              //--- Initialize swap flag
         switch(sort_column) {           //--- Check sort column
            case 0:                      //--- Sort by symbol name
               swap = sort_ascending ? symbol_data[j].name > symbol_data[j + 1].name : symbol_data[j].name < symbol_data[j + 1].name;
               break;
            case 1:                      //--- Sort by buys
               swap = sort_ascending ? symbol_data[j].buys > symbol_data[j + 1].buys : symbol_data[j].buys < symbol_data[j + 1].buys;
               break;
            case 2:                      //--- Sort by sells
               swap = sort_ascending ? symbol_data[j].sells > symbol_data[j + 1].sells : symbol_data[j].sells < symbol_data[j + 1].sells;
               break;
            case 3:                      //--- Sort by trades
               swap = sort_ascending ? symbol_data[j].trades > symbol_data[j + 1].trades : symbol_data[j].trades < symbol_data[j + 1].trades;
               break;
            case 4:                      //--- Sort by lots
               swap = sort_ascending ? symbol_data[j].lots > symbol_data[j + 1].lots : symbol_data[j].lots < symbol_data[j + 1].lots;
               break;
            case 5:                      //--- Sort by profit
               swap = sort_ascending ? symbol_data[j].profit > symbol_data[j + 1].profit : symbol_data[j].profit < symbol_data[j + 1].profit;
               break;
            case 6:                      //--- Sort by pending
               swap = sort_ascending ? symbol_data[j].pending > symbol_data[j + 1].pending : symbol_data[j].pending < symbol_data[j + 1].pending;
               break;
            case 7:                      //--- Sort by swaps
               swap = sort_ascending ? symbol_data[j].swaps > symbol_data[j + 1].swaps : symbol_data[j].swaps < symbol_data[j + 1].swaps;
               break;
            case 8:                      //--- Sort by comm
               swap = sort_ascending ? symbol_data[j].comm > symbol_data[j + 1].comm : symbol_data[j].comm < symbol_data[j + 1].comm;
               break;
         }
         if(swap) {                      //--- Check if swap needed
            SymbolData temp = symbol_data[j]; //--- Store temporary data
            symbol_data[j] = symbol_data[j + 1]; //--- Swap data
            symbol_data[j + 1] = temp;   //--- Complete swap
         }
      }
   }
}

Wir implementieren die Funktion „SortDashboard“, um eine dynamische Sortierung zu ermöglichen, mit der wir die Symboldaten nach ausgewählten Spalten ordnen können. Wir erhalten die Anzahl der Symbole mit ArraySize auf „symbol_data“ und speichern sie in „n“. Mit Hilfe von verschachtelten for-Schleifen durchlaufen wir „n – 1“ Symbole und vergleichen benachbarte Paare bis zu „n – i – 1“. Wir initialisieren das „swap“-Flag auf false und verwenden eine switch-Anweisung für „sort_column“, um die Sortierkriterien zu bestimmen: 0 für „name“, 1 für „buys“, 2 für „sells“, 3 für „trades“, 4 für „lots“, 5 für „profit“, 6 für „pending“, 7 für „swaps“ oder 8 für „comm“, wobei „swap“ auf true gesetzt wird, wenn der Vergleich (auf der Grundlage von „sort_ascending“) ergibt, dass eine Neuordnung erforderlich ist.

Wenn „swap“ wahr ist, speichern wir „symbol_data[j]“ in einer der Variablen „SymbolData“, vertauschen „symbol_data[j]“ mit „symbol_data[j + 1]“ und schließen „swap“ ab. Diese Bubble-Sort-Implementierung stellt sicher, dass das Dashboard nach jeder Spalte in aufsteigender oder absteigender Reihenfolge sortiert werden kann, was die Sichtbarkeit der Daten verbessert. Wir können diese Funktion nun in eine Hauptfunktion implementieren, die sich um die Aktualisierungen kümmert.

//+------------------------------------------------------------------+
//| Update dashboard function                                        |
//+------------------------------------------------------------------+
void UpdateDashboard() {
   bool needs_redraw = false;             //--- Initialize redraw flag
   CollectActiveSymbols();
   int current_num = ArraySize(symbol_data);
   if(current_num != prev_num_symbols) {
      // Delete old symbol and data labels
      for(int del_i = 0; del_i < prev_num_symbols; del_i++) {
         ObjectDelete(0, PREFIX + SYMB + IntegerToString(del_i));
         for(int del_j = 0; del_j < 8; del_j++) {
            ObjectDelete(0, PREFIX + DATA + IntegerToString(del_i) + "_" + IntegerToString(del_j));
         }
      }
      // Adjust panel sizes and positions
      int panel_height = (current_num + 3) * settings.row_height;
      ObjectSetInteger(0, PREFIX + PANEL, OBJPROP_YSIZE, panel_height);
      int footer_y = settings.panel_y + panel_height - settings.row_height - 5;
      ObjectSetInteger(0, PREFIX + FOOTER_PANEL, OBJPROP_YDISTANCE, footer_y);
      int account_panel_y = footer_y + settings.row_height + 10;
      ObjectSetInteger(0, PREFIX + ACCOUNT_PANEL, OBJPROP_YDISTANCE, account_panel_y);
      // Create new symbol and data labels
      int header_y = settings.panel_y + 8 + settings.label_y_offset;
      int first_row_y = header_y + settings.row_height;
      int symbol_x = settings.panel_x + 10 + settings.label_x_offset;
      for(int cr_i = 0; cr_i < current_num; cr_i++) {
         string symb_name = PREFIX + SYMB + IntegerToString(cr_i);
         createLABEL(symb_name, symbol_data[cr_i].name, symbol_x, first_row_y + cr_i * settings.row_height + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT);
         int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset;
         for(int cr_j = 0; cr_j < 8; cr_j++) {
            string data_name = PREFIX + DATA + IntegerToString(cr_i) + "_" + IntegerToString(cr_j);
            color init_color = data_default_colors[cr_j];
            string init_txt = (cr_j <= 2 || cr_j == 5) ? "0" : "0.00";
            createLABEL(data_name, init_txt, x_offset, first_row_y + cr_i * settings.row_height + settings.label_y_offset, init_color, settings.font_size, settings.font, ANCHOR_RIGHT);
            x_offset += column_widths[cr_j + 1];
         }
      }
      prev_num_symbols = current_num;
      needs_redraw = true;
   }
   // Reset totals
   totalBuys = 0;
   totalSells = 0;
   totalTrades = 0;
   totalLots = 0.0;
   totalProfit = 0.0;
   totalPending = 0;
   totalSwap = 0.0;
   totalComm = 0.0;
   // Calculate symbol data and totals (without updating labels yet)
   for(int i = 0; i < current_num; i++) {
      string symbol = symbol_data[i].name;
      for(int j = 0; j < 8; j++) {
         string value = "";
         color data_color = data_default_colors[j];
         double dval = 0.0;
         int ival = 0;
         switch(j) {
            case 0: // Buy positions
               value = countPositions(symbol, POSITION_TYPE_BUY);
               ival = (int)StringToInteger(value);
               if(value != symbol_data[i].buys_str) {
                  symbol_data[i].buys_str = value;
                  symbol_data[i].buys = ival;
               }
               totalBuys += ival;
               break;
            case 1: // Sell positions
               value = countPositions(symbol, POSITION_TYPE_SELL);
               ival = (int)StringToInteger(value);
               if(value != symbol_data[i].sells_str) {
                  symbol_data[i].sells_str = value;
                  symbol_data[i].sells = ival;
               }
               totalSells += ival;
               break;
            case 2: // Total trades
               value = countPositionsTotal(symbol);
               ival = (int)StringToInteger(value);
               if(value != symbol_data[i].trades_str) {
                  symbol_data[i].trades_str = value;
                  symbol_data[i].trades = ival;
               }
               totalTrades += ival;
               break;
            case 3: // Lots
               value = sumPositionDouble(symbol, POSITION_VOLUME);
               dval = StringToDouble(value);
               if(value != symbol_data[i].lots_str) {
                  symbol_data[i].lots_str = value;
                  symbol_data[i].lots = dval;
               }
               totalLots += dval;
               break;
            case 4: // Profit
               value = sumPositionDouble(symbol, POSITION_PROFIT);
               dval = StringToDouble(value);
               data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrGray;
               if(value != symbol_data[i].profit_str) {
                  symbol_data[i].profit_str = value;
                  symbol_data[i].profit = dval;
               }
               totalProfit += dval;
               break;
            case 5: // Pending
               value = countOrders(symbol);
               ival = (int)StringToInteger(value);
               if(value != symbol_data[i].pending_str) {
                  symbol_data[i].pending_str = value;
                  symbol_data[i].pending = ival;
               }
               totalPending += ival;
               break;
            case 6: // Swap
               value = sumPositionDouble(symbol, POSITION_SWAP);
               dval = StringToDouble(value);
               data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : data_color;
               if(value != symbol_data[i].swaps_str) {
                  symbol_data[i].swaps_str = value;
                  symbol_data[i].swaps = dval;
               }
               totalSwap += dval;
               break;
            case 7: // Comm
               dval = sumPositionCommission(symbol);
               value = DoubleToString(dval, 2);
               data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : data_color;
               if(value != symbol_data[i].comm_str) {
                  symbol_data[i].comm_str = value;
                  symbol_data[i].comm = dval;
               }
               totalComm += dval;
               break;
         }
      }
   }
   // Sort after calculating values
   SortDashboard();
}

Hier implementieren wir die Funktion „UpdateDashboard“, um das Dashboard zu aktualisieren und die Aktualisierung von Positions- und Kontodaten in Echtzeit zu gewährleisten, während gleichzeitig eine dynamische Anpassung an Symboländerungen erfolgt. Wir initialisieren „needs_redraw“ als false und rufen die Funktion „CollectActiveSymbols“ auf, um „symbol_data“ zu aktualisieren, indem wir prüfen, ob „current_num“ aus „ArraySize(symbol_data)“ von „prev_num_symbols“ abweicht. Falls abweichend, löschen wir die alten Kennzeichnungen mit ObjectDelete für „PREFIX + SYMB“ und „PREFIX + DATA“, passen die Größe der Felder an, indem wir „OBJPROP_YSIZE“ von „PREFIX + PANEL“ auf „(current_num + 3) * settings.row_height“, Aktualisierung von „OBJPROP_YDISTANCE“ von „PREFIX + FOOTER_PANEL“ und „PREFIX + ACCOUNT_PANEL“ auf der Grundlage der neuen „footer_y“ und „account_panel_y“, und erstellen Sie Symbol- und Datenkennzeichen mit „createLABEL“ für „symbol_data[cr_i].name“ und Anfangswerte unter Verwendung von „symbol_x“, „first_row_y + cr_i * settings.row_height + settings.label_y_offset“ und „x_offset“, erhöht um „column_widths“.

Wir setzen „prev_num_symbols“ auf „current_num“ und kennzeichnen „needs_redraw“ als true. Wir setzen Summen wie „totalBuys“, „totalSells“, „totalTrades“, „totalLots“, „totalProfit“, „totalPending“, „totalSwap“ und „totalComm“ auf Null zurück. Für jedes Symbol in „symbol_data“ iterieren wir durch acht Datenspalten, berechnen Werte mit „countPositions“, „countPositionsTotal“, „sumPositionDouble“ oder „sumPositionCommission“, aktualisieren „symbol_data[i]“-Felder wie „buys_str“, „sells“, „profit_str“ und das Setzen von Farben für „profit“, „swaps“ und „comm“ auf der Grundlage positiver (grün), negativer (rot) oder neutraler Werte und das Addieren der Summen. Wir rufen „SortDashboard“ auf, um die Anzeige auf der Grundlage der aktuellen „sort_column“ und „sort_ascending“ neu anzuordnen. Um die Funktion zu nutzen, müssen wir sie in den Initialisierungs- und Timerfunktionen aufrufen. Fügen wir sie einfach am Ende von OnInit hinzu.

// Set millisecond timer for updates
EventSetMillisecondTimer(MathMax(UpdateIntervalMs, 10)); //--- Set timer with minimum 10ms

// Initial update
prev_num_symbols = num_rows;
UpdateDashboard();                                       //--- Update dashboard

Als erstes setzen wir die vorherige Anzahl der Zeilen auf die Anzahl der berechneten Zeilen zur Initialisierung und rufen die Funktion „UpdateDashboard“ auf, um das Dashboard zu aktualisieren. Da wir dieselbe Funktion in der Funktion OnTimer aufrufen müssen, legen wir die Zeit mit der Funktion EventSetMillisecondTimer mit dem Aktualisierungsintervall fest, mindestens jedoch 10 Millisekunden, um die Systemressourcen nicht zu überlasten. Da wir den Timer erstellen, vergessen Sie nicht, ihn zu zerstören, wenn er nicht mehr benötigt wird, um Ressourcen freizugeben.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0, PREFIX, -1, -1);  //--- Delete all objects with PREFIX
   EventKillTimer();                     //--- Stop timer
}

In OnDeinit löschen wir mit der Funktion ObjectsDeleteAll alle Objekte mit dem „PREFIX“ und stoppen den Timer mit der Funktion EventKillTimer. Wir können nun die Aktualisierungsfunktion im „OnTimer“-Ereignishandler aufrufen, um die Aktualisierungen wie folgt durchzuführen.

//+------------------------------------------------------------------+
//| Timer function for millisecond-based updates                     |
//+------------------------------------------------------------------+
void OnTimer() {
   UpdateDashboard();                    //--- Update dashboard on timer event
}

Um die Blasensortierung zu aktivieren, müssen wir die Ereignisbehandlung von OnChartEvent implementieren. Hier ist die Logik, die wir dafür verwenden.

//+------------------------------------------------------------------+
//| Chart event handler for sorting and export                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if(id == CHARTEVENT_OBJECT_CLICK) {              //--- Handle object click event
      for(int i = 0; i < ArraySize(headers); i++) { //--- Iterate through headers
         if(sparam == PREFIX + HEADER + IntegerToString(i)) { //--- Check if header clicked
            if(sort_column == i)                    //--- Check if same column clicked
               sort_ascending = !sort_ascending;    //--- Toggle sort direction
            else {
               sort_column = i;                     //--- Set new sort column
               sort_ascending = true;               //--- Set to ascending
            }
            UpdateDashboard();                      //--- Update dashboard display
            break;                                  //--- Exit loop
         }
      }
   }
}

Wir implementieren die Ereignisbehandlung von OnChartEvent, um Nutzerinteraktionen für die Sortierung des Dashboards zu verarbeiten und seine Interaktivität zu verbessern. Für CHARTEVENT_OBJECT_CLICK durchlaufen wir die „Header“ mit ArraySize und prüfen, ob „sparam“ mit „PREFIX + HEADER + IntegerToString(i)“ übereinstimmt. Wenn der Index der angeklickten Überschrift gleich „sort_column“ ist, schalten wir „sort_ascending“ um; andernfalls setzen wir „sort_column“ auf den angeklickten Index und „sort_ascending“ auf true. Wir rufen „UpdateDashboard“ auf, um die Anzeige mit der neuen Sortierung zu aktualisieren und die Schleife zu unterbrechen. Dies ermöglicht eine dynamische Sortierung durch Anklicken der Spaltenüberschriften und macht die Datenanalyse flexibler. Nach der Kompilierung erhalten wir folgendes Ergebnis.

STATISCHES DASHBOARD

Anhand der Visualisierung können wir sehen, dass wir Hover-Tooltips haben, die uns sagen, was wir tun sollen, wie z. B. „Click to sort“, aber wenn wir klicken, passiert nichts. Die Informationen werden nicht einmal angezeigt. Der Grund dafür ist, dass die gesammelten Informationen im Dashboard nicht visuell aktualisiert werden, aber intern sind sie verfügbar. Aktualisieren wir also unsere Funktion „UpdateDashboard“, um dies zu berücksichtigen. Definieren wir zunächst die Logik, um eine atmende Kopfzeile zu haben, da dies die einfachste Möglichkeit ist, dem Panel Leben einzuhauchen, und wir wissen, dass wir auf dem richtigen Weg sind.

// Update header breathing effect every 500ms
glow_counter += MathMax(UpdateIntervalMs, 10); //--- Increment glow counter
if(glow_counter >= GLOW_INTERVAL_MS) { //--- Check if glow interval reached
   if(glow_direction) {                //--- Check if glowing forward
      glow_index++;                    //--- Increment glow index
      if(glow_index >= ArraySize(settings.header_shades) - 1) //--- Check if at end
         glow_direction = false;       //--- Reverse glow direction
   } else {                            //--- Glow backward
      glow_index--;                    //--- Decrement glow index
      if(glow_index <= 0)              //--- Check if at start
         glow_direction = true;        //--- Reverse glow direction
   }
   glow_counter = 0;                   //--- Reset glow counter
}
color header_shade = settings.header_shades[glow_index];          //--- Get current header shade
for(int i = 0; i < ArraySize(headers); i++) {                     //--- Iterate through headers
   string header_name = PREFIX + HEADER + IntegerToString(i);     //--- Define header name
   ObjectSetInteger(0, header_name, OBJPROP_COLOR, header_shade); //--- Update header color
   needs_redraw = true;                //--- Set redraw flag
}


// Batch redraw if needed
if(needs_redraw) {                     //--- Check if redraw needed
   ChartRedraw(0);                     //--- Redraw chart
}

Hier implementieren wir den Glüheffekt in der Kopfzeile und das abschließende Neuzeichnen in der Funktion „UpdateDashboard“, um das visuelle Feedback zu verbessern. Wir erhöhen den „glow_counter“ um das Maximum aus „UpdateIntervalMs“ und 10 und prüfen, ob er „GLOW_INTERVAL_MS“ (500ms) erreicht. Wenn „true“, wird „glow_index“ angepasst: inkrementierend, wenn „glow_direction“ wahr ist, umkehrend zu „false“, wenn das Ende von „settings.header_shades“ erreicht ist, oder dekrementierend, wenn „false“, umkehrend zu „true“ bei Null, dann wird „glow_counter“ auf 0 zurückgesetzt. Wir setzen „header_shade” aus „settings.header_shades[glow_index]” und durchlaufen „headers” mit ArraySize, aktualisieren jedes Label „PREFIX + HEADER + IntegerToString(i)” „OBJPROP_COLOR” mit ObjectSetInteger auf „header_shade” und setzen „needs_redraw” auf true.

Wenn „needs_redraw“ wahr ist, rufen wir ChartRedraw auf, um das Chart zu aktualisieren. Dies erzeugt einen zyklischen Glüheffekt in den Kopfzeilen und gewährleistet eine effiziente Aktualisierung der Nutzeroberfläche. Sie können die Farben, die Häufigkeit und die Deckkraft so ändern, wie Sie es wünschen. Wir erhalten die folgenden Ergebnisse.

ATMENDE KOPFZEILE

Jetzt, da wir eine atmende Kopfzeile haben, können wir uns der komplexeren Logik zuwenden, die darin besteht, unser Dashboard zu aktualisieren, um auch die Klicks zu verarbeiten.

// Update symbol and data labels after sorting
bool labels_updated = false;
for(int i = 0; i < current_num; i++) {
   string symbol = symbol_data[i].name;
   string symb_name = PREFIX + SYMB + IntegerToString(i);
   string current_symb_txt = ObjectGetString(0, symb_name, OBJPROP_TEXT);
   if(current_symb_txt != symbol) {
      ObjectSetString(0, symb_name, OBJPROP_TEXT, symbol);
      labels_updated = true;
   }
   for(int j = 0; j < 8; j++) {
      string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j);
      string value;
      color data_color = data_default_colors[j];
      switch(j) {
         case 0:
            value = symbol_data[i].buys_str;
            data_color = clrRed;
            break;
         case 1:
            value = symbol_data[i].sells_str;
            data_color = clrGreen;
            break;
         case 2:
            value = symbol_data[i].trades_str;
            data_color = clrDarkGray;
            break;
         case 3:
            value = symbol_data[i].lots_str;
            data_color = clrOrange;
            break;
         case 4:
            value = symbol_data[i].profit_str;
            data_color = (symbol_data[i].profit > 0) ? clrGreen : (symbol_data[i].profit < 0) ? clrRed : clrGray;
            break;
         case 5:
            value = symbol_data[i].pending_str;
            data_color = clrBlue;
            break;
         case 6:
            value = symbol_data[i].swaps_str;
            data_color = (symbol_data[i].swaps > 0) ? clrGreen : (symbol_data[i].swaps < 0) ? clrRed : clrPurple;
            break;
         case 7:
            value = symbol_data[i].comm_str;
            data_color = (symbol_data[i].comm > 0) ? clrGreen : (symbol_data[i].comm < 0) ? clrRed : clrBrown;
            break;
      }
      if(updateLABEL(data_name, value, data_color)) labels_updated = true;
   }
}
if(labels_updated) needs_redraw = true;
// Update totals
string new_total_buys = IntegerToString(totalBuys); //--- Format total buys
if(new_total_buys != total_buys_str) { //--- Check if changed
   total_buys_str = new_total_buys;    //--- Update string
   if(updateLABEL(PREFIX + FOOTER_DATA + "0", new_total_buys, clrRed)) needs_redraw = true; //--- Update label
}
string new_total_sells = IntegerToString(totalSells); //--- Format total sells
if(new_total_sells != total_sells_str) { //--- Check if changed
   total_sells_str = new_total_sells;  //--- Update string
   if(updateLABEL(PREFIX + FOOTER_DATA + "1", new_total_sells, clrGreen)) needs_redraw = true; //--- Update label
}
string new_total_trades = IntegerToString(totalTrades); //--- Format total trades
if(new_total_trades != total_trades_str) { //--- Check if changed
   total_trades_str = new_total_trades; //--- Update string
   if(updateLABEL(PREFIX + FOOTER_DATA + "2", new_total_trades, clrDarkGray)) needs_redraw = true; //--- Update label
}
string new_total_lots = DoubleToString(totalLots, 2); //--- Format total lots
if(new_total_lots != total_lots_str) { //--- Check if changed
   total_lots_str = new_total_lots;    //--- Update string
   if(updateLABEL(PREFIX + FOOTER_DATA + "3", new_total_lots, clrOrange)) needs_redraw = true; //--- Update label
}
string new_total_profit = DoubleToString(totalProfit, 2); //--- Format total profit
color total_profit_color = (totalProfit > 0) ? clrGreen : (totalProfit < 0) ? clrRed : clrGray; //--- Set color
if(new_total_profit != total_profit_str) { //--- Check if changed
   total_profit_str = new_total_profit; //--- Update string
   if(updateLABEL(PREFIX + FOOTER_DATA + "4", new_total_profit, total_profit_color)) needs_redraw = true; //--- Update label
}
string new_total_pending = IntegerToString(totalPending); //--- Format total pending
if(new_total_pending != total_pending_str) { //--- Check if changed
   total_pending_str = new_total_pending; //--- Update string
   if(updateLABEL(PREFIX + FOOTER_DATA + "5", new_total_pending, clrBlue)) needs_redraw = true; //--- Update label
}
string new_total_swap = DoubleToString(totalSwap, 2); //--- Format total swap
color total_swap_color = (totalSwap > 0) ? clrGreen : (totalSwap < 0) ? clrRed : clrPurple; //--- Set color
if(new_total_swap != total_swap_str) { //--- Check if changed
   total_swap_str = new_total_swap;    //--- Update string
   if(updateLABEL(PREFIX + FOOTER_DATA + "6", new_total_swap, total_swap_color)) needs_redraw = true; //--- Update label
}
string new_total_comm = DoubleToString(totalComm, 2); //--- Format total comm
color total_comm_color = (totalComm > 0) ? clrGreen : (totalComm < 0) ? clrRed : clrBrown; //--- Set color
if(new_total_comm != total_comm_str) { //--- Check if changed
   total_comm_str = new_total_comm;    //--- Update string
   if(updateLABEL(PREFIX + FOOTER_DATA + "7", new_total_comm, total_comm_color)) needs_redraw = true; //--- Update label
}

// Update account info
double balance = AccountInfoDouble(ACCOUNT_BALANCE); //--- Get balance
double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Get equity
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Get free margin
string new_bal = DoubleToString(balance, 2); //--- Format balance
if(new_bal != acc_bal_str) {          //--- Check if changed
   acc_bal_str = new_bal;             //--- Update string
   if(updateLABEL(PREFIX + ACC_DATA + "0", new_bal, clrBlack)) needs_redraw = true; //--- Update label
}
string new_eq = DoubleToString(equity, 2); //--- Format equity
color eq_color = (equity > balance) ? clrGreen : (equity < balance) ? clrRed : clrBlack; //--- Set color
if(new_eq != acc_eq_str) {            //--- Check if changed
   acc_eq_str = new_eq;               //--- Update string
   if(updateLABEL(PREFIX + ACC_DATA + "1", new_eq, eq_color)) needs_redraw = true; //--- Update label
}
string new_free = DoubleToString(free_margin, 2); //--- Format free margin
if(new_free != acc_free_str) {        //--- Check if changed
   acc_free_str = new_free;           //--- Update string
   if(updateLABEL(PREFIX + ACC_DATA + "2", new_free, clrBlack)) needs_redraw = true; //--- Update label
}

Wir aktualisieren Symbol-, Daten-, Gesamt- und Kontobeschriftungen in der Funktion „UpdateDashboard“, um sortierte und aktuelle Handelsdaten wiederzugeben und eine ansprechende Darstellung zu gewährleisten. Wir setzen „labels_updated“ auf false und durchlaufen eine Schleife durch die „current_num“-Symbole und aktualisieren „PREFIX + SYMB + IntegerToString(i)“ mit „symbol_data[i].name“ über „ObjectSetString“, wenn ObjectGetString abweicht, und setzen „labels_updated“ auf true. Für jedes Symbol werden acht Spalten durchlaufen, wobei „value“ und „data_color“ mit einem Schalter ausgewählt werden: „buys_str“ mit „clrRed“, „sells_str“ mit „clrGreen“, „trades_str“ mit „clrDarkGray“, „lots_str“ mit clrOrange, „profit_str“ mit bedingter Farbe basierend auf „symbol_data[i].profit“, „pending_str“ mit „clrBlue“, „swaps_str“ mit bedingter Farbe basierend auf „symbol_data[i].swaps“ und „comm_str“ mit bedingter Farbe basierend auf „symbol_data[i].comm“, Aktualisierung der „PREFIX + DATA“-Labels mit „updateLABEL“ und Setzen von „labels_updated“ bei Änderungen.

Wir aktualisieren Summen wie „total_buys_str“ mit „IntegerToString(totalBuys)“, „total_sells_str“, „total_trades_str“, „total_lots_str“ mit „DoubleToString(totalLots, 2)“, „total_profit_str“ mit bedingtem „total_profit_color“, „total_pending_str“, „total_swap_str“ mit „total_swap_color“ und „total_comm_str“ mit „total_comm_color“, unter Verwendung von „updateLABEL“ auf „PREFIX + FOOTER_DATA“-Beschriftungen und Setzen von „needs_redraw“ bei Aktualisierung. Für Kontoinformationen erhalten wir „balance“, „equity“ und „free_margin“ mit AccountInfoDouble, formatieren mit „DoubleToString“, aktualisieren „acc_bal_str“, „acc_eq_str“ mit bedingter „eq_color“ und „acc_free_str“, mit „updateLABEL“ auf „PREFIX + ACC_DATA“ Labels. Dadurch wird sichergestellt, dass das Dashboard aktuelle Daten mit dynamischen Farben anzeigt, um die Übersichtlichkeit zu erhöhen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

IP DYNAMISCHE SORTIERUNG

Anhand der Visualisierung können wir sehen, dass wir jetzt den dynamischen Bubble-Sort-Effekt erzielen, der alles in unseren Kopfspalten sowohl in aufsteigender als auch in absteigender Reihenfolge wiedergibt. Was jetzt noch fehlt, ist eine Funktion zum Exportieren der Daten nach zur weiteren Analyse. Hier ist die Logik, die wir dafür einsetzen.

//+------------------------------------------------------------------+
//| Export dashboard data to CSV                                     |
//+------------------------------------------------------------------+
void ExportToCSV() {
   string time_str = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES); //--- Get current time string
   StringReplace(time_str, " ", "_"); //--- Replace spaces
   StringReplace(time_str, ":", "-"); //--- Replace colons
   string filename = "Dashboard_" + time_str + ".csv"; //--- Define filename
   int handle = FileOpen(filename, FILE_WRITE|FILE_CSV); //--- Open CSV file in terminal's Files folder
   if(handle == INVALID_HANDLE) {        //--- Check for invalid handle
      Print("Failed to open CSV file '", filename, "'. Error code = ", GetLastError()); //--- Log error
      return;                            //--- Exit function
   }
   FileWrite(handle, "Symbol,Buy Positions,Sell Positions,Total Trades,Lots,Profit,Pending Orders,Swap,Comm"); //--- Write header
   for(int i = 0; i < ArraySize(symbol_data); i++) { //--- Iterate through symbols
      FileWrite(handle, symbol_data[i].name, symbol_data[i].buys, symbol_data[i].sells, symbol_data[i].trades, symbol_data[i].lots, symbol_data[i].profit, symbol_data[i].pending, symbol_data[i].swaps, symbol_data[i].comm); //--- Write symbol data
   }
   FileWrite(handle, "Total", totalBuys, totalSells, totalTrades, totalLots, totalProfit, totalPending, totalSwap, totalComm); //--- Write totals
   FileClose(handle);                    //--- Close file
   Print("Dashboard data exported to CSV: ", filename); //--- Log export success
}

Wir implementieren die Funktion „ExportToCSV“, um den Datenexport zu ermöglichen, sodass wir Handelsdaten für die Offline-Analyse speichern können. Wir erstellen „time_str“ mit TimeToString unter Verwendung von TimeCurrent und „TIME_DATE|TIME_MINUTES“, ersetzen Leerzeichen durch Unterstriche und Doppelpunkte durch Bindestriche mittels StringReplace, um einen sauberen Dateinamen zu erhalten, und definieren dann „filename“ als „Dashboard_“ plus „time_str“ plus „.csv“. Sie können dafür jede andere zulässige Erweiterung verwenden. Wir haben uns für CSV entschieden, da dies die gängigste Erweiterung ist. Dann öffnen wir die Datei mit FileOpen unter Verwendung von „FILE_WRITE|FILE_CSV“, protokollieren Fehler mit „Print“ und beenden das Programm, wenn „handle“ „INVALID_HANDLE“ ist.

Wir schreiben die Kopfzeile mit FileWrite, das die Spaltennamen auflistet, durchlaufen „symbol_data“ mit „ArraySize“, um für jedes Symbol „name“, „buys“, „sells“, „trades“, „lots“, „profit“, „pending“, „Swaps“ und „Comm“ zu schreiben, und eine Summenzeile mit „Total“ und den jeweiligen „totalBuys“, „totalSells“, „totalTrades“, „totalLots“, „totalProfit“, „totalPending“, „totalSwap“ und „totalComm“. Wir schließen die Datei mit FileClose und protokollieren den Erfolg mit „Print“. Dies ermöglicht einen bequemen CSV-Export für die Datenspeicherung. Wir können diese Funktion dann in der Ereignisbehandlung des Chart verwenden, wenn die Taste „E“ gedrückt wird. Wir haben den Schlüssel so gewählt, dass wir ihn für „Export“ leicht wiederfinden können, aber Sie können jeden beliebigen Schlüssel verwenden. Sie können eine Schaltfläche für die Exportarbeit haben, die wir nicht früh genug berücksichtigt haben. Hier ist die Logik, die wir dafür verwenden.

//+------------------------------------------------------------------+
//| Chart event handler for sorting and export                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if(id == CHARTEVENT_OBJECT_CLICK) {   //--- Handle object click event
      for(int i = 0; i < ArraySize(headers); i++) { //--- Iterate through headers
         if(sparam == PREFIX + HEADER + IntegerToString(i)) { //--- Check if header clicked
            if(sort_column == i)         //--- Check if same column clicked
               sort_ascending = !sort_ascending; //--- Toggle sort direction
            else {
               sort_column = i;          //--- Set new sort column
               sort_ascending = true;    //--- Set to ascending
            }
            UpdateDashboard();           //--- Update dashboard display
            break;                       //--- Exit loop
         }
      }
   }
   else if(id == CHARTEVENT_KEYDOWN && lparam == 'E') { //--- Handle 'E' key press
      ExportToCSV();                    //--- Export data to CSV
   }
}

Hier prüfen wir, ob die Ereignis-ID CHARTEVENT_KEYDOWN lautet und der Schlüssel „E“ war, und exportieren die Datei sofort. Es handelt sich um eine einfache Logik, die wir zur Verdeutlichung gelb hervorgehoben haben. Hier ist das Ergebnis.

IP EXPORT CSV

Aus der Visualisierung geht hervor, dass wir die Daten für die Analyse in verschiedene Dateien exportieren, die auf der aktuellen Zeit basieren, und dass wir sie überschreiben, wenn die aktuelle Zeit auf der Basis von Minuten übereinstimmt. Wenn Sie nicht warten wollen, bis eine Minute verstrichen ist, um in einer anderen Datei zu speichern, können Sie die aktuelle Zeitformatierung von Minuten in Sekunden ändern. Wir können feststellen, dass wir unsere Ziele insgesamt erreicht haben. Nun bleibt nur noch die Prüfung der Durchführbarkeit des Projekts, die im vorangegangenen Abschnitt behandelt wurde.


Backtests

Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

IP BACKTEST



Schlussfolgerung

Abschließend haben wir in MetaQuotes Language 5 ein Informations-Dashboard erstellt, das Multi-Symbol-Positionen und Kontowerte wie „Balance“, „Equity“ und „Free Margin“ überwacht, mit sortierbaren Spalten und Excel CSV-Export für eine optimierte Handelsüberwachung. Wir haben die Architektur und Implementierung detailliert beschrieben, wobei wir Strukturen wie „SymbolData“ und Funktionen wie „SortDashboard“ verwenden, um in Echtzeit organisierte Einblicke zu bieten. Sie können dieses Dashboard an Ihre Handelsbedürfnisse anpassen und so Ihre Fähigkeit zur effizienten Leistungsüberwachung verbessern. 

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

Beigefügte Dateien |
Entwicklung des Price Action Analysis Toolkit (Teil 35): Training und Einsatz von Vorhersagemodellen Entwicklung des Price Action Analysis Toolkit (Teil 35): Training und Einsatz von Vorhersagemodellen
Historische Daten sind alles andere als „Müll“ – sie sind die Grundlage für jede solide Marktanalyse. In diesem Artikel führen wir Sie Schritt für Schritt von der Erfassung der Historie über die Verwendung zur Erstellung eines Prognosemodells bis hin zum Einsatz dieses Modells für Live-Preisprognosen. Lesen Sie weiter, um zu erfahren, wie!
Klassische Strategien neu interpretieren (Teil 14): Analyse mehrerer Strategien Klassische Strategien neu interpretieren (Teil 14): Analyse mehrerer Strategien
In diesem Artikel setzen wir unsere Erforschung der Erstellung eines Ensembles von Handelsstrategien und der Verwendung des MT5 genetischen Optimierers zur Abstimmung der Strategieparameter fort. Heute haben wir die Daten in Python analysiert. Dabei hat sich gezeigt, dass unser Modell besser vorhersagen kann, welche Strategie besser abschneiden wird, und eine höhere Genauigkeit erreicht als die direkte Vorhersage der Marktrenditen. Als wir unsere Anwendung jedoch mit ihren statistischen Modellen testeten, fielen unsere Leistungswerte drastisch ab. In der Folge stellten wir fest, dass der genetische Optimierer leider stark korrelierte Strategien bevorzugte, was uns dazu veranlasste, unsere Methode zu überarbeiten, um die Stimmgewichte fest zu halten und die Optimierung stattdessen auf Indikatoreinstellungen zu konzentrieren.
Selbstoptimierende Expert Advisors in MQL5 (Teil 10): Matrix-Faktorisierung Selbstoptimierende Expert Advisors in MQL5 (Teil 10): Matrix-Faktorisierung
Die Faktorisierung ist ein mathematischer Prozess, der dazu dient, Erkenntnisse über die Eigenschaften von Daten zu gewinnen. Wenn wir die Faktorisierung auf große Mengen von Marktdaten anwenden – die in Zeilen und Spalten organisiert sind – können wir Muster und Merkmale des Marktes aufdecken. Die Faktorisierung ist ein mächtiges Werkzeug, und dieser Artikel zeigt Ihnen, wie Sie es im MetaTrader 5-Terminal über die MQL5-API nutzen können, um tiefere Einblicke in Ihre Marktdaten zu gewinnen.
Entwicklung des Price Action Analysis Toolkit (Teil 34): Umwandlung von Marktrohdaten in Prognosemodellen mithilfe einer fortschrittlichen Pipeline der Datenerfassung Entwicklung des Price Action Analysis Toolkit (Teil 34): Umwandlung von Marktrohdaten in Prognosemodellen mithilfe einer fortschrittlichen Pipeline der Datenerfassung
Haben Sie schon einmal einen plötzlichen Marktanstieg verpasst oder wurden Sie von einem solchen überrascht? Der beste Weg, aktuelle Ereignisse zu antizipieren, besteht darin, aus historischen Mustern zu lernen. Mit dem Ziel, ein ML-Modell zu trainieren, zeigt Ihnen dieser Artikel zunächst, wie Sie ein Skript in MetaTrader 5 erstellen, das historische Daten aufnimmt und sie zur Speicherung an Python sendet. Lesen Sie weiter, um die einzelnen Schritte in Aktion zu sehen.