English 中文 Deutsch 日本語
preview
Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (IV). Класс для панели управления торговлей

Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (IV). Класс для панели управления торговлей

MetaTrader 5Примеры |
81 0
Clemence Benjamin
Clemence Benjamin

Содержание


Введение

В прошлой статье и в нескольких более ранних мы говорили об организации кода. Хорошо организованный код проще поддерживать и развивать, поэтому мы уделяем этому такое внимание. Я использую принцип разделения ответственности "separation of concerns". Такой подход позволяет работать с отдельными элементами внутри NewAdminPanel, а сама панель остается модульной и хорошо структурированной. Мы независимо работаем над отдельными элементами и поэтому можем оттачивать ее и добиваться лучшего результата.

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

Обновление панели решает две основные задачи:

  1. Создание больших, хорошо организованных программ, которые проще разрабатывать и сопровождать.
  2. Быстрый доступ к торговым операциям в универсальном советнике, который объединяет в себе интерфейс коммуникации, интерфейс управления торговлей и, в ближайшем будущем, аналитический интерфейс.

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


Общая информация

Основная цель этой серии — применять MQL5 на практике на примере различных проектов. В данной статье мы разработаем класс панели управления торговлей (Trade Management Panel). Сразу напомню, что в MQL5 заголовок класса содержит объявления однотипных переменных. В данном контексте все торговые функции, которые мы планируем включить в панель, будут наследоваться от встроенных заголовков классов CTrade, CDialog, CLabel и CEdit.

После того как класс будет полностью разработан, мы интегрируем его методы в основную программу — советник NewAdminPanel. И конечно же, в конце мы увидим результаты тестирования. Кроме того, все исходные файлы приложены к статье, чтобы вы могли изучить реализацию, позаимствовать идеи и поэкспериментировать с кодом в собственных проектах.

На данном этапе я решил оставить создание главной панели непосредственно в основной программе, так как сама по себе она не сильно увеличивает код. Хотя у предыдущего подхода были свои преимущества, я выбрал такую структуру, поскольку она упрощает разработку за счет уменьшения зависимостей. Моя цель — сохранить основной программный код максимально сжатым, используя при этом отдельный класс для каждой конкретной функции. Базовые элементы интерфейса теперь создаются напрямую в главной программе, что дает более лаконичный и эффективный дизайну. В результате у нас больше нет вызовов методов класса AdminHomeDialog в NewAdminPanel. 

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

Итоговая панель управления торговлей

Преимущества нашего проекта

Преимущества инструментов, которые мы разрабатываем в рамках этого проекта:

  • Быстрая торговля — быстрое и удобное выполнение торговых операций.
  • Управление рисками — установка стоп-лосса и тейк-профита перед размещением ордеров, а также предварительное планирование сделок с отложенными ордерами.
  • Функции коммуникации — отправка сообщений другим трейдерам через разработанную ранее панель для общения.
  • Изучение продвинутого MQL5 — более глубокое понимание объектно-ориентированного программирования в MQL5.
  • Взаимодействие с сообществом — обмен мнениями и опытом в обсуждении.
  • Повторное использование — класс CTradeManagementPanel можно использовать в других проектах.
Далее подробно разберем все возможности класса панели управления торговлей, а затем рассмотрим его интеграцию в основную программу.


Класс панели управления сделками (CTradeManagementPanel)

1. Подготовка основы: заголовочные файлы и макросы

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

#include <Trade\Trade.mqh>
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Label.mqh>

//+------------------------------------------------------------------+
//| Defines for dimensions and spacing                               |
//+------------------------------------------------------------------+
#define BUTTON_WIDTH          100
#define BUTTON_HEIGHT         30
#define DELETE_BUTTON_WIDTH   130   
#define EDIT_WIDTH            80
#define EDIT_HEIGHT           20
#define DEFAULT_LABEL_HEIGHT  20    
#define SECTION_LABEL_WIDTH   250
#define GAP                   10
#define INPUT_LABEL_GAP       3     

2. Определим основной класс и его компоненты

В основе торговой панели лежит класс, который управляет как интерфейсом, так и логикой выполнения торговых операций. Наследуем его от основного класса диалога, благодаря чему он получает встроенные возможности управления интерфейсом. Мы же сосредоточимся на настройке поведения.

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

//+------------------------------------------------------------------+
//| Trade Management Panel class                                     |
//+------------------------------------------------------------------+
class CTradeManagementPanel : public CAppDialog
{
protected:
   // Store chart and subwindow values for helper functions.
   long m_chart;
   int  m_subwin;
   
   CTrade m_trade;  // Trade object for executing trades

   // Section Labels
   CLabel m_secQuickLabel;
   CLabel m_secPendingLabel;
   CLabel m_secAllOpsLabel;
   
   // Section 1: Quick Execution
   CButton m_buyButton;      // Market Buy button
   CButton m_sellButton;     // Market Sell button
   CEdit   m_volumeEdit;     // Volume input
   CLabel  m_lotLabel;       // "Lot:" label above volume input
   // TP and SL for market orders—with a label to their right
   CEdit   m_tpEdit;
   CLabel  m_tpRightLabel;   // "TP:" label
   CLabel  m_tpErrorLabel;
   CEdit   m_slEdit;
   CLabel  m_slRightLabel;   // "SL:" label
   CLabel  m_slErrorLabel;
   
   // Section 2: Pending Orders
   // Column Headers for pending orders
   CLabel  m_pendingPriceHeader;
   CLabel  m_pendingTPHeader;
   CLabel  m_pendingSLHeader;
   CLabel  m_pendingExpHeader;
   
   // Buy pending orders
   CButton m_buyLimitButton;
   CEdit   m_buyLimitPriceEdit;
   CEdit   m_buyLimitTPEdit;
   CEdit   m_buyLimitSLEdit;
   CEdit   m_buyLimitExpEdit;   // Expiration input for Buy Limit
   CButton m_buyStopButton;
   CEdit   m_buyStopPriceEdit;
   CEdit   m_buyStopTPEdit;
   CEdit   m_buyStopSLEdit;
   CEdit   m_buyStopExpEdit;    // Expiration input for Buy Stop
   
   // Sell pending orders
   CButton m_sellLimitButton;
   CEdit   m_sellLimitPriceEdit;
   CEdit   m_sellLimitTPEdit;
   CEdit   m_sellLimitSLEdit;
   CEdit   m_sellLimitExpEdit;  // Expiration input for Sell Limit
   CButton m_sellStopButton;
   CEdit   m_sellStopPriceEdit;
   CEdit   m_sellStopTPEdit;
   CEdit   m_sellStopSLEdit;
   CEdit   m_sellStopExpEdit;   // Expiration input for Sell Stop
   
   // Section 3: All Order Operations
   CButton m_closeAllButton;
   CButton m_closeProfitButton;
   CButton m_closeLossButton;
   CButton m_closeBuyButton;
   CButton m_closeSellButton;
   CButton m_deleteAllOrdersButton;
   CButton m_deleteLimitOrdersButton;
   CButton m_deleteStopOrdersButton;
   CButton m_deleteStopLimitOrdersButton;
   CButton m_resetButton;        
   
   //--- Helper: Create a text label using
   bool CreateLabelEx(CLabel &label, int x, int y, int height, string label_name, string text, color clr)
   {
      string unique_name = m_name + label_name;
      if(!label.Create(m_chart, unique_name, m_subwin, x, y, x, y+height))
         return false;
      if(!Add(label))
         return false;
      if(!label.Text(text))
         return false;
      label.Color(clr);
      return true;
   }
   
   //--- Helper: Create and add a button control
   bool CreateButton(CButton &button, string name, int x, int y, int w = BUTTON_WIDTH, int h = BUTTON_HEIGHT, color clr = clrWhite)
   {
      if(!button.Create(m_chart, name, m_subwin, x, y, x+w, y+h))
         return false;
      button.Text(name);
      button.Color(clr);
      if(!Add(button))
         return false;
      return true;
   }
   
   //--- Helper: Create and add an edit control
   bool CreateEdit(CEdit &edit, string name, int x, int y, int w = EDIT_WIDTH, int h = EDIT_HEIGHT)
   {
      if(!edit.Create(m_chart, name, m_subwin, x, y, x+w, y+h))
         return false;
      if(!Add(edit))
         return false;
      return true;
   }
   
   // Event handler declarations
   virtual void OnClickBuy();
   virtual void OnClickSell();
   virtual void OnClickBuyLimit();
   virtual void OnClickBuyStop();
   virtual void OnClickSellLimit();
   virtual void OnClickSellStop();
   virtual void OnClickCloseAll();
   virtual void OnClickCloseProfit();
   virtual void OnClickCloseLoss();
   virtual void OnClickCloseBuy();
   virtual void OnClickCloseSell();
   virtual void OnClickDeleteAllOrders();
   virtual void OnClickDeleteLimitOrders();
   virtual void OnClickDeleteStopOrders();
   virtual void OnClickDeleteStopLimitOrders();
   virtual void OnClickReset();  
   
   // Validation helpers for market orders
   bool ValidateBuyParameters(double volume, double tp, double sl, double ask);
   bool ValidateSellParameters(double volume, double tp, double sl, double bid);
   
public:
   CTradeManagementPanel();
   ~CTradeManagementPanel();
   virtual bool Create(const long chart, const string name, const int subwin,
                         const int x1, const int y1, const int x2, const int y2);
   virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   void Toggle();
};

3. Разделяем пользовательский интерфейс на логические секции

Согласитесь, хорошо организованным интерфейсом удобно пользоваться. Мы не будем размещать кнопки и поля ввода как попало, а поделим панель на три основные секции.

  1. Первая секция предназначена для быстрых рыночных ордеров, где пользователь может быстро совершить покупку или продажу, задать объем сделки и настроить уровни стоп-лосс и тейк-профит.
  2. Вторая секция предназначена для отложенных ордеров. Здесь пользователь указывает цену, время истечения и тип ордера, например Buy Limit или Sell Stop.
  3. Третья секция будет для массовых операций: закрыть все сделки, закрыть только прибыльные, удалить все отложенные ордера. Такое разделение на группы делает интерфейс более интуитивным и удобным для навигации.

//+------------------------------------------------------------------+
//| Create the trade management panel                                |
//+------------------------------------------------------------------+
bool CTradeManagementPanel::Create(const long chart, const string name, const int subwin,
                                     const int x1, const int y1, const int x2, const int y2)
{
   // Save chart and subwin for later use.
   m_chart  = chart;
   m_subwin = subwin;
   
   if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2))
      return false;
   
   int curX = GAP;
   int curY = GAP;
   
   // Section 1: Quick Order Execution
   if(!CreateLabelEx(m_secQuickLabel, curX, curY-10, DEFAULT_LABEL_HEIGHT, "SecQuick", "Quick Order Execution:", clrBlack))
      return false;
   curY += DEFAULT_LABEL_HEIGHT + GAP;
   
   // Market order row:
   if(!CreateButton(m_buyButton, "Buy", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen))
      return false;
   int volX = curX + BUTTON_WIDTH + GAP;
   if(!CreateLabelEx(m_lotLabel, volX, curY - DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "Lot", "Lot Size:", clrBlack))
      return false;
   if(!CreateEdit(m_volumeEdit, "VolumeEdit", volX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   double minVolume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   m_volumeEdit.Text(DoubleToString(minVolume, 2));
   int sellBtnX = volX + EDIT_WIDTH + GAP;
   if(!CreateButton(m_sellButton, "Sell", sellBtnX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed))
      return false;
   curY += BUTTON_HEIGHT + GAP;
   
   // Next row: TP and SL for market orders
   if(!CreateEdit(m_tpEdit, "TPEdit", 4*curX+ GAP , curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_tpEdit.Text("0.00000");
   if(!CreateLabelEx(m_tpRightLabel, curX  + INPUT_LABEL_GAP, curY, DEFAULT_LABEL_HEIGHT, "TP", "TP:", clrBlack))
      return false;
   if(!CreateLabelEx(m_tpErrorLabel, curX, curY + DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "TPError", "", clrBlack))
      return false;
   int slX = 2*EDIT_WIDTH  ; 
   if(!CreateEdit(m_slEdit, "SLEdit", slX + 4*curX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_slEdit.Text("0.00000");
   if(!CreateLabelEx(m_slRightLabel, slX  , curY, DEFAULT_LABEL_HEIGHT, "SL", "SL:", clrBlack))
      return false;
   if(!CreateLabelEx(m_slErrorLabel, slX, curY + DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "SLError", "", clrBlack))
      return false;
   curY += EDIT_HEIGHT + GAP*2;
   
   // Section 2: Pending Orders
   if(!CreateLabelEx(m_secPendingLabel, curX, curY, DEFAULT_LABEL_HEIGHT, "SecPend", "Pending Orders:", clrBlack))
      return false;
   curY += DEFAULT_LABEL_HEIGHT + GAP;
   
   // Column headers for pending orders (each label includes a colon)
   int headerX = curX + BUTTON_WIDTH + GAP;
   if(!CreateLabelEx(m_pendingPriceHeader, headerX, curY, DEFAULT_LABEL_HEIGHT, "PendPrice", "Price:", clrBlack))
      return false;
   if(!CreateLabelEx(m_pendingTPHeader, headerX + EDIT_WIDTH + GAP, curY, DEFAULT_LABEL_HEIGHT, "PendTP", "TP:", clrBlack))
      return false;
   if(!CreateLabelEx(m_pendingSLHeader, headerX + 2*(EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, "PendSL", "SL:", clrBlack))
      return false;
   if(!CreateLabelEx(m_pendingExpHeader, headerX + 3*(EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, "PendExp", "Expiration Date:", clrBlack))
      return false;
   curY += DEFAULT_LABEL_HEIGHT + GAP;
   
   // Prepare default expiration value as current day end time.
   datetime now = TimeCurrent();
   string exp_default = TimeToString(now, TIME_DATE) + " 23:59:59";
   
   // --- Buy Pending Orders ---
   // Buy Limit Order row:
   if(!CreateButton(m_buyLimitButton, "Buy Limit", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue))
      return false;
   int pendingX = curX + BUTTON_WIDTH + GAP;
   if(!CreateEdit(m_buyLimitPriceEdit, "BuyLimitPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   string askStr = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_ASK), 5);
   m_buyLimitPriceEdit.Text(askStr);
   int pending2X = pendingX + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyLimitTPEdit, "BuyLimitTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyLimitTPEdit.Text("0.00000");
   int pending3X = pending2X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyLimitSLEdit, "BuyLimitSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyLimitSLEdit.Text("0.00000");
   int pending4X = pending3X + EDIT_WIDTH + GAP; // Double width for expiration input
   if(!CreateEdit(m_buyLimitExpEdit, "BuyLimitExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyLimitExpEdit.Text(exp_default);
   curY += BUTTON_HEIGHT + GAP;
   
   // Buy Stop Order row:
   if(!CreateButton(m_buyStopButton, "Buy Stop", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue))
      return false;
   pendingX = curX + BUTTON_WIDTH + GAP;
   if(!CreateEdit(m_buyStopPriceEdit, "BuyStopPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyStopPriceEdit.Text(askStr);
   pending2X = pendingX + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyStopTPEdit, "BuyStopTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyStopTPEdit.Text("0.00000");
   pending3X = pending2X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyStopSLEdit, "BuyStopSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyStopSLEdit.Text("0.00000");
   pending4X = pending3X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyStopExpEdit, "BuyStopExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyStopExpEdit.Text(exp_default);
   curY += BUTTON_HEIGHT + GAP;
   
   // --- Sell Pending Orders ---
   // Sell Limit Order row:
   if(!CreateButton(m_sellLimitButton, "Sell Limit", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed))
      return false;
   pendingX = curX + BUTTON_WIDTH + GAP;
   if(!CreateEdit(m_sellLimitPriceEdit, "SellLimitPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   string bidStr = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_BID), 5);
   m_sellLimitPriceEdit.Text(bidStr);
   pending2X = pendingX + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellLimitTPEdit, "SellLimitTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellLimitTPEdit.Text("0.00000");
   pending3X = pending2X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellLimitSLEdit, "SellLimitSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellLimitSLEdit.Text("0.00000");
   pending4X = pending3X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellLimitExpEdit, "SellLimitExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellLimitExpEdit.Text(exp_default);
   curY += BUTTON_HEIGHT + GAP;
   
   // Sell Stop Order row:
   if(!CreateButton(m_sellStopButton, "Sell Stop", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed))
      return false;
   pendingX = curX + BUTTON_WIDTH + GAP;
   if(!CreateEdit(m_sellStopPriceEdit, "SellStopPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellStopPriceEdit.Text(bidStr);
   pending2X = pendingX + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellStopTPEdit, "SellStopTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellStopTPEdit.Text("0.00000");
   pending3X = pending2X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellStopSLEdit, "SellStopSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellStopSLEdit.Text("0.00000");
   pending4X = pending3X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellStopExpEdit, "SellStopExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellStopExpEdit.Text(exp_default);
   curY += BUTTON_HEIGHT + GAP;
   
   // Section 3: All Order Operations
   if(!CreateLabelEx(m_secAllOpsLabel, curX, curY, DEFAULT_LABEL_HEIGHT, "SecAllOps", "All Order Operations:", clrBlack))
      return false;
   curY += DEFAULT_LABEL_HEIGHT + GAP;   
   
   int deleteX = curX + 2*BUTTON_WIDTH + 2*GAP; 
   int deleteY = curY  ;      
   CreateButton(m_closeAllButton, "Close All", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed);
   CreateButton(m_closeProfitButton, "Close Profit", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen);
   CreateButton(m_deleteLimitOrdersButton, "Delete Limits", deleteX, curY, DELETE_BUTTON_WIDTH, BUTTON_HEIGHT, clrRed);
   curY += BUTTON_HEIGHT + GAP;
   
   CreateButton(m_closeLossButton, "Close Loss", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed);
   CreateButton(m_closeBuyButton, "Close Buy", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen);
   CreateButton(m_deleteStopOrdersButton, "Delete Stops", deleteX, curY  , DELETE_BUTTON_WIDTH+10, BUTTON_HEIGHT, clrRed);
   CreateButton(m_resetButton, "Reset", deleteX+ 2*BUTTON_WIDTH-3*curX, curY, DELETE_BUTTON_WIDTH, 2*BUTTON_HEIGHT, clrRed);
   curY += BUTTON_HEIGHT + GAP;
   
   CreateButton(m_closeSellButton, "Close Sell", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue);
   CreateButton(m_deleteAllOrdersButton, "Delete All", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed);
   CreateButton(m_deleteStopLimitOrdersButton, "Delete StopLimits", deleteX, curY , DELETE_BUTTON_WIDTH+10, BUTTON_HEIGHT, clrDarkRed);
   curY += BUTTON_HEIGHT + GAP;
  
   return true;
}

4. Автоматизируем создание элементов интерфейса с помощью вспомогательных функций

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

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

 //--- Helper: Create a text label using
   bool CreateLabelEx(CLabel &label, int x, int y, int height, string label_name, string text, color clr)
   {
      string unique_name = m_name + label_name;
      if(!label.Create(m_chart, unique_name, m_subwin, x, y, x, y+height))
         return false;
      if(!Add(label))
         return false;
      if(!label.Text(text))
         return false;
      label.Color(clr);
      return true;
   }
   
   //--- Helper: Create and add a button control
   bool CreateButton(CButton &button, string name, int x, int y, int w = BUTTON_WIDTH, int h = BUTTON_HEIGHT, color clr = clrWhite)
   {
      if(!button.Create(m_chart, name, m_subwin, x, y, x+w, y+h))
         return false;
      button.Text(name);
      button.Color(clr);
      if(!Add(button))
         return false;
      return true;
   }
   
   //--- Helper: Create and add an edit control
   bool CreateEdit(CEdit &edit, string name, int x, int y, int w = EDIT_WIDTH, int h = EDIT_HEIGHT)
   {
      if(!edit.Create(m_chart, name, m_subwin, x, y, x+w, y+h))
         return false;
      if(!Add(edit))
         return false;
      return true;
   }

5. Обработка пользовательских действий через централизованную систему событий

После создания интерфейса нам необходим механизм обработки действий пользователя. Снова вместо назначения отдельных функций для каждой кнопки мы используем централизованный диспетчер событий.

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

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
bool CTradeManagementPanel::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == m_buyButton.Name())            OnClickBuy();
      else if(sparam == m_sellButton.Name())        OnClickSell();
      else if(sparam == m_buyLimitButton.Name())    OnClickBuyLimit();
      else if(sparam == m_buyStopButton.Name())     OnClickBuyStop();
      else if(sparam == m_sellLimitButton.Name())   OnClickSellLimit();
      else if(sparam == m_sellStopButton.Name())    OnClickSellStop();
      else if(sparam == m_closeAllButton.Name())    OnClickCloseAll();
      else if(sparam == m_closeProfitButton.Name()) OnClickCloseProfit();
      else if(sparam == m_closeLossButton.Name())   OnClickCloseLoss();
      else if(sparam == m_closeBuyButton.Name())    OnClickCloseBuy();
      else if(sparam == m_closeSellButton.Name())   OnClickCloseSell();
      else if(sparam == m_deleteAllOrdersButton.Name())  OnClickDeleteAllOrders();
      else if(sparam == m_deleteLimitOrdersButton.Name()) OnClickDeleteLimitOrders();
      else if(sparam == m_deleteStopOrdersButton.Name())   OnClickDeleteStopOrders();
      else if(sparam == m_deleteStopLimitOrdersButton.Name()) OnClickDeleteStopLimitOrders();
      else if(sparam == m_resetButton.Name())       OnClickReset(); // Handle reset button click
   }
   return true;
}

6. Проверка и исполнение рыночных ордеров

Перед исполнением сделки система проверяет введенные параметры. Проверяется объем сделки и корректность уровней стоп-лосс и тейк-профит относительно текущей рыночной цены.

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

//+------------------------------------------------------------------+
//| Validate Buy order parameters (Market orders)                    |
//+------------------------------------------------------------------+
bool CTradeManagementPanel::ValidateBuyParameters(double volume, double tp, double sl, double ask)
{
   bool valid = true;
   m_tpErrorLabel.Text("");
   m_slErrorLabel.Text("");
   if(volume <= 0)
   {
      m_tpErrorLabel.Text("Invalid volume");
      m_tpErrorLabel.Color(clrRed);
      valid = false;
   }
   if(tp != 0 && tp <= ask)
   {
      m_tpErrorLabel.Text("Invalid TP");
      m_tpErrorLabel.Color(clrRed);
      valid = false;
   }
   if(sl != 0 && sl >= ask)
   {
      m_slErrorLabel.Text("Invalid SL");
      m_slErrorLabel.Color(clrRed);
      valid = false;
   }
   return valid;
}

//+------------------------------------------------------------------+
//| Validate Sell order parameters (Market orders)                   |
//+------------------------------------------------------------------+
bool CTradeManagementPanel::ValidateSellParameters(double volume, double tp, double sl, double bid)
{
   bool valid = true;
   m_tpErrorLabel.Text("");
   m_slErrorLabel.Text("");
   if(volume <= 0)
   {
      m_tpErrorLabel.Text("Invalid volume");
      m_tpErrorLabel.Color(clrRed);
      valid = false;
   }
   if(tp != 0 && tp >= bid)
   {
      m_tpErrorLabel.Text("Invalid TP");
      m_tpErrorLabel.Color(clrRed);
      valid = false;
   }
   if(sl != 0 && sl <= bid)
   {
      m_slErrorLabel.Text("Invalid SL");
      m_slErrorLabel.Color(clrRed);
      valid = false;
   }
   return valid;
}

7. Управление отложенными ордерами и дополнительные параметры

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

Вспомогательная функция интерпретирует настройки срока таких ордеров — фиксированное время или Good Till Canceled. То есть система проверяет корректность отложенных ордеров перед отправкой.

//+------------------------------------------------------------------+
//| Helper: Parse expiration input                                   |
//+------------------------------------------------------------------+
void ParseExpiration(string sExp, ENUM_ORDER_TYPE_TIME &type_time, datetime &expiration)
{
   if(StringCompare(StringToUpper(sExp), "GTC") == 0)
   {
      type_time = ORDER_TIME_GTC;
      expiration = 0;
   }
   else
   {
      type_time = ORDER_TIME_SPECIFIED;
      expiration = StringToTime(sExp);
   }
}

//+------------------------------------------------------------------+
//| Button click handlers - Pending Orders                           |
//+------------------------------------------------------------------+
void CTradeManagementPanel::OnClickBuyLimit()
{
   double price  = StringToDouble(m_buyLimitPriceEdit.Text());
   double tp     = StringToDouble(m_buyLimitTPEdit.Text());
   double sl     = StringToDouble(m_buyLimitSLEdit.Text());
   double volume = StringToDouble(m_volumeEdit.Text());
   string sExp   = m_buyLimitExpEdit.Text();
   ENUM_ORDER_TYPE_TIME type_time;
   datetime expiration;
   ParseExpiration(sExp, type_time, expiration);
   m_trade.BuyLimit(volume, price, Symbol(), sl, tp, type_time, expiration, "");
}

void CTradeManagementPanel::OnClickBuyStop()
{
   double price  = StringToDouble(m_buyStopPriceEdit.Text());
   double tp     = StringToDouble(m_buyStopTPEdit.Text());
   double sl     = StringToDouble(m_buyStopSLEdit.Text());
   double volume = StringToDouble(m_volumeEdit.Text());
   string sExp   = m_buyStopExpEdit.Text();
   ENUM_ORDER_TYPE_TIME type_time;
   datetime expiration;
   ParseExpiration(sExp, type_time, expiration);
   m_trade.BuyStop(volume, price, Symbol(), sl, tp, type_time, expiration, "");
}

void CTradeManagementPanel::OnClickSellLimit()
{
   double price  = StringToDouble(m_sellLimitPriceEdit.Text());
   double tp     = StringToDouble(m_sellLimitTPEdit.Text());
   double sl     = StringToDouble(m_sellLimitSLEdit.Text());
   double volume = StringToDouble(m_volumeEdit.Text());
   string sExp   = m_sellLimitExpEdit.Text();
   ENUM_ORDER_TYPE_TIME type_time;
   datetime expiration;
   ParseExpiration(sExp, type_time, expiration);
   m_trade.SellLimit(volume, price, Symbol(), sl, tp, type_time, expiration, "");
}

void CTradeManagementPanel::OnClickSellStop()
{
   double price  = StringToDouble(m_sellStopPriceEdit.Text());
   double tp     = StringToDouble(m_sellStopTPEdit.Text());
   double sl     = StringToDouble(m_sellStopSLEdit.Text());
   double volume = StringToDouble(m_volumeEdit.Text());
   string sExp   = m_sellStopExpEdit.Text();
   ENUM_ORDER_TYPE_TIME type_time;
   datetime expiration;
   ParseExpiration(sExp, type_time, expiration);
   m_trade.SellStop(volume, price, Symbol(), sl, tp, type_time, expiration, "");
}

8. Реализация массовых операций

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

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

//+------------------------------------------------------------------+
//| Button click handlers - All Order Operations                     |
//+------------------------------------------------------------------+
void CTradeManagementPanel::OnClickCloseAll()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickCloseProfit()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetDouble(POSITION_PROFIT) > 0)
         m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickCloseLoss()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetDouble(POSITION_PROFIT) < 0)
         m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickCloseBuy()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
         m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickCloseSell()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
         m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickDeleteAllOrders()
{
   for(int i = OrdersTotal()-1; i >= 0; i--)
      m_trade.OrderDelete(OrderGetTicket(i));
}

void CTradeManagementPanel::OnClickDeleteLimitOrders()
{
   for(int i = OrdersTotal()-1; i >= 0; i--)
      if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_LIMIT ||
         OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_LIMIT)
         m_trade.OrderDelete(OrderGetTicket(i));
}

void CTradeManagementPanel::OnClickDeleteStopOrders()
{
   for(int i = OrdersTotal()-1; i >= 0; i--)
      if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP ||
         OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
         m_trade.OrderDelete(OrderGetTicket(i));
}

void CTradeManagementPanel::OnClickDeleteStopLimitOrders()
{
   for(int i = OrdersTotal()-1; i >= 0; i--)
      if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP_LIMIT ||
         OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP_LIMIT)
         m_trade.OrderDelete(OrderGetTicket(i));
}

9. Добавим функций сброса и управления видимостью

Помимо основных функций добавим еще две небольшие, но полезные. Первая — кнопка сброса, которая очищает все поля ввода и возвращает панель в состояние по умолчанию. Это удобно, когда хочется начать настройку заново.

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

//+------------------------------------------------------------------+
//| Reset all input fields to default values                         |
//+------------------------------------------------------------------+
void CTradeManagementPanel::OnClickReset()
{
   // Reset volume to minimum allowed
   double minVolume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   m_volumeEdit.Text(DoubleToString(minVolume, 2));

   // Reset TP and SL for market orders
   m_tpEdit.Text("0.00000");
   m_slEdit.Text("0.00000");

   // Reset pending order prices to current ASK/BID
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   m_buyLimitPriceEdit.Text(DoubleToString(ask, 5));
   m_buyStopPriceEdit.Text(DoubleToString(ask, 5));
   m_sellLimitPriceEdit.Text(DoubleToString(bid, 5));
   m_sellStopPriceEdit.Text(DoubleToString(bid, 5));

   // Reset pending order TP/SL to 0
   m_buyLimitTPEdit.Text("0.00000");
   m_buyLimitSLEdit.Text("0.00000");
   m_buyStopTPEdit.Text("0.00000");
   m_buyStopSLEdit.Text("0.00000");
   m_sellLimitTPEdit.Text("0.00000");
   m_sellLimitSLEdit.Text("0.00000");
   m_sellStopTPEdit.Text("0.00000");
   m_sellStopSLEdit.Text("0.00000");

   // Reset expiration dates to current day's end
   datetime now = TimeCurrent();
   string exp_default = TimeToString(now, TIME_DATE) + " 23:59:59";
   m_buyLimitExpEdit.Text(exp_default);
   m_buyStopExpEdit.Text(exp_default);
   m_sellLimitExpEdit.Text(exp_default);
   m_sellStopExpEdit.Text(exp_default);

   // Clear error messages
   m_tpErrorLabel.Text("");
   m_slErrorLabel.Text("");
}

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


Интеграция с панелью администратора New_Admin_Panel

1. Подключение файла Trade Management в нашу панель New_Admin_Panel

Сначала подключим TradeManagementPanel, добавив его header-файл. После этого все необходимые компоненты для исполнения и управления торговыми операциями будут доступны на нашей панели администратора. Просто, но важно.

#include <TradeManagementPanel.mqh>

2. Настройка экземпляра торговой панели

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

CTradeManagementPanel  *g_tradePanel = NULL;

3. Добавляем кнопку управления торговлей

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

CButton g_tradeMgmtButton;

4. Кликабельность торговой панели

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

  1. Если торговая панель еще не существует, ее нужно создать.
  2. Если она уже создана — просто переключаем ее состояние (показать / скрыть).

void HandleTradeManagement()
{
   if(g_tradePanel == NULL)
   {
      g_tradePanel = new CTradeManagementPanel();
      if(!g_tradePanel.Create(g_chart_id, "TradeManagementPanel", g_subwin, 310, 20, 310+565, 20+510))
      {
         delete g_tradePanel;
         g_tradePanel = NULL;
         Print("Failed to create Trade Management Panel");
         return;
      }
   }
   g_tradePanel.Toggle();
   ChartRedraw();
}

Таким образом, пользователь получает мгновенный доступ к панели одним нажатием кнопки.

5. Подключаем обработку событий

Торговая панель должна реагировать на действия пользователя. Внутри обработчика событий будем отправлять действия пользователя на панель, если она открыта. Так панель будет реагировать в реальном времени на разные события: отправка ордеров, настройка уровней стоп-лосса или закрытие сделок.

if(g_tradePanel != NULL && g_tradePanel.IsVisible())
   return g_tradePanel.OnEvent(id, lparam, dparam, sparam);

6. Очистка ресурсов при завершении работы

При удалении советника необходимо освободить память, чтобы не тратить ненужные ресурсы. Это означает, что нам нужно корректно уничтожить торговую панель. 

if(g_tradePanel != NULL)
{
   g_tradePanel.Destroy(reason);
   delete g_tradePanel;
   g_tradePanel = NULL;
}

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


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

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

Использование панели управления торговлей


Заключение

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

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

Также хочу выразить благодарность пользователю Omega J Msigwa за вклад в библиотеку кодов. Его исходник Informative Dashboard подарил ценные идеи в процессе данной разработки. Исходный код, прикрепленный к этой статье, свободно доступен — берите его, используйте, меняйте, если нужно.

Файл Описание
TradeManagementPanel.mqh Исходный код заголовка класса, предоставляющий графический интерфейс для отправки, управления и изменения сделок в советнике New_Admin_Panel. Его можно включать в собственные советники.
New_Admin_Panel.mq5 Исходный код советника New_Admin_Panel. Создает центральный интерфейс для управления торговлей, общением, аналитикой и другими администраторскими функциями для работы в рамках торговой платформы.

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

Прикрепленные файлы |
New_Admin_Panel.mq5 (17.12 KB)
Искусство ведения логов (Часть 6): Сохранение логов в базу данных Искусство ведения логов (Часть 6): Сохранение логов в базу данных
В статье рассматривается использование баз данных для структурированного и масштабируемого хранения журналов событий. В ней рассматриваются основные понятия, ключевые операции, настройка и реализация обработчика баз данных на языке MQL5. В заключение, подтверждаются полученные результаты и подчеркиваются преимущества описанного подхода для оптимизации и эффективного мониторинга.
Возможности Мастера MQL5, которые вам нужно знать (Часть 59): Обучение с подкреплением (DDPG) совместно с паттернами скользящей средней и стохастика Возможности Мастера MQL5, которые вам нужно знать (Часть 59): Обучение с подкреплением (DDPG) совместно с паттернами скользящей средней и стохастика
В продолжение нашей предыдущей статьи о DDPG с использованием скользящей средней и стохастических индикаторов мы рассматриваем другие ключевые классы обучения с подкреплением, имеющие решающее значение для реализации DDPG. Хотя мы в основном пишем код на Python, конечный продукт — обученная нейронная сеть — будет экспортирован в формате ONNX в MQL5, где мы интегрируем его в качестве ресурса в советник, созданный в Мастере.
Разработка инструментария для анализа движения цен (Часть 19): ZigZag Analyzer Разработка инструментария для анализа движения цен (Часть 19): ZigZag Analyzer
Для анализа движения цены вручную трейдры используют линии тренда для подтверждения направления и определения потенциальных уровней разворота или продолжения тренда. В этой серии, где мы разрабатываем инструментарий для анализа движения цен, мы представляем инструмент который строит наклонные трендовые линий для удобного анализа рынка. Он четко обозначает ключевые тренды и уровни, необходимые для эффективной оценки ценового движения.
Нейросети в трейдинге: Асинхронная обработка событий в потоковых моделях (Окончание) Нейросети в трейдинге: Асинхронная обработка событий в потоковых моделях (Окончание)
В статье реализован событийный фреймворк EVA-Flow на MQL5 с объектом верхнего уровня CNeuronEVAFlow, встроенным в иерархию потоковых нейронов. Показаны подготовка, кодирование, первичное приближение потока и декодирование в режиме реального времени. Тесты на исторических и независимых данных MetaTrader 5 подтвердили контролируемые риски и положительное матожидание, что делает архитектуру пригодной для практического использования в стратегиях.