English Русский Português
preview
Desarrollamos un asesor experto multidivisas (Parte 26): Informador para instrumentos comerciales

Desarrollamos un asesor experto multidivisas (Parte 26): Informador para instrumentos comerciales

MetaTrader 5Probador |
251 3
Yuriy Bykov
Yuriy Bykov

Contenido


Introducción

En el último artículo finalmente llegamos a lo que podemos llamar un sistema completo que permite organizar la obtención automática de un asesor experto completo partiendo de una simple estrategia comercial, haciendo posible el funcionamiento paralelo de esta estrategia en diferentes instrumentos y marcos temporales. Asimismo, prestamos atención a las cuestiones relacionadas con el sistema de gestión del capital y el gestor de riesgos que permite detener la negociación si se dan situaciones desfavorables o, por el contrario, demasiado favorables.

A lo largo de la mayor parte de la serie, hemos trabajado con una única estrategia comercial simple, y solo en las últimas partes, cuando las características principales ya estaban generalmente implementadas, hemos considerado el proceso de adición de una nueva estrategia comercial y su uso como estrategia principal. Este ejemplo ha demostrado la posibilidad de liberar el potencial de casi cualquier estrategia comercial (si realmente existe, claro).

Pero una vez que hemos alcanzado nuestra altitud actual, se nos abre un campo aún más amplio para seguir trabajando. Y los ojos, por así decirlo, no logran decidirse por una dirección futura. Para superarlo, hemos intentado cambiar el enfoque de organización y almacenamiento del código fuente de este proyecto. Los primeros pasos se dieron en la Parte 23, en la que separamos la mayor parte del código en la llamada "parte de biblioteca", dejando el resto del código en la "parte de proyecto". A continuación, dirigimos nuestra atención a las características del nuevo repositorio de código, contando los primeros pasos en un artículo separado Cambiamos a MQL5 Algo Forge (Parte 1): Creación del repositorio principal. La estrategia de uso de las capacidades del nuevo repositorio está aún dando sus primeros pasos. En general, nos gustaría organizar la posibilidad de trabajar en paralelo en la biblioteca en varias direcciones a la vez. Hasta qué punto lo conseguiremos se verá más adelante.

Para ello, se requerirá práctica. Es la única forma de saber si las decisiones arquitectónicas tomadas han sido acertadas o no. Así pues, hoy vamos a intentar practicar la creación de un nuevo proyecto utilizando la biblioteca desarrollada, llamada Advisor. Hoy no vamos a comenzar a trabajar en un gran proyecto para desarrollar un asesor experto que utilice alguna estrategia de trading compleja: todo lo contrario. Haremos un proyecto que no tenga como objetivo en absoluto el desarrollo de un asesor comercial.


Trazando el camino

Un lector ha planteado una cuestión interesante en relación con la última estrategia añadida SimpleCandles. En esta estrategia, uno de los parámetros es el número de velas unidireccionales consecutivas en un marco temporal determinado. Por ello, sería bueno poder ver qué series de velas de este tipo se producen en diferentes instrumentos y diferentes marcos temporales, para no delegar completamente en el optimizador la selección de los valores adecuados.

De hecho, incluso para la optimización automática, sigue siendo necesario comprender dentro de qué límites cambiarán los parámetros de entrada. Obviamente, podríamos poner solo algún rango amplio, pero eso reduciría mucho la eficacia de la optimización. Al fin y al cabo, el número total de combinaciones de parámetros será mayor, y la probabilidad de acertar una combinación resultará menor. No podemos decir cuánto más baja, pero una simple comprensión de la disminución es suficiente para intentar hacer algunos esfuerzos para mejorar la eficiencia.

Además, recopilar esa información sobre el comportamiento de los precios de los distintos instrumentos nos ayudará a responder a la pregunta: "¿Podemos utilizar los mismos rangos de parámetros para distintos instrumentos?". Si es así, esto simplificará la organización de la primera fase de optimización, pero si no, hará que el proceso de creación de tareas de optimización en esta fase resulte un poco más complicado.

En general, vamos a intentar crear un EA auxiliar, o parte de un EA, que sea capaz de mostrarnos algunas estadísticas sobre símbolos y marcos temporales de una forma u otra. Tal vez nos resulte útil en el futuro para utilizarlo en estrategias comerciales.

Pero esa sería una segunda cuestión. Y la primera por la que comenzaremos es cómo debemos organizar el almacenamiento del código fuente con vistas a su uso posterior en otros proyectos.


Creación del proyecto

A primera vista parece: ¿qué puede ser tan difícil? Hemos almacenado código antes, podemos seguir así. Pero hay una diferencia. Una cosa es guardar todo el código del proyecto en una carpeta y, para cada proyecto siguiente, hacer una carpeta nueva y copiar en ella el código del proyecto anterior. Este enfoque resulta positivo por su sencillez y está bien justificado para un desarrollo estrictamente secuencial, sin tener que preocuparse por la compatibilidad con versiones anteriores. Resulta mucho más cómodo en la fase inicial, cuando los cambios significativos son bastante frecuentes. Y otra cosa es cuando nuestro proyecto empieza a crecer, y ahora distingue claramente partes separadas que no cambiarán (o cambiarán, pero pocas veces), y otras partes que pueden cambiar mucho o aparecer de cero.

En este caso, en nuestra opinión, las desventajas de almacenar todo el código en una carpeta empiezan a pesar más que las ventajas de dicho enfoque. Un poco antes, ya hemos movido la mayor parte del código a la carpeta MQL5/Include/antekov/Advisor, llamando a esta parte como código de biblioteca. Pero ahora usar esta disposición para la parte de la biblioteca no parece muy conveniente.

Imaginemos que estamos trabajando en dos proyectos paralelos que usan la biblioteca Advisor. La mayoría de los cambios afectan a la parte de diseño, pero también se están introduciendo algunos cambios en la parte de biblioteca. Si ambos proyectos se refieren a la parte de la biblioteca ubicada en el mismo lugar(MQL5/Include/antekov/Advisor), los conflictos serán más que posibles. Para evitarlos, deberemos cambiar al menos la parte de la biblioteca a otra versión apropiada almacenada en otra rama del repositorio cuando pasemos de un proyecto a otro. Aunque no tenga nada de complicado, la necesidad de tal manipulación no es bienvenida. Alguna vez podemos olvidarnos de alternar, y luego limpiar las ediciones ya hechas en la rama equivocada moviéndolas a otra rama.

Así que intentaremos cambiar el enfoque. Cada proyecto será un repositorio independiente. Dentro de la carpeta del proyecto habrá sin duda una carpeta Include que contendrá carpetas con partes de la biblioteca. Nota: no con una parte de la biblioteca, sino con varias, repartidas en diferentes carpetas. Cada parte de la biblioteca será un clon de un repositorio de código independiente.


Repositorio de la parte de la biblioteca

Para la parte de la biblioteca, crearemos un nuevo repositorio en MQL5 Algo Forge o cualquier otro repositorio GIT público. El nombre Adviso que utilizamos para la biblioteca parecía demasiado genérico. Así que añadiremos singularidad cambiándole el nombre a Adwizard. Así llamaremos a nuestra biblioteca a partir de ahora.

Luego colocaremos todos los archivos de la parte de la biblioteca en este repositorio. Al crearse un repositorio, contendrá una única rama llamada main. Crearemos una nueva rama develop, de la que surgirán las ramas para los artículos y las nuevas funciones de la biblioteca. Estas ramas auxiliares se cerrarán cuando se implementen las nuevas funciones, y las ediciones se inyectarán en la rama develop y luego en main. Por lo general, será cuando se complete el siguiente artículo.

Para asegurar que el código de este repositorio funciona cuando se clona en cualquier otra carpeta, hemos tenido que introducir algunas correcciones menores en algunos archivos de la biblioteca. Eran necesarios en aquellos lugares donde en las directivas de conexión #include utilizábamos rutas que llevaban a la carpeta Include con la biblioteca estándar. Después de reemplazarlos con rutas relativas, hemos eliminado la vinculación a una ubicación específica de la biblioteca en la carpeta MQL5/Include/antekov/Advisor.

Por ejemplo, en el archivo Optimisation.mqh, hemos realizado esta sustitución: 

#include <antekov/Advisor/Optimization/Optimizer.mqh>
#include "../Optimization/Optimizer.mqh"

En el archivo OptimizerTask.mqh, seguimos utilizando el único archivo de biblioteca de terceros de fxsaber. También lo movimos dentro de la biblioteca a la carpeta Utils:

#include <antekov/Advisor/Database/Database.mqh>
#include <fxsaber/MultiTester/MTTester.mqh> // https://www.mql5.com/es/code/26132

#include "../Database/Database.mqh"
#include "../Utils/MTTester.mqh" // https://www.mql5.com/es/code/26132

Estas ediciones ya las hemos enviado al repositorio de la biblioteca.


Repositorio del proyecto

Para el proyecto, crearemos un nuevo repositorio SymbolsInformer. En él, además de la rama main, haremos una rama para desarrollo llamada develop. Si va a haber varios artículos dedicados a este proyecto, deberemos separar las ediciones relacionadas con diferentes artículos en distintas ramas. Estos se generarán de la rama main y volverán a develop y main cuando estén listos.

Luego crearemos una carpeta para colocar la carpeta del proyecto, por ejemplo, MQL5/Experts/Article/17606. Clonaremos este repositorio en la carpeta seleccionada y crearemos una carpeta Include en ella. En esta carpeta colocaremos repositorios de otras bibliotecas de las que dependerá este proyecto. Por ahora, solo será la biblioteca Adwizard. Después clonaremos en la carpeta Include el repositorio de bibliotecas Adwizard. Si necesitaros otra biblioteca, la clonaremos en la misma carpeta Include.

Después de estas operaciones, obtendremos aproximadamente esta estructura de carpetas en la carpeta del terminal:

En la carpeta clonada del repositorio Adwizard, cambiaremos a la rama develop. Será común a todos los artículos. Si no realizamos cambios en la biblioteca Adwizard mientras trabajamos en este proyecto, permaneceremos en la rama develop, actualizándola cuando efectuemos nuevas ediciones mientras trabajamos en otros artículos. Si necesitamos arreglar algo en esta biblioteca debido al trabajo en el proyecto actual, entonces crearemos una nueva rama.

Después, en el repositorio del proyecto, crearemos una rama para trabajar en este artículo y empezaremos a desarrollar en ella. Ya hemos dado una breve descripción del proceso de creación de un nuevo proyecto; en otro artículo ofreceremos una más detallada.


Descripción del proyecto

Vamos a formular un breve pliego de condiciones para el desarrollo de la herramienta necesaria. Se implementará en forma de asesor experto, ya que no tendrá ningún parámetro de cálculo que requiera un recálculo periódico y la visualización de valores cambiantes durante un periodo de tiempo.

En primer lugar, para contar el número de series de velas unidireccionales, necesitaremos establecer un intervalo de tiempo para el que recopilaremos estas estadísticas. Existen diferentes maneras de hacerlo. Por ejemplo, podemos establecer el número de días a partir de la fecha actual, o dos fechas diferentes que indiquen el principio y el final. Probablemente, para empezar, solo calcularemos las estadísticas del intervalo a partir de la fecha actual. Y la duración podrá establecerse seleccionando un marco temporal (por ejemplo, diario) y el número de velas de este marco temporal. Lo llamaremos marco temporal principal.

A continuación, deberemos especificar de algún modo para qué instrumentos comerciales (símbolos) y en qué marcos temporales queremos realizar los cálculos. Obviamente, podemos realizar cálculos solo para el símbolo y el marco temporal en el que se ejecutará el asesor experto desarrollado. Pero será mejor establecer la capacidad de realizar cálculos para varios símbolos y marcos temporales.

Basándonos en lo anterior, crearemos la lista de parámetros de entrada del asesor experto:

  • Marco temporal principal
  • Número de velas del marco temporal principal
  • Lista de símbolos
  • Lista de marcos temporales

Este conjunto de parámetros podrá ampliarse en el futuro. Las listas de símbolos y marcos temporales contendrán sus nombres separados por comas. Los nombres de los marcos temporales se establecerán tal y como se nombran en el terminal, por ejemplo, M5, M15, H1, etc.

Para cada símbolo y marco temporal calcularemos los valores siguientes:

  • Tamaño medio de las velas:
    • alcista (vela "alcista" o "de compra" con un precio de cierre no inferior al precio de apertura);
    • bajista (vela "bajista" o "de venta", en la que el precio de cierre no es superior al de apertura);
    • todas (tanto alcistas como bajistas);
  • Duración media de la serie (una serie será una secuencia de dos o más velas con la misma dirección);
  • Número de series con longitud
    • 2
    • 3
    • ...
    • 8
    • 9

Esta lista también es una lista abierta, es decir, podemos añadirle nuevos valores de cálculo si lo deseamos.


Implementamos la primera versión

En primer lugar, intentaremos hacer la versión más sencilla posible. Guardaremos los valores calculados en matrices globales y mostraremos los resultados de alguna manera en el registro y en el gráfico como comentario. Aún no sabemos exactamente qué datos nos serán útiles y cuáles carecerán de sentido. Así pues, la primera versión será necesaria en primer lugar para determinar lo que necesitamos al fin y al cabo. En él, no prestaremos mucha atención a la comprobación de la coherencia de los parámetros de entrada ni a la organización del almacenamiento de la información.

Los parámetros de entrada, según la lista compilada, pueden ajustarse de esta manera:

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "::: Calculation period"
sinput ENUM_TIMEFRAMES mainTimeframe_  = PERIOD_D1;   // Main timeframe
input int         mainLength_          = 30;          // Number of candles on the main timeframe

input group "::: Symbols and timeframes "
sinput string     symbols_             = "";          // Symbols (comma separated)
sinput string     timeframes_          = "";          // Timeframes (e.g. M5, H1, H4)

Como los parámetros de entrada especifican los símbolos y marcos temporales en varias líneas, necesitaremos matrices que almacenen valores separados para cada nombre de símbolo y cada marco temporal.

Para almacenar los valores calculados crearemos matrices bidimensionales. El primer índice en ellas se asociará con el símbolo, y el segundo, con el marco temporal. Dado que al declarar matrices bidimensionales es necesario especificar el número de elementos de la segunda dimensión, introduciremos una constante TFN en la que especificaremos el número de todos los marcos temporales estándar existentes en la actualidad. En total eran 21.

// Number of existing timeframes
#define TFN (21)

// Global variables
string g_symbols[];                       // Array of all used symbols
ENUM_TIMEFRAMES g_timeframes[];           // Array of all used timeframes

// Arrays of calculated values.
// The first index is a symbol, the second index is a timeframe
double symbolAvrCandleSizes[][TFN];       // Array of average sizes of all candles
double symbolAvrBuyCandleSizes[][TFN];    // Array of average sizes of bullish candles 
double symbolAvrSellCandleSizes[][TFN];   // Array of average sizes of bearish candles

double symbolAvrSeriesLength[][TFN];      // Array of average series lengths 

int symbolCountSeries2[][TFN];            // Array of the number of series of length 2
int symbolCountSeries3[][TFN];            // Array of the number of series of length 3
int symbolCountSeries4[][TFN];            // Array of the number of series of length 4
int symbolCountSeries5[][TFN];            // Array of the number of series of length 5
int symbolCountSeries6[][TFN];            // Array of the number of series of length 6
int symbolCountSeries7[][TFN];            // Array of the number of series of length 7
int symbolCountSeries8[][TFN];            // Array of the number of series of length 8
int symbolCountSeries9[][TFN];            // Array of the number of series of length 9

Para las transformaciones entre constantes simbólicas de marcos temporales (de tipo ENUM_TIMEFRAMES), sus nombres de cadena e índices en el array de todos los marcos temporales realizaremos funciones auxiliares. Se usarán para realizar tres tareas:

  • obtener según el nombre de cadena la constante de símbolo (StringToTimeframe)
  • obtener a partir de la constante de símbolo el nombre de cadena del marco temporal sin el prefijo "PERIOD_" (TimeframeToString)
  • obtener a partir de una constante de símbolo el índice del array de todos los marcos temporales (TimeframeToIndex)

// Array of all timeframes
ENUM_TIMEFRAMES tfValues[] = {
   PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6,
   PERIOD_M10, PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30,
   PERIOD_H1, PERIOD_H2, PERIOD_H3, PERIOD_H4, PERIOD_H6,
   PERIOD_H8, PERIOD_H12, PERIOD_D1, PERIOD_W1, PERIOD_MN1
};

//+------------------------------------------------------------------+
//| Convert a string name to a timeframe                             |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES   StringToTimeframe(string s) {
// If the string contains the "_" symbol, leave only the characters that follow it
   int pos = StringFind(s, "_");
   if(pos != -1) {
      s = StringSubstr(s, pos + 1);
   }

// Convert to uppercase
   StringToUpper(s);

// Arrays of corresponding string names of timeframes
   string keys[] = {"M1", "M2", "M3", "M4", "M5", "M6", "M10", "M12", "M15", "M20", "M30",
                    "H1", "H2", "H3", "H4", "H6", "H8", "H12", "D1", "W1", "MN1"
                   };

// Search for a match and return it if found
   FOREACH(keys) {
      if(keys[i] == s)
         return tfValues[i];
   }

   return PERIOD_CURRENT;
}

//+------------------------------------------------------------------+
//| Convert a timeframe to a string name                             |
//+------------------------------------------------------------------+
string TimeframeToString(ENUM_TIMEFRAMES tf) {
// Get the timeframe name of the 'PERIOD_*' type
   string s = EnumToString(tf);

// Return the part of the name after the '_' symbol
   return StringSubstr(s, StringFind(s, "_") + 1);
}

//+------------------------------------------------------------------+
//| Convert a timeframe to an index in an array of all timeframes    |
//+------------------------------------------------------------------+
int   TimeframeToIndex(ENUM_TIMEFRAMES tf) {
// Search for a match and return it if found
   FOREACH(tfValues) {
      if(tfValues[i] == tf)
         return i;
   }

   return WRONG_VALUE;
}

El cálculo de todos los valores se realizará dentro de la función Calculate(). En ella, organizaremos un ciclo doble que iterará todas las combinaciones de símbolos y marcos temporales. Dentro de ellos, comprobaremos si hemos producido una nueva barra según este símbolo y marco temporal. En caso afirmativo, llamaremos a las funciones de cálculo auxiliares. También podemos transmitir a través del parámetro force una indicación para que todos los valores se calculen inmediatamente sin esperar a una nueva barra. Este modo se usará al iniciar el asesor experto para que podamos ver los resultados inmediatamente.

//+------------------------------------------------------------------+
//| Calculate all values                                             |
//+------------------------------------------------------------------+
void Calculate(bool force = false) {
   string symbol;
   ENUM_TIMEFRAMES tf;

// For each symbol and timeframe
   FOREACH_AS(g_symbols, symbol) {
      FOREACH_AS(g_timeframes, tf) {
         // If a new bar has arrived for the given symbol and timeframe, then 
         if(IsNewBar(symbol, tf) || force) {
            // Find the number of candles for calculation
            int n = PeriodSeconds(mainTimeframe_) * mainLength_ / PeriodSeconds(tf);

            // Calculate the average candle sizes
            CalculateAvrSizes(symbol, tf, n);

            // Calculate the lengths of candlestick series
            CalculateSeries(symbol, tf, n);
         }
      }
   }
}

Hemos colocado los cálculos directos en dos funciones auxiliares. Cada uno de ellos realizará el cálculo para un solo símbolo en un marco temporal. Además de estos dos parámetros, también transmitiremos un tercer parámetro: el número de velas sobre el que se realizará el cálculo.

El cálculo del tamaño medio de las velas se efectúa en la función CalculateAvrSizes(). En primer lugar, usamos el nombre del símbolo y el marco temporal para determinar los índices s y t del elemento en las matrices bidimensionales destinadas a almacenar este resultado. Las velas que tengan un precio de apertura igual al precio de cierre se considerarán tanto una vela alcista como una vela bajista. Las medias calculadas se redondearán hasta puntos enteros.

//+------------------------------------------------------------------+
//| Calculate average candle sizes                                   |
//+------------------------------------------------------------------+
void CalculateAvrSizes(string symbol, ENUM_TIMEFRAMES tf, int n) {
// Find the index used for the desired symbol
   int s;
   FIND(g_symbols, symbol, s);

// Find the index used for the desired timeframe
   int t = TimeframeToIndex(tf);

// Array for candles
   MqlRates rates[];

// Copy the required number of candles into the array
   int res = CopyRates(symbol, tf, 1, n, rates);

// If everything was copied, then
   if(res == n) {
      // Number of up and down candles
      int nBuy = 0, nSell = 0;

      // Zero out the elements for the calculated average values
      symbolAvrCandleSizes[s][t] = 0;
      symbolAvrBuyCandleSizes[s][t] = 0;
      symbolAvrSellCandleSizes[s][t] = 0;

      // For all candles
      FOREACH(rates) {
         // Find the candle size
         double size = rates[i].high - rates[i].low;

         // Add it to the total size of all candles
         symbolAvrCandleSizes[s][t] += size;

         // If this is a bullish candle, then we take it into account
         if(IsBuyRate(rates[i])) {
            symbolAvrBuyCandleSizes[s][t] += size;
            nBuy++;
         }

         // If this is a downward candle, take it into account 
         if(IsSellRate(rates[i])) {
            symbolAvrSellCandleSizes[s][t] += size;
            nSell++;
         }
      }

      // Get the size of one point for a symbol
      double point = SymbolInfoDouble(symbol, SYMBOL_POINT);

      // Find the average values in points
      symbolAvrCandleSizes[s][t] /= n * point;
      symbolAvrBuyCandleSizes[s][t] /= nBuy * point;
      symbolAvrSellCandleSizes[s][t] /= nSell * point;

      // Round them to whole points
      symbolAvrCandleSizes[s][t] = MathRound(symbolAvrCandleSizes[s][t]);
      symbolAvrBuyCandleSizes[s][t] = MathRound(symbolAvrBuyCandleSizes[s][t]);
      symbolAvrSellCandleSizes[s][t] = MathRound(symbolAvrSellCandleSizes[s][t]);
   }
}

El cálculo de las longitudes de las series se realiza de forma similar en la función CalculateSeries(). Usa la matriz auxiliar seriesLens, con un tamaño de 100 elementos. El índice del elemento de esta matriz se corresponde con la longitud de la serie, y el propio elemento se corresponde con el número de series de dicha longitud. Así, supondremos que la gran mayoría de las series tienen menos de cien velas de longitud. Solo mostraremos el número de series con una longitud inferior a 10 velas. Al final de la función escribiremos el número de series con exactamente esas longitudes desde la matriz seriesLens a los elementos de matriz correspondientes para los resultados con nombres como symbolCountSeries*.

//+------------------------------------------------------------------+
//| Calculate the lengths of candlestick series                      |
//+------------------------------------------------------------------+
void CalculateSeries(string symbol, ENUM_TIMEFRAMES tf, int n) {
// Find the index used for the desired symbol
   int s;
   FIND(g_symbols, symbol, s);

// Find the index used for the desired timeframe
   int t = TimeframeToIndex(tf);

// Array for candles
   MqlRates rates[];

// Copy the required number of candles into the array
   int res = CopyRates(symbol, tf, 1, n, rates);

// If everything was copied, then
   if(res == n) {
      // Current series length
      int curLen = 0;

      // Direction of the previous candle
      bool prevIsBuy = false;
      bool prevIsSell = false;

      // Array of numbers of series of different lengths (index = series length)
      int seriesLens[];

      // Set the size and initialize
      ArrayResize(seriesLens, 100);
      ArrayInitialize(seriesLens, 0);

      // For all candles
      FOREACH(rates) {
         // Determine the candle direction
         bool isBuy = IsBuyRate(rates[i]);
         bool isSell = IsSellRate(rates[i]);

         // If the direction is the same as the previous one, then
         if((isBuy && prevIsBuy) || (isSell && prevIsSell)) {
            // Increase the series length
            curLen++;
         } else {
            // Otherwise, if the length is within the required range, then
            if(curLen > 1 && curLen < 100) {
               // Increase the counter of the length series
               seriesLens[curLen]++;
            }
            // Reset the current series length
            curLen = 1;
         }
         // Save the direction of the current candle as the previous one
         prevIsBuy = isBuy;
         prevIsSell = isSell;
      }

      // Initialize the array element for the average series length
      symbolAvrSeriesLength[s][t] = 0;
      int count = 0;

      //  For all series lengths we find the sum and quantity
      FOREACH(seriesLens) {
         symbolAvrSeriesLength[s][t] += seriesLens[i] * i;
         count += seriesLens[i];
      }

      // Calculate the average length of candlestick series
      symbolAvrSeriesLength[s][t] /= (count > 0 ? count : 1);

      // Copy the values of the series lengths into the final arrays
      symbolCountSeries2[s][t] = seriesLens[2];
      symbolCountSeries3[s][t] = seriesLens[3];
      symbolCountSeries4[s][t] = seriesLens[4];
      symbolCountSeries5[s][t] = seriesLens[5];
      symbolCountSeries6[s][t] = seriesLens[6];
      symbolCountSeries7[s][t] = seriesLens[7];
      symbolCountSeries8[s][t] = seriesLens[8];
      symbolCountSeries9[s][t] = seriesLens[9];
   }
}

La función Show() será la encargada de mostrar los resultados. En la primera versión, nos limitaremos por ahora a la muestra en el registro del terminal y como comentario en el gráfico en el que se ejecutará el asesor experto. Así pues, nos bastará con presentar los resultados en forma de texto. Una función TextComment() independiente se encargará de dicha representación.

//+------------------------------------------------------------------+
//| Show results                                                     |
//+------------------------------------------------------------------+
void Show() {
// Get the results as text
   string text = TextComment();

// Show it in the comment and in the log
   Comment(text);
   Print(text);
}

En la función de inicialización del EA, tendremos que procesar los parámetros de entrada separando los nombres de los símbolos y los marcos temporales enumerados en valores independientes y preparar las matrices para registrar los resultados de los tamaños requeridos. Después, podemos llamar a la función de cálculo y mostrar los resultados:

//+------------------------------------------------------------------+
//| Initialize the EA                                                |
//+------------------------------------------------------------------+
int OnInit(void) {
// Fill in the symbol array for calculations from the inputs
   SPLIT(symbols_, g_symbols);

// If no symbols are specified, use the current single symbol
   if(ArraySize(g_symbols) == 0) {
      APPEND(g_symbols, Symbol());
   }
// Number of symbols for calculations
   int nSymbols = ArraySize(g_symbols);

// Initialize arrays for calculated values
   Initialize(nSymbols);

// Fill the array with timeframe names from the inputs
   string strTimeframes[];
   SPLIT(timeframes_, strTimeframes);
   ArrayResize(g_timeframes, 0);

// If timeframes are not specified, use the current one
   if(ArraySize(strTimeframes) == 0) {
      APPEND(strTimeframes, TimeframeToString(Period()));
   }

// Fill the timeframe array from the timeframe names array
   FOREACH(strTimeframes) {
      ENUM_TIMEFRAMES tf = StringToTimeframe(strTimeframes[i]);
      if(tf != PERIOD_CURRENT) {
         APPEND(g_timeframes, tf);
      }
   }

// Perform a forced recalculation
   Calculate(true);

// Show the results
   Show();

   return(INIT_SUCCEEDED);
}

En la función anterior, hemos usado la nueva macro SPLIT. Asimismo, hemos añadido al archivo Utils/Macros.mqh de la biblioteca Adwizard. Hasta ahora, esta es la única ampliación de la biblioteca que hemos necesitado para este proyecto.

La macro en sí se ha diseñado para dividir una cadena en partes mediante dos posibles caracteres delimitadores: coma y punto y coma.

#define SPLIT(V, A)      { string s=V; StringReplace(s, ";", ","); StringSplit(s, ',', A); }

Veamos ahora los resultados del EA desarrollado.


Probar el asesor experto

Ejecutaremos el EA con los parámetros por defecto en algún gráfico. Como resultado, podremos ver más o menos esta imagen:

Figura 1. Resultados del asesor experto con parámetros por defecto en AUDCAD H1

Como al mostrar el comentario en el gráfico se utiliza un tipo de letra no monoespaciado, no resulta especialmente cómodo visualizar los valores obtenidos en el gráfico. En el registro del terminal, la fuente es monoespaciada, por lo que se verá mejor allí. Veamos cómo se ven los resultados para múltiples símbolos y marcos temporales.

Ahora ejecutaremos el asesor experto con estos parámetros de entrada:

Y veremos el resultado.

Figura 2. Resultados del asesor experto para varios símbolos y marcos temporales

Ya hemos completado con éxito los cálculos para múltiples símbolos y marcos temporales. Los resultados se presentan en forma de tabla. Sí, todavía no resulta muy cómodo utilizarlos, pero para un rápido análisis preliminar esta pantalla es más que suficiente.



Conclusión

Bien, hoy hemos implementado la primera versión de un asesor auxiliar informador que muestra información sobre el tamaño medio de las velas en pips y las longitudes de las series de velas unidireccionales. A primera vista, esto tiene una relación un tanto indirecta con nuestro proyecto principal de crear un sistema de optimización automática y ejecutar asesores expertos multidivisa que apliquen diversas estrategias sencillas. Esto realmente es así, por lo que continuaremos trabajando sobre este EA fuera del ámbito de esta serie de artículos. Pero durante el propio trabajo, probaremos y ensayaremos la aplicación de muchas cosas que luego esperamos aplicar también con éxito al proyecto principal.

Ya hemos hecho un trabajo considerable. La selección de una estructura diferente y más óptima de organización del código permitirá paralelizar el trabajo en diferentes direcciones de desarrollo futuro de la biblioteca Adwizard. Y estas direcciones ya se están estudiando a fondo. Una de ellas consiste en construir una interfaz visual para gestionar el trabajo de los asesores finales. Y el proyecto analizado en este artículo nos ayudará, sin empantanarnos en la aplicación de cosas muy complicadas, a probar los distintos enfoques posibles. Una vez estudiadas sus ventajas y desventajas, y tras seleccionar el más adecuado, ya podremos centrarnos en el desarrollo del proyecto principal.

Gracias por su atención, ¡hasta pronto!


Advertencia importante

Todos los resultados expuestos en este artículo y en todos los artículos anteriores de la serie se basan únicamente en datos de pruebas históricas y no ofrecen ninguna garantía de lograr beneficios en el futuro. El trabajo de este proyecto es de carácter exploratorio. Todos los resultados publicados pueden ser usados por cualquiera bajo su propia responsabilidad.


Contenido del archivo

#
 Nombre
Versión  Descripción  Cambios recientes
  SymbolsInformer   Carpeta de trabajo del proyecto  
1 SymbolsInformer.mq5 1.00
Asesor experto mostrar información sobre las longitudes de las series de velas unidireccionales
Parte 26
  SymbolsInformer/Include/Adwizard/Base
  Clases básicas de las que heredan otras clases del proyecto    
2 Advisor.mqh 1.04. Clase básica del experto Parte 10
3 Factorable.mqh
1.05
Clase básica de objetos creados a partir de una cadena (string)
Parte 24
4 FactorableCreator.mqh
1.00 Clase creadora que enlaza nombres y constructores estáticos de clases herederas CFactorable Parte 24
5 Interface.mqh 1.01
Clase básica de visualización de diversos objetos
Parte 4
6 Receiver.mqh
1.04.  Clase básica de transferencia de volúmenes abiertos a posiciones de mercado
Parte 12
7 Strategy.mqh
1.04.
Clase básica de estrategia comercial
Parte 10
  SymbolsInformer/Include/Adwizard/Database
  Archivos para trabajar con todo tipo de bases de datos utilizadas por los asesores de proyectos
 
8 Database.mqh 1.12 Clase para trabajar con la base de datos Parte 25
9 db.adv.schema.sql 1.00
Esquema de la base de datos del asesor final Parte 22
10 db.cut.schema.sql
1.00 Esquema de una base de datos de optimización truncada
Parte 22
11 db.opt.schema.sql
1.05  Esquema de la base de datos de optimización
Parte 22
12 Storage.mqh   1.01
Clase de trabajo con almacenamiento Key-Value para el asesor experto final en la base de datos del asesor experto.
Parte 23
  SymbolsInformer/Include/Adwizard/Experts
  Archivos con partes comunes de asesores usados de distintos tipos
 
13 Expert.mqh  1.22 Archivo de biblioteca para el asesor final. Los parámetros del grupo pueden tomarse de la base de datos del asesor experto
Parte 23
14 Optimization.mqh  1.04. Archivo de biblioteca para el asesor experto que controla el inicio de las tareas de optimización
Parte 23
15 Stage1.mqh
1.19 Archivo de biblioteca del asesor experto para optimizar una única instancia de una estrategia comercial (Etapa 1)
Parte 23
16 Stage2.mqh 1.04. Archivo de biblioteca del asesor experto para optimizar un grupo de instancias de estrategias comerciales (Etapa 2)   Parte 23
17 Stage3.mqh
1.04. Archivo de biblioteca para el asesor experto que guarda el grupo normalizado generado de estrategias en la base de datos del asesor experto con un nombre especificado. Parte 23
  SymbolsInformer/Include/Adwizard/Optimization
  Clases responsables del trabajo de optimización automática
 
18 OptimizationJob.mqh 1.00 Clase para el funcionamiento de la etapa de proyecto de optimización
Parte 25
19 OptimizationProject.mqh 1.00 Clase para el proyecto de optimización Parte 25
20 OptimizationStage.mqh 1.00 Clase para la fase del proyecto de optimización Parte 25
21 OptimizationTask.mqh 1.00 Clase para la tarea de optimización (para la creación) Parte 25
22 Optimizer.mqh
1.03  Clase para el gestor de optimización automática de proyectos
Parte 22
23 OptimizerTask.mqh
1.03
Clase para la tarea de optimización (para el transportador)
Parte 22
  SymbolsInformer/Include/Adwizard/Strategies    Ejemplos de estrategias comerciales usadas para demostrar el funcionamiento del proyecto
 
24 HistoryStrategy.mqh 
1.00 Clase de estrategia comercial para reproducir la historia de transacciones
Parte 16
25 SimpleVolumesStrategy.mqh
1.11
Clase de estrategia comercial con uso de volúmenes de ticks
Parte 22
  SymbolsInformer/Include/Adwizard/Utils
  Utilidades auxiliares, macros para la reducción del código

26 ExpertHistory.mqh 1.00 Clase para exportar la historia de transacciones a un archivo Parte 16
27 Macros.mqh 1.07 Macros útiles para operaciones con arrays Parte 26
28 NewBarEvent.mqh 1.00  Clase de definición de una nueva barra para un símbolo específico  Parte 8
29 SymbolsMonitor.mqh  1.00 Clase de obtención de información sobre instrumentos comerciales (símbolos) Parte 21
  SymbolsInformer/Include/Adwizard/Virtual
  Clases para crear diversos objetos unidos mediante un sistema de órdenes y posiciones comerciales virtuales
 
30 Money.mqh 1.01  Clase básica de gestión de capital
Parte 12
31 TesterHandler.mqh  1.07 Clase para gestionar los eventos de optimización  Parte 23
32 VirtualAdvisor.mqh  1.10  Clase de asesor experto para trabajar con posiciones (órdenes) virtuales Parte 24
33 VirtualChartOrder.mqh  1.01  Clase de posición virtual gráfica Parte 18
34 VirtualHistoryAdvisor.mqh 1.00  Clase experta para reproducir la historia de transacciones  Parte 16
35 VirtualInterface.mqh  1.00  Clase de GUI del asesor  Parte 4
36 VirtualOrder.mqh 1.09  Clase de órdenes y posiciones virtuales  Parte 22
37 VirtualReceiver.mqh 1.04. Clase de transferencia de los volúmenes abiertos a las posiciones de mercado (receptor)  Parte 23
38 VirtualRiskManager.mqh  1.05 Clase de gestión de riesgos (gestor de riesgos)  Parte 24
39 VirtualStrategy.mqh 1.09  Clase de estrategia comercial con posiciones virtuales  Parte 23
40 VirtualStrategyGroup.mqh  1.03  Clase de grupo o grupos de estrategias comerciales Parte 24
41 VirtualSymbolReceiver.mqh  1.00 Clase de receptor simbólico  Parte 3

El código fuente también está disponible en los repositorios públicos SymbolsInformer y Adwizard

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

Archivos adjuntos |
SymbolsInformer.zip (123.2 KB)
Sergey Chalyshev
Sergey Chalyshev | 25 abr 2025 en 13:36
Es una pena.
Rashid Umarov
Rashid Umarov | 25 abr 2025 en 20:37
Sergey Chalyshev #:
Es una pena(

Mañana echaré un vistazo a su crítica informativa

Arup Nag
Arup Nag | 25 abr 2025 en 21:49

@Rashid Umarov

Hola

¿Estás siguiendo este hilo de cerca y eres capaz de ponerlo todo en práctica y optimizarlo?

¿Puedes ayudarme a hacerlo?

Saludos

Websockets para MetaTrader 5: conexiones de cliente asíncronas con la API de Windows Websockets para MetaTrader 5: conexiones de cliente asíncronas con la API de Windows
Este artículo detalla el desarrollo de una biblioteca personalizada vinculada dinámicamente y diseñada para facilitar las conexiones asíncronas de clientes WebSocket para las aplicaciones MetaTrader 5.
Pronosticamos barras Renko con ayuda de IA CatBoost Pronosticamos barras Renko con ayuda de IA CatBoost
¿Cómo utilizar las barras Renko junto con la IA? Hoy analizaremos el trading Renko en Fórex con una precisión de previsión del 59,27%. Asimismo, exploraremos las ventajas de las barras Renko para filtrar el ruido del mercado, aprenderemos por qué los indicadores de volumen son más importantes que los patrones de precios y cómo establecer el tamaño óptimo del bloque Renko para el EURUSD. s decir, veremos una guía paso a paso para integrar CatBoost, Python y MetaTrader 5 para crear nuestro propio sistema de previsión Forex Renko. Resulta ideal para tráders que buscan ir más allá del análisis técnico tradicional.
Simulación de mercado (Parte 17): Sockets (XI) Simulación de mercado (Parte 17): Sockets (XI)
Implementar la parte que se ejecutará aquí en MetaTrader 5 no es complicado. Pero hay diversos aspectos a los que hay que prestar atención. Esto es para que tú, querido lector, consigas hacer que el sistema funcione de verdad. Recuerda una cosa: no se ejecutará un único programa. En realidad, estarás ejecutando tres programas a la vez. Es importante que cada uno se implemente y se construya de forma que trabajen y se comuniquen entre sí. Es crucial que cada uno sepa qué está intentando o deseando hacer el otro.
Creación de un sistema personalizado de detección de regímenes de mercado en MQL5 (Parte 2): Asesor experto Creación de un sistema personalizado de detección de regímenes de mercado en MQL5 (Parte 2): Asesor experto
Este artículo detalla la construcción de un Asesor Experto Adaptativo (MarketRegimeEA) utilizando el detector de régimen de la Parte 1. Cambia automáticamente las estrategias comerciales y los parámetros de riesgo para mercados con tendencia, rango o volátiles. Se incluyen optimización práctica, manejo de transiciones y un indicador de múltiples marcos de tiempo.