
MQL5 Trading Tools (Part 1): Building an Interactive Visual Pending Orders Trade Assistant Tool
Introduction
Developing effective trading tools is essential for simplifying complex Forex trading tasks, yet creating intuitive interfaces that enhance decision-making remains a challenge. What if you could design a visual, interactive tool streamlining pending order placement within MetaTrader 5? In this article, we introduce a custom MetaQuotes Language 5 (MQL5) Expert Advisor (EA) that empowers traders with a Trade Assistant Tool, combining graphical precision with user-friendly controls to place Buy/Sell Stop and Limit orders efficiently. We’ll cover these steps in this order:
- Conceptual Design and Objectives of the Trade Assistant Tool
- Implementation in MQL5
- Backtesting
- Conclusion
By the end, you’ll have a clear understanding of how to build and test this tool, paving the way for advanced enhancements in the preceding parts.
Conceptual Design and Objectives of the Trade Assistant Tool
We aim to develop a Trade Assistant Tool that delivers a seamless and efficient experience for us by simplifying the process of placing pending orders in Forex trading. We envision the tool as a graphical user interface (GUI) that integrates directly with MetaTrader 5, enabling us to set up Buy Stop, Sell Stop, Buy Limit, and Sell Limit orders through an intuitive control panel. Our design is to include buttons for selecting the desired order type and an input field for specifying the lot size. We prioritize visual interaction, allowing us to define entry price, stop-loss (SL), and take-profit (TP) levels by dragging interactive elements on the chart, which provides immediate feedback on price levels and the point differences between them.
Our focus is to ensure the tool is accessible and responsive. We will design the interface to be responsive, enabling us to adjust price levels with accuracy and confirm orders with a single click, minimizing the time spent on setup. Additionally, we will incorporate options to cancel or close the interface, offering us flexibility to adapt quickly to changing market conditions. By creating a visually appealing and responsive tool, we seek to enhance our decision-making, reduce errors in order placement, and provide a foundation for future enhancements, such as advanced risk management features, that we will explore in subsequent iterations. In a nutshell, here is a visualization of what we envision to create.
Implementation in MQL5
To create the program in MQL5, we will need to define the program metadata, and then define some object name constants, and lastly, we include some library files that will enable us to do the trading activity.
//+------------------------------------------------------------------+ //| 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
Here, we lay the foundation for our Trade Assistant Tool by defining essential components, variables, and functions that enable the tool’s graphical and trading functionalities. We begin by including the "Trade.mqh" library, which provides the "CTrade" class for executing trading operations, such as placing pending orders. We then define a series of constants using #define to assign unique names for GUI elements, such as "PANEL_BG" for the control panel background, "LOT_EDIT" for the lot size input field, and buttons like "BUY_STOP_BTN" and "SELL_STOP_BTN" for order type selection, and so much more.
We implement three utility functions to manage chart object properties: the "Get_Price_d" function retrieves the price of an object as a double, the "Get_Price_s" function converts this price to a string formatted with the appropriate number of decimal places using the DoubleToString function, and the "update_Text" function updates the text of an object using the ObjectSetString function to display real-time price information.
To handle the positioning and sizing of the interactive rectangles, we declare sets of integer variables like "xd1", "yd1", "xs1", and "ys1" for each rectangle ("REC1" through "REC5"), representing their x-distance, y-distance, x-size, and y-size on the chart.
Finally, we define key control panel variables: "tool_visible" as a boolean to track the tool’s visibility, "selected_order_type" as a string to store the chosen order type, "lot_size" as a double initialized to 0.01 for trade volume, "obj_Trade" as a "CTrade" object for executing trades, and "panel_x" and "panel_y" as integers to set the control panel’s position at coordinates (10, 30).
These elements collectively establish the structural backbone for our tool’s interactive interface and trading capabilities. So we can now move on to creating the control panel, but first, we will need a function to create a custom button.
//+------------------------------------------------------------------+ //| 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 }
We define the "createButton" function to build customizable buttons for our tool. It accepts parameters like "objName" for the button’s name, "text" for its label, "xD" and "yD" for position, "xS" and "yS" for size, "clrTxt" and "clrBG" for text and background colors, "fontsize" (default 12), "clrBorder" (default "clrNONE"), "isBack" (default false), and "font" (default "Calibri").
We use the ResetLastError function to clear error codes, and then the ObjectCreate function to create an OBJ_BUTTON. If it fails, we call the Print function with __FUNCTION__ and GetLastError to log the error and return false.
On success, we set properties like position, size, and colors using the ObjectSetInteger and ObjectSetString functions, disable state and selection, and call the ChartRedraw function to update the chart, returning true. This will enable us to create interactive buttons. So with this function, we can create the control panel function.
//+------------------------------------------------------------------+ //| 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 }
Here, we define the "createControlPanel" function to construct the main graphical user interface (GUI) for our Trade Assistant Tool. We start by using the ObjectCreate function to create a background rectangle named "PANEL_BG" with type OBJ_RECTANGLE_LABEL, positioned at "panel_x" and "panel_y" (set to 10 and 30), with a size of 250x280 pixels, a dark gray background ("C'070,070,070'"), a white border ("clrWhite"), and foreground placement (OBJPROP_BACK set to false).
We then call the "createButton" function to add a close button ("CLOSE_BTN") at the top-right corner, displaying a cross symbol (character 203 from "Wingdings"), styled in crimson. The input is defined in MQL5 documentation as below but you can use one of your liking.
For lot size input, we use the ObjectCreate function to create an edit field ("LOT_EDIT") of type OBJ_EDIT at "panel_x + 70", "panel_y + 40", sized 110x25 pixels, initialized with "0.01", and styled with black text, white background, and centered Arial font using ObjectSetInteger and ObjectSetString functions.
We create three labels for displaying trade information using the "ObjectCreate" function: "PRICE_LABEL" for entry price at "panel_x + 10", "panel_y + 70", spanning 230 pixels with default text "Entry: -"; "SL_LABEL" for stop-loss at "panel_x + 10", "panel_y + 95", with yellow text and default "SL: -"; and "TP_LABEL" for take-profit at "panel_x + 130", "panel_y + 95", with lime text and default "TP: -", all using bold Arial font and centered alignment.
Finally, we use the "createButton" function to add order type buttons—"BUY_STOP_BTN" and "SELL_STOP_BTN" at "panel_y + 140", "BUY_LIMIT_BTN" and "SELL_LIMIT_BTN" at "panel_y + 180"—in green and red, respectively, and action buttons "PLACE_ORDER_BTN" and "CANCEL_BTN" at "panel_y + 240" in blue and gray, all sized 110x30 pixels with Arial font. This setup forms the interactive control panel for our tool. We can call the function in the OnInit event handler to initialize the panel.
//+------------------------------------------------------------------+ //| 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 }
On the OnInit event handler, we call the "createControlPanel" function to construct the graphical user interface, setting up the control panel with buttons, labels, and input fields. Next, we use the ChartRedraw function to refresh the chart, ensuring the panel is displayed immediately. Finally, we return INIT_SUCCEEDED to indicate successful initialization. Upon compilation, we have the following output.
From the image, we can see that we have created the control panel. What we need to create now is the assistant panel that we can use to dynamically get the chart prices and fill automatically to the control panel and trade based on them. This will require integrating the chart scale and the screen scale but we got you covered. What we will do is have everything in a function. Then, we will add event listeners that will call respective functions when the control button is interacted with.
//+------------------------------------------------------------------+ //| 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 } } }
Here, we implement the OnChartEvent event handler to handle user interactions with the tool. The function receives parameters: "id" for the event type, "lparam" for data like x-coordinates, "dparam" for data like y-coordinates, and "sparam" for string data like object names. We check if "id" equals CHARTEVENT_OBJECT_CLICK to detect object clicks, and if "sparam" matches "BUY_STOP_BTN", we set the "selected_order_type" variable to "BUY_STOP", enabling us to register the user’s selection of a Buy Stop order. If that is the case, we will need a function to show the tool.
//+------------------------------------------------------------------+ //| 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 }
To show the chart price tool, we implement the "showTool" function and begin by hiding the control panel using the ObjectSetInteger function, setting OBJPROP_BACK to false for objects like "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", and "CLOSE_BTN".
We calculate the tool’s x-position with the ChartGetInteger function to get CHART_WIDTH_IN_PIXELS, setting "tool_x" 450 pixels from the right edge. For "BUY_STOP" or "BUY_LIMIT" orders, we use the "createButton" function to create "REC1" (TP) at "tool_x", y=20, sized 350x30 in green, and set variables "xd1", "yd1", "xs1", "ys1" via "ObjectGetInteger", then position "REC2" to "REC5" vertically (TP, entry, SL) with "xd2" to "xd5", "yd2" to "yd5", "xs2" to "xs5", "ys2" to "ys5".
For sell orders, we create "REC5" (SL) in red and arrange "REC2" to "REC1" (SL, entry, TP).
We declare "dt_tp", "dt_sl", "dt_prc" for time, "price_tp", "price_sl", and "price_prc" for prices, and "window" for the chart, using the ChartXYToTimePrice function to convert coordinates of "REC1", "REC3", and "REC5" to price and time. We call the "createHL" function to draw "TP_HL", "PR_HL", and "SL_HL" in teal, blue, and red.
Depending on "selected_order_type", we use "createButton" to create the remaining rectangles ("REC2", "REC3", "REC4", "REC5" for buy; "REC2", "REC3", "REC4", "REC1" for sell) with appropriate colors. We update text using the "update_Text" function for "REC1", "REC3", "REC5", "PRICE_LABEL", "SL_LABEL", and "TP_LABEL", calculating point differences with "Get_Price_d", "Get_Price_s", DoubleToString, and MathAbs.
Finally, we set "tool_visible" to true, enable mouse events with ChartSetInteger, and call ChartRedraw to display the tool. To create the horizontal lines, we use the following function.
//+------------------------------------------------------------------+ //| 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 }
Here, we just create the OBJ_HLINE object and set the necessary object parameters as we did with the function for creating buttons earlier on. We will also need a function to show the panel as below.
//+------------------------------------------------------------------+ //| 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 }
We define the "showPanel" function to display our control panel. We use the ObjectSetInteger function to set "OBJPROP_BACK" to false for "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", and "CLOSE_BTN", making them visible.
We reset the state with the "update_Text" function, setting "PRICE_LABEL" to "Entry: -", "SL_LABEL" to "SL: -", "TP_LABEL" to "TP: -", and "PLACE_ORDER_BTN" to "Place Order", clear "selected_order_type", set "tool_visible" to false, disable mouse events via ChartSetInteger, and call ChartRedraw to update the chart.
To delete the tool and panel, we use the following functions, by calling the ObjectDelete function on respective objects.
//+------------------------------------------------------------------+ //| 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 }
To place the orders when the respective button is clicked, we use the following function.
//+------------------------------------------------------------------+ //| 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 } } }
We implement the "placeOrder" function to execute pending orders for our tool and we start by retrieving the entry price ("price"), stop-loss ("sl"), and take-profit ("tp") using the "Get_Price_d" function for "PR_HL", "SL_HL", and "TP_HL", get the current "symbol" with the Symbol function, and set a 24-hour "expiration" using the TimeCurrent function.
We validate "lot_size" (>0) and ensure "price", "sl", and "tp" are positive, exiting with the Print function if invalid. For "BUY_STOP" or "BUY_LIMIT", we check "sl" is below "price" and "tp" is above, and for "SELL_STOP" or "SELL_LIMIT", "sl" is above and "tp" is below, using "Print" to log errors and exit if conditions fail. If "selected_order_type" is invalid, we exit with a "Print" message.
We then use "obj_Trade" methods—"BuyStop", "SellStop", "BuyLimit", or "SellLimit"—to place the order, logging success or failure with "Print" and GetLastError if it fails. Armed with these functions, we can call them on respective button clicks as below.
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 }
Upon compilation, we have the following outcome.
From the image, we can see that we can create the respective price chart tool dynamically. What we need to do next is make the tool intractable by being able to move it along the chart. Here is the logic we apply in the OnChartEvent event handler.
//+------------------------------------------------------------------+ //| 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
First, we define variables for the OnChartEvent function to enable drag-and-drop in our Trade Assistant Tool. "prevMouseState" tracks mouse state changes, while "mlbDownX1", "mlbDownY1", "mlbDownXD_R1", "mlbDownYD_R1" (and similar for "REC2" to "REC5") store mouse and rectangle coordinates for "REC1" (TP), "REC3" (entry), and "REC5" (SL) during clicks. Boolean flags "movingState_R1", "movingState_R3", and "movingState_R5" indicate if these rectangles are being dragged. We can then use these control variables to define the movement of the price tool.
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 }
Here, we extend the OnChartEvent function to handle mouse movement for dragging chart objects in our tool when "tool_visible" is true and "id" is CHARTEVENT_MOUSE_MOVE. We extract "MouseD_X", "MouseD_Y", and "MouseState" from "lparam", "dparam", and "sparam", and use the ObjectGetInteger function to get position and size ("XD_R1", "YD_R1", "XS_R1", "YS_R1" for "REC1", and similarly for "REC2" to "REC5").
On mouse click ("prevMouseState" 0 to "MouseState" 1), we store mouse coordinates in "mlbDownX1", "mlbDownY1" and rectangle positions in "mlbDownXD_R1", "mlbDownYD_R1" (and for "REC2" to "REC5"), setting "movingState_R1", "movingState_R3", or "movingState_R5" to true if the click is within "REC1", "REC3", or "REC5" bounds.
For "movingState_R1" (TP), we disable scrolling with ChartSetInteger, validate TP position (above entry for "BUY_STOP"/"BUY_LIMIT", below for sell), update "REC1" and "REC2"/"REC4" positions and sizes with ObjectSetInteger, convert coordinates to price using ChartXYToTimePrice, update "TP_HL" with ObjectSetDouble, and refresh text with "update_Text", "Get_Price_d", "Get_Price_s", DoubleToString, and MathAbs.
Similarly, for "movingState_R5" (SL), we adjust "REC5" and "REC4"/"REC2", update "SL_HL", and refresh text. For "movingState_R3" (entry), we move all rectangles and update "PR_HL", "TP_HL", "SL_HL", and text.
On mouse release ("MouseState" 0), we reset "movingState" flags, enable scrolling, and update "prevMouseState", calling ChartRedraw to reflect changes. Finally, what we need to do is delete the objects completely when we remove the program and update the lot size on the tick to make the changes reflect, though you can leave this as it is not very necessary.
//+------------------------------------------------------------------+ //| 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 }
Here, we implement two essential event handlers for our tool: OnDeinit and OnTick. In the OnDeinit function, triggered when the Expert Advisor is removed from the chart, we call the "deleteObjects" function to remove chart objects like "REC1" to "REC5", "TP_HL", "SL_HL", and "PR_HL", and the "deletePanel" function to delete control panel objects such as "PANEL_BG", "LOT_EDIT", and buttons like "BUY_STOP_BTN", ensuring a clean exit.
In the OnTick function, executed on each price tick, we use the ObjectGetString function to retrieve the text from the "LOT_EDIT" field, convert it to a double using the StringToDouble function, and update the "lot_size" variable if the "new_lot" value is positive, keeping our tool’s lot size synchronized with user input.
Upon compilation, we have the following output.
From the visualization, we can see that when we click on any of the trade buttons, the respective trade price tool is generated and then when dragged, it is updated in real-time and prices reflect in the trade panel ready to be used for trading purposes when the place trade button is clicked, the respective trades are placed dynamically. This verifies that we have achieved our objective, and what remains is testing the panel to ensure it is perfect, and that is handled in the next section.
Backtesting
We did the testing and below is the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image format.
Conclusion
In conclusion, we’ve developed an interactive Trade Assistant Tool in MQL5 that combines visual precision with intuitive controls, streamlining how we place pending orders. We’ve demonstrated how to design its user-friendly Graphical User Interface (GUI), implement it with functions like "createControlPanel" and "placeOrder", and ensure its reliability through structured implementation and validation. You can customize this tool to suit your trading style, enhancing your order placement efficiency, and look forward to the preceding parts where we’ll introduce advanced features like risk management and draggable panels. Keep tuned.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use