English Русский 中文 Deutsch 日本語
preview
Herramientas de trading de MQL5 (Parte 1): Creación de una herramienta interactiva de asistencia para operaciones con órdenes pendientes

Herramientas de trading de MQL5 (Parte 1): Creación de una herramienta interactiva de asistencia para operaciones con órdenes pendientes

MetaTrader 5Trading |
23 1
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

El desarrollo de herramientas de trading eficaces es esencial para simplificar las complejas tareas del mercado de divisas; sin embargo, la creación de interfaces intuitivas que mejoren la toma de decisiones sigue siendo un reto. ¿Y si pudiera diseñar una herramienta visual e interactiva que agilizara la colocación de órdenes pendientes en MetaTrader 5? En este artículo, presentamos un asesor experto (EA) personalizado escrito en MetaQuotes Language 5 (MQL5) que pone a disposición de los operadores una herramienta de asistencia para operar, la cual combina precisión gráfica con controles intuitivos para colocar órdenes stop y limit de compra/venta de manera eficiente. Abordaremos estos pasos en este orden:

  1. Diseño conceptual y objetivos de la herramienta de asistencia para operar
  2. Implementación en MQL5
  3. Backtesting
  4. Conclusión

Al finalizar, comprenderás claramente cómo construir y probar esta herramienta, lo que allanará el camino para mejoras avanzadas en las siguientes partes.


Diseño conceptual y objetivos de la herramienta de asistencia para operar

Nuestro objetivo es desarrollar una herramienta de asistencia para operar que nos ofrezca una experiencia fluida y eficiente, simplificando el proceso de colocación de órdenes pendientes en el mercado de divisas. Concebimos la herramienta como una interfaz gráfica de usuario (GUI) que se integra directamente con MetaTrader 5, lo que nos permite configurar órdenes de Buy Stop, Sell Stop, Buy Limit y Sell Limit a través de un panel de control intuitivo. Nuestro diseño prevé la inclusión de botones para seleccionar el tipo de orden deseado y un campo de entrada para especificar el tamaño del lote. Damos prioridad a la interacción visual, lo que nos permite definir los niveles de precio de entrada, stop-loss (SL) y take-profit (TP) arrastrando elementos interactivos sobre el gráfico, lo que ofrece información inmediata sobre los niveles de precios y las diferencias entre ellos.

Nuestro objetivo es garantizar que la herramienta sea accesible y se adapte a las necesidades de los usuarios. Diseñaremos una interfaz adaptativa que nos permita ajustar los precios con precisión y confirmar las órdenes con un solo clic, lo que reducirá al mínimo el tiempo dedicado a la configuración. Además, incorporaremos opciones para cancelar o cerrar la interfaz, lo que nos proporcionará flexibilidad para adaptarnos rápidamente a las condiciones cambiantes del mercado. Con la creación de una herramienta visualmente atractiva y adaptativa, pretendemos mejorar nuestra toma de decisiones, reducir los errores en la colocación de órdenes y sentar las bases para futuras mejoras, como funciones avanzadas de gestión de riesgos, que exploraremos en versiones posteriores. En pocas palabras, aquí tienen una representación de lo que nos proponemos crear.

PLAN DE LA INTERFAZ GRÁFICA


Implementación en MQL5

Para crear el programa en MQL5, tendremos que definir los metadatos del programa; a continuación, definir algunas constantes de nombres de objetos y, por último, incluir algunos archivos de biblioteca que nos permitirán realizar operaciones de trading.

//+------------------------------------------------------------------+
//|                                         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

En este punto, sentamos las bases de nuestra herramienta de asistencia para operar definiendo los componentes, variables y funciones esenciales que permiten las funcionalidades gráficas y de trading de la herramienta. Comenzamos incluyendo la biblioteca «Trade.mqh», que proporciona la clase «CTrade» para realizar operaciones, como la colocación de órdenes pendientes. A continuación, definimos una serie de constantes mediante #define para asignar nombres únicos a los elementos de la interfaz gráfica de usuario, como «PANEL_BG» para el fondo del panel de control, «LOT_EDIT» para el campo de entrada para el tamaño del lote, y botones como «BUY_STOP_BTN» y «SELL_STOP_BTN» para la selección del tipo de orden, entre muchos otros.

Implementamos tres funciones de utilidad para gestionar las propiedades de los objetos de gráfico: la función «Get_Price_d» recupera el precio de un objeto como un valor de tipo double, la función «Get_Price_s» convierte este precio a una cadena formateada con el número adecuado de decimales utilizando la función DoubleToString, y la función «update_Text» actualiza el texto de un objeto utilizando la función ObjectSetString para mostrar información sobre los precios en tiempo real.

Para gestionar la posición y el tamaño de los rectángulos interactivos, declaramos conjuntos de variables enteras como «xd1», «yd1», «xs1» y «ys1» para cada rectángulo (de «REC1» a «REC5»), que representan su distancia en el eje X, su distancia en el eje Y, su tamaño en el eje X y su tamaño en el eje Y en el gráfico.

Por último, definimos las variables clave del panel de control: «tool_visible» como un valor booleano para controlar la visibilidad de la herramienta, «selected_order_type» como una cadena de caracteres para almacenar el tipo de orden seleccionado, «lot_size» como un número de doble precisión inicializado en 0,01 para el volumen de la operación, «obj_Trade» como un objeto «CTrade» para ejecutar operaciones, y «panel_x» y «panel_y» como números enteros para establecer la posición del panel de control en las coordenadas (10, 30).

En conjunto, estos elementos constituyen la columna vertebral de la interfaz interactiva y las funciones de negociación de nuestra herramienta. Así pues, ya podemos pasar a crear el panel de control, pero antes necesitaremos una función para crear un botón personalizado.

//+------------------------------------------------------------------+
//| 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
}

Definimos la función «createButton» para crear botones personalizables para nuestra herramienta. Acepta parámetros como «objName» para el nombre del botón, «text» para su etiqueta, «xD» y «yD» para la posición, «xS» y «yS» para el tamaño, «clrTxt» y «clrBG» para los colores del texto y del fondo, «fontsize» (por defecto 12), «clrBorder» (por defecto «clrNONE»), «isBack» (por defecto false) y «font» (por defecto «Calibri»).

Utilizamos la función ResetLastError para borrar los códigos de error y, a continuación, la función ObjectCreate para crear un OBJ_BUTTON. Si falla, llamamos a la función Print con __FUNCTION__ y GetLastError para registrar el error y devolver «false».

Si la operación se realiza correctamente, establecemos propiedades como la posición, el tamaño y los colores mediante las funciones ObjectSetInteger y ObjectSetString, desactivamos el estado y la selección, y llamamos a la función ChartRedraw para actualizar el gráfico, devolviendo «true». Esto nos permitirá crear botones interactivos. Así pues, con esta función, podemos crear la función del panel de control.

//+------------------------------------------------------------------+
//| 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
}

Aquí definimos la función «createControlPanel» para crear la interfaz gráfica de usuario (GUI) principal de nuestra herramienta de asistencia para operar. Comenzamos utilizando la función ObjectCreate para crear un rectángulo de fondo denominado «PANEL_BG» con el tipo OBJ_RECTANGLE_LABEL, situado en «panel_x» y «panel_y» (establecidos en 10 y 30), con un tamaño de 250 x 280 píxeles, un fondo gris oscuro («C'070,070,070'»), un borde blanco («clrWhite») y la posición en primer plano (OBJPROP_BACK establecida en false).

A continuación, llamamos a la función «createButton» para añadir un botón de cierre («CLOSE_BTN») en la esquina superior derecha, que muestra un símbolo de cruz (el carácter 203 de «Wingdings») con un estilo de color carmesí. La entrada se define en la documentación de MQL5 tal y como se indica a continuación, pero puede utilizar la que prefiera.

TABLA DE WINGDINGS DE MQL5

Para introducir el tamaño del lote, utilizamos la función ObjectCreate para crear un campo de edición («LOT_EDIT») de tipo OBJ_EDIT en «panel_x + 70», «panel_y + 40», con un tamaño de 110 x 25 píxeles, inicializado con «0,01» y con un estilo de texto negro, fondo blanco y fuente Arial centrada, utilizando las funciones ObjectSetInteger y ObjectSetString.

Creamos tres etiquetas para mostrar la información de la operación utilizando la función «ObjectCreate»: «PRICE_LABEL» para el precio de entrada en «panel_x + 10», «panel_y + 70», con una extensión de 230 píxeles y el texto predeterminado «Entry: -»; «SL_LABEL» para el stop-loss en «panel_x + 10», «panel_y + 95», con texto en amarillo y el valor predeterminado «SL: -»; y «TP_LABEL» para el take-profit en «panel_x + 130», «panel_y + 95», con texto en color lima y el texto predeterminado «TP: -», todas ellas utilizando la fuente Arial en negrita y alineación centrada.

Por último, utilizamos la función «createButton» para añadir los botones de tipo de orden —«BUY_STOP_BTN» y «SELL_STOP_BTN» en «panel_y + 140», «BUY_LIMIT_BTN» y «SELL_LIMIT_BTN» en «panel_y + 180» —en verde y rojo, respectivamente, y los botones de acción «PLACE_ORDER_BTN» y «CANCEL_BTN» en «panel_y + 240» en azul y gris, todos con un tamaño de 110 x 30 píxeles y la fuente Arial. Esta configuración constituye el panel de control interactivo de nuestra herramienta. Podemos llamar a la función en el controlador de eventos OnInit para inicializar el 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
}

En el controlador de eventos OnInit, llamamos a la función «createControlPanel» para crear la interfaz gráfica de usuario, configurando el panel de control con botones, etiquetas y campos de entrada. A continuación, utilizamos la función ChartRedraw para actualizar el gráfico, asegurándonos de que el panel se muestre de inmediato. Por último, devolvemos INIT_SUCCEEDED para indicar que la inicialización se ha realizado correctamente. Tras la compilación, obtenemos el siguiente resultado.

EL PANEL DE CONTROL

En la imagen podemos ver que hemos creado el panel de control. Lo que tenemos que crear ahora es el panel auxiliar que nos permita obtener dinámicamente los precios del gráfico, introducirlos automáticamente en el panel de control y operar en función de ellos. Para ello será necesario integrar la escala del gráfico y la escala de la pantalla, pero no se preocupe, nosotros nos encargamos. Lo que haremos es incluirlo todo en una función. A continuación, añadiremos detectores de eventos que activarán las funciones correspondientes cuando se interactúe con el botón de control.

//+------------------------------------------------------------------+
//| 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
      }
   }
}

Aquí implementamos el controlador de eventos OnChartEvent para gestionar las interacciones del usuario con la herramienta. La función recibe los siguientes parámetros: «id» para el tipo de evento, «lparam» para datos como las coordenadas x, «dparam» para datos como las coordenadas y, y «sparam» para datos de tipo cadena, como los nombres de los objetos. Comprobamos si «id» es igual a CHARTEVENT_OBJECT_CLICK para detectar clics en objetos, y si «sparam» coincide con «BUY_STOP_BTN», establecemos la variable «selected_order_type» en «BUY_STOP», lo que nos permite registrar la selección del usuario de una orden de compra con límite de stop. Si ese es el caso, necesitaremos una función para mostrar la herramienta.

//+------------------------------------------------------------------+
//| 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
}

Para mostrar la herramienta de precios del gráfico, implementamos la función «showTool» y comenzamos ocultando el panel de control mediante la función ObjectSetInteger, estableciendo OBJPROP_BACK en false para objetos como «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» y «CLOSE_BTN».

Calculamos la posición x de la herramienta mediante la función ChartGetInteger para obtener CHART_WIDTH_IN_PIXELS, fijando «tool_x» a 450 píxeles del borde derecho. Para las órdenes «BUY_STOP» o «BUY_LIMIT», utilizamos la función «createButton» para crear «REC1» (TP) en «tool_x», y=20, con un tamaño de 350x30 en color verde, y establecemos las variables «xd1», «yd1», «xs1», «ys1» mediante «ObjectGetInteger»; a continuación, colocamos «REC2» a «REC5» verticalmente (TP, entrada, SL) con «xd2» a «xd5», «yd2» a «yd5», «xs2» a «xs5» y «ys2» a «ys5».

En el caso de las órdenes de venta, creamos «REC5» (SL) en rojo y configuramos «REC2» a «REC1» (SL, entrada, TP).

Declaramos «dt_tp», «dt_sl» y «dt_prc» para el tiempo, «price_tp», «price_sl» y «price_prc» para los precios, y «window» para el gráfico, utilizando la función ChartXYToTimePrice para convertir las coordenadas de «REC1», «REC3» y «REC5» a precio y tiempo. Llamamos a la función «createHL» para dibujar «TP_HL», «PR_HL» y «SL_HL» en verde azulado, azul y rojo.

En función del valor de «selected_order_type», utilizamos «createButton» para crear los rectángulos restantes («REC2», «REC3», «REC4», «REC5» para la compra; «REC2», «REC3», «REC4», «REC1» para la venta) con los colores correspondientes. Actualizamos el texto mediante la función «update_Text» para «REC1», «REC3», «REC5», «PRICE_LABEL», «SL_LABEL» y «TP_LABEL», calculando las diferencias de puntos con «Get_Price_d», «Get_Price_s», DoubleToString y MathAbs.

Por último, establecemos «tool_visible» en «true», habilitamos los eventos del ratón con ChartSetInteger y llamamos a ChartRedraw para mostrar la herramienta. Para crear las líneas horizontales, utilizamos la siguiente función.

//+------------------------------------------------------------------+
//| 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
}

En este caso, simplemente creamos el objeto OBJ_HLINE y configuramos los parámetros necesarios, tal y como hicimos anteriormente con la función para crear botones. También necesitaremos una función para mostrar el panel como se indica a continuación.

//+------------------------------------------------------------------+
//| 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
}

Definimos la función «showPanel» para mostrar nuestro panel de control. Utilizamos la función ObjectSetInteger para establecer «OBJPROP_BACK» en «false» para «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» y «CLOSE_BTN», haciéndolos visibles.

Restablecemos el estado con la función «update_Text», estableciendo «PRICE_LABEL» en «Entry: -», «SL_LABEL» en «SL: -», «TP_LABEL» en «TP: -» y «PLACE_ORDER_BTN» en «Place Order», borramos «selected_order_type», establecemos «tool_visible» en falso, desactivamos los eventos del ratón mediante ChartSetInteger y llamamos a ChartRedraw para actualizar el gráfico.

Para eliminar la herramienta y el panel, utilizamos las siguientes funciones, llamando a la función ObjectDelete en los objetos correspondientes.

//+------------------------------------------------------------------+
//| 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
}

Para enviar los pedidos al hacer clic en el botón correspondiente, utilizamos la siguiente función.

//+------------------------------------------------------------------+
//| 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
      }
   }
}

Implementamos la función «placeOrder» para ejecutar las órdenes pendientes de nuestra herramienta y comenzamos recuperando el precio de entrada («price»), el stop-loss («sl») y el take-profit («tp») mediante la función «Get_Price_d» para «PR_HL», «SL_HL» y «TP_HL», obtenemos el «symbol» actual con la función Symbol y establecemos un «expiration» de 24 horas utilizando la función TimeCurrent.

Validamos «lot_size» (>0) y nos aseguramos de que «price», «sl» y «tp» sean valores positivos; si no cumplen estos requisitos, salimos de la función Print. En el caso de «BUY_STOP» o «BUY_LIMIT», comprobamos que «sl» sea inferior a «price» y que «tp» sea superior; y en el caso de «SELL_STOP» o «SELL_LIMIT», que «sl» sea superior y «tp» sea inferior, utilizando «Print» para registrar los errores y salir si no se cumplen las condiciones. Si «selected_order_type» no es válido, salimos del programa mostrando un mensaje de «Print».

A continuación, utilizamos los métodos de «obj_Trade» —«BuyStop», «SellStop», «BuyLimit» o «SellLimit»— para realizar la orden, registrando si se ha realizado correctamente o si ha fallado mediante «Print» y GetLastError en caso de fallo. Una vez que disponemos de estas funciones, podemos invocarlas al hacer clic en los botones correspondientes, tal y como se muestra a continuación.

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
}

Tras la compilación, obtenemos el siguiente resultado.

PANEL + HERRAMIENTA DE TABLA DE PRECIOS

En la imagen podemos ver que es posible crear dinámicamente la herramienta correspondiente para el gráfico de precios. Lo que tenemos que hacer a continuación es hacer que la herramienta sea manejable, de modo que podamos moverla por el gráfico. Esta es la lógica que aplicamos en el controlador de eventos 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

En primer lugar, definimos las variables para la función OnChartEvent con el fin de habilitar la función de arrastrar y soltar en nuestra herramienta Trade Assistant. «prevMouseState» registra los cambios en el estado del ratón, mientras que «mlbDownX1», «mlbDownY1», «mlbDownXD_R1», «mlbDownYD_R1» (y similares para «REC2» a «REC5») almacenan las coordenadas del ratón y del rectángulo para «REC1» (TP), «REC3» (entrada) y «REC5» (SL) durante los clics. Los indicadores booleanos «movingState_R1», «movingState_R3» y «movingState_R5» indican si estos rectángulos se están arrastrando. A continuación, podemos utilizar estas variables de control para definir el movimiento de la herramienta de precios.

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
}

En este caso, ampliamos la función OnChartEvent para gestionar el movimiento del ratón al arrastrar objetos del gráfico en nuestra herramienta cuando «tool_visible» es verdadero y «id» es CHARTEVENT_MOUSE_MOVE. Extraemos «MouseD_X», «MouseD_Y» y «MouseState» de «lparam», «dparam» y «sparam», y utilizamos la función ObjectGetInteger para obtener la posición y el tamaño («XD_R1», «YD_R1», «XS_R1», «YS_R1» para «REC1», y de forma similar para «REC2» a «REC5»).

Al hacer clic con el ratón (pasando de «prevMouseState» 0 a «MouseState» 1), almacenamos las coordenadas del ratón en «mlbDownX1», «mlbDownY1» y las posiciones de los rectángulos en «mlbDownXD_R1», «mlbDownYD_R1» (y para «REC2» a «REC5»), estableciendo «movingState_R1», «movingState_R3» o «movingState_R5» en verdadero si el clic se produce dentro de los límites de «REC1», «REC3» o «REC5».

Para «movingState_R1» (TP), desactivamos el desplazamiento con ChartSetInteger, validamos la posición de TP (por encima de la entrada para «BUY_STOP»/«BUY_LIMIT», por debajo para la venta), actualizamos «REC1» y «REC2»/« REC4» y los tamaños con ObjectSetInteger, convertimos las coordenadas a precio utilizando ChartXYToTimePrice, actualizamos «TP_HL» con ObjectSetDouble y actualizamos el texto con «update_Text», «Get_Price_d», «Get_Price_s», DoubleToString y MathAbs.

Del mismo modo, para «movingState_R5» (SL), ajustamos «REC5» y «REC4»/«REC2», actualizamos «SL_HL» y actualizamos el texto. En el caso de «movingState_R3» (entrada), movemos todos los rectángulos y actualizamos «PR_HL», «TP_HL», «SL_HL» y el texto.

Al soltar el botón del ratón («MouseState» 0), restablecemos los indicadores «movingState», habilitamos el desplazamiento y actualizamos «prevMouseState», llamando a ChartRedraw para reflejar los cambios. Por último, lo que debemos hacer es eliminar los objetos por completo al desinstalar el programa y actualizar el tamaño del lote en el tick para que los cambios se reflejen, aunque puede dejarlo tal cual, ya que no es muy necesario.

//+------------------------------------------------------------------+
//| 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
}

Aquí implementamos dos controladores de eventos esenciales para nuestra herramienta: OnDeinit y OnTick. En la función OnDeinit, que se activa cuando se elimina el asesor experto del gráfico, llamamos a la función «deleteObjects» para eliminar objetos del gráfico como «REC1» a «REC5», «TP_HL», «SL_HL» y «PR_HL», y la función «deletePanel» para eliminar objetos del panel de control como «PANEL_BG», «LOT_EDIT» y botones como «BUY_STOP_BTN», garantizando así una salida limpia.

En la función OnTick, que se ejecuta con cada cambio de cotización, utilizamos la función ObjectGetString para recuperar el texto del campo «LOT_EDIT», lo convertimos en un valor de tipo double mediante la función StringToDouble y actualizamos la variable «lot_size» si el valor de «new_lot» es positivo, manteniendo así el tamaño del lote de nuestra herramienta sincronizado con la entrada del usuario.

Tras la compilación, obtenemos el siguiente resultado.

RESULTADO FINAL

A partir de la visualización, podemos observar que, al hacer clic en cualquiera de los botones de operación, se genera la herramienta de precios correspondiente y, al arrastrarla, se actualiza en tiempo real; los precios se reflejan en el panel de operaciones, listos para ser utilizados con fines de negociación; cuando se hace clic en el botón «Realizar operación», las operaciones correspondientes se ejecutan de forma dinámica. Esto confirma que hemos alcanzado nuestro objetivo; lo que queda por hacer es probar el panel para asegurarnos de que funciona a la perfección, y eso se aborda en la siguiente sección.


Backtesting

Hemos realizado las pruebas y a continuación se muestra la visualización compilada en un único archivo GIF (Graphics Interchange Format).

RESULTADOS DEL BACKTEST


Conclusión

En conclusión, hemos desarrollado una herramienta interactiva de asistencia para operaciones en MQL5 que combina precisión visual con controles intuitivos, lo que agiliza el proceso de colocación de órdenes pendientes. Hemos mostrado cómo diseñar su interfaz gráfica de usuario (GUI), fácil de usar; cómo implementarla con funciones como «createControlPanel» y «placeOrder»; y cómo garantizar su fiabilidad mediante una implementación estructurada y procesos de validación. Puede personalizar esta herramienta para adaptarla a su estilo de negociación, mejorando así la eficiencia en la colocación de órdenes. No se pierda las próximas entregas, en las que presentaremos funciones avanzadas como la gestión de riesgos y los paneles arrastrables. Manténganse atentos.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17931

Archivos adjuntos |
zhanglei 张
zhanglei 张 | 22 ene 2026 en 01:23
¿Es posible utilizar el precio de mercado como precio de oferta y abrir una orden directamente al precio de mercado cuando se determina el stop loss ajustando el take profit y el stop loss?
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Simulación de mercado: Iniciando SQL en MQL5 (I) Simulación de mercado: Iniciando SQL en MQL5 (I)
En este artículo, comenzaremos a explorar el uso de SQL dentro de un código MQL5. Veremos cómo podemos crear una base de datos. O, mejor dicho, cómo podemos crear un archivo de base de datos en SQLite, utilizando, para ello, recursos o procedimientos incluidos en el lenguaje MQL5. Veremos también cómo crear una tabla y, después, cómo crear una relación entre tablas mediante una clave primaria y una clave foránea. Todo esto usando, nuevamente, MQL5. Veremos lo sencillo que es crear un código que, en el futuro, podrá portarse a otras implementaciones de SQL, usando una clase que nos ayude a ocultar la implementación creada. Y, lo más importante de todo, veremos que, en diversos momentos, podemos correr el riesgo de que algo no salga bien al usar SQL. Esto se debe a que, dentro del código MQL5, un código SQL siempre se colocará dentro de una STRING.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Simulación de mercado (Parte 24): Iniciando SQL (VII) Simulación de mercado (Parte 24): Iniciando SQL (VII)
En el artículo anterior terminamos de hacer las debidas presentaciones sobre SQL. Así, lo que me había propuesto mostrar y explicar sobre SQL, a mi juicio, quedó debidamente explicado. Esto, para que todos los que vengan a ver el sistema de repetición/simulador en construcción consigan, como mínimo, tener alguna noción de lo que puede estar ocurriendo allí. Esto se debe a que no tiene sentido programar diversas cosas que SQL puede cubrir perfectamente.