Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (IV). Класс для панели управления торговлей
Содержание
- Введение
- Общая информация
- Класс панели управления сделками (CTradeManagementPanel)
- Интеграция с панелью администратора New_Admin_Panel
- Тестирование
- Заключение
Введение
В прошлой статье и в нескольких более ранних мы говорили об организации кода. Хорошо организованный код проще поддерживать и развивать, поэтому мы уделяем этому такое внимание. Я использую принцип разделения ответственности "separation of concerns". Такой подход позволяет работать с отдельными элементами внутри NewAdminPanel, а сама панель остается модульной и хорошо структурированной. Мы независимо работаем над отдельными элементами и поэтому можем оттачивать ее и добиваться лучшего результата.
Изначально возможности нашей панели были не такими богатыми. Сейчас же она стала гораздо мощнее. Теперь у нас есть гибкость при выполнении быстрых торговых операций. Кроме того, прямо в панели появилось управление рисками — всё в одном интерфейсе. Также прямо с панели теперь можно настраивать отложенные ордера.
Обновление панели решает две основные задачи:
- Создание больших, хорошо организованных программ, которые проще разрабатывать и сопровождать.
- Быстрый доступ к торговым операциям в универсальном советнике, который объединяет в себе интерфейс коммуникации, интерфейс управления торговлей и, в ближайшем будущем, аналитический интерфейс.
Начнем с обзора текущего состояния и того, как мы будем двигаться к реализации этих улучшений до конца.
Общая информация
Основная цель этой серии — применять 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. Разделяем пользовательский интерфейс на логические секции
Согласитесь, хорошо организованным интерфейсом удобно пользоваться. Мы не будем размещать кнопки и поля ввода как попало, а поделим панель на три основные секции.
- Первая секция предназначена для быстрых рыночных ордеров, где пользователь может быстро совершить покупку или продажу, задать объем сделки и настроить уровни стоп-лосс и тейк-профит.
- Вторая секция предназначена для отложенных ордеров. Здесь пользователь указывает цену, время истечения и тип ордера, например Buy Limit или Sell Stop.
- Третья секция будет для массовых операций: закрыть все сделки, закрыть только прибыльные, удалить все отложенные ордера. Такое разделение на группы делает интерфейс более интуитивным и удобным для навигации.
//+------------------------------------------------------------------+ //| 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. Кликабельность торговой панели
Здесь начинается самое интересное. Мы создали функцию, которая управляет жизненным циклом торговой панели.
- Если торговая панель еще не существует, ее нужно создать.
- Если она уже создана — просто переключаем ее состояние (показать / скрыть).
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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Искусство ведения логов (Часть 6): Сохранение логов в базу данных
Возможности Мастера MQL5, которые вам нужно знать (Часть 59): Обучение с подкреплением (DDPG) совместно с паттернами скользящей средней и стохастика
Разработка инструментария для анализа движения цен (Часть 19): ZigZag Analyzer
Нейросети в трейдинге: Асинхронная обработка событий в потоковых моделях (Окончание)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования