English Русский 中文 Deutsch 日本語 Português
Utilidad para la selección y navegación en MQL5 y MQL4: añadiendo las pestañas de "recordatorios" y guardando objetos gráficos

Utilidad para la selección y navegación en MQL5 y MQL4: añadiendo las pestañas de "recordatorios" y guardando objetos gráficos

MetaTrader 5Estadística y análisis | 3 abril 2019, 16:27
977 0
Roman Klymenko
Roman Klymenko

Introducción

En el artículo anterior creamos una utilidad para filtrar y seleccionar instrumentos con el punto de entrada conveniente. Aprendimos a filtrar los instrumentos en función de los parámetros más diversos, y también a navegar por los instrumentos con la ayuda de botones especialmente creados al efecto. Pero, por el momento, la selección de instrumentos no marcha muy bien. Tenemos que registrar los activos de los instrumentos seleccionados en un papel, lo cual contribuirá enormemente a la deforestación de nuestro planeta.

En este artículo, salvaremos los árboles de su exterminio. Asimismo, aprenderemos a guardar automáticamente los objetos gráficos creados en el gráfico, para no tener que crearlos posteriormente una y otra vez.

Usando las posibilidades de la compilación condicional

En primer lugar, vamos a simplificar el futuro funcionamiento de la portabilidad de la utilidad al lenguaje MQL4. En el artículo anterior, ya sustituimos un bloque por otro, para que el programa comenzara a funcionar en el lenguaje MQL4. Ahora, nos encontramos ante una tarea complicada. Podemos, o bien ejecutar el desarrollo en algún lenguaje, por ejemplo MQL5, y después cambiar constantemente los bloques de código que no funcionan en MQL4 por los necesarios. O bien desarrollar paralelamente dos programas: tanto en el lenguaje MQL5, como en el lenguaje MQL4.

Ninguna de las dos opciones es la óptima. Necesitamos, o bien ejecutar constantemente (en cada versión) el mismo trabajo de sustitución de bloques que no funcionen en MQL4, o bien mantener en la cabeza aquellas partes del código que hemos cambiado, para introducir dichos cambios en la utilidad en el otro lenguaje.

Por eso, vamos a ir por otro camino. Tanto MQL5, como MQL4, dan soporte a directivas de compilación condicional que permiten, dependiendo de las condiciones, ejecutar bien un bloque de código, bien otro. Entre estas directivas, existe una construcción que se ejecuta dependiendo de la versión actual del lenguaje MQL. Su sintaxis principal es la siguiente:

      #ifdef __MQL5__ 
         // bloque de código que se ejecutará solo en MQL5
      #else 
         // bloque de código que se ejecutará solo en MQL4
      #endif 

Vamos a usarlo, para que nuestra función checkSYMBwithPOS funcione correctamente tanto el lenguaje MQL5, como en el lenguaje MQL4, sin que haya necesidad de realizar sustituciones constantes:

bool checkSYMBwithPOS(string name){
   // Ocultar los símbolos de los que hay posiciones u órdenes
   bool isskip=false;
   if( noSYMBwithPOS ){
      #ifdef __MQL5__ 
         // miramos la lista de todas las posiciones abiertas
         int cntMyPos=PositionsTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            // si hay posiciones del símbolo actual, omitimos
            if(PositionGetSymbol(ti) == name ){
               isskip=true;
               break;
            }
         }
         if(!isskip){
            int cntMyPosO=OrdersTotal();
            if(cntMyPosO>0){
               for(int ti=cntMyPosO-1; ti>=0; ti--){
                  ulong orderTicket=OrderGetTicket(ti);
                  if( OrderGetString(ORDER_SYMBOL) == name ){
                     isskip=true;
                     break;
                  }
               }
            }
         }
      #else 
         // miramos la lista de todas las posiciones abiertas
         int cntMyPos=OrdersTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if(OrderSymbol() == name ){
               isskip=true;
               break;
            }
         }
      #endif 
   }
   return isskip;
}

A continuación, portaremos directamente los bloques que no funcionen en MQL4 con la ayuda de esta construcción.

Pestañas para los recordatorios

Para salvar los bosques de nuestro planeta, hemos creado 3 pestañas en las que se representarán solo aquellos instrumentos que hemos seleccionado preliminarmente entre otros. Llamaremos estas pestañas: Long, Short y Range. Claro está que no es obligatorio añadir a ellas solo aquellos instrumentos que van hacia arriba, hacia abajo o se desplazan lateralmente. Puede usted utilizarlas según su propio criterio.

En total, en el gráfico donde estará iniciada nuestra utilidad, aparecerá una fila más con 4 botones: los botones All y los tres descritos anteriormente.

Por defecto, estará pulsado el botón All, esto significa que más abajo se representará la lista con todos los instrumentos que cumplen con nuestros filtros:

Añadiendo las pestañas para la selección de los recordatorios

Bien, ya hemos planteado la tarea. Solo queda implementarla. Para ello, deberemos reescribir una pequeña parte de nuestra utilidad.

Matrices para guardar el contenido de las pestañas. En primer lugar, añadiremos las variables para guardar el contenido de nuestras pestañas. Antes teníamos solo una pestaña y su contenido se guardaba en la variable arrPanel1. Vamos a añadir variables análogas para las otras pestañas:

// matrices de los símbolos que se muestran en la pestaña correspondiente:
CArrayString arrPanel1;
CArrayString arrPanel2;
CArrayString arrPanel3;
CArrayString arrPanel4;

Además, para tener la posibilidad recurrir a las pestañas en el ciclo, crearemos una matriz más. En ella se guardarán los punteros a las cuatro matrices creadas anteriormente:

// matriz para la combinación de todas las pestañas
CArrayString *arrPanels[4];

Y en la propia función OnInit(), inicializamos esta matriz:

   arrPanels[0]=&arrPanel1;
   arrPanels[1]=&arrPanel2;
   arrPanels[2]=&arrPanel3;
   arrPanels[3]=&arrPanel4;

Encabezados de las pestañas. Puesto que el trabajo con las pestañas tendrá lugar en el ciclo, lo ideal sería guardar también los nombres de las pestañas en la matriz, y recurrir a la misma desde el ciclo. Así que también vamos a crear una matriz con los nombres de nuestras pestañas:

// Matriz con los nombres de las pestañas
string panelNames[4]={"All", "LONG", "SHORT", "Range"};

Variables auxiliares. Otro cambio más está relacionado con la variable panel1val. Hemos cambiado su nombre a panelval. Se trata de un cambio puramente cosmético. Pero es imprescindible mencionarlo.

Asimismo, hemos añadido la variable cur_panel, que contiene el índice de la pestaña activa en este momento. El tipo de esta variable es uchar. Es decir, puede adquirir un valor de 0 a 255, lo cual es de sobra suficiente, ya que solo tenemos 4 pestañas.

Por defecto, está activa la primera pestaña, es decir, la pestaña con el índice 0 en la matriz. Por eso, añadimos en la función OnInit() una línea que asigne a esta variable el valor 0. Con ello, la función OnInit() adquirirá su aspecto definitivo:

int OnInit()
  {
   // índice de la pestaña activa en este momento
   cur_panel=0;
   // inicializamos la matriz de pestañas
   arrPanels[0]=&arrPanel1;
   arrPanels[1]=&arrPanel2;
   arrPanels[2]=&arrPanel3;
   arrPanels[3]=&arrPanel4;
   
   start_symbols();

//--- create timer
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }

Otros cambios. Si enumeramos ahora todo lo que ha sido modificado, a cualquier lector le resultará aburrido. Por eso, vamos a pasar a los cambios principales. Podrá detectar por sí mismo los detalles menos relevantes, si así lo desea, comparando los códigos fuente de la utilidad con aquel que se adjuntó al artículo anterior.

Merece la pena mencionar entre los cambios principales aquellos relacionados con la muestra de nuestras nuevas transacciones. Hemos decidido trabajar con las pestañas en el ciclo. Veamos cómo sucederá esto.

Puesto que ahora tenemos una línea con las pestañas, debemos mostrarla de alguna forma. Para ello, crearemos una función aparte. Su código es el siguiente:

void show_panel_buttons(){
   int btn_left=0;
   // determinamos la coordenada máxima posible en el eje x en la que se puede mostrar la pestaña.
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   string tmpName="";
   
   for( int i=0; i<ArraySize(panelNames); i++ ){
      // si la coordenada del comienzo del nuevo botón es mayor a la máxima posible,
      // pasaremos a la línea siguiente.
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      // si en la pestaña "recordatorios" hay símbolos, al nombre de la pestaña
      // se le añade su cantidad
      tmpName=panelNames[i];
      if(i>0 && arrPanels[i].Total()>0 ){
         tmpName+=" ("+(string) arrPanels[i].Total()+")";
      }
      
      // mostramos los botones de las pestañas
      ObjectCreate(0, exprefix+"panels"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetString(0,exprefix+"panels"+(string) i,OBJPROP_TEXT,tmpName);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_SELECTABLE,false);
      // si en este momento está activa la pestaña de este botón,
      // la hacemos pulsada
      if( cur_panel == i ){
         ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_STATE, true);
      }
      
      btn_left+=BTN_WIDTH;
   }

}

Llamaremos esta función en la función start_symbols, antes de llamar la función show_symbols, que muestra los botones de los símbolos.

La propia función show_symbols también ha cambiado, pero no de forma sustancial. Ahora mostramos en el gráfico solo los botones de los símbolos que se encuentran en la pestaña activa en este momento:

   // para cada símbolo en la matriz de la pestaña activa en este momento
   // mostramos el botón en el gráfico
   // en el botón escribiremos el nombre del símbolo
   for( int i=0; i<arrPanels[cur_panel].Total(); i++ ){
      
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      
      ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,arrPanels[cur_panel].At(i));    
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);

      if( !noSYMBwithPOS || cur_panel>0 ){
         if( checkSYMBwithPOS(arrPanels[cur_panel].At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
      }
      
      btn_left+=BTN_WIDTH;
   }

Botones para añadir símbolos a las pestañas. Ya nos hemos aclarado con la muestra de las pestañas. Ahora debemos añadir de alguna forma los símbolos a la pestaña elegida. Para hacerlo, utilizaremos los nuevos botones Add LONG, Add SHORT, Add Range en la página del gráfico abierto.

Si recuerda, en el artículo anterior implementamos la posibilidad de clicar en el botón del símbolo necesario. Y después del ciclo por el símbolo se abre el gráfico, en cuya esquina inferior izquierda se muestra el bloque de botones de navegación por la lista de todos los símbolos. Añadiremos nuestros botones a este bloque. Dependiendo de si este símbolo existe en la pestaña correspondiente, el botón o bien lo añadirá a la pestaña, o bien lo eliminará de la misma.

Este bloque de botones se muestra con la ayuda de la función createBTNS. Añadimos en ella el ciclo de muestra de nuevos botones:

   for( int i=ArraySize(panelNames)-1; i>0; i-- ){
      isyes=false;
      if(arrPanels[i].Total()){
         for(int j=0; j<arrPanels[i].Total(); j++){
            if( arrPanels[i].At(j)==name ){
               isyes=true;
               break;
            }
         }
      }
      if( isyes ){
         ObjectCreate(CID, exprefix+"_p_btn_panelfrom"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_XDISTANCE,110); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_YDISTANCE,tmpHeight);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_CORNER,CORNER_LEFT_LOWER); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_SELECTABLE,false); 
         ObjectSetString(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_TEXT,"Del "+panelNames[i]);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_BGCOLOR,clrPink); 
      }else{
         ObjectCreate(CID, exprefix+"_p_btn_panelto"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_XDISTANCE,110); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_YDISTANCE,tmpHeight);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_CORNER,CORNER_LEFT_LOWER); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_SELECTABLE,false); 
         ObjectSetString(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_TEXT,"Add "+panelNames[i]);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_BGCOLOR,clrHoneydew); 
      }
      tmpHeight+=25;
   }

Bloque de botones de navegación

Para que la pulsación en los nuevos botones funcione, añadimos a la función OnTimer() el código de comprobación del estado de los datos de los botones. Todo funciona de forma análoga a lo que hicimos en el artículo anterior:

            for( uchar i=1; i<ArraySize(panelNames); i++ ){
               // si está pulsado el botón de eliminación de la pestaña, eliminamos primero el símbolo, y después abrimos el gráfico de este símbolo
               if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_STATE)==true ){
                  delToPanel(i, ChartSymbol(curChartID[tmpCIDcnt-1]));
                  curchart();
                  return;
               }
               // si está pulsado el botón de adición a la pestaña, primero añadimos el símbolo, y después abrimos el gráfico del siguiente símbolo
               if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_panelto"+(string) i,OBJPROP_STATE)==true ){
                  addToPanel(i, ChartSymbol(curChartID[tmpCIDcnt-1]));
                  nextchart();
                  return;
               }
            }

La función delToPanel primero elimina el símbolo de la pestaña elegida, y después actualiza o bien todos los botones de los símbolos en el gráfico donde está iniciada nuestra utilidad, o bien solo los botones de los encabezados:

void delToPanel(uchar num, string name){
   // iteramos por toda la matriz y eliminamos el primer elemento
   // cuyo nombre sea análogo a nuestro símbolo
   for(int i=0; i<arrPanels[num].Total(); i++){
      if( arrPanels[num].At(i)==name ){
         arrPanels[num].Delete(i);
         break;
      }
   }
   // si en este momento está abierta dicha pestaña,
   if(num==cur_panel){
      initial_btn_line();
      // eliminamos del gráfico los botones de los símbolos creados anteriormente:
      ObjectsDeleteAll(0, exprefix);
      // mostramos la lista actualizada de símbolos:
      show_panel_buttons();
      show_symbols();
   }else{
      // si está abierta otra pestaña cualquiera, entonces solo actualizamos los botones del encabezado
      upd_panel_title();
   }
   
   
}

La función addToPanel es opuesta a la que acabamos de ver. Actualiza el símbolo en la pestaña. Pero, aparte de la adición, también comprueba si este símbolo se encuentra en otras pestañas. Si existe, el símbolo es eliminado de allí:

void addToPanel(uchar num, string name){
   // añadimos el símbolo a la pestaña
   arrPanels[num].Add(name);
   // eliminamos el símbolo de las otras pestañas,
   // si está allí
   for( int j=1; j<ArraySize(arrPanels); j++ ){
      if(j==num) continue;
      for(int i=0; i<arrPanels[j].Total(); i++){
         if( arrPanels[j].At(i)==name ){
            if( panelval==i && i>0 ){
               panelval--;
            }
            arrPanels[j].Delete(i);
            break;
         }
      }
   }
   if(num==cur_panel){
      initial_btn_line();
      // eliminamos del gráfico los botones de los símbolos creados anteriormente:
      ObjectsDeleteAll(0, exprefix);
      // mostramos la lista de símbolos:
      show_panel_buttons();
      show_symbols();
   }else{
      upd_panel_title();
   }
}

Guardamos el contenido de las pestañas entre los inicios de la utilidad. Claro que está muy bien añadir los símbolos a las pestañas. Pero, ¿qué hacemos si cerramos el asesor por casualidad? Entonces, todos nuestros esfuerzos caerán en saco roto. ¿Y qué hacemos, añadirlo todo de nuevo? Vamos a hacer que las listas de símbolos que añadimos a las pestañas de los recordatorios se guarden en un archivo, para luego restaurarse en aperturas sucesivas.

No en vano hemos utilizado los objetos del tipo CArrayString para guardar las listas de los símbolos seleccionados. Una de las múltiples ventajas de los objetos de este tipo es la presencia de métodos estándar que permiten fácilmente tanto trasladar todo el contenido de la matriz a un archivo, como restaurar una matriz desde un archivo. Después los usaremos para guardar el contenido de las matrices en un archivo antes de cerrar la utilidad. Es decir, añadimos a la función estándar OnDeinit() la llamada de nuestra nueva función savePanels:

void savePanels(){
   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Save(fh);
         FileClose(fh);
      }
   }
}

Por consiguiente, restauraremos el contenido de las matrices en la función estándar OnInit():

   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Load(fh);
         FileClose(fh); 
      }
   }

Añadiendo un encabezado para identificar los parámetros actuales

Si usted comercia en diferentes mercados, necesitará ajustar constantemente la utilidad de un mercado a otro. Y es que, si usted ahora tiene la intención de probar a comerciar en el mercado de valores americano en la primera hora u hora y media de apertura del mercado, ¿para qué necesita las acciones del mercado europeo o ruso, que ya hace mucho que han abierto? Además, al comerciar en el mercado ruso, usted no necesita en absoluto las acciones del americano, o las del europeo, etc.

Para no confundirse y tener delante solo aquellos instrumentos con los que usted quiere trabajar en este momento, lo mejor es crear conjuntos de parámetros aparte para los mercados de distintos países y el mercado Fórex. Y cargar solo el archivo set necesario, dependiendo de las necesidades actuales. Esto no supone ninguna complicación, y le ocupará solo unos segundos. Otra cosa es comprender en este caso qué ajustes están cargados en este momento, y eso es un poco complicado.

Para ver directamente qué conjunto de parámetros está cargado, vamos a añadir el parámetro de entrada cmt, en el que anotaremos aclaraciones sobre qué mercado estamos usando en este momento:

input string         cmt=""; //Parámetros para (eng)

Mostraremos este comentario en la línea con los botones de nuestras pestañas:

Mostrando el encabezamiento del conjunto actual

Para ello, basta con añadir a la función show_panel_buttons, el bloque de código siguiente tras la muestra de todos los botones:

   // mostramos el comentario, si ha sido especificado:
   if(StringLen(cmt)>0){
      string tmpCMT=cmt;
      ObjectCreate(0, exprefix+"title", OBJ_EDIT, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XDISTANCE,btn_left+11); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XSIZE,133); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_COLOR,clrGold); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BGCOLOR,clrNONE); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BORDER_COLOR,clrBlack);
      ObjectSetString(0,exprefix+"title",OBJPROP_TEXT,tmpCMT);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_SELECTABLE,false);
   }

Aparte de identificar el conjunto de ajustes actual, el parámetro de entrada cmt nos ayudará a separar las listas de los símbolos que han entrado en las pestañas de los recordatorios. ¿No le parece a usted que si añadimos el símbolo a la pestaña de recordatorios para trabajar en el mercado de valores americano, no necesitaremos en absoluto este símbolo al trabajar con el mercado de valores ruso? Los archivos set con diferentes conjuntos de parámetros deben estar en distintas listas para las pestañas de los recordatorios.

Para hacer esto, deberemos modificar un poco el código que guarda las matrices en un archivo, y restaura desde el mismo. Como ejemplo, analizaremos la versión modificada de la función de guardado en archivos:

void savePanels(){
   string tmpCmt=cmt;
   StringReplace(tmpCmt, " ", "_");
   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+tmpCmt+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Save(fh);
         FileClose(fh);
      }
   }
}

Guardando los objetos gráficos

Otro problema que debemos resolver para trabajar con gráficos normalmente es el guardado y restauración automáticos de los objetos gráficos que hemos creado en el gráfico. Y es que si hemos trazado un nivel en el gráfico, es lógico esperar que tras el cierre de la ventana del gráfico y su nueva apertura veamos el nivel creado anteriormente. No podemos dedicarnos a trazar decenas de niveles cada vez que abramos un gráfico de algún instrumento.

El código que hemos escrito hasta ahora ha funcinado tanto para MQL5, como para MQL4. Pero las funciones que guardan y restauran los objetos gráficos nos van a dar problemas. Mientras que en MQL4 el tipo de objeto gráfico y sus propiedades aparte se han descrito como constantes de tipo numérico, en MQL5 se han usado para ello enumeraciones (tipo enum). Por este motivo, ha resultado demasiado problemático guardarlas en un archivo y restaurarlas desde allí. Por lo menos, el autor no ha logrado implementar esta tarea en general. Hablando de lo esencial, en nuestro caso, la funcionalidad del guardado de los objetos gráficos para MQL4 será más funcional, si se puede decir así. En teoría, puede guardar cualquier objeto gráfico (no lo hemos puesto a prueba en la práctica con todos los objetos, así que podría haber excepciones). Pero la funcionalidad para MQL5 podrá trabajar solo con líneas horizontales, marcas y campos de texto. Si usted requiere la posibilidad de guardar otros objetos gráficos, deberá implementarla por su propia cuenta.

MQL4. Puesto que para MQL4 el código de guardado de los objetos gráficos es más sencillo, vamos a comenzar precisamente con las funciones para este lenguaje. Bien, la función de guardado de objetos gráficos en un archivo:

   void savechart(ulong id){
      // guardamos los objetos gráficos solo si el parámetro de entrada 
      // "Guardar los objetos gráficos  creados" = true
      if(saveGraphics){
         // obtenemos el nombre del símbolo
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         // limpiamos la matriz de objetos gráficos
         saveG.Resize(0);
         
         // añadimos a la matriz todos los objetos gráficos creados por el usuario, así como las propiedades de estos objetos
         int obj_total=ObjectsTotal((long) id); 
         string name;
         string tmpObjLine="";
         for(int i=0;i<obj_total;i++){
            name = ObjectName((long) id, i);
            if( StringFind(name, exprefix)<0 && StringFind(name, "fix")<0 && StringFind(name, "take")<0 && StringFind(name, "stop loss")<0 && StringFind(name, "sell")<0 && StringFind(name, "buy")<0 ){
               tmpObjLine=name;
               
               StringAdd(tmpObjLine, "|int~OBJPROP_TYPE~"+(string)(int) OBJPROP_TYPE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TYPE));
               StringAdd(tmpObjLine, "|int~OBJPROP_COLOR~"+(string)(int) OBJPROP_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_COLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_STYLE~"+(string)(int) OBJPROP_STYLE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STYLE));
               StringAdd(tmpObjLine, "|int~OBJPROP_WIDTH~"+(string)(int) OBJPROP_WIDTH+"~"+(string) ObjectGetInteger(id, name, OBJPROP_WIDTH));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIME~"+(string)(int) OBJPROP_TIME+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIME));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIMEFRAMES~"+(string)(int) OBJPROP_TIMEFRAMES+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
               StringAdd(tmpObjLine, "|int~OBJPROP_ANCHOR~"+(string)(int) OBJPROP_ANCHOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_XDISTANCE~"+(string)(int) OBJPROP_XDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YDISTANCE~"+(string)(int) OBJPROP_YDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_STATE~"+(string)(int) OBJPROP_STATE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STATE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XSIZE~"+(string)(int) OBJPROP_XSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YSIZE~"+(string)(int) OBJPROP_YSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XOFFSET~"+(string)(int) OBJPROP_XOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_YOFFSET~"+(string)(int) OBJPROP_YOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_BGCOLOR~"+(string)(int) OBJPROP_BGCOLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_BORDER_COLOR~"+(string)(int) OBJPROP_BORDER_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));
               StringAdd(tmpObjLine, "|double~OBJPROP_PRICE~"+(string)(int) OBJPROP_PRICE+"~"+(string) ObjectGetDouble(id, name, OBJPROP_PRICE));
               StringAdd(tmpObjLine, "|string~OBJPROP_TEXT~"+(string)(int) OBJPROP_TEXT+"~"+(string) ObjectGetString(id, name, OBJPROP_TEXT));
               
               saveG.Add(tmpObjLine);
            }
         }
         // guardamos el contenido de la matriz en un archivo
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Save(fh);
            FileClose(fh);
         }
      }
   }

Como podemos notar, no guardamos todas las propiedades del objeto, sino solo OBJPROP_COLOR, OBJPROP_STYLE, OBJPROP_WIDTH, OBJPROP_TIME, OBJPROP_TIMEFRAMES, OBJPROP_ANCHOR, OBJPROP_XDISTANCE, OBJPROP_YDISTANCE, OBJPROP_STATE, OBJPROP_XSIZE, OBJPROP_YSIZE, OBJPROP_XOFFSET, OBJPROP_YOFFSET, OBJPROP_BGCOLOR, OBJPROP_BORDER_COLOR, OBJPROP_PRICE, OBJPROP_TEXT. Si al trabajar con la utilidad, alguno de los objetos gráficos usados por usted se guarda incorrectamente, significará que no todas las propiedades utilizadas por él han sido guardadas. En este caso, bastará con que usted añada a esta función el guardado de las propiedades que faltan, y este tipo de objetos gráficos también tendrá soporte.

Ahora, vamos a ver la función que carga los objetos gráficos desde el archivo y los muestra en el gráfico:

   void loadchart(ulong id){
      // representamos los objetos gráficos solo si el parámetro de entrada 
      // "Guardar los objetos gráficos  creados" = true
      if(saveGraphics){
         // obtenemos el nombre del símbolo
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         string tmpObjLine[];
         string tmpObjName="";
         string sep1="|";
         string sep2="~";
         
         // limpiamos la matriz de objetos gráficos
         saveG.Resize(0);
         // cargamos la matriz de las listas de objetos gráficos desde el archivo
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Load(fh);
            FileClose(fh); 
         }
         // mostramos secuencialmente en el gráfico los objetos gráficos
         for( int i=0; i<saveG.Total(); i++ ){
            StringSplit(saveG.At(i), StringGetCharacter(sep1,0), tmpObjLine);
            for( int j=0; j<ArraySize(tmpObjLine); j++ ){
               if(j>0){
                  string tmpObjSubLine[];
                  StringSplit(tmpObjLine[j], StringGetCharacter(sep2,0), tmpObjSubLine);
                  if(ArraySize(tmpObjSubLine)==4){
                     if(tmpObjSubLine[0]=="int"){
                        // en la línea, el tipo de objeto va siempre en primer lugar
                        // así que crearemos siempre en primer lugar el objeto, y ya después formaremos sus propiedades
                        if(tmpObjSubLine[1]=="OBJPROP_TYPE"){
                           ObjectCreate(id, tmpObjName, (int) tmpObjSubLine[3], 0, 0, 0);
                        }else if( (int) tmpObjSubLine[3] >= 0 ){
                           ObjectSetInteger(id, tmpObjName, (int) tmpObjSubLine[2], (int) tmpObjSubLine[3]);
                        }
                     }else if(tmpObjSubLine[0]=="double"){
                        if( (double) tmpObjSubLine[3] >= 0 ){
                           ObjectSetDouble(id, tmpObjName, (int) tmpObjSubLine[2], (double) tmpObjSubLine[3]);
                        }
                     }else if(tmpObjSubLine[0]=="string"){
                        if( StringLen(tmpObjSubLine[3]) > 0 ){
                           ObjectSetString(id, tmpObjName, (int) tmpObjSubLine[2], tmpObjSubLine[3]);
                        }
                     }
                  }
               }else{
                  tmpObjName=tmpObjLine[j];
               }
            }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }
         
         
      }
   }

MQL5. Como ya hemos dicho, para MQL5, estas funciones se diferencian en el mal sentido:

   void savechart(ulong id){
      if(saveGraphics){
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         saveG.Resize(0);
         
         int obj_total=ObjectsTotal((long) id); 
         string name;
         string tmpObjLine="";
         for(int i=0;i<obj_total;i++){
            name = ObjectName((long) id, i);
            if( StringFind(name, exprefix)<0 && StringFind(name, "fix")<0 && StringFind(name, "take")<0 && StringFind(name, "stop loss")<0 && StringFind(name, "sell")<0 && StringFind(name, "buy")<0 ){
               tmpObjLine=name;
               // ya sabemos trabajar con los objetos del tipo OBJ_HLINE, OBJ_TEXT y OBJ_LABEL,
               //por eso, omitimos los objetos de otros tipos
               if( ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_HLINE && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_TEXT && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_LABEL ){
                  continue;
               }
               StringAdd(tmpObjLine, "|int~OBJPROP_TYPE~"+(string)(int) OBJPROP_TYPE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TYPE));
               StringAdd(tmpObjLine, "|int~OBJPROP_COLOR~"+(string)(int) OBJPROP_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_COLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_STYLE~"+(string)(int) OBJPROP_STYLE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STYLE));
               StringAdd(tmpObjLine, "|int~OBJPROP_WIDTH~"+(string)(int) OBJPROP_WIDTH+"~"+(string) ObjectGetInteger(id, name, OBJPROP_WIDTH));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIME~"+(string)(int) OBJPROP_TIME+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIME));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIMEFRAMES~"+(string)(int) OBJPROP_TIMEFRAMES+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
               StringAdd(tmpObjLine, "|int~OBJPROP_ANCHOR~"+(string)(int) OBJPROP_ANCHOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_XDISTANCE~"+(string)(int) OBJPROP_XDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YDISTANCE~"+(string)(int) OBJPROP_YDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_STATE~"+(string)(int) OBJPROP_STATE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STATE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XSIZE~"+(string)(int) OBJPROP_XSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YSIZE~"+(string)(int) OBJPROP_YSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XOFFSET~"+(string)(int) OBJPROP_XOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_YOFFSET~"+(string)(int) OBJPROP_YOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_BGCOLOR~"+(string)(int) OBJPROP_BGCOLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_BORDER_COLOR~"+(string)(int) OBJPROP_BORDER_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));
               StringAdd(tmpObjLine, "|double~OBJPROP_PRICE~"+(string)(int) OBJPROP_PRICE+"~"+(string) ObjectGetDouble(id, name, OBJPROP_PRICE));
               StringAdd(tmpObjLine, "|string~OBJPROP_TEXT~"+(string)(int) OBJPROP_TEXT+"~"+(string) ObjectGetString(id, name, OBJPROP_TEXT));
               
               saveG.Add(tmpObjLine);
            }
         }
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Save(fh);
            FileClose(fh);
         }
      }
   }
   void loadchart(ulong id){
      if(saveGraphics){
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         string tmpObjLine[];
         string tmpObjName="";
         string sep1="|";
         string sep2="~";
         
         saveG.Resize(0);
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Load(fh);
            FileClose(fh); 
         }
         for( int i=0; i<saveG.Total(); i++ ){
            StringSplit(saveG.At(i), StringGetCharacter(sep1,0), tmpObjLine);
            for( int j=0; j<ArraySize(tmpObjLine); j++ ){
               if(j>0){
                  string tmpObjSubLine[];
                  StringSplit(tmpObjLine[j], StringGetCharacter(sep2,0), tmpObjSubLine);
                  if(ArraySize(tmpObjSubLine)==4){
                     if(tmpObjSubLine[0]=="int"){
                        // creamos el objeto dependiendo de su tipo
                        if(tmpObjSubLine[1]=="OBJPROP_TYPE"){
                           switch((int) tmpObjSubLine[3]){
                              case 1:
                                 ObjectCreate(id, tmpObjName, OBJ_HLINE, 0, 0, 0);
                                 break;
                              case 101:
                                 ObjectCreate(id, tmpObjName, OBJ_TEXT, 0, 0, 0);
                                 break;
                              case 102:
                                 ObjectCreate(id, tmpObjName, OBJ_LABEL, 0, 0, 0);
                                 break;
                           }
                        }else if( (int) tmpObjSubLine[3] >= 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_COLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_COLOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_STYLE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_STYLE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_WIDTH"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_WIDTH, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_TIME"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_TIME, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_TIMEFRAMES"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_TIMEFRAMES, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_ANCHOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_ANCHOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XDISTANCE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XDISTANCE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YDISTANCE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YDISTANCE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_STATE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_STATE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XSIZE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XSIZE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YSIZE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YSIZE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XOFFSET"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XOFFSET, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YOFFSET"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YOFFSET, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_BGCOLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_BGCOLOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_BORDER_COLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_BORDER_COLOR, (int) tmpObjSubLine[3]);
                           }
                        }
                     }else if(tmpObjSubLine[0]=="double"){
                        if( (double) tmpObjSubLine[3] >= 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_PRICE"){
                              ObjectSetDouble(id, tmpObjName, OBJPROP_PRICE, (double) tmpObjSubLine[3]);
                           }
                        }
                     }else if(tmpObjSubLine[0]=="string"){
                        if( StringLen(tmpObjSubLine[3]) > 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_TEXT"){
                              ObjectSetString(id, tmpObjName, OBJPROP_TEXT, tmpObjSubLine[3]);
                           }
                        }
                     }
                  }
               }else{
                  tmpObjName=tmpObjLine[j];
               }
            }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }
         
         
      }
   }

Si miramos atentamente, podremos notar que debemos usar una línea aparte de creación de objetos para los objetos de diferentes tipos, mientras que en MQL4, basta con una línea para todos los objetos. Lo mismo sucede con las propiedades del objeto. En MQL4, hemos utilizado una línea de creación de propiedad dependiendo de su tipo (de línea, real o entera). En cambio, en MQL5, cada propiedad necesita una línea aparte para su creación.

Combinando los lenguajes. Vamos a usar la compilación condicional para que el asesor use la versión necesaria dependiendo de la versión del lenguaje:

#ifdef __MQL5__ 
   void savechart(ulong id){
      // función para MQL5
   }
   void loadchart(ulong id){
      // ...
   }
#else 
   void savechart(ulong id){
      // función para MQL4
   }
   void loadchart(ulong id){
      // ...
   }
#endif 

Usando las funciones. Ahora vamos a añadir a las partes correspondientes del programa la llamada de estas funciones.

Añadiremos la llamada de loadchart dentro de la función showcharts, que abre el gráfico cuyo botón hemos pulsado.

La llamada de la función de guardado del gráfico se debe añadir a los bloques de código de reacción a la pulsación de los botones de navegación por el gráfico: Next chart, Prev chart, Close chart y de los botones de adición/eliminación del símbolo de las pestañas de deberes.

Usaremos el sitio web finviz.com para la selección preliminar de acciones

En el artículo anterior se mencionaba que la lista de símbolos para el filtrado se toma, no solo de la lista de todos los símbolos disponibles del bróker, sino también del parámetro de entrada. En primer lugar, para mostrar solo el conjunto de símbolos limitado que usted necesita, en el orden necesario. Y en segundo lugar, el conjunto de símbolos propio se puede usar para el filtrado provisional en el sitio web finviz.com u otro semejante.

Claro que en el artículo anterior creamos un conjunto de ciertos parámetros de entrada que permitían filtrar los símbolos por precio, ATR, etc. Pero las posibilidades que hemos implementado no soportan comparación alguna con el filtro del sitio web finviz.com. Y, lo más importante, en MQL4 no hay posibilidad de filtrar los instrumentos según el volumen real. Y en muchas estrategias comerciales basadas en los niveles esto es muy importante. En el propio sitio web finviz.com, podemos filtrar tanto el volumen medio de la acción, como el volumen comerciado de la acción en el día actual.

Añadiendo la posibilidad de obtener una lista de símbolos a partir del parámetro de entrada. Para usar una lista de símbolos tomada de sitios web de terceros, vamos a añadir a nuestra utilidad 3 parámetros de entrada adicionales:

input string         ""; // Solo símbolos (separador- ; o espacio)
input string         ""; // Añadir un prefijo a los símbolos
input string         ""; // Añadir un sufijo a los símbolos

Necesitamos los parámetros onlySymbolsPrefix y onlySymbolsSuffix en el caso de que los nombres de los instrumentos de su bróker se distingan de los códigos oficiales de los activos financieros. Esto sucede con muchos brókeres. Hay quien añade al final del nombre del activo el sufijo .us para las acciones del mercado americano, y .eu para las acciones europeas. Hay quien añade el sufijo m a los códigos de cualquier activo. Otros brókeres añaden el símbolo # al inicio del código de activos de las acciones.

Añadiendo la posibilidad de importar símbolos desde un archivo. Adelantándonos un poco, diremos que la importación de símbolos desde el parámetro de entrada supone un problema. Y este problema se relaciona con la longitud máxima de esta línea. Al usar el parámetro de entrada, estaremos limitados a un máximo de 15-20 códigos de activos. No podremos pegar ninguno más allí. Por eso, el parámetro de entrada se puede utilizar solo para limitar los instrumentos de trabajo con un pequeño número de símbolos.

Aparte del parámetro de entrada, vamos a añadir también la posibilidad de importar símbolos del archivo symbols.txt, que crearemos en la carpeta Files. Más concretamente, el que usted creará si así lo desea, colocando allí los símbolos necesarios.

Implementación en el código. Vamos a dividir el proceso de formación de la lista de símbolos para la pestaña All en dos bloques.

El primer bloque comprobará si hay símbolos en el archivo o parámetro de entrada, y si los hay, rellenará con ellos la matriz result. Añadimos la matriz a la función OnInit():

         // si en el parámetro de entrada "Solamente símbolos (separador - ; o espacio)"
         // hay algunos datos
         if( StringLen(onlySymbols)>0 ){
            // dividimos la línea del parámetro de entrada en los elementos de la matriz
            // el separador de los elementos será el símbolo ;
            StringSplit(onlySymbols,StringGetCharacter(";",0),result); 
            if( ArraySize(result)>1 ){
            }else{
               // si, como resultado de la división, se contiene solo un valor en la matriz,
               // significa que no hemos logrado la división, y por lo visto el separador en la línea es un espacio
               // por eso, ahora dividimos la línea del parámetro de entrada en los elementos de la matriz
               // y el separador de los elementos será el espacio
               StringSplit(onlySymbols,StringGetCharacter(" ",0),result); 
            }
         // de lo contrario, comprobamos si existe en la carpeta Files el archivo symbols.txt
         }else if( FileIsExist("symbols.txt") ){
            // si el archivo existe, ubicamos su contenido en la variable temporal outfile
            int filehandle=FileOpen("symbols.txt",FILE_READ|FILE_TXT); 
            if(filehandle>=0){
               string outfile=FileReadString(filehandle);
               // si la variable outfile contine alguna línea,
               // tratamos de dividirla en los elementos de la matriz,
               // usando primero para ello el separador ;, y después el espacio
               if(StringLen(outfile)>0){
                  StringSplit(outfile,StringGetCharacter(";",0),result); 
                  if( ArraySize(result)>1 ){
                  }else{
                     StringSplit(outfile,StringGetCharacter(" ",0),result); 
                  }
                  if( ArraySize(result)>1 ){
                     from_txt=true;
                  }
               }
               FileClose(filehandle);
            }
         }

Y en la función prepare_symbols, primero comprobamos si en la matriz result se contienen algunos datos, y de ser así, los usamos. En caso contrario, para el posterior filtrado, usaremos o bien todos los símbolos que ofrece el bróker, o bien solo aquellos que han sido añadidos al panel Observación del mercado:

   // si en la matriz hay más de dos símbolos, los usamos,
   // añadiendo preliminarmente a los mismos el sufijo o prefijo requerido, en caso necesario
   if( ArraySize(result)>1 ){
      for(int j=0;j<ArraySize(result);j++){
         StringReplace(result[j], " ", "");
         if(StringLen(result[j])<1){
            continue;
         }
         tmpSymbols.Add(onlySymbolsPrefix+result[j]+onlySymbolsSuffix);
      }
   // de lo contrario, usamos todos los símbolos ofrecidos por el bróker
   }else{
      for( int i=0; i<SymbolsTotal(noSYMBmarketWath); i++ ){
         tmpSymbols.Add(SymbolName(i, noSYMBmarketWath));
      }
   }

Formando una lista de símbolos con la ayuda del filtro de finviz.com. Finalmente, vamos a ver cómo importar a nuestra utilidad los filtros seleccionados en el sitio web finviz.com.

En realidad, es muy sencillo. Basta con entrar en la pestaña Tickers de la página Screener después del filtrado. Como resultado, se mostrará ante usted una nube con los nombres de los activos a seleccionar. Los seleccionamos todos, los copiamos y los pegamos en el archivo symbols.txt, o bien en el parámetro de entrada. Si hay más de una página con los resultados del filtrado, pasamos a la siguiente y hacemos lo mismo.

Conclusión

Hoy hemos realizado un gran volumen de trabajo. Y la funcionalidad de nuestra utilidad es ahora mayor. Ahora podemos usarla sin ningún problema para seleccionar acciones, olvidando para siempre las notitas de papel. Esperamos que al menos los bosques nos estén agradecidos =)

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

Archivos adjuntos |
finder4.mq4 (125.66 KB)
finder4.ex4 (83.41 KB)
finder.mq5 (125.65 KB)
finder.ex5 (151.23 KB)
Analizando resultados comerciales con la ayuda de informes HTML Analizando resultados comerciales con la ayuda de informes HTML
Aparte de los informes comerciales, MetaTrader 5 permite guardar informes sobre la simulación y la optimización de expertos. El informe de simulación, al igual que la historia comercial, puede guardarse en dos formatos: XLSX y HTML, mientras que el informe de oprtimización se guarda en formato XML. En este artículo, vamos a analizar con detalle el informe HTML del simulador, el informe XML de optimización y el informe HTML con la historia comercial.
Uso práctico de las redes neuronales de Kohonen en el trading algorítmico (Parte I) Instrumental Uso práctico de las redes neuronales de Kohonen en el trading algorítmico (Parte I) Instrumental
El presente artículo desarrolla la idea del uso de redes de Kohonen en MetaTrader 5 que fue abordada en algunas publicaciones anteriores. Las clases corregidas y mejoradas proporcionan el instrumental para solucionar las tareas prácticas.
Aplicación práctica de las correlaciones en el trading Aplicación práctica de las correlaciones en el trading
En este artículo hablaremos sobre el concepto de correlación de magnitudes, y también analizaremos los métodos de cálculo de los coeficientes de correlación y su aplicación práctica en el comercio. La correlación es la interacción estadística de dos o más magnitudes aleatorias (o bien magnitudes que se pueden considerar tales con cierto nivel de precisión permisible). En este caso, además, el cambio de los valores de una o varias de estas magnitudes acompaña al cambio sistemático de los valores de otra u otras magnitudes.
Aplicando el método de Montecarlo al aprendizaje por refuerzo Aplicando el método de Montecarlo al aprendizaje por refuerzo
Aplicación de Reinforcement learning para el desarrollo de expertos autodidactas. En el artículo anterior ya nos familiarizamos con el algoritmo de Random Decision Forest y escribimos un sencillo experto autodidacta basado en Reinforcement learning (aprendizaje por refuerzo). Se destacaron las principales ventajas de este enfoque, tales como la sencillez de escritura del algoritmo comercial y la alta velocidad de entrenamiento. El aprendizaje por refuerzo (en lo sucesivo AR) se implementa fácilmente en cualquier experto comercial y aumenta su velocidad de optimización.