
Crear Paneles de Control Activos en MQL5 para Trading
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.
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)
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 4. Elementos de la pestaña "Pending"
Pestaña "Settings" ("Configuración"):
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"
A continuación, crearemos sub-pestañas. Para trabajar con operaciones de trading:
Figura 7. Elementos para trabajar con posiciones
Y 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 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 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 12. Elementos de la pestaña "Modify/Close"
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"
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")
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
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
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
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
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)
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
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
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ó.
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





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso