Libro de Recetas MQL5: Propiedades de Posición en el Panel de Información Personalizada
Anatoli Kazharski | 28 mayo, 2014
Introducción
En esta ocasión crearemos un Asesor Experto que obtendrá propiedades de posición en el símbolo actual y las mostrará en el panel de información personalizada durante operaciones de trading manual. El panel de información se creará usando objetos gráficos, y la información que muestra se actualizará con cada tick. Esto será mucho más conveniente que tener que ejecutar todo el rato el script descrito en el artículo anterior de la serie llamado "MQL5 Cookbook: Getting Position Properties" (“Libro de Recetas MQL5: Obtener Propiedades de Posición”).
Desarrollar un Asesor Experto
Empecemos con objetos gráficos. Para crear el panel de información necesitaremos objetos para el fondo, encabezamiento, nombres y valores de propiedades de posición. El fondo y encabezamiento requerirán un rectángulo que no se mueva con el precio. El rectángulo se puede crear usando objetos gráficos tales como Rectangle Label (etiqueta de rectángulo) o Edit, (editar), mientras que los nombres y valores de propiedades de objeto se harán usando Text Labels(etiquetas de texto).
Antes de proceder con el código, prepararemos primero un layout para el panel información. Su conveniencia radica en el hecho de que podemos cambiar rápidamente cualquier propiedad en la ventana de configuración y personalizar el aspecto del panel de información.
Cada objeto tiene una ventana de configuración que se puede abrir desde el menú de contexto de un objeto seleccionado. La ventana de configuración también se puede abrir desde la Object List (lista de objetos, Ctrl+B), seleccionando el objeto requerido de haciendo clic en Properties (propiedades). El layout del panel de información se muestra abajo. También se puede usar para calcular tamaños y coordenadas al escribir un código. Cuando el código para el panel información está listo, deberá eliminar los objetos de layout manualmente, puesto que el Asesor Experto no será capaz de “verlos” y, por tanto, no los eliminará del gráfico.
Fig. 1. Preparar el layout para el panel información.
Ahora debemos crear una plantilla para el Asesor Experto. Esto se puede hacer igual de rápido que el script. En MQL5 Wizard, la opción de Asesor Experto (plantilla) está seleccionada por defecto. Seguiremos con los siguientes pasos sin hacer ningún cambio en las opciones puesto que en estos momentos no se necesita. A continuación haga click en Finish (finalizar) y verá una plantilla como la de abajo:
//+------------------------------------------------------------------+ //| PositionPropertiesPanel.mq5 | //| Copyright 2012, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2012, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
Puede observar que la plantilla de Asesor Experto es diferente de la plantilla de script. Aparte de las propiedades de programa (#property), hay tres funciones principales: OnInit(), OnDeinit() y OnTick().
La función OnInit() se llama al cargar el programa, cambiar parámetros externos, compilar el programa -suponiendo que el programa en ese momento esté añadido en el gráfico-, y al cambiar el símbolo o período. Si es necesario, puede inicializar determinadas variables o arrays en esta función para poder trabajar con ellos más tarde.
La función OnDeinit() se llama al eliminar el programa del gráfico, cambiar la cuenta, símbolo o período. Todas las posibles razones de desinicialización se facilitan en el material de referencia MQL5. Este Asesor Experto empleará una función definida por el usuario, GetDeinitReasonText(), tque convierte el identificador de razón de desinicialización (el parámetro de función OnDeinit()) a texto.
Y, finalmente, la función OnTick() se llama cada vez que haya un nuevo tick en el símbolo en cuyo gráfico esté operando actualmente el Asesor Experto.
Ahora preparemos todas las constantes, variables y arrays que vamos a usar en el Asesor Experto. Los colocaremos al principio del programa. En primer lugar, defina las variables cuyos valores permanecen inalterables a través del programa:
//--- #define INFOPANEL_SIZE 14 // Size of the array for info panel objects #define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME) // Name of the Expert Advisor //---
A esto le siguen variables globales para propiedades de posición:
//--- GLOBAL VARIABLES bool pos_open=false; // Flag of presence/absence of an open position string pos_symbol=""; // Symbol long pos_magic=0; // Magic number string pos_comment=""; // Comment double pos_swap=0.0; // Swap double pos_commission=0.0; // Commission double pos_price=0.0; // Position price double pos_cprice=0.0; // Current price of the position double pos_profit=0.0; // Profit/Loss of the position double pos_volume=0.0; // Position volume double pos_sl=0.0; // Stop Loss of the position double pos_tp=0.0; // Take Profit of the position datetime pos_time=NULL; // Position opening time long pos_id=0; // Position identifier ENUM_POSITION_TYPE pos_type=WRONG_VALUE; // Position type
Después de las variables, declararemos arrays de nombres de objetos gráficos. Estos objetos mostrarán propiedades de posición y sus valores en el gráfico. Para ello, crearemos dos arrays de cadena de caracteres e inicializaremos sus elementos a valores. En los paréntesis cuadrados usaremos el valor de la constante INFOPANEL_SIZE declarada al principio del programa. En total habrá 14 elementos en cada array.
// Array of names of objects that display names of position properties string positionPropertyNames[INFOPANEL_SIZE]= { "name_pos_symbol", "name_pos_magic", "name_pos_comment", "name_pos_swap", "name_pos_commission", "name_pos_price", "name_pos_cprice", "name_pos_profit", "name_pos_volume", "name_pos_sl", "name_pos_tp", "name_pos_time", "name_pos_id", "name_pos_type" }; //--- // Array of names of objects that display values of position properties string positionPropertyValues[INFOPANEL_SIZE]= { "value_pos_symbol", "value_pos_magic", "value_pos_comment", "value_pos_swap", "value_pos_commission", "value_pos_price", "value_pos_cprice", "value_pos_profit", "value_pos_volume", "value_pos_sl", "value_pos_tp", "value_pos_time", "value_pos_id", "value_pos_type" }; //---
Usando estos nombres, puede encontrar programáticamente el objeto necesario en el gráfico y configurar o cambiar sus propiedades tales como el texto mostrado, color, tamaño, etc. Además, estos nombres se mostrarán en la ventana de Object List (Ctrl+B) tras ser creados en el gráfico. Pero usted no podrá verlos allí, puesto que los objetos creados por el programa MQL5 están ocultos por defecto. Para hacerlos visibles, debe hacer clic en List All (mostrar todos) en la ventana de Object List. Esto nos ayuda a separar los objetos creados manualmente de los objetos creados programáticamente, lo que resulta muy conveniente.
Además, necesitaremos funciones definidas por el usuario que se emplearán en el Asesor Experto para crear objetos gráficos. La función ofrecida por MQL5 para la creación de objetos gráficos es ObjectCreate(). Pero, puesto que también necesitamos configurar propiedades de objeto, aunque puede que los objetos mismos deban crearse más de una vez, sería mejor pensar en un método más conveniente y compacto que se pudiera implementar en una sola línea de código.
Para crear el fondo y encabezamiento del panel de información usaremos el objeto gráfico Edit. Escribamos la función CreateEdit():
//+------------------------------------------------------------------+ //| CREATING THE EDIT OBJECT | //+------------------------------------------------------------------+ void CreateEdit(long chart_id, // chart id int sub_window, // (sub)window number string name, // object name string text, // displayed text ENUM_BASE_CORNER corner, // chart corner string font_name, // font int font_size, // font size color font_color, // font color int x_size, // width int y_size, // height int x_distance, // X-coordinate int y_distance, // Y-coordinate long z_order, // Z-order color background_color, // background color bool read_only) // Read Only flag { // If the object has been created successfully,... if(ObjectCreate(chart_id,name,OBJ_EDIT,sub_window,0,0)) { // ...set its properties ObjectSetString(chart_id,name,OBJPROP_TEXT,text); // displayed text ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner); // set the chart corner ObjectSetString(chart_id,name,OBJPROP_FONT,font_name); // set the font ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size); // set the font size ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color); // font color ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,background_color); // background color ObjectSetInteger(chart_id,name,OBJPROP_XSIZE,x_size); // width ObjectSetInteger(chart_id,name,OBJPROP_YSIZE,y_size); // height ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance); // set the X coordinate ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance); // set the Y coordinate ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false); // cannot select the object if FALSE ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order); // Z-order of the object ObjectSetInteger(chart_id,name,OBJPROP_READONLY,read_only); // Read Only ObjectSetInteger(chart_id,name,OBJPROP_ALIGN,ALIGN_LEFT); // align left ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n"); // no tooltip if "\n" } }
Ahora, el objeto gráfico Edit (OBJ_EDIT) se puede crear usando una sola línea de código. Lo ilustraremos con un ejemplo al crear una función que configurará el panel información en el gráfico.
Sigamos ahora con los objetos de Text Label que se usarán para mostrar la lista de propiedades de posición y sus valores, y creemos la función CreateLabel() de forma similar:
//+------------------------------------------------------------------+ //| CREATING THE LABEL OBJECT | //+------------------------------------------------------------------+ void CreateLabel(long chart_id, // chart id int sub_window, // (sub)window number string name, // object name string text, // displayed text ENUM_ANCHOR_POINT anchor, // anchor point ENUM_BASE_CORNER corner, // chart corner string font_name, // font int font_size, // font size color font_color, // font color int x_distance, // X-coordinate int y_distance, // Y-coordinate long z_order) // Z-order { // If the object has been created successfully,... if(ObjectCreate(chart_id,name,OBJ_LABEL,sub_window,0,0)) { // ...set its properties ObjectSetString(chart_id,name,OBJPROP_TEXT,text); // displayed text ObjectSetString(chart_id,name,OBJPROP_FONT,font_name); // set the font ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color); // set the font color ObjectSetInteger(chart_id,name,OBJPROP_ANCHOR,anchor); // set the anchor point ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner); // set the chart corner ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size); // set the font size ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance); // set the X-coordinate ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance); // set the Y-coordinate ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false); // cannot select the object if FALSE ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order); // Z-order of the object ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n"); // no tooltip if "\n" } }
También es recomendable echar un vistazo a las descripciones de función en el material de referencia de MQL5.
Al eliminarse del gráfico, el Asesor Experto debe eliminar a su vez todos los objetos que añadió anteriormente al gráfico. Para ello, simplemente puede pasar el nombre del objeto a la función DeleteObjectByName(). Esta buscará el objeto por el nombre especificado y lo eliminará, si lo encuentra, usando la función incorporada ObjectFind(), que busca el objeto, y la función ObjectDelete(), que lo elimina.
//+------------------------------------------------------------------+ //| DELETING THE OBJECT BY NAME | //+------------------------------------------------------------------+ void DeleteObjectByname(string name) { int sub_window=0; // Returns the number of the subwindow where the object is located bool res =false; // Result following an attempt to delete the object //--- Find the object by name sub_window=ObjectFind(ChartID(),name); //--- if(sub_window>=0) // If it has been found,.. { res=ObjectDelete(ChartID(),name); // ...delete it //--- // If an error occurred when deleting the object,.. if(!res) // ...print the relevant message { Print("Error deleting the object: ("+IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); } } }
Además, en la función DeleteObjectByName() implementaremos también una comprobación de errores al eliminar un objeto. existe un error, aparecerá un mensaje relevante con el código de error y su descripción. Como puede ver en el código de arriba, usamos una función definida por el usuario adicional que convierte el código de error a una descripción textual: la función ErrorDescription(). Puesto que hay muchos códigos de error, ejemplificaré lo que he explicado usando sólo una parte de esta función (vea el código de abajo). La versión completa del código se puede encontrar en el archivo de código fuente adjunto en este artículo.
//+------------------------------------------------------------------+ //| RETURNING THE ERROR DESCRIPTION | //+------------------------------------------------------------------+ string ErrorDescription(int error_code) { string error_string=""; //--- switch(error_code) { //--- Trade server return codes case 10004: error_string="Requote"; break; case 10006: error_string="Request rejected"; break; case 10007: error_string="Request canceled by trader"; break; case 10008: error_string="Order placed"; break; case 10009: error_string="Request executed"; break; case 10010: error_string="Request executed partially"; break; case 10011: error_string="Request processing error"; break; case 10012: error_string="Request timed out"; break; case 10013: error_string="Invalid request"; break; case 10014: error_string="Invalid request volume"; break; case 10015: error_string="Invalid request price"; break; case 10016: error_string="Invalid Stop orders in the request"; break; case 10017: error_string="Trading forbidden"; break; case 10018: error_string="Market is closed"; break; case 10019: error_string="Insufficient funds"; break; case 10020: error_string="Prices changed"; break; case 10021: error_string="No quotes to process the request"; break; case 10022: error_string="Invalid order expiration in the request"; break; case 10023: error_string="Order status changed"; break; case 10024: error_string="Too many requests"; break; case 10025: error_string="No changes in the request"; break; case 10026: error_string="Automated trading is disabled by trader"; break; case 10027: error_string="Automated trading is disabled by the client terminal"; break; case 10028: error_string="Request blocked for processing"; break; case 10029: error_string="Order or position frozen"; break; case 10030: error_string="The specified type of order execution by balance is not supported"; break; case 10031: error_string="No connection with trade server"; break; case 10032: error_string="Transaction is allowed for live accounts only"; break; case 10033: error_string="You have reached the maximum number of pending orders"; break; case 10034: error_string="You have reached the maximum order and position volume for this symbol"; break; ... } //--- return(error_string); }
En el artículo anterior tratamos la función GetPositionProperties(), que obtiene propiedades de posición. Ahora, la estructura de la función va a ser un poco más compleja. Comprobaremos si hay una posición actualmente abierta con la flag de presencia/ausencia de una posición abierta almacenada en la variable global pos_open. Esta información se puede solicitar en otras funciones sin tener que llamar a la función PositionSelect() cada vez que esto ocurre.
Después, si existe una posición abierta, obtendremos sus propiedades; de lo contrario todas las variables volverán a cero. Escribamos ahora una función ZeroPositionProperties() simple:
//+------------------------------------------------------------------+ //| ZEROING OUT VARIABLES FOR POSITION PROPERTIES | //+------------------------------------------------------------------+ void ZeroPositionProperties() { pos_symbol =""; pos_comment =""; pos_magic =0; pos_price =0.0; pos_cprice =0.0; pos_sl =0.0; pos_tp =0.0; pos_type =WRONG_VALUE; pos_volume =0.0; pos_commission =0.0; pos_swap =0.0; pos_profit =0.0; pos_time =NULL; pos_id =0; }
Además, al final de la función GetPositionProperties(), llamaremos a la función definida por el usuario SetInfoPanel(), que dibujar/actualiza el panel de información en el gráfico.
//+------------------------------------------------------------------+ //| GETTING POSITION PROPERTIES | //+------------------------------------------------------------------+ void GetPositionProperties() { // Check if there is an open position pos_open=PositionSelect(_Symbol); //--- if(pos_open) // If an open position exists, get its properties { pos_symbol =PositionGetString(POSITION_SYMBOL); pos_comment =PositionGetString(POSITION_COMMENT); pos_magic =PositionGetInteger(POSITION_MAGIC); pos_price =PositionGetDouble(POSITION_PRICE_OPEN); pos_cprice =PositionGetDouble(POSITION_PRICE_CURRENT); pos_sl =PositionGetDouble(POSITION_SL); pos_tp =PositionGetDouble(POSITION_TP); pos_type =(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); pos_volume =PositionGetDouble(POSITION_VOLUME); pos_commission =PositionGetDouble(POSITION_COMMISSION); pos_swap =PositionGetDouble(POSITION_SWAP); pos_profit =PositionGetDouble(POSITION_PROFIT); pos_time =(datetime)PositionGetInteger(POSITION_TIME); pos_id =PositionGetInteger(POSITION_IDENTIFIER); } else // If there is no open position, zero out variables for position properties ZeroPositionProperties(); //--- SetInfoPanel(); // Set/update the info panel }
Escribamos ahora la función SetInfoPanel(). Abajo está el código de la función con comentarios detallados:
//+------------------------------------------------------------------+ //| SETTING THE INFO PANEL | //|------------------------------------------------------------------+ void SetInfoPanel() { int y_bg=18; // Y-coordinate for the background and header int y_property=32; // Y-coordinate for the list of properties and their values int line_height=12; // Line height //--- int font_size=8; // Font size string font_name="Calibri"; // Font color font_color=clrWhite; // Font color //--- ENUM_ANCHOR_POINT anchor=ANCHOR_RIGHT_UPPER; // Anchor point in the top right corner ENUM_BASE_CORNER corner=CORNER_RIGHT_UPPER; // Origin of coordinates in the top right corner of the chart //--- X-coordinates int x_first_column=120; // First column (names of properties) int x_second_column=10; // Second column (values of properties) //--- Array of Y-coordinates for the names of position properties and their values int y_prop_array[INFOPANEL_SIZE]={0}; //--- Fill the array with coordinates for each line on the info panel y_prop_array[0]=y_property; y_prop_array[1]=y_property+line_height; y_prop_array[2]=y_property+line_height*2; y_prop_array[3]=y_property+line_height*3; y_prop_array[4]=y_property+line_height*4; y_prop_array[5]=y_property+line_height*5; y_prop_array[6]=y_property+line_height*6; y_prop_array[7]=y_property+line_height*7; y_prop_array[8]=y_property+line_height*8; y_prop_array[9]=y_property+line_height*9; y_prop_array[10]=y_property+line_height*10; y_prop_array[11]=y_property+line_height*11; y_prop_array[12]=y_property+line_height*12; y_prop_array[13]=y_property+line_height*13; //--- Background of the info panel CreateEdit(0,0,"InfoPanelBackground","",corner,font_name,8,clrWhite,230,190,231,y_bg,0,C'15,15,15',true); //--- Header of the info panel CreateEdit(0,0,"InfoPanelHeader","POSITION PROPERTIES",corner,font_name,8,clrWhite,230,14,231,y_bg,1,clrFireBrick,true); //--- List of the names of position properties and their values // Property name CreateLabel(0,0,pos_prop_names[0],"Symbol :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[0],2); // Property value CreateLabel(0,0,pos_prop_values[0],GetValInfoPanel(0),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[0],2); //--- CreateLabel(0,0,pos_prop_names[1],"Magic Number :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[1],2); CreateLabel(0,0,pos_prop_values[1],GetValInfoPanel(1),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[1],2); //--- CreateLabel(0,0,pos_prop_names[2],"Comment :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[2],2); CreateLabel(0,0,pos_prop_values[2],GetValInfoPanel(2),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[2],2); //--- CreateLabel(0,0,pos_prop_names[3],"Swap :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[3],2); CreateLabel(0,0,pos_prop_values[3],GetValInfoPanel(3),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[3],2); //--- CreateLabel(0,0,pos_prop_names[4],"Commission :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[4],2); CreateLabel(0,0,pos_prop_values[4],GetValInfoPanel(4),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[4],2); //--- CreateLabel(0,0,pos_prop_names[5],"Open Price :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[5],2); CreateLabel(0,0,pos_prop_values[5],GetValInfoPanel(5),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[5],2); //--- CreateLabel(0,0,pos_prop_names[6],"Current Price :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[6],2); CreateLabel(0,0,pos_prop_values[6],GetValInfoPanel(6),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[6],2); //--- CreateLabel(0,0,pos_prop_names[7],"Profit :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[7],2); CreateLabel(0,0,pos_prop_values[7],GetValInfoPanel(7),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[7],2); //--- CreateLabel(0,0,pos_prop_names[8],"Volume :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[8],2); CreateLabel(0,0,pos_prop_values[8],GetValInfoPanel(8),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[8],2); //--- CreateLabel(0,0,pos_prop_names[9],"Stop Loss :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[9],2); CreateLabel(0,0,pos_prop_values[9],GetValInfoPanel(9),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[9],2); //--- CreateLabel(0,0,pos_prop_names[10],"Take Profit :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[10],2); CreateLabel(0,0,pos_prop_values[10],GetValInfoPanel(10),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[10],2); //--- CreateLabel(0,0,pos_prop_names[11],"Time :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[11],2); CreateLabel(0,0,pos_prop_values[11],GetValInfoPanel(11),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[11],2); //--- CreateLabel(0,0,pos_prop_names[12],"Identifier :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[12],2); CreateLabel(0,0,pos_prop_values[12],GetValInfoPanel(12),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[12],2); //--- CreateLabel(0,0,pos_prop_names[13],"Type :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[13],2); CreateLabel(0,0,pos_prop_values[13],GetValInfoPanel(13),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[13],2); //--- ChartRedraw(); // Redraw the chart }
Observemos más en detalle la función SetInfoPanel(). Las variables que tienen que ver con propiedades de objetos gráficos (coordenadas, color, fuente, texto mostrado, etc) se declaran al principio de la función. Preste atención al proceso de llenar el array de coordenadas Y para la lista de propiedades de posición en el panel de información: se implementa de forma que quede claro para principiantes. Pero se puede reducir a un par de líneas de código al usar un bucle. Puede escribirlo de la siguiente manera:
//--- Fill the array with coordinates for each line on the info panel for(int i=0; i<INFOPANEL_SIZE; i++) { if(i==0) y_prop_array[i]=y_property; else y_prop_array[i]=y_property+line_height*i; }
A continuación, todas las propiedades de los objetos que deben mostrarse en el panel se deben especificar en los parámetros de las funciones CreateLabel() y CreateEdit(), creadas anteriormente, tomando un objeto detrás de otro. La lista entera también se puede implementar en un par de líneas de código usando un bucle. Para ello, debemos crear otro array para objetos que mostrará el texto de los nombres de propiedades de posición en el gráfico. Estos serán sus deberes.
La función GetPropertyValue(), que recibe el número de objeto, devuelve el valor que después se pasa a la función CreateLabel() como cuarto parámetro (texto mostrado). Esto concierne a todos los objetos que mostrarán valores de propiedades de posición. El valor devuelto por la función es una cadena de caracteres ajustada que se mostrará en el panel. Abajo está el código de la función con comentarios detallados:
//+------------------------------------------------------------------+ //| RETURNING THE STRING WITH POSITION PROPERTY VALUE | //+------------------------------------------------------------------+ string GetPropertyValue(int number) { //--- Sign indicating the lack of an open position or a certain property // E.g. the lack of a comment, Stop Loss or Take Profit string empty="-"; //--- If an open position exists, return the value of the requested property if(pos_open) { switch(number) { case 0 : return(pos_symbol); break; case 1 : return(IntegerToString((int)pos_magic)); break; //--- return the value of the comment, if any, otherwise return the sign indicating the lack of comment case 2 : return(pos_comment!="" ? pos_comment : empty); break; case 3 : return(DoubleToString(pos_swap,2)); break; case 4 : return(DoubleToString(pos_commission,2)); break; case 5 : return(DoubleToString(pos_price,_Digits)); break; case 6 : return(DoubleToString(pos_cprice,_Digits)); break; case 7 : return(DoubleToString(pos_profit,2)); break; case 8 : return(DoubleToString(pos_volume,2)); break; case 9 : return(pos_sl!=0.0 ? DoubleToString(pos_sl,_Digits) : empty); break; case 10 : return(pos_tp!=0.0 ? DoubleToString(pos_tp,_Digits) : empty); break; case 11 : return(TimeToString(pos_time,TIME_DATE|TIME_MINUTES)); break; case 12 : return(IntegerToString((int)pos_id)); break; case 13 : return(PositionTypeToString(pos_type)); break; default : return(empty); } } //--- // If there is no open position, return the sign indicating the lack of the open position "-" return(empty); }
El código de arriba sugiere que para cada número pasado a la función se prepara un valor determinado, suponiendo que haya una posición abierta. Si no hay posición abierta alguna, la función devolverá un guión (-), mostrado para todos los objetos que tienen que ver con valores de propiedad de posición.
Al final de la función SetInfoPanel() llamaremos a la función ChartRedraw(), designada para un rediseño forzado de gráfico. Si no se la llama, no podrá ver los cambios hechos.
Ahora debemos escribir la función que eliminará todos los objetos gráficos creados por el Asesor Experto. La llamaremos DeleteInfoPanel():
//+------------------------------------------------------------------+ //| DELETING THE INFO PANEL | //+------------------------------------------------------------------+ void DeleteInfoPanel() { DeleteObjectByName("InfoPanelBackground"); // Delete the panel background DeleteObjectByName("InfoPanelHeader"); // Delete the panel header //--- Delete position properties and their values for(int i=0; i<INFOPANEL_SIZE; i++) { DeleteObjectByName(pos_prop_names[i]); // Delete the property DeleteObjectByName(pos_prop_values[i]); // Delete the value } //--- ChartRedraw(); // Redraw the chart }
Ahora solo debemos distribuir los métodos que hemos creado entre las principales funciones del Asesor Experto que presentamos originalmente en la plantilla tras crearlo en el MQL5 Wizard. Esta es la parte más fácil:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Get the properties and set the panel GetPositionProperties(); //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Print the deinitialization reason to the journal Print(GetDeinitReasonText(reason)); //--- When deleting from the chart if(reason==REASON_REMOVE) //--- Delete all objects relating to the info panel from the chart DeleteInfoPanel(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Get the properties and update the values on the panel GetPositionProperties(); } //+------------------------------------------------------------------+
Lo único que podría suponerle un problema es la función GetDeinitReasonText(), que devuelve una descripción textual del código de razón de desinicialización:
//+---------------------------------------------------------------------+ //| RETURNING A TEXTUAL DESCRIPTION OF THE DEINITIALIZATION REASON CODE | //+---------------------------------------------------------------------+ string GetDeinitReasonText(int reason_code) { string text=""; //--- switch(reason_code) { case REASON_PROGRAM : // 0 text="The Expert Advisor has stopped working calling the ExpertRemove() function."; break; case REASON_REMOVE : // 1 text="The '"+EXPERT_NAME+"' program has been removed from the chart."; break; case REASON_RECOMPILE : // 2 text="The '"+EXPERT_NAME+"' program has been recompiled."; break; case REASON_CHARTCHANGE : // 3 text="Chart symbol or period has been changed."; break; case REASON_CHARTCLOSE : // 4 text="The chart is closed."; break; case REASON_PARAMETERS : // 5 text="Input parameters have been changed by the user."; break; case REASON_ACCOUNT : // 6 text="A different account has been activated."; break; case REASON_TEMPLATE : // 7 text="A different chart template has been applied."; break; case REASON_INITFAILED : // 8 text="A flag specifying that the OnInit() handler returned zero value."; break; case REASON_CLOSE : // 9 text="The terminal has been closed."; break; default : text="The reason is undefined."; } //--- return text; }
Si intenta usar el Asesor Experto en un símbolo de gráfico que actualmente no tiene ninguna posición abierta, verá guiones en lugar de valores de propiedad de posición en el panel. El panel tendrá el mismo aspecto después de que cierre una posición determinada.
Fig. 2. Panel de información en ausencia de una posición abierta.
Si el Asesor Experto se añade al gráfico del símbolo que tiene una posición abierta, o si se abre una posición tras añadir el Asesor Experto al gráfico, todos los guiones se sustituirán con los valores de propiedad de posición correspondientes:
Fig. 3. Panel de posición mostrando propiedades de la posición abierta.
Hay una pequeña peculiaridad: tras cerrar la posición, los valores en el panel no se actualizarán hasta que entre un nuevo tick. Hay una forma para hacer que los valores actualicen inmediatamente, pero los pasos para ello se tratarán en el siguiente artículo de la serie.
Conclusión
Algunas de las funciones presentadas en este artículo también se usarán en los siguientes artículos de la serie de Libros de Recetas de MQL5, mientras que otras se modificarán y mejorarán dependiendo de la tarea que tengamos entre manos. Es recomendable leer los artículos en orden, uno detrás de otro, puesto que cada artículo es una continuación lógica del anterior. También depende de su nivel de competencia y capacidades, por lo que podría ser más razonable e interesante empezar con publicaciones más recientes.
El archivo del código fuente está adjunto al artículo.