English
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 17): Die Grid-Mart Scalping Strategie mit einem dynamischen Dashboard meistern

Automatisieren von Handelsstrategien in MQL5 (Teil 17): Die Grid-Mart Scalping Strategie mit einem dynamischen Dashboard meistern

MetaTrader 5Handel |
31 5
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorigen Artikel (Teil 16) haben wir den Midnight Range Breakout mit der Strategie Break of Structure automatisiert, um Kursausbrüche zu erfassen. In Teil 17 konzentrieren wir uns nun auf die Automatisierung der Strategie des Grid-Mart Scalping in MetaQuotes Language 5 (MQL5) und entwickeln einen Expert Advisor, der die gitterbasierte Martingale-Handelsgeschäfte ausführt und ein dynamisches Dashboard für die Echtzeitüberwachung bietet. Wir werden die folgenden Themen behandeln:

  1. Verstehen der Strategie des Grid Mart Scalping
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende dieses Artikels werden Sie ein voll funktionsfähiges MQL5-Programm haben, das die Märkte mit Präzision scalpt und die Handelsmetriken visualisiert - legen wir los!


Verstehen der Strategie des Grid Mart Scalping

Die Strategie Grid Mart Scalping verwendet einen Martingale-Ansatz, bei dem Kauf- oder Verkaufsaufträge in festen Preisintervallen (z. B. 2,0 Pips) platziert werden, um kleine Gewinne aus Marktschwankungen zu erzielen, während die Losgrößen nach Verlusten erhöht werden, um das Kapital schnell wiederherzustellen. Sie setzt auf den Hochfrequenzhandel und strebt bescheidene Gewinne (z. B. 4 Pips) pro Handel an. Es erfordert jedoch ein sorgfältiges Risikomanagement aufgrund des exponentiellen Anstiegs der Losgröße, der durch konfigurierbare Grenzen wie maximale Rasterwerte und tägliche Drawdown-Schwellenwerte begrenzt wird. Diese Strategie gedeiht in volatilen Märkten, erfordert aber eine präzise Konfiguration, um erhebliche Drawdowns bei lang anhaltenden Trends zu vermeiden.

Unser Implementierungsplan sieht die Erstellung eines MQL5 Expert Advisors vor, um die Grid-Mart-Strategie durch die Berechnung von Rasterintervallen, die Verwaltung der Losgrößenprogression und die Ausführung von Trades mit vordefinierten Stop-Loss- und Take-Profit-Levels zu automatisieren. Das Programm bietet ein dynamisches Dashboard zur Anzeige von Echtzeit-Kennzahlen wie Spread, aktive Losgrößen und Kontostatus mit farbkodierten Darstellungen, die die Entscheidungsfindung erleichtern. Robuste Risikokontrollen, einschließlich Limits für die Inanspruchnahme und Beschränkungen der Netzgröße, sorgen für eine konsistente Performance unter allen Marktbedingungen. Kurz gesagt, das ist es, was wir schaffen wollen.

STRATEGIEPLAN


Implementation in MQL5

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

//+------------------------------------------------------------------+
//|                                      GridMart Scalper MT5 EA.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"

#include <Trade/Trade.mqh>

//--- Declare trade object to execute trading operations
CTrade obj_Trade;

//--- Trading state variables
double dailyBalance = 0;           //--- Store the daily starting balance for drawdown monitoring
datetime dailyResetTime = 0;       //--- Track the last daily reset time
bool tradingEnabled = true;        //--- Indicate if trading is allowed based on drawdown limits
datetime lastBarTime = 0;          //--- Store the timestamp of the last processed bar
bool hasBuyPosition = false;       //--- Flag if there is an active buy position
bool hasSellPosition = false;      //--- Flag if there is an active sell position
bool openNewTrade = false;         //--- Signal when a new trade should be opened
int activeOrders = 0;              //--- Count the number of active orders
double latestBuyPrice = 0;         //--- Store the price of the latest buy order
double latestSellPrice = 0;        //--- Store the price of the latest sell order
double calculatedLot = 0;          //--- Hold the calculated lot size for new orders
bool modifyPositions = false;      //--- Indicate if position SL/TP need modification
double weightedPrice = 0;          //--- Track the weighted average price of open positions
double targetTP = 0;               //--- Store the target take-profit price
double targetSL = 0;               //--- Store the target stop-loss price
bool updateSLTP = false;           //--- Signal when SL/TP updates are needed
int cycleCount = 0;                //--- Count the number of trading cycles
double totalVolume = 0;            //--- Accumulate the total volume of open positions
bool dashboardVisible = true;      //--- Control visibility of the dashboard

//--- Dashboard dragging and hover state variables
bool panelDragging = false;        //--- Indicate if the dashboard is being dragged
int panelDragX = 0;                //--- Store the X-coordinate of the mouse during dragging
int panelDragY = 0;                //--- Store the Y-coordinate of the mouse during dragging
int panelStartX = 0;               //--- Store the initial X-position of the dashboard
int panelStartY = 0;               //--- Store the initial Y-position of the dashboard
bool closeButtonHovered = false;   //--- Track if the close button is hovered
bool headerHovered = false;        //--- Track if the header is hovered

input group "Main EA Settings"
input string EA_NAME = "GridMart Scalper";    // EA Name
input bool CONTINUE_TRADING = true;           // Continue Trading After Cycle
input int MAX_CYCLES = 1000;                  // Max Trading Cycles
input int START_HOUR = 0;                     // Start Trading Hour
input int END_HOUR = 23;                      // End Trading Hour
input int LOT_MODE = 1;                       // Lot Mode (1=Multiplier, 2=Fixed)
input double BASE_LOT = 0.01;                 // Base Lot Size
input int STOP_LOSS_PIPS = 100;               // Stop Loss (Pips)
input int TAKE_PROFIT_PIPS = 
3 ;               // Take Profit (Pips)
input double GRID_DISTANCE = 3.0;             // Grid Distance (Pips)
input double LOT_MULTIPLIER = 1.3;            // Lot Multiplier
input int MAX_GRID_LEVELS = 30;               // Max Grid Levels
input int LOT_PRECISION = 2;                  // Lot Decimal Precision
input int MAGIC = 1234567890;                 // Magic Number
input color TEXT_COLOR = clrWhite;            // Dashboard Text Color

input group "EA Risk Management Settings"
input bool ENABLE_DAILY_DRAWDOWN = false;     // Enable Daily Drawdown Limiter
input double DRAWDOWN_LIMIT = -1.0;           // Daily Drawdown Threshold (-%)
input bool CLOSE_ON_DRAWDOWN = false;         // Close Positions When Threshold Hit

input group "Dashboard Settings"
input int PANEL_X = 30;                       // Initial X Distance (pixels)
input int PANEL_Y = 50;                       // Initial Y Distance (pixels)

//--- Pip value for price calculations
double pipValue;

//--- Dashboard constants
const int DASHBOARD_WIDTH = 300;                   //--- Width of the dashboard in pixels
const int DASHBOARD_HEIGHT = 260;                  //--- Height of the dashboard in pixels
const int HEADER_HEIGHT = 30;                      //--- Height of the header section
const int CLOSE_BUTTON_WIDTH = 40;                 //--- Width of the close button
const int CLOSE_BUTTON_HEIGHT = 28;                //--- Height of the close button
const color HEADER_NORMAL_COLOR = clrGold;         //--- Normal color of the header
const color HEADER_HOVER_COLOR = C'200,150,0';     //--- Header color when hovered
const color BACKGROUND_COLOR = clrDarkSlateGray;   //--- Background color of the dashboard
const color BORDER_COLOR = clrBlack;               //--- Border color of dashboard elements
const color SECTION_TITLE_COLOR = clrLightGray;    //--- Color for section titles
const color CLOSE_BUTTON_NORMAL_BG = clrCrimson;   //--- Normal background color of the close button
const color CLOSE_BUTTON_HOVER_BG = clrDodgerBlue; //--- Hover background color of the close button
const color CLOSE_BUTTON_NORMAL_BORDER = clrBlack; //--- Normal border color of the close button
const color CLOSE_BUTTON_HOVER_BORDER = clrBlue;   //--- Hover border color of the close button
const color VALUE_POSITIVE_COLOR = clrLimeGreen;   //--- Color for positive values (e.g., profit, low spread)
const color VALUE_NEGATIVE_COLOR = clrOrange;      //--- Color for negative or warning values (e.g., loss, high spread)
const color VALUE_LOSS_COLOR = clrHotPink;         //--- Color for negative profit
const color VALUE_ACTIVE_COLOR = clrGold;          //--- Color for active states (e.g., open orders, medium spread)
const color VALUE_DRAWDOWN_INACTIVE = clrAqua;     //--- Color for inactive drawdown state
const color VALUE_DRAWDOWN_ACTIVE = clrRed;        //--- Color for active drawdown state
const int FONT_SIZE_HEADER = 12;                   //--- Header text font size (pt)
const int FONT_SIZE_SECTION_TITLE = 11;            //--- Section title font size (pt)
const int FONT_SIZE_METRIC = 9;                    //--- Metric label/value font size (pt)
const int FONT_SIZE_BUTTON = 12;                   //--- Button font size (pt)

Hier implementieren wir die Strategie in MQL5 und initialisieren die Kernkomponenten des Programms, um gitterbasierte Martingale-Handelsgeschäfte zu automatisieren und ein dynamisches Dashboard zu unterstützen. Wir deklarieren das „CTrade“-Objekt als „obj_Trade“ und verwenden „#include <Trade/Trade.mqh>“, um die Handelsausführung zu verwalten. Wir definieren Variablen wie „dailyBalance“, um den Saldo zu verfolgen, „lastBarTime“, um Bar-Zeitstempel mit der Funktion iTime zu speichern, „hasBuyPosition“ und „hasSellPosition“, um aktive Trades zu kennzeichnen, und „activeOrders“, um offene Positionen zu zählen.

Wir stellen Eingaben wie „GRID_DISTANCE = 3.0“ für die Rasterintervalle, „LOT_MULTIPLIER = 1.3“ für die Lot-Skalierung und „TAKE_PROFIT_PIPS = 3“ für die Gewinnziele ein und verwenden „MAGIC = 1234567890“, um die Handelsgeschäfte zu identifizieren. Zur Risikokontrolle werden „ENABLE_DAILY_DRAWDOWN“ und „DRAWDOWN_LIMIT“ eingesetzt, wobei „cycleCount“ und „MAX_CYCLES“ die Handelszyklen begrenzen. Wir konfigurieren das Dashboard mit „DASHBOARD_WIDTH = 300“, „FONT_SIZE_METRIC = 9“, „panelDragging“ für die Drag-Funktionalität, „closeButtonHovered“ für Hover-Effekte und „VALUE_POSITIVE_COLOR = clrLimeGreen“ für die visuelle Darstellung, mit „pipValue“ für die genaue Preisgestaltung. Daraus ergibt sich die folgende Nutzeroberfläche.

NUTZERINTERFACE

Aus dem Bild geht hervor, dass wir das Programm über die definierte Nutzeroberfläche steuern können. Nun müssen wir noch einige Hilfsfunktionen definieren, die wir verwenden werden, wenn wir grundlegende und häufige Aktionen wie das Währungspaar oder den Positionstyp benötigen. Hier ist die Logik, die wir dafür angepasst haben.

//+------------------------------------------------------------------+
//| Retrieve the current account balance                             |
//+------------------------------------------------------------------+
double GetAccountBalance() {
   //--- Return the current account balance
   return AccountInfoDouble(ACCOUNT_BALANCE);
}

//+------------------------------------------------------------------+
//| Retrieve the magic number of the selected position               |
//+------------------------------------------------------------------+
long GetPositionMagic() {
   //--- Return the magic number of the selected position
   return PositionGetInteger(POSITION_MAGIC);
}

//+------------------------------------------------------------------+
//| Retrieve the open price of the selected position                 |
//+------------------------------------------------------------------+
double GetPositionOpenPrice() {
   //--- Return the open price of the selected position
   return PositionGetDouble(POSITION_PRICE_OPEN);
}

//+------------------------------------------------------------------+
//| Retrieve the stop-loss price of the selected position            |
//+------------------------------------------------------------------+
double GetPositionSL() {
   //--- Return the stop-loss price of the selected position
   return PositionGetDouble(POSITION_SL);
}

//+------------------------------------------------------------------+
//| Retrieve the take-profit price of the selected position          |
//+------------------------------------------------------------------+
double GetPositionTP() {
   //--- Return the take-profit price of the selected position
   return PositionGetDouble(POSITION_TP);
}

//+------------------------------------------------------------------+
//| Retrieve the symbol of the selected position                     |
//+------------------------------------------------------------------+
string GetPositionSymbol() {
   //--- Return the symbol of the selected position
   return PositionGetString(POSITION_SYMBOL);
}

//+------------------------------------------------------------------+
//| Retrieve the ticket number of the selected position              |
//+------------------------------------------------------------------+
ulong GetPositionTicket() {
   //--- Return the ticket number of the selected position
   return PositionGetInteger(POSITION_TICKET);
}

//+------------------------------------------------------------------+
//| Retrieve the open time of the selected position                  |
//+------------------------------------------------------------------+
datetime GetPositionOpenTime() {
   //--- Return the open time of the selected position as a datetime
   return (datetime)PositionGetInteger(POSITION_TIME);
}

//+------------------------------------------------------------------+
//| Retrieve the type of the selected position                       |
//+------------------------------------------------------------------+
int GetPositionType() {
   //--- Return the type of the selected position (buy/sell)
   return (int)PositionGetInteger(POSITION_TYPE);
}

Wir verwenden die Funktion „GetAccountBalance“ mit der Funktion AccountInfoDouble, um den aktuellen Kontostand abzurufen und so eine Saldenkontrolle für das Risikomanagement zu ermöglichen.

Wir implementieren die Funktion „GetPositionMagic“ mit der Funktion PositionGetInteger, um die magische Zahl einer Position abzurufen, die Funktion „GetPositionOpenPrice“ mit PositionGetDouble, um den Eröffnungskurs zu erhalten, und die Funktionen „GetPositionSL“ und „GetPositionTP“ mit PositionGetDouble, um auf Stop-Loss- bzw. Take-Profit-Levels zuzugreifen, die präzise Handelsberechnungen unterstützen.

Zusätzlich definieren wir die Funktion „GetPositionSymbol“ mit PositionGetString, um das Symbol einer Position zu überprüfen, die Funktionen „GetPositionTicket“ und „GetPositionOpenTime“ mit „PositionGetInteger“, um Positionskennungen und Eröffnungszeiten zu verfolgen, und die Funktion „GetPositionType“, um den Kauf- oder Verkaufsstatus zu bestimmen, was eine genaue Positionsüberwachung und Handelslogik erleichtert. Jetzt können wir mit der Erstellung des Dashboards fortfahren, und wir benötigen Hilfsfunktionen, um die Arbeit zu erleichtern.

//+------------------------------------------------------------------+
//| Create a rectangular object for the dashboard                    |
//+------------------------------------------------------------------+
void CreateRectangle(string name, int x, int y, int width, int height, color bgColor, color borderColor) {
   //--- Check if object does not exist
   if (ObjectFind(0, name) < 0) {
      //--- Create a rectangle label object
      ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   }
   //--- Set X-coordinate
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   //--- Set Y-coordinate
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   //--- Set width
   ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
   //--- Set height
   ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
   //--- Set background color
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgColor);
   //--- Set border type to flat
   ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   //--- Set border color
   ObjectSetInteger(0, name, OBJPROP_COLOR, borderColor);
   //--- Set border width
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   //--- Set object to foreground
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   //--- Disable object selection
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   //--- Hide object from object list
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

//+------------------------------------------------------------------+
//| Create a text label for the dashboard                            |
//+------------------------------------------------------------------+
void CreateTextLabel(string name, int x, int y, string text, color clr, int fontSize, string font = "Arial") {
   //--- Check if object does not exist
   if (ObjectFind(0, name) < 0) {
      //--- Create a text label object
      ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
   }
   //--- Set X-coordinate
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   //--- Set Y-coordinate
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   //--- Set label text
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   //--- Set font
   ObjectSetString(0, name, OBJPROP_FONT, font);
   //--- Set font size
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
   //--- Set text color
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   //--- Set object to foreground
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   //--- Disable object selection
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   //--- Hide object from object list
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

//+------------------------------------------------------------------+
//| Create a button for the dashboard                                |
//+------------------------------------------------------------------+
void CreateButton(string name, string text, int x, int y, int width, int height, color textColor, color bgColor, int fontSize, color borderColor, bool isBack, string font = "Arial") {
   //--- Check if object does not exist
   if (ObjectFind(0, name) < 0) {
      //--- Create a button object
      ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
   }
   //--- Set X-coordinate
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   //--- Set Y-coordinate
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   //--- Set width
   ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
   //--- Set height
   ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
   //--- Set button text
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   //--- Set font
   ObjectSetString(0, name, OBJPROP_FONT, font);
   //--- Set font size
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
   //--- Set text color
   ObjectSetInteger(0, name, OBJPROP_COLOR, textColor);
   //--- Set background color
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgColor);
   //--- Set border color
   ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, borderColor);
   //--- Set background rendering
   ObjectSetInteger(0, name, OBJPROP_BACK, isBack);
   //--- Reset button state
   ObjectSetInteger(0, name, OBJPROP_STATE, false);
   //--- Disable button selection
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   //--- Hide button from object list
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

Hier definieren wir die Funktion „CreateRectangle“ mit den Funktionen ObjectCreate und ObjectSetInteger, um rechteckige Elemente wie den Hintergrund und die Kopfzeile des Dashboards zu zeichnen und Eigenschaften wie Position, Größe und Farben für ein ausgefeiltes Layout festzulegen. Wir implementieren die Funktion „CreateTextLabel“ und verwenden „ObjectCreate“ und ObjectSetString, um Metriken wie Streuung und Losgrößen mit anpassbaren Schrift- und Farbeinstellungen für eine klare Lesbarkeit anzuzeigen.

Darüber hinaus definieren wir die Funktion „CreateButton“, um interaktive Schaltflächen wie die Schaltfläche „Schließen“ hinzuzufügen, die Nutzeraktionen mit maßgeschneidertem Styling und Hover-Effekten ermöglichen und so ein nahtloses und visuell intuitives Dashboard-Erlebnis gewährleisten. Wir können nun diese Funktionen verwenden, um die Dashboard-Elemente in einer neuen Funktion zu erstellen, aber da wir die Gesamtzahl der Gewinne benötigen, müssen wir die Funktion dafür definieren.

//+------------------------------------------------------------------+
//| Calculate the total unrealized profit of open positions          |
//+------------------------------------------------------------------+
double CalculateTotalProfit() {
   //--- Initialize profit accumulator
   double profit = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Accumulate unrealized profit
         profit += PositionGetDouble(POSITION_PROFIT);
      }
   }
   //--- Return total profit
   return profit;
}

Hier implementieren wir die Funktion „CalculateTotalProfit“ mit PositionsTotal und PositionGetTicket, um durch die offenen Positionen zu iterieren, jede über PositionSelectByTicket auszuwählen und ihr Symbol und ihre magische Zahl mit „GetPositionSymbol“ und „GetPositionMagic“ zu verifizieren, um den Gesamtgewinn zu ermitteln. Wir akkumulieren nicht realisierte Gewinne, indem wir die Funktion PositionGetDouble verwenden, um den Gewinn jeder Position abzurufen, und speichern die Summe in der Variablen „profit“, was eine genaue Überwachung der Handelsergebnisse ermöglicht. Anschließend können wir die Funktion zur Erstellung des Dashboards wie folgt zusammenstellen.

//+------------------------------------------------------------------+
//| Update the dashboard with real-time trading metrics              |
//+------------------------------------------------------------------+
void UpdateDashboard() {
   //--- Exit if dashboard is not visible
   if (!dashboardVisible) return;

   //--- Create dashboard background rectangle
   CreateRectangle("Dashboard", panelStartX, panelStartY, DASHBOARD_WIDTH, DASHBOARD_HEIGHT, BACKGROUND_COLOR, BORDER_COLOR);

   //--- Create header rectangle
   CreateRectangle("Header", panelStartX, panelStartY, DASHBOARD_WIDTH, HEADER_HEIGHT, headerHovered ? HEADER_HOVER_COLOR : HEADER_NORMAL_COLOR, BORDER_COLOR);
   //--- Create header text label
   CreateTextLabel("HeaderText", panelStartX + 10, panelStartY + 8, EA_NAME, clrBlack, FONT_SIZE_HEADER, "Arial Bold");

   //--- Create close button
   CreateButton("CloseButton", CharToString(122), panelStartX + DASHBOARD_WIDTH - CLOSE_BUTTON_WIDTH, panelStartY + 1, CLOSE_BUTTON_WIDTH, CLOSE_BUTTON_HEIGHT, clrWhite, closeButtonHovered ? CLOSE_BUTTON_HOVER_BG : CLOSE_BUTTON_NORMAL_BG, FONT_SIZE_BUTTON, closeButtonHovered ? CLOSE_BUTTON_HOVER_BORDER : CLOSE_BUTTON_NORMAL_BORDER, false, "Wingdings");

   //--- Initialize dashboard content layout
   //--- Set initial Y-position below header
   int sectionY = panelStartY + HEADER_HEIGHT + 15;
   //--- Set left column X-position for labels
   int labelXLeft = panelStartX + 15;
   //--- Set right column X-position for values
   int valueXRight = panelStartX + 160;
   //--- Set row height for metrics
   int rowHeight = 15;

   //--- Pre-calculate values for conditional coloring
   //--- Calculate total unrealized profit
   double profit = CalculateTotalProfit();
   //--- Set profit color based on value
   color profitColor = (profit > 0) ? VALUE_POSITIVE_COLOR : (profit < 0) ? VALUE_LOSS_COLOR : TEXT_COLOR;

   //--- Get current equity
   double equity = AccountInfoDouble(ACCOUNT_EQUITY);
   //--- Get current balance
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   //--- Set equity color based on comparison with balance
   color equityColor = (equity > balance) ? VALUE_POSITIVE_COLOR : (equity < balance) ? VALUE_NEGATIVE_COLOR : TEXT_COLOR;

   //--- Set balance color based on comparison with daily balance
   color balanceColor = (balance > dailyBalance) ? VALUE_POSITIVE_COLOR : (balance < dailyBalance) ? VALUE_NEGATIVE_COLOR : TEXT_COLOR;

   //--- Set open orders color based on active orders
   color ordersColor = (activeOrders > 0) ? VALUE_ACTIVE_COLOR : TEXT_COLOR;

   //--- Set drawdown active color based on trading state
   color drawdownColor = tradingEnabled ? VALUE_DRAWDOWN_INACTIVE : VALUE_DRAWDOWN_ACTIVE;

   //--- Set lot sizes color based on active orders
   color lotsColor = (activeOrders > 0) ? VALUE_ACTIVE_COLOR : TEXT_COLOR;

   //--- Calculate dynamic spread and color
   //--- Get current ask price
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   //--- Get current bid price
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   //--- Calculate spread in points
   double spread = (ask - bid) / Point();
   //--- Format spread with 1 decimal place for display
   string spreadDisplay = DoubleToString(spread, 1);
   //--- Initialize spread color
   color spreadColor;
   //--- Check if spread is low (favorable)
   if (spread <= 2.0) {
      //--- Set color to lime green for low spread
      spreadColor = VALUE_POSITIVE_COLOR;
   }
   //--- Check if spread is medium (moderate)
   else if (spread <= 5.0) {
      //--- Set color to gold for medium spread
      spreadColor = VALUE_ACTIVE_COLOR;
   }
   //--- Spread is high (costly)
   else {
      //--- Set color to orange for high spread
      spreadColor = VALUE_NEGATIVE_COLOR;
   }

   //--- Account Information Section
   //--- Create section title
   CreateTextLabel("SectionAccount", labelXLeft, sectionY, "Account Information", SECTION_TITLE_COLOR, FONT_SIZE_SECTION_TITLE, "Arial Bold");
   //--- Move to next row
   sectionY += rowHeight + 5;
   //--- Create account number label
   CreateTextLabel("AccountNumberLabel", labelXLeft, sectionY, "Account:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create account number value
   CreateTextLabel("AccountNumberValue", valueXRight, sectionY, DoubleToString(AccountInfoInteger(ACCOUNT_LOGIN), 0), TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create account name label
   CreateTextLabel("AccountNameLabel", labelXLeft, sectionY, "Name:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create account name value
   CreateTextLabel("AccountNameValue", valueXRight, sectionY, AccountInfoString(ACCOUNT_NAME), TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create leverage label
   CreateTextLabel("LeverageLabel", labelXLeft, sectionY, "Leverage:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create leverage value
   CreateTextLabel("LeverageValue", valueXRight, sectionY, "1:" + DoubleToString(AccountInfoInteger(ACCOUNT_LEVERAGE), 0), TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;

   //--- Market Information Section
   //--- Create section title
   CreateTextLabel("SectionMarket", labelXLeft, sectionY, "Market Information", SECTION_TITLE_COLOR, FONT_SIZE_SECTION_TITLE, "Arial Bold");
   //--- Move to next row
   sectionY += rowHeight + 5;
   //--- Create spread label
   CreateTextLabel("SpreadLabel", labelXLeft, sectionY, "Spread:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create spread value with dynamic color
   CreateTextLabel("SpreadValue", valueXRight, sectionY, spreadDisplay, spreadColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;

   //--- Trading Statistics Section
   //--- Create section title
   CreateTextLabel("SectionTrading", labelXLeft, sectionY, "Trading Statistics", SECTION_TITLE_COLOR, FONT_SIZE_SECTION_TITLE, "Arial Bold");
   //--- Move to next row
   sectionY += rowHeight + 5;
   //--- Create balance label
   CreateTextLabel("BalanceLabel", labelXLeft, sectionY, "Balance:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create balance value with dynamic color
   CreateTextLabel("BalanceValue", valueXRight, sectionY, DoubleToString(balance, 2), balanceColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create equity label
   CreateTextLabel("EquityLabel", labelXLeft, sectionY, "Equity:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create equity value with dynamic color
   CreateTextLabel("EquityValue", valueXRight, sectionY, DoubleToString(equity, 2), equityColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create profit label
   CreateTextLabel("ProfitLabel", labelXLeft, sectionY, "Profit:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create profit value with dynamic color
   CreateTextLabel("ProfitValue", valueXRight, sectionY, DoubleToString(profit, 2), profitColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create open orders label
   CreateTextLabel("OrdersLabel", labelXLeft, sectionY, "Open Orders:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create open orders value with dynamic color
   CreateTextLabel("OrdersValue", valueXRight, sectionY, IntegerToString(activeOrders), ordersColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;
   //--- Create drawdown active label
   CreateTextLabel("DrawdownLabel", labelXLeft, sectionY, "Drawdown Active:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create drawdown active value with dynamic color
   CreateTextLabel("DrawdownValue", valueXRight, sectionY, tradingEnabled ? "No" : "Yes", drawdownColor, FONT_SIZE_METRIC);
   //--- Move to next row
   sectionY += rowHeight;

   //--- Active Lot Sizes
   //--- Create active lots label
   CreateTextLabel("ActiveLotsLabel", labelXLeft, sectionY, "Active Lots:", TEXT_COLOR, FONT_SIZE_METRIC);
   //--- Create active lots value with dynamic color
   CreateTextLabel("ActiveLotsValue", valueXRight, sectionY, GetActiveLotSizes(), lotsColor, FONT_SIZE_METRIC);

   //--- Redraw the chart to update display
   ChartRedraw(0);
}

Hier definieren wir die Funktion „UpdateDashboard“, um ein dynamisches Dashboard für Echtzeit-Handelsmetriken zu erstellen, das eine umfassende Schnittstelle für die Leistungsüberwachung bietet. Wir beginnen mit der Überprüfung der Variable „dashboardVisible“, um sicherzustellen, dass Aktualisierungen nur erfolgen, wenn das Dashboard aktiv ist, und um unnötige Verarbeitung zu vermeiden. Wir verwenden die Funktion „CreateRectangle“, um das Haupt-Dashboard-Panel und die Kopfzeile zu zeichnen, wobei wir die Abmessungen mit „DASHBOARD_WIDTH“ und „HEADER_HEIGHT“ festlegen und die Anwendung von Farben wie „BACKGROUND_COLOR“ und „HEADER_NORMAL_COLOR“ oder „HEADER_HOVER_COLOR“ auf der Grundlage des „headerHovered“-Status für visuelles Feedback.

Wir verwenden die Funktion „CreateTextLabel“, um wichtige Kennzahlen wie Kontostand, Kapital, Gewinn, Spread, offene Aufträge, Drawdown-Status und aktive Losgrößen anzuzeigen und sie in Abschnitte wie Kontoinformationen, Marktinformationen und Handelsstatistiken zu gliedern. Wir berechnen den Spread mit Hilfe der Funktion SymbolInfoDouble, um die Geld- und Briefkurse abzurufen, und wenden eine bedingte Farbkodierung mit „VALUE_POSITIVE_COLOR“ für niedrige Spreads (≤ 2,0 Punkte), „VALUE_ACTIVE_COLOR“ für mittlere Spreads (2,1-5,0 Punkte) und „VALUE_NEGATIVE_COLOR“ für hohe Spreads (> 5,0 Punkte) an. Sie können dies auf Ihre bevorzugten Bereiche ändern. Dann verwenden wir AccountInfoDouble, um den Saldo und das Kapital abzurufen, und „CalculateTotalProfit“, um den nicht realisierten Gewinn zu ermitteln, wobei wir Farben wie „profitColor“ auf der Grundlage des Gewinnwerts für eine intuitive Überwachung zuweisen.

Wir integrieren die Funktion „CreateButton“, um eine interaktive Schließen-Schaltfläche hinzuzufügen, die mit „CLOSE_BUTTON_WIDTH“, „FONT_SIZE_BUTTON“ und dynamischen Farben („CLOSE_BUTTON_NORMAL_BG“ oder „CLOSE_BUTTON_HOVER_BG“) auf der Grundlage von „closeButtonHovered“ gestaltet ist und die Nutzerinteraktion verbessert. Wir rufen die Funktion „GetActiveLotSizes“ auf, um bis zu drei Losgrößen in aufsteigender Reihenfolge anzuzeigen, wobei wir „lotsColor“ zur visuellen Unterscheidung verwenden, und wir verwalten das Layout mit Variablen wie „sectionY“, „labelXLeft“ und „valueXRight“ zur präzisen Positionierung. Wir schließen die Aktualisierungen mit der Funktion ChartRedraw ab, um ein reibungsloses Rendering zu gewährleisten. Wir können diese Funktion nun in OnInit aufrufen, um die erste Darstellung zu erstellen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   //--- Calculate pip value based on symbol digits (3 or 5 digits: multiply by 10)
   pipValue = (_Digits == 3 || _Digits == 5) ? 10.0 * Point() : Point();
   //--- Set the magic number for trade operations
   obj_Trade.SetExpertMagicNumber(MAGIC);
   //--- Initialize dashboard visibility
   dashboardVisible = true;
   //--- Set initial X-coordinate of the dashboard
   panelStartX = PANEL_X;
   //--- Set initial Y-coordinate of the dashboard
   panelStartY = PANEL_Y;
   //--- Initialize the dashboard display
   UpdateDashboard();
   //--- Enable mouse move events for dragging and hovering
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
   //--- Return successful initialization
   return INIT_SUCCEEDED;
}

In OnInit berechnen wir „pipValue“ mit der Funktion Point, setzen „obj_Trade“ mit „SetExpertMagicNumber“ für „MAGIC“ und aktivieren „dashboardVisible“. Wir positionieren das Dashboard mit „panelStartX“ und „panelStartY“ mit „PANEL_X“ und „PANEL_Y“, rufen die Funktion „UpdateDashboard“ auf, um es anzuzeigen, und verwenden ChartSetInteger für Mausinteraktionen und geben INIT_SUCCEEDED zurück. In OnDeInit löschen wir die erstellten Objekte wie folgt.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   //--- Remove all graphical objects from the chart
   ObjectsDeleteAll(0);
   //--- Disable mouse move events
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
}

Wir verwenden einfach die Funktion ObjectsDeleteAll, um alle Chart-Objekte zu löschen, da wir sie nicht mehr benötigen. Nach der Zusammenstellung ergibt sich folgendes Bild.

ANFANGSPANEL

Um das Panel unhandlich zu machen, rufen wir die Ereignisbehandlung durch OnChartEvent auf, um Hover- und Dragging-Effekte zu behandeln. Hier ist die Logik, die wir implementieren, damit das funktioniert.

//+------------------------------------------------------------------+
//| Expert chart event handler                                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   //--- Exit if dashboard is not visible
   if (!dashboardVisible) return;

   //--- Handle mouse click events
   if (id == CHARTEVENT_CLICK) {
      //--- Get X-coordinate of the click
      int x = (int)lparam;
      //--- Get Y-coordinate of the click
      int y = (int)dparam;
      //--- Calculate close button X-position
      int buttonX = panelStartX + DASHBOARD_WIDTH - CLOSE_BUTTON_WIDTH - 5;
      //--- Calculate close button Y-position
      int buttonY = panelStartY + 1;
      //--- Check if click is within close button bounds
      if (x >= buttonX && x <= buttonX + CLOSE_BUTTON_WIDTH && y >= buttonY && y <= buttonY + CLOSE_BUTTON_HEIGHT) {
         //--- Hide the dashboard
         dashboardVisible = false;
         //--- Remove all graphical objects
         ObjectsDeleteAll(0);
         //--- Redraw the chart
         ChartRedraw(0);
      }
   }

   //--- Handle mouse move events
   if (id == CHARTEVENT_MOUSE_MOVE) {
      //--- Get X-coordinate of the mouse
      int mouseX = (int)lparam;
      //--- Get Y-coordinate of the mouse
      int mouseY = (int)dparam;
      //--- Get mouse state (e.g., button pressed)
      int mouseState = (int)sparam;

      //--- Update close button hover state
      //--- Calculate close button X-position
      int buttonX = panelStartX + DASHBOARD_WIDTH - CLOSE_BUTTON_WIDTH - 5;
      //--- Calculate close button Y-position
      int buttonY = panelStartY + 1;
      //--- Check if mouse is over the close button
      bool isCloseHovered = (mouseX >= buttonX && mouseX <= buttonX + CLOSE_BUTTON_WIDTH && mouseY >= buttonY && mouseY <= buttonY + CLOSE_BUTTON_HEIGHT);
      //--- Update close button hover state if changed
      if (isCloseHovered != closeButtonHovered) {
         //--- Set new hover state
         closeButtonHovered = isCloseHovered;
         //--- Update close button background color
         ObjectSetInteger(0, "CloseButton", OBJPROP_BGCOLOR, isCloseHovered ? CLOSE_BUTTON_HOVER_BG : CLOSE_BUTTON_NORMAL_BG);
         //--- Update close button border color
         ObjectSetInteger(0, "CloseButton", OBJPROP_BORDER_COLOR, isCloseHovered ? CLOSE_BUTTON_HOVER_BORDER : CLOSE_BUTTON_NORMAL_BORDER);
         //--- Redraw the chart
         ChartRedraw(0);
      }

      //--- Update header hover state
      //--- Set header X-position
      int headerX = panelStartX;
      //--- Set header Y-position
      int headerY = panelStartY;
      //--- Check if mouse is over the header
      bool isHeaderHovered = (mouseX >= headerX && mouseX <= headerX + DASHBOARD_WIDTH && mouseY >= headerY && mouseY <= headerY + HEADER_HEIGHT);
      //--- Update header hover state if changed
      if (isHeaderHovered != headerHovered) {
         //--- Set new hover state
         headerHovered = isHeaderHovered;
         //--- Update header background color
         ObjectSetInteger(0, "Header", OBJPROP_BGCOLOR, isHeaderHovered ? HEADER_HOVER_COLOR : HEADER_NORMAL_COLOR);
         //--- Redraw the chart
         ChartRedraw(0);
      }

      //--- Handle panel dragging
      //--- Store previous mouse state for click detection
      static int prevMouseState = 0;
      //--- Check for mouse button press (start dragging)
      if (prevMouseState == 0 && mouseState == 1) {
         //--- Check if header is hovered to initiate dragging
         if (isHeaderHovered) {
            //--- Enable dragging mode
            panelDragging = true;
            //--- Store initial mouse X-coordinate
            panelDragX = mouseX;
            //--- Store initial mouse Y-coordinate
            panelDragY = mouseY;
            //--- Get current dashboard X-position
            panelStartX = (int)ObjectGetInteger(0, "Dashboard", OBJPROP_XDISTANCE);
            //--- Get current dashboard Y-position
            panelStartY = (int)ObjectGetInteger(0, "Dashboard", OBJPROP_YDISTANCE);
            //--- Disable chart scrolling during dragging
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
         }
      }

      //--- Update dashboard position during dragging
      if (panelDragging && mouseState == 1) {
         //--- Calculate X movement delta
         int dx = mouseX - panelDragX;
         //--- Calculate Y movement delta
         int dy = mouseY - panelDragY;
         //--- Update dashboard X-position
         panelStartX += dx;
         //--- Update dashboard Y-position
         panelStartY += dy;
         //--- Refresh the dashboard with new position
         UpdateDashboard();
         //--- Update stored mouse X-coordinate
         panelDragX = mouseX;
         //--- Update stored mouse Y-coordinate
         panelDragY = mouseY;
         //--- Redraw the chart
         ChartRedraw(0);
      }

      //--- Stop dragging when mouse button is released
      if (mouseState == 0) {
         //--- Check if dragging is active
         if (panelDragging) {
            //--- Disable dragging mode
            panelDragging = false;
            //--- Re-enable chart scrolling
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
         }
      }

      //--- Update previous mouse state
      prevMouseState = mouseState;
   }
}

In OnChartEvent wird zunächst die Variable „dashboardVisible“ überprüft, um sicherzustellen, dass die Ereignisverarbeitung nur dann erfolgt, wenn das Dashboard aktiv ist, und um die Leistung zu optimieren, indem unnötige Aktualisierungen übersprungen werden. Bei Mausklick-Ereignissen berechnen wir die Position der Schließtaste mithilfe von „panelStartX“, „DASHBOARD_WIDTH“, „CLOSE_BUTTON_WIDTH“ und „panelStartY“, und wenn ein Klick innerhalb dieser Grenzen liegt, setzen wir „dashboardVisible“ auf false, rufen die Funktion ObjectsDeleteAll auf, um alle grafischen Objekte zu entfernen, und verwenden die Funktion ChartRedraw, um das Chart zu aktualisieren, wodurch das Dashboard effektiv ausgeblendet wird.

Für Mausbewegungsereignisse verfolgen wir die Position des Cursors mit „mouseX“ und „mouseY“ und überwachen „mouseState“, um Aktionen wie Klicken oder Loslassen zu erkennen. Wir aktualisieren den Schwebezustand der Schließen-Schaltfläche, indem wir „mouseX“ und „mouseY“ mit ihren Koordinaten vergleichen, „closeButtonHovered“ setzen setzen und die Funktion ObjectSetInteger verwenden, um OBJPROP_BGCOLOR und „OBJPROP_BORDER_COLOR“ auf „CLOSE_BUTTON_HOVER_BG“ oder „CLOSE_BUTTON_NORMAL_BG“ für visuelles Feedback anzupassen. In ähnlicher Weise verwalten wir den Schwebezustand der Kopfzeile mit „headerHovered“ und wenden „HEADER_HOVER_COLOR“ oder „HEADER_NORMAL_COLOR“ über „ObjectSetInteger“ an, um die Interaktivität zu verbessern.

Um das Ziehen des Dashboards zu ermöglichen, verwenden wir einen statischen „prevMouseState“, um das Drücken der Maustaste zu erkennen und den Ziehmodus mit „panelDragging“ zu starten, wenn der Mauszeiger über der Kopfzeile schwebt, und speichern die Anfangskoordinaten in „panelDragX“ und „panelDragY“. Wir rufen die aktuelle Position des Dashboards mit der Funktion ObjectGetInteger für „OBJPROP_XDISTANCE“ und OBJPROP_YDISTANCE ab, deaktivieren das Scrollen des Charts mit der Funktion ChartSetInteger und aktualisieren „panelStartX“ und „panelStartY“ auf der Grundlage der Mausbewegungsdeltas, indem wir die Funktion „UpdateDashboard“ aufrufen, um das Dashboard in Echtzeit neu zu positionieren. Wenn die Maustaste losgelassen wird, setzen wir „panelDragging“ zurück und aktivieren das Scrollen mit „ChartSetInteger“ erneut. Jede Aktualisierung wird mit ChartRedraw abgeschlossen, um eine nahtlose Nutzererfahrung zu gewährleisten. Nach der Zusammenstellung ergibt sich folgendes Bild.

DASHBOARD UI GIF

Anhand der Visualisierung können wir sehen, dass wir mit dem Mauszeiger über die Schaltflächen ziehen, die Metriken aktualisieren und das gesamte Dashboard schließen können. Wir können nun zum kritischen Teil übergehen, nämlich der Eröffnung und Verwaltung der Positionen, und das Dashboard wird uns die Arbeit erleichtern, indem es den Fortschritt dynamisch visualisiert. Um dies zu erreichen, benötigen wir einige Hilfsfunktionen, um die Losgrößen und mehr zu berechnen (siehe unten).

//+------------------------------------------------------------------+
//| Calculate the lot size for a new trade                           |
//+------------------------------------------------------------------+
double CalculateLotSize(ENUM_POSITION_TYPE tradeType) {
   //--- Initialize lot size
   double lotSize = 0;
   //--- Select lot size calculation mode
   switch (LOT_MODE) {
      //--- Fixed lot size mode
      case 0:
         //--- Use base lot size
         lotSize = BASE_LOT;
         break;
      //--- Multiplier-based lot size mode
      case 1:
         //--- Calculate lot size with multiplier based on active orders
         lotSize = NormalizeDouble(BASE_LOT * MathPow(LOT_MULTIPLIER, activeOrders), LOT_PRECISION);
         break;
      //--- Fixed lot size with multiplier on loss mode
      case 2: {
         //--- Initialize last close time
         datetime lastClose = 0;
         //--- Set default lot size
         lotSize = BASE_LOT;
         //--- Select trade history for the last 24 hours
         HistorySelect(TimeCurrent() - 24 * 60 * 60, TimeCurrent());
         //--- Iterate through trade history
         for (int i = HistoryDealsTotal() - 1; i >= 0; i--) {
            //--- Get deal ticket
            ulong ticket = HistoryDealGetTicket(i);
            //--- Select deal by ticket
            if (HistoryDealSelect(ticket) && HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT &&
                HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol && HistoryDealGetInteger(ticket, DEAL_MAGIC) == MAGIC) {
               //--- Check if deal is more recent
               if (lastClose < HistoryDealGetInteger(ticket, DEAL_TIME)) {
                  //--- Update last close time
                  lastClose = (int)HistoryDealGetInteger(ticket, DEAL_TIME);
                  //--- Check if deal resulted in a loss
                  if (HistoryDealGetDouble(ticket, DEAL_PROFIT) < 0) {
                     //--- Increase lot size by multiplier
                     lotSize = NormalizeDouble(HistoryDealGetDouble(ticket, DEAL_VOLUME) * LOT_MULTIPLIER, LOT_PRECISION);
                  } else {
                     //--- Reset to base lot size
                     lotSize = BASE_LOT;
                  }
               }
            }
         }
         break;
      }
   }
   //--- Return calculated lot size
   return lotSize;
}

//+------------------------------------------------------------------+
//| Count the number of active orders                                |
//+------------------------------------------------------------------+
int CountActiveOrders() {
   //--- Initialize order counter
   int count = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Check if position is buy or sell
         if (GetPositionType() == POSITION_TYPE_BUY || GetPositionType() == POSITION_TYPE_SELL) {
            //--- Increment order counter
            count++;
         }
      }
   }
   //--- Return total active orders
   return count;
}

//+------------------------------------------------------------------+
//| Return a formatted string of active lot sizes in ascending order |
//+------------------------------------------------------------------+
string GetActiveLotSizes() {
   //--- Check if no active orders
   if (activeOrders == 0) {
      //--- Return waiting message
      return "[Waiting]";
   }
   //--- Initialize array for lot sizes
   double lotSizes[];
   //--- Resize array to match active orders
   ArrayResize(lotSizes, activeOrders);
   //--- Initialize counter
   int count = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0 && count < activeOrders; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Store position volume (lot size)
         lotSizes[count] = PositionGetDouble(POSITION_VOLUME);
         //--- Increment counter
         count++;
      }
   }
   //--- Sort lot sizes in ascending order
   ArraySort(lotSizes);
   //--- Initialize result string
   string result = "";
   //--- Determine maximum number of lots to display (up to 3)
   int maxDisplay = (activeOrders > 3) ? 3 : activeOrders;
   //--- Format lot sizes
   for (int i = 0; i < maxDisplay; i++) {
      //--- Add comma and space for subsequent entries
      if (i > 0) result += ", ";
      //--- Convert lot size to string with specified precision
      result += DoubleToString(lotSizes[i], LOT_PRECISION);
   }
   //--- Append ellipsis if more than 3 orders
   if (activeOrders > 3) result += ", ...";
   //--- Return formatted lot sizes in brackets
   return "[" + result + "]";
}

Hier definieren wir die Funktionen „CalculateLotSize“, „CountActiveOrders“ und „GetActiveLotSizes“, die bei der Handelsgrößenbestimmung, der Positionsverfolgung und der Dashboard-Aktualisierung helfen und eine präzise Ausführung sowie eine umfassende Echtzeitüberwachung gewährleisten.

Wir implementieren die Funktion „CalculateLotSize“ zur Bestimmung des Handelsvolumens auf der Grundlage der Eingabe „LOT_MODE“ und unterstützen drei Modi: Feste Losgröße durch direkte Rückgabe von „BASE_LOT“, Martingale-Skalierung durch Anwendung der Funktion MathPow mit „LOT_MULTIPLIER“ und „activeOrders“, um die Losgröße exponentiell zu erhöhen, oder verlustbasierte Anpassungen, bei denen wir die HistorySelect-Funktion verwenden, um die letzten 24 Stunden der Trades zu überprüfen.

Im verlustbasierten Modus iterieren wir mit HistoryDealsTotal, rufen die Deal-Details über HistoryDealGetTicket und HistoryDealGetDouble ab und passen „lotSize“ mit NormalizeDouble an, wenn das letzte Handelsgeschäft ein Verlust war, und setzen es andernfalls auf „BASE_LOT“ zurück, um präzise, auf die Handelsergebnisse zugeschnittene Lot-Berechnungen zu gewährleisten.

Wir verwenden die Funktion „CountActiveOrders“, um eine genaue Zählung der offenen Positionen zu erhalten, die für die Martingale-Skalierung und die Genauigkeit des Dashboards entscheidend ist. Wir durchlaufen alle Positionen mit der Funktion PositionsTotal, wählen jede mit PositionGetTicket aus und überprüfen die Relevanz mit den Funktionen „GetPositionSymbol“ und „GetPositionMagic“, wobei wir die Variable „count“ für Kauf- oder Verkaufspositionen, die durch „GetPositionType“ identifiziert wurden, inkrementieren und so „activeOrders“ zuverlässig aktualisieren.

Darüber hinaus haben wir die Funktion „GetActiveLotSizes“ entwickelt, mit der die Losgrößen auf dem Dashboard formatiert und angezeigt werden können, sodass der Nutzer einen besseren Überblick über die aktiven Trades erhält. Wir überprüfen „activeOrders“ und geben „[Waiting]“ zurück, wenn keine vorhanden sind, andernfalls initialisieren wir ein Array mit ArrayResize, um die Losgrößen zu speichern. Wir iterieren die Positionen mit PositionsTotal, wählen sie mit „PositionGetTicket“ aus und verwenden PositionGetDouble, um die Volumina in „LotSizes“ zu sammeln und sortieren sie mit der Funktion ArraySort in aufsteigender Reihenfolge. Wir formatieren bis zu drei Lots mit DoubleToString mit „LOT_PRECISION“, fügen Kommas und eine Ellipse für mehr als drei Orders hinzu und geben das Ergebnis in Klammern zurück, um eine klare, professionelle Anzeige der Handelsvolumina für die Echtzeitüberwachung zu ermöglichen. Dennoch müssen wir Funktionen definieren, die uns bei der Auftragsvergabe helfen, wie unten dargestellt.

//+------------------------------------------------------------------+
//| Place a new trade order                                          |
//+------------------------------------------------------------------+
int PlaceOrder(ENUM_ORDER_TYPE orderType, double lot, double price, double slPrice, int gridLevel) {
   //--- Calculate stop-loss price
   double sl = CalculateSL(slPrice, STOP_LOSS_PIPS);
   //--- Calculate take-profit price
   double tp = CalculateTP(price, TAKE_PROFIT_PIPS, orderType);
   //--- Initialize ticket number
   int ticket = 0;
   //--- Set maximum retry attempts
   int retries = 100;
   //--- Create trade comment with grid level
   string comment = "GridMart Scalper-" + IntegerToString(gridLevel);

   //--- Attempt to place order with retries
   for (int i = 0; i < retries; i++) {
      //--- Open position with specified parameters
      ticket = obj_Trade.PositionOpen(_Symbol, orderType, lot, price, sl, tp, comment);
      //--- Get last error code
      int error = GetLastError();
      //--- Exit loop if order is successful
      if (error == 0) break;
      //--- Check for retryable errors (server busy, trade context busy, etc.)
      if (!(error == 4 || error == 137 || error == 146 || error == 136)) break;
      //--- Wait before retrying
      Sleep(5000);
   }
   //--- Return order ticket (or error code if negative)
   return ticket;
}

//+------------------------------------------------------------------+
//| Calculate the stop-loss price for a trade                        |
//+------------------------------------------------------------------+
double CalculateSL(double price, int points) {
   //--- Check if stop-loss is disabled
   if (points == 0) return 0;
   //--- Calculate stop-loss price (subtract points from price)
   return price - points * pipValue;
}

//+------------------------------------------------------------------+
//| Calculate the take-profit price for a trade                      |
//+------------------------------------------------------------------+
double CalculateTP(double price, int points, ENUM_ORDER_TYPE orderType) {
   //--- Check if take-profit is disabled
   if (points == 0) return 0;
   //--- Calculate take-profit for buy order (add points)
   if (orderType == ORDER_TYPE_BUY) return price + points * pipValue;
   //--- Calculate take-profit for sell order (subtract points)
   return price - points * pipValue;
}

//+------------------------------------------------------------------+
//| Retrieve the latest buy order price                              |
//+------------------------------------------------------------------+
double GetLatestBuyPrice() {
   //--- Initialize price
   double price = 0;
   //--- Initialize latest ticket
   int latestTicket = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC && GetPositionType() == POSITION_TYPE_BUY) {
         //--- Check if ticket is more recent
         if ((int)ticket > latestTicket) {
            //--- Update price
            price = GetPositionOpenPrice();
            //--- Update latest ticket
            latestTicket = (int)ticket;
         }
      }
   }
   //--- Return latest buy price
   return price;
}

//+------------------------------------------------------------------+
//| Retrieve the latest sell order price                             |
//+------------------------------------------------------------------+
double GetLatestSellPrice() {
   //--- Initialize price
   double price = 0;
   //--- Initialize latest ticket
   int latestTicket = 0;
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC && GetPositionType() == POSITION_TYPE_SELL) {
         //--- Check if ticket is more recent
         if ((int)ticket > latestTicket) {
            //--- Update price
            price = GetPositionOpenPrice();
            //--- Update latest ticket
            latestTicket = (int)ticket;
         }
      }
   }
   //--- Return latest sell price
   return price;
}

//+------------------------------------------------------------------+
//| Check if trading is allowed                                      |
//+------------------------------------------------------------------+
int IsTradingAllowed() {
   //--- Always allow trading
   return 1;
}

Hier implementieren wir die Funktion „PlaceOrder“, um Handelsgeschäfte zu initiieren, und verwenden die Funktion „PositionOpen“ von „obj_Trade“, um Positionen mit Parametern wie „Lot“, „Price“ und einem mit IntegerToString formatierten Kommentar für „GridLevel“ zu öffnen, wobei wir eine Wiederholungslogik mit GetLastError und Sleep für eine stabile Ausführung einbauen. Wir verwenden die Funktion „CalculateSL“, um Stop-Loss-Kurse zu berechnen, indem wir „STOP_LOSS_PIPS“ multipliziert mit „pipValue“ von „slPrice“ abziehen.und die Funktion „CalculateTP“, um Take-Profit-Levels festzulegen, indem „TAKE_PROFIT_PIPS“ auf der Grundlage von „orderType“ für Kauf- bzw. Verkaufstransaktionen addiert bzw. subtrahiert wird.

Wir entwickeln die Funktion „GetLatestBuyPrice“, um den Preis der letzten Kaufposition zu ermitteln, iterieren mit PositionsTotal, wählen Positionen über PositionGetTicket aus und verifizieren mit „GetPositionSymbol“, „GetPositionMagic“ und „GetPositionType“ und aktualisieren den „Preis“ mit „GetPositionOpenPrice“ für das höchste „Ticket“.

In ähnlicher Weise implementieren wir die Funktion „GetLatestSellPrice“, um den Preis der letzten Verkaufsposition abzurufen, und folgen dabei der gleichen Logik, um eine genaue Platzierung im Raster zu gewährleisten. Wir definieren die Funktion „IsTradingAllowed“ so, dass sie einen konstanten Wert von 1 zurückgibt, was einen kontinuierlichen Handel ohne Einschränkungen ermöglicht, was den Hochfrequenzansatz der Strategie unterstützt. Wir können nun diese Funktionen verwenden, um die eigentliche Handelslogik in OnTick zu definieren.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   //--- Get current ask price
   double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
   //--- Get current bid price
   double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);

   //--- Update dashboard if visible
   if (dashboardVisible) UpdateDashboard();

   //--- Check if trading is allowed
   if (IsTradingAllowed()) {
      //--- Get current bar time
      datetime currentBarTime = iTime(_Symbol, _Period, 0);
      //--- Exit if the bar hasn’t changed
      if (lastBarTime == currentBarTime) return;
      //--- Update last bar time
      lastBarTime = currentBarTime;
      //--- Count active orders
      activeOrders = CountActiveOrders();
      //--- Reset SL/TP update flag if no active orders
      if (activeOrders == 0) updateSLTP = false;

      //--- Reset position flags
      hasBuyPosition = false;
      hasSellPosition = false;
      //--- Iterate through open positions
      for (int i = PositionsTotal() - 1; i >= 0; i--) {
         //--- Get position ticket
         ulong ticket = PositionGetTicket(i);
         //--- Select position by ticket
         if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
            //--- Check for buy position
            if (GetPositionType() == POSITION_TYPE_BUY) {
               //--- Set buy position flag
               hasBuyPosition = true;
               //--- Clear sell position flag
               hasSellPosition = false;
               //--- Exit loop after finding a position
               break;
            }
            //--- Check for sell position
            else if (GetPositionType() == POSITION_TYPE_SELL) {
               //--- Set sell position flag
               hasSellPosition = true;
               //--- Clear buy position flag
               hasBuyPosition = false;
               //--- Exit loop after finding a position
               break;
            }
         }
      }

      //--- Check conditions to open new trades
      if (activeOrders > 0 && activeOrders <= MAX_GRID_LEVELS) {
         //--- Get latest buy price
         latestBuyPrice = GetLatestBuyPrice();
         //--- Get latest sell price
         latestSellPrice = GetLatestSellPrice();
         //--- Check if a new buy trade is needed
         if (hasBuyPosition && latestBuyPrice - ask >= GRID_DISTANCE * pipValue) openNewTrade = true;
         //--- Check if a new sell trade is needed
         if (hasSellPosition && bid - latestSellPrice >= GRID_DISTANCE * pipValue) openNewTrade = true;
      }
      //--- Allow new trades if no active orders
      if (activeOrders < 1) {
         //--- Clear position flags
         hasBuyPosition = false;
         hasSellPosition = false;
         //--- Signal to open a new trade
         openNewTrade = true;
      }

      //--- Execute new trade if signaled
      if (openNewTrade) {
         //--- Update latest buy price
         latestBuyPrice = GetLatestBuyPrice();
         //--- Update latest sell price
         latestSellPrice = GetLatestSellPrice();
         //--- Handle sell position
         if (hasSellPosition) {
            //--- Calculate lot size for sell order
            calculatedLot = CalculateLotSize(POSITION_TYPE_SELL);
            //--- Check if lot size is valid and trading is enabled
            if (calculatedLot > 0 && tradingEnabled) {
               //--- Place sell order
               int ticket = PlaceOrder(ORDER_TYPE_SELL, calculatedLot, bid, ask, activeOrders);
               //--- Check for order placement errors
               if (ticket < 0) {
                  //--- Log error message
                  Print("Sell Order Error: ", GetLastError());
                  return;
               }
               //--- Update latest sell price
               latestSellPrice = GetLatestSellPrice();
               //--- Clear new trade signal
               openNewTrade = false;
               //--- Signal to modify positions
               modifyPositions = true;
            }
         }
         //--- Handle buy position
         else if (hasBuyPosition) {
            //--- Calculate lot size for buy order
            calculatedLot = CalculateLotSize(POSITION_TYPE_BUY);
            //--- Check if lot size is valid and trading is enabled
            if (calculatedLot > 0 && tradingEnabled) {
               //--- Place buy order
               int ticket = PlaceOrder(ORDER_TYPE_BUY, calculatedLot, ask, bid, activeOrders);
               //--- Check for order placement errors
               if (ticket < 0) {
                  //--- Log error message
                  Print("Buy Order Error: ", GetLastError());
                  return;
               }
               //--- Update latest buy price
               latestBuyPrice = GetLatestBuyPrice();
               //--- Clear new trade signal
               openNewTrade = false;
               //--- Signal to modify positions
               modifyPositions = true;
            }
         }

         //---

      }
   }
}

Wir definieren in OnTick die zentrale Handelslogik des Programms, die die Handelsausführung in Echtzeit und das Positionsmanagement orchestriert. Zunächst werden die aktuellen Marktpreise mit der Funktion SymbolInfoDouble abgerufen, um die mit NormalizeDouble normalisierten „Ask“- und „Bid“-Werte zu erfassen, und das Dashboard wird mit der Funktion „UpdateDashboard“ aktualisiert, wenn „dashboardVisible“ wahr ist. Wir überprüfen die Handelsberechtigungen mit der Funktion „IsTradingAllowed“, um sicherzustellen, dass nur dann gehandelt wird, wenn die Bedingungen es zulassen, und verwenden die Funktion iTime, um den Zeitstempel des aktuellen Balkens abzurufen und ihn in „currentBarTime“ zu speichern, um eine redundante Verarbeitung durch den Vergleich mit „lastBarTime“ zu vermeiden.

Wir verwalten die Positionsverfolgung, indem wir die Funktion „CountActiveOrders“ aufrufen, um „activeOrders“ zu aktualisieren, „updateSLTP“ zurücksetzen, wenn keine Aufträge vorhanden sind, und Positionen mit PositionsTotal und PositionGetTicket iterieren, um „hasBuyPosition“ oder „hasSellPosition“ auf der Grundlage von „GetPositionType“ zu setzen.

Wir werten die Rasterbedingungen mit „GetLatestBuyPrice“ und „GetLatestSellPrice“ aus und lösen „openNewTrade“ aus, wenn die Preisbewegungen „GRID_DISTANCE“ mal „pipValue“ überschreiten oder wenn „activeOrders“ unter „MAX_GRID_LEVELS“ liegt. Wenn „openNewTrade“ wahr ist, berechnen wir die Losgrößen mit der Funktion „CalculateLotSize“, führen Handelsgeschäfte über die Funktion „PlaceOrder“ für Kauf- oder Verkaufsaufträge aus, protokollieren Fehler mit „Print“ und GetLastError, wenn die Platzierung fehlschlägt, und aktualisieren „latestBuyPrice“, „latestSellPrice“ und „modifyPositions“, um laufende Handelsgeschäfte zu verwalten und präzise und effiziente Scalping-Operationen zu gewährleisten. Um neue Positionen zu eröffnen, verwenden wir die folgende Logik zur Signalerzeugung.

//--- Check conditions to open a new trade without existing positions
MqlDateTime timeStruct;
//--- Get current time
TimeCurrent(timeStruct);
//--- Verify trading hours, cycle limit, and new trade conditions
if (timeStruct.hour >= START_HOUR && timeStruct.hour < END_HOUR && cycleCount < MAX_CYCLES && CONTINUE_TRADING && openNewTrade && activeOrders < 1) {
   //--- Get previous bar close price
   double closePrev = iClose(_Symbol, PERIOD_CURRENT, 2);
   //--- Get current bar close price
   double closeCurrent = iClose(_Symbol, PERIOD_CURRENT, 1);
   //--- Check if no existing positions
   if (!hasSellPosition && !hasBuyPosition) {
      //--- Check for bearish signal (previous close > current close)
      if (closePrev > closeCurrent) {
         //--- Calculate lot size for sell order
         calculatedLot = CalculateLotSize(POSITION_TYPE_SELL);
         //--- Check if lot size is valid and trading is enabled
         if (calculatedLot > 0 && tradingEnabled) {
            //--- Place sell order
            int ticket = PlaceOrder(ORDER_TYPE_SELL, calculatedLot, bid, bid, activeOrders);
            //--- Check for order placement errors
            if (ticket < 0) {
               //--- Log error message
               Print("Sell Order Error: ", GetLastError());
               return;
            }
            //--- Increment cycle count
            cycleCount++;
            //--- Update latest buy price
            latestBuyPrice = GetLatestBuyPrice();
            //--- Signal to modify positions
            modifyPositions = true;
         }
      }
      //--- Check for bullish signal (previous close <= current close)
      else {
         //--- Calculate lot size for buy order
         calculatedLot = CalculateLotSize(POSITION_TYPE_BUY);
         //--- Check if lot size is valid and trading is enabled
         if (calculatedLot > 0 && tradingEnabled) {
            //--- Place buy order
            int ticket = PlaceOrder(ORDER_TYPE_BUY, calculatedLot, ask, ask, activeOrders);
            //--- Check for order placement errors
            if (ticket < 0) {
               //--- Log error message
               Print("Buy Order Error: ", GetLastError());
               return;
            }
            //--- Increment cycle count
            cycleCount++;
            //--- Update latest sell price
            latestSellPrice = GetLatestSellPrice();
            //--- Signal to modify positions
            modifyPositions = true;
         }
      }
   }
}

Um einen neuen Handel einzuleiten, verwenden wir zunächst die Funktion TimeCurrent, um die aktuelle Zeit in der Variablen „timeStruct“ abzurufen und zu prüfen, ob „timeStruct.hour“ innerhalb von „START_HOUR“ und „END_HOUR“ liegt, „cycleCount“ unter „MAX_CYCLES“ liegt und „CONTINUE_TRADING“ und „openNewTrade“ wahr sind, wobei „activeOrders“ kleiner als 1 ist. Wir holen den vorherigen und den aktuellen Schlusskurs des Balkens mit der Funktion iClose und speichern sie in „closePrev“ und „closeCurrent“ und bestätigen mit „hasSellPosition“ und „hasBuyPosition“, dass keine Positionen bestehen.

Bei einem Abwärtssignal (wenn „closePrev“ größer ist als „closeCurrent“) berechnen wir die Losgröße mit der Funktion „CalculateLotSize“ für eine Verkaufsorder, überprüfen „calculatedLot“ und „tradingEnabled“ und führen das Handelsgeschäft mit der Funktion „PlaceOrder“ aus, wobei wir bei Bedarf Fehler über „Print“ und GetLastError protokollieren. Bei einem Aufwärtssignal (wenn „closePrev“ kleiner oder gleich „closeCurrent“ ist) führen wir den gleichen Prozess für eine Kauforder durch und aktualisieren „cycleCount“ und „latestBuyPrice“ bzw. „latestSellPrice“ mit „GetLatestBuyPrice“ bzw. „GetLatestSellPrice“ aktualisiert und „modifyPositions“ auf „true“ gesetzt, was eine präzise Handelseinleitung und Positionsverwaltung ermöglicht.

Sie können dies durch jede Ihrer Handelsstrategien ersetzen. Wir haben nur eine einfache Strategie zur Signalerzeugung verwendet, da das Hauptziel darin besteht, die Positionen durch Anwendung der Strategie zu verwalten. Sobald wir Positionen eröffnen, müssen wir sie überprüfen und ändern, wenn der Preis wie unten beschrieben steigt.

//--- Update active orders count
activeOrders = CountActiveOrders();
//--- Reset weighted price and total volume
weightedPrice = 0;
totalVolume = 0;
//--- Calculate weighted price and total volume
for (int i = PositionsTotal() - 1; i >= 0; i--) {
   //--- Get position ticket
   ulong ticket = PositionGetTicket(i);
   //--- Select position by ticket
   if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
      //--- Accumulate weighted price (price * volume)
      weightedPrice += GetPositionOpenPrice() * PositionGetDouble(POSITION_VOLUME);
      //--- Accumulate total volume
      totalVolume += PositionGetDouble(POSITION_VOLUME);
   }
}

//--- Normalize weighted price if there are active orders
if (activeOrders > 0) weightedPrice = NormalizeDouble(weightedPrice / totalVolume, _Digits);
//--- Check if positions need SL/TP modification
if (modifyPositions) {
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Handle buy positions
         if (GetPositionType() == POSITION_TYPE_BUY) {
            //--- Set take-profit for buy position
            targetTP = weightedPrice + TAKE_PROFIT_PIPS * pipValue;
            //--- Set stop-loss for buy position
            targetSL = weightedPrice - STOP_LOSS_PIPS * pipValue;
            //--- Signal SL/TP update
            updateSLTP = true;
         }
         //--- Handle sell positions
         else if (GetPositionType() == POSITION_TYPE_SELL) {
            //--- Set take-profit for sell position
            targetTP = weightedPrice - TAKE_PROFIT_PIPS * pipValue;
            //--- Set stop-loss for sell position
            targetSL = weightedPrice + STOP_LOSS_PIPS * pipValue;
            //--- Signal SL/TP update
            updateSLTP = true;
         }
      }
   }
}

//--- Apply SL/TP modifications if needed
if (modifyPositions && updateSLTP) {
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (PositionSelectByTicket(ticket) && GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
         //--- Modify position with new SL/TP
         if (obj_Trade.PositionModify(ticket, targetSL, targetTP)) {
            //--- Clear modification signal on success
            modifyPositions = false;
         }
      }
   }
}

Hier aktualisieren wir „activeOrders“ mit „CountActiveOrders“, setzen „weightedPrice“ und „totalVolume“ zurück und iterieren Positionen mit „PositionsTotal“ und „PositionGetTicket“, wobei „weightedPrice“ und „totalVolume“ über die Funktionen „GetPositionOpenPrice“ und „PositionGetDouble“ akkumuliert werden. Wir normalisieren „weightedPrice“ mit NormalizeDouble, wenn „activeOrders“ existiert, setzen „targetTP“ und „targetSL“ mit „GetPositionType“ und „pipValue“ wenn „modifyPositions“ wahr ist, und wenden Aktualisierungen mit „PositionModify“ von „obj_Trade“ an, wenn „updateSLTP“, und setzen „modifyPositions“ bei Erfolg zurück. Nach der Kompilierung erhalten wir folgendes Ergebnis.

ERÖFFNET AUFTRÄGE

Aus dem Bild geht hervor, dass wir Aufträge geöffnet haben, die sich bereits im Verwaltungsmodus befinden. Jetzt müssen wir nur noch die Risikomanagementlogik anwenden, um den täglichen Drawdown zu überwachen und zu begrenzen. Zu diesem Zweck werden wir eine Funktion einrichten, die die gesamte Verfolgung übernimmt und das Programm bei Bedarf wie folgt anhält.

//+------------------------------------------------------------------+
//| Monitor daily drawdown and control trading state                 |
//+------------------------------------------------------------------+
void MonitorDailyDrawdown() {
   //--- Initialize daily profit accumulator
   double totalDayProfit = 0.0;
   //--- Get current time
   datetime end = TimeCurrent();
   //--- Get current date as string
   string sdate = TimeToString(TimeCurrent(), TIME_DATE);
   //--- Convert date to datetime (start of day)
   datetime start = StringToTime(sdate);
   //--- Set end of day (24 hours later)
   datetime to = start + (1 * 24 * 60 * 60);

   //--- Check if daily reset is needed
   if (dailyResetTime < to) {
      //--- Update reset time
      dailyResetTime = to;
      //--- Store current balance as daily starting balance
      dailyBalance = GetAccountBalance();
   }
   //--- Select trade history for the day
   HistorySelect(start, end);
   //--- Get total number of deals
   int totalDeals = HistoryDealsTotal();

   //--- Iterate through trade history
   for (int i = 0; i < totalDeals; i++) {
      //--- Get deal ticket
      ulong ticket = HistoryDealGetTicket(i);
      //--- Check if deal is a position close
      if (HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) {
         //--- Calculate deal profit (including commission and swap)
         double latestDayProfit = (HistoryDealGetDouble(ticket, DEAL_PROFIT) +
                                  HistoryDealGetDouble(ticket, DEAL_COMMISSION) +
                                  HistoryDealGetDouble(ticket, DEAL_SWAP));
         //--- Accumulate daily profit
         totalDayProfit += latestDayProfit;
      }
   }

   //--- Calculate starting balance for the day
   double startingBalance = GetAccountBalance() - totalDayProfit;
   //--- Calculate daily profit/drawdown percentage
   double dailyProfitOrDrawdown = NormalizeDouble((totalDayProfit * 100 / startingBalance), 2);

   //--- Check if drawdown limit is exceeded
   if (dailyProfitOrDrawdown <= DRAWDOWN_LIMIT) {
      //--- Close all positions if configured
      if (CLOSE_ON_DRAWDOWN) CloseAllPositions();
      //--- Disable trading
      tradingEnabled = false;
   } else {
      //--- Enable trading
      tradingEnabled = true;
   }
}

//+------------------------------------------------------------------+
//| Close all open positions managed by the EA                       |
//+------------------------------------------------------------------+
void CloseAllPositions() {
   //--- Iterate through open positions
   for (int i = PositionsTotal() - 1; i >= 0; i--) {
      //--- Get position ticket
      ulong ticket = PositionGetTicket(i);
      //--- Select position by ticket
      if (ticket > 0 && PositionSelectByTicket(ticket)) {
         //--- Check if position belongs to this EA
         if (GetPositionSymbol() == _Symbol && GetPositionMagic() == MAGIC) {
            //--- Close the position
            obj_Trade.PositionClose(ticket);
         }
      }
   }
}

Um das Risikomanagement durchzusetzen, implementieren wir die Funktion „MonitorDailyDrawdown“, um die tägliche Performance zu verfolgen. Dabei verwenden wir TimeCurrent und TimeToString, um den Zeitbereich des Tages festzulegen, und HistorySelect, um auf die Handelsgeschichte zuzugreifen. Wir berechnen den Gesamtgewinn mit den Funktionen HistoryDealGetTicket und HistoryDealGetDouble, normalisieren den Drawdown-Prozentsatz mit NormalizeDouble und passen „tradingEnabled“ basierend auf „DRAWDOWN_LIMIT“ an, indem wir die Funktion „CloseAllPositions“ aufrufen, wenn „CLOSE_ON_DRAWDOWN“ wahr ist.

Wir definieren „CloseAllPositions“, um Positionen mit „PositionsTotal“ und PositionGetTicket zu iterieren und relevante Trades mit „PositionClose“ von „obj_Trade“ zu schließen, nachdem wir sie mit „GetPositionSymbol“ und „GetPositionMagic“ verifiziert haben, was eine robuste Drawdown-Kontrolle gewährleistet. Wir können diese Funktion dann bei jedem Tick aufrufen, um das Risikomanagement durchzuführen, wenn es in Frage kommt.

//--- Monitor daily drawdown if enabled
if (ENABLE_DAILY_DRAWDOWN) MonitorDailyDrawdown();

Wir prüfen die Bedingung „ENABLE_DAILY_DRAWDOWN“, um festzustellen, ob die Drawdown-Kontrolle aktiv ist, und wenn ja, rufen wir die Funktion „MonitorDailyDrawdown“ auf, um die täglichen Gewinne und Verluste zu bewerten, „tradingEnabled“ anzupassen und möglicherweise Positionen zu schließen, um das Konto vor übermäßigen Verlusten zu schützen. Nach der Kompilierung erhalten wir folgendes Ergebnis.

MANAGEMENT DER ENDPOSITIONEN GIF

Anhand der Visualisierung können wir erkennen, dass wir die Positionen eröffnen, dynamisch verwalten und bei Erreichen der Ziele schließen können, womit wir unser Ziel, die Einrichtung der Grid-Mart-Strategie, erreicht haben. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.


Backtests

Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.

Backtest-Grafik:

GRAPH

Bericht des Backtest:

BERICHT


Schlussfolgerung

Zusammenfassend haben wir ein MQL5-Programm entwickelt, das die Grid-Mart Scalping-Strategie automatisiert, indem es Grid-basierte Martingale-Trades mit einem dynamischen Dashboard zur Echtzeit-Überwachung von Schlüsselmetriken wie Spread, Gewinn und Losgrößen ausführt. Mit präziser Handelsausführung, robustem Risikomanagement durch Drawdown-Kontrollen und einer interaktiven Schnittstelle können Sie dieses Programm weiter verbessern, indem Sie seine Parameter anpassen oder zusätzliche Strategien integrieren, die Ihren Handelsvorlieben entsprechen.

Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Volatilität der Märkte kann zu Verlusten führen. Gründliche Backtests und sorgfältiges Risikomanagement sind entscheidend, bevor Sie dieses Programm auf den Live-Märkten einsetzen.

Wenn Sie diese Techniken beherrschen, können Sie dieses Programm weiter verbessern, um es robuster zu machen, oder es als Grundgerüst für die Entwicklung anderer Handelsstrategien verwenden, um Ihren Weg im algorithmischen Handel zu stärken.

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (5)
Ahmet Parlakbilek
Ahmet Parlakbilek | 14 Mai 2025 in 21:27
Netter Artikel, aber es gibt einen erheblichen Fehler in CalculateSL.
Allan Munene Mutiiria
Allan Munene Mutiiria | 14 Mai 2025 in 22:45
Ahmet Parlakbilek #:
Netter Artikel, aber es gibt einen erheblichen Fehler in CalculateSL.

Sicher. Danke. Was ist damit los?

Ahmet Parlakbilek
Ahmet Parlakbilek | 15 Mai 2025 in 06:36
Allan Munene Mutiiria #:

Sicher. Danke. Was ist denn los mit ihm?

Sie haben die Handhabung der Verkaufsseite vergessen. Im Anhang finden Sie die korrigierte Version.

Allan Munene Mutiiria
Allan Munene Mutiiria | 15 Mai 2025 in 15:44
Ahmet Parlakbilek #:

Sie haben die Handhabung der Verkaufsseite vergessen. Im Anhang finden Sie die korrigierte Fassung.

Ooh. Ja, klar. Sicher. Das wird für andere eine große Hilfe sein. Ich danke Ihnen.

Pramendra
Pramendra | 18 Mai 2025 in 11:28
Sir, bitte reparieren.
Datenwissenschaft und ML (Teil 38): AI Transfer Learning auf den Forexmärkten Datenwissenschaft und ML (Teil 38): AI Transfer Learning auf den Forexmärkten
Die KI-Durchbrüche, die die Schlagzeilen beherrschen, von ChatGPT bis hin zu selbstfahrenden Autos, entstehen nicht durch isolierte Modelle, sondern durch kumulatives Wissen, das aus verschiedenen Modellen oder gemeinsamen Bereichen übertragen wird. Jetzt kann derselbe Ansatz "einmal lernen, überall anwenden" angewandt werden, um unsere KI-Modelle im algorithmischen Handel zu transformieren. In diesem Artikel erfahren wir, wie wir die aus verschiedenen Instrumenten gewonnenen Informationen nutzen können, um mit Hilfe von Transfer Learning die Vorhersagen für andere Instrumente zu verbessern.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 64): Verwendung von Mustern von DeMarker und Envelope-Kanälen mit dem Kernel des weißen Rauschens MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 64): Verwendung von Mustern von DeMarker und Envelope-Kanälen mit dem Kernel des weißen Rauschens
Der DeMarker-Oszillator und der Envelopes-Indikator sind Momentum- und Unterstützungs-/Widerstands-Tools, die bei der Entwicklung eines Expert Advisors kombiniert werden können. Wir knüpfen an unseren letzten Artikel an, in dem diese beiden Indikatoren vorgestellt wurden, indem wir das maschinelle Lernen in den Mix aufnehmen. Wir verwenden ein rekurrentes neuronales Netz, das den Kernel des weißen Rauschens nutzt, um die vektorisierten Signale dieser beiden Indikatoren zu verarbeiten. Dies geschieht in einer nutzerdefinierten Signalklassendatei, die mit dem MQL5-Assistenten arbeitet, um einen Expert Advisor zusammenzustellen.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 65): Verwendung von FrAMA-Mustern und des Force Index MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 65): Verwendung von FrAMA-Mustern und des Force Index
Der Fractal Adaptive Moving Average (FrAMA) und der Oszillator Force Index sind ein weiteres Paar von Indikatoren, die in Verbindung mit einem MQL5 Expert Advisor verwendet werden können. Diese beiden Indikatoren ergänzen sich ein wenig, denn der FrAMA ist ein Trendfolgeindikator, während der Force Index ein volumenbasierter Oszillator ist. Wie immer verwenden wir den MQL5-Assistenten, um das Potenzial der beiden schnell zu erkunden.
Entwicklung des Price Action Analysis Toolkit (Teil 23): Stärkemessung einer Währung Entwicklung des Price Action Analysis Toolkit (Teil 23): Stärkemessung einer Währung
Wissen Sie, was die Richtung eines Währungspaares wirklich bestimmt? Es geht um die Stärke der einzelnen Währungen. In diesem Artikel werden wir die Stärke einer Währung messen, indem wir jedes Paar, in dem sie vorkommt, in einer Schleife durchgehen. Aufgrund dieser Erkenntnisse können wir vorhersagen, wie sich diese Paare auf der Grundlage ihrer relativen Stärke entwickeln werden. Lesen Sie weiter, um mehr zu erfahren.