Торговые инструменты на MQL5 (Часть 1): Интерактивный визуальный помощник для работы с отложенными ордерами
Введение
Разработка эффективных торговых инструментов имеет важное значение для упрощения сложных задач в торговле на Форексе, однако создание интуитивно понятных интерфейсов, улучшающих процесс принятия решений, остается сложной задачей. Что если можно было бы разработать визуальный интерактивный инструмент, упрощающий установку отложенных ордеров в MetaTrader 5? В статье представлен советник на языке MetaQuotes Language 5 (MQL5) предоставляющий трейдерам помощь в торговле, сочетающий графическую точность с удобным управлением для эффективного размещения стоп- и лимитных ордеров на покупку/продажу. Мы рассмотрим его разработку в следующем порядке:
В итоге вы получите четкое понимание того, как создавать и тестировать этот инструмент, а также совершенствовать его.
Концептуальный дизайн и цели инструмента
Наша цель — разработать инструмент, который поможет удобно и эффективно размещать отложенные ордера на рынке Форекс. Инструмент должен располагать графическим интерфейсом (graphical user interface, GUI), который напрямую интегрируется с MetaTrader 5, позволяя нам устанавливать ордера Buy Stop, Sell Stop, Buy Limit и Sell Limit через интуитивно понятную панель управления. В нашем проекте предусмотрены кнопки для выбора желаемого типа ордера и поле ввода для указания размера партии. Мы уделяем приоритетное внимание визуальному взаимодействию, что позволяет нам определять цену входа, уровни стоп-лосса и тейк-профита путем перетаскивания интерактивных элементов на график, что обеспечивает мгновенную обратную связь об уровнях цен и разности между ними.
Наша главная задача — обеспечить доступность и адаптивность инструмента. Мы разработаем адаптивный интерфейс, который позволит нам точно корректировать уровни цен и подтверждать ордера одним щелчком мыши, сводя к минимуму время, затрачиваемое на настройку. Кроме того, мы добавим возможность отмены или закрытия интерфейса, что обеспечит нам гибкость и позволит быстро адаптироваться к меняющимся рыночным условиям. Создавая визуально привлекательный и адаптивный инструмент, мы стремимся улучшить процесс принятия решений, уменьшить количество ошибок при размещении ордеров и заложить основу для будущих усовершенствований, таких как расширенные функции управления рисками, которые мы рассмотрим в последующих статьях. Вкратце, вот визуализация того, что мы планируем создать.

Реализация средствами MQL5
Для создания программы в MQL5, нам потребуется определить метаданные программы, затем определить некоторые константы имен объектов и, наконец, включить несколько файлов библиотек, которые позволят нам осуществлять торговые операции.
//+------------------------------------------------------------------+ //| TRADE ASSISTANT GUI TOOL | //| Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. | //| https://youtube.com/@ForexAlgo-Trader? | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader" #property link "https://youtube.com/@ForexAlgo-Trader?" #property version "1.00" #include <Trade/Trade.mqh> //--- Include the Trade library for trading operations // Control panel object names #define PANEL_BG "PANEL_BG" //--- Define constant for panel background object name #define LOT_EDIT "LOT_EDIT" //--- Define constant for lot size edit field object name #define PRICE_LABEL "PRICE_LABEL" //--- Define constant for price label object name #define SL_LABEL "SL_LABEL" //--- Define constant for stop-loss label object name #define TP_LABEL "TP_LABEL" //--- Define constant for take-profit label object name #define BUY_STOP_BTN "BUY_STOP_BTN" //--- Define constant for buy stop button object name #define SELL_STOP_BTN "SELL_STOP_BTN" //--- Define constant for sell stop button object name #define BUY_LIMIT_BTN "BUY_LIMIT_BTN" //--- Define constant for buy limit button object name #define SELL_LIMIT_BTN "SELL_LIMIT_BTN" //--- Define constant for sell limit button object name #define PLACE_ORDER_BTN "PLACE_ORDER_BTN" //--- Define constant for place order button object name #define CANCEL_BTN "CANCEL_BTN" //--- Define constant for cancel button object name #define CLOSE_BTN "CLOSE_BTN" //--- Define constant for close button object name #define REC1 "REC1" //--- Define constant for rectangle 1 (TP) object name #define REC2 "REC2" //--- Define constant for rectangle 2 object name #define REC3 "REC3" //--- Define constant for rectangle 3 (Entry) object name #define REC4 "REC4" //--- Define constant for rectangle 4 object name #define REC5 "REC5" //--- Define constant for rectangle 5 (SL) object name #define TP_HL "TP_HL" //--- Define constant for take-profit horizontal line object name #define SL_HL "SL_HL" //--- Define constant for stop-loss horizontal line object name #define PR_HL "PR_HL" //--- Define constant for price (entry) horizontal line object name double Get_Price_d(string name) { return ObjectGetDouble(0, name, OBJPROP_PRICE); } //--- Function to get price as double for an object string Get_Price_s(string name) { return DoubleToString(ObjectGetDouble(0, name, OBJPROP_PRICE), _Digits); } //--- Function to get price as string with proper digits string update_Text(string name, string val) { return (string)ObjectSetString(0, name, OBJPROP_TEXT, val); } //--- Function to update text of an object int xd1, yd1, xs1, ys1, //--- Variables for rectangle 1 position and size xd2, yd2, xs2, ys2, //--- Variables for rectangle 2 position and size xd3, yd3, xs3, ys3, //--- Variables for rectangle 3 position and size xd4, yd4, xs4, ys4, //--- Variables for rectangle 4 position and size xd5, yd5, xs5, ys5; //--- Variables for rectangle 5 position and size // Control panel variables bool tool_visible = false; //--- Flag to track if trading tool is visible string selected_order_type = ""; //--- Variable to store selected order type double lot_size = 0.01; //--- Default lot size for trades CTrade obj_Trade; //--- Trade object for executing trading operations int panel_x = 10, panel_y = 30; //--- Panel position coordinates
Здесь мы закладываем основу для нашего инструмента, определяя основные компоненты, переменные и функции, которые обеспечивают графическое и торговое функционирование инструмента. Начнем с подключения библиотеки Trade.mqh, которая предоставляет класс CTrade для выполнения торговых операций, таких как размещение отложенных ордеров. Затем мы определяем ряд констант, используя #define для присвоения уникальных имен элементам графического интерфейса, таким как PANEL_BG для фона панели управления, LOT_EDIT - для поля ввода размера партии, а также кнопкам, таким как BUY_STOP_BTN и SELL_STOP_BTN для выбора типа ордера, и многому другому.
Реализуем три вспомогательные функции для управления свойствами объектов диаграммы: функция Get_Price_d извлекает цену объекта в виде числа с плавающей запятой (double), а функция Get_Price_s преобразует эту цену в строку, отформатированную с соответствующим количеством десятичных знаков, используя функцию DoubleToString, а функция update_Text обновляет текст объекта с помощью функции ObjectSetString для отображения информации о ценах в реальном времени.
Для управления позиционированием и размером интерактивных прямоугольников мы объявляем наборы целочисленных переменных, таких как xd1, yd1, xs1 и ys1 для каждого прямоугольника (REC1 — REC5), представляющих их расстояние по оси x, расстояние по оси y, размер по оси x и размер по оси y на графике.
Наконец, мы определяем ключевые переменные панели управления: tool_visible - логическое значение для отслеживания видимости инструмента, selected_order_type - строка для хранения выбранного типа ордера, lot_size - значение типа double, инициализированное значением 0,01 для объема сделок, obj_Trade - объект CTrade для исполнения сделок, а также panel_x и panel_y - целые числа для установки положения панели управления в координатах (10, 30).
В совокупности эти элементы формируют структурную основу для интерактивного интерфейса нашего инструмента и его торговых возможностей. Теперь мы можем перейти к созданию панели управления, но сначала нам понадобится функция для создания пользовательской кнопки.
//+------------------------------------------------------------------+ //| Create button | //+------------------------------------------------------------------+ bool createButton(string objName, string text, int xD, int yD, int xS, int yS, color clrTxt, color clrBG, int fontsize = 12, color clrBorder = clrNONE, bool isBack = false, string font = "Calibri") { ResetLastError(); //--- Reset last error code if(!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { //--- Create button object Print(__FUNCTION__, ": Failed to create Btn: Error Code: ", GetLastError()); //--- Print error message return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set button x-position ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set button y-position ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set button width ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set button height ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set button corner ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set button text ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontsize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set text color ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBG); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, isBack); //--- Set background/foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Reset button state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selection ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected state ChartRedraw(0); //--- Redraw chart return true; //--- Return success }
Мы определяем функцию createButton для создания настраиваемых кнопок для нашего инструмента. Она принимает такие параметры, как objName для имени кнопки, text для ее метки, xD и yD для положения, xS и yS для размера, clrTxt и clrBG для цвета текста и фона, fontsize (по умолчанию 12), clrBorder (по умолчанию clrNONE), isBack (по умолчанию false) и font (по умолчанию Calibri).
Мы используем функцию ResetLastError для очистки кодов ошибок, а затем функцию ObjectCreate для создания OBJ_BUTTON. Если это не сработает, вызовем функцию Print с __FUNCTION__ и GetLastError для регистрации ошибки и вернем false.
В случае успеха мы устанавливаем такие свойства, как положение, размер и цвета, используя функции ObjectSetInteger и ObjectSetString, отключаем state и selection, а также вызываем функцию ChartRedraw для обновления графика, возвращая true. Это позволит нам создавать интерактивные кнопки. Таким образом с помощью этой функции мы можем создать функцию панели управления.
//+------------------------------------------------------------------+ //| Create control panel | //+------------------------------------------------------------------+ void createControlPanel() { // Background rectangle ObjectCreate(0, PANEL_BG, OBJ_RECTANGLE_LABEL, 0, 0, 0); //--- Create panel background rectangle ObjectSetInteger(0, PANEL_BG, OBJPROP_XDISTANCE, panel_x); //--- Set background x-position ObjectSetInteger(0, PANEL_BG, OBJPROP_YDISTANCE, panel_y); //--- Set background y-position ObjectSetInteger(0, PANEL_BG, OBJPROP_XSIZE, 250); //--- Set background width ObjectSetInteger(0, PANEL_BG, OBJPROP_YSIZE, 280); //--- Set background height ObjectSetInteger(0, PANEL_BG, OBJPROP_BGCOLOR, C'070,070,070'); //--- Set background color ObjectSetInteger(0, PANEL_BG, OBJPROP_BORDER_COLOR, clrWhite); //--- Set border color ObjectSetInteger(0, PANEL_BG, OBJPROP_BACK, false); //--- Set background to foreground createButton(CLOSE_BTN, CharToString(203), panel_x + 209, panel_y + 1, 40, 25, clrWhite, clrCrimson, 12, C'070,070,070', false, "Wingdings"); //--- Create close button // Lot size input ObjectCreate(0, LOT_EDIT, OBJ_EDIT, 0, 0, 0); //--- Create lot size edit field ObjectSetInteger(0, LOT_EDIT, OBJPROP_XDISTANCE, panel_x + 70); //--- Set edit field x-position ObjectSetInteger(0, LOT_EDIT, OBJPROP_YDISTANCE, panel_y + 40); //--- Set edit field y-position ObjectSetInteger(0, LOT_EDIT, OBJPROP_XSIZE, 110); //--- Set edit field width ObjectSetInteger(0, LOT_EDIT, OBJPROP_YSIZE, 25); //--- Set edit field height ObjectSetString(0, LOT_EDIT, OBJPROP_TEXT, "0.01"); //--- Set default lot size text ObjectSetInteger(0, LOT_EDIT, OBJPROP_COLOR, clrBlack); //--- Set text color ObjectSetInteger(0, LOT_EDIT, OBJPROP_BGCOLOR, clrWhite); //--- Set background color ObjectSetInteger(0, LOT_EDIT, OBJPROP_BORDER_COLOR, clrBlack); //--- Set border color ObjectSetInteger(0, LOT_EDIT, OBJPROP_ALIGN, ALIGN_CENTER); //--- Center text ObjectSetString(0, LOT_EDIT, OBJPROP_FONT, "Arial"); //--- Set font ObjectSetInteger(0, LOT_EDIT, OBJPROP_FONTSIZE, 13); //--- Set font size ObjectSetInteger(0, LOT_EDIT, OBJPROP_BACK, false); //--- Set to foreground // Entry price label ObjectCreate(0, PRICE_LABEL, OBJ_LABEL, 0, 0, 0); //--- Create entry price label ObjectSetInteger(0, PRICE_LABEL, OBJPROP_XDISTANCE, panel_x + 10); //--- Set label x-position ObjectSetInteger(0, PRICE_LABEL, OBJPROP_YDISTANCE, panel_y + 70); //--- Set label y-position ObjectSetInteger(0, PRICE_LABEL, OBJPROP_XSIZE, 230); //--- Set label width ObjectSetString(0, PRICE_LABEL, OBJPROP_TEXT, "Entry: -"); //--- Set default text ObjectSetString(0, PRICE_LABEL, OBJPROP_FONT, "Arial Bold"); //--- Set font ObjectSetInteger(0, PRICE_LABEL, OBJPROP_FONTSIZE, 13); //--- Set font size ObjectSetInteger(0, PRICE_LABEL, OBJPROP_COLOR, clrWhite); //--- Set text color ObjectSetInteger(0, PRICE_LABEL, OBJPROP_ALIGN, ALIGN_CENTER); //--- Center text ObjectSetInteger(0, PRICE_LABEL, OBJPROP_BACK, false); //--- Set to foreground // SL and TP labels ObjectCreate(0, SL_LABEL, OBJ_LABEL, 0, 0, 0); //--- Create stop-loss label ObjectSetInteger(0, SL_LABEL, OBJPROP_XDISTANCE, panel_x + 10); //--- Set label x-position ObjectSetInteger(0, SL_LABEL, OBJPROP_YDISTANCE, panel_y + 95); //--- Set label y-position ObjectSetInteger(0, SL_LABEL, OBJPROP_XSIZE, 110); //--- Set label width ObjectSetString(0, SL_LABEL, OBJPROP_TEXT, "SL: -"); //--- Set default text ObjectSetString(0, SL_LABEL, OBJPROP_FONT, "Arial Bold"); //--- Set font ObjectSetInteger(0, SL_LABEL, OBJPROP_FONTSIZE, 12); //--- Set font size ObjectSetInteger(0, SL_LABEL, OBJPROP_COLOR, clrYellow); //--- Set text color ObjectSetInteger(0, SL_LABEL, OBJPROP_ALIGN, ALIGN_CENTER); //--- Center text ObjectSetInteger(0, SL_LABEL, OBJPROP_BACK, false); //--- Set to foreground ObjectCreate(0, TP_LABEL, OBJ_LABEL, 0, 0, 0); //--- Create take-profit label ObjectSetInteger(0, TP_LABEL, OBJPROP_XDISTANCE, panel_x + 130); //--- Set label x-position ObjectSetInteger(0, TP_LABEL, OBJPROP_YDISTANCE, panel_y + 95); //--- Set label y-position ObjectSetInteger(0, TP_LABEL, OBJPROP_XSIZE, 110); //--- Set label width ObjectSetString(0, TP_LABEL, OBJPROP_TEXT, "TP: -"); //--- Set default text ObjectSetString(0, TP_LABEL, OBJPROP_FONT, "Arial Bold"); //--- Set font ObjectSetInteger(0, TP_LABEL, OBJPROP_FONTSIZE, 12); //--- Set font size ObjectSetInteger(0, TP_LABEL, OBJPROP_COLOR, clrLime); //--- Set text color ObjectSetInteger(0, TP_LABEL, OBJPROP_ALIGN, ALIGN_CENTER); //--- Center text ObjectSetInteger(0, TP_LABEL, OBJPROP_BACK, false); //--- Set to foreground // Order type buttons createButton(BUY_STOP_BTN, "Buy Stop", panel_x + 10, panel_y + 140, 110, 30, clrWhite, clrForestGreen, 10, clrBlack, false, "Arial"); //--- Create Buy Stop button createButton(SELL_STOP_BTN, "Sell Stop", panel_x + 130, panel_y + 140, 110, 30, clrWhite, clrFireBrick, 10, clrBlack, false, "Arial"); //--- Create Sell Stop button createButton(BUY_LIMIT_BTN, "Buy Limit", panel_x + 10, panel_y + 180, 110, 30, clrWhite, clrForestGreen, 10, clrBlack, false, "Arial"); //--- Create Buy Limit button createButton(SELL_LIMIT_BTN, "Sell Limit", panel_x + 130, panel_y + 180, 110, 30, clrWhite, clrFireBrick, 10, clrBlack, false, "Arial"); //--- Create Sell Limit button // Place Order and Cancel buttons createButton(PLACE_ORDER_BTN, "Place Order", panel_x + 10, panel_y + 240, 110, 30, clrWhite, clrDodgerBlue, 10, clrBlack, false, "Arial"); //--- Create Place Order button createButton(CANCEL_BTN, "Cancel", panel_x + 130, panel_y + 240, 110, 30, clrWhite, clrSlateGray, 10, clrBlack, false, "Arial"); //--- Create Cancel button }
Здесь мы определяем функцию createControlPanel для построения основного элемента графического интерфейса пользователя для нашего инструмента. Начнем с использования функции ObjectCreate для создания фонового прямоугольника PANEL_BG типа OBJ_RECTANGLE_LABEL, расположенного в точках panel_x и panel_y (значения 10 и 30), с размером 250x280 пикселей, с темно-серым фоном (C'070,070,070'), белой рамкой (clrWhite) и расположением на переднем плане (OBJPROP_BACK равен false).
Затем вызываем функцию createButton, чтобы добавить кнопку закрытия (CLOSE_BTN) в правом верхнем углу, отображающую символ крестика (символ 203 из Wingdings), оформленный в малиновом цвете. Входные данные определены в документации MQL5 следующим образом, но вы можете использовать любой другой вариант по своему усмотрению.

Для ввода размера лота мы используем функцию ObjectCreate для создания поля редактирования (LOT_EDIT) типа OBJ_EDIT в panel_x + 70, panel_y + 40, размером 110x25 пикселей, инициализированный значением 0.01 и оформленный черным текстом, белым фоном и центрированным шрифтом Arial с использованием функций ObjectSetInteger и ObjectSetString.
Создаем три метки для отображения информации о сделке с помощью функции ObjectCreate: PRICE_LABEL для цены входа на panel_x + 10, panel_y + 70, занимающей 230 пикселей с текстом по умолчанию "Entry: -"; SL_LABEL для стоп-лосса на panel_x + 10, panel_y + 95 с желтым текстом по умолчанию "SL: -" и TP_LABEL для тейк-профита на panel_x + 130, panel_y + 95 с лаймовым текстом по умолчанию "TP: -", все с использованием жирного шрифта Arial и выравниванием по центру.
Наконец, мы используем функцию createButton для добавления кнопок типов ордеров — BUY_STOP_BTN и SELL_STOP_BTN на panel_y + 140, BUY_LIMIT_BTN и SELL_LIMIT_BTN на panel_y + 180 — зеленого и красного цвета соответственно, а также кнопок действий PLACE_ORDER_BTN и CANCEL_BTN на panel_y + 240 синего и серого цвета, все размером 110x30 пикселей, шрифт - Arial. Эта конфигурация образует интерактивную панель управления для нашего инструмента. Мы можем вызвать функцию в обработчике событий OnInit для инициализации панели.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Create control panel createControlPanel(); //--- Call function to create the control panel ChartRedraw(0); //--- Redraw chart to display panel return(INIT_SUCCEEDED); //--- Return successful initialization }
В обработчике событий OnInit мы вызываем функцию createControlPanel для построения графического интерфейса пользователя, настраивая панель управления с кнопками, метками и полями ввода. Далее используем функцию ChartRedraw для обновления графика, обеспечивая немедленное отображение панели. Наконец возвращаем INIT_SUCCEEDED для подтверждения успешной инициализации. После компиляции получаем следующий результат.

Мы создали панель управления. Теперь нам нужно создать вспомогательную панель, которую мы сможем использовать для динамического получения цен с графика, автоматического заполнения панели управления и совершения сделок на их основе. Для этого потребуется интегрировать масштаб графика и масштаб экрана. Объединим всё в функции. Затем добавим обработчики событий, которые будут вызывать соответствующие функции при взаимодействии с кнопкой управления.
//+------------------------------------------------------------------+ //| Expert onchart event function | //+------------------------------------------------------------------+ void OnChartEvent( const int id, //--- Event ID const long& lparam, //--- Long parameter (e.g., x-coordinate for mouse) const double& dparam, //--- Double parameter (e.g., y-coordinate for mouse) const string& sparam //--- String parameter (e.g., object name) ) { if(id == CHARTEVENT_OBJECT_CLICK) { //--- Handle object click events // Handle order type buttons if(sparam == BUY_STOP_BTN) { //--- Check if Buy Stop button clicked selected_order_type = "BUY_STOP"; //--- Set order type to Buy Stop } } }
Здесь реализуем обработчик события OnChartEvent для обработки взаимодействия пользователя с инструментом. Функция принимает параметры: id для типа события, lparam для данных, таких как координаты x, dparam для данных, таких как координаты y, и sparam для строковых данных, таких как имена объектов. Проверяем, равно ли id CHARTEVENT_OBJECT_CLICK для обнаружения кликов по объектам, и если sparam совпадает с BUY_STOP_BTN, устанавливаем переменную selected_order_type на BUY_STOP, что позволяет нам зарегистрировать выбор пользователем ордера Buy Stop. В таком случае нам потребуется функция для отображения инструмента.
//+------------------------------------------------------------------+ //| Show main tool | //+------------------------------------------------------------------+ void showTool() { // Hide panel ObjectSetInteger(0, PANEL_BG, OBJPROP_BACK, false); //--- Hide panel background ObjectSetInteger(0, LOT_EDIT, OBJPROP_BACK, false); //--- Hide lot edit field ObjectSetInteger(0, PRICE_LABEL, OBJPROP_BACK, false); //--- Hide price label ObjectSetInteger(0, SL_LABEL, OBJPROP_BACK, false); //--- Hide SL label ObjectSetInteger(0, TP_LABEL, OBJPROP_BACK, false); //--- Hide TP label ObjectSetInteger(0, BUY_STOP_BTN, OBJPROP_BACK, false); //--- Hide Buy Stop button ObjectSetInteger(0, SELL_STOP_BTN, OBJPROP_BACK, false); //--- Hide Sell Stop button ObjectSetInteger(0, BUY_LIMIT_BTN, OBJPROP_BACK, false); //--- Hide Buy Limit button ObjectSetInteger(0, SELL_LIMIT_BTN, OBJPROP_BACK, false); //--- Hide Sell Limit button ObjectSetInteger(0, PLACE_ORDER_BTN, OBJPROP_BACK, false); //--- Hide Place Order button ObjectSetInteger(0, CANCEL_BTN, OBJPROP_BACK, false); //--- Hide Cancel button ObjectSetInteger(0, CLOSE_BTN, OBJPROP_BACK, false); //--- Hide Close button // Create main tool 150 pixels from the right edge int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width int tool_x = chart_width - 400 - 50; //--- Calculate tool x-position (400 is REC1 width, 50 is margin) if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") { //--- Check for buy orders // Buy orders: TP at top, entry in middle, SL at bottom createButton(REC1, "", tool_x, 20, 350, 30, clrWhite, clrGreen, 13, clrBlack, false, "Arial Black"); //--- Create TP rectangle xd1 = (int)ObjectGetInteger(0, REC1, OBJPROP_XDISTANCE); //--- Get REC1 x-distance yd1 = (int)ObjectGetInteger(0, REC1, OBJPROP_YDISTANCE); //--- Get REC1 y-distance xs1 = (int)ObjectGetInteger(0, REC1, OBJPROP_XSIZE); //--- Get REC1 x-size ys1 = (int)ObjectGetInteger(0, REC1, OBJPROP_YSIZE); //--- Get REC1 y-size xd2 = xd1; //--- Set REC2 x-distance yd2 = yd1 + ys1; //--- Set REC2 y-distance xs2 = xs1; //--- Set REC2 x-size ys2 = 100; //--- Set REC2 y-size xd3 = xd2; //--- Set REC3 x-distance yd3 = yd2 + ys2; //--- Set REC3 y-distance xs3 = xs2; //--- Set REC3 x-size ys3 = 30; //--- Set REC3 y-size xd4 = xd3; //--- Set REC4 x-distance yd4 = yd3 + ys3; //--- Set REC4 y-distance xs4 = xs3; //--- Set REC4 x-size ys4 = 100; //--- Set REC4 y-size xd5 = xd4; //--- Set REC5 x-distance yd5 = yd4 + ys4; //--- Set REC5 y-distance xs5 = xs4; //--- Set REC5 x-size ys5 = 30; //--- Set REC5 y-size } else { //--- Handle sell orders // Sell orders: SL at top, entry in middle, TP at bottom createButton(REC5, "", tool_x, 20, 350, 30, clrWhite, clrRed, 13, clrBlack, false, "Arial Black"); //--- Create SL rectangle xd5 = (int)ObjectGetInteger(0, REC5, OBJPROP_XDISTANCE); //--- Get REC5 x-distance yd5 = (int)ObjectGetInteger(0, REC5, OBJPROP_YDISTANCE); //--- Get REC5 y-distance xs5 = (int)ObjectGetInteger(0, REC5, OBJPROP_XSIZE); //--- Get REC5 x-size ys5 = (int)ObjectGetInteger(0, REC5, OBJPROP_YSIZE); //--- Get REC5 y-size xd2 = xd5; //--- Set REC2 x-distance yd2 = yd5 + ys5; //--- Set REC2 y-distance xs2 = xs5; //--- Set REC2 x-size ys2 = 100; //--- Set REC2 y-size xd3 = xd2; //--- Set REC3 x-distance yd3 = yd2 + ys2; //--- Set REC3 y-distance xs3 = xs2; //--- Set REC3 x-size ys3 = 30; //--- Set REC3 y-size xd4 = xd3; //--- Set REC4 x-distance yd4 = yd3 + ys3; //--- Set REC4 y-distance xs4 = xs3; //--- Set REC4 x-size ys4 = 100; //--- Set REC4 y-size xd1 = xd4; //--- Set REC1 x-distance yd1 = yd4 + ys4; //--- Set REC1 y-distance xs1 = xs4; //--- Set REC1 x-size ys1 = 30; //--- Set REC1 y-size } datetime dt_tp = 0, dt_sl = 0, dt_prc = 0; //--- Variables for time double price_tp = 0, price_sl = 0, price_prc = 0; //--- Variables for price int window = 0; //--- Chart window ChartXYToTimePrice(0, xd1, yd1 + ys1, window, dt_tp, price_tp); //--- Convert REC1 coordinates to time and price ChartXYToTimePrice(0, xd3, yd3 + ys3, window, dt_prc, price_prc); //--- Convert REC3 coordinates to time and price ChartXYToTimePrice(0, xd5, yd5 + ys5, window, dt_sl, price_sl); //--- Convert REC5 coordinates to time and price createHL(TP_HL, dt_tp, price_tp, clrTeal); //--- Create TP horizontal line createHL(PR_HL, dt_prc, price_prc, clrBlue); //--- Create entry horizontal line createHL(SL_HL, dt_sl, price_sl, clrRed); //--- Create SL horizontal line if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") { //--- Check for buy orders createButton(REC2, "", xd2, yd2, xs2, ys2, clrWhite, clrHoneydew, 12, clrBlack, true); //--- Create REC2 createButton(REC3, "", xd3, yd3, xs3, ys3, clrBlack, clrLightGray, 13, clrBlack, false, "Arial Black"); //--- Create REC3 createButton(REC4, "", xd4, yd4, xs4, ys4, clrWhite, clrLinen, 12, clrBlack, true); //--- Create REC4 createButton(REC5, "", xd5, yd5, xs5, ys5, clrWhite, clrRed, 13, clrBlack, false, "Arial Black"); //--- Create REC5 } else { //--- Handle sell orders createButton(REC2, "", xd2, yd2, xs2, ys2, clrWhite, clrHoneydew, 12, clrBlack, true); //--- Create REC2 createButton(REC3, "", xd3, yd3, xs3, ys3, clrBlack, clrLightGray, 13, clrBlack, false, "Arial Black"); //--- Create REC3 createButton(REC4, "", xd4, yd4, xs4, ys4, clrWhite, clrLinen, 12, clrBlack, true); //--- Create REC4 createButton(REC1, "", xd1, yd1, xs1, ys1, clrWhite, clrGreen, 13, clrBlack, false, "Arial Black"); //--- Create REC1 } update_Text(REC1, "TP: " + DoubleToString(MathAbs((Get_Price_d(TP_HL) - Get_Price_d(PR_HL)) / _Point), 0) + " Points | " + Get_Price_s(TP_HL)); //--- Update REC1 text update_Text(REC3, selected_order_type + ": | Lot: " + DoubleToString(lot_size, 2) + " | " + Get_Price_s(PR_HL)); //--- Update REC3 text update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), 0) + " Points | " + Get_Price_s(SL_HL)); //--- Update REC5 text update_Text(PRICE_LABEL, "Entry: " + Get_Price_s(PR_HL)); //--- Update entry label text update_Text(SL_LABEL, "SL: " + Get_Price_s(SL_HL)); //--- Update SL label text update_Text(TP_LABEL, "TP: " + Get_Price_s(TP_HL)); //--- Update TP label text tool_visible = true; //--- Set tool visibility flag ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Enable mouse move events ChartRedraw(0); //--- Redraw chart }
Чтобы отобразить инструмент построения графика цен, мы реализуем функцию "showTool" и начнем с того, что скроем панель управления, используя функцию ObjectSetInteger. Установим OBJPROP_BACK на false для объектов PANEL_BG, LOT_EDIT, PRICE_LABEL, SL_LABEL, TP_LABEL, BUY_STOP_BTN, SELL_STOP_BTN, BUY_LIMIT_BTN, SELL_LIMIT_BTN, PLACE_ORDER_BTN, CANCEL_BTN и CLOSE_BTN.
Вычислим координаты инструмента по оси X с помощью функции ChartGetInteger для получения CHART_WIDTH_IN_PIXELS, установив tool_x на 450 пикселей от правого края. Для ордеров BUY_STOP или BUY_LIMIT используем функцию createButton для создания REC1 (TP) по координатам tool_x, y=20, размером 350x30, зеленого цвета, и устанавливаем переменные xd1, yd1, xs1, ys1 с помощью функции ObjectGetInteger, затем позиционируем REC2 - REC5 по вертикали (TP, entry, SL) с помощью параметров xd2 - xd5, yd2 - yd5, xs2 - xs5, ys2 - ys5.
Для ордеров на продажу создаем ордер REC5 (SL) красным цветом и располагаем ордера REC2 до REC1 (SL, вход, TP).
Мы объявляем переменные dt_tp, dt_sl, dt_prc для времени, price_tp, price_sl и price_prc для цен, а также переменную window для графика, используя функцию ChartXYToTimePrice для преобразования координат REC1, REC3 и REC5 в цену и время. Вызовем функцию createHL, чтобы нарисовать TP_HL, PR_HL и SL_HL бирюзовым, синим и красным цветами.
В зависимости от параметра selected_order_type используем функцию createButton для создания оставшихся прямоугольников (REC2, REC3, REC4, REC5 для покупки; REC2, REC3, REC4, REC1 для продажи) с соответствующими цветами. Мы обновляем текст, используя функцию update_Text для REC1, REC3, REC5, PRICE_LABEL, SL_LABEL и TP_LABEL и вычисляя разницу в пунктах с помощью функций Get_Price_d, Get_Price_s, DoubleToString и MathAbs.
Наконец, устанавливаем параметр tool_visible на true, включаем обработку событий мыши с помощью ChartSetInteger и вызываем ChartRedraw для отображения инструмента. Для создания горизонтальных линий мы используем следующую функцию.
//+------------------------------------------------------------------+ //| Create horizontal line | //+------------------------------------------------------------------+ bool createHL(string objName, datetime time1, double price1, color clr) { ResetLastError(); //--- Reset last error code if(!ObjectCreate(0, objName, OBJ_HLINE, 0, time1, price1)) { //--- Create horizontal line Print(__FUNCTION__, ": Failed to create HL: Error Code: ", GetLastError()); //--- Print error message return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_TIME, time1); //--- Set line time ObjectSetDouble(0, objName, OBJPROP_PRICE, price1); //--- Set line price ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); //--- Set line color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set to foreground ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASHDOTDOT); //--- Set line style ChartRedraw(0); //--- Redraw chart return true; //--- Return success }
Здесь мы просто создаем объект OBJ_HLINE и устанавливаем необходимые параметры объекта, как мы это делали ранее с функцией создания кнопок. Нам также понадобится функция отображения панели, как показано ниже.
//+------------------------------------------------------------------+ //| Show control panel | //+------------------------------------------------------------------+ void showPanel() { // Show panel ObjectSetInteger(0, PANEL_BG, OBJPROP_BACK, false); //--- Show panel background ObjectSetInteger(0, LOT_EDIT, OBJPROP_BACK, false); //--- Show lot edit field ObjectSetInteger(0, PRICE_LABEL, OBJPROP_BACK, false); //--- Show price label ObjectSetInteger(0, SL_LABEL, OBJPROP_BACK, false); //--- Show SL label ObjectSetInteger(0, TP_LABEL, OBJPROP_BACK, false); //--- Show TP label ObjectSetInteger(0, BUY_STOP_BTN, OBJPROP_BACK, false); //--- Show Buy Stop button ObjectSetInteger(0, SELL_STOP_BTN, OBJPROP_BACK, false); //--- Show Sell Stop button ObjectSetInteger(0, BUY_LIMIT_BTN, OBJPROP_BACK, false); //--- Show Buy Limit button ObjectSetInteger(0, SELL_LIMIT_BTN, OBJPROP_BACK, false); //--- Show Sell Limit button ObjectSetInteger(0, PLACE_ORDER_BTN, OBJPROP_BACK, false); //--- Show Place Order button ObjectSetInteger(0, CANCEL_BTN, OBJPROP_BACK, false); //--- Show Cancel button ObjectSetInteger(0, CLOSE_BTN, OBJPROP_BACK, false); //--- Show Close button // Reset panel state update_Text(PRICE_LABEL, "Entry: -"); //--- Reset entry label text update_Text(SL_LABEL, "SL: -"); //--- Reset SL label text update_Text(TP_LABEL, "TP: -"); //--- Reset TP label text update_Text(PLACE_ORDER_BTN, "Place Order"); //--- Reset Place Order button text selected_order_type = ""; //--- Clear selected order type tool_visible = false; //--- Hide tool ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); //--- Disable mouse move events ChartRedraw(0); //--- Redraw chart }
Определяем функцию showPanel для отображения нашей панели управления. Используем функцию ObjectSetInteger для установки OBJPROP_BACK на false для PANEL_BG, LOT_EDIT, PRICE_LABEL, SL_LABEL, TP_LABEL, BUY_STOP_BTN, SELL_STOP_BTN, BUY_LIMIT_BTN, SELL_LIMIT_BTN, PLACE_ORDER_BTN, CANCEL_BTN и CLOSE_BTN, чтобы сделать их видимыми.
Сбрасываем состояние с помощью функции update_Text, устанавливая PRICE_LABEL на "Entry: -", SL_LABEL на "SL: -", TP_LABEL на "TP: -" и PLACE_ORDER_BTN на Place Order, очищаем selected_order_type, устанавливаем tool_visible на false, отключаем события мыши с помощью ChartSetInteger и вызываем ChartRedraw для обновления графика.
Для удаления инструмента и панели, используем следующие функции, вызывая функцию ObjectDelete на соответствующих объектах.
//+------------------------------------------------------------------+ //| Delete main tool objects | //+------------------------------------------------------------------+ void deleteObjects() { ObjectDelete(0, REC1); //--- Delete REC1 object ObjectDelete(0, REC2); //--- Delete REC2 object ObjectDelete(0, REC3); //--- Delete REC3 object ObjectDelete(0, REC4); //--- Delete REC4 object ObjectDelete(0, REC5); //--- Delete REC5 object ObjectDelete(0, TP_HL); //--- Delete TP horizontal line ObjectDelete(0, SL_HL); //--- Delete SL horizontal line ObjectDelete(0, PR_HL); //--- Delete entry horizontal line ChartRedraw(0); //--- Redraw chart } //+------------------------------------------------------------------+ //| Delete control panel objects | //+------------------------------------------------------------------+ void deletePanel() { ObjectDelete(0, PANEL_BG); //--- Delete panel background ObjectDelete(0, LOT_EDIT); //--- Delete lot edit field ObjectDelete(0, PRICE_LABEL); //--- Delete price label ObjectDelete(0, SL_LABEL); //--- Delete SL label ObjectDelete(0, TP_LABEL); //--- Delete TP label ObjectDelete(0, BUY_STOP_BTN); //--- Delete Buy Stop button ObjectDelete(0, SELL_STOP_BTN); //--- Delete Sell Stop button ObjectDelete(0, BUY_LIMIT_BTN); //--- Delete Buy Limit button ObjectDelete(0, SELL_LIMIT_BTN); //--- Delete Sell Limit button ObjectDelete(0, PLACE_ORDER_BTN); //--- Delete Place Order button ObjectDelete(0, CANCEL_BTN); //--- Delete Cancel button ObjectDelete(0, CLOSE_BTN); //--- Delete Close button ChartRedraw(0); //--- Redraw chart }
Для оформления ордеров при нажатии соответствующей кнопки используем следующую функцию.
//+------------------------------------------------------------------+ //| Place order based on selected type | //+------------------------------------------------------------------+ void placeOrder() { double price = Get_Price_d(PR_HL); //--- Get entry price double sl = Get_Price_d(SL_HL); //--- Get stop-loss price double tp = Get_Price_d(TP_HL); //--- Get take-profit price string symbol = Symbol(); //--- Get current symbol datetime expiration = TimeCurrent() + 3600 * 24; //--- Set 24-hour order expiration // Validate lot size if(lot_size <= 0) { //--- Check if lot size is valid Print("Invalid lot size: ", lot_size); //--- Print error message return; //--- Exit function } // Validate prices if(price <= 0 || sl <= 0 || tp <= 0) { //--- Check if prices are valid Print("Invalid prices: Entry=", price, ", SL=", sl, ", TP=", tp, " (all must be positive)"); //--- Print error message return; //--- Exit function } // Validate price relationships based on order type if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") { //--- Check for buy orders if(sl >= price) { //--- Check if SL is below entry Print("Invalid SL for ", selected_order_type, ": SL=", sl, " must be below Entry=", price); //--- Print error message return; //--- Exit function } if(tp <= price) { //--- Check if TP is above entry Print("Invalid TP for ", selected_order_type, ": TP=", tp, " must be above Entry=", price); //--- Print error message return; //--- Exit function } } else if(selected_order_type == "SELL_STOP" || selected_order_type == "SELL_LIMIT") { //--- Check for sell orders if(sl <= price) { //--- Check if SL is above entry Print("Invalid SL for ", selected_order_type, ": SL=", sl, " must be above Entry=", price); //--- Print error message return; //--- Exit function } if(tp >= price) { //--- Check if TP is below entry Print("Invalid TP for ", selected_order_type, ": TP=", tp, " must be below Entry=", price); // AMPK--- Print error message return; //--- Exit function } } else { //--- Handle invalid order type Print("Invalid order type: ", selected_order_type); //--- Print error message return; //--- Exit function } // Place the order if(selected_order_type == "BUY_STOP") { //--- Handle Buy Stop order if(!obj_Trade.BuyStop(lot_size, price, symbol, sl, tp, ORDER_TIME_DAY, expiration)) { //--- Attempt to place Buy Stop order Print("Buy Stop failed: Entry=", price, ", SL=", sl, ", TP=", tp, ", Error=", GetLastError()); //--- Print error message } else { //--- Order placed successfully Print("Buy Stop placed: Entry=", price, ", SL=", sl, ", TP=", tp); //--- Print success message } } else if(selected_order_type == "SELL_STOP") { //--- Handle Sell Stop order if(!obj_Trade.SellStop(lot_size, price, symbol, sl, tp, ORDER_TIME_DAY, expiration)) { //--- Attempt to place Sell Stop order Print("Sell Stop failed: Entry=", price, ", SL=", sl, ", TP=", tp, ", Error=", GetLastError()); //--- Print error message } else { //--- Order placed successfully Print("Sell Stop placed: Entry=", price, ", SL=", sl, ", TP=", tp); //--- Print success message } } else if(selected_order_type == "BUY_LIMIT") { //--- Handle Buy Limit order if(!obj_Trade.BuyLimit(lot_size, price, symbol, sl, tp, ORDER_TIME_DAY, expiration)) { //--- Attempt to place Buy Limit order Print("Buy Limit failed: Entry=", price, ", SL=", sl, ", TP=", tp, ", Error=", GetLastError()); //--- Print error message } else { //--- Order placed successfully Print("Buy Limit placed: Entry=", price, ", SL=", sl, ", TP=", tp); //--- Print success message } } else if(selected_order_type == "SELL_LIMIT") { //--- Handle Sell Limit order if(!obj_Trade.SellLimit(lot_size, price, symbol, sl, tp, ORDER_TIME_DAY, expiration)) { //--- Attempt to place Sell Limit order Print("Sell Limit failed: Entry=", price, ", SL=", sl, ", TP=", tp, ", Error=", GetLastError()); //--- Print error message } else { //--- Order placed successfully Print("Sell Limit placed: Entry=", price, ", SL=", sl, ", TP=", tp); //--- Print success message } } }
Используем функцию placeOrder для исполнения отложенных ордеров для нашего инструмента и начинаем с получения цены входа (price), стоп-лосса (sl) и тейк-профита (tp) с помощью функции Get_Price_d для PR_HL, SL_HL и TP_HL, а затем получаем текущий symbol с помощью функции Symbol и устанавливаем 24-часовой "срок действия" с помощью функции TimeCurrent.
Проверяем lot_size (>0) и убеждаемся, что price, sl и tp положительны, завершая работу функцией Print, если условие не выполняется. Для условий BUY_STOP или BUY_LIMIT проверяем, находится ли sl ниже price и выше tp, а для условий SELL_STOP или SELL_LIMIT — находится ли sl выше и ниже tp, используя функцию Print для регистрации ошибок и завершения работы в случае невыполнения условий. При недопустимом значении selected_order_type, завершаем работу с сообщением "Print".
Затем мы используем методы объекта obj_Trade — BuyStop, SellStop, BuyLimit или SellLimit — для размещения ордера, регистрируя успех или неудачу с помощью функции Print и GetLastError, если что-то идет не так. Обладая этими функциями, мы можем вызывать их нажатием соответствующих кнопок, как показано ниже.
if(id == CHARTEVENT_OBJECT_CLICK) { //--- Handle object click events // Handle order type buttons if(sparam == BUY_STOP_BTN) { //--- Check if Buy Stop button clicked selected_order_type = "BUY_STOP"; //--- Set order type to Buy Stop showTool(); //--- Show trading tool update_Text(PLACE_ORDER_BTN, "Place Buy Stop"); //--- Update place order button text } else if(sparam == SELL_STOP_BTN) { //--- Check if Sell Stop button clicked selected_order_type = "SELL_STOP"; //--- Set order type to Sell Stop showTool(); //--- Show trading tool update_Text(PLACE_ORDER_BTN, "Place Sell Stop"); //--- Update place order button text } else if(sparam == BUY_LIMIT_BTN) { //--- Check if Buy Limit button clicked selected_order_type = "BUY_LIMIT"; //--- Set order type to Buy Limit showTool(); //--- Show trading tool update_Text(PLACE_ORDER_BTN, "Place Buy Limit"); //--- Update place order button text } else if(sparam == SELL_LIMIT_BTN) { //--- Check if Sell Limit button clicked selected_order_type = "SELL_LIMIT"; //--- Set order type to Sell Limit showTool(); //--- Show trading tool update_Text(PLACE_ORDER_BTN, "Place Sell Limit");//--- Update place order button text } else if(sparam == PLACE_ORDER_BTN) { //--- Check if Place Order button clicked placeOrder(); //--- Execute order placement deleteObjects(); //--- Delete tool objects showPanel(); //--- Show control panel } else if(sparam == CANCEL_BTN) { //--- Check if Cancel button clicked deleteObjects(); //--- Delete tool objects showPanel(); //--- Show control panel } else if(sparam == CLOSE_BTN) { //--- Check if Close button clicked deleteObjects(); //--- Delete tool objects deletePanel(); //--- Delete control panel } ObjectSetInteger(0, sparam, OBJPROP_STATE, false); //--- Reset button state ChartRedraw(0); //--- Redraw chart }
В результате компиляции мы получаем следующее.

Итак, мы можем динамически создавать соответствующий инструмент для построения ценового графика. Следующий шаг — сделать инструмент интерактивным, добавив возможность перемещать его вдоль графика. Вот логика, которую мы применяем в обработчике событий OnChartEvent.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ int prevMouseState = 0; //--- Variable to track previous mouse state int mlbDownX1 = 0, mlbDownY1 = 0, mlbDownXD_R1 = 0, mlbDownYD_R1 = 0; //--- Variables for mouse down coordinates for REC1 int mlbDownX2 = 0, mlbDownY2 = 0, mlbDownXD_R2 = 0, mlbDownYD_R2 = 0; //--- Variables for mouse down coordinates for REC2 int mlbDownX3 = 0, mlbDownY3 = 0, mlbDownXD_R3 = 0, mlbDownYD_R3 = 0; //--- Variables for mouse down coordinates for REC3 int mlbDownX4 = 0, mlbDownY4 = 0, mlbDownXD_R4 = 0, mlbDownYD_R4 = 0; //--- Variables for mouse down coordinates for REC4 int mlbDownX5 = 0, mlbDownY5 = 0, mlbDownXD_R5 = 0, mlbDownYD_R5 = 0; //--- Variables for mouse down coordinates for REC5 bool movingState_R1 = false; //--- Flag for REC1 movement state bool movingState_R3 = false; //--- Flag for REC3 movement state bool movingState_R5 = false; //--- Flag for REC5 movement state
Сначала мы определяем переменные для функции OnChartEvent, чтобы включить функцию перетаскивания в нашем инструменте Trade Assistant Tool. prevMouseState отслеживает изменения состояния мыши, а mlbDownX1, mlbDownY1, mlbDownXD_R1 и mlbDownYD_R1 (и аналогичные для REC2 - REC5) хранят координаты мыши и прямоугольника для REC1 (TP), REC3 (entry) и REC5 (SL) во время кликов. Логические флаги movingState_R1, movingState_R3 и movingState_R5 указывают, перетаскиваются ли эти прямоугольники. Затем мы можем использовать эти управляющие переменные для определения движения ценового инструмента.
if(id == CHARTEVENT_MOUSE_MOVE && tool_visible) { //--- Handle mouse move events when tool is visible int MouseD_X = (int)lparam; //--- Get mouse x-coordinate int MouseD_Y = (int)dparam; //--- Get mouse y-coordinate int MouseState = (int)sparam; //--- Get mouse state int XD_R1 = (int)ObjectGetInteger(0, REC1, OBJPROP_XDISTANCE); //--- Get REC1 x-distance int YD_R1 = (int)ObjectGetInteger(0, REC1, OBJPROP_YDISTANCE); //--- Get REC1 y-distance int XS_R1 = (int)ObjectGetInteger(0, REC1, OBJPROP_XSIZE); //--- Get REC1 x-size int YS_R1 = (int)ObjectGetInteger(0, REC1, OBJPROP_YSIZE); //--- Get REC1 y-size int XD_R2 = (int)ObjectGetInteger(0, REC2, OBJPROP_XDISTANCE); //--- Get REC2 x-distance int YD_R2 = (int)ObjectGetInteger(0, REC2, OBJPROP_YDISTANCE); //--- Get REC2 y-distance int XS_R2 = (int)ObjectGetInteger(0, REC2, OBJPROP_XSIZE); //--- Get REC2 x-size int YS_R2 = (int)ObjectGetInteger(0, REC2, OBJPROP_YSIZE); //--- Get REC2 y-size int XD_R3 = (int)ObjectGetInteger(0, REC3, OBJPROP_XDISTANCE); //--- Get REC3 x-distance int YD_R3 = (int)ObjectGetInteger(0, REC3, OBJPROP_YDISTANCE); //--- Get REC3 y-distance int XS_R3 = (int)ObjectGetInteger(0, REC3, OBJPROP_XSIZE); //--- Get REC3 x-size int YS_R3 = (int)ObjectGetInteger(0, REC3, OBJPROP_YSIZE); //--- Get REC3 y-size int XD_R4 = (int)ObjectGetInteger(0, REC4, OBJPROP_XDISTANCE); //--- Get REC4 x-distance int YD_R4 = (int)ObjectGetInteger(0, REC4, OBJPROP_YDISTANCE); //--- Get REC4 y-distance int XS_R4 = (int)ObjectGetInteger(0, REC4, OBJPROP_XSIZE); //--- Get REC4 x-size int YS_R4 = (int)ObjectGetInteger(0, REC4, OBJPROP_YSIZE); //--- Get REC4 y-size int XD_R5 = (int)ObjectGetInteger(0, REC5, OBJPROP_XDISTANCE); //--- Get REC5 x-distance int YD_R5 = (int)ObjectGetInteger(0, REC5, OBJPROP_YDISTANCE); //--- Get REC5 y-distance int XS_R5 = (int)ObjectGetInteger(0, REC5, OBJPROP_XSIZE); //--- Get REC5 x-size int YS_R5 = (int)ObjectGetInteger(0, REC5, OBJPROP_YSIZE); //--- Get REC5 y-size if(prevMouseState == 0 && MouseState == 1) { //--- Check for mouse button down mlbDownX1 = MouseD_X; //--- Store mouse x-coordinate for REC1 mlbDownY1 = MouseD_Y; //--- Store mouse y-coordinate for REC1 mlbDownXD_R1 = XD_R1; //--- Store REC1 x-distance mlbDownYD_R1 = YD_R1; //--- Store REC1 y-distance mlbDownX2 = MouseD_X; //--- Store mouse x-coordinate for REC2 mlbDownY2 = MouseD_Y; //--- Store mouse y-coordinate for REC2 mlbDownXD_R2 = XD_R2; //--- Store REC2 x-distance mlbDownYD_R2 = YD_R2; //--- Store REC2 y-distance mlbDownX3 = MouseD_X; //--- Store mouse x-coordinate for REC3 mlbDownY3 = MouseD_Y; //--- Store mouse y-coordinate for REC3 mlbDownXD_R3 = XD_R3; //--- Store REC3 x-distance mlbDownYD_R3 = YD_R3; //--- Store REC3 y-distance mlbDownX4 = MouseD_X; //--- Store mouse x-coordinate for REC4 mlbDownY4 = MouseD_Y; //--- Store mouse y-coordinate for REC4 mlbDownXD_R4 = XD_R4; //--- Store REC4 x-distance mlbDownYD_R4 = YD_R4; //--- Store REC4 y-distance mlbDownX5 = MouseD_X; //--- Store mouse x-coordinate for REC5 mlbDownY5 = MouseD_Y; //--- Store mouse y-coordinate for REC5 mlbDownXD_R5 = XD_R5; //--- Store REC5 x-distance mlbDownYD_R5 = YD_R5; //--- Store REC5 y-distance if(MouseD_X >= XD_R1 && MouseD_X <= XD_R1 + XS_R1 && //--- Check if mouse is within REC1 bounds MouseD_Y >= YD_R1 && MouseD_Y <= YD_R1 + YS_R1) { movingState_R1 = true; //--- Enable REC1 movement } if(MouseD_X >= XD_R3 && MouseD_X <= XD_R3 + XS_R3 && //--- Check if mouse is within REC3 bounds MouseD_Y >= YD_R3 && MouseD_Y <= YD_R3 + YS_R3) { movingState_R3 = true; //--- Enable REC3 movement } if(MouseD_X >= XD_R5 && MouseD_X <= XD_R5 + XS_R5 && //--- Check if mouse is within REC5 bounds MouseD_Y >= YD_R5 && MouseD_Y <= YD_R5 + YS_R5) { movingState_R5 = true; //--- Enable REC5 movement } } if(movingState_R1) { //--- Handle REC1 (TP) movement ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable chart scrolling bool canMove = false; //--- Flag to check if movement is valid if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") { //--- Check for buy orders if(YD_R1 + YS_R1 < YD_R3) { //--- Ensure TP is above entry for buy orders canMove = true; //--- Allow movement ObjectSetInteger(0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1); //--- Update REC1 y-position ObjectSetInteger(0, REC2, OBJPROP_YDISTANCE, YD_R1 + YS_R1); //--- Update REC2 y-position ObjectSetInteger(0, REC2, OBJPROP_YSIZE, YD_R3 - (YD_R1 + YS_R1)); //--- Update REC2 y-size } } else { //--- Handle sell orders if(YD_R1 > YD_R3 + YS_R3) { //--- Ensure TP is below entry for sell orders canMove = true; //--- Allow movement ObjectSetInteger(0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1); //--- Update REC1 y-position ObjectSetInteger(0, REC4, OBJPROP_YDISTANCE, YD_R3 + YS_R3); //--- Update REC4 y-position ObjectSetInteger(0, REC4, OBJPROP_YSIZE, YD_R1 - (YD_R3 + YS_R3)); //--- Update REC4 y-size } } if(canMove) { //--- If movement is valid datetime dt_TP = 0; //--- Variable for TP time double price_TP = 0; //--- Variable for TP price int window = 0; //--- Chart window ChartXYToTimePrice(0, XD_R1, YD_R1 + YS_R1, window, dt_TP, price_TP); //--- Convert chart coordinates to time and price ObjectSetInteger(0, TP_HL, OBJPROP_TIME, dt_TP); //--- Update TP horizontal line time ObjectSetDouble(0, TP_HL, OBJPROP_PRICE, price_TP); //--- Update TP horizontal line price update_Text(REC1, "TP: " + DoubleToString(MathAbs((Get_Price_d(TP_HL) - Get_Price_d(PR_HL)) / _Point), 0) + " Points | " + Get_Price_s(TP_HL)); //--- Update REC1 text update_Text(TP_LABEL, "TP: " + Get_Price_s(TP_HL)); //--- Update TP label text } ChartRedraw(0); //--- Redraw chart } if(movingState_R5) { //--- Handle REC5 (SL) movement ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable chart scrolling bool canMove = false; //--- Flag to check if movement is valid if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") { //--- Check for buy orders if(YD_R5 > YD_R4) { //--- Ensure SL is below entry for buy orders canMove = true; //--- Allow movement ObjectSetInteger(0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5); //--- Update REC5 y-position ObjectSetInteger(0, REC4, OBJPROP_YDISTANCE, YD_R3 + YS_R3); //--- Update REC4 y-position ObjectSetInteger(0, REC4, OBJPROP_YSIZE, YD_R5 - (YD_R3 + YS_R3)); //--- Update REC4 y-size } } else { //--- Handle sell orders if(YD_R5 + YS_R5 < YD_R3) { //--- Ensure SL is above entry for sell orders canMove = true; //--- Allow movement ObjectSetInteger(0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5); //--- Update REC5 y-position ObjectSetInteger(0, REC2, OBJPROP_YDISTANCE, YD_R5 + YS_R5); //--- Update REC2 y-position ObjectSetInteger(0, REC2, OBJPROP_YSIZE, YD_R3 - (YD_R5 + YS_R5)); //--- Update REC2 y-size } } if(canMove) { //--- If movement is valid datetime dt_SL = 0; //--- Variable for SL time double price_SL = 0; //--- Variable for SL price int window = 0; //--- Chart window ChartXYToTimePrice(0, XD_R5, YD_R5 + YS_R5, window, dt_SL, price_SL); //--- Convert chart coordinates to time and price ObjectSetInteger(0, SL_HL, OBJPROP_TIME, dt_SL); //--- Update SL horizontal line time ObjectSetDouble(0, SL_HL, OBJPROP_PRICE, price_SL); //--- Update SL horizontal line price update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), 0) + " Points | " + Get_Price_s(SL_HL)); //--- Update REC5 text update_Text(SL_LABEL, "SL: " + Get_Price_s(SL_HL)); //--- Update SL label text } ChartRedraw(0); //--- Redraw chart } if(movingState_R3) { //--- Handle REC3 (Entry) movement ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable chart scrolling ObjectSetInteger(0, REC3, OBJPROP_XDISTANCE, mlbDownXD_R3 + MouseD_X - mlbDownX3); //--- Update REC3 x-position ObjectSetInteger(0, REC3, OBJPROP_YDISTANCE, mlbDownYD_R3 + MouseD_Y - mlbDownY3); //--- Update REC3 y-position ObjectSetInteger(0, REC1, OBJPROP_XDISTANCE, mlbDownXD_R1 + MouseD_X - mlbDownX1); //--- Update REC1 x-position ObjectSetInteger(0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1); //--- Update REC1 y-position ObjectSetInteger(0, REC2, OBJPROP_XDISTANCE, mlbDownXD_R2 + MouseD_X - mlbDownX2); //--- Update REC2 x-position ObjectSetInteger(0, REC2, OBJPROP_YDISTANCE, mlbDownYD_R2 + MouseD_Y - mlbDownY2); //--- Update REC2 y-position ObjectSetInteger(0, REC4, OBJPROP_XDISTANCE, mlbDownXD_R4 + MouseD_X - mlbDownX4); //--- Update REC4 x-position ObjectSetInteger(0, REC4, OBJPROP_YDISTANCE, mlbDownYD_R4 + MouseD_Y - mlbDownY4); //--- Update REC4 y-position ObjectSetInteger(0, REC5, OBJPROP_XDISTANCE, mlbDownXD_R5 + MouseD_X - mlbDownX5); //--- Update REC5 x-position ObjectSetInteger(0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5); //--- Update REC5 y-position datetime dt_PRC = 0, dt_SL1 = 0, dt_TP1 = 0; //--- Variables for time double price_PRC = 0, price_SL1 = 0, price_TP1 = 0; //--- Variables for price int window = 0; //--- Chart window ChartXYToTimePrice(0, XD_R3, YD_R3 + YS_R3, window, dt_PRC, price_PRC); //--- Convert REC3 coordinates to time and price ChartXYToTimePrice(0, XD_R5, YD_R5 + YS_R5, window, dt_SL1, price_SL1); //--- Convert REC5 coordinates to time and price ChartXYToTimePrice(0, XD_R1, YD_R1 + YS_R1, window, dt_TP1, price_TP1); //--- Convert REC1 coordinates to time and price ObjectSetInteger(0, PR_HL, OBJPROP_TIME, dt_PRC); //--- Update entry horizontal line time ObjectSetDouble(0, PR_HL, OBJPROP_PRICE, price_PRC); //--- Update entry horizontal line price ObjectSetInteger(0, TP_HL, OBJPROP_TIME, dt_TP1); //--- Update TP horizontal line time ObjectSetDouble(0, TP_HL, OBJPROP_PRICE, price_TP1); //--- Update TP horizontal line price ObjectSetInteger(0, SL_HL, OBJPROP_TIME, dt_SL1); //--- Update SL horizontal line time ObjectSetDouble(0, SL_HL, OBJPROP_PRICE, price_SL1); //--- Update SL horizontal line price update_Text(REC1, "TP: " + DoubleToString(MathAbs((Get_Price_d(TP_HL) - Get_Price_d(PR_HL)) / _Point), 0) + " Points | " + Get_Price_s(TP_HL)); //--- Update REC1 text update_Text(REC3, selected_order_type + ": | Lot: " + DoubleToString(lot_size, 2) + " | " + Get_Price_s(PR_HL)); //--- Update REC3 text update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), 0) + " Points | " + Get_Price_s(SL_HL)); //--- Update REC5 text update_Text(PRICE_LABEL, "Entry: " + Get_Price_s(PR_HL)); //--- Update entry label text update_Text(SL_LABEL, "SL: " + Get_Price_s(SL_HL)); //--- Update SL label text update_Text(TP_LABEL, "TP: " + Get_Price_s(TP_HL)); //--- Update TP label text ChartRedraw(0); //--- Redraw chart } if(MouseState == 0) { //--- Check if mouse button is released movingState_R1 = false; //--- Disable REC1 movement movingState_R3 = false; //--- Disable REC3 movement movingState_R5 = false; //--- Disable REC5 movement ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable chart scrolling } prevMouseState = MouseState; //--- Update previous mouse state }
Здесь мы расширяем функцию OnChartEvent для обработки движений мыши при перетаскивании объектов графика в нашем инструменте, когда tool_visible равен true и id равен CHARTEVENT_MOUSE_MOVE. Мы извлекаем значения MouseD_X, MouseD_Y и MouseState из параметров lparam, dparam и sparam и используем функцию ObjectGetInteger для получения позиции и размера (XD_R1, YD_R1, XS_R1, YS_R1 для REC1, и аналогично для REC2 - REC5).
При клике мышью (prevMouseState 0 до MouseState 1) мы сохраняем координаты мыши в mlbDownX1, mlbDownY1 и позиции прямоугольников в mlbDownXD_R1, mlbDownYD_R1 (и для REC2 - REC5), устанавливая movingState_R1, movingState_R3 или movingState_R5 в значение true, если клик происходит в пределах REC1, REC3 или REC5.
Для параметра movingState_R1 (TP) мы отключаем прокрутку с помощью ChartSetInteger, проверяем позицию TP (выше — для BUY_STOP/BUY_LIMIT, ниже — для продажи), обновляем позиции и размеры REC1 и REC2/REC4 с помощью ObjectSetInteger, преобразуем координаты в цену, используя ChartXYToTimePrice, обновляем TP_HL с помощью ObjectSetDouble, а также обновляем текст update_Text, Get_Price_d, Get_Price_s, DoubleToString и MathAbs.
Аналогично, для movingState_R5 (SL) мы корректируем REC5 и REC4/REC2, обновляем SL_HL и текст. Для movingState_R3 (entry) мы перемещаем все прямоугольники и обновляем PR_HL, TP_HL, SL_HL и текст.
При отпускании кнопки мыши (MouseState 0) мы сбрасываем флаги movingState, включаем прокрутку и обновляем prevMouseState, вызывая ChartRedraw для отображения изменений. Наконец, нам нужно полностью удалить объекты при удалении программы и обновить размер лота на тике, чтобы отобразить изменения. Впрочем, это не критично.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { deleteObjects(); //--- Delete tool objects deletePanel(); //--- Delete control panel objects } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update lot size from edit field string lot_text = ObjectGetString(0, LOT_EDIT, OBJPROP_TEXT); //--- Get lot size text from edit field double new_lot = StringToDouble(lot_text); //--- Convert lot size text to double if(new_lot > 0) lot_size = new_lot; //--- Update lot size if valid }
Здесь мы реализуем два важных обработчика событий для нашего инструмента - OnDeinit и OnTick. В функции OnDeinit, которая срабатывает при удалении советника с графика, мы вызываем функцию deleteObjects для удаления объектов графика, таких как REC1 - REC5, TP_HL, SL_HL и PR_HL, а также функцию deletePanel для удаления объектов панели управления, таких как PANEL_BG, LOT_EDIT и кнопок, таких как BUY_STOP_BTN, обеспечивая корректный выход из программы.
В функции OnTick, выполняемой при каждом изменении цены, мы используем функцию ObjectGetString для извлечения текста из поля LOT_EDIT, конвертирования его в тип double с использованием функции StringToDouble и обновления переменной lot_size, если значение new_lot положительное, поддерживая синхронизацию размера лота в нашем инструменте с пользовательскими входными данными.
После компиляции получаем следующий результат.

Из визуализации видно, что при нажатии на любую из кнопок торговли генерируется соответствующий инструмент цены сделки, который затем при перетаскивании обновляется в режиме реального времени, и цены отображаются на торговой панели готовыми к использованию. При нажатии на кнопку размещения сделки соответствующие сделки динамически размещаются. Осталось только протестировать панель, чтобы убедиться в ее безупречной работе.
Тестирование на истории
Мы провели тестирование, и ниже представлена сводная визуализация в одном файле в формате GIF.

Заключение
Мы разработали интерактивный инструмент Trade Assistant Tool на языке MQL5, который сочетает визуальную точность с интуитивно понятным управлением, оптимизируя процесс размещения отложенных ордеров. Мы продемонстрировали, как разработать удобный графический пользовательский интерфейс (GUI) с помощью таких функций, как createControlPanel и placeOrder, и обеспечить его надежность посредством структурированной реализации и проверки. Вы можете настроить этот инструмент в соответствии со своим стилем торговли, повысив эффективность размещения ордеров. В последующих статьях мы представим расширенные функции, такие как управление рисками и перетаскиваемые панели. До встречи!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17931
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Машинное обучение и Data Science (Часть 39): Тестируем связку новостей с ИИ
Как использовать конечные разности для прогнозирования цен
Торговля LLM-агента со встроенной философией топ-трейдеров
Торговые инструменты на MQL5 (Часть 2): Улучшение интерактивного торгового помощника через динамическую визуализацию
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Вышла новая статья MQL5 Trading Tools (Part 1): создание интерактивного визуального помощника по торговле отложенными ордерами:
Автор Аллан Мунене Мутирия