English Русский 中文 Deutsch 日本語 Português
Sincronización de varios gráficos del mismo símbolo en timeframes diferentes

Sincronización de varios gráficos del mismo símbolo en timeframes diferentes

MetaTrader 5Ejemplos | 19 abril 2018, 13:56
5 730 3
Dmitriy Gizlyk
Dmitriy Gizlyk

Introducción

Desde la época de Elder y hasta nuestros días, los traders toman las decisiones comerciales analizando los gráficos en los marcos temporales (timeframe) diferentes. Creo que cada uno conoce la situación cuando en los gráficos se colocan los objetos que demuestran las tendencias globales, y después, se analiza el comportamiento del precio cerca de los objetos colocados en los timeframes menores. En el proceso de este análisis, los objetos creados anteriormente pueden ser corregidos. Los medios existentes del MetaTrader 5 permiten realizar este trabajo en un gráfico mediante el cambio del timeframe, guardando los objetos colocados. ¿Pero qué hacemos si haya que monitorear el precio en varios gráficos simultáneamente?

Como opción, podemos utilizar las plantillas en estas situaciones. Pero al cambiar incluso uno de los objetos, es necesarios guardar la plantilla de nuevo y aplicarla nuevamente a cada uno de los gráficos. En el presente artículo, yo propongo automatizar este proceso y designar la sincronización de los gráficos al indicador.


1. Planteamiento del problema

El principal objetivo de nuestro indicador consiste en sincronizar los gráficos en MetaTrader 5. En este caso, el programa debe definir los gráficos necesarios según el instrumento financiero. Al mismo tiempo, el programa tiene que monitorear constantemente el estado de todos los objetos gráficos en los gráficos en custión. El programa tiene que repetir cada cambio del objeto de uno de los gráficos en los demás gráficos.

Hay dos modos para monitorear el estado de los objetos gráficos. El primer modo consiste en determinar la periodicidad de comprobar y sincronizar todos los objetos colocados. La ventaja de este enfoque es que una instancia del programa en uno de los gráficos es suficiente para nosotros. Pero hay dos problemas:

  • el retardo de la sincronización provocado por la periodicidad de la actualización;
  • es complicado determinar cuál es el estado del objeto se considera como último.

A primera vista, ambas cuestiones se solucionan aumentando la frecuencia de la sincronización y del guardado de la última información sincronizada sobre los objetos en las variables del programa o en el archivo en el disco. Pero, cuando crece el número de los objetos en los gráficos, se aumenta el tiempo de la ejecución de cada ciclo, y el volumen de la información almacenada. Además, hay que tener en cuenta que los indicadores se inician en el flujo común de MetaTrader 5, a diferencia de los Asesores Expertos (EAs) Por esa razón, la carga excesiva sobre el indicador puede provocar el retardo en la ejecución de otros indicadores, y en el terminal en general.

El segundo enfoque es el siguiente: encargar el monitoreo del cambio de los objetos al terminal, e iniciar la sincronización de los objetos de acuerdo con los eventos del terminal, procesándolos en la función OnChartEvent. Este enfoque permite al programa reaccionar inmediatamente después de la creación o el cambio del objeto, y, de esta manera, reducir al máximo el retardo. Por esta razón, no es necesario guardar la información sobre todos los objetos sincronizados ni comprobar su estado en todos los gráficos periódicamente. Eso reduce considerablemente la carga del programa.

Puede parecer que la segunda variante nos conviene al cien por cien. Pero aquí también hay «una gota de acibar....»: es que solamente los eventos del gráfico en el que tenemos iniciado el programa llaman a la función OnChartEvent. Y eso, no nos pararía, si hubiéramos despejado la situación con el gráfico Maestro, en el cual se realizarían todos los cálculos. Así, sólo una instancia del indicador nos sería suficiente. Pero no queremos estar limitados con un solo gráfico para el cambio de los objetos. Necesitamos iniciar las instancias del indicador en cada uno de los gráficos. Puede realizar este trabajo personalmente, o bien automatizarlo usando la función ChartIndicatorAdd.

Tomando en cuenta todo los dicho, he elegido la segunda opción para implementar el programa. De esta manera, podemos dividir el trabajo de nuestro indicador en dos bloques.

  1. Cuando se inicia el indicador, los gráficos abiertos se filtran por el símbolo. Se comprueba la presencia del indicador en los gráficos abiertos del instrumento financiero correspondiente. Todos los objetos del gráfico actual se clonan en los gráficos seleccionados.

  2. Procesamiento de los eventos del gráfico. Cuando aparece el evento de la creación o del cambio de un objeto gráfico, el programa lee la información desde el gráfico sobre el objeto modificado y pasa estos datos a todos los gráficos de la lista creada anteriormente.


Durante el proceso del trabajo de nuestro programa, el usuario puede abrir y cerrar los gráficos. Por esta razón, con el fin de mantener la actualidad de la lista de los gráficos, yo propondría iniciar el primer proceso usando el temporizador con una determinada periodicidad.


2. Organización del trabajo con los gráficos

El primer proceso es la clonación del indicador en los gráficos. Para realizar esta tarea, vamos a crear la clase CCloneIndy. Guardaremos el nombre del instrumento financiero, del indicador y la ruta para llamar al indicador en sus variables. La clase tendrá una función pública (SearchCharts) para seleccionar los gráficos necesarios. Esta función va a recibir el identificador del gráfico inicial, y va a devolver el array de los gráficos seleccionados.

class CCloneIndy
  {
private:
   string            s_Symbol;
   string            s_IndyName;
   string            s_IndyPath;

public:
                     CCloneIndy();
                    ~CCloneIndy();
   bool              SearchCharts(long chart,long &charts[]);

protected:
   bool              AddChartToArray(const long chart,long &charts[]);
   bool              AddIndicator(const long master_chart,const long slave_chart);
  };

Al inicializar la clase, guardamos los datos iniciales en las variables y denominamos un nombre corto del indicador. Lo vamos a necesitar para obtener los parámetros necesarios al iniciar las copias del indicador.

CCloneIndy::CCloneIndy()
  {
   s_Symbol=_Symbol;
   s_IndyName=MQLInfoString(MQL_PROGRAM_NAME);
   s_IndyPath=MQLInfoString(MQL_PROGRAM_PATH);
   int pos=StringFind(s_IndyPath,"\\Indicators\\",0);
   if(pos>=0)
     {
      pos+=12;
      s_IndyPath=StringSubstr(s_IndyPath,pos);
     }
   IndicatorSetString(INDICATOR_SHORTNAME,s_IndyName);
  }

2.1. Función para seleccionar los gráficos según el instrumento financiero

Vamos a analizar detalladamente como trabaja la función de la selección de los gráficos necesarios. Primero, comprobaremos el identificador del gráfico maestro traspasado en los parámetros de la función. Si no es válido, la función devuelve inmediatamente false. Si el identificador no está establecido, asignamos el identificador del gráfico actual al parámetro. Luego, comprobamos si el nombre del instrumento guardado corresponde al símbolo del gráfico maestro. Si coinciden, reescribimos el nombre del instrumento para la filtración de los gráficos.

bool CCloneIndy::SearchCharts(long chart,long &charts[])
  {
   switch((int)chart)
     {
      case -1:
        return false;
        break;
      case 0:
        chart=ChartID();
        break;
      default:
        if(s_Symbol!=ChartSymbol(chart))
           s_Symbol=ChartSymbol(chart);
        break;
     }

En el siguiente paso, organizamos el repaso de todos los gráficos abiertos. Si el identificador del gráfico comprobado coincide con el identificador del gráfico maestro, pasamos al siguiente.

   long check_chart=ChartFirst();
   while(check_chart!=-1)
     {
      if(check_chart==chart)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Luego, comprobamos si el símbolo del gráfico analizado corresponde al que buscamos. Si el instrumento no satisface la condición de la búsqueda, pasamos al siguiente gráfico.

      if(ChartSymbol(check_chart)!=s_Symbol)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Después de eso comprobamos si nuestro indicador se encuentra en el gráfico comprobado. Si ya está adjuntado, guardamos el identificador en el array y pasamos al siguiente.

      int handl=ChartIndicatorGet(check_chart,0,s_IndyName);
      if(handl!=INVALID_HANDLE)
        {
         AddChartToArray(check_chart,charts);
         check_chart=ChartNext(check_chart);
         continue;
        }

Si el indicador todavía no se encuentra en el gráfico comprobado, iniciamos la función de su llamada, indicando los identificadores del gráfico maestro y del gráfico que se comprueba. Si la operación es fallida, pasamos al siguiente gráfico, y a este gráfico intentamos asignar el indicador durante la siguiente llamada de la función.

      if(mi.TypeMenuItem()!=MI_HAS_CONTEXT_MENU)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Si el indicador se ha adjuntado al gráfico con éxito, añadimos el identificador de este gráfico al array. En este caso, se inicia la función de la clonación de todos los objetos desde el gráfico maestro al que se comprueba.

      AddChartToArray(check_chart, charts);
      check_chart=ChartNext(check_chart);
     }
//---
   return true;
  }

Una vez terminado el ciclo, salimos de la función y devolvemos el resultado true.

2.2. Función de la llamada al indicador

Analizaremos detenidamente la función de la referencia del indicador con el gráfico. En los parámetros, ella recibe los identificadores del gráfico maestro y del gráfico receptor. Su correspondencia se verifica en el principio de la función: los identificadores tienen que ser válidos y no iguales.

bool CCloneIndy::AddIndicator(const long master_chart,const long slave_chart)
  {
   if(master_chart<0 || slave_chart<=0 || master_chart==slave_chart)
      return false;

Luego, obtenemos el manejador (handle) del indicador en el gráfico maestro. Si es inválido, salimos de la función con el resultado false.

   int master_handle=ChartIndicatorGet(master_chart,0,s_IndyName);
   if(master_handle==INVALID_HANDLE)
      return false;

Si el handle es válido, obtenemos los parámetros para llamar al indicador análogo para el gráfico nuevo. Si hay error en obtención de los parámetros, salimos de la función con el resultado false.

   MqlParam params[];
   ENUM_INDICATOR type;
   if(IndicatorParameters(master_handle,type,params)<0)
      return false;

En el siguiente paso, en la primera celda del array, escribimos la ruta para llamar al indicador guardada durante la inicialización; y en la segunda celda, escribimos el identificador del gráfico que va a servir para el indicador. Averiguamos el timeframe del gráfico receptor y llamamos al indicador. Si la invocación del indicador falla, salimos de la función con el resultado false.

0.
   params[1].integer_value=slave_chart;
   ENUM_TIMEFRAMES Timeframe=ChartPeriod(slave_chart);
   int slave_handle=IndicatorCreate(s_Symbol,Timeframe,type,ArraySize(params),params);
   if(slave_handle<0)
      return false;

Al final de la función, añadimos el indicador obtenido según el handle al gráfico receptor.

   return ChartIndicatorAdd(slave_chart,0,slave_handle);
  }

Probablemente, puede surgir una pregunta, ¿porqué no se puede adjuntar el indicador al gráfico receptor según el handle desde el gráfico maestro de inmediato? La respuesta es sencilla: para eso, el indicador y el gráfico tienen que corresponder según el instrumento y el timeframe. Mientras que nuestras tareas suponen que el timeframe de los gráficos será diferente.

Encontrará el código fuente de la clase en los archivos adjuntos.

3. Clases para trabajar con los objetos gráficos

El siguiente proceso de nuestro programa es el procesamiento de los eventos y el traspaso de los datos sobre los objetos gráficos en otros gráficos. Antes de empezara a escribir el código, es necesario determinar la tecnología del traspaso de datos entre los gráficos.

Los medios de MQL5 permiten a los programas de un gráfico crear y modificar los objetos en otro gráfico, mediante la especificación del identificador del gráfico en las funciones del trabajo con objetos gráficos. Eso conviene para trabajar con una cantidad pequeña de los gráficos y objetos gráficos.

No obstante, existe otra opción. Antes, hemos decidido usar los eventos para monitorear la modificación de los objetos en el gráfico. Incluso hemos escrito el código para añadir las copias del indicador a todos los gráficos que nos interesan. ¿Entonces, porqué no podemos usar el modelos de eventos para traspasar los datos sobre los objetos modificados entre los indicadores en los gráficos diferentes? El trabajo con los objetos será encargado para el indicador que se encuentra en el gráfico. Este enfoque nos permitirá distribuir el trabajo con los objetos entre todos los indicadores y crear un cierto modelo asincrónico.

Suena bien, pero, como se sabe, la función OnChartEvent recibe sólo 4 parámetros:

  • identificador del evento;
  • parámetro del evento del tipo long;
  • parámetro del evento del tipo double;
  • parámetro del evento del tipo string.

¿Pero cómo podemos meter toda la información sobre el objeto en estos 4 parámetros? Simplemente vamos a pasar el identificador del evento, mientras que toda la información sobre el objeto va a escribirse en el parámetro tipo string. Para recopilar la información sobre el objeto en una variable tipo string, usaremos los materiales del artículo «Uso de los repositorios en la nube para el intercambio de datos entre los terminales»

Creamos la clase CCloneObjects, que va a recopilar los datos sobre los objetos gráficos y mostrarlos luego en el gráfico.

class CCloneObjects
  {
private:
   string            HLineToString(long chart, string name, int part);
   string            VLineToString(long chart, string name, int part);
   string            TrendToString(long chart, string name, int part);
   string            RectangleToString(long chart, string name, int part);
   bool              CopySettingsToObject(long chart,string name,string &settings[]);

public:
                     CCloneObjects();
                    ~CCloneObjects();
//---
   string            CreateMessage(long chart, string name, int part);
   bool              DrawObjects(long chart, string message);
  };

El trabajo de esta clase ya ha sido descrito en detalle: creo que no hay sentido repetir su descripción aquí. Pero creo que merece la pena fijarse en un detalle: cuando la función EventChartCustom genera un evento personalizado, la longitud del parámetro sparam se limita con 63 caracteres. Por eso, al traspasar los datos sobre el objeto a otros gráficos, vamos a dividir el mensaje en dos partes. Por esa razón, en la función de la creación del mensaje, ha sido añadido el parámetro para indicar la porción necesaria de datos. Como ejemplo, abajo se muestra el código de la función para recopilar la información sobre la línea de tendencia.

string CCloneObjects::TrendToString(long chart,string name, int part)
  {
   string result = NULL;
   if(ObjectFind(chart,name)!=0)
      return result;
   
   switch(part)
     {
      case 0:
        result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=0="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,0),5)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,0))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=1="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,1),5)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=1="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,1))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_LEFT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_LEFT))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_RIGHT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_RIGHT))+"|";
        break;
      default:
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_COLOR)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_COLOR))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_WIDTH)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_WIDTH))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_STYLE)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_STYLE))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_BACK)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_BACK))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TEXT)+"="+ObjectGetString(chart,name,OBJPROP_TEXT)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TOOLTIP)+"="+ObjectGetString(chart,name,OBJPROP_TOOLTIP);
        break;
     }
   return result;
  }

En los archivos adjuntos, encontrará el código de todas las funciones.


4. Componemos el indicador

Tenemos todo preparado. Ahora vamos a componer nuestro indicador para monitorear y clonar los objetos gráficos. Tendremos un solo parámetro para nuestro indicador (identificador del gráfico).

sinput long    Chart =  0;

Cuando el indicador se inicia, el valor de este parámetro siempre tiene que ser igual a cero. Puede surgir la siguiente pregunta, ¿para qué tenemos que introducir el parámetro que nunca va a cambiarse?

Su valor va a cambiar, cuando el indicador se inicia desde el programa con el fin de ser adjuntado a otros gráficos.

Es que la función ChartID devuelve el identificador del gráfico del cual ha sido invocado el indicador, y no del cual al que ha sido adjuntado. Eso está relacionado con las particularidades del procesamiento de los indicadores en MetaTrader 5. Si el mismo indicador se invoca varias veces para el mismo instrumento y timeframe, se inicia sólo una vez (durante la primera llamada). Las siguientes llamadas a él -incluso desde otros gráficos- llaman al indicador ya iniciado. En su lugar, el indicador trabaja en su gráfico y devuelve la información sobre él. De esta manera, cuando se invocan las instancias del indicador en la clase CCloneIndy, las nuevas copias del indicador van a trabajar y devolver la información sobre el gráfico desde el cual ha sido iniciada su primera instancia. Para evitar eso, tenemos que especificar para cada instancia del indicador el gráfico el cual va a procesar.

Vamos a analizar el código del indicador con más detalles. Declaramos lo siguiente en las variables globales:

  • instancias de las clases creadas anteriormente
  • variables para almacenar el identificador del gráfico de trabajo
  • array para almacenar los identificadores de los gráficos a los cuales se clonarán los objetos
CCloneIndy    *CloneIndy;
CCloneObjects *CloneObjects;
long           l_Chart;
long           ar_Charts[];

Inicializamos las instancias de las clases para el trabajo con los gráficos y objetos en la función OnInit.

int OnInit()
  {
//--- indicator Create classes
   CloneIndy   =  new   CCloneIndy();
   if(CheckPointer(CloneIndy)==POINTER_INVALID)
      return INIT_FAILED;
   CloneObjects=  new CCloneObjects();
   if(CheckPointer(CloneObjects)==POINTER_INVALID)
      return INIT_FAILED;

Inicializamos el identificador del gráfico de trabajo.

   l_Chart=(Chart>0 ? Chart : ChartID());

Realizamos la búsqueda de los gráficos abiertos según el instrumento. En caso de necesidad, las copias del indicador serán añadidas a los gráficos encontrados.

   CloneIndy.SearchCharts(l_Chart,ar_Charts);

Al final de la función, inicializamos el temporizador con el intervalo de 10 segundos. La única tarea del temporizador será actualizar la lista de los gráficos para la clonación de los objetos.

   EventSetTimer(10);
//---
   return(INIT_SUCCEEDED);
  }

En la función OnCalculate, no va a ejecutarse ninguna operación. Como ya ha sido mencionado antes, nuestro indicador se basa en el modelo de eventos. Por tanto, toda la funcionalidad de nuestro indicador se concentrará en la función OnChartEvent. Al principio de la función, declaramos las variables locales auxiliares.

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   string message1=NULL;
   string message2=NULL;
   int total=0;

Luego, construimos la derivación de las operaciones dependiendo del evento entrante en el operador switch.

El primer bloque de las operaciones va a recopilar y pasar la información sobre la creación o el cambio del objeto a otros gráficos. Se invoca según los eventos de la creación, cambio o movimiento del objeto en el gráfico. Si los eventos aparecen, el indicador crea dos mensajes con los datos sobre el estado de los objetos, y luego inicia el ciclo para su envío a todos los gráficos con los identificadores desde nuestro array.

   switch(id)
     {
      case CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_OBJECT_DRAG:
        message1=CloneObjects.CreateMessage(l_Chart,sparam,0);
        message2=CloneObjects.CreateMessage(l_Chart,sparam,1);
        total=ArraySize(ar_Charts);
        for(int i=0;i<total;i++)
          {
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,message1);
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,message2);
          }
        break;

El siguiente bloque se inicia cuando el objeto se elimina desde el gráfico. En este caso, no hace falta preparar ningún mensaje porque para eliminar un objeto, será suficiente simplemente mencionarlo, es que ya tenemos su nombre en la variable sparam. Por eso, iniciamos de inmediato el ciclo del envío de los mensajes a otros gráficos.

      case CHARTEVENT_OBJECT_DELETE:
        total=ArraySize(ar_Charts);
        for(int i=0;i<total;i++)
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,sparam);
        break;

Los siguientes dos bloques sirven para procesar los mensajes recibidos de otros gráficos. Cuando se recibe la información sobre la creación o la modificación del objeto, llamamos a la función que muestra el objeto en el gráfico. Pasamos el identificador del gráfico de trabajo y el mensaje obtenido en los parámetros de la función.

      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_DRAG:
        CloneObjects.DrawObjects(l_Chart,sparam);
        ChartRedraw(l_Chart);
        break;

Cuando se recibe la información sobre la eliminación del objeto, llamamos a la función de la eliminación del objeto análogo en el gráfico de trabajo.

      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_DELETE:
        ObjectDelete(l_Chart,sparam);
        ChartRedraw(l_Chart);
        break;
     }
  }

Usted puede estudiar el código del indicador y de las clases utilizadas en los archivos adjuntos.


Conclusión

En el presente artículo se explica la tecnología de la construcción del indicador que copia automáticamente los objetos gráficos entre los gráficos del terminal en tiempo real. Además, ha sido implementado el mecanismo del intercambio mutuo de los datos entre los gráficos abiertos en el terminal. Dicha tecnología no limita al usuario en cuanto a la cantidad de los gráficos a sincronizar. Al mismo tiempo, el usuario puede crear, modificar y eliminar los objetos gráficos en cada uno de los gráficos sincronizados. El trabajo del indicador se demuestra en el siguiente vídeo:



Referencias

  1. Uso de los repositorios en la nube para el intercambio de datos entre los terminales

Los programas usados en el artículo:

#
 Nombre
Tipo 
Descripción 
1 ChartObjectsClone.mq5  Indicador  Indicador del intercambio de los datos entre los gráficos
2 CloneIndy.mqh  Librería de la clase  Clase para seleccionar los gráficos según el instrumento financiero
3 CloneObjects.mqh  Librería de la clase  Clase para trabajar con los objetos gráficos
4 ChartObjectsClone.mqproj    Archivo de la descripción del proyecto

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

Archivos adjuntos |
MQL5.zip (56.08 KB)
Jorge Luis Paiba Rojas
Jorge Luis Paiba Rojas | 14 ago. 2022 en 23:00
cuando se compila el codigo aparecen 39 errores 
Dmitriy Gizlyk
Dmitriy Gizlyk | 15 ago. 2022 en 09:24
scalp-91 #cuando se compila el codigo aparecen 39 errores 

Buenas tardes,
Antes de compilar, debe copiar todos los archivos del archivo en un subdirectorio.

Miguel Angel Vico Alba
Miguel Angel Vico Alba | 15 ago. 2022 en 11:11

Acabo de compilarlo y probarlo y funciona correctamente.

¡Buen trabajo Dmitriy! ;)

Trabajando con los resultados de la optimización mediante la interfaz gráfica Trabajando con los resultados de la optimización mediante la interfaz gráfica
Continuamos desarrollar el tema del procesamiento y el análisis de los resultados de la optimización. Ahora nuestra tarea consiste en seleccionar 100 mejores resultados de la optimización y mostrarlos en la tabla de la interfaz gráfica. Hagamos que el usuario obtenga el gráfico del balance de multisímbolos y de la reducción (drawdown) en gráficos separados seleccionando una fila de la tabla de los resultados de la optimización.
Creando un feed de noticias personalizado en MetaTrader 5 Creando un feed de noticias personalizado en MetaTrader 5
En el artículo se analiza la posibilidad de crear un feed de noticias flexible, que ofrecezca multitud de opciones para elegir el tipo de noticias y su fuente. El artículo muestra cómo se pueden integrar web API con el terminal MetaTrader 5.
Random Decision Forest en el aprendizaje reforzado Random Decision Forest en el aprendizaje reforzado
Random Forest (RF) (en castellano, Bosques Aleatorios) con aplicación del bagging es uno de los métodos del aprendizaje automático más fuerte, que cede un poco ante el boosting del gradiente (Potenciación del gradiente). En este artículo, se realiza el intento de desarrollar un sistema comercial autoenseñable, que toma decisiones a base de la experiencia adquirida de la interacción con el mercado.
Desarrollando los Asesores Expertos multimódulo Desarrollando los Asesores Expertos multimódulo
El lenguaje de programación MQL permite implementar el concepto del diseño modular de las estrategias comerciales. En este artículo, se muestra el ejemplo del desarrollo del Asesor Experto multimódulo compuesto de los módulos de archivos compilados separadamente.