
Создаем интерактивную MQL5-панель с использованием класса Controls (Часть 2): Добавление отзывчивости кнопок
Введение
В предыдущей статье, мы успешно создали основные компоненты нашей панели на языке MetaQuotes Language 5. На этом этапе собранные нами кнопки и метки оставались статичными, обеспечивая базовую, но неактивную структуру. Теперь пришло время выйти за визуальные рамки. В текущей статье мы сосредоточимся на том, чтобы сделать панель по-настоящему интерактивной. Мы вдохнем жизнь в компоненты, добавив возможность реагировать на действия пользователя, превратив нашу панель управления в динамичный инструмент, готовый к торговому взаимодействию в реальном времени.
Мы рассмотрим автоматизацию функционала кнопок, созданных в первой части, чтобы они реагировали на нажатие и редактирование. Мы узнаем, как настраивать события, которые запускают определенные действия, позволяя пользователю осмысленно взаимодействовать с панелью. Мы рассмотрим ключевые темы, в том числе:
- Элементы, подлежащие автоматизации - подробный обзор компонентов, которые получат функциональность.
- Автоматизация взаимодействия с графическим интерфейсом пользователя в MQL5 - необходимый код для эффективного реагирования кнопок на действия пользователя.
- Заключение - подведение итогов.
Давайте углубимся в эти темы, чтобы улучшить наш торговый интерфейс!
Элементы, подлежащие автоматизации
Мы сосредоточимся на автоматизации кнопок, которые мы создали в первой части нашей панели MQL5. Каждая кнопка имеет определенную функцию, и мы хотим убедиться, что они интуитивно понятны и реагируют на команды пользователя. Такая реакция необходима, поскольку, в отличие от программы, работающей в фоновом режиме, торговая панель должна быть удобной и доступной для пользователя. Во-первых, у нас есть кнопка в правом верхнем углу панели, которая предназначена для закрытия всего интерфейса. Если торговая среда открыта на графике MetaTrader 5, должна быть возможность закрыть панель так же, как закрывается приложение.
Пока кнопка торговли активна, мы разместим кнопки, которые выполняют определенные торговые операции. К ним относятся Buy (покупка), Sell (продажа), Sell Stop (стоп-ордер на продажу), Sell Limit (лимитный ордер на продажу), Buy Stop (стоп-ордер на покупку) и Buy Limit (лимитный ордер на покупку). Эти кнопки позволят быстро размещать ордера и будут способствовать немедленному реагированию на постоянно меняющийся рынок. Мы также автоматизируем кнопки закрытия, которые фактически будут управлять сделками, когда кнопка закрытия активна. Среди них есть Close All (закрыть все) и Close All Profit Trades (закрыть все прибыльные сделки), а также одна, при упоминании которой у меня буквально сводит пальцы - Close All Pending Orders (закрыть все отложенные ордера).
Наконец, мы автоматизируем кнопку информации, при нажатии на которую разворачивается интерфейс кнопок, содержащий подробную информацию об учетной записи пользователя и дополнительную информацию. Это поможет информировать трейдеров о важных деталях, связанных с их счетами, тем самым помогая им принимать более обоснованные решения. Цель всего этого — создать адаптивную торговую панель, которая упрощает выполнение операций, необходимых трейдеру.
Чтобы облегчить понимание этих процессов и компонентов автоматизации, ниже приводится их подробное описание с учетом предыдущего этапа.
Приступим к автоматизации. Если вы еще не знаете, как создать статическую сборку элементов графического интерфейса, рекомендую прочитать предыдущую статью.
Автоматизация взаимодействия с графическим интерфейсом в MQL5
Мы перейдем от простых процессов к сложным, чтобы наша структура была организована в хронологическом порядке. Мы будем обновлять информацию по счету при каждом изменении тика или котировки цены. Для этого нам понадобится обработчик событий OnTick, встроенная MQL5-функция, которая обычно вызывается при изменении котировок цен. Функция имеет тип данных void, что означает, что она обрабатывает выполнения напрямую и не должна возвращать никаких выходных данных. Функция должна выглядеть примерно так, как показано ниже.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ ... } //+------------------------------------------------------------------+
Это обработчик событий, который отвечает за обновление цен и, таким образом, является основой нашей логики. Мы добавим логику управления к этой функции, как показано ниже:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Start of the OnTick function, called on every price tick //--- Check if the background color of the INFO button is yellow if (obj_Btn_INFO.ColorBackground() == clrYellow) { //--- Update the account equity display on the panel obj_Btn_ACC_EQUITY.Text(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2)); //--- Update the account balance display on the panel obj_Btn_ACC_BALANCE.Text(DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2)); //--- Update the server trade time display on the panel obj_Btn_TIME.Text(TimeToString(TimeTradeServer(), TIME_DATE | TIME_SECONDS)); } //--- End of the OnTick function } //+------------------------------------------------------------------+
Первое действие, которое мы выполняем в функции OnTick, — это проверка цвета фона информационной кнопки (obj_Btn_INFO). Если фон кнопки желтый, это означает, что мы активировали режим отображения информации. При выполнении этого условия мы обновим различные отображения на панели, связанные с аккаунтом. В частности, мы обновляем отображение баланса счета, извлекая текущий баланс с помощью функции AccountInfoDouble и передавая свойство ACCOUNT_EQUITY в качестве входного параметра и форматируя его до двух десятичных знаков с помощью функции DoubleToString. Затем мы присваиваем это значение тексту кнопки obj_Btn_ACC_EQUITY, гарантируя, что у нас всегда под рукой самая последняя информация об эквити.
Далее мы аналогичным образом обновляем отображение баланса счета, извлекая его с помощью свойства параметра ACCOUNT_BALANCE, форматируя до двух десятичных знаков и устанавливая его в качестве текста кнопки obj_Btn_ACC_BALANCE. Наконец, мы обновляем отображение времени сделки на сервере, получая текущее время сделки на сервере с помощью функции TimeTradeServer, отформатировав ее так, чтобы она включала как дату, так и секунды, и обновив текст кнопки obj_Btn_TIME этим значением. Это гарантирует, что мы всегда в курсе последних торговых событий, что необходимо для своевременного принятия решений в торговых сценариях. Ниже представлены результаты.
Из визуализации видно, что поле времени обновляется нужным образом. Итак, первый компонент автоматизации готов. Пока всё выглядит довольно легко. Перейдем к остальным компонентам нашей графической панели. Автоматизация остальных элементов будет осуществляться внутри обработчика функции OnChartEvent, поэтому давайте подробнее рассмотрим его входные параметры, а также его функции.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ... }
Целью функции является обработка изменений графика, внесенных пользователем или MQL5-программой. Таким образом, взаимодействия, которые будет выполнять пользователь, такие как перемещение мыши, редактирование полей кнопок и нажатие меток и кнопок, будут фиксироваться и обрабатываться этим обработчиком событий. Давайте разберем его аргументы для более глубокой интерпретации:
- id - параметр представляет собой идентификатор события и соответствует одному из 11 предопределенных типов событий. К ним относятся такие события, как нажатия клавиш, движения мыши, создание объектов, изменения графиков и пользовательские события. Для пользовательских событий можно использовать идентификаторы от CHARTEVENT_CUSTOM до CHARTEVENT_CUSTOM_LAST. Ниже показаны 11 типов событий:
- lparam - параметр события типа long. Его значение зависит от конкретного обрабатываемого события. Например, он может представлять собой код клавиши во время события нажатия клавиши.
- dparam - параметр события типа double. Подобно lparam, его значение меняется в зависимости от типа события. Например, во время движения мыши он может передавать положение курсора мыши.
- sparam - параметр события строкового типа. Опять же, его значение зависит от события. Например, при создании объекта он может содержать имя только что созданного объекта.
Для более наглядной демонстрации внутри функции сделаем распечатку, содержащую все четыре аргумента журнала.
// Print the 4 function parameters Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);
Функция выведет идентификатор события графика, его значение события типа long, значение события типа double и строковое значение. Давайте посмотрим на следующий GIF-файл для лучшего понимания.
Из представленного GIF-изображения теперь все должно быть ясно. Перейдем к регистрации щелчков мыши на элементах панели графического интерфейса. Таким образом, нашим идентификатором будет CHARTEVENT_OBJECT_CLICK.
//Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); if (id==CHARTEVENT_OBJECT_CLICK){ //--- }
Сначала мы закомментируем предыдущую строку кода, чтобы не засорять наш журнал ненужной информацией. Используемые две косые черты (//) называются однострочными комментариями и комментируют код с начала и продолжают до конца строки, отсюда и их название — "однострочные" комментарии. Комментарии игнорируются компьютером во время выполнения. Мы используем оператор if, чтобы проверить, был ли щелчок по объекту. Это достигается путем приравнивания идентификатора события графика к перечислениям щелчков по объектам. После щелчка по объекту давайте выведем аргументы и посмотрим на результаты. Используется следующий код.
if (id==CHARTEVENT_OBJECT_CLICK){ Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam); ... }
В функции распечатки мы просто изменили LPARAM на LP и DPARAM на DP, чтобы сосредоточиться только на идентификаторе события графика и имени выбранного объекта. Оттуда мы получим идентификатор объекта и при необходимости предпримем действие. Ниже приведена иллюстрация логики:
Первая кнопка, которую мы автоматизируем, — это кнопка Trade.
if (sparam==obj_Btn_TRADE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name()); // Reset the pressed states of all buttons to ensure only one button appears pressed at a time obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Change the background color of the Trade button to yellow to indicate it is active obj_Btn_TRADE.ColorBackground(clrYellow); // Set the background color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBackground(clrSilver); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to yellow to match its background obj_Btn_TRADE.ColorBorder(clrYellow); // Set the border color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBorder(clrSilver); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Close section if it exists destroySection_Close(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Trade section, bringing it to the forefront createSection_Trade(); }
Здесь мы управляем взаимодействием при нажатии кнопки Trade, проверяя, совпадает ли строковый параметр с названием кнопки Trade, используя функцию Name. Сначала используем функцию Print для фиксации имени нажатого объекта, что помогает при отладке, подтверждая, какая кнопка была нажата. Затем мы сбрасываем нажатое состояние всех соответствующих кнопок — obj_Btn_TRADE, obj_Btn_CLOSE и obj_Btn_INFO — вызывая функцию Pressed для каждой кнопки и передавая флаг false. This ensures that only one button is visually pressed at a time. После сброса мы визуально указываем, что кнопка Trade активна, изменяя цвет ее фона на желтый с помощью функции ColorBackground, а цвет фона кнопок Close и Info устанавливаем серебристым, чтобы показать, что они неактивны. Аналогично мы обновляем цвета границ с помощью функции ColorBorder — желтый для кнопки Trade и серебристый для остальных.
Затем мы очищаем интерфейс, вызывая функции destroySection_Close и destroySection_Information, чтобы удалить любой контент, отображаемый для разделов Close или Information. Наконец, мы вызываем функцию createSection_Trade для динамического создания и отображения торгового раздела, гарантируя пользователю доступ к торговому интерфейсу. Аналогичное действие мы проделываем и с кнопкой Close.
// Check if the clicked object is the Close button else if (sparam==obj_Btn_CLOSE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade button to silver, indicating it's inactive obj_Btn_TRADE.ColorBackground(clrSilver); // Change the background color of the Close button to yellow to indicate it is active obj_Btn_CLOSE.ColorBackground(clrYellow); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to silver obj_Btn_TRADE.ColorBorder(clrSilver); // Set the border color of the Close button to yellow obj_Btn_CLOSE.ColorBorder(clrYellow); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Close section, bringing it to the forefront createSection_Close(); }
Здесь мы делаем почти ту же логику, что и с кнопкой Trade. Мы обрабатываем сценарий нажатия кнопки Close, проверяя, соответствует ли строковый параметр названию кнопки Close, используя функцию Name. Сначала мы фиксируем имя нажатой кнопки с помощью функции Print для отладки. Это помогает нам подтвердить, что была нажата правильная кнопка. Далее мы сбрасываем нажатые состояния кнопок obj_Btn_TRADE, obj_Btn_CLOSE и obj_Btn_INFO с помощью функции Pressed. Это гарантирует, что ни одна из кнопок не останется визуально нажатой после нажатия. Затем мы обновляем интерфейс, изменяя цвет фона кнопки Close на желтый с помощью функции ColorBackground, указывая на ее активность, а также делая цвет фона кнопок Trade и Information серебристым, чтобы указать на то, что они неактивны.
Аналогично настраиваем цвета границ кнопок с помощью функции ColorBorder, делая границу кнопки Close желтой, чтобы она соответствовала ее фону, а границы кнопок Trade и Information устанавливаем серебристыми, чтобы указать на то, что они неактивны. Для очистки вызываем функции destroySection_Trade и destroySection_Information, чтобы удалить любой существующий контент для торгового или информационного разделов. Наконец, вызываем функцию createSection_Close для динамического создания и отображения интерфейса, связанного с закрытием позиций или ордеров, гарантируя пользователю возможность взаимодействовать с соответствующими опциями закрытия на панели. Кнопка Information использует ту же логику. Фрагмент кода выглядит следующим образом:
// Check if the clicked object is the Information button else if (sparam==obj_Btn_INFO.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_INFO.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade and Close buttons to silver, indicating they are inactive obj_Btn_TRADE.ColorBackground(clrSilver); obj_Btn_CLOSE.ColorBackground(clrSilver); // Change the background color of the Info button to yellow to indicate it is active obj_Btn_INFO.ColorBackground(clrYellow); // Set the border color of the Trade and Close buttons to silver obj_Btn_TRADE.ColorBorder(clrSilver); obj_Btn_CLOSE.ColorBorder(clrSilver); // Set the border color of the Info button to yellow obj_Btn_INFO.ColorBorder(clrYellow); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Close section if it exists destroySection_Close(); // Create the Information section, bringing it to the forefront createSection_Information(); }
При компиляции и запуске программы мы получаем следующий вывод при взаимодействии с кнопкой управления.
Всё работает. Теперь мы хотим удалить панель при нажатии кнопки Windows.
// Check if the clicked object is the exit button (X button) else if (sparam==obj_Btn_X.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_X.Name()); // Call functions to destroy all sections, effectively closing the entire panel destroySection_Trade(); destroySection_Close(); destroySection_Information(); // Call a function to destroy the main panel itself destroySection_Main_Panel(); }
Здесь мы обрабатываем случай нажатия кнопки выхода, проверяя, соответствует ли строковый параметр названию кнопки выхода, используя функцию Name. Сначала мы фиксируем имя выбранного объекта для отладки с помощью функции Print, которая помогает подтвердить, что кнопка выхода была нажата.
Далее мы приступаем к закрытию всей панели, вызывая несколько функций. Сначала мы вызываем destroySection_Trade, destroySection_Close и destroySection_Information, чтобы удалить все разделы, связанные с торговлей, закрытием позиций или отображением информации о счете, если они в данный момент отображаются. Эти функции обеспечивают правильную очистку всех интерактивных разделов панели. Наконец, мы вызываем функцию destroySection_Main_Panel, которая отвечает за удаление самой главной панели. Это удаляет все элементы панели из интерфейса, полностью закрывая панель.
Теперь нам необходимо обрабатывать торговые операции при нажатии соответствующих кнопок. Чтобы сделать это легко, нам нужно включить экземпляр класса, который поможет в этом. Таким образом, мы включаем торговый экземпляр, используя #include в начале исходного кода. Это дает нам доступ к классу CTrade, который мы используем для создания торгового объекта, что необходимо для осуществления торговых операций.
#include <Trade/Trade.mqh>
CTrade obj_Trade;
Препроцессор заменит строку #include<Trade/Trade.mqh> содержимым файла Trade.mqh. Угловые скобки указывают, что файл Trade.mqh будет взят из стандартного каталога (обычно это каталог_установки_терминала\MQL5\Include). Текущий каталог не включен в поиск. Строку можно разместить в любом месте программы, но обычно все включения размещаются в начале исходного кода для лучшей структуры кода и удобства ссылок. Благодаря разработчикам MQL5 объявление объекта obj_Trade класса CTrade предоставит нам легкий доступ к методам, содержащимся в этом классе.
После включения торговой библиотеки мы можем приступить к реализации логики открытия позиции на продажу при нажатии кнопки Sell. Логика представлена ниже.
else if (sparam==obj_Btn_SELL.Name()){ //--- Check if the Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Bid; //--- Set the entry price for selling to the current bid price double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the sell order }
Здесь, как только мы подтверждаем, что кнопка Sell была нажата, мы фиксируем событие нажатия кнопки с помощью функции Print для отладки. При этом выводится имя нажатой кнопки. Затем мы приступаем к сбору основных данных о торговле. Сначала мы извлекаем и нормализуем текущие цены ask и bid, используя функцию SymbolInfoDouble, которая извлекает рыночные цены. Функция NormalizeDouble гарантирует, что эти цены будут правильно отформатированы в соответствии с количеством знаков после запятой, определенным _Digits.
Далее получаем размер лота из поля ввода, преобразуя текст из поля Lots в число с помощью функции StringToDouble. Цена входа устанавливается на уровне текущей цены bid, поскольку ордер на продажу использует эту цену для исполнения. Мы также рассчитываем значения стоп-лосса и тейк-профита на основе введенных пользователем данных. Стоп-лосс рассчитывается путем добавления пользовательского значения (из поля ввода SL) к цене ask, скорректированной на минимальный прирост цены символа - _Point. Аналогично, тейк-профит рассчитывается путем вычитания заданного пользователем значения (из поля ввода TP) из цены ask.
Наконец, мы фиксируем данные ордера, такие как размер лота, цена входа, стоп-лосс и тейк-профит, используя функцию Print. Ордер на продажу исполняется путем вызова функции Sell из объекта obj_Trade с передачей необходимых параметров: размера лота, символа, цены входа, стоп-лосса и тейк-профита. Для позиции на покупку используется аналогичная логика, как показано ниже.
else if (sparam==obj_Btn_BUY.Name()){ //--- Check if the Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Ask; //--- Set the entry price for buying to the current ask price double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the buy order }
При компиляции и тестировании прогресса мы получаем следующий вывод.
Для открытия лимитных и стоп-ордеров используется схожая логика. Однако, поскольку они не используют напрямую текущие рыночные котировки, нам потребуется настроить и включить дополнительный алгоритм для проверки безопасности. Начнем с кнопки Sell Stop.
else if (sparam==obj_Btn_SELLSTOP.Name()){ //--- Check if the Sell Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid - stopslevel*_Point; //--- Calculate the valid price for placing a sell stop order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell stop order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell stop order } }
Здесь мы обрабатываем логику автоматизации при нажатии кнопки Sell Stop, проверяя, соответствует ли параметр переменной строки имени кнопки Sell Stop при использовании функции Name. Как только мы это подтвердим, мы как обычно регистрируем событие с помощью функции Print, выводя имя нажатой кнопки для отладки. Начнем с извлечения и нормализации цен ask и bid с помощью функции SymbolInfoDouble для извлечения рыночных цен и NormalizeDouble для их форматирования по количеству знаков после запятой, определяемому _Digits.
Далее мы получаем "заданную пользователем цену" из поля ввода Price, преобразуя ее из текста в числовое значение с помощью функции StringToDouble. Кроме того, мы извлекаем минимальный уровень стопа для символа, который необходим для проверки стоп-ордеров, используя SymbolInfoInteger с параметром SYMBOL_TRADE_STOPS_LEVEL. Мы рассчитываем действительную цену путем вычитания уровня стопа (переведенного в пункты) из цены bid.
Затем мы проверяем, является ли "цена, определенная пользователем" действительной, сравнивая ее с рассчитанной действительной ценой. Если цена пользователя превышает допустимый диапазон, мы регистрируем сообщение об ошибке, указывающее на "недопустимую цену стопов". Если указанная пользователем цена действительна, мы переходим к следующим шагам. Мы извлекаем размер лота из поля ввода, используя StringToDouble для преобразования текста в число. Цена входа устанавливается на уровне цены, определенной пользователем. Затем мы рассчитываем стоп-лосс, добавляя заданное пользователем значение стоп-лосса (из поля SL) к цене входа и корректируя его на основе прироста цены символа _Point. Аналогично, тейк-профит рассчитывается путем вычитания заданного пользователем значения (из поля TP) из цены входа.
Наконец, мы фиксируем данные ордера, включая размер лота, "цену входа", "стоп-лосс" и "тейк-профит", используя функцию Print. Стоп-ордер на продажу выполняется путем вызова функции Sell Stop из объекта obj_Trade, передачи соответствующих параметров, таких как размер лота, цена входа, символ, стоп-лосс и тейк-профит, а также размещения стоп-ордера на продажу на основании инструкций пользователя. Для выполнения остальных ордеров мы используем аналогичный подход.
else if (sparam==obj_Btn_BUYSTOP.Name()){ //--- Check if the Buy Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask + stopslevel*_Point; //--- Calculate the valid price for placing a buy stop order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy stop order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy stop order } } else if (sparam==obj_Btn_SELLLIMIT.Name()){ //--- Check if the Sell Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid + stopslevel*_Point; //--- Calculate the valid price for placing a sell limit order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell limit order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell limit order } } else if (sparam==obj_Btn_BUYLIMIT.Name()){ //--- Check if the Buy Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask - stopslevel*_Point; //--- Calculate the valid price for placing a buy limit order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy limit order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy limit order } }
При тестировании кнопок мы получаем следующие выходные данные.
Всё работает как надо. Переходим к закрытию позиций и удалению отложенных ордеров. Начнем с логики закрытия всех открытых рыночных позиций.
else if (sparam==obj_Btn_CLOSE_ALL.Name()){ //--- Check if the Close All button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol obj_Trade.PositionClose(pos_ticket); //--- Close the position } } } } }
Здесь мы просто обрабатываем логику для автоматизации кнопки Close All, которая при нажатии закроет все активные позиции. Начнем с проверки того, была ли нажата кнопка, и запишем событие в журнал, выведя название нажатой кнопки для целей отладки с помощью функции Print. Далее инициируем цикл for, который перебирает все открытые позиции на счете, используя функцию PositionsTotal, которая возвращает общее количество открытых позиций. Цикл начинается с последней позиции в списке (представленной как "PositionsTotal() - 1") и работает в обратном направлении, уменьшая i после каждой итерации, чтобы гарантировать обработку всех позиций.
Для каждой итерации мы используем функцию PositionGetTicket для извлечения номера тикета текущей позиции. Номер тикета однозначно идентифицирует каждую позицию. Затем мы проверяем, действителен ли тикет, убедившись, что его значение больше 0. Если действителен, используем функцию PositionSelectByTicket для выбора позиции, связанной с этим тикетом.
После выбора позиции мы далее проверяем, соответствует ли позиция символу, по которому мы управляем сделками, используя функцию PositionGetString с параметром POSITION_SYMBOL и сравнивая ее с _Symbol, который представляет текущий торговый символ. Если позиция соответствует символу, мы вызываем функцию PositionClose из объекта obj_Trade и передаем номер тикета в качестве параметра для закрытия позиции. Процесс продолжается до тех пор, пока не будут закрыты все позиции, соответствующие торговому символу. Чтобы закрыть все позиции на продажу, мы будем использовать ту же функцию, но добавим к ней дополнительную логику управления следующим образом.
else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ //--- Check if the Close All Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position obj_Trade.PositionClose(pos_ticket); //--- Close the sell position } } } } } }
Этот подход аналогичен тому, который мы используем для закрытия всех позиций. Таким образом, мы выделили новый раздел, чтобы сосредоточиться на дополнительной логике, используемой для закрытия только позиций на продажу при нажатии кнопки Close All Sell. После перебора всех позиций мы извлекаем тип каждой позиции, используя функцию PositionGetInteger с константой POSITION_TYPE. Это значение затем преобразуется в перечисление ENUM_POSITION_TYPE для простоты понимания (типирование). После того, как мы определили тип позиции, мы специально проверяем, является ли текущая позиция позицией на продажу, сравнивая ее со свойством POSITION_TYPE_SELL. Если условие оценивается как true, что означает, что позиция действительно является продажей, мы приступаем к ее закрытию с помощью функции PositionClose из объекта obj_Trade. То же самое мы делаем при закрытии позиций на покупку.
else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ //--- Check if the Close All Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position obj_Trade.PositionClose(pos_ticket); //--- Close the buy position } } } } } }
При закрытии всех убыточных позиций на продажу нам все равно необходимо расширить текущий подход, включив в него прибыль выбранной позиции, которую мы затем сможем использовать для сравнения и определения того, приносят ли они прибыль или убыток. Логика представлена ниже.
else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ //--- Check if the Close Loss Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing sell position } } } } } } }
Здесь, пройдя по всем позициям и убедившись, что текущая позиция имеет тип sell, мы вводим проверку прибыли/убытка по каждой позиции. Функция PositionGetDouble извлекает текущую прибыль/убыток выбранной позиции, используя константу POSITION_PROFIT, чтобы определить, является ли позиция прибыльной или убыточной. Затем мы добавляем условный оператор для проверки того, меньше ли значение прибыли/убытка нуля, что будет означать, что позиция находится в убытке. Если это условие выполняется, мы приступаем к закрытию убыточной позиции на продажу, вызывая функцию PositionClose. Эта дополнительная логика, которую мы выделили желтым цветом, гарантирует, что будут закрыты только те позиции на продажу, которые в данный момент убыточны. Аналогичный подход мы используем и для других кнопок.
else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ //--- Check if the Close Loss Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ //--- Check if the Close Profit Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ //--- Check if the Close Profit Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ //--- Check if the Close All Loss button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ //--- Check if the Close All Profit button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable position } } } } } }
Закончив с логикой позиций, мы можем перейти к логике отложенных ордеров. Здесь мы удаляем все отложенные ордера. Это достигается с помощью следующей логики.
else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ //--- Check if the Close Pending button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); //--- Log the button click event for (int i = OrdersTotal() -1; i >= 0; i--){ //--- Loop through all pending orders ulong order_ticket = OrderGetTicket(i); //--- Get the ticket of the order if (order_ticket > 0){ //--- Check if the order ticket is valid if (OrderSelect(order_ticket)){ //--- Select the order by ticket if (OrderGetString(ORDER_SYMBOL)==_Symbol){ //--- Check if the order matches the symbol obj_Trade.OrderDelete(order_ticket); //--- Delete the pending order } } } } }
Здесь мы сосредоточимся на функциональности закрытия отложенных ордеров при нажатии кнопки Close Pending. Начнем с проверки того, совпадает ли строковый параметр с именем кнопки Close Pending, что означает, что кнопка активирована. После подтверждения нажатия кнопки мы регистрируем действие для отладки с помощью функции Print, которая выводит название нажатой кнопки. Далее мы входим в цикл, который перебирает все отложенные ордера, используя функцию OrdersTotal для получения общего количества ордеров и уменьшения индекса в обратном порядке, чтобы избежать пропуска ордеров при удалении. Для каждого ордера мы извлекаем номер тикета с помощью функции OrderGetTicket. Затем мы проверяем, действителен ли тикет, убедившись, что его значение больше нуля. Если он действителен, мы переходим к выбору ордера, используя функцию OrderSelect.
После выбора ордера проверяем, соответствует ли он символу, указанному _Symbol, вызвав функцию OrderGetString с константой ORDER_SYMBOL. Если соответствует, вызываем функцию OrderDelete для удаления отложенного ордера, связанного с извлеченным тикетом. Весь этот процесс позволяет эффективно закрыть все отложенные ордера, привязанные к указанному торговому символу, при активации кнопки, гарантируя намерение пользователя эффективно управлять своими ордерами. После всего этого мы обновляем график с помощью функции ChartRedraw, как показано ниже, чтобы убедиться, что изменения вступили в силу на графике.
ChartRedraw(0);
Это все, что нам нужно для автоматизации панели. При запуске программы и тестировании интерфейса закрытия мы видим следующее.
Логика обработчика закрытия OnChartEvent, который мы внедрили в систему для обработки событий кликов по графику:
//+------------------------------------------------------------------+ //| Handling chart events | //+------------------------------------------------------------------+ void OnChartEvent( const int id, // Event ID indicating the type of event (e.g., mouse click, timer, etc.) const long& lparam, // Long type parameter associated with the event, usually containing data like mouse coordinates or object IDs const double& dparam, // Double type parameter associated with the event, used for floating-point values related to the event const string& sparam // String type parameter associated with the event, typically the name of the object that triggered the event ){ // Print the 4 function parameters //Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); // Check if the event is a click on a chart object if (id == CHARTEVENT_OBJECT_CLICK){ //Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam); // Check if the clicked object is the Trade button if (sparam==obj_Btn_TRADE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name()); // Reset the pressed states of all buttons to ensure only one button appears pressed at a time obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Change the background color of the Trade button to yellow to indicate it is active obj_Btn_TRADE.ColorBackground(clrYellow); // Set the background color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBackground(clrSilver); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to yellow to match its background obj_Btn_TRADE.ColorBorder(clrYellow); // Set the border color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBorder(clrSilver); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Close section if it exists destroySection_Close(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Trade section, bringing it to the forefront createSection_Trade(); } // Check if the clicked object is the Close button else if (sparam==obj_Btn_CLOSE.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade button to silver, indicating it's inactive obj_Btn_TRADE.ColorBackground(clrSilver); // Change the background color of the Close button to yellow to indicate it is active obj_Btn_CLOSE.ColorBackground(clrYellow); obj_Btn_INFO.ColorBackground(clrSilver); // Set the border color of the Trade button to silver obj_Btn_TRADE.ColorBorder(clrSilver); // Set the border color of the Close button to yellow obj_Btn_CLOSE.ColorBorder(clrYellow); obj_Btn_INFO.ColorBorder(clrSilver); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Information section if it exists destroySection_Information(); // Create the Close section, bringing it to the forefront createSection_Close(); } // Check if the clicked object is the Information button else if (sparam==obj_Btn_INFO.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_INFO.Name()); // Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); // Set the background color of the Trade and Close buttons to silver, indicating they are inactive obj_Btn_TRADE.ColorBackground(clrSilver); obj_Btn_CLOSE.ColorBackground(clrSilver); // Change the background color of the Info button to yellow to indicate it is active obj_Btn_INFO.ColorBackground(clrYellow); // Set the border color of the Trade and Close buttons to silver obj_Btn_TRADE.ColorBorder(clrSilver); obj_Btn_CLOSE.ColorBorder(clrSilver); // Set the border color of the Info button to yellow obj_Btn_INFO.ColorBorder(clrYellow); // Call a function to destroy the Trade section if it exists destroySection_Trade(); // Call a function to destroy the Close section if it exists destroySection_Close(); // Create the Information section, bringing it to the forefront createSection_Information(); } // Check if the clicked object is the exit button (X button) else if (sparam==obj_Btn_X.Name()){ // Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_X.Name()); // Call functions to destroy all sections, effectively closing the entire panel destroySection_Trade(); destroySection_Close(); destroySection_Information(); // Call a function to destroy the main panel itself destroySection_Main_Panel(); } else if (sparam==obj_Btn_SELL.Name()){ //--- Check if the Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Bid; //--- Set the entry price for selling to the current bid price double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the sell order } else if (sparam==obj_Btn_BUY.Name()){ //--- Check if the Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = Ask; //--- Set the entry price for buying to the current ask price double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the buy order } else if (sparam==obj_Btn_SELLSTOP.Name()){ //--- Check if the Sell Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid - stopslevel*_Point; //--- Calculate the valid price for placing a sell stop order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell stop order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell stop order } } else if (sparam==obj_Btn_BUYSTOP.Name()){ //--- Check if the Buy Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask + stopslevel*_Point; //--- Calculate the valid price for placing a buy stop order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy stop order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy stop order } } else if (sparam==obj_Btn_SELLLIMIT.Name()){ //--- Check if the Sell Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Bid + stopslevel*_Point; //--- Calculate the valid price for placing a sell limit order if (user_price < valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message } else if (user_price >= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the sell limit order double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell limit order } } else if (sparam==obj_Btn_BUYLIMIT.Name()){ //--- Check if the Buy Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); //--- Log the button click event double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol double valid_price = Ask - stopslevel*_Point; //--- Calculate the valid price for placing a buy limit order if (user_price > valid_price){ //--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message } else if (user_price <= valid_price){ //--- If the user-defined price is valid double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field double entry_price = user_price; //--- Set the entry price for the buy limit order double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy limit order } } else if (sparam==obj_Btn_CLOSE_ALL.Name()){ //--- Check if the Close All button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol obj_Trade.PositionClose(pos_ticket); //--- Close the position } } } } } else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ //--- Check if the Close All Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position obj_Trade.PositionClose(pos_ticket); //--- Close the sell position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ //--- Check if the Close All Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position obj_Trade.PositionClose(pos_ticket); //--- Close the buy position } } } } } } else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ //--- Check if the Close Loss Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ //--- Check if the Close Loss Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ //--- Check if the Close Profit Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ //--- Check if the Close Profit Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ //--- Check if the Close All Loss button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss < 0){ //--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); //--- Close the losing position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ //--- Check if the Close All Profit button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); //--- Log the button click event for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position if (pos_ticket > 0){ //--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position if (profit_loss >= 0){ //--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); //--- Close the profitable position } } } } } } else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ //--- Check if the Close Pending button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); //--- Log the button click event for (int i = OrdersTotal() -1; i >= 0; i--){ //--- Loop through all pending orders ulong order_ticket = OrderGetTicket(i); //--- Get the ticket of the order if (order_ticket > 0){ //--- Check if the order ticket is valid if (OrderSelect(order_ticket)){ //--- Select the order by ticket if (OrderGetString(ORDER_SYMBOL)==_Symbol){ //--- Check if the order matches the symbol obj_Trade.OrderDelete(order_ticket); //--- Delete the pending order } } } } } } ChartRedraw(0); }
Результаты показаны ниже.
Отлично! Мы успешно вдохнули жизнь в нашу панель, сделав ее полностью интерактивной и адаптивной. Теперь она поддерживает нажатие кнопок, обновление данных в реальном времени и реагирование на активные состояния, что улучшает общий пользовательский опыт и функциональность нашего торгового интерфейса.
Заключение
Улучшения, которые мы внедрили в нашу графическую панель MetaQuotes Language 5, значительно повышают ее интерактивность. Благодаря добавлению обновлений данных в реальном времени и отзывчивым нажатиям кнопок мы теперь можем легко и интуитивно взаимодействовать с панелью. Эти функции не только упрощают выполнение ордеров на покупку и продажу, но и обеспечивают мгновенный доступ к информации о торговом счете в реальном времени, что позволяет принимать быстрые и обоснованные торговые решения по мере изменения рыночных условий.
Более того, автоматизация различных компонентов, таких как управление позициями и отображение информации о счете, добавляет торговле еще больше удобства и эффективности. Графическая панель становится мощным инструментом для современных трейдеров, позволяя закрывать позиции и ордера одним щелчком мыши и предлагая настраиваемые параметры. Более чистое и организованное рабочее пространство позволяет лучше сосредоточиться и повысить производительность. Надеюсь, статья предоставила ценную информацию об улучшении графической панели MQL5, а объяснения оказались понятными и информативными. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16146





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Вы вообще читали статью?
Вы вообще читали статью?
Панно красивое и функциональное. Спасибо.
Добро пожаловать
а есть в планах (свернуть/развернуть) панель!? и неплохо бы реализовать перемещение окна панели по графику!