English Русский 中文 Deutsch 日本語 Português
preview
Multibot en MetaTrader: iniciamos múltiples robots desde un gráfico

Multibot en MetaTrader: iniciamos múltiples robots desde un gráfico

MetaTrader 5Ejemplos | 1 agosto 2023, 12:56
726 0
Evgeniy Ilin
Evgeniy Ilin

Apartados


Introducción

En el mundo del trading en los mercados financieros, los sistemas comerciales automatizados se han convertido en una parte integral del proceso de toma de decisiones. Estos sistemas se pueden configurar para analizar el mercado, tomar decisiones de entrada y salida y ejecutar transacciones utilizando reglas y algoritmos predefinidos. No obstante, configurar y ejecutar robots en múltiples gráficos puede resultar una tarea que requiere mucho tiempo. Cada robot debe configurarse individualmente para cada programa, lo cual lleva tiempo y requiere un esfuerzo adicional.

En este artículo, mostraré al lector mi implementación de una plantilla simple que nos permitirá crear un robot universal para múltiples gráficos en MetaTrader 4 y 5. Nuestra plantilla nos permitirá adjuntar el robot a un gráfico y el resto de los gráficos se procesarán dentro del asesor. Por lo tanto, nuestra plantilla simplificará enormemente el proceso de configuración y ejecución de robots en múltiples gráficos, ahorrando tiempo y esfuerzo a los tráders. En este artículo, analizaremos con detalle el proceso de creación de un robot de este tipo en MQL5, comenzando con la idea y terminando con las pruebas.


Planteamiento del problema y límites de aplicabilidad

Esta idea me vino a la cabeza no hace mucho, pero siendo objetivo, he observado decisiones similares de vendedores profesionales durante mucho tiempo. Todo esto se debe a que no soy el primero, ni soy el último, pero como siempre, deben darse algunas condiciones para que el programador comience a tomar decisiones de este tipo. La principal razón para desarrollar este tipo de asesores expertos en la tienda MQL5 es el deseo de comodidad por parte del usuario, pero en mi caso existía una motivación ligeramente distinta, pues primero tenía que probar varias estrategias simultáneamente para varios instrumentos, o la misma estrategia, pero para ver sus características multidivisa.

Además, un factor muy importante a la hora de probar una estrategia en el simulador, especialmente en el modo multidivisa, es la curva global de rentabilidad, que supone la base de cualquier evaluación de los sistemas de comercio automático al realizarse una prueba retrospectiva con datos históricos. Al probar los sistemas comerciales por separado con un instrumento, resulta bastante difícil combinar dichos informes más adelante. Siendo honestos, no conozco tales herramientas, al menos para MetaTrader 5, y en cuanto a la cuarta versión del terminal, claro que hay una herramienta no oficial para dichas manipulaciones; la usé en al menos un artículo, pero, obviamente, este enfoque no es el preferible.

Además del proceso de prueba, existe otro proceso igualmente importante de comercio automático y sincronización de asesores similares que trabajan de forma independiente, cada uno en su propio gráfico. Si hay demasiados gráficos de este tipo, esto puede requerir recursos informáticos adicionales y, además, ralentizar o empeorar el rendimiento comercial, así como provocar errores imprevistos y otros incidentes desagradables que pueden tener un efecto muy desagradable en el resultado comercial final. Para cada uno de estos asesores, deberemos pensar en identificadores únicos para las órdenes, protección contra solicitudes de alta frecuencia al servidor, así como muchas, muchas otras cosas que no resultan obvias a primera vista.

Un tema aparte, muy sensible en el proceso de aplicación será el procesamiento de la parte gráfica del asesor. Ahora todos los creadores de asesores más o menos hábiles, hacen al menos una versión mínima de alguna indicación en el gráfico al que se adjunta el asesor. De esta forma, el asesor se ve más serio e inspira más confianza y, finalmente, casi siempre, la muestra de alguna información en el gráfico a veces permite un control más efectivo sobre el proceso comercial del asesor y, en algunos casos, añade elementos para el control manual. Todo esto se conoce por interfaz de usuario. Si el número de estos asesores aumenta drásticamente en los gráficos, la carga de la actualización de la información gráfica, textual y numérica en las interfaces aumentará considerablemente . Obviamente, al usar una plantilla múltiple, tendremos una interfaz que requerirá un mínimo de recursos de terminal .

Por supuesto que una plantilla de este tipo no será la panacea para cualquier problema, pero sí que creo que le ayudará mucho en sus proyectos. Yo utilizo diferentes robots y, en general, todos los enfoques tienen su razón de ser, pero, a mi juicio, muchos programadores novatos pueden encontrar útil este patrón. No hay necesidad de copiarlo por completo, pero si lo desea, puede corregirlo usted mismo fácilmente. Mi objetivo no es ofrecerle algo extraordinario, sino tratar de mostrarle y explicarle una de las opciones para resolver tales problemas.


Diferencias entre los terminales MetaTrader 4 y MetaTrader 5 al usar un multibot

Lo que me gusta del último MetaTrader 5 es la potencia de su simulador, que al usar el enfoque robótico anteriormente mencionado, nos ofrece todas las funciones que necesitamos para realizar pruebas con múltiples instrumentos al mismo tiempo. En este simulador, las cotizaciones se sincronizan automáticamente con el tiempo y obtenemos una curva de rentabilidad claramente sincronizada en una escala temporal. En MetaTrader 4 no existe tal funcionalidad; esta es su mayor desventaja, en mi opinión. Sin embargo, vale la pena señalar que MetaQuotes está haciendo todo lo posible para dar soporte al cuarto terminal y su popularidad sigue siendo alta. Yo, como usuario activo del mismo, puedo decir que estas carencias no resultan tan importantes como parece a primera vista.

El lenguaje MQL4 se actualizó recientemente a MQL5, pero en la práctica, esto significa que al escribir plantillas como la nuestra, tendremos diferencias mínimas en el código. Por suerte para el lector, es una buena tradición intentar hacer cosas como esta para ambos terminales, así que yo no me desviaré de la norma, y podrá obtener una plantilla para ambos terminales. Mejoras similares al antiguo terminal, entre otras cosas, nos permiten usar las siguientes funciones que realmente necesitamos:

  • CopyClose - solicitar precios de cierre de las barras
  • CopyOpen - solicitar precios de apertura de las barras
  • CopyHigh - solicitar picos de las barras
  • CopyLow - solicitar valles de las barras
  • CopyTime - solicitar hora de apertura de las barras
  • SymbolInfoTick - solicitar el último tick entrante para el símbolo solicitado
  • SymbolInfoInteger - solicitar los datos de los símbolos que se pueden describir mediante números enteros y listas numeradas
  • SymbolInfo****** - otras funciones que necesitamos

Estas funciones están presentes tanto en MQL4 como en MQL5, y nos permiten obtener los datos de las barras para cualquier instrumento y periodo. Por lo tanto, la única diferencia desagradable entre el simulador del cuatro y el cinco es que estas funciones en el cuarto terminal funcionarán solo para el gráfico actual en el que se esté realizando la prueba, mientras que el resto de las solicitudes simplemente nos informarán de que no hay datos debido a las peculiaridades en las construcción del simulador para MetaTrader 4. Por lo tanto, al probar nuestra plantilla, solo obtendremos operaciones en el instrumento seleccionado y solo una de las curvas de beneficio para un solo robot.

En el quinto terminal, si que obtendremos ya la información de todos los instrumentos solicitados, y una línea general de rentabilidad. Pero en cuanto al uso en el trading, solo con el comercio directo con un robot de este tipo en ambos terminales,obtendremos el rendimiento completo de dicha plantilla. En otras palabras, la diferencia se encuentra solo en el simulador. Pero incluso en estos casos, podemos salirnos con la nuestra con el hecho de que al crear un asesor, resulta mejor comenzar con una versión para MetaTrader 5 y, después de todas las pruebas necesarias, crear rápidamente una versión para el cuarto terminal.

Obviamente, hay una serie de diferencias que no he tratado, solo quiero enfatizar la importancia de algunas de ellas, ya que estos matices deben conocerse a la hora de construir una estructura competente para tal plantilla. MetaTrader 5 es definitivamente mejor que su predecesor, y sin embargo, quisiera decir que no tengo deseo alguno de deshacerme del cuarto terminal, porque en muchas situaciones su elevado consumo no resulta tan grande en comparación con el quinto. Ambas herramientas siguen siendo buenas.


Matices a considerar en la construcción de una plantilla universal.

Para crear una plantilla de este tipo, deberemos antes comprender cómo funciona el terminal, qué es un asesor experto y qué es un gráfico de MetaTrader. Además, tendremos que entender que cada gráfico es un objeto aparte. Cada uno de estos gráficos se puede asociar con varios indicadores y con solo un asesor experto. Los gráficos pueden repetirse, es decir, podemos tener varios gráficos idénticos. Por lo general, se crean varios gráficos para ejecutar varios asesores expertos diferentes en el mismo instrumento-periodo, o para ejecutar varias copias de un asesor experto con diferentes configuraciones. Entendiendo estas sutilezas, debemos llegar a la conclusión de que para renunciar a múltiples gráficos a favor de nuestra plantilla, tendremos que implementar todo esto dentro de nuestra plantilla. Todo lo mencionado se puede representar en forma de esquema:

objects structure

Merece la pena hablar por separado sobre los ticks. La desventaja de este enfoque es que no podremos acceder al manejador de aparición de un nuevo tick para cada gráfico, y tendremos que contentarnos con los ticks del gráfico en el que se halla nuestro robot, o usar un temporizador. En última instancia, esto supondrá momentos desagradables para los robots de tick, que son los siguientes:

  • Tendremos que escribir nuestros propios manejadores OnTick
  • Estos manejadores deberán implementarse como un derivado de OnTimer
  • Los ticks no serán ideales porque OnTimer funciona con cierto retraso (la magnitud del retraso no es importante, pero su presencia es importante)
  • Para obtener los ticks, necesitaremos la función SymbolInfoTick

Creo que para quienes cuentan cada milisegundo, este puede ser un momento ineludible, especialmente para aquellos a los que les gusta le arbitraje. Sin embargo, en mi plantilla, no haremos hincapié en esto. Tras años de construir diferentes sistemas, me he detenido en el paradigma del comercio con barras. Esto significa que las transacciones comerciales y otros cálculos suceden en su mayor parte al aparecer una nueva barra. Este enfoque tiene una serie de ventajas obvias:

  • La inexactitud al determinar el inicio de una nueva barra no afecta significativamente el trading
  • Cuanto más largo sea el periodo de la barra, menor será esta influencia.
  • La discreción en forma de barras ofrece un aumento en la velocidad de prueba en varios órdenes de magnitud
  • Ofrece la misma calidad de prueba tanto al probar ticks reales como con ticks artificiales.

Es solo que incluso por sí mismo, este enfoque enseña un cierto paradigma de creación de asesores expertos. Dicho paradigma elimina muchos problemas asociados con los asesores expertos de ticks, acelera el proceso de prueba, ofrece una mayor esperanza matemática de beneficios, que es el principal obstáculo, y también ahorra mucho tiempo y potencia informática. Si lo pensamos bien, podemos encontrar muchas más ventajas, pero creo que esto será suficiente en el contexto de este artículo.

Para implementar nuestra plantilla, no será necesario implementar toda la estructura del área de trabajo del terminal comercial dentro de nuestra plantilla, sino que bastará con implementar un gráfico aparte para cada robot. Este no es el esquema más óptimo, pero si acordamos que cada instrumento individual estará presente solo una vez en la lista de instrumentos, entonces esta optimización no será necesaria. Este será su aspecto:

our realization

Bueno, hemos confirmado el esquema más simple para la implementación de gráficos. Ahora es el momento de pensar en los parámetros de entrada de dicha plantilla y, lo que es más importante, en cómo tener en cuenta la cantidad dinámica de gráficos y asesores expertos para cada situación, pero haciéndolo dentro de las capacidades permitidas del lenguaje MQL5. La única forma de resolver este problema es utilizando variables de entrada de tipo string. Las líneas permiten almacenar una gran cantidad de datos. De hecho, para describir todos los parámetros necesarios para dicha plantilla, se requerirán matrices dinámicas en los datos de entrada.  Obviamente, nadie implementará cosas así, simplemente porque pocas personas aprovecharían tales oportunidades. Precisamente dicha línea será nuestro array dinámico, en el que podremos poner lo que deseemos. Usaremos esta. Para nuestra plantilla más simple, introduciremos 3 variables como esta:

  • Charts - nuestros gráficos (lista)
  • Chart Lots - lotes para comerciar (lista)
  • Chart Timeframes - marcos temporales de los gráficos (lista)

En general, podemos combinar todos estos datos en una sola línea, pero su estructura resultará compleja y será difícil para los potenciales usuarios descubrir cómo describir correctamente los datos. Asimismo, será muy fácil cometer errores al rellenarla y pueden suceder muchas cosas desagradables al usarla, eso por no mencionar la increíble complejidad de la función de conversión que extraerá estos datos de las líneas. He visto soluciones similares de vendedores y, en general, lo hicieron todo bien. Todos los datos se enumeran simplemente separados por comas. Al comienzo del asesor, estos datos se extraen de la línea usando funciones especiales y se rellenan en los arrays dinámicos correspondientes, que luego se utilizan en el código. Nosotros también seguiremos este camino. Podemos añadir más líneas similares con reglas de enumeración idénticas. Como separador, usaremos el carácter ":". La cuestión es que si usamos una coma, entonces no estará claro cómo lidiar con los arrays de tipo double como "Chart Lots". Así que utilizaremos este separador. En este sentido, también querría señalar que podemos añadir más de estas variables de tipo string y, en general, podemos crear una plantilla aún más completa y universal, pero mi tarea aquí es solo mostrar cómo implementar esto y ofrecer la primera versión de la plantilla, que, si deseamos, será muy fácil y rápida de modificar.

Sigamos un poco más. No basta con implementar dichos arrays, además deberemos implementar variables comunes, por ejemplo:

  • Work Timeframe For Unsigned - periodo del gráfico, allí donde no se especifique
  • Fix Lot For Unsigned - lote, allí donde no se especifique

En el caso de la lista "Charts", deberemos rellenarla, pero cuando se trate de las indicaciones "Charlottes" y "Chart Timeframes", ahí no será necesario: por ejemplo, podemos tomar un solo lote para todos los gráficos y el mismo periodo para todos los gráficos. Implementaremos una funcionalidad similar en nuestra plantilla. Sería deseable aplicar dichas reglas siempre que sea posible, garantizando así la brevedad y la claridad general al establecer los parámetros de entrada del asesor experto creado sobre la base de dichas plantillas. 

Ahora vamos a definir algunas otras variables importantes para una implementación mínima de dicho patrón:

  • Last Bars Count - número de últimas barras del gráfico que almacenaremos para cada gráfico
  • Deposit For Lot - depósito para usar el lote especificado
  • First Magic - identificador comercial único para un robot individual

Con la primera variable, creo que nadie tendrá preguntas. Aquí tenemos la segunda, creo que no queda clara para todos. De esta forma regulo el lote automático en mis asesores expertos. Si configuramos su valor en "0", entonces indicaremos al algoritmo que será necesario comerciar solo con un lote fijo, que estableceremos solo en la línea correspondiente o en la variable general que hemos analizado antes. De lo contrario, configuremos el depósito necesario para que se pueda aplicar exactamente el lote especificado en la configuración. No resulta complicado entender que con un depósito más pequeño o más grande, este lote cambiará su valor según la fórmula:

  • Lot = Input Lot * ( Current Deposit / Deposit For Lot )

Creo que todo debería quedar muy claro ahora. Si queremos un lote fijo, indicaremos cero, y en otros casos ajustaremos el depósito en la configuración de entrada según los riesgos. Simple, ¿no? De ser necesario, podremos cambiar el enfoque de evaluación de riesgos y establecer un lote automático, pero personalmente me gusta esta opción, aquí no tiene sentido pasarse de listo.

Mención aparte merece la sincronización y, en particular, la configuración del "Expert Magic Number" o identificador único del asesor. Al comerciar con asesores o incluso de forma mixta, cualquier programador que se precie prestará especial atención a esta variable en particular. Lo que ocurre es que al utilizar varios asesores, resulta esencial asegurarse de que cada asesor tenga su propio identificador único, de lo contrario, al trabajar con órdenes, transacciones o posiciones, nos encontraremos con un completo lío: nuestras estrategias dejarán de funcionar correctamente, y en la mayoría de los casos dejará de funcionar por completo, espero que no tenga que explicar por qué. Cada vez que coloquemos el asesor experto en un gráfico,en este caso será necesario configurar estos identificadores y controlarlos para que no se repitan. Cualquier error cometido, cualquier falta de atención, se pagarán bien caros. Además, si cerramos accidentalmente el gráfico con el asesor, deberemos configurarlo de nuevo y, como comprenderá, la probabilidad de cometer un error aumentará considerablemente, además, todo esto resulta muy desagradable en muchos otros aspectos. Por ejemplo, si cerramos el gráfico y olvidamos cuál era el identificador, tendremos que profundizar en la historia comercial para buscarlo. Sin él, el asesor experto reiniciado puede funcionar incorrectamente y pueden suceder muchas, muchas cosas más desagradables.

El uso de una plantilla similar a la mía elimina dicho control y minimiza posibles errores durante la operación debido a que necesitamos configurar solo el identificador de inicio en los ajustes de asesor, mientras que el resto de los identificadores se generarán automáticamente mediante incrementos y se asignarán a las copias correspondientes de los asesores. Con cada reinicio, este proceso ocurrirá automáticamente; en cualquier caso, recordar solo un identificador de inicio resulta mucho más fácil que recordar alguno a la mitad, ¿no?


Escribiendo una plantilla universal

Ha llegado el momento de implementar nuestra plantilla. Intentaré ir al grano, así que quien necesite esta plantilla, podrá descargarla y ver el resto en el código fuente. Aquí mostraremos solo lo que está directamente relacionado con nuestra idea. Los stops y otros parámetros los hará cada uno a su propio gusto. Podrá ver mi implementación en el código fuente.  Primero, definiremos nuestras variables de entrada, que definitivamente necesitaremos:

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input string SymbolsE="EURUSD:GBPUSD:USDCHF:USDJPY:NZDUSD:AUDUSD:USDCAD";//Charts
input string LotsE="0.01:0.01:0.01:0.01:0.01:0.01:0.01";//Chart Lots
input string TimeframesE="H1:H1:H1:H1:H1:H1:H1";//Chart Timeframes
input int LastBars=10;//Last Bars Count
input ENUM_TIMEFRAMES TimeframeE=PERIOD_M1;//Work Timeframe For Unsigned
input double RepurchaseLotE=0.01;//Fix Lot For Unsigned
input double DepositForRepurchaseLotE=0.00;//Deposit For Lot (if "0" then fix)
input int MagicE=156;//First Magic

Aquí podemos ver de forma visual un ejemplo de cómo completar las variables de tipo string que también reflejan nuestros arrays dinámicos, así como un ejemplo de variables compartidas. Por cierto, este código tendrá la misma forma tanto en MQL4 como en MQL5. He intentado hacerlo todo lo más similar posible.

Ahora debemos decidir cómo obtendremos nuestros datos de las líneas. De esto se encargará la función correspondiente, pero primero crearemos arrays donde nuestra función añadirá los datos obtenidos de las líneas:

//+------------------------------------------------------------------+
//|Arrays                                                            |
//+------------------------------------------------------------------+
string S[];// Symbols array
double L[];//Lots array
ENUM_TIMEFRAMES T[];//Timeframes array

La siguiente función rellenará estos arrays:

//+------------------------------------------------------------------+
//| Fill arrays                                                      |
//+------------------------------------------------------------------+
void ConstructArrays()
   {
      int SCount=1;
      for (int i = 0; i < StringLen(SymbolsE); i++)//calculation of the number of tools
         {
         if (SymbolsE[i] == ':')
            {
            SCount++;
            }
         }
      ArrayResize(S,SCount);//set the size of the character array
      ArrayResize(CN,SCount);//set the size of the array to use bars for each character
      int Hc=0;//found instrument index
      for (int i = 0; i < StringLen(SymbolsE); i++)//building an array of tools
         {
         if (i == 0)//if we just started
            {
            int LastIndex=-1;
            for (int j = i; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i,LastIndex);
               Hc++;
               }
            else
               {
               S[Hc]=SymbolsE;
               Hc++;
               }
            }          
         if (SymbolsE[i] == ':')
            {
            int LastIndex=-1;
            for (int j = i+1; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,LastIndex-(i+1));
               Hc++;
               }
            else
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,StringLen(SymbolsE)-(i+1));
               Hc++;
               }               
            }
         }
      for (int i = 0; i < ArraySize(S); i++)//assignment of the requested number of bars
         {
         CN[i]=LastBars;
         }
      ConstructLots();
      ConstructTimeframe();         
   }

En resumen, aquí la cantidad de datos en la línea se calculará gracias a los separadores y , en función del primer array, se establecerá el tamaño de todos los demás, idéntico al array con los símbolos, después de lo cual los símbolos se completarán primero, y luego se procesarán funciones como ConstructLots y ConstructTimeframe. Su implementación es similar a la implementación de esta función con algunas diferencias, podrá verla en el código fuente. No las he añadido al artículo para no mostrar código duplicado.

Sigamos un poco más. Ahora deberemos crear las clases correspondientes para los objetos del gráfico virtual y el robot virtual vinculado a él, respectivamente. Comenzaremos definiendo que los gráficos virtuales y los asesores expertos se almacenen en arrays:

//+------------------------------------------------------------------+
//| Charts & experts pointers                                        |
//+------------------------------------------------------------------+
Chart *Charts[];
BotInstance *Bots[];

Empezaremos con la clase de gráfico:

//+------------------------------------------------------------------+
//| Chart class                                                      |
//+------------------------------------------------------------------+
class Chart
   {
   public:
   datetime TimeI[];
   double CloseI[];
   double OpenI[];
   double HighI[];
   double LowI[];
   string BasicSymbol;//the base instrument that was extracted from the substring
   double ChartPoint;//point size of the current chart
   double ChartAsk;//Ask
   double ChartBid;//Bid
   datetime tTimeI[];//auxiliary array to control the appearance of a new bar
   static int TCN;//tcn
   string CurrentSymbol;//symbol
   ENUM_TIMEFRAMES Timeframe;//timeframe
   int copied;//how much data is copied
   int lastcopied;//last amount of data copied
   datetime LastCloseTime;//last bar time
   MqlTick LastTick;//last tick fos this instrument
   
   Chart()
      {
      ArrayResize(tTimeI,2);
      }
   
   void ChartTick()//this chart tick
      {
      SymbolInfoTick(CurrentSymbol,LastTick);
      ArraySetAsSeries(tTimeI,false);
      copied=CopyTime(CurrentSymbol,Timeframe,0,2,tTimeI);
      ArraySetAsSeries(tTimeI,true);
      if ( copied == 2 && tTimeI[1] > LastCloseTime )
         {
         ArraySetAsSeries(CloseI,false);                        
         ArraySetAsSeries(OpenI,false);                           
         ArraySetAsSeries(HighI,false);                        
         ArraySetAsSeries(LowI,false);                              
         ArraySetAsSeries(TimeI,false);                                                            
         lastcopied=CopyClose(CurrentSymbol,Timeframe,0,Chart::TCN+2,CloseI);
         lastcopied=CopyOpen(CurrentSymbol,Timeframe,0,Chart::TCN+2,OpenI);   
         lastcopied=CopyHigh(CurrentSymbol,Timeframe,0,Chart::TCN+2,HighI);   
         lastcopied=CopyLow(CurrentSymbol,Timeframe,0,Chart::TCN+2,LowI);
         lastcopied=CopyTime(CurrentSymbol,Timeframe,0,Chart::TCN+2,TimeI);
         ArraySetAsSeries(CloseI,true);
         ArraySetAsSeries(OpenI,true);
         ArraySetAsSeries(HighI,true);                        
         ArraySetAsSeries(LowI,true);
         ArraySetAsSeries(TimeI,true);         
         LastCloseTime=tTimeI[1];
         }
      ChartBid=LastTick.bid;
      ChartAsk=LastTick.ask;
      ChartPoint=SymbolInfoDouble(CurrentSymbol,SYMBOL_POINT);
      }
   };
int Chart::TCN = 0;

Esta clase tiene solo una función que controla la actualización de los ticks y las barras, así como los campos necesarios para identificar algunos parámetros requeridos de un gráfico en particular. Ahí no se encuentran todos los parámetros, sino solo algunos. Si lo desea, podrá añadir los que falten agregando su actualización, por ejemplo, de la misma manera que se actualiza "ChartPoint". Los arrays de barras están hechos al estilo MQL4. Todavía encuentro muy cómodo trabajar con los arrays predefinidos en MQL4. Resulta muy cómodo cuando sabes que la barra cero es la barra actual. En cualquier caso, este es solo mi parecer, no obligo a nadie a ceñirse a él.

Vamos a avanzar un poquito más. Ahora necesitaremos describir la clase de un asesor virtual separado:

//+------------------------------------------------------------------+
//| Bot instance class                                               |
//+------------------------------------------------------------------+
class BotInstance//expert advisor object
   {
   public:
   CPositionInfo  m_position;// trade position object
   CTrade         m_trade;// trading object   
   ///-------------------this robot settings----------------------
   int MagicF;//Magic
   string CurrentSymbol;//Symbol
   double CurrentLot;//Start Lot
   int chartindex;//Chart Index
   ///------------------------------------------------------------   
      
   
   ///constructor
   BotInstance(int index,int chartindex0)//load all data from hat using index, + chart index
      {
      chartindex=chartindex0;
      MagicF=MagicE+index;
      CurrentSymbol=Charts[chartindex].CurrentSymbol;
      CurrentLot=L[index];
      m_trade.SetExpertMagicNumber(MagicF);
      }
   ///
   
   void InstanceTick()//bot tick
      {
      if ( bNewBar() ) Trade();
      }
      
   private:
   datetime Time0;
   bool bNewBar()//new bar
      {
      if ( Time0 < Charts[chartindex].TimeI[1] && Charts[chartindex].ChartPoint != 0.0 )
         {
         if (Time0 != 0)
            {
            Time0=Charts[chartindex].TimeI[1];
            return true;
            }
         else
            {
            Time0=Charts[chartindex].TimeI[1];
            return false;
            }
         }
      else return false;
      }
      
   //////************************************Main Logic********************************************************************
   void Trade()//main trade function
      {
      //Close[0]   -->   Charts[chartindex].CloseI[0] - example of access to data arrays of bars of the corresponding chart
      //Open[0]   -->   Charts[chartindex].OpenI[0] -----------------------------------------------------------------------
      //High[0]   -->   Charts[chartindex].HighI[0] -----------------------------------------------------------------------
      //Low[0]   -->   Charts[chartindex].LowI[0] -------------------------------------------------------------------------
      //Time[0]   -->   Charts[chartindex].TimeI[0] -----------------------------------------------------------------------      

      if ( true )
         {
            CloseBuyF();
            //CloseSellF();       
         }
      if ( true )
         {
            BuyF();
            //SellF(); 
         }

      }
      
   double OptimalLot()//optimal lot calculation
      {
      if (DepositForRepurchaseLotE != 0.0) return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE);
      else return CurrentLot;
      }
      
   //here you can add functionality or variables if the trading function turns out to be too complicated
   //////*******************************************************************************************************************
   
   ///trade functions
   int OrdersG()//the number of open positions / orders of this virtual robot
      {
      ulong ticket;
      bool ord;
      int OrdersG=0;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol )
            {
            OrdersG++;
            }
         }
      return OrdersG;
      }
   
   /////////********/////////********//////////***********/////////trade function code block
   void BuyF()//buy market
      {
      double DtA;
      double CorrectedLot;
   
      DtA=double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));//unique bot marker last try datetime
      if ( (DtA > 0 || DtA < 0) )
         {
         CorrectedLot=OptimalLot(Charts[chartindex]);
         if ( CorrectedLot > 0.0 )
            {
            //try buy logic
            }            
         }
      }
      
   void SellF()//sell market
      {
      //Same logic
      }

   void CloseSellF()//close sell position
      {
      ulong ticket;
      bool ord;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL 
         && PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol )
            {
            //Close Sell logic
            }
         }    
      }
      
   void CloseBuyF()//close buy position
      {
      //same logic 
      }        
      
   bool bOurMagic(ulong ticket,int magiccount)//whether the magic of the current deal matches one of the possible magics of our robot
      {
      int MagicT[];
      ArrayResize(MagicT,magiccount);
      for ( int i=0; i<magiccount; i++ )
         {
         MagicT[i]=MagicE+i;
         }
      for ( int i=0; i<ArraySize(MagicT); i++ )
         {
         if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) return true;
         }
      return false;
      }
   /////////********/////////********//////////***********/////////end trade function code block
   };

Hemos eliminado parte de la lógica más reiterativa para reducir la cantidad de código. Precisamente en esta clase debe implementarse el algoritmo completo de nuestro asesor. Funcionalidad principal presente en esta clase:

  • Trade() - principal función comercial que se llama en el manejador de barras para el gráfico correspondiente
  • BuyF() - función de compra según el mercado
  • SellF() - función de venta según el mercado
  • CloseBuyF() - función para cerrar posiciones de compra según el mercado
  • CloseSellF() - función para cerrar posiciones de venta según el mercado

Este es el conjunto mínimo de funciones para mostrar el comercio de barras. Para esta demostración, solo necesitaremos abrir cualquier posición y cerrarla en la siguiente barra. No necesitaremos más en el marco de este artículo. Hay alguna funcionalidad adicional de esta clase que debería complementar la comprensión:

  • OrdersG() - simplemente calcula las posiciones abiertas en un instrumento específico vinculado al gráfico
  • OptimalLot() - preparación de un lote antes de enviarlo a la función comercial (selección de un lote fijo o cálculo automático del lote)
  • bOurMagic() - verificación de las transacciones de la historia para comprobar que cumplan con la lista de válidas (para filtrar solo la propia historia)

Estas funciones pueden ser necesarias para implementar la lógica comercial. Estos son solo ejemplos. También sería bueno recordar el nuevo manejador de barra:

  • InstanceTick() - simulación de ticks en un ejemplar aparte del asesor
  • bNewBar() - predicado para buscar una nueva barra (usado dentro de InstanceTick)

Si este predicado muestra una nueva barra, entonces se activará nuestra función "Trade", en la que se supone que deberemos escribir la lógica comercial principal. La conexión con el gráfico correspondiente se realizará usando la variable "chartindex" asignada al crear un ejemplar. Así cada ejemplar del asesor sabrá de qué gráfico deberá tomar una cotización para el trabajo. 

Ahora, consideremos el proceso de creación de los gráficos virtuales y los asesores expertos. Primero, se crearán los gráficos virtuales:

//+------------------------------------------------------------------+
//| Creation of graph objects                                        |
//+------------------------------------------------------------------+
void CreateCharts()
   {
   bool bAlready;
   int num=0;
   string TempSymbols[];
   string Symbols[];
   ConstructArrays();//array preparation
   int tempcnum=CN[0];
   Chart::TCN=tempcnum;//required number of stored bars for all instruments
   for (int j = 0; j < ArraySize(Charts); j++)//fill in all the names and set the dimensions of all time series, each graph
      {
      Charts[j] = new Chart();
      Charts[j].lastcopied=0;
      ArrayResize(Charts[j].CloseI,tempcnum+2);//assign size to character arrays
      ArrayResize(Charts[j].OpenI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].HighI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].LowI,tempcnum+2);//-----------------------------------
      ArrayResize(Charts[j].TimeI,tempcnum+2);//----------------------------------
      Charts[j].CurrentSymbol = S[j];//symbol
      Charts[j].Timeframe = T[j];//timeframe
      }
   ArrayResize(Bots,ArraySize(S));//assign a size to the array of bots      
   }

Después de crear los gráficos y establecer el tamaño del array con los asesores virtuales, deberemos crear los ejemplares de los propios asesores y establecer la conexión entre los asesores virtuales y los gráficos:

//+------------------------------------------------------------------+
//| create and hang all virtual robots on charts                     |
//+------------------------------------------------------------------+
void CreateInstances()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      for (int j = 0; j < ArraySize(Charts); j++)
         {
         if ( Charts[j].CurrentSymbol == S[i] )
            {
            Bots[i] = new BotInstance(i,j);
            break;
            } 
         }
      }
   }

La conexión se realiza usando el índice "j", que se escribe en cada ejemplar del asesor virtual cuando se crea. Ahí se destacará la variablecorrespondiente que ya mostramos más arriba. Todo esto, por supuesto, se puede hacer de muchas maneras y de una forma mucho más elegante, pero creo que lo principal es que el significado general quede claro. 

Bueno, todo lo que queda es mostrar cómo se simulan los ticks en cada gráfico y el asesor experto asociado a él:

//+------------------------------------------------------------------+
//| All bcharts & all bots tick imitation                            |
//+------------------------------------------------------------------+
void AllChartsTick()
   {
   for (int i = 0; i < ArraySize(Charts); i++)
      {
      Charts[i].ChartTick();
      }
   }

void AllBotsTick()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick();
      }
   }

Lo único que quiero señalar es que esta plantilla se ha obtenido reelaborando una plantilla mía más compleja que estaba destinada a propósitos mucho más serios, por lo que en algunos lugares puede haber algo superfluo. Creo que no le resultará difícil eliminar los excesos y volver la plantilla más armoniosa, si esto le es muy importante.

Además de la plantilla, hay una interfaz simple que, a mi juicio, también puede ser útil, por ejemplo, al escribir una orden en forma independiente o para otros fines:


Dejaremos espacio libre en esta interfaz: será suficiente para 3 entradas, si no hay suficiente espacio, podrá expandirse fácilmente y, en general, cambiar su estructura. Pero en este ejemplo específico, para añadir los tres campos que faltan, deberemos encontrar los siguientes lugares en el código:

//+------------------------------------------------------------------+
//| Reserved elements                                                |
//+------------------------------------------------------------------+

   "template-UNSIGNED1",//UNSIGNED1
   "template-UNSIGNED2",//UNSIGNED2
   "template-UNSIGNED3",//UNSIGNED3

   //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1
   //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2
   //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3

   ////////////////////////////
   //TempText="UNSIGNED1 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED2 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED3 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);
   //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText);
   ///////////////////////////
 

Las primeras tres entradas asignarán los nombres de los nuevos elementos en la interfaz, las siguientes tres se usarán al crear la interfaz al inicio del asesor experto y las últimas tres se usarán en la función para actualizar la información en la interfaz. Bien, creo que vale la pena terminar aquí con esta parte y proceder a comprobar el rendimiento de ambas plantillas. Para demostrar visualmente el funcionamiento, bastará con mostrar este en el visualizador del simulador. Solo mostraré la opción para MetaTrader 5, porque su visualizador resulta idóneo para estas tareas, y además, el resultado del funcionamiento mostrará claramente todo lo que necesitamos para confirmar el rendimiento:

checking using MetaTrader 5 tester visualization


Como podemos ver en la captura de pantalla, hemos cargado los 7 gráficos para las principales parejas de divisas del mercado Fórex. El registro de visualización muestra que se están negociando todos los instrumentos de la lista. El comercio se realiza de forma independiente, según sea necesario. En otras palabras, estos asesores expertos operan cada uno en su propio gráfico y no interactúan de ninguna manera


Conclusión

En este artículo, hemos analizado los principales sutilezas relacionadas con la creación de plantillas universales para los terminales MetaTrader 4 y MetaTrader 5, hemos creado una plantilla simple pero funcional, hemos analizado los puntos más importantes de su funcionamiento y también hemos confirmado claramente que su construcción es correcta utilizando el visualizador del simulador de MetaTrader 5. Creo que resulta bastante obvio que un patrón como este no es tan complicado. En general, podemos realizar varias implementaciones de dichos patrones, pero resulta obvio que dichos patrones pueden ser completamente diferentes y todo funcionará: lo principal es comprender los matices básicos de la construcción de tales estructuras. Estoy seguro de que muchos lectores podrán aprender algo útil a partir de ahí o reelaborar las plantillas para su uso personal.


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

Archivos adjuntos |
MultiTemplate.mq4 (93.94 KB)
MultiTemplate.mq5 (91.41 KB)
Aprendizaje automático y Data Science (Parte 14): Aplicación de los mapas de Kohonen a los mercados Aprendizaje automático y Data Science (Parte 14): Aplicación de los mapas de Kohonen a los mercados
¿Quiere encontrar un nuevo enfoque comercial que lo ayude a orientarse en mercados complejos y en cambio constante? Eche un vistazo a los mapas de Kohonen, una forma innovadora de redes neuronales artificiales que puede ayudarle a descubrir patrones y tendencias ocultos en los datos del mercado. En este artículo, veremos cómo funcionan los mapas de Kohonen y cómo usarlos para desarrollar estrategias comerciales efectivas. Creo que este nuevo enfoque resultará de interés tanto a los tráders experimentados como para los principiantes.
Ejemplo de un conjunto de modelos ONNX en MQL5 Ejemplo de un conjunto de modelos ONNX en MQL5
ONNX (Open Neural Network eXchange) es un estándar abierto para representar redes neuronales. En este artículo, le mostraremos la posibilidad de usar dos modelos ONNX simultáneamente en un asesor experto.
Cómo conectar MetaTrader 5 a PostgreSQL Cómo conectar MetaTrader 5 a PostgreSQL
Este artículo describiremos cuatro métodos para conectar el código MQL5 a una base de datos de Postgres y ofreceremos una guía paso a paso para configurar un entorno de desarrollo para uno de ellos, la API REST, utilizando el Subsistema de Windows para Linux (WSL). Asimismo, mostraremos una aplicación demostrativa de la API con el código MQL5 correspondiente para insertar datos y consultas a las tablas correspondientes, así como un asesor demo para usar estos datos.
Estrategia comercial con el indicador de mejora de reconocimiento de velas Doji Estrategia comercial con el indicador de mejora de reconocimiento de velas Doji
El indicador sobre metabarras ha detectado más velas que el clásico. Veamos si aporta un beneficio real en el trading automatizado.