
Utilidad para la selección y navegación en MQL5 y MQL4: añadiendo las pestañas de "recordatorios" y guardando objetos gráficos
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:
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; }
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:
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





- 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