English Русский
preview
MQL5-Trading Tools (Teil 19): Entwicklung einer interaktiven Werkzeugpalette zum Zeichnen in Charts

MQL5-Trading Tools (Teil 19): Entwicklung einer interaktiven Werkzeugpalette zum Zeichnen in Charts

MetaTrader 5Handel |
21 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 18) haben wir vektorbasierte Rechtecke und Dreiecke kombiniert, um abgerundete Sprechblasen mit steuerbarer Ausrichtung in MetaQuotes Language 5 (MQL5) zu erstellen und damit dynamische UI-Elemente für Handelsoberflächen zu ermöglichen. In Teil 19 entwickeln wir eine interaktive Werkzeugpalette zum Zeichnen in Charts, mit verschiebbaren Panels, Größenanpassung, Umschaltung zwischen dunklem und hellem Design sowie Schaltflächen für Werkzeuge wie Fadenkreuz, Linien und Formen. Dieses individuell anpassbare System verbessert die Analyse durch Interaktionen in Echtzeit und klare Anweisungen. Wir werden die folgenden Themen behandeln:

  1. Entwurf interaktiver Werkzeugpaletten für Charts
  2. Implementierung in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende verfügen Sie über eine funktionsfähige Palette, mit der Sie Ihre Handelsabläufe erweitern können – legen wir los!


Entwurf interaktiver Werkzeugpaletten für Charts

Die interaktive Werkzeugpalette zum Zeichnen in Charts bündelt Schaltflächen für wichtige Funktionen wie Fadenkreuze, Trendlinien, Linien, Rechtecke, Fibonacci, Text und Pfeile und ermöglicht so eine schnelle Auswahl und Anwendung auf Handelscharts mit Echtzeit-Feedback. Sie unterstützt das Ziehen, die Größenanpassung, den Wechsel zwischen dem dunklen und dem hellen Modus sowie das Minimieren zur Optimierung des Arbeitsbereichs und bietet gleichzeitig Anleitungen und Statusmeldungen für eine intuitive Bedienung. Dieses Design optimiert die Analyse durch eine anpassbare, reaktionsfähige Benutzeroberfläche, die sich an die Interaktionen des Benutzers anpasst, ohne das Chart zu überladen. Wir planen, Zeichenflächen-Objekte für die Kopfzeile und das Panel zu verwenden, Mausereignisse für die Interaktivität zu verarbeiten, Enumerationen für Größenänderungsmodi und Werkzeuge zu definieren und die Zeichenlogik über das Erstellen von Objekten im Chart umzusetzen. Kurz gesagt: Hier ist eine visuelle Darstellung unserer Ziele.

ZIELE DER WERKZEUGPALETTE


Implementierung in MQL5

Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, wechseln Sie zum „Navigator“, suchen Sie den Ordner „Experts“, klicken Sie auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald dies erledigt ist, müssen wir in der Programmierumgebung einige Eingabeparameter sowie die globalen Variablen deklarieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//|                                                Tools Palette.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict

#include <Canvas/Canvas.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CCanvas canvasHeader;                                   //--- Declare header canvas
CCanvas canvasTools;                                    //--- Declare tools canvas

string canvasHeaderName = "HeaderCanvas";               //--- Set header canvas name
string canvasToolsName = "ToolsCanvas";                 //--- Set tools canvas name

input int CanvasX = 20;                                 // Panel X position
input int CanvasY = 30;                                 // Panel Y position
input int CanvasWidth = 350;                            // Panel width
input int CanvasHeight = 200;                           // Panel height
input double BackgroundOpacity = 0.8;                   // Background opacity

bool is_dark_theme = true;                              //--- Set initial dark theme flag
color DarkHeaderColor = C'60,60,60';                    //--- Set dark header color
color DarkHeaderHoverColor = clrRed;                    //--- Set dark header hover color
color DarkHeaderDragColor = clrMediumBlue;              //--- Set dark header drag color
color DarkHeaderTextColor = clrWhite;                   //--- Set dark header text color
color DarkBorderColor = clrBlack;                       //--- Set dark border color
color DarkTopColor = clrBlack;                          //--- Set dark top color
color DarkIconColor = clrWhite;                         //--- Set dark icon color
color LightHeaderColor = clrSilver;                     //--- Set light header color
color LightHeaderHoverColor = clrRed;                   //--- Set light header hover color
color LightHeaderDragColor = clrMediumBlue;             //--- Set light header drag color
color LightHeaderTextColor = clrBlack;                  //--- Set light header text color
color LightBorderColor = clrBlack;                      //--- Set light border color
color LightTopColor = clrWhite;                         //--- Set light top color
color LightIconColor = clrBlack;                        //--- Set light icon color
color DarkInstructionColor = clrLime;                   //--- Set dark instruction color
color LightInstructionColor = clrGreen;                 //--- Set light instruction color
color DarkStatusColor = clrMagenta;                     //--- Set dark status color
color LightStatusColor = clrPurple;                     //--- Set light status color
color DarkActiveBtnColor = clrGreen;                    //--- Set dark active button color
color LightActiveBtnColor = clrDarkGreen;               //--- Set light active button color
color DarkHoverBtnColor = clrDarkGray;                  //--- Set dark hover button color
color LightHoverBtnColor = clrLightGray;                //--- Set light hover button color

int currentCanvasX = CanvasX;                           //--- Initialize current X position
int currentCanvasY = CanvasY;                           //--- Initialize current Y position
int currentWidth = CanvasWidth;                         //--- Initialize current width
int currentHeight = CanvasHeight;                       //--- Initialize current height
bool panel_dragging = false;                            //--- Initialize panel dragging flag
int panel_drag_x = 0, panel_drag_y = 0;                 //--- Initialize drag coordinates
int panel_start_x = 0, panel_start_y = 0;               //--- Initialize start coordinates
bool resizing = false;                                  //--- Initialize resizing flag

enum ENUM_RESIZE_MODE {
   NONE,                                                // No resize
   BOTTOM,                                              // Bottom resize
   RIGHT,                                               // Right resize
   BOTTOM_RIGHT                                         // Bottom-right resize
};

ENUM_RESIZE_MODE resize_mode = NONE;                    //--- Initialize resize mode
ENUM_RESIZE_MODE hover_mode = NONE;                     //--- Initialize hover mode
int resize_start_x = 0, resize_start_y = 0;             //--- Initialize resize start coordinates
int start_width = 0, start_height = 0;                  //--- Initialize start dimensions
const int resize_thickness = 5;                         //--- Set resize thickness
const int min_width = 150;                              //--- Set minimum width
const int min_height = 200;                             //--- Set minimum height
int hover_mouse_local_x = 0;                            //--- Initialize local hover X
int hover_mouse_local_y = 0;                            //--- Initialize local hover Y
bool header_hovered = false;                            //--- Initialize header hover flag
bool minimize_hovered = false;                          //--- Initialize minimize hover flag
bool close_hovered = false;                             //--- Initialize close hover flag
bool theme_hovered = false;                             //--- Initialize theme hover flag
bool resize_hovered = false;                            //--- Initialize resize hover flag
int prev_mouse_state = 0;                               //--- Initialize previous mouse state
int header_height = 27;                                 //--- Set header height
int button_size = 25;                                   //--- Set button size
int theme_x_offset = -75;                               //--- Set theme X offset
int minimize_x_offset = -50;                            //--- Set minimize X offset
int close_x_offset = -25;                               //--- Set close X offset
bool panel_minimized = false;                           //--- Initialize minimized flag

enum TOOL_TYPE {
   TOOL_NONE,                                           // No tool
   TOOL_CROSSHAIR,                                      // Crosshair tool
   TOOL_TRENDLINE,                                      // Trendline tool
   TOOL_HLINE,                                          // Horizontal line tool
   TOOL_VLINE,                                          // Vertical line tool
   TOOL_RECTANGLE,                                      // Rectangle tool
   TOOL_FIBO,                                           // Fibonacci tool
   TOOL_TEXT,                                           // Text tool
   TOOL_ARROW                                           // Arrow tool
};

TOOL_TYPE active_tool = TOOL_NONE;                      //--- Initialize active tool
bool drawing_first_click = false;                       //--- Initialize first click flag
datetime draw_time1 = 0;                                //--- Initialize draw time 1
double draw_price1 = 0.0;                               //--- Initialize draw price 1
long local_chart_id = 0;                                //--- Initialize local chart ID
string current_instruction = "";                        //--- Initialize current instruction

struct ToolButton {                                     // Define tool button structure
   string symbol;                                       // Store symbol
   string font;                                         // Store font
   TOOL_TYPE type;                                      // Store type
   string tooltip;                                      // Store tooltip
};

ToolButton buttons[8];                                  //--- Declare buttons array
int hovered_tool_index = -1;                            //--- Initialize hovered tool index

bool measuring = false;                                 //--- Initialize measuring flag
datetime fixed_time = 0;                                //--- Initialize fixed time
double fixed_price = 0.0;                               //--- Initialize fixed price
ulong last_click_time = 0;                              //--- Initialize last click time

const int tools_y_offset = -1;                          //--- Set tools Y offset

Wir beginnen die Implementierung, indem wir die Zeichenflächen-Bibliothek mit #include <Canvas/Canvas.mqh> einbinden, um auf die zentralen Grafikfunktionen für die Erstellung der Palette zugreifen zu können. Als Nächstes deklarieren wir die globalen Zeichenflächen-Objekte „canvasHeader“ und „canvasTools“ für den Kopfbereich und den Hauptbereich des Panels und weisen ihnen zur besseren Übersicht Namen wie „HeaderCanvas“ und „ToolsCanvas“ zu. Außerdem fügen wir Benutzereingaben für Position, Größe und Hintergrundtransparenz hinzu, um eine individuelle Anpassung zu ermöglichen. Um den Wechsel zwischen Designs zu unterstützen, setzen wir „is_dark_theme“ auf „true“ und definieren Farbkonstanten für den dunklen und den hellen Modus, die die Überschriften, Rahmen, Symbole, Anweisungen, Statusanzeigen und Schaltflächen abdecken und so eine einheitliche Optik bei allen Umschaltoptionen gewährleisten.

Wir verfolgen den Status des Panels mithilfe von Variablen für die aktuelle Position und die Abmessungen, Drag-Flags, Größenänderungsmodi über die Enumeration ENUM_RESIZE_MODE (NONE, BOTTOM, RIGHT, BOTTOM_RIGHT), Hover-Zustände sowie Konstanten wie Mindestgrößen und Randbreiten, um die Bedienbarkeit sicherzustellen. Für Werkzeuge definieren wir die Enumeration „TOOL_TYPE“, die Optionen von NONE bis ARROW auflistet, initialisieren „active_tool“ auf NONE, und verwalten Zeichenflags wie „drawing_first_click“ sowie eine „ToolButton“-Struktur, die Symbol, Schriftart, Typ und Tooltip enthält, und erstellen ein Array „buttons[8]“ für die Palettenelemente. Abschließend haben wir Variablen für den Messmodus, Fixpunkte, Klick-Timing, Hover-Indizes und einen y-Versatz für die Ausrichtung der Werkzeuge eingerichtet, um interaktive Funktionen vorzubereiten. Wir benötigen dann einige Hilfsfunktionen, um den Code modular zu gestalten.

//+------------------------------------------------------------------+
//| Get theme-aware colors                                           |
//+------------------------------------------------------------------+
color GetHeaderColor() { return is_dark_theme ? DarkHeaderColor : LightHeaderColor; }                //--- Return header color based on theme
color GetHeaderHoverColor() { return is_dark_theme ? DarkHeaderHoverColor : LightHeaderHoverColor; } //--- Return header hover color based on theme
color GetHeaderDragColor() { return is_dark_theme ? DarkHeaderDragColor : LightHeaderDragColor; }    //--- Return header drag color based on theme
color GetHeaderTextColor() { return is_dark_theme ? DarkHeaderTextColor : LightHeaderTextColor; }    //--- Return header text color based on theme
color GetBorderColor() { return is_dark_theme ? DarkBorderColor : LightBorderColor; }                //--- Return border color based on theme
color GetTopColor() { return is_dark_theme ? DarkTopColor : LightTopColor; }                         //--- Return top color based on theme
color GetIconColor() { return is_dark_theme ? DarkIconColor : LightIconColor; }                      //--- Return icon color based on theme
color GetInstructionColor() { return is_dark_theme ? DarkInstructionColor : LightInstructionColor; } //--- Return instruction color based on theme
color GetStatusColor() { return is_dark_theme ? DarkStatusColor : LightStatusColor; }                //--- Return status color based on theme
color GetActiveBtnColor() { return is_dark_theme ? DarkActiveBtnColor : LightActiveBtnColor; }       //--- Return active button color based on theme
color GetHoverBtnColor() { return is_dark_theme ? DarkHoverBtnColor : LightHoverBtnColor; }          //--- Return hover button color based on theme

//+------------------------------------------------------------------+
//| Get tool name from type                                          |
//+------------------------------------------------------------------+
string GetToolName(TOOL_TYPE t) {
   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      if (buttons[i].type == t) {                               //--- Check if type matches
         return StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Return tool name
      }
   }
   return "None";                                               //--- Return default if no match
}

//+------------------------------------------------------------------+
//| Check if tool is single-click                                    |
//+------------------------------------------------------------------+
bool IsSingleClickTool(TOOL_TYPE t) {
   switch(t) {                                                  //--- Switch on tool type
      case TOOL_HLINE:                                          //--- Handle horizontal line
      case TOOL_VLINE:                                          //--- Handle vertical line
      case TOOL_TEXT:                                           //--- Handle text
      case TOOL_ARROW:                                          //--- Handle arrow
         return true;                                           //--- Return true for single-click tools
      default:                                                  //--- Handle default
         return false;                                          //--- Return false otherwise
   }
}

Anschließend definieren wir eine Reihe von Getter-Funktionen wie „GetHeaderColor“, um themenabhängige Farben abzurufen. Jede dieser Funktionen gibt je nach dem Flag „is_dark_theme“ die dunkle oder helle Variante zurück und gewährleistet so ein einheitliches Styling für Kopfzeilen, Hover-Effekte, Ziehvorgänge, Texte, Rahmen, Symbole, Anweisungen, Statusanzeigen, aktive Schaltflächen und Hover-Effekte – und das ohne überflüssige Prüfungen. Anschließend extrahiert die Funktion „GetToolName“ den Namen eines Werkzeugs anhand seines Typs, indem sie das Array „buttons“ durchläuft, nach dem Typ sucht und zur übersichtlichen Darstellung den Tooltip bis zum Wort „Tool“ kürzt; wird keine Übereinstimmung gefunden, wird standardmäßig „None“ zurückgegeben. Um Werkzeuge zu klassifizieren, implementieren wir „IsSingleClickTool“ mithilfe einer switch-Anweisung für den Werkzeugtyp, die für horizontale/vertikale Linien, Text und Pfeile, die nur einen Klick erfordern, den Wert „true“ zurückgibt, und andernfalls den Wert „false“, um eine präzise Ereignisbehandlung zu gewährleisten. Nachdem das erledigt ist, können wir mit der Erstellung des Headers und des Hauptteils der Palette beginnen.

//+------------------------------------------------------------------+
//| Draw header                                                      |
//+------------------------------------------------------------------+
void DrawHeader() {
   canvasHeader.Erase(0);                                       //--- Erase header canvas

   color header_bg = panel_dragging ? GetHeaderDragColor() : (header_hovered ? GetHeaderHoverColor() : GetHeaderColor()); //--- Get header background color
   canvasHeader.FillRectangle(0, 0, currentWidth - 1, header_height - 1, ColorToARGB(header_bg, 255)); //--- Fill header rectangle

   uint argbBorder = ColorToARGB(GetBorderColor(), 255);        //--- Get border ARGB
   canvasHeader.Rectangle(0, 0, currentWidth - 1, header_height - 1, argbBorder); //--- Draw border rectangle

   canvasHeader.FontSet("Arial Bold", 13);                      //--- Set font for title
   canvasHeader.TextOut(10, (header_height - 13)/2, "Tools Palette", ColorToARGB(GetHeaderTextColor(), 255)); //--- Draw title text

   int button_y = (header_height - 20)/2;                       //--- Compute button Y position

   int theme_button_x = currentWidth + theme_x_offset;          //--- Compute theme button X
   canvasHeader.FontSet("Wingdings", 20);                       //--- Set font for theme symbol
   string theme_symbol = CharToString(91);                      //--- Set theme symbol
   int theme_w = canvasHeader.TextWidth(theme_symbol);          //--- Get theme symbol width
   int theme_x = theme_button_x + (button_size - theme_w) / 2;  //--- Compute theme X position
   color theme_color = theme_hovered ? clrYellow : GetHeaderTextColor(); //--- Get theme color
   canvasHeader.TextOut(theme_x, button_y, theme_symbol, ColorToARGB(theme_color, 255)); //--- Draw theme symbol

   int min_button_x = currentWidth + minimize_x_offset;         //--- Compute minimize button X
   string min_symbol = panel_minimized ? CharToString(111) : CharToString(114); //--- Set minimize symbol
   int min_w = canvasHeader.TextWidth(min_symbol);              //--- Get minimize symbol width
   int min_x = min_button_x + (button_size - min_w) / 2;        //--- Compute minimize X position
   color min_color = minimize_hovered ? clrYellow : GetHeaderTextColor(); //--- Get minimize color
   canvasHeader.TextOut(min_x, button_y, min_symbol, ColorToARGB(min_color, 255)); //--- Draw minimize symbol

   int close_button_x = currentWidth + close_x_offset;          //--- Compute close button X
   canvasHeader.FontSet("Webdings", 20);                        //--- Set font for close symbol
   string close_symbol = CharToString(114);                     //--- Set close symbol
   int close_w = canvasHeader.TextWidth(close_symbol);          //--- Get close symbol width
   int close_x = close_button_x + (button_size - close_w) / 2;  //--- Compute close X position
   color close_color = close_hovered ? clrRed : GetHeaderTextColor(); //--- Get close color
   canvasHeader.TextOut(close_x, button_y, close_symbol, ColorToARGB(close_color, 255)); //--- Draw close symbol

   canvasHeader.Update();                                       //--- Update header canvas
}

//+------------------------------------------------------------------+
//| Draw tools panel                                                 |
//+------------------------------------------------------------------+
void DrawToolsPanel() {
   canvasTools.Erase(0);                                        //--- Erase tools canvas

   uint argb_fill = ColorToARGB(GetTopColor(), (uchar)(255 * BackgroundOpacity)); //--- Get fill ARGB
   canvasTools.FillRectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_fill); //--- Fill background rectangle

   uint argb_border = ColorToARGB(GetBorderColor(), 255);       //--- Get border ARGB
   canvasTools.Rectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_border); //--- Draw border rectangle

   int columns = 4;                                             //--- Set number of columns
   int padding = 5;                                             //--- Set padding
   int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width
   int button_height = 70;                                      //--- Set button height
   int start_x = padding;                                       //--- Set start X
   int start_y = padding;                                       //--- Set start Y

   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      int col = i % columns;                                    //--- Compute column
      int row = i / columns;                                    //--- Compute row
      int x = start_x + col * (button_width + padding);         //--- Compute X position
      int y = start_y + row * (button_height + padding);        //--- Compute Y position

      color btn_bg = (active_tool == buttons[i].type) ? GetActiveBtnColor() : ((i == hovered_tool_index) ? GetHoverBtnColor() : clrNONE); //--- Get button background
      if (btn_bg != clrNONE) canvasTools.FillRectangle(x, y, x + button_width - 1, y + button_height - 1, ColorToARGB(btn_bg, 255)); //--- Fill button rectangle if needed

      canvasTools.FontSet(buttons[i].font, 35);                 //--- Set font for icon
      int text_w = canvasTools.TextWidth(buttons[i].symbol);    //--- Get icon width
      int text_h = canvasTools.TextHeight(buttons[i].symbol);   //--- Get icon height
      int icon_x = x + (button_width - text_w) / 2;             //--- Compute icon X
      int icon_y = y + 5;                                       //--- Set icon Y
      canvasTools.TextOut(icon_x, icon_y, buttons[i].symbol, ColorToARGB(GetIconColor(), 255)); //--- Draw icon

      string tool_name = StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Get tool name
      canvasTools.FontSet("Arial Bold", 12);                    //--- Set font for name
      int name_w = canvasTools.TextWidth(tool_name);            //--- Get name width
      canvasTools.TextOut(x + (button_width - name_w) / 2, y + button_height - 20, tool_name, ColorToARGB(GetIconColor(), 255)); //--- Draw tool name
   }

   if (StringLen(current_instruction) > 0) {                    //--- Check if instruction exists
      canvasTools.FontSet("Arial", 14);                         //--- Set font for instruction
      int instr_w = canvasTools.TextWidth(current_instruction); //--- Get instruction width
      int instr_x = (currentWidth - instr_w) / 2;               //--- Compute instruction X
      canvasTools.TextOut(instr_x, currentHeight - 30, current_instruction, ColorToARGB(GetInstructionColor(), 255)); //--- Draw instruction
   }

   string status_text = "Active: " + GetToolName(active_tool);  //--- Set status text
   canvasTools.FontSet("Arial Bold", 14);                       //--- Set font for status
   int status_w = canvasTools.TextWidth(status_text);           //--- Get status width
   int status_x = (currentWidth - status_w) / 2;                //--- Compute status X
   canvasTools.TextOut(status_x, currentHeight - 15, status_text, ColorToARGB(GetStatusColor(), 255)); //--- Draw status

   if (resize_hovered || resizing) {                            //--- Check if resize hovered or resizing
      string icon_font = "Wingdings 3";                         //--- Set icon font
      int icon_size = 25;                                       //--- Set icon size
      uchar icon_code = 0;                                      //--- Initialize icon code
      int angle = 0;                                            //--- Initialize angle
      switch (hover_mode) {                                     //--- Switch on hover mode
         case BOTTOM: icon_code = (uchar)'2'; angle = 0; break; //--- Set for bottom
         case RIGHT: icon_code = (uchar)'1'; angle = 0; break;  //--- Set for right
         case BOTTOM_RIGHT: icon_code = (uchar)'2'; angle = 450; break; //--- Set for bottom-right
      }
      if (icon_code != 0) {                                     //--- Check if icon code set
         canvasTools.FontSet(icon_font, icon_size);             //--- Set font for icon
         canvasTools.FontAngleSet(angle);                       //--- Set font angle
         color icon_color = (resize_hovered || resizing) ? clrRed : GetIconColor(); //--- Get icon color
         int icon_x = (hover_mode == BOTTOM) ? MathMin(MathMax(hover_mouse_local_x - icon_size/2, 0), currentWidth - icon_size) : currentWidth - icon_size - 2; //--- Compute icon X
         int icon_y = (hover_mode == RIGHT) ? MathMin(MathMax(hover_mouse_local_y - icon_size/2, 0), currentHeight - icon_size) : currentHeight - icon_size - 2; //--- Compute icon Y
         if (hover_mode == BOTTOM_RIGHT) { icon_x = currentWidth - icon_size - 10; icon_y = currentHeight - icon_size; } //--- Adjust for bottom-right
         canvasTools.TextOut(icon_x, icon_y, CharToString(icon_code), ColorToARGB(icon_color, 255)); //--- Draw icon
         canvasTools.FontAngleSet(0);                           //--- Reset font angle
      }
   }

   canvasTools.Update();                                        //--- Update tools canvas
}

Um die anfängliche Darstellung zu erstellen, implementieren wir die Funktion „DrawHeader“, um die Kopfzeile der Palette auf der Zeichenfläche darzustellen. Dabei löschen wir sie zunächst mit Erase auf 0 und füllen dann ein Rechteck mit einer dynamischen Hintergrundfarbe, die über Getter wie „GetHeaderDragColor“ beim Ziehen abgerufen wird, „GetHeaderHoverColor“, wenn der Mauszeiger darüber schwebt, oder andernfalls die Standardfarbe, umgewandelt in ARGB. Als Nächstes zeichnen wir den Rahmen mit Rectangle und ARGB-Farben aus „GetBorderColor“ und legen für den Titel „Tools Palette“ mit FontSet und TextOut die Schriftart „Arial“ in fetter Schrift in der Textfarbe des Themes fest. Um Steuerelemente hinzuzufügen, berechnen wir die Y-Zentrierung der Schaltfläche, legen dann für das Design die Schriftart Wingdings fest, positionieren und färben das Symbol „[“ je nach Hover-Zustand ein und zeichnen es mit „TextOut“. Ähnlich verfahren wir beim Minimieren (Umschalten zwischen den Symbolen „o“ und „r“) und beim Schließen („X“ in Webdings), wobei wir die Farben für den Hover-Zustand wie Gelb oder Rot anpassen. Abschließend aktualisieren wir die Zeichenfläche mit Update, um die Änderungen anzuzeigen. Bei Bedarf können Sie die Symbole mit Ihren eigenen Zeichen anpassen. Sehen Sie sich eine Auswahl der verfügbaren Schriftzeichen an.

SYMBOLSCHRIFTARTEN

In der Funktion „DrawToolsPanel“ löschen wir die Zeichenfläche der Werkzeuge, füllen sie mit der aus „GetTopColor“ abgeleiteten ARGB-Hintergrundfarbe, die entsprechend „BackgroundOpacity“ angepasst wurde, und zeichnen das Rahmenrechteck. Wir ordnen die Schaltflächen in vier Spalten mit Abstand an, füllen den Hintergrund in einer Schleife, wenn sie aktiv sind oder der Mauszeiger darüberfährt, unter Verwendung von „GetActiveBtnColor“ oder „GetHoverBtnColor“, legen benutzerdefinierte Schriftarten für Symbole wie das „v“ in „Wingdings“ fest, zentrieren und zeichnen sie mit TextOut in der Symbolfarbe, anschließend fügen wir darunter die fettgedruckten Werkzeugnamen hinzu. Wenn sich in „current_instruction“ eine Anweisung befindet, zeichnen wir diese mit „GetInstructionColor“ zentriert im unteren Bereich; den Status des aktiven Werkzeugs fügen wir auf ähnliche Weise hinzu. Um die Größenänderung anzuzeigen, wenn „resize_hovered“ oder „resizing“ gilt, legen wir die Schriftart „Wingdings 3“ fest, bestimmen den Pfeilcode und den Winkel entsprechend „hover_mode“ (z. B. ‚2‘ bei 450 für unten rechts), positionieren den Pfeil dynamisch, zeichnen ihn in Rot oder der Symbolfarbe und setzen den Winkel zurück. Zum Abschluss aktualisieren wir die Zeichenfläche mit „Update“. Wir können dies nun initialisieren, indem wir es anhand der folgenden Logik zeichnen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   currentCanvasX = CanvasX;                                    //--- Set current X from input
   currentCanvasY = CanvasY;                                    //--- Set current Y from input
   currentWidth = CanvasWidth;                                  //--- Set current width from input
   currentHeight = CanvasHeight;                                //--- Set current height from input

   if (!canvasHeader.CreateBitmapLabel(0, 0, canvasHeaderName, currentCanvasX, currentCanvasY, currentWidth, header_height, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create header canvas
      Print("Failed to create Header Canvas");                  //--- Print failure message
      return(INIT_FAILED);                                      //--- Return initialization failure
   }

   if (!canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create tools canvas
      Print("Failed to create Tools Canvas");                   //--- Print failure message
      return(INIT_FAILED);                                      //--- Return initialization failure
   }

   buttons[0].symbol = CharToString('v'); buttons[0].font = "Wingdings"; buttons[0].type = TOOL_CROSSHAIR; buttons[0].tooltip = "Crosshair Tool"; //--- Initialize button 0
   buttons[1].symbol = CharToString('.'); buttons[1].font = "Wingdings 3"; buttons[1].type = TOOL_TRENDLINE; buttons[1].tooltip = "Trendline Tool"; //--- Initialize button 1
   buttons[2].symbol = CharToString('*'); buttons[2].font = "Wingdings 3"; buttons[2].type = TOOL_HLINE; buttons[2].tooltip = "Horizontal Line Tool"; //--- Initialize button 2
   buttons[3].symbol = CharToString('+'); buttons[3].font = "Wingdings 3"; buttons[3].type = TOOL_VLINE; buttons[3].tooltip = "Vertical Line Tool"; //--- Initialize button 3
   buttons[4].symbol = CharToString('c'); buttons[4].font = "Webdings"; buttons[4].type = TOOL_RECTANGLE; buttons[4].tooltip = "Rectangle Tool"; //--- Initialize button 4
   buttons[5].symbol = CharToString('"'); buttons[5].font = "Webdings"; buttons[5].type = TOOL_FIBO; buttons[5].tooltip = "Fibonacci Tool"; //--- Initialize button 5
   buttons[6].symbol = CharToString('>'); buttons[6].font = "Webdings"; buttons[6].type = TOOL_TEXT; buttons[6].tooltip = "Text Tool"; //--- Initialize button 6
   buttons[7].symbol = CharToString(225); buttons[7].font = "Wingdings"; buttons[7].type = TOOL_ARROW; buttons[7].tooltip = "Arrow Tool"; //--- Initialize button 7

   DrawHeader();                                                //--- Draw header
   DrawToolsPanel();                                            //--- Draw tools panel

   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);            //--- Enable mouse move events
   ChartRedraw();                                               //--- Redraw chart
   local_chart_id = ChartID();                                  //--- Set local chart ID
   return(INIT_SUCCEEDED);                                      //--- Return initialization success
}

In OnInit initialisieren wir die aktuellen Positionen und Abmessungen der Werkzeugpalette anhand der Benutzereingaben und stellen dabei sicher, dass diese mit den konfigurierten Werten übereinstimmen. Als Nächstes erstellen wir die Header-Zeichenfläche mithilfe von CreateBitmapLabel mit ARGB-Normalisierung für die Transparenz, protokollieren Fehler und geben bei Misserfolg INIT_FAILED zurück; ebenso verfahren wir beim Werkzeug-Panel, das mit einem y-Versatz unterhalb der Header-Zeichenfläche positioniert ist. Um die Palette zu füllen, initialisieren wir die Elemente des Arrays „buttons“ mit bestimmten Symbolen (z. B. „v“ für das Fadenkreuz), Schriftarten wie Wingdings, Werkzeugtypen aus der Enumeration und Tooltips für Informationen beim Darüberfahren mit der Maus. Anschließend zeichnen wir die Kopfzeile und das Werkzeug-Panel mithilfe der entsprechenden Funktionen, aktivieren Mausbewegungsereignisse mit ChartSetInteger, um Interaktionen zu erfassen, zeichnen das Chart neu, speichern die lokale Chart-ID für die Objektverwaltung und geben INIT_SUCCEEDED zurück. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

INITIALISIERUNG DES PANELS

Wir können sehen, dass das Panel korrekt initialisiert wurde. Nun erweitern wir das Panel um interaktive Funktionen und Designänderungen. Wir werden noch einige weitere Hilfsfunktionen definieren, um das ganz einfach zu bewerkstelligen.

//+------------------------------------------------------------------+
//| Toggle theme                                                     |
//+------------------------------------------------------------------+
void ToggleTheme() {
   is_dark_theme = !is_dark_theme;                              //--- Toggle theme flag
   DrawHeader();                                                //--- Redraw header
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Toggle minimize                                                  |
//+------------------------------------------------------------------+
void ToggleMinimize() {
   panel_minimized = !panel_minimized;                          //--- Toggle minimized flag
   if (panel_minimized) {                                       //--- Check if minimized
      canvasTools.Destroy();                                    //--- Destroy tools canvas
   } else {                                                     //--- Handle restored
      canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE); //--- Recreate tools canvas
      DrawToolsPanel();                                         //--- Redraw tools panel
   }
   DrawHeader();                                                //--- Redraw header
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Is mouse over header excluding buttons                           |
//+------------------------------------------------------------------+
bool IsMouseOverHeader(int mx, int my) {
   if (mx < currentCanvasX || mx > currentCanvasX + currentWidth || my < currentCanvasY || my > currentCanvasY + header_height) return false; //--- Check outside header
   int btn_left = currentCanvasX + currentWidth + theme_x_offset; //--- Compute buttons left
   if (mx >= btn_left && mx <= btn_left + button_size * 3) return false; //--- Check over buttons
   return true;                                                 //--- Return true if over header
}

//+------------------------------------------------------------------+
//| Is mouse over specific button                                    |
//+------------------------------------------------------------------+
bool IsMouseOverButton(int mx, int my, int offset) {
   int btn_left = currentCanvasX + currentWidth + offset;       //--- Compute button left
   return (mx >= btn_left && mx <= btn_left + button_size && my >= currentCanvasY && my <= currentCanvasY + header_height); //--- Return if over button
}

//+------------------------------------------------------------------+
//| Is mouse over resize area                                        |
//+------------------------------------------------------------------+
bool IsMouseOverResize(int mx, int my, ENUM_RESIZE_MODE &rm) {
   if (panel_minimized) return false;                           //--- Return false if minimized
   int tx = currentCanvasX;                                     //--- Set tools X
   int ty = currentCanvasY + header_height + tools_y_offset;    //--- Set tools Y
   int tr = tx + currentWidth;                                  //--- Set right
   int tb = ty + currentHeight;                                 //--- Set bottom
   bool over_right = mx >= tr - resize_thickness && mx <= tr && my >= ty && my <= tb; //--- Check over right
   bool over_bottom = my >= tb - resize_thickness && my <= tb && mx >= tx && mx <= tr; //--- Check over bottom
   if (over_bottom && over_right) { rm = BOTTOM_RIGHT; return true; } //--- Set and return bottom-right
   if (over_bottom) { rm = BOTTOM; return true; }               //--- Set and return bottom
   if (over_right) { rm = RIGHT; return true; }                 //--- Set and return right
   return false;                                                //--- Return false otherwise
}

//+------------------------------------------------------------------+
//| Get hovered tool                                                 |
//+------------------------------------------------------------------+
int GetHoveredTool(int mx, int my) {
   int lx = mx - currentCanvasX;                                //--- Compute local X
   int ly = my - (currentCanvasY + header_height + tools_y_offset); //--- Compute local Y
   if (lx < 0 || lx > currentWidth || ly < 0 || ly > currentHeight) return -1; //--- Return -1 if outside

   int columns = 4;                                             //--- Set columns
   int padding = 5;                                             //--- Set padding
   int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width
   int button_height = 70;                                      //--- Set button height

   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      int col = i % columns;                                    //--- Compute column
      int row = i / columns;                                    //--- Compute row
      int x = padding + col * (button_width + padding);         //--- Compute X
      int y = padding + row * (button_height + padding);        //--- Compute Y
      if (lx >= x && lx < x + button_width && ly >= y && ly < y + button_height) return i; //--- Return index if over
   }
   return -1;                                                   //--- Return -1 if none
}

//+------------------------------------------------------------------+
//| Toggle tool activation                                           |
//+------------------------------------------------------------------+
void ToggleTool(TOOL_TYPE type) {
   if (active_tool == type) {                                   //--- Check if already active
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Handle crosshair deactivation
         DeleteCrosshair();                                     //--- Delete crosshair
         measuring = false;                                     //--- Reset measuring
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
      }
      active_tool = TOOL_NONE;                                  //--- Deactivate tool
      drawing_first_click = false;                              //--- Reset first click
      current_instruction = "";                                 //--- Clear instruction
   } else {                                                     //--- Handle activation
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Deactivate previous crosshair
         DeleteCrosshair();                                     //--- Delete crosshair
         measuring = false;                                     //--- Reset measuring
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
      }
      active_tool = type;                                       //--- Set active tool
      drawing_first_click = false;                              //--- Reset first click
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Handle crosshair instruction
         current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction
      } else {                                                  //--- Handle other tools
         current_instruction = "Click on chart to draw.";       //--- Set instruction
      }
   }
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Deactivate current tool on Escape                                |
//+------------------------------------------------------------------+
void DeactivateTool() {
   if (active_tool == TOOL_CROSSHAIR) {                         //--- Check if crosshair active
      DeleteCrosshair();                                        //--- Delete crosshair
      measuring = false;                                        //--- Reset measuring
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);             //--- Enable scroll
   }
   active_tool = TOOL_NONE;                                     //--- Deactivate tool
   drawing_first_click = false;                                 //--- Reset first click
   current_instruction = "";                                    //--- Clear instruction
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Delete measure objects                                           |
//+------------------------------------------------------------------+
void DeleteMeasureObjects() {
   ObjectDelete(local_chart_id, "Cross_HF");                    //--- Delete fixed horizontal
   ObjectDelete(local_chart_id, "Cross_VF");                    //--- Delete fixed vertical
   ObjectDelete(local_chart_id, "Measure_Line");                //--- Delete measure line
   ObjectDelete(local_chart_id, "Measure_Label");               //--- Delete measure label
}

//+------------------------------------------------------------------+
//| Delete crosshair                                                 |
//+------------------------------------------------------------------+
void DeleteCrosshair() {
   ObjectDelete(local_chart_id, "Cross_H");                     //--- Delete moving horizontal
   ObjectDelete(local_chart_id, "Cross_V");                     //--- Delete moving vertical
   DeleteMeasureObjects();                                      //--- Delete measure objects
   ChartRedraw();                                               //--- Redraw chart
}

Hier implementieren wir die Funktion „ToggleTheme“, um zwischen dem dunklen und dem hellen Modus zu wechseln, indem wir das Flag „is_dark_theme“ umkehren, anschließend die Kopfzeile und das Werkzeug-Panel neu zeichnen, um die neuen Farben anzuwenden, und das Chart aktualisieren, damit die Änderung sofort sichtbar wird. Als Nächstes erstellen wir „ToggleMinimize“, um den Zustand des Panels mit „panel_minimized“ umzuschalten. Dabei wird die Werkzeug-Zeichenfläche beim Minimieren aus Platzgründen gelöscht und beim Wiederherstellen neu erstellt, gefolgt von einer Neuzeichnung der Kopfzeile und einer Aktualisierung des Charts. Um Interaktionen zu erkennen, definieren wir „IsMouseOverHeader“, das prüft, ob sich die Maus innerhalb der Grenzen der Kopfzeile befindet, dabei jedoch den Schaltflächenbereich ab dem Theme-Offset ausschließt und nur für verschiebbare Bereiche den Wert „true“ zurückgibt.

Ebenso überprüft „IsMouseOverButton“ die Mausposition über einer bestimmten Schaltfläche, indem es deren linken Rand anhand des Offsets berechnet und diesen mit der Größe der Schaltfläche sowie der Höhe der Kopfzeile vergleicht. Zur Größenanpassung prüft „IsMouseOverResize“, ob sich der Mauszeiger in der Nähe der unteren, rechten oder einer der Ecken des Werkzeugfensters befindet (wobei minimierte Fenster ignoriert werden), aktualisiert den Referenzmodus entsprechend (z. B. „BOTTOM_RIGHT“) und gibt „true“ zurück, wenn dies festgestellt wird. Wir fügen „GetHoveredTool“ hinzu, um die Schaltfläche unter dem Mauszeiger zu identifizieren, indem wir die Koordinaten in lokale Koordinaten umrechnen, ein 4-spaltiges Raster mit Abständen durchlaufen und den Index zurückgeben, wenn sich der Mauszeiger innerhalb der Grenzen einer Schaltfläche befindet, andernfalls -1.

Zur Verwaltung der Werkzeuge wird „ToggleTool“ je nach Typ aktiviert oder deaktiviert. Dabei wird beim Wechsel das Fadenkreuz entfernt, die Zeichenflags werden zurückgesetzt und vor dem Neuzeichnen des Charts wird eine entsprechende Anweisung wie „Zum Zeichnen auf das Chart klicken“ angezeigt. „DeactivateTool“ setzt das aktive Werkzeug vollständig zurück, wobei insbesondere Fadenkreuzelemente gelöscht und das Scrollen aktiviert werden; anschließend werden die Anweisungen aktualisiert und die Anzeige neu gezeichnet. Abschließend führen wir eine Bereinigung durch: Mit „DeleteMeasureObjects“ werden das feste Fadenkreuz sowie die Messlinie und die Beschriftung entfernt, und mit „DeleteCrosshair“ wird das bewegliche Fadenkreuz gelöscht sowie die Funktion zum Löschen der Messung aufgerufen, woraufhin das Chart neu gezeichnet wird. Wir werden uns nun mit den Funktionen der Objekte und deren Erstellung befassen.

//+------------------------------------------------------------------+
//| Handle chart drawing for tools                                   |
//+------------------------------------------------------------------+
void HandleDrawing(int mx, int my) {
   datetime time;                                               //--- Declare time
   double price;                                                //--- Declare price
   int sw;                                                      //--- Declare subwindow
   if (!ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) return; //--- Convert XY to time/price or return

   string name = "Tool_" + TimeToString(TimeCurrent(), TIME_SECONDS); //--- Set object name
   color col = clrBlue;                                         //--- Set color
   ENUM_OBJECT obj_type = OBJ_VLINE;                            //--- Initialize object type
   datetime time1 = time;                                       //--- Set time1
   double price1 = price;                                       //--- Set price1
   datetime time2 = 0;                                          //--- Set time2
   double price2 = 0.0;                                         //--- Set price2
   bool create = true;                                          //--- Set create flag
   bool valid_obj = false;                                      //--- Set valid object flag

   string tool_name = GetToolName(active_tool);                 //--- Get tool name

   if (IsSingleClickTool(active_tool)) {                        //--- Check if single-click tool
      switch (active_tool) {                                    //--- Switch on tool
         case TOOL_HLINE:                                       //--- Handle horizontal line
            obj_type = OBJ_HLINE;                               //--- Set type
            time1 = 0;                                          //--- Set time1
            price1 = price;                                     //--- Set price1
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_VLINE:                                       //--- Handle vertical line
            obj_type = OBJ_VLINE;                               //--- Set type
            time1 = time;                                       //--- Set time1
            price1 = 0;                                         //--- Set price1
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_TEXT:                                        //--- Handle text
            obj_type = OBJ_TEXT;                                //--- Set type
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_ARROW:                                       //--- Handle arrow
            obj_type = OBJ_ARROW;                               //--- Set type
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
      }
   } else {                                                     //--- Handle two-click tools
      if (!drawing_first_click) {                               //--- Check if first click
         draw_time1 = time;                                     //--- Set draw time1
         draw_price1 = price;                                   //--- Set draw price1
         drawing_first_click = true;                            //--- Set first click flag
         create = false;                                        //--- Set no create
         current_instruction = "Click second point for " + tool_name + "."; //--- Set instruction
      } else {                                                  //--- Handle second click
         ChartXYToTimePrice(local_chart_id, mx, my, sw, time2, price2); //--- Get time2/price2
         drawing_first_click = false;                           //--- Reset first click
         time1 = draw_time1;                                    //--- Set time1
         price1 = draw_price1;                                  //--- Set price1
         current_instruction = "Click on chart to draw.";       //--- Set instruction
      }
      switch (active_tool) {                                    //--- Switch on tool
         case TOOL_TRENDLINE: obj_type = OBJ_TREND; valid_obj = true; break; //--- Set trendline
         case TOOL_RECTANGLE: obj_type = OBJ_RECTANGLE; valid_obj = true; break; //--- Set rectangle
         case TOOL_FIBO: obj_type = OBJ_FIBO; valid_obj = true; break; //--- Set fibo
      }
   }

   if (create && valid_obj) {                                   //--- Check if create and valid
      if (ObjectCreate(local_chart_id, name, obj_type, sw, time1, price1, time2, price2)) { //--- Create object
         ObjectSetInteger(local_chart_id, name, OBJPROP_COLOR, col); //--- Set color
         ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
         ObjectSetInteger(local_chart_id, name, OBJPROP_WIDTH, 1); //--- Set width
         if (obj_type == OBJ_TEXT) ObjectSetString(local_chart_id, name, OBJPROP_TEXT, "Text"); //--- Set text
         if (obj_type == OBJ_ARROW) ObjectSetInteger(local_chart_id, name, OBJPROP_ARROWCODE, 233); //--- Set arrow code
         if (obj_type == OBJ_HLINE || obj_type == OBJ_VLINE) ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_DASH); //--- Set dash for lines
         ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTABLE, true); //--- Set selectable
         ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTED, true); //--- Set selected
         ChartRedraw(local_chart_id);                           //--- Redraw chart
         active_tool = TOOL_NONE;                               //--- Deactivate tool
         current_instruction = "";                              //--- Clear instruction
      }
   }
   DrawToolsPanel();                                            //--- Redraw tools panel
}

Um die Chartdarstellung zu steuern, implementieren wir die Funktion „HandleDrawing“, die die Erstellung von Chart-Objekten basierend auf dem aktiven Werkzeug und der Mausposition verwaltet. Dabei werden zunächst die Koordinaten mit ChartXYToTimePrice in Zeit- und Kurswerte umgewandelt; bei Fehlschlag wird die Funktion vorzeitig beendet. Als Nächstes generieren wir einen eindeutigen Objektnamen unter Verwendung der aktuellen Uhrzeit, legen die Standardfarbe Blau und den vertikalen Linientyp fest und verzweigen dann über „IsSingleClickTool“ zu den Ein-Klick-Werkzeugen, wobei wir Objekttypen wie OBJ_HLINE für horizontale Linien (Zurücksetzen der Zeiten) konfigurieren, OBJ_VLINE für vertikale Linien (Preise zurücksetzen), OBJ_TEXT oder OBJ_ARROW, markieren diese als gültig und setzen das Erstellungsflag.

Bei Zwei-Klick-Werkzeugen erfassen wir den ersten Punkt, wenn „drawing_first_click“ falsch ist, und aktualisieren dabei die globalen Variablen und die Anweisung, ohne das Objekt zu erstellen; beim zweiten Klick wird der Endpunkt abgerufen, das Flag zurückgesetzt und Typen wie OBJ_TREND für Trendlinien, OBJ_RECTANGLE oder OBJ_FIBO konfiguriert, wobei das Objekt validiert wird. Wenn die Erstellung markiert und gültig ist, verwenden wir ObjectCreate mit Parametern, legen Eigenschaften wie Farbe, Stil, Breite, Text oder gegebenenfalls den Pfeilcode fest, wählen „gestrichelte“ Linien sowie die Zustände „auswählbar“ und „ausgewählt“ aus, zeichnen das Chart anschließend mit ChartRedraw neu und deaktivieren das Werkzeug, wobei wir die Anweisungen löschen. Abschließend aktualisieren wir das Werkzeug-Panel mit „DrawToolsPanel“, um die Änderungen zu übernehmen. Kümmern wir uns nun um das Fadenkreuz; der Rest ist unkompliziert.

//+------------------------------------------------------------------+
//| Update moving crosshair                                          |
//+------------------------------------------------------------------+
void UpdateMovingCrosshair(datetime time, double price) {
   string h_name = "Cross_H";                                   //--- Set horizontal name
   string v_name = "Cross_V";                                   //--- Set vertical name

   if (ObjectFind(local_chart_id, h_name) < 0) {                //--- Check if horizontal exists
      ObjectCreate(local_chart_id, h_name, OBJ_HLINE, 0, 0, price); //--- Create horizontal line
      ObjectSetInteger(local_chart_id, h_name, OBJPROP_COLOR, clrRed); //--- Set color
      ObjectSetInteger(local_chart_id, h_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, h_name, 0, 0, price);          //--- Move horizontal
   }

   if (ObjectFind(local_chart_id, v_name) < 0) {                //--- Check if vertical exists
      ObjectCreate(local_chart_id, v_name, OBJ_VLINE, 0, time, 0); //--- Create vertical line
      ObjectSetInteger(local_chart_id, v_name, OBJPROP_COLOR, clrRed); //--- Set color
      ObjectSetInteger(local_chart_id, v_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, v_name, 0, time, 0);           //--- Move vertical
   }
}

//+------------------------------------------------------------------+
//| Update fixed crosshair                                           |
//+------------------------------------------------------------------+
void UpdateFixedCrosshair() {
   string hf_name = "Cross_HF";                                 //--- Set fixed horizontal name
   string vf_name = "Cross_VF";                                 //--- Set fixed vertical name

   if (ObjectFind(local_chart_id, hf_name) < 0) {               //--- Check if fixed horizontal exists
      ObjectCreate(local_chart_id, hf_name, OBJ_HLINE, 0, 0, fixed_price); //--- Create fixed horizontal
      ObjectSetInteger(local_chart_id, hf_name, OBJPROP_COLOR, clrGreen); //--- Set color
      ObjectSetInteger(local_chart_id, hf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, hf_name, 0, 0, fixed_price);   //--- Move fixed horizontal
   }

   if (ObjectFind(local_chart_id, vf_name) < 0) {               //--- Check if fixed vertical exists
      ObjectCreate(local_chart_id, vf_name, OBJ_VLINE, 0, fixed_time, 0); //--- Create fixed vertical
      ObjectSetInteger(local_chart_id, vf_name, OBJPROP_COLOR, clrGreen); //--- Set color
      ObjectSetInteger(local_chart_id, vf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, vf_name, 0, fixed_time, 0);    //--- Move fixed vertical
   }
}

Wir implementieren die Funktion „UpdateMovingCrosshair“, um ein dynamisches Fadenkreuz im Chart zu verwalten. Dabei definieren wir Namen für die horizontale Linie „Cross_H“ und die vertikale Linie „Cross_V“, prüfen mit ObjectFind, ob diese jeweils vorhanden sind (bei Nichtvorhandensein wird ein negativer Rückgabewert ausgegeben), erstellen sie über „ObjectCreate“ als OBJ_HLINE oder „OBJ_VLINE“ mit durchgezogener roter Linie, falls sie noch nicht vorhanden sind, oder verschieben sie mit ObjectMove auf den aktuellen Zeitpunkt und Kurs, falls vorhanden, und sorgen so für eine Cursorverfolgung in Echtzeit. Als Nächstes erstellen wir die Funktion „UpdateFixedCrosshair“ für ein statisches Referenzfadenkreuz unter Verwendung der Namen „Cross_HF“ und „Cross_VF“. Dabei prüfen wir ebenfalls die Existenz, erstellen das Fadenkreuz mit einem durchgehenden grünen Stil zu festgelegten Zeitpunkten („fixed_time“) und Kursen („fixed_price“) oder aktualisieren Positionen, um im Messmodus dauerhafte Markierungen für Vergleiche zu ermöglichen. Nun werden wir den Klick darauf behandeln.

//+------------------------------------------------------------------+
//| Handle crosshair click                                           |
//+------------------------------------------------------------------+
void HandleCrosshairClick(datetime time, double price) {
   ulong now = GetMicrosecondCount();                           //--- Get current microseconds
   if (now - last_click_time < 500000) {                        //--- Check for double-click
      if (!measuring) {                                         //--- Check not measuring
         fixed_time = time;                                     //--- Set fixed time
         fixed_price = price;                                   //--- Set fixed price
         measuring = true;                                      //--- Set measuring flag
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);         //--- Disable scroll
         current_instruction = "Measuring. Double-click to exit."; //--- Set instruction
      } else {                                                  //--- Handle exit measuring
         measuring = false;                                     //--- Reset measuring
         DeleteMeasureObjects();                                //--- Delete objects
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
         current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction
      }
      DrawToolsPanel();                                         //--- Redraw tools panel
      last_click_time = 0;                                      //--- Reset click time
   } else {                                                     //--- Handle single click
      last_click_time = now;                                    //--- Update click time
   }
}

Wir implementieren die Funktion „HandleCrosshairClick“, um Klicks im Fadenkreuzmodus zu verarbeiten. Dabei rufen wir mit GetMicrosecondCount die aktuelle Mikrosekundenanzahl ab, um Doppelklicks innerhalb von 500 ms nach „last_click_time“ zu erkennen. Bei einem Doppelklick wird die Messfunktion umgeschaltet: Ist sie nicht aktiv, werden der Fixpunkt auf die aktuelle Zeit und den aktuellen Kurs gesetzt, die Messung aktiviert, das Scrollen des Charts mit ChartSetInteger deaktiviert und die Anweisung aktualisiert; falls aktiv, wird die Messung zurückgesetzt, Objekte werden mit „DeleteMeasureObjects“ gelöscht, Bildlauf wird wieder aktiviert und die Standardanweisung wiederhergestellt, anschließend wird das Werkzeugfenster neu gezeichnet und die Klickzeit zurückgesetzt. Bei Einzelklicks setzen wir „last_click_time“ auf den aktuellen Zeitpunkt, wodurch nachfolgende Klicks als Doppelklicks erkannt werden können. Definieren wir nun eine Logik zur Aktualisierung der Messlinie zwischen den Fadenkreuzen.

//+------------------------------------------------------------------+
//| Update measure line                                              |
//+------------------------------------------------------------------+
void UpdateMeasureLine(datetime time, double price) {
   string line_name = "Measure_Line";                           //--- Set measure line name
   if (ObjectFind(local_chart_id, line_name) < 0) {             //--- Check if line exists
      ObjectCreate(local_chart_id, line_name, OBJ_TREND, 0, fixed_time, fixed_price, time, price); //--- Create trend line
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_COLOR, clrBlue); //--- Set color
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_STYLE, STYLE_DASHDOT); //--- Set style
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_WIDTH, 1); //--- Set width
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, line_name, 0, fixed_time, fixed_price); //--- Move point 0
      ObjectMove(local_chart_id, line_name, 1, time, price);    //--- Move point 1
   }
}

//+------------------------------------------------------------------+
//| Update measure label                                             |
//+------------------------------------------------------------------+
void UpdateMeasureLabel(int mx, int my, datetime time, double price) {
   string label_name = "Measure_Label";                         //--- Set measure label name
   long period_sec = PeriodSeconds(_Period);                    //--- Get period seconds
   long virtual_fixed = fixed_time / period_sec;                //--- Compute virtual fixed
   long virtual_time = time / period_sec;                       //--- Compute virtual time
   int bars_diff = (int)MathAbs(virtual_fixed - virtual_time);  //--- Compute bars difference
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);      //--- Get point value
   long digits = SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);     //--- Get digits
   double pip_size = (digits == 3 || digits == 5) ? point * 10 : point; //--- Compute pip size
   double pips = MathAbs(price - fixed_price) / pip_size;       //--- Compute pips
   string price_diff = DoubleToString(MathAbs(price - fixed_price), (int)digits); //--- Format price diff
   string text = StringFormat("%d bars, %.1f pips, Price Diff: %s", bars_diff, pips, price_diff); //--- Format text

   if (ObjectFind(local_chart_id, label_name) < 0) {            //--- Check if label exists
      ObjectCreate(local_chart_id, label_name, OBJ_LABEL, 0, 0, 0); //--- Create label
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Set X distance
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Set Y distance
      ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Set text
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_FONTSIZE, 9); //--- Set font size
      ObjectSetString(local_chart_id, label_name, OBJPROP_FONT, "Arial"); //--- Set font
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_COLOR, clrBlue); //--- Set color
   } else {                                                     //--- Handle existing
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Update X distance
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Update Y distance
      ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Update text
   }
}

Für die Messlinie implementieren wir die Funktion „UpdateMeasureLine“, um im Messmodus eine Verbindungslinie anzuzeigen, wobei wir den Namen „Measure_Line“ definieren, überprüfen mit ObjectFind, ob sie vorhanden ist, und erstellen sie als OBJ_TREND vom festen zum aktuellen Zeitpunkt/Kurs mit blauem Strich-Punkt-Stil, Breite 1 und ohne Strahl, falls sie nicht vorhanden ist, oder verschieben ihre Punkte mit ObjectMove, falls sie vorhanden ist, um eine visuelle Verbindung zwischen den Punkten herzustellen. Wir erstellen die Funktion „UpdateMeasureLabel“, um dynamische Kennzahlen in der Nähe der Maus anzuzeigen. Dazu berechnen wir die Differenz der Bars, indem wir die Zeiten durch die Periodensekunden aus PeriodSeconds dividieren, um virtuelle Bars zu erhalten, und mit MathAbs den Absolutwert ermitteln. Anschließend berechnen wir die Pips anhand des Symbolwerts aus SymbolInfoDouble, der auf 3 oder 5 Stellen angepasst wird (durch Multiplikation mit 10), wobei die absolute Kursdifferenz gemäß der Funktion SymbolInfoInteger auf die entsprechende Stellenanzahl formatiert wird.

Um den Text mit StringFormat zu formatieren – einschließlich Bars, Pips, Rundung auf eine Dezimalstelle und Kursdifferenz –, prüfen wir, ob eine Beschriftung vorhanden ist, erstellen diese als OBJ_LABEL in der oberen linken Ecke mit Maus-Offset-Position, Schriftart „Arial“ in Schriftgröße 9, Farbe Blau, falls neu, oder aktualisieren Position und Text, falls bereits vorhanden, um das Benutzer-Feedback zu den Messwerten zu verbessern. Diese Kennzahlenberechnung ist von entscheidender Bedeutung, da sie Zeit- und Preisspannen über unterschiedliche Symbolgenauigkeiten und Zeiträume hinweg genau quantifiziert und so eine präzise Analyse ohne manuelle Berechnungen ermöglicht. Um die Palette zum Leben zu erwecken, werden wir im Ereignis-Handler Chartereignisse verarbeiten, um intuitive Interaktionen zu ermöglichen.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_KEYDOWN) {                              //--- Check key down event
      if (lparam == 27) {                                       //--- Check escape key
         DeactivateTool();                                      //--- Deactivate tool
      }
      return;                                                   //--- Return
   }
   if (id != CHARTEVENT_MOUSE_MOVE) return;                     //--- Return if not mouse move
   int mx = (int)lparam;                                        //--- Set mouse X
   int my = (int)dparam;                                        //--- Set mouse Y
   int ms = (int)sparam;                                        //--- Set mouse state

   bool prev_hh = header_hovered;                               //--- Store previous header hover
   bool prev_th = theme_hovered;                                //--- Store previous theme hover
   bool prev_mh = minimize_hovered;                             //--- Store previous minimize hover
   bool prev_ch = close_hovered;                                //--- Store previous close hover
   bool prev_rh = resize_hovered;                               //--- Store previous resize hover

   header_hovered = IsMouseOverHeader(mx, my);                  //--- Update header hover
   theme_hovered = IsMouseOverButton(mx, my, theme_x_offset);   //--- Update theme hover
   minimize_hovered = IsMouseOverButton(mx, my, minimize_x_offset); //--- Update minimize hover
   close_hovered = IsMouseOverButton(mx, my, close_x_offset);   //--- Update close hover
   resize_hovered = IsMouseOverResize(mx, my, hover_mode);      //--- Update resize hover

   string resize_tooltip = "";                                  //--- Initialize resize tooltip
   if (resize_hovered) {                                        //--- Check resize hovered
      switch (hover_mode) {                                     //--- Switch on hover mode
         case BOTTOM: resize_tooltip = "Resize Bottom"; break;  //--- Set bottom tooltip
         case RIGHT: resize_tooltip = "Resize Right"; break;    //--- Set right tooltip
         case BOTTOM_RIGHT: resize_tooltip = "Resize Bottom-Right"; break; //--- Set bottom-right tooltip
      }
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, resize_tooltip); //--- Set tooltip
   } else if (!panel_minimized && hovered_tool_index >= 0) {    //--- Check tool hovered
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, buttons[hovered_tool_index].tooltip); //--- Set tool tooltip
   } else {                                                     //--- Handle no hover
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, ""); //--- Clear tooltip
   }

   if (prev_hh != header_hovered || prev_th != theme_hovered || prev_mh != minimize_hovered || prev_ch != close_hovered || prev_rh != resize_hovered) { //--- Check if hovers changed
      DrawHeader();                                             //--- Redraw header
      if (!panel_minimized) DrawToolsPanel();                   //--- Redraw tools if not minimized
      ChartRedraw();                                            //--- Redraw chart
   }

   if (!panel_minimized) {                                      //--- Check not minimized
      int ht = GetHoveredTool(mx, my);                          //--- Get hovered tool
      if (ht != hovered_tool_index) {                           //--- Check if changed
         hovered_tool_index = ht;                               //--- Update index
         DrawToolsPanel();                                      //--- Redraw tools
         ChartRedraw();                                         //--- Redraw chart
      }
   }

   if (resize_hovered || resizing) {                            //--- Check resize hovered or resizing
      hover_mouse_local_x = mx - currentCanvasX;                //--- Update local X
      hover_mouse_local_y = my - (currentCanvasY + header_height + tools_y_offset); //--- Update local Y
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   if (ms == 1 && prev_mouse_state == 0) {                      //--- Check mouse down
      if (header_hovered) {                                     //--- Check header hovered
         panel_dragging = true;                                 //--- Set dragging flag
         panel_drag_x = mx;                                     //--- Set drag X
         panel_drag_y = my;                                     //--- Set drag Y
         panel_start_x = currentCanvasX;                        //--- Set start X
         panel_start_y = currentCanvasY;                        //--- Set start Y
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);         //--- Disable scroll
         DrawHeader();                                          //--- Redraw header
         ChartRedraw();                                         //--- Redraw chart
      } else if (theme_hovered) {                               //--- Check theme hovered
         ToggleTheme();                                         //--- Toggle theme
      } else if (minimize_hovered) {                            //--- Check minimize hovered
         ToggleMinimize();                                      //--- Toggle minimize
      } else if (close_hovered) {                               //--- Check close hovered
         OnDeinit(0);                                           //--- Call deinit
      } else if (!panel_minimized) {                            //--- Check not minimized
         int ht = GetHoveredTool(mx, my);                       //--- Get hovered tool
         if (ht >= 0) {                                         //--- Check valid tool
            ToggleTool(buttons[ht].type);                       //--- Toggle tool
         } else if (active_tool != TOOL_NONE && active_tool != TOOL_CROSSHAIR) { //--- Check active tool
            HandleDrawing(mx, my);                              //--- Handle drawing
         } else if (IsMouseOverResize(mx, my, resize_mode)) {   //--- Check resize area
            resizing = true;                                    //--- Set resizing flag
            resize_start_x = mx;                                //--- Set start X
            resize_start_y = my;                                //--- Set start Y
            start_width = currentWidth;                         //--- Set start width
            start_height = currentHeight;                       //--- Set start height
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable scroll
            DrawToolsPanel();                                   //--- Redraw tools
            ChartRedraw();                                      //--- Redraw chart
         }
      }
   }

   if (panel_dragging && ms == 1) {                             //--- Check dragging
      int dx = mx - panel_drag_x;                               //--- Compute delta X
      int dy = my - panel_drag_y;                               //--- Compute delta Y
      currentCanvasX = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentWidth, panel_start_x + dx)); //--- Update X
      currentCanvasY = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - header_height - (panel_minimized ? 0 : currentHeight) - tools_y_offset, panel_start_y + dy)); //--- Update Y
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set header X
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_YDISTANCE, currentCanvasY); //--- Set header Y
      if (!panel_minimized) {                                   //--- Check not minimized
         ObjectSetInteger(0, canvasToolsName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set tools X
         ObjectSetInteger(0, canvasToolsName, OBJPROP_YDISTANCE, currentCanvasY + header_height + tools_y_offset); //--- Set tools Y
      }
      ChartRedraw();                                            //--- Redraw chart
   }

   if (resizing && ms == 1) {                                   //--- Check resizing
      int dx = mx - resize_start_x;                             //--- Compute delta X
      int dy = my - resize_start_y;                             //--- Compute delta Y
      int nw = MathMax(min_width, start_width + (resize_mode == RIGHT || resize_mode == BOTTOM_RIGHT ? dx : 0)); //--- Compute new width
      int nh = MathMax(min_height, start_height + (resize_mode == BOTTOM || resize_mode == BOTTOM_RIGHT ? dy : 0)); //--- Compute new height
      nw = MathMin(nw, (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentCanvasX); //--- Clamp width
      nh = MathMin(nh, (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - currentCanvasY - header_height - tools_y_offset); //--- Clamp height
      currentWidth = nw;                                        //--- Update width
      currentHeight = nh;                                       //--- Update height
      canvasTools.Resize(currentWidth, currentHeight);          //--- Resize tools canvas
      ObjectSetInteger(0, canvasToolsName, OBJPROP_XSIZE, currentWidth); //--- Set tools X size
      ObjectSetInteger(0, canvasToolsName, OBJPROP_YSIZE, currentHeight); //--- Set tools Y size
      canvasHeader.Resize(currentWidth, header_height);         //--- Resize header canvas
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, currentWidth); //--- Set header X size
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Set header Y size
      DrawHeader();                                             //--- Redraw header
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   if (ms == 0 && prev_mouse_state == 1) {                      //--- Check mouse up
      panel_dragging = false;                                   //--- Reset dragging
      resizing = false;                                         //--- Reset resizing
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);             //--- Enable scroll
      DrawHeader();                                             //--- Redraw header
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   datetime time = 0;                                           //--- Initialize time
   double price = 0.0;                                          //--- Initialize price
   int sw = 0;                                                  //--- Initialize subwindow
   if (ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) { //--- Convert XY to time/price
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Check crosshair active
         UpdateMovingCrosshair(time, price);                    //--- Update moving crosshair
         if (measuring) {                                       //--- Check measuring
            UpdateFixedCrosshair();                             //--- Update fixed crosshair
            UpdateMeasureLine(time, price);                     //--- Update measure line
            UpdateMeasureLabel(mx, my, time, price);            //--- Update measure label
         }
         ChartRedraw();                                         //--- Redraw chart
      }
      if (ms == 1 && prev_mouse_state == 0 && active_tool == TOOL_CROSSHAIR && 
          !header_hovered && !theme_hovered && !minimize_hovered && !close_hovered && !resize_hovered && 
          (panel_minimized || GetHoveredTool(mx, my) == -1)) {   //--- Check crosshair click conditions
         HandleCrosshairClick(time, price);                      //--- Handle click
         ChartRedraw();                                          //--- Redraw chart
      }
   }

   prev_mouse_state = ms;                                       //--- Update previous state
}

Wir verwenden die Ereignisbehandlung von OnChartEvent, um alle Benutzerinteraktionen mit der Palette und den Werkzeugen zu verwalten. Zunächst prüfen wir auf CHARTEVENT_KEYDOWN, um die Escape-Taste (lparam 27) zu erkennen, deaktivieren das aktuelle Werkzeug mit „DeactivateTool“ und kehren dann vorzeitig zurück. Falls Sie sich fragen sollten: Das Escape-Zeichen hat den Code 27 in der ASCII-Tabelle. Siehe unten.

ASCII-ZEICHEN 27 – ESCAPE

Als Nächstes: Wenn es sich nicht um ein CHARTEVENT_MOUSE_MOVE handelt, beenden wir die Prozedur; ansonsten extrahieren wir die Mauskoordinaten X und Y sowie den Status aus den Parametern, speichern die vorherigen Hover-Flags und aktualisieren sie mithilfe von Funktionen wie „IsMouseOverHeader“, „IsMouseOverButton“ für Theme/Minimieren/Schließen und „IsMouseOverResize“, die „hover_mode“ setzen. Als Nächstes legen wir mit ObjectSetString die Tooltips für das jeweils unter dem Mauszeiger liegende Werkzeug für Größenänderungsmodi oder bei Mauszeiger-Überfahrt aktivierte Werkzeugschaltflächen fest und löschen diese, falls keine vorhanden sind; wenn sich eine Mauszeiger-Überfahrt geändert hat, werden die Kopfzeile und die Werkzeuge (sofern nicht minimiert) neu gezeichnet, gefolgt von der Funktion ChartRedraw. Wenn das Fenster nicht minimiert ist, ermitteln wir den Index des Werkzeugs, über dem der Mauszeiger schwebt, mit „GetHoveredTool“ und zeichnen es neu, falls sich etwas geändert hat; bei Größenänderungen werden die lokalen Hover-Koordinaten aktualisiert und die Werkzeuge neu gezeichnet.

Beim Herunterdrücken der Maustaste (ms 1, zuvor 0) initiieren wir das Ziehen, wenn der Mauszeiger über der Kopfzeile schwebt, indem wir Flags setzen, das Scrollen deaktivieren und die Kopfzeile neu zeichnen; das Thema umschalten oder minimieren, wenn der Mauszeiger über den entsprechenden Schaltflächen schwebt; bei „Schließen“ die Funktion „deinit“ aufrufen; oder, wenn der Mauszeiger über einer Werkzeugschaltfläche schwebt, deren Typ umschalten; das Zeichnen von Elementen, die kein Fadenkreuz sind, mit „HandleDrawing“ verarbeiten; die Größenänderung starten, wenn sich der Mauszeiger über dem Größenänderungsbereich befindet, indem Flags gesetzt, Startvorgänge ausgelöst und das Scrollen deaktiviert werden, bevor die Werkzeuge neu gezeichnet werden. Während des Ziehvorgangs (ms 1) berechnen wir die Deltas, begrenzen die neuen Positionen mithilfe der Funktionen MathMax und MathMin auf die Chartgrenzen, aktualisieren die globalen Variablen und legen die Objektabstände für die Kopfzeile und die Werkzeuge fest (sofern diese sichtbar sind); anschließend wird das Chart neu gezeichnet. Zur Größenanpassung (ms 1) werden die neuen Abmessungen auf Basis des Modus berechnet und begrenzt, die globalen Variablen aktualisiert, die Zeichenflächen mit Resize angepasst und die Größen über ObjectSetInteger festgelegt; anschließend werden Kopfzeile, Werkzeuge und Chart neu gezeichnet.

Beim Loslassen der Maustaste (ms 0, prev 1) werden die Flags für das Ziehen und die Größenänderung zurückgesetzt, das Scrollen wieder aktiviert, die Kopfzeile und die Werkzeuge neu gezeichnet und das Chart aktualisiert. Wir wandeln die Mausposition mit ChartXYToTimePrice in Zeit/Preis um und aktualisieren, falls das Fadenkreuz aktiv ist, das bewegliche Fadenkreuz; bei einer Messung werden außerdem das feste Fadenkreuz, die Messlinie und die Beschriftung aktualisiert; bei einem qualifizierten Klick außerhalb der Palette wird dies mit „HandleCrosshairClick“ verarbeitet und neu gezeichnet. Zuletzt sollten Sie „prev_mouse_state“ für nachfolgende Ereignisse auf den aktuellen Wert aktualisieren, um eine reaktionsschnelle, zustandsbehaftete Interaktivität zu gewährleisten. Schließlich müssen wir unsere Objekte löschen, wenn sie nicht mehr benötigt werden.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   canvasHeader.Destroy();                                      //--- Destroy header canvas
   canvasTools.Destroy();                                       //--- Destroy tools canvas
   DeleteCrosshair();                                           //--- Delete crosshair
   measuring = false;                                           //--- Reset measuring flag
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);                //--- Enable mouse scroll

   int total = ObjectsTotal(local_chart_id);                    //--- Get total objects
   for (int i = total - 1; i >= 0; i--) {                       //--- Loop over objects backward
      string obj_name = ObjectName(local_chart_id, i);          //--- Get object name
      if (StringFind(obj_name, "Tool_") == 0) {                 //--- Check if our tool
         ObjectDelete(local_chart_id, obj_name);                //--- Delete object
      }
   }

   ChartRedraw();                                               //--- Redraw chart
}

In OnDeinit führen wir eine Bereinigung durch, indem wir die Header- und Werkzeuge-Zeichenflächen mit der Methode Destroy zerstören, alle aktiven Fadenkreuze über „DeleteCrosshair“ entfernen, das Messflag auf „false“ zurücksetzen und das Maus-Scrollen über die Funktion ChartSetInteger wieder aktivieren. Um sicherzustellen, dass keine Objekte übrig bleiben, ermitteln wir die Gesamtanzahl mit ObjectsTotal im lokalen Chart, durchlaufen die Liste rückwärts, um ein sicheres Löschen zu gewährleisten, rufen die Namen mit ObjectName ab und entfernen diejenigen, deren Namen mit „Tool_“ beginnen, mithilfe der Funktion ObjectDelete. Abschließend rufen wir ChartRedraw auf, um das Chart zu aktualisieren, und schließen damit die Freigabe der Ressourcen beim Beenden des Programms ab. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

FADENKREUZ IN AKTION

Aus der Visualisierung geht hervor, dass wir eine interaktive Werkzeugpalette erstellt und damit unsere Ziele erreicht haben. Nun bleibt nur noch, die Funktionsfähigkeit des Systems zu testen, was in den folgenden Teilen behandelt wurde.


Backtests

Wir haben die Tests durchgeführt; unten sehen Sie die resultierende Visualisierung als einzelnes GIF-Bild.

WERKZEUGPALETTE GIF


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir in MQL5 eine interaktive Werkzeugpalette zum Zeichnen in Charts entwickelt haben, die über verschiebbare und in der Größe anpassbare Panels sowie einen Wechsel zwischen dem dunklen und dem hellen Modus verfügt. Wir haben Schaltflächen für verschiedene Werkzeuge wie Fadenkreuze, Trendlinien, Linien, Rechtecke, Fibonacci-Retracements, Text und Pfeile integriert und die Mausereignisse so gesteuert, dass eine nahtlose Aktivierung und Interaktion direkt im Chart möglich ist. Dieses anpassbare System verbessert die Handelsanalyse durch Echtzeit-Feedback und Anweisungen und bietet eine vielseitige Benutzeroberfläche für effiziente Arbeitsabläufe. Mit dieser Palette interaktiver Werkzeuge sind Sie bestens gerüstet, um Anmerkungen und Messungen in Charts zu optimieren – und können diese nahtlos in fortgeschrittene Handelsstrategien integrieren. In den folgenden Teilen werden wir zu einer detaillierteren Palette mit besser organisierten Werkzeugen übergehen. Bleiben Sie dran!

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

Beigefügte Dateien |
Tools_Palette.mq5 (62.96 KB)
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Verwendung des MQL5-Wirtschaftskalenders zur Nachrichtenfilterung (Teil 1): Implementierung von Zeitfenstern vor und nach Nachrichtenereignissen in MQL5 Verwendung des MQL5-Wirtschaftskalenders zur Nachrichtenfilterung (Teil 1): Implementierung von Zeitfenstern vor und nach Nachrichtenereignissen in MQL5
Wir entwickeln einen kalendergesteuerten Nachrichtenfilter vollständig in MQL5 und verzichten dabei auf Webanfragen und externe DLLs. Teil 1 behandelt das Laden und Zwischenspeichern von Ereignissen, deren Zuordnung zu Symbolen nach Währung, das Filtern nach Bedeutung, die Definition von Vor- und Nachlaufzeitfenstern sowie das Sperren neuer Trades während aktiver Nachrichten, mit der optionalen Möglichkeit, Positionen vor Nachrichtenereignissen zu schließen. Das Ergebnis ist ein konfigurierbarer, prop-firm-tauglicher Kontrollmechanismus, der falsche Handelspausen vermeidet und Einstiegspunkte bei hohen Schwankungen schützt.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
MetaTrader 5 – Blaupause für maschinelles Lernen (Teil 7): Von verstreuten Experimenten zu reproduzierbaren Ergebnissen MetaTrader 5 – Blaupause für maschinelles Lernen (Teil 7): Von verstreuten Experimenten zu reproduzierbaren Ergebnissen
Im neuesten Teil dieser Reihe gehen wir über einzelne Techniken des maschinellen Lernens hinaus und befassen uns mit dem Forschungschaos, unter dem viele quantitative Trader leiden. Im Mittelpunkt dieses Artikels steht der Übergang von Ad-hoc-Experimenten in Notebooks zu einer klar strukturierten, produktionsreifen Pipeline, die Reproduzierbarkeit, Nachvollziehbarkeit und Effizienz gewährleistet.