MQL5-Handelswerkzeuge (Teil 7): Informatives Dashboard für Multi-Symbol-Positionen und Kontoüberwachung
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:
- Verstehen der Architektur des Informations-Dashboards
- Implementation in MQL5
- Backtests
- 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!

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.

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.

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.

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.

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.

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:

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.

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.

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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Entwicklung des Price Action Analysis Toolkit (Teil 35): Training und Einsatz von Vorhersagemodellen
Klassische Strategien neu interpretieren (Teil 14): Analyse mehrerer Strategien
Selbstoptimierende Expert Advisors in MQL5 (Teil 10): Matrix-Faktorisierung
Entwicklung des Price Action Analysis Toolkit (Teil 34): Umwandlung von Marktrohdaten in Prognosemodellen mithilfe einer fortschrittlichen Pipeline der Datenerfassung
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.