Utilidad para la selección y navegación en MQL5 y MQL4: añadiendo la búsqueda automática de patrones y visualización de símbolos encontrados

Roman Klymenko | 3 mayo, 2019

Introducción

Muchas estrategias comerciales requieren un análisis constante de los símbolos para buscar diferentes patrones de entrada en el mercado. Está claro que es útil gastar su tiempo para eso desde el punto de vista del entendimiento del mercado. Pero a veces puede surgir una situación cuando el trader necesite una herramienta que simplemente muestra una lista de los símbolos para los cuales hay un patrón necesario en el momento dado. Pues, en este artículo, intentaremos diseñar esta herramienta para algunos patrones del trading intradía.

Para ser más exacto, hoy vamos a ampliar las capacidades de nuestra utilidad, añadiendo la funcionalidad de la filtración automática de los símbolos según los parámetros necesarios. Para eso, crearemos una serie de pestañas que se encargan de la búsqueda de los símbolos que actualmente representan determinados patrones utilizados en el trading: curvas parabólicas, niveles del aire o «segmentos planos», brechas (gap), etc. Además, aprenderemos a añadir nuestras propias pestañas (está claro si Usted conoce los fundamentos de la programación en el lenguaje MQL).

Como en el artículo anterior, nuestra utilidad va a funcionar tanto en MQL4, como en MQL5. Hay que mencionar de antemano que la apertura de las pestañas de filtración automática en MQL5 es más lenta que en MQL4. Eso ocurre cuando el símbolo solicitado no tiene un historial con la profundidad necesaria. En este caso, MetaTrader 5 solicitará el historial al servidor comercial y construirá los marcos temporales (timeframes) necesarios.

Por tanto, si no pasa nada cuando Usted hace clic en la pestaña, o todos los botones desaparecen, entonces no se ponga nervioso y simplemente espere. Dentro de apenas unos 20 segundos, los símbolos necesarios serán visualizados. A pesar de todo, la espera de unos 20 segundos es más rápida que repasar manualmente los gráficos de centenares de símbolos. Por otro lado, en MetaTrader 4, Usted necesita asegurar personalmente el historial para los símbolos y los timeframes necesarios, lo que también requiere tiempo y la atención a dichos detalles.

Aparte de eso, si su ordenador tiene una pequeña cantidad de la memoria operativa (o trabaja con una máquina virtual que tiene disponible una cantidad limitada de la RAM, como 1 Gb), entonces, en MQL5, el trabajo del EA puede interrumpirse por el error de la falta de memoria cuando se abren las pestañas de filtración automática. En MQL4 no hay este problema, porque todo el historial de los timeframes se carga independientemente y con una profundidad diferente. Podemos solucionar este problema en MQL5 limitando la opción «Máx. barras en la ventana».

Añadiendo la funcionalidad de pestañas de filtración automática

En primer lugar, vamos a considerar cómo podemos insertar una pestaña de filtración automática en la utilidad. Para eso, tenemos que comprender cómo el mecanismo de estas pestañas está organizado por dentro. Pues, vamos a empezar con eso.

La funcionalidad de la inserción de las pestañas fue implementada en el artículo anterior. Como puede recordar, lo hacemos desde el ciclo. Los nombres de las pestañas del recordatorio se guardan en el array. Los nombres de las pestañas de filtración automática también se guardan en un array separado:

string panelNamesAddon[9]={"Air Level", "Parabolic", "Gap", "4 weeks Min/Max", "365 days Min/Max", "Round levels", "Mostly Up/Down", "All time high/low", "High=Close"};

Es decir, para añadir su pestaña, es necesario aumentar el tamaño de este array a 1 y añadir el nombre de su pestaña al final de la lista de pestañas. Después de eso, la nueva pestaña aparecerá inmediatamente en la utilidad.

Por cierto, la función show_panel_buttons se encarga de la visualización de las pestañas. Su código fue modificado, para que se muestren todas las pestañas de la filtración automática además de las pestañas del recordatorio.

void show_panel_buttons(){
   int btn_left=0;
   // definimos la coordenada máxima posible en el eje X, en la que se puede mostrar las pestañas.
   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 inicio del nuevo botón supera la máxima posible,
      // pasamos 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); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_BGCOLOR,clrSilver); 
      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;
   }
   // Si el parámetro de entrada permite mostrar las pestañas de filtración automática, las mostramos
   if(useAddonsLevels){
      for( int i=0; i<ArraySize(panelNamesAddon); i++ ){
         if( btn_left>btn_right-BTN_WIDTH ){
            btn_line++;
            btn_left=0;
         }
         tmpName=panelNamesAddon[i];
         // Si la pestaña se llama Gap, mostramos en ella el valor actual del parámetro de entrada
         // que define el por ciento del gap
         if(tmpName=="Gap"){
            StringAdd(tmpName, " ("+(string) gap_min+"%)");
         }
         
         ObjectCreate(0, exprefix+"panels"+(string) (i+ArraySize(panelNames)), OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XDISTANCE,btn_left); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_FONTSIZE,8); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_COLOR,clrBlack); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_BGCOLOR,clrLightSteelBlue); 
         ObjectSetString(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_TEXT,tmpName);
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_SELECTABLE,false);
         if( cur_panel == i+ArraySize(panelNames) ){
            ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_STATE, true);
         }
         
         btn_left+=BTN_WIDTH;
      }
   }

   // mostramos el comentario si está definido:
   if(StringLen(cmt)>0){
      string tmpCMT=cmt;
      if(from_txt){
         StringAdd(tmpCMT, ", from txt");
      }
      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);
   }
   

}

Como podemos ver en el código de la función, las pestañas de filtración automática van a mostrarse sólo si el parámetro de entrada addons_infowatch permite su visualización. Aparte de él, vamos a añadir dos parámetros para ajustar las pestañas de filtración automática:

sinput string        delimeter_05="";      // --- Pestañas adicionales ---
input bool           useAddonsLevels=true; // Mostrar pestañas adicionales
input bool           addons_infowatch=true;// Ocultar el símbolo si no está presente en Observación del Mercado
input int            addon_tabs_scale=3;   // Escala de pestañas adicionales (0-5)

Recuerde que solamente el parámetro addons_infowatch requiere una aclaración adicional. Si está establecido, se filtran sólo los símbolos mostrados en la Observación del Mercado. En caso contrario, van a filtrarse todos los símbolos ofrecidos por su bróker.

Como resultado, al iniciar la nueva versión de la utilidad, aparecen las nuevas pestañas de filtración automática, aparte de las pestañas de los recordatorios.

Añadiendo las pestañas de filtración automática

Pero volvamos al código. Hemos aprendido añadirlas pestañas de filtración automática. No obstante, si las pulsamos, no se mostrarán ningunos símbolos. Eso se debe al hecho de que la funcionalidad de la visualización de los símbolos todavía no está implementada. Todos los botones de la pestaña abierta en este momento se muestran a través de la función show_symbols. Vamos a añadir aquí la funcionalidad de la visualización de las pestañas de filtración automática. Como resultado, la función será la siguiente:

void show_symbols(){
   
   // inicializamos las variables para determinar las coordenadas X y Y
   int btn_left=0;
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   btn_line++;
   
   
   
   if( cur_panel==0 ){
      ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear All");
      btn_left+=BTN_WIDTH;
      ObjectCreate(0, exprefix+"showpos", OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"showpos",OBJPROP_TEXT,"Show Pos");
      btn_left+=BTN_WIDTH;
   }else if( cur_panel<ArraySize(arrPanels) && arrPanels[cur_panel].Total()>0 ){
      ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear");
      btn_left+=BTN_WIDTH;
      ObjectCreate(0, exprefix+"new_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"new_"+(string) cur_panel,OBJPROP_TEXT,"Open All");
      btn_left+=BTN_WIDTH;
   }
   
   // si el índice de la pestaña abierta en este momento supera la cantidad de los elementos del array de las pestañas de los recordatorios
   // y de la pestaña con todos los símbolos,
   // entonces, es la pestaña de filtración automática
   if(cur_panel>(ArraySize(arrPanels)-1)){
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      int tmpNumAddon=cur_panel-ArraySize(arrPanels);
      addonArr.Resize(0);
      arrTT.Resize(0);
      string addonName;
      
      CArrayString tmpSymbols;
      if( tmpNumAddon==0 && air_only_home ){
         for( int j=1; j<ArraySize(arrPanels); j++ ){
            for( int k=0; k<arrPanels[j].Total(); k++ ){
               string curName=arrPanels[j].At(k);
               bool isYes=false;
               for( int r=0; r<tmpSymbols.Total(); r++ ){
                  if(tmpSymbols.At(r)==curName){
                     isYes=true;
                     break;
                  }
               }
               if(!isYes){
                  tmpSymbols.Add(arrPanels[j].At(k));
               }
            }
         }
      }else{
         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);
            }
         }else{
            for( int i=0; i<SymbolsTotal(addons_infowatch); i++ ){
               tmpSymbols.Add(SymbolName(i, addons_infowatch));
            }
         }
      }
      
      switch(tmpNumAddon){
         case 0: // air levels
            // código de la visualización del contenido de la pestaña
            break;
         case 1: // parabolic
            // código de la visualización del contenido de la pestaña
            break;
         case 2: // Gap
            // código de la visualización del contenido de la pestaña
            break;
         case 3: //4 weeks min/max
            // código de la visualización del contenido de la pestaña
            break;
         case 4: //365 days min/max
            // código de la visualización del contenido de la pestaña
            break;
         case 5: // round levels
            // código de la visualización del contenido de la pestaña
            break;
         case 6: // mostly up/down
            // código de la visualización del contenido de la pestaña
            break;
         case 7: // all time high/low
            // código de la visualización del contenido de la pestaña
            break;
         case 8: // high=close
            // código de la visualización del contenido de la pestaña
            break;
      }
      
      // mostramos un botón en el gráfico para cada símbolo en el array
      // el nombre del símbolo vamos a escribir en el botón
      for( int i=0; i<addonArr.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,addonArr.At(i));    
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);
         if( arrTT.At(i)>0 ){
            ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TOOLTIP,(string) arrTT.At(i));    
         }
   
         if( checkSYMBwithPOS(addonArr.At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
         
         btn_left+=BTN_WIDTH;
      }
      
      // puesto que hemos mostrado los botones, salimos de la función
      return;
   }
   
  // para cada símbolo en el array de la pestaña activa en este momento
  // mostramos un botón en el gráfico
   // el nombre del símbolo vamos a escribir en el botón
   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;
   }
   
}

Como podemos observar, el código para mostrar el contenido de la pestaña se encuentra dentro del operador switch. El índice del elemento del array que contiene el nombre de la pestaña se indica en el operador case. De esta manera, para añadir una pestaña personalizada después de añadir su nombre al array, basta con añadir un nuevo case con el número que supera el último número usado a 1.

Luego, al analizar unas determinadas pestañas, veremos los ejemplos del código que se usan para filtrar automáticamente los símbolos con diferentes parámetros. Pero ya podemos notar que el código de todas las pestañas es el mismo.

La lista de todos los símbolos que tenemos que filtrar ya se encuentra en el array tmpSymbols. Por eso, el código de cada pestaña empieza con el ciclo for:

            for( int i=0; i<tmpSymbols.Total(); i++ ){
               addonName=tmpSymbols[i];
               // código que define  si hay que mostrar el símbolo o no
            }

Niveles del aire (segmentos planos)

Al negociar a partir de los niveles, todas las entradas se realizan cerca de los segmentos planos. Es decir, cuando el precio en cada barra toca el mismo precio (o el precio cercano) con su high o low. Por ejemplo, como se muestra en la imagen:

Ejemplo del segmento plano

Puede que no sea el ejemplo perfecto, ya que el nivel obtenido experimenta constantemente las rupturas falsas. Pero por otro lado, se considera que la presencia de las rupturas falsas sólo refuerza el nivel =)

Es bastante engorroso buscar estas situaciones manualmente, por eso vamos a intentar automatizar este proceso.

Generalmente, los segmentos planos se buscan en los gráficos de 5 minutos. Pero no es una condición obligatoria. Algunos traders trabajan con los gráficos de 15 minutos, otros usan los gráficos de 30 minutos. Se considera que cuanto más grande sea el timeframe en el que ha sido detectado el segmento plano, la entrada será de mayor calidad.

Por eso, vamos a añadir los parámetros de entrada que permiten determinar los timeframes donde hay que buscar los segmentos planos:

sinput string        delimeter_06=""; // --- Pestaña adicional Niveles de aire ---
input bool           air_level_m5=true; // Buscar niveles de aire en M5
input bool           air_level_m15=true; // Buscar niveles de aire en M15
input bool           air_level_m30=false; // Buscar niveles de aire en M30
input bool           air_level_h1=false; // Buscar niveles de aire en H1
input bool           air_level_h4=false; // Buscar niveles de aire en H4
input bool           air_level_d1=false; // Buscar niveles de aire en D1

Hay que recordar que cuanto más alta sea la cantidad de los timeframes que Usted selecciona, más lento será el proceso de la búsqueda. Por eso, será mejor limitar la búsqueda con uno o dos timeframes.

Además, añadiremos otros parámetros de entrada.

input uchar          air_level_count=4; // Número de las barras del nivel
input uchar          air_level_offset=3; // Desplazamiento respecto al nivel en puntos
input bool           air_level_cur_day=true; // Sólo los niveles en dirección del día actual
input bool           air_level_prev_day=true; // Sólo los niveles en dirección del día anterior
input bool           air_only_home=false; // Buscar sólo en «recordatorios»

El parámetro Número de barras del nivel permite especificar el número de las barras que deben tocar consecutivamente el precio cercano para admitir que se trata de un segmento falso. El valor ideal para este parámetro es 4 de barras. Cuanto más grande sea el número de las barras, mayor será la calidad del segmento plano, pero menor será su cantidad encontrada.

El parámetro Desplazamiento respecto al nivel, en puntos permite especificar el intervalo de los puntos, dentro del cual tiene que estar el precio para reconocerlo «próximo». Es decir, si el valor de este parámetro es 0, las barras tienen que tocar exactamente el mismo precio en céntimos. Generalmente, la cantidad de estos segmentos planos será muy pequeña. Sobre todo, en los timeframes grandes. En la mayoría de los casos, el precio no alcanza el nivel especificado parando en algunos puntos. Por eso, por defecto, el valor de este parámetro es igual a 3 puntos.

Si Usted negocia sólo en la dirección del movimiento del día de hoy y/o día de ayer, entonces los parámetros Sólo los niveles en la dirección del día actual y Sólo los niveles en la dirección del día de ayer permiten filtrar los símbolos en los que los segmentos planos han sido detectados en la dirección opuesta al movimiento del precio.

La dirección del movimiento del precio se determina por la razón entre el precio de cierre de la barra diaria y el precio de apertura. Si el precio del cierre de la barra del día anterior supera el precio de cierre, entonces buscamos sólo los segmentos planos en Long. Es decir, aquéllos que tocan el precio próximo por Low.

El último parámetro: Buscar sólo en «Recordatorios». Si durante el día Usted trabaja sólo con los símbolos que han sido añadidos a las pestañas de los recordatorios, define este parámetro para que la utilidad busque los segmentos planos sólo en los símbolos que figuran en este momento en las pestañas de los recordatorios.

Pues, ahora vamos a analizar el propio código que filtra los símbolos que actualmente tienen los segmentos planos. Más abajo viene el código para M5:

               if(air_level_m5 && CopyRates(addonName, PERIOD_M5, 0, air_level_count+1, rates)==air_level_count+1){
                  if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)<=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)<=0) && rates[0].high<rates[1].high ){
                     bool isOk=true;
                     for( int j=1; j<air_level_count; j++ ){
                        if( MathAbs(rates[1].high-rates[j+1].high) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){
                        }else{
                           isOk=false;
                        }
                     }
                     if(isOk && !skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(rates[1].high);
                     }
                  }else if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)>=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)>=0) && rates[0].low>rates[1].low ){
                     bool isOk=true;
                     for( int j=1; j<air_level_count; j++ ){
                        if( MathAbs(rates[1].low-rates[j+1].low) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){
                        }else{
                           isOk=false;
                        }
                     }
                     if(isOk && !skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(rates[1].low);
                     }
                  }
               }

Como en todos los demás ejemplos del código de filtración automática, inmediatamente después de la llamada a la función CopyRates, empezamos a trabajar con los resultados de su llamada. No es de todo correcto. En la Ayuda para MQL, se recomienda esperar algún tiempo, para que la utilidad reciba e escriba los datos en el array. Pero si incluso vamos a esperar 50 milisegundos, entonces, la verificación de 100 símbolos supone un retardo adicional en 5 segundos. Mientras que muchos brókers ofrecen centenares de los símbolos del mercado de acciones. Como resultado, al usar un retardo, la espera de la visualización del contenido de la pestaña puede llevar bastante tiempo.

Por eso, empezaremos a trabajar inmediatamente con el array de los resultados. En la práctica, eso no supone ningún problema. Tal vez, haya uno. Es decir, probablemente, sea el problema causado por la falta del retardo.

La cosa es que no siempre los datos actuales se transfieren al array como resultado de la ejecución de la función CopyRates. A veces la filtración usa los datos obsoletos. En este caso, basta con actualizar la pestaña con el fin de obtener la lista actual de los símbolos. Por cierto, si alguien no recuerda el contenido del primer articulo de la serie, para actualizar la visualización de la pestaña, utilice la tecla R del teclado.

Volvamos al código. Si decide añadir su propias pestañas de la filtración automática, preste atención en cómo se ocurre la selección de los símbolos.

Si el símbolo se encaja a nuestras condiciones, colocamos su nombre en el array addonArr. Además, se puede indicar entre paréntesis el timeframe que va a usarse cuando se abre el gráfico del símbolo, en vez del timeframe predefinido.

Aparte de eso, hay que insertar el valor en el array arrTT. Si insertamos el valor 0 en este array, no va a suceder nada. Pero si introducimos algún precio en el array, se trazará una línea horizontal al nivel de este precio cuando se abre el gráfico de este símbolo. Eso se hace por conveniencia, para que se vea en seguida el precio en que se detecta el segmento plano.

Curvas parabólicas

Las curvas parabólicas aparecen cuando después del movimiento en una dirección, el precio empieza a moverse en la dirección opuesta, y cada nuevo mínimo/máximo de la barra es mayor o menor que la anterior. Se considera que en este caso, hay una gran probabilidad de que el precio vaya en la dirección del aumento del low o disminución del high. En este caso, aparece la probabilidad de usar un pequeño Stop Loss.

En otras palabras, después de la disminución, el precio empieza a moverse hacia arriba, y cada low de la barra está por encima del anterior. En este caso, entramos en Long colocando el nivel Stop detrás de por lo menos de uno de las barras anteriores.

Como ejemplo, podemos analizar el siguiente gráfico.

Ejemplo de una curva parabólica

Las curvas parabólicas en cuestión se muestran con las flechas.

Vamos a buscar las curvas parabólicas en M5 usando el siguiente código:

               if(CopyRates(addonName, PERIOD_M5, 0, 6, rates)==6){
                  if( rates[0].low>rates[1].low && rates[1].low>rates[2].low && rates[2].low<=rates[3].low && rates[3].low<=rates[4].low && rates[4].low<=rates[5].low ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }else if( rates[0].high<rates[1].high && rates[1].high<rates[2].high && rates[2].high>=rates[3].high && rates[3].high>=rates[4].high && rates[4].high>=rates[5].high ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Es decir, si low de la barra actual supera el low de la anterior. Además, el low de la barra anterior supera el low de la anterior. Mientras que las demás 3 barras anteriores tienen low igual, o cada una de ellas es menor que la otra. En otras palabras, si primero 3 barras van hacia abajo, y son seguidas por 2 barras hacia arriba, vamos a considerar que es el inicio de una curva parabólica en Long.

Gaps

Si para su trading Usted utiliza las estrategias comerciales que trabajan con acciones que tienen un gap en una u otra dirección, la pestaña Gap le permitirá seleccionar las acciones necesarias. Ella contiene sólo los símbolos con los gaps durante el día actual. Aparte, Usted puede usar el parámetro de entrada Tamaño mínimo del gap para cambiar el valor del gap mínimo (en por cientos del precio actual).

Por defecto, se muestran sólo los símbolos con el tamaño del gap de por lo menos 1%.

El código fuente de esta pestaña es simple:

               if(CopyRates(addonName, PERIOD_D1, 0, 2, rates)==2){
                  if( rates[0].open>rates[1].close+(rates[0].open*(gap_min/100)) || rates[0].open<rates[1].close-(rates[0].open*(gap_min/100)) ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Mínimos/máximos de 4 semanas

La pestaña 4 weeks Min/Max proporciona una lista de los símbolos cuyo precio actual es máximo/mínimo durante 4 semanas. Es su código:

               if(CopyRates(addonName, PERIOD_W1, 0, 4, rates)==4){
                  bool newMin=true;
                  bool newMax=true;
                  if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){
                     newMin=false;
                     newMax=false;
                  }else{
                     for( int j=1; j<4; j++ ){
                        if( rates[0].high < rates[j].high ){
                           newMax=false;
                        }
                        if( rates[0].low > rates[j].low ){
                           newMin=false;
                        }
                     }
                  }
                  if( newMin || newMax ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Mínimos/máximos anuales

Simultáneamente a la pestaña anterior, se puede obtener los símbolos cuyos precio se encuentra en este momento en los mínimos/máximos anuales:

               if(CopyRates(addonName, PERIOD_W1, 0, 52, rates)==52){
                  bool newMin=true;
                  bool newMax=true;
                  if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){
                     newMin=false;
                     newMax=false;
                  }else{
                     for( int j=1; j<52; j++ ){
                        if( rates[0].high < rates[j].high ){
                           newMax=false;
                        }
                        if( rates[0].low > rates[j].low ){
                           newMin=false;
                        }
                     }
                  }
                  if( newMin || newMax ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Precio próximo a los niveles redondos

Se considera que los precios redondos en las acciones son los niveles «naturales» del soporte/resistencia. Por eso, en algunos sistemas comerciales, las acciones que en este momento se negocian cerca de sus niveles redondos representan un interés especial.

El precio que se termina en 0 o 50 céntimos será considerado redondo. Por ejemplo, 125 dólares 0 céntimos, o 79 dólares 50 céntimos.

Como resultado, tenemos el siguiente código:

               switch((int) SymbolInfoInteger(addonName, SYMBOL_DIGITS)){
                  case 0:
                     break;
                  case 2:
                     if(CopyRates(addonName, PERIOD_M5, 0, 1, rates)==1){
                        double tmpRound=rates[0].close - (int) rates[0].close;
                        if( (tmpRound>0.46 && tmpRound<0.54) || tmpRound>0.96 || tmpRound<0.04 ){
                           if(!skip_symbol(addonName)){
                              addonArr.Add(addonName+" (M5)");
                              arrTT.Add(0);
                           }
                        }
                     }
                     break;
               }
Es decir, vamos a determinar los precios redondos sólo para los instrumentos en cuyos precios hay dos dígitos tras la coma. Si Usted trabaja con otros instrumentos, basta con añadir su propia verificación para ellos de manera semejante.

La mayor parte de tiempo arriba/abajo

Los instrumentos que se mueven arriba o abajo la mayor parte de tiempo también pueden ser de particular interés. Para buscarlos, añadimos los siguientes parámetros de entrada:

sinput string        delimeter_08=""; // --- Pestaña adicional Mostly Up/Down ---
input int            mostly_count=15; // Verifica los últimos días
input int            mostly_percent=90; // Porcentaje en una dirección excedida

El parámetro Verifica el último número especificado de días permite determinar el número de días durante los cuales vamos a buscar los instrumentos necesarios. Es decir, vamos a buscar los movimientos (principalmente unidireccionales) en D1.

El parámetro Porcentaje en una dirección excedida permite especificar el porcentaje mínimo por el cual una dirección del movimiento excede la otra. Por defecto, este parámetro tiene el valor 90. Es decir, para entrar en esta pestaña, el precio tiene que ir en la misma dirección durante 90% de días.

Generalmente, estos instrumentos son pocos. Por eso, tal vez, haya que reducir este porcentaje.

El código de esta pestaña es siguiente:

               if(CopyRates(addonName, PERIOD_D1, 1, mostly_count, rates)==mostly_count){
                     int mostlyLong=0;
                     int mostlyShort=0;
                     for( int j=0; j<mostly_count; j++ ){
                        if(rates[j].close>rates[j].open){
                           mostlyLong++;
                        }else if(rates[j].close<rates[j].open){
                           mostlyShort++;
                        }
                     }
                     if( !mostlyLong || !mostlyShort ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( ((mostlyLong*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( ((mostlyShort*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Cada barra con high/low nuevo

Esta pestaña permite detectar el proceso de la acumulación dentro del instrumento, es decir, el período cuando el precio se mueve en la misma dirección, lentamente pero de forma constante. Generalmente, la acumulación se termina con la realización, o sea, con las barras grandes en la dirección de la acumulación.

Los siguientes parámetros nos ayudarán a buscar los movimientos unidireccionales:

sinput string        delimeter_09=""; // --- Pestaña adicional All time High/Low ---
input ENUM_TIMEFRAMES alltime_period=PERIOD_D1; // Período
input int            alltime_count=15; // Verificar los últimos barras especificados

El código de la filtración será el siguiente:

               if(CopyRates(addonName, alltime_period, 1, alltime_count, rates)==alltime_count){
                     bool alltimeHigh=true;
                     bool alltimeLow=true;
                     for( int j=1; j<alltime_count; j++ ){
                        if(rates[j].high>rates[j-1].high){
                           alltimeHigh=false;
                        }
                        if(rates[j].low<rates[j-1].low){
                           alltimeLow=false;
                        }
                     }
                     if( alltimeHigh || alltimeLow ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Sesión cerrada en los máximos/mínimos del día

Es la última pestaña que vamos a añadir. Dependiendo del momento de su llamada, ella permite detectar lo siguiente:

Se considera que si el precio se cerró en el máximo/mínimo del día, entonces, el comprador/vendedor no tuvo tiempo realizar sus planes. Eso quiere decir que el día siguiente el precio seguirá en la misma dirección.

Los siguientes parámetros de entrada nos ayudarán a buscar los instrumentos:

sinput string        delimeter_10=""; // --- Pestaña adicional High=Close ---
input ENUM_TIMEFRAMES highclose_period=PERIOD_D1; // Período
input int            highclose_offset=0; // Error de máximos/mínimos en puntos

Al buscar el máximo/mínimo del día, no es necesario que el precio se cierre exactamente en punto extremo. Se admite un retroceso desde el precio extremo en unos o decenas de puntos. El parámetro Error de máximos/mínimos en puntos permite definir este retroceso en puntos.

Es el código de nuestra pestaña:

               if(CopyRates(addonName, highclose_period, 0, 1, rates)==1){
                     if( rates[0].close+highclose_offset >= rates[0].high ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( rates[0].close-highclose_offset <= rates[0].low ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Añadiendo pestañas personalizadas

Está claro que no hemos considerado todos los patrones posibles. Si Usted utiliza otros patrones y tiene habilidades en la programación en MQ, puede añadir fácilmente sus pestañas personalizadas a la utilidad. Sería bueno si dejara en los comentarios el código de su pestaña personalizada con la descripción del patrón que busca.

Las mejoras del código propuesto también son bienvenidas si Usted considera que sería más conveniente buscar el patrón correspondiente de otra manera.

Para concluir, voy a recordar cómo añadir una pestaña de filtración automática personalizada a la utilidad. Eso se hace en dos pasos.

Primero, hay que añadir el nombre de la pestaña nueva al array con los nombres de las pestañas panelNamesAddon. Además, no olvide aumentar el tamaño del array a 1.

Segundo, el operador switch de la función show_symbols debe contener el nuevo case con el valor excediendo el valor máximo utilizado a 1. Después, dentro del operador case se escribe el código que comprueba las condiciones para el símbolo actual. La plantilla del código será la siguiente:

         case index: // nombre de la pestaña
            for( int i=0; i<tmpSymbols.Total(); i++ ){
               addonName=tmpSymbols[i];

               // código de verificación
            }
            
            break;

Conclusiones

Hemos ampliado aún más la funcionalidad de nuestra utilidad. Me gustaría creer que ahora es más útil para los traders.

Además, en este artículo no hemos reescrito el código ninguna vez, dependiendo de la versión de MQL. Todo lo que hemos hecho hoy, va a funcionar tanto en MQL4, como en MQL5.

Como puede ver, no es tan difícil desarrollar las utilidades multiplataforma en el lenguaje MQL. La mayor parte de las funcionalidades de MQL5 también se soportan en MQL4 de la misma manera. Por tanto, ¿puede que valga la pena olvidar por un tiempo de diferentes clases y otras posibilidades únicas de MQL5, y realizar el desarrollo de tal manera que nuestros esfuerzos estén disponibles para el mayor número de los traders?

Claro que yo no declaro la guerra a las clases. Cuanto más que las clases ya están presentes en el lenguaje MQL4, y los códigos con ellas aparecen en CodeBase. Simplemente, propongo apartar esta posibilidad del lenguaje MQL5 por algún tiempo.