English Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 17): Освоение стратегии скальпинга Grid-Mart с динамической информационной панелью

Автоматизация торговых стратегий на MQL5 (Часть 17): Освоение стратегии скальпинга Grid-Mart с динамической информационной панелью

MetaTrader 5Трейдинг |
498 5
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В нашей предыдущей статье (Часть 16) мы автоматизировали Пробой полуночного диапазона с помощью стратегии Прорыв структуры для сбора данных о ценовых пробоях. Теперь, в Части 17, мы сосредоточимся на автоматизации стратегии скальпинга Grid-Mart на MetaQuotes Language 5 (MQL5), разработке советника, который исполняет сделки по мартингейлу на основе сетки и имеет динамическую информационную панель для мониторинга в режиме реального времени. В статье рассмотрим следующие темы:

  1. Изучение стратегии скальпинга Grid-Mart
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

К концу настоящей статьи у вас будет полностью функциональная программа на MQL5, которая точно скальпирует рынки и визуализирует торговые показатели — давайте погрузимся в работу!


Изучение стратегии скальпинга Grid-Mart

Стратегия скальпинга Grid-Mart использует подход мартингейла на основе сетки, размещая ордера на покупку или продажу с фиксированными ценовыми интервалами (например, 2,0 пипса) для получения небольшой прибыли от колебаний рынка и увеличения размера лотов после убытков для быстрого восстановления капитала. Она основана на высокочастотной торговле, нацеленной на скромную прибыль (например, 4 пипса) за сделку. Однако требует тщательного управления рисками из-за экспоненциального роста размера лота, который ограничен настраиваемыми лимитами, такими как максимальные уровни сетки и пороги ежедневной просадки. Данная стратегия эффективна на волатильных рынках, но требует точной настройки, чтобы избежать значительных просадок во время длительных трендов.

Наш план реализации включает в себя создание советника на MQL5 для автоматизации стратегии Grid-Mart путем расчета интервалов сетки, управления изменением размера лотов и исполнения сделок с заранее определенными уровнями стоп-лосса и тейк-профита. Программа будет оснащена динамической информационной панелью для отображения показателей в режиме реального времени, таких как спред, размеры активных лотов и статус счета, с цветовой кодировкой, которая поможет в принятии решений. Надежный контроль рисков, включая лимиты просадки и ограничения по размеру сетки, обеспечит стабильную работу в любых рыночных условиях. В двух словах, вот что мы собираемся создать.

STRATEGY PLAN


Реализация средствами MQL5

Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку «Индикаторы» (Indicators), перейдите на вкладку "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования нам нужно будет объявить некоторые глобальные переменные, которые будем использовать во всей программе.

//+------------------------------------------------------------------+
//|                                      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)

Здесь мы реализуем стратегию на MQL5, инициализирующую основные компоненты программы для автоматизации сделок по мартингейлу на основе сетки и поддержки динамической информационной панели. Объявляем объект "CTrade" как "obj_Trade", используя "#include <Trade/Trade.mqh>" для управления исполнением сделок. Определяем такие переменные, как "dailyBalance" для отслеживания баланса счета, "lastBarTime" для хранения временных меток баров с функцией iTime,  "hasBuyPosition" и "hasSellPosition" для обозначения активных сделок и "activeOrders" для подсчета открытых позиций.

Устанавливаем такие входные данные, как "GRID_DISTANCE = 3.0" для интервалов сетки, "LOT_MULTIPLIER = 1.3" для масштабирования лотов и "TAKE_PROFIT_PIPS = 3" для целей получения прибыли, используя "MAGIC = 1234567890" для идентификации сделок. Включаем "ENABLE_DAILY_DRAWDOWN" и "DRAWDOWN_LIMIT" для управления рисками, а "cycleCount" и "MAX_CYCLES" ограничивают торговые циклы. Настраиваем информационную панель с параметрами "DASHBOARD_WIDTH = 300", "FONT_SIZE_METRIC = 9", "panelDragging" для функций перетаскивания, "closeButtonHovered" для эффектов наведения курсора мыши и "VALUE_POSITIVE_COLOR = clrLimeGreen" для визуальных элементов, используя "pipValue" для точного прайсинга. Это дает нам следующий пользовательский интерфейс.

USER INTERFACE

На изображении видно, что мы можем управлять программой из определенного пользовательского интерфейса. Теперь нам нужно продолжить определение некоторых вспомогательных функций, которые мы будем использовать, когда нам понадобятся базовые и частые действия, такие как выбор валютной пары или типа позиции. Для этого мы применили следующую логику.

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

Используем функцию "GetAccountBalance" совместно с функцией AccountInfoDouble для получения текущего баланса счета, что позволяет отслеживать баланс для управления рисками.

Реализуем функцию "GetPositionMagic", используя функцию PositionGetInteger для получения магического числа позиции, функцию "GetPositionOpenPrice" с функцией PositionGetDouble для получения цены открытия, а также функции "GetPositionSL" и "GetPositionTP" с функцией "PositionGetDouble" для доступа к уровням стоп-лосса и тейк-профита, соответственно, поддерживая точные торговые расчеты.

Кроме того, определяем функцию "GetPositionSymbol" с помощью PositionGetString для проверки символа позиции, функции "GetPositionTicket" и "GetPositionOpenTime" с помощью "PositionGetInteger" для отслеживания идентификаторов позиций и времени открытия, а также функцию "GetPositionType" для определения статуса покупки или продажи, что облегчает точный мониторинг позиции и торговую логику. Теперь можем перейти к созданию информационной панели и нам понадобятся вспомогательные функции, которые облегчат работу.

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

Здесь мы определяем функцию "CreateRectangle" с помощью функций ObjectCreate и ObjectSetInteger для рисования прямоугольных элементов, таких как фон и заголовок информационной панели, устанавливая такие свойства, как положение, размер и цвета, для более четкой раскладки. Мы реализуем функцию "CreateTextLabel", используя "ObjectCreate" и ObjectSetString для отображения таких показателей, как спред и размеры лотов, с пользовательскими настройками шрифта и цвета для удобства чтения.

Кроме того, определяем функцию "CreateButton" для добавления интерактивных кнопок, таких как кнопка закрытия, позволяющих выполнять действия пользователя с индивидуальным дизайном и эффектом наведения курсора мыши, обеспечивая плавное и визуально интуитивно понятное использование информационной панели. Теперь мы можем использовать эти функции для создания элементов информационной панели в новой функции, но поскольку нам понадобится общий подсчет прибыли, давайте определим функцию для этого.

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

Здесь мы реализуем функцию "CalculateTotalProfit" с помощью PositionsTotal и PositionGetTicket, чтобы выполнить перебор через открытые позиции, выбрав каждую посредством PositionSelectByTicket и проверяя её символ и магическое число с помощью "GetPositionSymbol" и "GetPositionMagic", чтобы получить общую прибыль. Накапливаем нереализованную прибыль, используя функцию PositionGetDouble для получения прибыли по каждой позиции, сохраняя сумму в переменной "profit", что позволяет точно отслеживать результаты торговли. Затем можем продолжить создавать функцию создания информационной панели следующим образом.

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

Здесь мы определяем функцию "UpdateDashboard" для отображения динамической информационной панели торговых показателей в режиме реального времени, предоставляющей всеобъемлющий интерфейс для мониторинга работы. Мы начинаем с проверки переменной "dashboardVisible", чтобы убедиться, что обновления происходят только тогда, когда панель мониторинга активна, что предотвращает ненужную обработку. Используем функцию "CreateRectangle" для рисования главной информационной панели и заголовка, устанавливая размеры с помощью "DASHBOARD_WIDTH" и "HEADER_HEIGHT" и применяя цвета, такие как "BACKGROUND_COLOR" и "HEADER_NORMAL_COLOR" или "HEADER_HOVER_COLOR", в зависимости от состояния "headerHovered" для визуальной обратной связи.

Используем функцию "CreateTextLabel" для отображения критически важных показателей, включая баланс счета, эквити, прибыль, спред, открытые ордера, статус просадки и размеры активных лотов, разделяя их на такие разделы, как информация об учетной записи, рыночная информация и торговая статистика. Рассчитываем спред, используя функцию SymbolInfoDouble для получения цен ask и bid, применяя условное цветовое кодирование: "VALUE_POSITIVE_COLOR" для низких спредов (≤ 2,0 пипса), "VALUE_ACTIVE_COLOR" для средних спредов (2,1-5,0 пипсов) и "VALUE_NEGATIVE_COLOR" для высоких спредов (> 5,0 пипсов). Вы можете изменить это значение в соответствии со своими предпочтениями. Затем используем AccountInfoDouble для определения баланса и собственного капитала и "CalculateTotalProfit" для определения нереализованной прибыли, присваивая цвета, такие как "profitColor", на основе значения прибыли для интуитивно понятного мониторинга.

Интегрируем функцию "CreateButton", чтобы добавить интерактивную кнопку закрытия, настроенную с помощью "CLOSE_BUTTON_WIDTH", "FONT_SIZE_BUTTON" и динамические цвета ("CLOSE_BUTTON_NORMAL_BG" или "CLOSE_BUTTON_HOVER_BG") на основе "closeButtonHovered", тем самым улучшая взаимодействие с пользователем. Вызываем функцию "GetActiveLotSizes", чтобы отобразить до трех размеров лотов в порядке возрастания, используя "lotsColor" для визуального различения, и управляем раскладкой с помощью таких переменных, как "sectionY", "labelXLeft" и "valueXRight" для точного позиционирования. Завершаем работу над обновлениями с помощью функции ChartRedraw, чтобы обеспечить плавный рендеринг. Теперь можем вызвать эту функцию в обработчике событий OnInit, чтобы создать первое отображение.

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

В обработчике событий OnInit вычисляем "pipValue" с помощью функции Point, устанавливаем "obj_Trade" с помощью "SetExpertMagicNumber" для "MAGIC" и включаем "dashboardVisible". Размещаем информационную панель, используя "panelStartX" и "panelStartY" с "PANEL_X" и "PANEL_Y", вызываем функцию "UpdateDashboard" для ее отображения и используем ChartSetInteger для взаимодействия с мышью, возвращая INIT_SUCCEEDED. В обработчике событий OnDeinit обязательно удаляем созданные объекты следующим образом.

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

Мы просто используем функцию ObjectsDeleteAll, чтобы удалить все объекты на графике, поскольку они нам больше не нужны. После компиляции получаем такой результат.

INITIAL PANEL

Чтобы сделать панель недоступной, вызываем обработчик событий OnChartEvent для обработки эффектов наведения курсора мыши и перетаскивания. Вот логика, которую мы реализуем, чтобы всё работало.

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

В обработчике событий OnChartEvent начинаем с проверки переменной "dashboardVisible", чтобы убедиться, что обработка событий происходит только при активной информационной панели, оптимизируя работу за счет пропуска ненужных обновлений. Для событий щелчка мыши мы вычисляем положение кнопки закрытия, используя "panelStartX", "DASHBOARD_WIDTH", "CLOSE_BUTTON_WIDTH" и "panelStartY". И если щелчок попадает в ее пределы, устанавливаем для "dashboardVisible" значение false, вызываем функцию ObjectsDeleteAll, чтобы удалить все графические объекты, и используем функцию ChartRedraw, чтобы обновить график, эффективно скрыв информационную панель.

Для событий перемещения мыши отслеживаем положение курсора с помощью "mouseX" и "mouseY", а также отслеживаем "mouseState" для обнаружения таких действий, как щелчки или отпускания. Обновляем состояние кнопки закрытия при наведении курсора мыши, сравнивая "mouseX" и "mouseY" с ее координатами, устанавливая "closeButtonHovered" и используя функцию ObjectSetInteger, чтобы настроить OBJPROP_BGCOLOR и "OBJPROP_BORDER_COLOR" на "CLOSE_BUTTON_HOVER_BG" или "CLOSE_BUTTON_NORMAL_BG" для визуальной обратной связи. Аналогично управляем состоянием заголовка при наведении на него курсора с помощью "headerHovered", применяя "HEADER_HOVER_COLOR" или "HEADER_NORMAL_COLOR" через "ObjectSetInteger" для повышения интерактивности.

Чтобы включить перетаскивание информационной панели, мы используем статический параметр "prevMouseState" для обнаружения нажатий кнопок мыши, запуская режим перетаскивания с помощью "panelDragging" при наведении курсора мыши на заголовок, и сохраняем начальные координаты в "panelDragX" и "panelDragY". Получаем текущее положение информационной панели с помощью функции ObjectGetInteger для "OBJPROP_XDISTANCE" и OBJPROP_YDISTANCE, отключаем прокрутку графика с использованием функции ChartSetInteger и обновляем "panelStartX" и "panelStartY" на основе дельт движения мыши, вызывая функцию "UpdateDashboard", чтобы переместить информационную панель в режиме реального времени. Когда кнопка мыши отпущена, сбрасываем "panelDragging" и снова включаем прокрутку с помощью "ChartSetInteger", завершая каждое обновление с помощью ChartRedraw, чтобы обеспечить пользователю удобство работы. После компиляции получаем такой результат.

DASHBOARD UI GIF

На изображении видно, что мы можем перетаскивать кнопки и наводить на них курсор мыши, обновлять показатели и закрывать всю информационную панель. Теперь мы можем перейти к важнейшей части - открытию позиций и управлению ими, а информационная панель облегчит работу, динамически отображая прогресс. Для достижения этой цели нам понадобятся некоторые вспомогательные функции для расчета размеров лотов и многого другого, как показано ниже.

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

Здесь мы определяем функции "CalculateLotSize", "CountActiveOrders" и "GetActiveLotSizes", которые помогают определять размер сделки, отслеживать позиции и обновлять информационную панель, обеспечивая точное исполнение и всесторонний мониторинг в режиме реального времени.

Реализуем функцию "CalculateLotSize" для определения объемов сделок на основе входных данных "LOT_MODE", поддерживающую три режима: фиксированный размер лота путем прямого возврата "BASE_LOT", масштабирование по мартингейлу путем применения функции MathPow с "LOT_MULTIPLIER" и "activeOrders" для экспоненциального увеличения лотов или корректировки на основе убытков, при которых мы используем функцию HistorySelect для просмотра сделок за последние 24 часа.

В режиме loss-based выполним перебор с помощью HistoryDealsTotal, получим данные сделки посредством HistoryDealGetTicket и HistoryDealGetDouble и произведем корректировку параметра "lotSize" с помощью NormalizeDouble, если самая последняя сделка была убыточной, а в противном случае выполняя сброс настроек до "BASE_LOT", обеспечивая точное вычисление лотов с учетом торговых результатов.

Используем функцию "CountActiveOrders" для поддержания точного количества открытых позиций, что имеет решающее значение для масштабирования по мартингейлу и точности информационной панели. Выполняем перебор всех позиций, используя функцию PositionsTotal, выбираем каждую с помощью PositionGetTicket и проверяем актуальность с помощью функций "GetPositionSymbol" и "GetPositionMagic", увеличивая переменную "count" для позиций на покупку или продажу, определенных с помощью "GetPositionType", таким образом, надежно обновляя "activeOrders".

Кроме того, мы разработали функцию "GetActiveLotSizes" для форматирования и отображения размеров лотов на информационной панели, что улучшает видимость активных сделок пользователями. Проверяем "activeOrders", чтобы вернуть "[Waiting]", если таковых не существует, в противном случае инициализируем массив с помощью ArrayResize для хранения размеров лотов. Перебираем позиции с помощью PositionsTotal, выбираем их с помощью "PositionGetTicket" и используем PositionGetDouble для сбора объемов в "lotSizes", сортируя их по возрастанию с помощью функции ArraySort.  Форматируем до трех лотов, используя DoubleToString совместно с "LOT_PRECISION", добавляя запятые и многоточие для более чем трех ордеров, и возвращаем результат в квадратных скобках, обеспечивая четкое и профессиональное отображение объемов сделок для мониторинга в режиме реального времени. Тем не менее, нам нужно определить функции, которые помогут в размещении ордеров, как показано ниже.

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

Здесь мы реализуем функцию "PlaceOrder" для инициирования сделок, используя функцию "PositionOpen" из "obj_Trade" для открытия позиций с такими параметрами, как "lot", "price" и комментарий, отформатированный с помощью IntegerToString для "gridLevel", включая логику повторных попыток с помощью GetLastError и Sleep для надежного исполнения. Используем функцию "CalculateSL" для расчета цен стоп-лосса путем вычитания "STOP_LOSS_PIPS", умноженного на "pipValue", из "slPrice", и функцию "CalculateTP" для установки уровней тейк-профита, добавляя или вычитая "TAKE_PROFIT_PIPS" на основе "orderType" для сделок на покупку или продажу соответственно.

Создаем функцию "GetLatestBuyPrice" для определения цены самой последней позиции на покупку, выполняем итерацию с помощью PositionsTotal, выбираем позиции с помощью PositionGetTicket и проверяем с помощью "GetPositionSymbol", "GetPositionMagic" и "GetPositionType", обновляем "price" с помощью "GetPositionOpenPrice" для получения самого высокого "ticket".

Аналогичным образом реализуем функцию "GetLatestSellPrice" для получения цены последней позиции на продажу, следуя той же логике, чтобы обеспечить точное размещение сетки. Задаем функцию "IsTradingAllowed", которая возвращает постоянное значение, равное 1, позволяющую вести непрерывную торговлю без ограничений, что поддерживает высокочастотный подход стратегии. Теперь мы можем использовать эти функции для определения реальной торговой логики в обработчике событий OnTick

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

         //---

      }
   }
}

Определяем обработчик событий OnTick, который управляет основной торговой логикой программы, организуя выполнение сделок в режиме реального времени и управление позициями. Начинаем с получения текущих рыночных цен с помощью функции SymbolInfoDouble, чтобы получить значения "ask" и "bid", нормализованные с помощью NormalizeDouble, и обновляем информационную панель с помощью функции "UpdateDashboard", если значение "dashboardVisible" равно true. Проверяем торговые разрешения с помощью функции "IsTradingAllowed", гарантирующей, что сделки будут совершаться только тогда, когда позволяют условия, и используем функцию iTime для получения временной метки текущего бара, сохраняя ее в "currentBarTime", чтобы предотвратить избыточную обработку путем сравнения с "lastBarTime".

Управляем отслеживанием позиций, вызывая функцию "CountActiveOrders" для обновления "activeOrders", сбрасывая "updateSLTP", если ордеров не существует, и выполняя перебор позиций с помощью PositionsTotal и PositionGetTicket, чтобы установить "hasBuyPosition" или "hasSellPosition" на основе "GetPositionType".

Оцениваем условия сетки, используя "GetLatestBuyPrice" и "GetLatestSellPrice", запуская "openNewTrade", когда колебания цен превышают "GRID_DISTANCE", умноженное на "pipValue", или если "activeOrders" находится ниже "MAX_GRID_LEVELS". Когда значение "openNewTrade" равно true, мы рассчитываем размеры лотов с помощью функции "CalculateLotSize", исполняем сделки с помощью функции "PlaceOrder" для ордеров на покупку или продажу, регистрируем ошибки с помощью "Print" и GetLastError, если размещение завершается неудачно, и обновляем "latestBuyPrice", "latestSellPrice" и "modifyPositions" для управления текущими сделками, обеспечивая точные и эффективные операции скальпинга. Чтобы открыть новые позиции, мы используем следующую логику для генерации сигнала.

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

Чтобы инициировать новые сделки, мы начинаем с использования функции TimeCurrent для извлечения текущего времени в переменную "timeStruct", проверяя, попадает ли "timeStruct.hour" в "START_HOUR" и "END_HOUR", "cycleCount" находится ниже "MAX_CYCLES", а "CONTINUE_TRADING" и "openNewTrade" имеют значение true, если "activeOrders" меньше 1. Извлекаем цены закрытия предыдущего и текущего баров с помощью функции iClose, сохраняем их в "closePrev" и "closeCurrent", а также подтверждаем отсутствие позиций с помощью "hasSellPosition" и "hasBuyPosition".

В отношении медвежьего сигнала (когда "closePrev" превышает "closeCurrent") рассчитываем размер лота, используя функцию "CalculateLotSize" для ордера на продажу, проверяем "calculatedLot" и "tradingEnabled" и исполняем сделку с помощью функции "PlaceOrder", регистрируя ошибки с помощью "Print" и GetLastError, при необходимости. В отношении бычьего сигнала (когда "closePrev" меньше или равно "closeCurrent") выполняем тот же процесс для ордера на покупку, обновляя "cycleCount" и "latestBuyPrice" или "latestSellPrice" с помощью "GetLatestBuyPrice" или "GetLatestSellPrice" и устанавливая "modifyPositions" в значение true, включая точное инициирование сделок и управление позициями.

Вы можете заменить эту стратегию любой из ваших торговых стратегий. Мы просто использовали простую стратегию генерации сигналов, поскольку основной целью является управление позициями с помощью этой стратегии. После открытия позиций нам нужно проверять и изменять их при повышении цены, как показано ниже.

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

Здесь мы обновляем "activeOrders" с помощью "CountActiveOrders", производим сброс "weightedPrice" и "totalVolume", и выполняем перебор позиций, используя PositionsTotal и PositionGetTicket, накапливая "weightedPrice" и "totalVolume" посредством функций "GetPositionOpenPrice" и PositionGetDouble.  Нормализуем "weightedPrice" с помощью NormalizeDouble, если "activeOrders" существует, устанавливаем "targetTP" и "targetSL", используя "GetPositionType" и "pipValue", когда "modifyPositions" имеет значение true, и применяем обновления с помощью "PositionModify" из "obj_Trade", если значение "updateSLTP", сбрасывая "modifyPositions" в случае успешного выполнения. После компиляции получаем следующий результат.

ORDERS OPENED

На изображении видно, что у нас открыты ордера, которые уже находятся в режиме управления. Сейчас нам просто необходимо применить логику управления рисками для мониторинга ежедневной просадки и ее ограничения. Для этого у нас будет функция, которая будет выполнять все отслеживание и останавливать программу при необходимости следующим образом.

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

Для обеспечения возможности управления рисками мы реализуем функцию "MonitorDailyDrawdown", чтобы отслеживать ежедневные показатели, используя функции TimeCurrent и TimeToString, чтобы задать диапазон на день и HistorySelect для получения доступа к истории сделок. Рассчитываем общую прибыль с помощью функций HistoryDealGetTicket и HistoryDealGetDouble, нормализуем просадку в процентах посредством NormalizeDouble и корректируем "tradingEnabled" на основе "DRAWDOWN_LIMIT", вызывая функцию "CloseAllPositions" если значение "CLOSE_ON_DRAWDOWN" равно true.

Определяем "CloseAllPositions" для выполнения перебора по позициям с помощью "PositionsTotal" и PositionGetTicket, закрывая соответствующие сделки с помощью "PositionClose" из "obj_Trade" после проверки с помощью "GetPositionSymbol" и "GetPositionMagic", обеспечивая надежный контроль просадки. Затем мы можем вызывать эту функцию на каждом тике, чтобы осуществлять управление рисками, когда это необходимо.

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

Проверяем условие "ENABLE_DAILY_DRAWDOWN", чтобы определить, активен ли контроль просадки, и если это так, вызываем функцию "MonitorDailyDrawdown" для оценки ежедневных прибылей и убытков, корректируем "tradingEnabled" и потенциально закрываем позиции, защищая счет от чрезмерных убытков. После компиляции получаем следующий результат.

FINAL POSITIONS MANAGEMENT GIF

На изображении видно, что мы можем открывать позиции, динамически управлять ими и закрывать их после достижения целевых показателей, тем самым достигая нашей цели по настройке стратегии grid-mart. Осталось провести тестирование программы на истории. Это будет выполнено в следующем разделе.


Тестирование на истории

После тщательного тестирования на истории мы получили следующие результаты.

График тестирования на истории:

График

Отчет о тестировании на истории:

REPORT


Заключение

В заключение отметим, что нами разработана программа на MQL5, которая автоматизирует стратегию скальпинга Grid-Mart, исполняющая сделки по мартингейлу на основе сетки с динамической информационной панелью для мониторинга ключевых показателей, таких как спред, прибыль и размеры лотов, в режиме реального времени. Благодаря точному исполнению сделок, надежному управлению рисками с помощью контроля просадки и интерактивному интерфейсу вы можете еще больше усовершенствовать эту программу, настраивая ее параметры или интегрируя дополнительные стратегии в соответствии с вашими торговыми предпочтениями.

Отказ от ответственности: Содержание настоящей статьи предназначено только для целей обучения. Торговля сопряжена со значительными финансовыми рисками, а волатильность рынка может привести к убыткам. Тщательное тестирование на истории и управление рисками имеют решающее значение перед внедрением этой программы в реальных условиях рынка.

Овладев этими методами, вы сможете еще больше усовершенствовать данную программу и сделать ее более надежной, либо использовать ее в качестве основы для разработки других торговых стратегий, расширяя возможности вашей алгоритмической торговли.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18038

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
Ahmet Parlakbilek
Ahmet Parlakbilek | 14 мая 2025 в 21:27
Хорошая статья, но в CalculateSL есть существенная ошибка.
Allan Munene Mutiiria
Allan Munene Mutiiria | 14 мая 2025 в 22:45
Ahmet Parlakbilek #:
Хорошая статья, но в CalculateSL есть существенная ошибка.

Конечно, спасибо. Что с ним?

Ahmet Parlakbilek
Ahmet Parlakbilek | 15 мая 2025 в 06:36
Allan Munene Mutiiria #:

Конечно. Спасибо. Что с ним?

Вы забыли обработать сторону продажи. Прилагаю исправленную версию.

Allan Munene Mutiiria
Allan Munene Mutiiria | 15 мая 2025 в 15:44
Ahmet Parlakbilek #:

Вы забыли обработать сторону продажи. Прилагаю исправленную версию.

Ооо. Да. Конечно. Это будет очень полезно для других. Спасибо.

Pramendra
Pramendra | 18 мая 2025 в 11:28
Сэр, пожалуйста, исправьте.
Искусство ведения логов (Часть 5): Оптимизация обработчика с помощью кэширования и ротации Искусство ведения логов (Часть 5): Оптимизация обработчика с помощью кэширования и ротации
В этой статье мы улучшим библиотеку логов путем добавления форматтеров в обработчики, класса CIntervalWatcher для управления циклами выполнения, оптимизации с кэшированием и ротацией файлов, тестов производительности и практических примеров. Благодаря этим улучшениям мы получим эффективную, масштабируемую и адаптируемую систему ведения логов к различным сценариям разработки.
Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (I) Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (I)
В этом обсуждении рассматриваются проблемы, возникающие при работе с большими базами кодов. Мы рассмотрим лучшие практики организации кода в MQL5 и реализуем практический подход для повышения читаемости и масштабируемости исходного кода нашей панели торгового администратора. Кроме того, мы начнем разработку повторно используемых компонентов кода, которые потенциально могут принести пользу другим разработчикам при создании алгоритмов. Присоединяйтесь к обсуждению.
Математические модели в сеточных стратегиях Математические модели в сеточных стратегиях
В этой статье мы рассмотрим применение математики к сеточным стратегиям. Мы разберем основные принципы работы стратегии, её преимущества и недостатки. Вы узнаете, как построить торговую сетку, задавать оптимальные параметры и эффективно управлять рисками.
Введение в MQL5 (Часть 11): Руководство для начинающих по работе со встроенными индикаторами в MQL5 (II) Введение в MQL5 (Часть 11): Руководство для начинающих по работе со встроенными индикаторами в MQL5 (II)
В этой статье мы узнаем, как написать на MQL5 советника с использованием нескольких индикаторов, таких как RSI, MA и Stochastic Oscillator. Индикаторы будут искать скрытые бычьи и медвежьи расхождения. В статье представлены примеры и исходный код с подробными комментариями — изучайте их, чтобы узнать, как эффективно управлять рисками и автоматизировать торговлю.