Русский 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 |
40 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:

//+------------------------------------------------------------------+
//| Входные параметры                                                |
//+------------------------------------------------------------------+
input group "::: Период для расчётов"
sinput ENUM_TIMEFRAMES mainTimeframe_  = PERIOD_D1;   // Основной таймфрейм
input int         mainLength_          = 30;          // Количество свечей основного таймфрейма

input group "::: Символы и таймфреймы"
sinput string     symbols_             = "";          // Символы (через запятую)
sinput string     timeframes_          = "";          // Таймфреймы (напр. 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.

// Количество существующих таймфреймов
#define TFN (21)

// Глобальные переменные
string g_symbols[];                       // Массив всех используемых символов
ENUM_TIMEFRAMES g_timeframes[];           // Массив всех используемых таймфреймов

// Массивы расчётных значений.
// Первый индекс - символ, второй индекс - таймфрейм
double symbolAvrCandleSizes[][TFN];       // Массив средних размеров всех свечей
double symbolAvrBuyCandleSizes[][TFN];    // Массив средних размеров свечей вверх
double symbolAvrSellCandleSizes[][TFN];   // Массив средних размеров свечей вниз

double symbolAvrSeriesLength[][TFN];      // Массив средних длин серий

int symbolCountSeries2[][TFN];            // Массив количества серий длины 2
int symbolCountSeries3[][TFN];            // Массив количества серий длины 3
int symbolCountSeries4[][TFN];            // Массив количества серий длины 4
int symbolCountSeries5[][TFN];            // Массив количества серий длины 5
int symbolCountSeries6[][TFN];            // Массив количества серий длины 6
int symbolCountSeries7[][TFN];            // Массив количества серий длины 7
int symbolCountSeries8[][TFN];            // Массив количества серий длины 8
int symbolCountSeries9[][TFN];            // Массив количества серий длины 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)

// Массив всех таймфреймов
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
};

//+------------------------------------------------------------------+
//| Преобразование строкового названия в таймфрейм                   |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES   StringToTimeframe(string s) {
// Если в строке есть символ "_", то оставляем только символы, идущие после него
   int pos = StringFind(s, "_");
   if(pos != -1) {
      s = StringSubstr(s, pos + 1);
   }

// Переводим в верхний регистр
   StringToUpper(s);

// Массивы соответствующих строковых названий таймфреймов
   string keys[] = {"M1", "M2", "M3", "M4", "M5", "M6", "M10", "M12", "M15", "M20", "M30",
                    "H1", "H2", "H3", "H4", "H6", "H8", "H12", "D1", "W1", "MN1"
                   };

// Ищем соответствие и возвращаем, если нашли
   FOREACH(keys) {
      if(keys[i] == s)
         return tfValues[i];
   }

   return PERIOD_CURRENT;
}

//+------------------------------------------------------------------+
//| Преобразование таймфрейма в строковое название                   |
//+------------------------------------------------------------------+
string TimeframeToString(ENUM_TIMEFRAMES tf) {
// Получаем название таймфрейма вида 'PERIOD_*'
   string s = EnumToString(tf);

// Возвращаем часть названия после символа '_'
   return StringSubstr(s, StringFind(s, "_") + 1);
}

//+------------------------------------------------------------------+
//| Преобразование таймфрейма в индекс в массиве всех таймфреймов    |
//+------------------------------------------------------------------+
int   TimeframeToIndex(ENUM_TIMEFRAMES tf) {
// Ищем соответствие и возвращаем, если нашли
   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.

//+------------------------------------------------------------------+
//| Расчёт всех величин                                              |
//+------------------------------------------------------------------+
void Calculate(bool force = false) {
   string symbol;
   ENUM_TIMEFRAMES tf;

// Для каждого символа и таймфрейма
   FOREACH_AS(g_symbols, symbol) {
      FOREACH_AS(g_timeframes, tf) {
         // Если для данного символа и таймфрейма наступил новый бар, то
         if(IsNewBar(symbol, tf) || force) {
            // Находим количество свечей для расчёта
            int n = PeriodSeconds(mainTimeframe_) * mainLength_ / PeriodSeconds(tf);

            // Рассчитываем средние размеры свечей
            CalculateAvrSizes(symbol, tf, n);

            // Рассчитываем длины серий свечей
            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.

//+------------------------------------------------------------------+
//| Расчёт средних размеров свечей                                  |
//+------------------------------------------------------------------+
void CalculateAvrSizes(string symbol, ENUM_TIMEFRAMES tf, int n) {
// Находим индекс, который используется для нужного символа
   int s;
   FIND(g_symbols, symbol, s);

// Находим индекс, который используется для нужного таймфрейма
   int t = TimeframeToIndex(tf);

// Массив для свечей
   MqlRates rates[];

// Копируем нужное количество свечей в массив
   int res = CopyRates(symbol, tf, 1, n, rates);

// Если всё скопировалось, то
   if(res == n) {
      // Количество свечей вверх и вниз
      int nBuy = 0, nSell = 0;

      // Обнуляем элементы для расчётных средних значений
      symbolAvrCandleSizes[s][t] = 0;
      symbolAvrBuyCandleSizes[s][t] = 0;
      symbolAvrSellCandleSizes[s][t] = 0;

      // Для всех свечей
      FOREACH(rates) {
         // Находим размер свечи
         double size = rates[i].high - rates[i].low;

         // Добавляем его в суммарный размер всех свечей
         symbolAvrCandleSizes[s][t] += size;

         // Если это свеча вверх, то учитываем её
         if(IsBuyRate(rates[i])) {
            symbolAvrBuyCandleSizes[s][t] += size;
            nBuy++;
         }

         // Если это свеча вниз, то учитываем её
         if(IsSellRate(rates[i])) {
            symbolAvrSellCandleSizes[s][t] += size;
            nSell++;
         }
      }

      // Получаем размер одного пункта для символа
      double point = SymbolInfoDouble(symbol, SYMBOL_POINT);

      // Находим средние значения в пунктах
      symbolAvrCandleSizes[s][t] /= n * point;
      symbolAvrBuyCandleSizes[s][t] /= nBuy * point;
      symbolAvrSellCandleSizes[s][t] /= nSell * point;

      // Округляем их до целых пунктов
      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*.

//+------------------------------------------------------------------+
//| Расчёт длин серий свечей                                         |
//+------------------------------------------------------------------+
void CalculateSeries(string symbol, ENUM_TIMEFRAMES tf, int n) {
// Находим индекс, который используется для нужного символа
   int s;
   FIND(g_symbols, symbol, s);

// Находим индекс, который используется для нужного таймфрейма
   int t = TimeframeToIndex(tf);

// Массив для свечей
   MqlRates rates[];

// Копируем нужное количество свечей в массив
   int res = CopyRates(symbol, tf, 1, n, rates);

// Если всё скопировалось, то
   if(res == n) {
      // Текущая длина серии
      int curLen = 0;

      // Направление предыдущей свечи
      bool prevIsBuy = false;
      bool prevIsSell = false;

      // Массив количеств серий разной длины (индекс = длина серии)
      int seriesLens[];

      // Устанавливаем размер и инициализируем
      ArrayResize(seriesLens, 100);
      ArrayInitialize(seriesLens, 0);

      // Для всех свечей
      FOREACH(rates) {
         // Определяем направление свечи
         bool isBuy = IsBuyRate(rates[i]);
         bool isSell = IsSellRate(rates[i]);

         // Если направление совпадает с предыдущим, то
         if((isBuy && prevIsBuy) || (isSell && prevIsSell)) {
            // Увеличиваем длину серии
            curLen++;
         } else {
            // Иначе если длина попадает в требуемый диапазон, то
            if(curLen > 1 && curLen < 100) {
               // Увеличивем счётчик серий этой длины
               seriesLens[curLen]++;
            }
            // Сбрасываем текущую длину серии
            curLen = 1;
         }
         // Запоминаем направление текущей свечи как предыдущее
         prevIsBuy = isBuy;
         prevIsSell = isSell;
      }

      // Инициализируем элемент массива для средней длины серии
      symbolAvrSeriesLength[s][t] = 0;
      int count = 0;

      // Для всех длин серий находим сумму и количество
      FOREACH(seriesLens) {
         symbolAvrSeriesLength[s][t] += seriesLens[i] * i;
         count += seriesLens[i];
      }

      // Вычисляем среднюю длину серий свечей
      symbolAvrSeriesLength[s][t] /= (count > 0 ? count : 1);

      // Копируем значения длин серий в итоговые массивы
      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.

//+------------------------------------------------------------------+
//| Показ результатов                                                |
//+------------------------------------------------------------------+
void Show() {
// Получаем результаты в виде текста
   string text = TextComment();

// Показываем его в комментарии и в логе
   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:

//+------------------------------------------------------------------+
//| Инициализация советника                                          |
//+------------------------------------------------------------------+
int OnInit(void) {
// Наполняем массив символов для расчётов из входных параметров
   SPLIT(symbols_, g_symbols);

// Если символы не указаны, используем один текущий символ
   if(ArraySize(g_symbols) == 0) {
      APPEND(g_symbols, Symbol());
   }
// Количество символов для расчётов
   int nSymbols = ArraySize(g_symbols);

// Инициализируем массивы для расчётных значений
   Initialize(nSymbols);

// Наполняем массив названий таймфреймов из входных параметров
   string strTimeframes[];
   SPLIT(timeframes_, strTimeframes);
   ArrayResize(g_timeframes, 0);

// Если таймфреймы не указаны, используем один текущий таймфрейм
   if(ArraySize(strTimeframes) == 0) {
      APPEND(strTimeframes, TimeframeToString(Period()));
   }

// Наполняем массив таймфреймов из массива названий таймфреймов
   FOREACH(strTimeframes) {
      ENUM_TIMEFRAMES tf = StringToTimeframe(strTimeframes[i]);
      if(tf != PERIOD_CURRENT) {
         APPEND(g_timeframes, tf);
      }
   }

// Выполняем принудительный перерасчёт
   Calculate(true);

// Показываем результаты
   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 (130.77 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.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
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.