English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Crear Paneles de Control Activos en MQL5 para Trading

Crear Paneles de Control Activos en MQL5 para Trading

MetaTrader 5Asesores Expertos | 18 febrero 2014, 14:41
2 784 0
Евгений
Евгений

Introducción

La eficiencia es muy esencial en un entorno de trabajo, especialmente en trading, donde la velocidad y la precisión tienen un papel importantísimo. Al preparar el terminal de trabajo, cada uno debe hacer su puesto de trabajo tan confortable como pueda para implementar los análisis y entrar al mercado lo antes posible. Pero la realidad es que los desarrolladores no siempre pueden contentar a todos, y es imposible centrarse en los deseos de cada uno para ciertas funciones.

Por ejemplo, para un especulador, cada fracción de segundo y cada pulsación de la tecla "Nueva orden" es importante, y la configuración subsiguiente de todos los parámetros podría ser crítica.

¿Cómo encontrar, pues, una solución? La solución está en la personalización de los elementos, puesto que MetaTrader 5 facilita componentes tan maravillosos como "Button" ("Botón"), "Edit" ("Editar") y "Label" ("Etiqueta"). Vamos a ello.


2. Opciones de Panel

Primero, decidamos qué tipo de funciones son esenciales para un panel. Pondremos el mayor énfasis en trading, usando el panel y, por tanto, incluyendo las siguientes funciones:

  • Abrir posición
  • Colocar una orden pendiente
  • Modificación de una posición/orden
  • Cierre de la posición
  • Eliminación de una orden pendiente

Tampoco nos vendrá mal añadir la posibilidad de personalizar el espectro de colores del panel, tamaño de fuente y configuración para guardar cambios. Describamos todos los elementos del futuro panel más detalladamente. Especificaremos el nombre del objeto, su tipo y descripción de su propósito para cada función del panel. El nombre de cada objeto empezará con "ActP" - esto será una especie de clave que indica que el objeto pertenece a ese panel.

2.1. Posiciones Abiertas

A continuación introduciremos todos los parámetros necesarios para la apertura de la posición, y la implementaremos pulsando un botón. Las líneas auxiliares, que se activan haciendo click en un cuadro de confirmación, nos asistirán en la configuración de niveles de Stop Loss y Take Profit. La selección del tipo de ejecución se hará usando los botones de radio.

Nombre
Tipo
Descripción
 ActP_buy_button1  Botón
 Botón para una operación de Compra
 ActP_sell_button1
 Botón
 Botón para una operación de Venta
 ActP_DealLines_check1
 Flag
 Set/reset flag of the auxiliary lines
 ActP_Exe_radio1
 Botón de radio
 Grupo de botones de radio para seleccionar el tipo de operación de trading
 ActP_SL_edit1
 Campo de entrada
 Campo para introducir un Stop Loss
 ActP_TP_edit1
 Campo de entrada
 Campo para introducir un Take Profit
 ActP_Lots_edit1
 Campo de entrada
 Campo para introducir la cantidad
 ActP_dev_edit1
 Campo de entrada
 Campo para introducir una desviación tolerable durante la apertura
 ActP_mag_edit1
 Campo de entrada
 Campo para introducir un número
 ActP_comm_edit1  Campo de entrada  Campo para introducir comentarios

Tabla 1 Lista de los elementos del panel "Trade opening" ("Apertura de trading")

2.2 Colocar una Orden Pendiente

A continuación introduciremos todos los parámetros necesarios para colocar una orden pendiente, y la colocaremos presionando una tecla. Las líneas de apoyo, que se activan confirmando un flag, nos ayudaran a configurar niveles de Stop Loss, Take Profit, stop-limit y fechas de caducidad. La selección del tipo de ejecución y el tipo de fecha de caducidad se realizará con la ayuda de un grupo de botones de radio.

Nombre
Tipo
Descripción
 ActP_buy_button2  Botón
 Button for setting a Buy order
 ActP_sell_button2
 Botón
 Botón para configurar una orden de trading
 ActP_DealLines_check2
 Flag
 Flag para establecer / restablecer las líneas auxiliares
 ActP_lim_check2  Flag  Flag para establecer / restablecer una orden de stop-limit 
 ActP_Exe_radio2
 Botón de radio
 Grupo de botones de radio para seleccionar el tipo de ejecución de la orden
 ActP_exp_radio2  Botón de radio  Grupo de botones de radio para seleccionar el tipo de caducidad de la orden
 ActP_SL_edit2
 Campo de entrada
 Campo para introducir un Stop Loss
 ActP_TP_edit2
 Campo de entrada
 Campo para introducir un Take Profit
 ActP_Lots_edit2
 Campo de entrada
 Campo para introducir la cantidad
 ActP_limpr_edit2
 Campo de entrada  Campo para introducir un precio de orden stop-limit
 ActP_mag_edit2
 Campo de entrada
 Campo para introducir el número mágico
 ActP_comm_edit2  Campo de entrada  Campo para introducir comentarios
 ActP_exp_edit2  Campo de entrada  Campo para introducir la fecha de caducidad
 ActP_Pr_edit2  Campo de entrada  Campo para introducir el precio de la orden de ejecución 

 Tabla 2 Lista de los elementos del panel de "Colocación de órdenes pendientes"

2.3. Modificación / Cierre de Operaciones de Trading

A continuación introduciremos todos los parámetros necesarios para la modificación y cierre de una operación de trading. Las líneas auxiliares, que se activan haciendo click en un cuadro de confirmación, nos asistirán en la instalación de niveles de Stop Loss y Take Profit. La selección de operaciones de trading se generarán de una lista desplegable.

Nombre
Tipo
Descripción
 ActP_ord_button5   Lista desplegable   Lista de selecciones para una operación de trading
 ActP_mod_button4  Botón
 Botón de modificación de operación 
 ActP_del_button4
 Botón
 Botón de cierre de operación 
 ActP_DealLines_check4
 Flag
 Flag para establecer / restablecer las líneas auxiliares
 ActP_SL_edit4
 Campo de entrada
 Campo para introducir un Stop Loss
 ActP_TP_edit4
 Campo de entrada
 Campo para introducir un Take Profit
 ActP_Lots_edit4
 Campo de entrada
 Campo para introducir la cantidad 
 ActP_dev_edit4
 Campo de entrada
 Campo para introducir una desviación tolerable 
 ActP_mag_edit4
 Campo de entrada
 Campo para mostrar el número mágico (solo lectura)
 ActP_Pr_edit4  Campo de entrada  Campo para mostrar el precio de apertura (solo lectura)

Tabla 3. Lista de los elementos del panel de "Modificación / cierre de operaciones de trading"

2.4. Modificación / Eliminación de Órdenes

A continuación introduciremos todos los parámetros necesarios para la modificación y eliminación de órdenes pendientes. Las líneas de apoyo, que se activan haciendo click en un cuadro de confirmación, nos ayudaran a configurar niveles de Stop Loss, Take Profit, stop-limit y fechas de caducidad. La selección del tipo de fechas de caducidad se generará con la ayuda de grupos de botones de radio. La selección de órdenes se generará de una lista desplegable.

Nombre
Tipo
Descripción
 ActP_ord_button5  Lista desplegable  Lista para seleccionar la orden
 ActP_mod_button3  Botón
 Botón de modificación de orden 
 ActP_del_button3
 Botón
 Botón de eliminación de orden 
 ActP_DealLines_check3
 Flag
 Flag para establecer / restablecer las líneas auxiliares
 ActP_exp_radio3  Botón de radio  Grupo de botones de radio para seleccionar el tipo de caducidad de una orden
 ActP_SL_edit3
 Campo de entrada
 Campo para introducir un Stop Loss
 ActP_TP_edit3
 Campo de entrada
 Campo para introducir un Take Profit
 ActP_Lots_edit3
 Campo de entrada
 Campo para mostrar el volumen (solo lectura)
 ActP_limpr_edit3
 Campo de entrada  Campo para introducir el precio de una orden de stop-limit 
 ActP_mag_edit3
 Campo de entrada
 Campo para mostrar números mágicos (solo lectura)
 ActP_comm_edit3  Campo de entrada  Campo para introducir comentarios
 ActP_exp_edit3  Campo de entrada  Campo para introducir la fecha de caducidad
 ActP_Pr_edit3  Campo de entrada  Campo para introducir el precio de la ejecución de orden 
 ActP_ticket_edit3  Campo de entrada  Campo para mostrar el ticket de orden (solo lectura) 

Tabla 4. Lista de los elementos del panel de "Modificación / eliminación de órdenes"

2.5 Configuración

A continuación, elegiremos el color de los botones, etiquetas y textos de la lista desplegable, y configuraremos varios tamaños de fuente.

Nombre
Tipo
Descripción
 ActP_col1_button6  Lista desplegable
 Lista de selecciones de color para botones
 ActP_col2_button6
 Lista desplegable
 Lista de selección de colores para etiquetas
 ActP_col3_button6
 Lista desplegable
 Lista de selecciones de color para texto
 ActP_font_edit6
 Campo de entrada
 Campo para especificar el tamaño de fuente

Tabla 5. Lista de los elementos del panel "Configuración"

También se puede añadir un botón para dar la posibilidad de minimizar el panel si no se está usando. Puede que ya haya notado la presencia de instrumentos como las "líneas de apoyo". ¿Qué son, y para qué las necesitamos? Usando estas líneas podremos configurar un Stop Loss, Take Profit, el precio de activar una orden pendiente, el precio de una orden de stop-limit (líneas horizontales), así como la fecha de caducidad de una orden pospuesta (línea vertical), simplemente usando el ratón para arrastrar estas líneas a la fecha/precio deseado.

Después de todo, una instalación visual es más conveniente que una textual (introducir precios / fechas manualmente en los campos correspondientes). Asimismo, estas líneas servirán como "puntos destacados" de un parámetro de la orden seleccionada. Puesto que puede haber muchas órdenes, las líneas compartidas del terminal estándar, que normalmente muestran precios, pueden resultar muy confusas. 


3. Instrucciones Generales para la Creación de Interfaz

Hemos conseguido nuestro objetivo: crear una forma de asistente gráfico dentro de la operación de trading. Para este propósito necesitamos la interfaz más sencilla de usar. Primero, debe quedar claro que todos los elementos de control (y habrá varios) deben crearse usando software, y por tanto la posición y tamaño de objetos se debe calcular previamente.

Ahora, imagine que tras un largo y pesado proceso para conseguir las coordenadas de los objetos, asegurándonos de que no coinciden y que están claramente visibles, necesitamos añadir un nuevo objeto, ¡y debemos reconstruir nuestro esquema entero de nuevo!

Los que ya están familiarizados con los elementos de Desarrollo Rápido de Aplicaciones ("Rapid Application Development", como Delphi, C + + Builder, etc.) saben lo rápido que se puede crear la interfaz de usuario más complicada.

Tratemos de implementarla usando MQL5. Primero, usando un ratón, localizaremos los objetos de control de la forma más adecuada, y ajustaremos sus tamaños. A continuación, escribiremos un script sencillo que leerá las propiedades de todos los objetos en el gráfico y los grabará en un archivo. Cuando sean necesarias, podremos recuperar esas propiedades y reconstruir completamente los objetos en cualquier gráfico.

El código del script podría ser así:

//+------------------------------------------------------------------+
//|                                  Component properties writer.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

input int interfaceID=1; //input parameter - the identifier of the stored interface
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //Open file for writing  
   int handle=FileOpen("Active_Panel_scheme_"+IntegerToString(interfaceID)+".bin", FILE_WRITE|FILE_BIN);
   if(handle!=INVALID_HANDLE)
     {
      //We will go all the objects on the chart
      for(int i=0;i<ObjectsTotal(0);i++)
        {
         string name=ObjectName(0,i);
         //And write their properties in the file
         FileWriteString(handle,name,100);
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_TYPE));

         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_COLOR));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STYLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_WIDTH));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BACK));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTED));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTABLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_READONLY));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_FONTSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STATE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BGCOLOR));

         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_TEXT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_FONT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,0),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,1),100);

         FileWriteDouble(handle,ObjectGetDouble(0,name,OBJPROP_PRICE));
        }
      //Close file
      FileClose(handle);
      Alert("Done!");
     }
  }
//+------------------------------------------------------------------+

Como puede ver, el código es muy sencillo. Transforma algunas propiedades de todos los objetos del gráfico al código binario. Lo más importante es no olvidar el orden de secuencia de las propiedades grabadas al leer el archivo. 

El script está listo, de modo que pasemos a la creación de la interfaz.

Y lo primero que debemos hacer es organizar el menú principal por el tipo de sus pestañas. ¿Por qué necesitamos pestañas? Porque hay muchos objetos, y hacer que quepan todos ellos en la pantalla resultaría problemático. Y puesto que los objetos están agrupados por categorías (vea la tabla de arriba), es más fácil colocar cada grupo en una pestaña separada.   

Por tanto, usando el menú del terminal Insert -> Objects -> Button (Insertar -> Objetos -> Botón) crearemos cinco botones en la parte de arriba del gráfico, que servirán como nuestro menú principal.

Figura 1. Pestañas del panel

Fig. 1 Pestañas del panel

No olvidemos que los objetos se pueden duplicar fácilmente seleccionando uno y después arrastrándolo con el ratón con la tecla "Ctrl" pulsada. Con esto, crearemos una copia del objeto, en lugar de recolocar el original.

Debemos prestar especial atención a los nombres de los objetos, sin olvidar que todos ellos deben empezar con "ActP". Además, añadiremos "main" ("principal") al nombre de la cadena de caracteres, lo que indicará que el objeto pertenece a la barra del menú principal.

Figura 2. Lista de objetos (pestañas del panel)

Figura 2. Lista de objetos (pestañas del panel)

De forma similar aplicaremos los contenidos de las pestañas al nuevo gráfico. ¡Los contenidos de cada pestaña se deben colocar en un gráfico separado!

Pestaña "Market" ("Mercado"):

Figura 3. Elementos de la pestaña "Market"

Figura 3. Elementos de la pestaña "Market"

Pestaña "Pending" ("Pendiente"):

Figura 4. Elementos de la pestaña "Pending"

Figura 4. Elementos de la pestaña "Pending"

Pestaña "Settings" ("Configuración"):

Figura 5. Elementos de la pestaña "Settings"

Figura 5. Elementos de la pestaña "Settings"

La última pestaña "Modify / close" ("Modificar / cerrar") es diferente: servirá para modificar o eliminar órdenes pendientes, así como modificar y cerrar transacciones de trading. Sería razonable dividir el trabajo con operaciones de trading y el trabajo con órdenes en dos sub-pestañas separadas. Primero, creemos un botón que activará la lista desplegable, de la que elegiremos una orden o una operación de trading para trabajar con ella.

Figura 6. Elementos de la pestaña "Modify/Close"

Figura 6. Elementos de la pestaña "Modify/Close"

A continuación, crearemos sub-pestañas. Para trabajar con operaciones de trading:

Figura 7. Elementos para trabajar con posiciones

Figura 7. Elementos para trabajar con posiciones

Y para trabajar con órdenes:

Figura 8. Sub-pestaña para trabajar con órdenes

Figura 8. Sub-pestaña para trabajar con órdenes

Ya está, la interfaz ha sido creada.

Aplicamos el script a cada uno de los gráficos para guardar cada pestaña en un archivo separado. El parámetro de entrada "interfaceID" debe ser diferente para cada pestaña:

  • 0 - Página principal
  • 1 - Market
  • 2 - Pending
  • 3 - Botón para activar la lista de selección operación de trading / orden
  • 4 - Settings
  • 6 - Sub-pestaña para trabajar con operaciones de trading
  • 7 - Sub-pestaña para trabajar con órdenes

La pestaña número 5 se corresponde con el botón para "minimizar la ventana" en el menú principal, de modo que no hay objetos en ella, y nos la podemos saltar.

Tras todas estas manipulaciones, los siguientes archivos aparecerán en la carpeta de directorio del terminal -> MQL5 ->:

Figura 8. Lista de archivos de esquemas de paneles

Figura 9. Lista de archivos de esquemas de paneles


4. Descargar Elementos de Interfaz

Ahora, los elementos de interfaz se almacenan en archivos y están listos para empezar a funcionar. Para empezar, determinemos el lugar en el que se localizará nuestro panel. Si lo colocamos directamente en el gráfico principal, tapará el gráfico de precios. Esto sería muy inconveniente. Por tanto, lo más razonable será colocar el panel en la sub-ventana del gráfico principal. Un indicador puede crear este panel.

Creémoslo:

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window //place the indicator in a separate window

int OnInit()
  {
//--- indicator buffers mapping
   //Set the short name of the indicator
   IndicatorSetString(INDICATOR_SHORTNAME, "AP");
//---
   return(0);
  }

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
//--- return value of prev_calculated for next call
   return(rates_total);
  }

El código es muy sencillo, porque la función principal de este indicador es la creación de sub-ventanas, más que la realización de cálculos diversos. Lo único que haremos será instalar un nombre de indicador "corto", con el que encontraremos su sub-ventana. Compilaremos y aplicaremos un gráfico al indicador, y aparecerá una ventana.

Ahora centrémonos en el panel del Asesor Experto. Crearemos un nuevo Asesor Experto.

La función OnInit () contendrá los siguientes operadores:

double Bid,Ask;         //variables for current prices
datetime time_current;  //time of last tick
int wnd=-1;             //index of the window with an indicator
bool last_loaded=false; //flag indicating whether it's a first initialization or not
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   //Start the timer at intervals of 1 second
   EventSetTimer(1); 
   //Get the latest prices
   get_prices();
   //Define the window with an indicator
   wnd=ChartWindowFind(0,"AP");
   //If the first initialization - create interface
   if(!last_loaded) create_interface();
//---
   return(0);
  }

Aquí lanzaremos un temporizador (la razón de ello la explicaremos a continuación) y obtendremos los últimos precios del mercado usando ChartWindowFind. Localizaremos la ventana del indicador y la guardaremos como variable. Flag last_loaded - indica si es la primera vez que se inicializó el Asesor Experto o no. Esta información se necesitará para evitar cargar de nuevo la interfaz durante una reinicialización. 

La función create_interface () tiene el siguiente aspecto:

//+------------------------------------------------------------------+
//| Function of the interface creation                               |
//+------------------------------------------------------------------+
void create_interface()
  {
   //if reset settings is selected
   if(Reset_Expert_Settings)
     {
     //Reset
      GlobalVariableDel("ActP_buttons_color");
      GlobalVariableDel("ActP_label_color");
      GlobalVariableDel("ActP_text_color");
      GlobalVariableDel("ActP_font_size");
     }

   //Create the main menu interface
   ApplyScheme(0);
   //Create the interface tab "Market"
   ApplyScheme(1);
   //Set all objects as unmarked
   Objects_Selectable("ActP",false);
   //redraw the chart
   ChartRedraw();
  }

El primer paso es comprobar el parámetro de entrada "reset settings" ("restablecer configuración"), y si está instalado, eliminar las variables globales responsables de la configuración. A continuación explicaremos cómo afecta esta acción al panel. Además, la función ApplyScheme () creará una interfaz de un archivo. 

//+------------------------------------------------------------------+
//| The function for the interface loading                           |
//| ID - ID of the saved interface                                   |
//+------------------------------------------------------------------+
bool ApplyScheme(int ID)
  {
   string fname="Active_Panel_scheme_custom_"+IntegerToString(ID)+".bin";
   //download the standard scheme if there isn't saved scheme 
   if(!FileIsExist(fname)) fname="Active_Panel_scheme_"+IntegerToString(ID)+".bin";
   //open file for reading
   int handle=FileOpen(fname,FILE_READ|FILE_BIN);
   //file opened
   if(handle!=INVALID_HANDLE)
     {
      //Loading all objects
      while(!FileIsEnding(handle))
        {
         string obj_name=FileReadString(handle,100);
         int _wnd=wnd;
         //the auxiliary lines are in the main window
         if(StringFind(obj_name,"line")>=0) _wnd=0;
         ENUM_OBJECT obj_type=FileReadInteger(handle);
         //creating object
         ObjectCreate(0, obj_name, obj_type, _wnd, 0, 0);   
         //and apply the properties 
         ObjectSetInteger(0,obj_name,OBJPROP_XDISTANCE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_YDISTANCE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_XSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_YSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_COLOR,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_STYLE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_WIDTH,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_BACK,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_SELECTED,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_SELECTABLE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_READONLY,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_FONTSIZE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_STATE,FileReadInteger(handle));
         ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,FileReadInteger(handle));

         ObjectSetString(0,obj_name,OBJPROP_TEXT,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_FONT,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_BMPFILE,0,FileReadString(handle,100));
         ObjectSetString(0,obj_name,OBJPROP_BMPFILE,1,FileReadString(handle,100));

         ObjectSetDouble(0,obj_name,OBJPROP_PRICE,FileReadDouble(handle));

         //Set color for the objects
         if(GlobalVariableCheck("ActP_buttons_color") && obj_type==OBJ_BUTTON)
            ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,GlobalVariableGet("ActP_buttons_color"));
         if(GlobalVariableCheck("ActP_label_color") && obj_type==OBJ_LABEL)
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_label_color"));
         if(GlobalVariableCheck("ActP_text_color") && (obj_type==OBJ_EDIT || obj_type==OBJ_BUTTON))
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_text_color"));
         if(GlobalVariableCheck("ActP_font_size") && (obj_type==OBJ_EDIT || obj_type==OBJ_LABEL))
            ObjectSetInteger(0,obj_name,OBJPROP_FONTSIZE,GlobalVariableGet("ActP_font_size"));
         //Set global variable font size
         if(obj_name=="ActP_font_edit6" && GlobalVariableCheck("ActP_font_size"))
            ObjectSetString(0,obj_name,OBJPROP_TEXT,IntegerToString(GlobalVariableGet("ActP_font_size")));
        }
      //Close file
      FileClose(handle);
      return(true);
     }
   return(false);
  }

De nuevo, esto no es nada complicado. La función abrirá el archivo deseado con una interfaz previamente guardada y lo creará en la ventana que identificamos antes (en el indicador de abajo). Asimismo, seleccionaremos los colores de los objetos y los tamaños de fuente de las variables globales del terminal. 

La función Objects_Selectable () deselecciona todos los objetos excepto las líneas auxiliares. Con ello activaremos las animaciones de los botones y evitaremos eliminar accidentalmente un objeto necesario.

//+------------------------------------------------------------------+
//| Function of setting objects as unselectable                      |
//+------------------------------------------------------------------+
void  Objects_Selectable(string IDstr,bool flag)
  {
   //Check all the objects
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //If the object belongs to the panel
      if(StringFind(n,IDstr)>=0)
        {
         //Lines remain untouched
         if(!flag)
            if(StringFind(n,"line")>-1) continue; 
         //Set everything unselectable except the lines
         ObjectSetInteger(0,n,OBJPROP_SELECTABLE,flag); 
        }
     }
  }

Ahora veamos la función OnTick(). Nos servirá para obtener los últimos precios en el mercado.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //Get the latest prices
   get_prices();
  }

La función get_prices() tiene esta forma:

//+------------------------------------------------------------------+
//| Function obtain information on tick                              |
//+------------------------------------------------------------------+
void get_prices()
  {
   MqlTick tick;
   //if the tick was
   if(SymbolInfoTick(Symbol(),tick))
     {
      //obtain information
      Bid=tick.bid;
      Ask=tick.ask;
      time_current=tick.time;
     }
  }

Y no se olvide de OnDeinit ():

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   //if the deinitialisation reason isn't the timeframe or symbol change
   if(reason!=REASON_CHARTCHANGE)
     {
      //reset initialization flag
      last_loaded=false;  
      //Delete all panel objects
      ObjectsDeleteAll_my("ActP");
      //Delete files with the saved state of the tabs
      FileDelete("Active_Panel_scheme_custom_1.bin");
      FileDelete("Active_Panel_scheme_custom_2.bin");
      FileDelete("Active_Panel_scheme_custom_3.bin");
      FileDelete("Active_Panel_scheme_custom_4.bin");
      FileDelete("Active_Panel_scheme_custom_5.bin");
     }
   //otherwise set a flag
   else last_loaded=true;
   //stop the timer
   EventKillTimer();
  }

Primero, comprobaremos la razón de la desinicialización: si es a causa de un cambio de intervalo y / o símbolos, no eliminaremos el elemento del panel. En el resto de los casos, elimine todos los elementos usando la función ObjectsDeleteAll_my ().

//+------------------------------------------------------------------+
//| The function deletes all panel objects                           |
//| IDstr - object identifier                                        |
//+------------------------------------------------------------------+
void  ObjectsDeleteAll_my(string IDstr)
  {
   //check all the objects
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //if the name contains the identifier - remove the object
      if(StringFind(n,IDstr)>=0) ObjectDelete(0,n);
     }
  }

Tras compilar y ejecutar el Asesor Experto, obtendremos el siguiente resultado:

Figura 10. Ejemplo de un Expert Advisor funcional

Figura 10. Ejemplo de un Asesor Experto funcional

No obstante, nada de esto nos resultará demasiado útil a no ser que hagamos que todos estos objetos respondan a nuestra manipulación.


5. Control de Eventos

La interfaz ha sido creada, y ahora tenemos que lograr que funcione. Todas nuestras acciones con objetos generan eventos específicos. La función OnChartEvent OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) es el mecanismo controlador de eventos ChartEvent . De todos los eventos, nos interesan los siguientes: 

  • CHARTEVENT_CLICK - click en el gráfico
  • CHARTEVENT_OBJECT_ENDEDIT - acabar de editar el campo de entrada 
  • CHARTEVENT_OBJECT_CLICK - click en el objeto gráfico

En nuestro caso, el parámetro de la función "id" indica la identificación del evento; "sparam" indica el nombre del objeto que genera este evento; y todos los demás parámetros no nos interesan.

El primer evento que exploraremos es el click en el botón del menú principal.

5.1. Controlar Eventos del Menú Principal

Recuerde que el menú principal consta de cinco botones. Cuando se hace click en uno de ellos, debería pasar al estado de "pulsado", dirigirnos a la interfaz correcta y cargar las pestañas correspondientes. A continuación, el resto de los botones del menú deberían pasar a estado de "no pulsado".

//+------------------------------------------------------------------+
//| Event handlers                                                   |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on a graphic object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //main menu button click
      if(sparam=="ActP_main_1") {Main_controls_click(1); ChartRedraw(); return;}
       //Here we execute the corresponding operators
      if(sparam=="ActP_main_2") {Main_controls_click(2); ChartRedraw(); return;}
      if(sparam=="ActP_main_3") {Main_controls_click(3); ChartRedraw(); return;}
      if(sparam=="ActP_main_4") {Main_controls_click(4); ChartRedraw(); return;}
      if(sparam=="ActP_main_5") {Main_controls_click(5); ChartRedraw(); return;}
   ...   
   }
...
}

Si se hizo click en el botón de menú, hemos realizado la función Main_controls_click(). Dibujemos el gráfico de nuevo usando ChartRedraw(), y completemos la función. Debemos completar la ejecución porque solo se puede pulsar un objeto cada vez, y por tanto, todas las demás implementaciones implicarán un desperdicio de tiempo de CPU.

//+------------------------------------------------------------------+
//| Tab processor                                                    |
//| ID - index of clicked tab                                        |
//+------------------------------------------------------------------+
void Main_controls_click(int ID)
  {
   int loaded=ID;
   //we will go all tabs
   for(int i=1;i<6;i++)
     {
      //for all except the selected set inactive
      if(i!=ID) 
        {
         //also remember the last active tab
         if(ObjectGetInteger(0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE)==1) loaded=i;
         ObjectSetInteger(0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE,0);
        }
     }
//if(loaded==ID) return;
   //set an active state for the selected
   ObjectSetInteger(0,"ActP_main_"+IntegerToString(ID),OBJPROP_STATE,1);
   //delete the drop-down lists
   DeleteLists("ActP_orders_list_"); 
   DeleteLists("ActP_color_list_");
   //and set the list buttons to the unpressed state
   ObjectSetInteger(0,"ActP_ord_button5",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col1_button6",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col2_button6",OBJPROP_STATE,0);
   ObjectSetInteger(0,"ActP_col3_button6",OBJPROP_STATE,0);
   //save state of the last active tab
   SaveScheme(loaded);
   //remove old tab
   DeleteScheme("ActP");
   //and load a new
   ApplyScheme(ID);
   //Set all objects as unselected
   Objects_Selectable("ActP",false);
  }

Ya conocemos las funciones Objects_Selectable() y ApplyScheme(), y más tarde veremos la función DeleteLists().

La función SaveScheme() guarda un archivo de interfaz para que los objetos mantengan todas sus propiedades durante una recarga:

//+------------------------------------------------------------------+
//| Interface saving function                                        |
//+------------------------------------------------------------------+
void SaveScheme(int interfaceID)
  {
   //open file for writing
   int handle=FileOpen("Active_Panel_scheme_custom_"+IntegerToString(interfaceID)+".bin",FILE_WRITE|FILE_BIN);
   //if file opened
   if(handle!=INVALID_HANDLE)
     {
      //go all the chart objects
      for(int i=0;i<ObjectsTotal(0);i++)
        {
         string name=ObjectName(0,i);
         //if the object belongs to the panel
         if(StringFind(name,"ActP")<0) continue;
         //and it isn't a tab
         if(StringFind(name,"main")>=0) continue; 
         //write the object properties to a file
         FileWriteString(handle,name,100);
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_TYPE));

         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YDISTANCE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_XSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_YSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_COLOR));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STYLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_WIDTH));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BACK));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTED));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_SELECTABLE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_READONLY));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_FONTSIZE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_STATE));
         FileWriteInteger(handle,ObjectGetInteger(0,name,OBJPROP_BGCOLOR));

         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_TEXT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_FONT),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,0),100);
         FileWriteString(handle,ObjectGetString(0,name,OBJPROP_BMPFILE,1),100);

         FileWriteDouble(handle,ObjectGetDouble(0,name,OBJPROP_PRICE));
        }
      //Close file
      FileClose(handle);
     }
  }

La función DeleteScheme() elimina los objetos de la pestaña.

//+------------------------------------------------------------------+
//| Function to delete all the panel objects, except tabs            |
//+------------------------------------------------------------------+
void  DeleteScheme(string IDstr)
  {
   //we will go through all the objects
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //remove everything but the tab
      if(StringFind(n,IDstr)>=0 && StringFind(n,"main")<0) ObjectDelete(0,n);
     }
  }

Por tanto, ejecutando la función Main_controls_click() eliminaremos la pestaña antigua, guardándola de antemano, y cargando una nueva.

Veremos los resultados compilando el Asesor Experto.

Ahora haremos click en el botón del menú principal y cargaremos las nuevas pestañas, manteniéndolas en el estado de las pestañas originales.

Figura 11. Elementos de la pestaña "Pending"

Figura 11. Elementos de la pestaña "Pending"

Figura 12. Elementos de la pestaña "Modify/Close"

Figura 12. Elementos de la pestaña "Modify/Close"


Figura 12. Elementos de la pestaña "Settings"

Figura 13. Elementos de la pestaña "Settings"

Con esto, podemos terminar la manipulación del menú principal, puesto que ahora funciona correctamente.

5.2. Control del Evento Componente "Flag"

La configuración de líneas auxiliares y órdenes de stop-limit se hace usado los componentes "flag", pero no en la lista de objetos gráficos de MT5. De modo que creémoslos. Hay un objeto "graphic label" ("etiqueta gráfica) que en realidad es una imagen que tiene un estado de "on" (encendido) y otro de "off (apagado)." Este estado se puede cambiar haciendo click en el objeto. Se puede configurar una imagen separada para cada estado. Elija una imagen para cada uno de los estados:

  •  Activado
  •  Desactivado

Configuremos las imágenes en las propiedades del objeto:

Figura 13. Configurar las propiedades del elemento "flag"

Figura 13. Configurar las propiedades del elemento "flag"

Debemos recordar que para que las imágenes estén disponibles en la lista deben estar localizadas en la carpeta "Terminal folder-> MQL5-> Images" y tener la extensión ".Bmp".

Pasemos al procesamiento de eventos, que se da cuando hace click en un objeto. Usaremos el ejemplo de la flag responsable de colocar líneas auxiliares en la apertura de una operación de trading.

      //click on the flag of the setting of auxiliary lines during transaction opening
      if(sparam=="ActP_DealLines_check1")
      {
         //Check the flag state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         //If the flag is set
         if(selected)
         {
            //Retrieve the value of the stop loss and take profit from the input fields
            string SL_txt=ObjectGetString(0, "ActP_SL_edit1", OBJPROP_TEXT);
            string TP_txt=ObjectGetString(0, "ActP_TP_edit1", OBJPROP_TEXT);
            double val_SL, val_TP;
            
            //If the Stop field is not empty
            //save the value
            if(SL_txt!="")
               val_SL=StringToDouble(SL_txt);

            //if empty
            else
            {
               //Take the max. and min. prices of chart
               double pr_max=ChartGetDouble(0, CHART_PRICE_MAX);
               double pr_min=ChartGetDouble(0, CHART_PRICE_MIN);
               //Set the stop at the 1/3 of the chart price range
               val_SL=pr_min+(pr_max-pr_min)*0.33;
            }   
            
            //Similarly processes the Take
            if(TP_txt!="")
               val_TP=StringToDouble(TP_txt);
            else
            {
               double pr_max=ChartGetDouble(0, CHART_PRICE_MAX);
               double pr_min=ChartGetDouble(0, CHART_PRICE_MIN);
               val_TP=pr_max-(pr_max-pr_min)*0.33;
            }      
            //Move the line to new positions
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, val_SL);  
            ObjectSetDouble(0, "ActP_TP_line1", OBJPROP_PRICE, val_TP);  
         }
          //If the flag is unset
         else
         {
             //remove the lines
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, 0);
            ObjectSetDouble(0, "ActP_TP_line1", OBJPROP_PRICE, 0);
         }
          //redraw the chart
         ChartRedraw();
          //and finish the function 
         return;
      }

El mismo método se usa para las flags responsables para el procesamiento e instalación de líneas auxiliares en la pestaña de cierre / modificación de órdenes pendientes. Por tanto, no entraremos en detalles sobre ellas en este artículo. Los que quieran familiarizarse con ellas pueden usar el código del Asesor Experto.

La configuración de órdenes de stop-limit de la pestaña "Pending" tiene el siguiente controlador:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...

   //Event - click on a graphic object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //Click on the orders stoplimit check box
      if(sparam=="ActP_limit_check2")
      {
         //Check the flag state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected) //if flag is set
         {
            //set the new color for the price edit
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, White);
            //enable it for the edit
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_READONLY, false);
            //установим в поле значение текущей цены
            //Set the current price as the field value
            ObjectSetString(0, "ActP_limpr_edit2", OBJPROP_TEXT, DoubleToString(Bid, _Digits));
            //if the auxiliary lines are allowed
            //move them
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetDouble(0, "ActP_lim_line2", OBJPROP_PRICE, Bid);
         }  
          //if flag is unset
         else
         {
            //set the field unavailable for editing
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_BGCOLOR, LavenderBlush);
            //set the field color
            ObjectSetInteger(0, "ActP_limpr_edit2", OBJPROP_READONLY, true);
            //and "empty" text
            ObjectSetString(0, "ActP_limpr_edit2", OBJPROP_TEXT, "");
            //if the auxiliary lines are allowed
            //move them to the zero point
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetDouble(0, "ActP_lim_line2", OBJPROP_PRICE, 0);
         }
      }       
   ...   
   }
...
}

Ya hemos terminado de trabajar con flags. Tengamos en cuenta el siguiente objeto de producción propia: "radio buttons group" ("grupo de botones de radio").

5.3. Controlar el Evento Componente "Radio buttons Group"

Usando este componente, seleccionaremos el tipo de operación de trading y el tipo de fecha de caducidad. Igual que en el caso de las flags, usaremos etiquetas gráficas, pero esta vez con nuevas imágenes. 

  • Activado
  • Desactivado

Pero aquí el problema se complica a causa de la necesidad de restablecer todos los botones de radio a un estado de inactividad, excepto aquellos en los que hace click. Considere el ejemplo del botón de radio de tipo de ejecución de orden:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //event - click on a graphic object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //click on radion button 1 - order execution type
      if(sparam=="ActP_Exe1_radio2")
      {
         //check the radio button state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         //set the appropriate state
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         //if it selected
         if(selected)
         {
            //reset the other radio buttons
            ObjectSetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE, false);
            //redraw the chart
            ChartRedraw();
            //finish the execution of function
            return;
         }
         //redraw the chart
         ChartRedraw();
         //finish the execution of function
         return;
      }
 
       //Similarly for the radio button 2
      if(sparam=="ActP_Exe2_radio2") 
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         if(selected)
         {
            ObjectSetInteger(0, "ActP_Exe1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE, false);
            ChartRedraw();
            return;
         }
         ChartRedraw();
         return;
      }   

       //Similarly for the radio button 3
      if(sparam=="ActP_Exe3_radio2") 
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         if(selected)
         {
            ObjectSetInteger(0, "ActP_Exe1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE, false);
            ChartRedraw();
            return;
         }
         ChartRedraw();
         return;
      }     
   ...   
   }
...
}

Los botones de radio de tipo de caducidad de orden se diferencian solo en el hecho de que al hacer click en el tercero, debe llevar a cabo un paso adicional: configurar una nueva fecha en la entrada de tiempo de la caducidad de una orden.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on a graphic object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //Click on the 3rd radio button - order expiration date
      if(sparam=="ActP_exp3_radio2")
      {
         //checking it state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         ObjectSetInteger(0,sparam,OBJPROP_STATE, 1);
         //if it selected
         if(selected)
         {
            //reset the remained radio buttons
            ObjectSetInteger(0, "ActP_exp1_radio2", OBJPROP_STATE, false);
            ObjectSetInteger(0, "ActP_exp2_radio2", OBJPROP_STATE, false);
            //set the new date to the date edit field
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_BGCOLOR, White);
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_READONLY, false);
            ObjectSetString(0, "ActP_exp_edit2", OBJPROP_TEXT, TimeToString(time_current));
            //if auxiliary lines are allowed 
            //set the new time line
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetInteger(0, "ActP_exp_line2", OBJPROP_TIME, time_current);
            ChartRedraw();
            return;
         }

          //if it isn't selected
         else
         {
            //set the edit field as not available for editing
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_BGCOLOR, LavenderBlush);
            ObjectSetInteger(0, "ActP_exp_edit2", OBJPROP_READONLY, true);
            //remove the auxiliary line
            if(ObjectGetInteger(0, "ActP_DealLines_check2", OBJPROP_STATE)==1)
               ObjectSetInteger(0, "ActP_exp_line2", OBJPROP_TIME, 0);
         }      
         ChartRedraw();
         return;
   ...   
   }
...
}

Ahora ya hemos terminado de trabajar con los botones de radio.

5.4. Crear y controlar eventos de listas desplegables

Usaremos la lista desplegable para elegir órdenes / operaciones de trading para su modificación / cierre / eliminación y paneles de selección. Empecemos con la lista de operaciones de trading / órdenes.

Lo primero que aparece en la pestaña "Modification / closure" ("Modificación / cierre") es un botón con el mensaje "Select an order -->" ("Seleccione una orden -->"). Este será el botón que active la lista. Al hacer click en él debería aparecer la lista desplegable, y tras hacer nuestra selección, debería replegarse de nuevo. Echemos un vistazo al controlador CHARTEVENT_OBJECT_CLICK de este botón:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //event - click on a graphic object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //click on the drop-down list activate button (order select)

      if(sparam=="ActP_ord_button5")
      {
         //check status
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         //the list is activated
         if(selected)// the list is selected
         {
            //delete interface
            DeleteScheme("ActP", true);
            //arrays for serving the information about the orders
            string info[100];
            //array for the tickets
            int tickets[100];
            //initialize it
            ArrayInitialize(tickets, -1);
            //get orders info
            get_ord_info(info, tickets);
            //create the list
            create_list(info, tickets);
         }
          //the list isn't active
         else
         {
            //delete it
            DeleteLists("ActP_orders_list_");
         }
          //redraw the chart
         ChartRedraw();
          //finish the function
         return;
      }      
   ...   
   }
...
}

Nuestro objetivo principal es determinar si las operaciones de trading / órdenes están en el mercado, y de ser así, extraer información de ellas y mostrarla en la lista. La función get_ord_info() tiene este papel:

//+------------------------------------------------------------------+
//| The function for obtaining the information about orders          |
//+------------------------------------------------------------------+
void get_ord_info(string &info[],int &tickets[])
  {
   //initialize the counter
   int cnt=0;
   string inf;
   //if there is an open position
   if(PositionSelect(Symbol()))
     {
     //combine all order infomation in a single line
      double vol=PositionGetDouble(POSITION_VOLUME);
      int typ=PositionGetInteger(POSITION_TYPE);
      if(typ==POSITION_TYPE_BUY) inf+="BUY ";
      if(typ==POSITION_TYPE_SELL) inf+="SELL ";
      inf+=DoubleToString(vol, MathCeil(MathAbs(MathLog(vol)/MathLog(10))))+" lots";
      inf+=" at "+DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), Digits());
      //write the results
      info[cnt]=inf;
      tickets[cnt]=0;
      //increment the counter
      cnt++;
     }

   //all orders
   for(int i=0;i<OrdersTotal();i++)
     {
      //get ticket
      int ticket=OrderGetTicket(i);
      //if order symbol is equal to chart symbol
      if(OrderGetString(ORDER_SYMBOL)==Symbol())
        {
         //combine all order infomation in a single line
         inf="#"+IntegerToString(ticket)+" ";
         int typ=OrderGetInteger(ORDER_TYPE);
         double vol=OrderGetDouble(ORDER_VOLUME_CURRENT);
         if(typ==ORDER_TYPE_BUY_LIMIT) inf+="BUY LIMIT ";
         if(typ==ORDER_TYPE_SELL_LIMIT) inf+="SELL LIMIT ";
         if(typ==ORDER_TYPE_BUY_STOP) inf+="BUY STOP ";
         if(typ==ORDER_TYPE_SELL_STOP) inf+="SELL STOP ";
         if(typ==ORDER_TYPE_BUY_STOP_LIMIT) inf+="BUY STOP LIMIT ";
         if(typ==ORDER_TYPE_SELL_STOP_LIMIT) inf+="SELL STOP LIMIT ";
         inf+=DoubleToString(vol, MathCeil(MathAbs(MathLog(vol)/MathLog(10))))+" lots";
         inf+=" at "+DoubleToString(OrderGetDouble(ORDER_PRICE_OPEN), Digits());
         //write the results
         info[cnt]=inf;
         tickets[cnt]=ticket;
         //increment the counter
         cnt++;
        }
     }
  }

Combinará un bloque de información y tickets de órdenes y operaciones de trading.

Además, la función create_list() creará una lista basada en esta información.

//+------------------------------------------------------------------+
//| The function creates list of positions                           |
//| info - array for the positions                                   |
//| tickets - array for the tickets                                  |
//+------------------------------------------------------------------+
void create_list(string &info[],int &tickets[])
  {
   //get the coordinates of the list activation button
   int x=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_XDISTANCE);
   int y=ObjectGetInteger(0, "ActP_ord_button5", OBJPROP_YDISTANCE)+ObjectGetInteger(0, "ActP_ord_button5", OBJPROP_YSIZE);
   //get colors
   color col=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_COLOR);
   color bgcol=ObjectGetInteger(0,"ActP_ord_button5",OBJPROP_BGCOLOR);
   //get window height
   int wnd_height=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,wnd);
   int y_cnt=0;
   //proceed arrays
   for(int i=0;i<100;i++)
     {
      //break if end reached
      if(tickets[i]==-1) break;
      //calculate the list item coordinates
      int y_pos=y+y_cnt*20;
      //if the windiow limits are reachedl, start a new column
      if(y_pos+20>wnd_height) {x+=300; y_cnt=0;}
      y_pos=y+y_cnt*20;
      y_cnt++;
      string name="ActP_orders_list_"+IntegerToString(i)+" $"+IntegerToString(tickets[i]);
      //create element
      create_button(name,info[i],x,y_pos,300,20);
      //and set its properties
      ObjectSetInteger(0,name,OBJPROP_COLOR,col);
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0);
      ObjectSetInteger(0,name,OBJPROP_STATE,0);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8);
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgcol);
     }
  }

Y finalmente, la función DeleteLists () elimina los elementos de la lista:

//+------------------------------------------------------------------+
//| The function for the list deletion                               |
//+------------------------------------------------------------------+
void  DeleteLists(string IDstr)
  {
   //proceed all objects
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //delete lists
      if(StringFind(n,IDstr)>=0 && StringFind(n,"main")<0) ObjectDelete(0,n);
     }
  }

De modo que ahora, al hacer click en el botón de activación, se crea una lista. Debemos conseguir que funcione, puesto que con cada click en cualquier elemento de la lista debe ocurrir una acción específica. En concreto, se debe cargar una interfaz para trabajar con una orden, y esta se debe llenar con información sobre la orden / operación de trading. 

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   // Event - click on a graphic object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //Click not on an item of order selection list
      if(StringFind(sparam, "ActP_orders_list_")<0)
      {
          //Remove it
         DeleteLists("ActP_orders_list_");
          //Set the activation button to "unpressed"
         ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
          //redraw chart
         ChartRedraw();
      }     
       //Click on the order selection list item
      else
      {
          //Set a new name for the activation button
         ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, ObjectGetString(0, sparam, OBJPROP_TEXT));
          //Set the activation button to "unpressed"
         ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
          //get ticket from the list item description
         int ticket=StringToInteger(StringSubstr(sparam, StringFind(sparam, "$")+1));
          //Load the interface
         SetScheme(ticket);
          //and delete the list
         DeleteLists("ActP_orders_list_");
          //chart redraw
         ChartRedraw();
      }
   ...   
   }
...
}

Aquí es donde se complican las cosas. Puesto que no sabemos de antemano el tamaño de la lista y los nombres de sus objetos, tendremos que extraer información de ella tomando el nombre del elemento de la lista. La función SetScheme() configurará la interfaz adecuada para trabajar con una operación de trading o con una orden pendiente:

//+------------------------------------------------------------------+
//| The function sets the interface depending on type:               |
//| position or pending order                                        |
//| t - ticket                                                       |
//+------------------------------------------------------------------+
void SetScheme(int t)
  {
   //if position
   if(t==0)
     {
      //check for its presence
      if(PositionSelect(Symbol()))
        {
         //delete old scheme
         DeleteScheme("ActP",true);
         //and apply new
         ApplyScheme(6);
         //set position parameters
         SetPositionParams();
         //the objects are unavailable for the selection
         Objects_Selectable("ActP",false);
        }
     }
   //if order
   if(t>0)
     {
      //check for its presence
      if(OrderSelect(t))
        {
         //delete old scheme
         DeleteScheme("ActP",true);
         //and apply new
         ApplyScheme(7);
         //set order parameters
         SetOrderParams(t);
         //the objects are unavailable for the selection
         Objects_Selectable("ActP",false);
        }
     }
  }

Las funciones SetPositionParams() y SetOrderParams() instalan las propiedades requeridas de la interfaz cargada:

//+------------------------------------------------------------------+
//| Set position parameters for the objects                          |
//+------------------------------------------------------------------+
void SetPositionParams()
  {
   //if position is exists
   if(PositionSelect(Symbol()))
     {
      //get its parameters
      double pr=PositionGetDouble(POSITION_PRICE_OPEN);
      double lots=PositionGetDouble(POSITION_VOLUME);
      double sl=PositionGetDouble(POSITION_SL);
      double tp=PositionGetDouble(POSITION_TP);
      double mag=PositionGetInteger(POSITION_MAGIC);
      //and set new values to the objects
      ObjectSetString(0,"ActP_Pr_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(pr)));
      ObjectSetString(0,"ActP_lots_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(lots)));
      ObjectSetString(0,"ActP_SL_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(sl)));
      ObjectSetString(0,"ActP_TP_edit4",OBJPROP_TEXT,str_del_zero(DoubleToString(tp)));
      if(mag!=0) ObjectSetString(0,"ActP_mag_edit4",OBJPROP_TEXT,IntegerToString(mag));
      //redraw chart
      ChartRedraw();
     }
   //if there isn't position, show message 
   else MessageBox("There isn't open position for "+Symbol());
  }
//+------------------------------------------------------------------+
//| Set pending order parameters for the objects                     |
//| ticket - order ticket                                            |
//+------------------------------------------------------------------+
void SetOrderParams(int ticket)
  {
   //if order exists
   if(OrderSelect(ticket) && OrderGetString(ORDER_SYMBOL)==Symbol())
     {
      //get its parameters
      double pr=OrderGetDouble(ORDER_PRICE_OPEN);
      double lots=OrderGetDouble(ORDER_VOLUME_CURRENT);
      double sl=OrderGetDouble(ORDER_SL);
      double tp=OrderGetDouble(ORDER_TP);
      double mag=OrderGetInteger(ORDER_MAGIC);
      double lim=OrderGetDouble(ORDER_PRICE_STOPLIMIT);
      datetime expir=OrderGetInteger(ORDER_TIME_EXPIRATION);
      ENUM_ORDER_TYPE type=OrderGetInteger(ORDER_TYPE);
      ENUM_ORDER_TYPE_TIME expir_type=OrderGetInteger(ORDER_TYPE_TIME);
      
      //of order type is stoplimit, modify the interface
      if(type==ORDER_TYPE_BUY_STOP_LIMIT || type==ORDER_TYPE_SELL_STOP_LIMIT)
        {
         //set new value to the order price edit
         ObjectSetString(0,"ActP_limpr_edit3",OBJPROP_TEXT,DoubleToString(lim,_Digits));
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,White);
         //set order price available for edit
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_READONLY,false);
        }
      //if order type isn't stoplimit, modify the interface
      else
        {
         ObjectSetString(0,"ActP_limpr_edit3",OBJPROP_TEXT,"");
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_BGCOLOR,LavenderBlush);
         ObjectSetInteger(0,"ActP_limpr_edit3",OBJPROP_READONLY,true);
        }

      //check expiration type
      //and set interface elements
      switch(expir_type)
        {
         case ORDER_TIME_GTC:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,1);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,0);
            break;
           }
         case ORDER_TIME_DAY:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,1);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,0);
            break;
           }
         case ORDER_TIME_SPECIFIED:
           {
            ObjectSetInteger(0,"ActP_exp1_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp2_radio3",OBJPROP_STATE,0);
            ObjectSetInteger(0,"ActP_exp3_radio3",OBJPROP_STATE,1);
            //in addition, set new value to the edit
            ObjectSetString(0,"ActP_exp_edit3",OBJPROP_TEXT,TimeToString(expir));
            break;
           }
        }
      //set new values for the objects
      ObjectSetString(0,"ActP_Pr_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(pr)));
      ObjectSetString(0,"ActP_lots_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(lots)));
      ObjectSetString(0,"ActP_SL_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(sl)));
      ObjectSetString(0,"ActP_TP_edit3",OBJPROP_TEXT,str_del_zero(DoubleToString(tp)));
      ObjectSetString(0,"ActP_ticket_edit3",OBJPROP_TEXT,IntegerToString(ticket));
      if(mag!=0) ObjectSetString(0,"ActP_mag_edit3",OBJPROP_TEXT,IntegerToString(mag));
      ChartRedraw();
     }
   //if there isn't such order, show message
   else MessageBox("There isn't an order with ticket "+IntegerToString(ticket)+" for "+Symbol());
  }

Y el toque final: la lista debería eliminarse al hacer click en el gráfico, usando CHARTEVENT_CLICK para este evento:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - is click on the chart
   if(id==CHARTEVENT_CLICK)
   {
       //delete all lists
      DeleteLists("ActP_orders_list_");
      DeleteLists("ActP_color_list_"); 
       //Set the activate buttons to the unpressed state
      ObjectSetInteger(0, "ActP_ord_button5", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col2_button6", OBJPROP_STATE, 0);
      ObjectSetInteger(0, "ActP_col3_button6", OBJPROP_STATE, 0);
      ChartRedraw(); 
      return;
   }
...
}

Como resultado, tendremos una bonita lista desplegable:

Figura 14. Un ejemplo del panel de lista desplegable "Modify/Close" ("Modificar / Cerrar")

Figura 14. Un ejemplo del panel de lista desplegable "Modify/Close" ("Modificar / Cerrar")

Ahora debemos crear una lista de selecciones de color en la pestaña de configuración. 

Piense en los controladores de los botones de activación:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on the chart
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //Click on the button to activate the colors drop-down list
      if(sparam=="ActP_col1_button6")
      {
          //check state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //the list is active
         if(selected)//the list is active
         {
             //creat list
            create_color_list(100, "ActP_col1_button6", 1);
             //Set the position of the remaining buttons to "unpressed"
            ObjectSetInteger(0, "ActP_col2_button6", OBJPROP_STATE, 0);
            ObjectSetInteger(0, "ActP_col3_button6", OBJPROP_STATE, 0);
             //delete other lists
            DeleteLists("ActP_color_list_2");
            DeleteLists("ActP_color_list_3");                 
         }
          //the list isn't selected
         else
         {
             //delete it
            DeleteLists("ActP_color_list_");
         }
          //redraw chart
         ChartRedraw();
          //finish the execution of function
         return;
      }
   ...   
   }
...
}

Aquí seguiremos el mismo método que en la lista de selección de orden.

La función para crear una lista es diferente:

//+------------------------------------------------------------------+
//| Function for creating the colors list                            |
//| y_max - maximal list widthа                                      |
//| ID - list ID                                                     |
//| num - interface number                                           |
//+------------------------------------------------------------------+
void create_color_list(int y_max,string ID,int num)
  {
  //Get the coordinates of the list activation button
   int x=ObjectGetInteger(0,ID,OBJPROP_XDISTANCE);
   int y=ObjectGetInteger(0, ID, OBJPROP_YDISTANCE)+ObjectGetInteger(0, ID, OBJPROP_YSIZE);
   //get color
   color col=ObjectGetInteger(0,ID,OBJPROP_COLOR);
   //and window width
   int wnd_height=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,wnd);
   y_max+=y;
   int y_cnt=0;
   //We will go through the colors array
   for(int i=0;i<132;i++)
     {
      color bgcol=colors[i];
      //calculate list item coordinates
      int y_pos=y+y_cnt*20;
      //if we reached the boundaries of the window, start new column
      if(y_pos+20>wnd_height || y_pos+20>y_max) {x+=20; y_cnt=0;}
      y_pos=y+y_cnt*20;
      y_cnt++;
      //create new element
      string name="ActP_color_list_"+IntegerToString(num)+ID+IntegerToString(i);
      create_button(name,"",x,y_pos,20,20);
      //and set its properties
      ObjectSetInteger(0,name,OBJPROP_COLOR,col);
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0);
      ObjectSetInteger(0,name,OBJPROP_STATE,0);
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgcol);
     }
  }

A continuación, observemos el proceso de click para el elemento de la lista:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on chart
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //click isn't on the color list button
      if(StringFind(sparam, "ActP_color_list_1")<0)
      {
          //delete list
         DeleteLists("ActP_color_list_1");
          //set color list activation button to "unpressed"
         ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
          //redraw chart
         ChartRedraw();
      }     
       //click on the color list
      else
      {
          //get color from the list
         color col=ObjectGetInteger(0, sparam, OBJPROP_BGCOLOR);
          //set it for all the buttons
         SetButtonsColor(col);
          //set button to unpressed
         ObjectSetInteger(0, "ActP_col1_button6", OBJPROP_STATE, 0);
          //delete list
         DeleteLists("ActP_color_list_1");
          //redraw chart
         ChartRedraw();
      }
   ...   
   }
...
}

La función SetButtonsColor() configura el color de los botones:

//+------------------------------------------------------------------+
//| The function sets color for all buttons                          |
//| col - color                                                      |
//+------------------------------------------------------------------+
void SetButtonsColor(color col)
  {
   //We will go through all the objects
   for(int i=ObjectsTotal(0);i>=0;i--)
     {
      string n=ObjectName(0,i);
      //If the object belongs to the panel and its has a button type
      //set color
      if(StringFind(n,"ActP")>=0 && ObjectGetInteger(0,n,OBJPROP_TYPE)==OBJ_BUTTON) 
         ObjectSetInteger(0,n,OBJPROP_BGCOLOR,col); 
     }
   //set global variable
   GlobalVariableSet("ActP_buttons_color",col);
  }

Veamos los resultados más abajo:

Figura 15. Configurar el color de los botones

Figura 15. Configurar el color de los botones

Las listas de selección de color y etiquetas de texto son similares. Como resultado, podemos crear un colorido panel con unos pocos clicks:

Figura 16. Cambiar colores de paneles, botones y texto

Figura 16. Cambiar colores de paneles, botones y texto

Ya hemos terminado con las listas. Sigamos con los campos de entrada.

5.5. Controlar el Evento Campo de Entrada

Un campo de entrada generará un evento CHARTEVENT_OBJECT_ENDEDIT, que ocurre al completar la edición del texto en el campo. La única razón por la que necesitamos controlar este evento es a causa de la configuración de líneas auxiliares para precios, relevantes para los precios en los campos de entrada.

Consideremos el ejemplo de una línea de stop:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //End edit event
   if(id==CHARTEVENT_OBJECT_ENDEDIT)//end edit event
   {
   ...
      //if edit field is SL field
      if(sparam=="ActP_SL_edit1")
      {
        //and auxiliary lines are enabled
         if(ObjectGetInteger(0,"ActP_DealLines_check1",OBJPROP_STATE)==1)
         {
            //get text from the field
            double sl_val=StringToDouble(ObjectGetString(0, "ActP_SL_edit1", OBJPROP_TEXT));
            //move lines at new position
            ObjectSetDouble(0, "ActP_SL_line1", OBJPROP_PRICE, sl_val);
         }
         //redraw chart
         ChartRedraw();
         //it ins't necessary to proceed the other objects, because the event from the one
         return;
      }
   ...   
   }
...
}

Otros campos se procesan de forma similar.

5.6 Controlar Eventos de Temporizador

El temporizador se usa para monitorizar las líneas auxiliares. De este modo, al mover las líneas, los valores de precios a las que están enlazadas se mueven automáticamente al campo de entrada. La función OnTimer() se ejecuta con cada tick del temporizador.

Considere el ejemplo del establecimiento de líneas de Stop Loss y Take Profit rastreándolas con la pestaña activa "Market" ("Mercado"):

void OnTimer()// Timer handler
{
   //panel 1 is active
   if(ObjectGetInteger(0, "ActP_main_1", OBJPROP_STATE)==1)
   {  
      //if auxiliary lines are allowed
      if(ObjectGetInteger(0,"ActP_DealLines_check1",OBJPROP_STATE)==1)
      {
         //set new values to the edit fields
         double sl_pr=NormalizeDouble(ObjectGetDouble(0, "ActP_SL_line1", OBJPROP_PRICE), _Digits);
         //stop loss
         ObjectSetString(0, "ActP_SL_edit1", OBJPROP_TEXT, DoubleToString(sl_pr, _Digits));
         //take profit
         double tp_pr=NormalizeDouble(ObjectGetDouble(0, "ActP_TP_line1", OBJPROP_PRICE), _Digits);
         ObjectSetString(0, "ActP_TP_edit1", OBJPROP_TEXT, DoubleToString(tp_pr, _Digits));
      }   
   }
   ...
   //redraw chart
   ChartRedraw();
}
//+------------------------------------------------------------------+

El rastreo de otras líneas se implementa de forma similar.


6. Ejecutar Operaciones de Trading

Llegados a este punto, hemos llenado todos los campos de entrada necesarios, activado cuadros de confirmación y botones de radio. Ahora es el momento de comprobar operaciones de trading basadas en los datos que ya tenemos.

6.1. Abrir Transacción

La pestaña "From the market" ("Desde el mercado") contiene los botones "Buy" ("Compra") y "Sell" ("Venta"). Si todos los campos se llenan correctamente, se debería implementar una operación de trading al hacer click en cualquiera de los botones.

Fijémonos en los controladores de estos botones:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on the object on the chart
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //click on the Buy button
      if(sparam=="ActP_buy_button1")
      {
          //check its state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //if it "pressed"
         if(selected)
         {
             //try to perform a deal
            deal(ORDER_TYPE_BUY);
             //and set the button to the unpressed state
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }

          //redraw chart
         ChartRedraw();
          //and finish the function execution
         return;
      }
      //******************************************
       //the similar for the sell button
      if(sparam=="ActP_sell_button1")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {
            deal(ORDER_TYPE_SELL);
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
         ChartRedraw();
         return;
      }     
   ...   
   }
...
}

Como puede ver, la función deal() funciona correctamente.

//+------------------------------------------------------------------+
//| Deal function                                                    |
//+------------------------------------------------------------------+
int deal(ENUM_ORDER_TYPE typ)
  {
   //get the data from the objects
   double SL=StringToDouble(ObjectGetString(0,"ActP_SL_edit1",OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit1", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit1",OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0, "ActP_Magic_edit1", OBJPROP_TEXT));
   int dev=StringToInteger(ObjectGetString(0, "ActP_Dev_edit1", OBJPROP_TEXT));
   string comm=ObjectGetString(0,"ActP_Comm_edit1",OBJPROP_TEXT);
   ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK;
   if(ObjectGetInteger(0,"ActP_Exe2_radio1",OBJPROP_STATE)==1) filling=ORDER_FILLING_IOC;
   //prepare request
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_DEAL;
   req.symbol=Symbol();
   req.volume=lots;
   req.price=Ask;
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.deviation=dev;
   req.type=typ;
   req.type_filling=filling;
   req.magic=mag;
   req.comment=comm;
   //send order
   OrderSend(req,res);
   //show message with the result
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //return retcode
   return(res.retcode);
  }

Nada extraño. Leímos primero la información requerida de los objetos y, con esa base, creamos una solicitud de trading.

Comprobemos nuestro trabajo:

Figura 17. Operaciones de trading: el resultado de la ejecución de Compra

Figura 17. Operaciones de trading: el resultado de la ejecución de Compra

Como puede ver, la operación de Compra se ha realizado con éxito.

6.2. Configurar una Orden Pendiente

Los botones "Buy" ("Compra") y "Sell" ("Venta") de la pestaña "Pending" ("Pendiente") son los responsables del establecimiento de órdenes pendientes.

Consideremos los controladores:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on the chart object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //click on the pending order set button
      if(sparam=="ActP_buy_button2")
      {
         //check the button state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         //if it pressed
         if(selected)
         {
            ENUM_ORDER_TYPE typ; 
            //get the pending order from the edit
            double pr=NormalizeDouble(StringToDouble(ObjectGetString(0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits());
            //if it isn't stoplimit order
            if(ObjectGetInteger(0, "ActP_limit_check2", OBJPROP_STATE)==0)
            {
               //if the order price is below the current price, set limit order
               if(Ask>pr) typ=ORDER_TYPE_BUY_LIMIT; 
               //overwise - stop order
               else typ=ORDER_TYPE_BUY_STOP;
            }
              //if stoplimit order is specified
            else
            {
               //set operation type
               typ=ORDER_TYPE_BUY_STOP_LIMIT;
            }   
              //try to place order
            order(typ);
              //set button to the unpressed state
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //redraw chart
         ChartRedraw();
          //and finish the execution of function
         return;
      }     
      //******************************************  

       //similar for the sell pending order
      if(sparam=="ActP_sell_button2")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {
            ENUM_ORDER_TYPE typ;
            double pr=NormalizeDouble(StringToDouble(ObjectGetString(0, "ActP_Pr_edit2", OBJPROP_TEXT)), Digits());
            if(ObjectGetInteger(0, "ActP_limit_check2", OBJPROP_STATE)==0)
            {
               if(Bid<pr) typ=ORDER_TYPE_SELL_LIMIT;
               else typ=ORDER_TYPE_SELL_STOP;
            }
            else
            {
               typ=ORDER_TYPE_SELL_STOP_LIMIT;
            }   
            order(typ);
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
         ChartRedraw();
         return;
      }        
   ...   
   }
...
}

Aquí determinaremos el tipo de órdenes futuras en base a la relación del precio de mercado actual con el precio establecido. Tras ello, la función order() determinará la orden:

//+------------------------------------------------------------------+
//| The function places an order                                     |
//+------------------------------------------------------------------+
int order(ENUM_ORDER_TYPE typ)
  {
   //get the order details from the objects
   double pr=StringToDouble(ObjectGetString(0,"ActP_Pr_edit2",OBJPROP_TEXT));
   double stoplim=StringToDouble(ObjectGetString(0,"ActP_limpr_edit2",OBJPROP_TEXT));
   double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit2", OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit2", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit2",OBJPROP_TEXT));
   datetime expir=StringToTime(ObjectGetString(0,"ActP_exp_edit2",OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0,"ActP_Magic_edit2",OBJPROP_TEXT));
   string comm=ObjectGetString(0,"ActP_Comm_edit2",OBJPROP_TEXT);
   ENUM_ORDER_TYPE_FILLING filling=ORDER_FILLING_FOK;
   if(ObjectGetInteger(0, "ActP_Exe2_radio2", OBJPROP_STATE)==1) filling=ORDER_FILLING_IOC;
   if(ObjectGetInteger(0, "ActP_Exe3_radio2", OBJPROP_STATE)==1) filling=ORDER_FILLING_RETURN;
   ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC;
   if(ObjectGetInteger(0, "ActP_exp2_radio2", OBJPROP_STATE)==1) expir_type=ORDER_TIME_DAY;
   if(ObjectGetInteger(0, "ActP_exp3_radio2", OBJPROP_STATE)==1) expir_type=ORDER_TIME_SPECIFIED;

   //prepare request
   MqlTradeRequest req;
   MqlTradeResult res; 
   req.action=TRADE_ACTION_PENDING;
   req.symbol=Symbol();
   req.volume=lots;
   req.price=NormalizeDouble(pr,Digits());
   req.stoplimit=NormalizeDouble(stoplim,Digits());
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.type=typ;
   req.type_filling=filling;
   req.type_time=expir_type;
   req.expiration=expir;
   req.comment=comm;
   req.magic=mag;
   //place order
   OrderSend(req,res);
   //show message with the result
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //return retcode
   return(res.retcode);
  }

Comprobemos nuestro trabajo:

Figura 18. Operaciones de trading: establecimiento de orden pendiente resultante

Figura 18. Operaciones de trading: establecimiento de orden pendiente resultante

El stop-limit de "Buy" se ha configurado con éxito.

6.3. Modificación de Posición

El botón "Edit" ("Editar") en la pestaña "Modify/Close" ("Modificar / Cerrar") es el responsable de la modificación de la posición seleccionada:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on the graphic object on the chart
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //click on the modify position button
      if(sparam=="ActP_mod_button4")
      {
          //check the button state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //if it pressed
         if(selected)//if pressed
         {
            //modify position
            modify_pos();
            //delete the elements of the scheme
            DeleteScheme("ActP" ,true);
            //and reset it (update the interface)
            SetScheme(0);
            //set the button to the unpressed state
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //redraw chart
         ChartRedraw();
          //finish the execution of function
         return;
      }     
   ...   
   }
...
}

La función Modify_pos() es directamente responsable de la modificación:

//+------------------------------------------------------------------+
//| The function modifies the position parameters                    |
//+------------------------------------------------------------------+
int modify_pos()
  {
   if(!PositionSelect(Symbol())) MessageBox("There isn't open position for symbol "+Symbol(),"Message");
   //get the details from the edit objects
   double SL=StringToDouble(ObjectGetString(0,"ActP_SL_edit4",OBJPROP_TEXT));  
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit4", OBJPROP_TEXT));
   int dev=StringToInteger(ObjectGetString(0,"ActP_dev_edit4",OBJPROP_TEXT));
   //prepare request
   MqlTradeRequest req;
   MqlTradeResult res;  
   req.action=TRADE_ACTION_SLTP;
   req.symbol=Symbol();
   req.sl=NormalizeDouble(SL, _Digits);
   req.tp=NormalizeDouble(TP, _Digits);
   req.deviation=dev;
   //send request
   OrderSend(req,res);
   //show message with the result
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //return retcode
   return(res.retcode);
  }

Resultados:

Figura 19. Operaciones de trading: el resultado de modificar las propiedades de la operación (configurar TP y SL)

Figura 19. Operaciones de trading: el resultado de modificar las propiedades de la operación (configurar TP y SL)


Los niveles de Stop Loss y Take Profit se han cambiado con éxito.

6.4. Cerrar una Posición

El botón "Close" ("Cerrar") en la pestaña "Modify/Close" es el responsable del cierre (posiblemente parcial) de la posición:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on the chart object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //click on the close button
      if(sparam=="ActP_del_button4")
      {
         //check the button state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //if pressed
         if(selected)
         {
            //try to close position
            int retcode=close_pos();
            //if successful
            if(retcode==10009)
            {
               //delete scheme elements
               DeleteScheme("ActP" ,true);
               //set the new text for the list activisation
               ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, "Select order -->");
            }
            //set button state to unpressed
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //redraw chart
         ChartRedraw();
          //finish the execution of function
         return;
      }     
   ...   
   }
...
}

La función close_pos() es responsable del cierre:

//+------------------------------------------------------------------+
//| Closes the position                                              |
//+------------------------------------------------------------------+
int close_pos()
  {
   if(!PositionSelect(Symbol())) MessageBox("There isn't open position for symbol "+Symbol(),"Message");
   //get the position details from the objects
   double lots=StringToDouble(ObjectGetString(0,"ActP_lots_edit4",OBJPROP_TEXT));
   if(lots>PositionGetDouble(POSITION_VOLUME)) lots=PositionGetDouble(POSITION_VOLUME);
   int dev=StringToInteger(ObjectGetString(0, "ActP_dev_edit4", OBJPROP_TEXT));
   int mag=StringToInteger(ObjectGetString(0, "ActP_mag_edit4", OBJPROP_TEXT));

   //prepare request
   MqlTradeRequest req;
   MqlTradeResult res;

   //the opposite deal is dependent on position type
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
     {
      req.price=Bid;
      req.type=ORDER_TYPE_SELL;
     }
   else
     {
      req.price=Ask;
      req.type=ORDER_TYPE_BUY;
     }

   req.action=TRADE_ACTION_DEAL;
   req.symbol=Symbol();
   req.volume=lots;
   req.sl=0;
   req.tp=0;
   req.deviation=dev;
   req.type_filling=ORDER_FILLING_FOK;
   req.magic=mag;
   //send request
   OrderSend(req,res);
   //show message with the result
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //return retcode
   return(res.retcode);
  }

El resultado: se cerraron 1,5 lotes de la transacción seleccionada:

Figura 20. Trading: cierre parcial de posición

Figura 20. Trading: cierre parcial de posición

6.5. Modificación de una Orden Pendiente

El botón "Edit" ("Editar") en la pestaña "Modify/Close"es el responsable de la modificación de la orden seleccionada:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on the chart graphic object
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
      //click on the order modify button
      if(sparam=="ActP_mod_button3")
      {
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
         if(selected)
         {     
            //get the order ticket from the edit
            string button_name=ObjectGetString(0, "ActP_ord_button5", OBJPROP_TEXT);
            long ticket=StringToInteger(StringSubstr(button_name, 1, StringFind(button_name, " ")-1));
            //modifying an order
            modify_order(ticket);
            //update interface
            DeleteScheme("ActP" ,true);
            SetScheme(ticket);
            //set button to unpressed state
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //redraw chart
         ChartRedraw();
          //and finish the execution of function
         return;
      }     
   ...   
   }
...
}

La función Modify_order () es responsable de la modificación:

//+------------------------------------------------------------------+
//| The function modifies an order                                   |
//| ticket - order ticket                                            |
//+------------------------------------------------------------------+
int modify_order(int ticket)
  {
   //get the order details from the corresponding chart objects
   double pr=StringToDouble(ObjectGetString(0,"ActP_Pr_edit3",OBJPROP_TEXT)); 
   double stoplim=StringToDouble(ObjectGetString(0,"ActP_limpr_edit3",OBJPROP_TEXT));
   double SL=StringToDouble(ObjectGetString(0, "ActP_SL_edit3", OBJPROP_TEXT));
   double TP=StringToDouble(ObjectGetString(0, "ActP_TP_edit3", OBJPROP_TEXT));
   double lots=StringToDouble(ObjectGetString(0,"ActP_Lots_edit3",OBJPROP_TEXT));
   datetime expir=StringToTime(ObjectGetString(0,"ActP_exp_edit3",OBJPROP_TEXT));
   ENUM_ORDER_TYPE_TIME expir_type=ORDER_TIME_GTC;
   if(ObjectGetInteger(0, "ActP_exp2_radio3", OBJPROP_STATE)==1) expir_type=ORDER_TIME_DAY;
   if(ObjectGetInteger(0, "ActP_exp3_radio3", OBJPROP_STATE)==1) expir_type=ORDER_TIME_SPECIFIED;

   //prepare request to modify
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_MODIFY;
   req.order=ticket;
   req.volume=lots;
   req.price=NormalizeDouble(pr,Digits());
   req.stoplimit=NormalizeDouble(stoplim,Digits());
   req.sl=NormalizeDouble(SL, Digits());
   req.tp=NormalizeDouble(TP, Digits());
   req.type_time=expir_type;
   req.expiration=expir;
   //send request
   OrderSend(req,res);
   //show message with the result
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //return retcode
   return(res.retcode);
  }

Veamos el resultado: una orden se modificó con éxito:

Figura 21. Modificación de una orden pendiente

Figura 21. Modificación de una orden pendiente

6.6. Eliminar una Orden Pendiente

El botón "Delete" ("Eliminar") en la pestaña "Modify/Close"es el responsable de la eliminación de la orden seleccionada:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
...
   //Event - click on the graphic object on the chart
   if(id==CHARTEVENT_OBJECT_CLICK)
   {
   ...
       //click on the order delete button
      if(sparam=="ActP_del_button3")
      {
         //check the button state
         bool selected=ObjectGetInteger(0,sparam,OBJPROP_STATE);
          //if pressed
         if(selected)
         {
            //get the ticket from the list
            string button_name=ObjectGetString(0, "ActP_ord_button5", OBJPROP_TEXT);
            long ticket=StringToInteger(StringSubstr(button_name, 1, StringFind(button_name, " ")-1));
            //try to delete order
            int retcode=del_order(ticket);
            //if successful
            if(retcode==10009)
            {
               //delete all objects of the scheme
               DeleteScheme("ActP" ,true);
               //set new text for the list activation button
               ObjectSetString(0, "ActP_ord_button5", OBJPROP_TEXT, "Select an order -->");
            }
             //set button state to unpressed
            ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
         }
          //redraw chart
         ChartRedraw();
          //and finish the execution of function
         return;
      }     
   ...   
   }
...
}

La función del_order() es responsable de la eliminación de órdenes:

//+------------------------------------------------------------------+
//| The function for pending order deletion                          |
//| ticket - order ticket                                            |
//+------------------------------------------------------------------+
int del_order(int ticket)
  {
   //prepare request for deletion
   MqlTradeRequest req;
   MqlTradeResult res;
   req.action=TRADE_ACTION_REMOVE;
   req.order=ticket;
   //send request
   OrderSend(req,res);
   //show message with the result
   MessageBox(RetcodeDescription(res.retcode),"Message");
   //return retcode
   return(res.retcode);
  }

Veamos el resultado: la orden se eliminó.

Figura 22. Trading: eliminación de una orden pendiente

Fig. 22 Operaciones de trading: eliminación de una orden pendiente


Conclusión

Hemos comprobado todas las funciones del panel y funcionan correctamente.

Esperamos que el conocimiento adquirido al leer este artículo le ayude con el desarrollo de paneles de control activos, que le resultarán indispensables para trabajar en el mercado. 

Para comenzar con el panel, debe descomprimir el archivo en una carpeta con el terminal, después aplicar el indicador AP al gráfico, y solo entonces ejecutar el Panel Activo Asesor Experto.


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/62

Archivos adjuntos |
Conexión del Asesor Experto con ICQ en MQL5 Conexión del Asesor Experto con ICQ en MQL5
Este artículo describe el método de intercambio de información entre el Asesor Experto y usuarios de ICQ, y presenta varios ejemplos. El material facilitado resultará interesante para aquellos que deseen recibir información de trading remotamente de un terminal de cliente, a través de un ICQ client en su teléfono móvil o PDA.
Dr. Tradelove o Cómo dejé de preocuparme y escribí un Asesor Experto Autoenseñable Dr. Tradelove o Cómo dejé de preocuparme y escribí un Asesor Experto Autoenseñable
Hace poco más de un año joo nos ha dado en su artículo "Algoritmos genéticos: ¡Es fácil!" una herramienta para la implementación del Algoritmo Genético en MQL5. Pues, vamos a utilizar esta herramienta y crearemos un Asesor Experto que, al alcanzar unas condiciones extremas, realice la Optimización Genética de sus propios parámetros...
Crear un juego de la "Serpiente" en MQL5 Crear un juego de la "Serpiente" en MQL5
Este artículo describe un ejemplo de programación del juego de la "Serpiente". En MQL5, la programación para juegos se hizo posible principalmente a causa de sus herramientas para controlar eventos. La programación orientada al objeto simplifica inmensamente este proceso. En este artículo aprenderá sobre las herramientas de procesamiento de eventos, los ejemplos de uso de las clases de la Biblioteca MQL5 Estándar y detalles de llamadas de funciones periódicas.
La estrategia "Todo o Nada" en Forex La estrategia "Todo o Nada" en Forex
El objetivo de este artículo es crear una estrategia comercial máximamente sencilla que implemente el principio de juego "Todo o Nada". No se plantea la tarea de crear un Asesor Experto rentable. El objetivo consiste en multiplicar el depósito inicial con la probabilidad máximamente posible. ¿Será posible usar Forex para conseguir grandes beneficios contra la probabilidad de perderlo todo, sin saber nada sobre el análisis técnico y sin usar ningunos indicadores?