Português
preview
Desarrollo de un sistema de repetición (Parte 40): Inicio de la segunda fase (I)

Desarrollo de un sistema de repetición (Parte 40): Inicio de la segunda fase (I)

MetaTrader 5Ejemplos | 9 abril 2024, 09:49
77 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición (Parte 39): Pavimentando el terreno (III), mostré cómo podemos tener comunicación entre procesos de forma que sea posible realizar ciertas acciones. Por el momento, estamos utilizando un EA y un indicador, pero podremos ampliar estas herramientas según sea necesario.

La principal y mayor ventaja de contar con este tipo de comunicación es que podemos modularizar nuestro sistema. Quizá aún no comprendan lo que podemos hacer realmente. Es entonces cuando podemos intercambiar información entre procesos a través de un canal más "seguro" que el uso de variables globales terminales.

Para ello, y para demostrar cómo integrar nuestro sistema de repetición/simulador de forma totalmente modular, demos un paso atrás y luego otro adelante. Aquí en este artículo, vamos a eliminar el sistema de estudio, que utiliza el ratón del EA, y convertiremos este mismo sistema de estudio en un indicador para que sea compatible con la próxima etapa que vamos a ensamblar.

Si lo consiguen, se darán cuenta de que después podremos hacer mucho más. Con toda probabilidad, el indicador no se finalizará aquí, ya que necesitaremos hacer otras cosas más adelante. Pero con el tiempo, se completará. La gran ventaja es que será un módulo, lo que puede mejorarse o cambiarse sin afectar al resto de nuestro sistema principal.


Codificación inicial

Nuestro código se ensamblará a partir de lo que ya tenemos en nuestras manos. Haremos cambios mínimos para que el código presente en el EA se convierta en un indicador.

Lo primero que hay que hacer es revisar el código del archivo de cabecera InterProcess.mqh. Pueden ver el código completo a continuación:

01. #property copyright "Daniel Jose"
02. //+------------------------------------------------------------------+
03. #define def_SymbolReplay                    "RePlay"
04. #define def_GlobalVariableReplay            def_SymbolReplay + "_Infos"
05. #define def_GlobalVariableIdGraphics        def_SymbolReplay + "_ID"
06. #define def_GlobalVariableServerTime        def_SymbolReplay + "_Time"
07. #define def_MaxPosSlider                    400
08. //+------------------------------------------------------------------+
09. union u_Interprocess
10. {
11.     union u_0
12.     {
13.             double  df_Value;  // Value of the terminal global variable...
14.             ulong   IdGraphic; // Contains the Graph ID of the asset...
15.     }u_Value;
16.     struct st_0
17.     {
18.             bool    isPlay;     // Indicates whether we are in Play or Pause mode...
19.             bool    isWait;     // Tells the user to wait...
20.             bool    isHedging;  // If true we are in a Hedging account, if false the account is Netting...
21.             bool    isSync;     // If true indicates that the service is synchronized...
22.             ushort  iPosShift;  // Value between 0 and 400...
23.     }s_Infos;
24.     datetime        ServerTime;
25. };
26. //+------------------------------------------------------------------+
27. union uCast_Double
28. {
29.     double   dValue;
30.     long     _long;                    // 1 Information
31.     datetime _datetime;                // 1 Information
32.     int      _int[sizeof(double)];     // 2 Informations
33.     char     _char[sizeof(double)];    // 8 Informations
34. };
35. //+------------------------------------------------------------------+

Código del archivo InterProcess.mqh

Lo que realmente nos interesa de este código está entre las líneas 27 y 34. Aquí tenemos una unión, que nos permitirá transferir información entre procesos. Esta unión tiene ahora algunos tipos de datos que necesitaremos y utilizaremos desde el principio. La idea aquí es facilitar la transferencia de datos que vimos en el artículo anterior, pero de una manera práctica y decidida.

Estas líneas se han añadido al archivo InterProcess.mqh. Continuemos, pero ahora haremos otros cambios en el código. Estos se realizarán en el archivo de clase C_Study.mqh.

Para empezar, cambiaremos algo al principio de la clase. Esto se puede ver en el siguiente fragmento:

class C_Study : public C_Mouse
{
        protected:
                enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
        private :
                enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};

La línea señalada ha sido eliminada de la cláusula privada y ahora se declara en la cláusula protegida. Aunque esto no suponga una gran diferencia práctica, para mí, hace que sea más fácil de entender. Pero para mí, esto hace que sea más fácil de entender. Las cláusulas privadas indican que su contenido no se puede acceder desde fuera de la clase, pero en el caso de las enumeraciones no es así; siempre se tratan como si fueran públicas, independientemente de la cláusula de definición.

La parte en la que realmente se ha producido un cambio se ve a continuación:

//+------------------------------------------------------------------+
void Update(const eStatusMarket arg)
void Update(void)
{
   datetime dt;
                                
   switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
   switch (m_Info.Status)
   {
      case eCloseMarket : m_Info.szInfo = "Closed Market";
         break;
      case eInReplay    :
      case eInTrading   :
         if ((dt = GetBarTime()) < ULONG_MAX)
         {
            m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
            break;
         }
      case eAuction     : m_Info.szInfo = "Auction";
         break;
      default           : m_Info.szInfo = "ERROR";
   }
   Draw();
}
//+------------------------------------------------------------------+
void Update(const MqlBookInfo &book[])
{
   m_Info.Status = (ArraySize(book) == 0 ? eCloseMarket : (def_InfoTerminal.szSymbol == def_SymbolReplay ? eInReplay : eInTrading));
   for (int c0 = 0; (c0 < ArraySize(book)) && (m_Info.Status != eAuction); c0++)
      if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Info.Status = eAuction;
   this.Update();
}
//+------------------------------------------------------------------+

Todas las líneas señaladas han sido eliminadas de la clase y se han añadido las líneas resaltadas en su lugar.

¿Pero por qué ocurre esto? Para entenderlo, hay que darse cuenta de que, al convertir el código del EA en un indicador, habrá un cambio en la forma de hacer las cosas.

El código en cuestión se refiere al uso del ratón como medio para generar estudios.

Cuando utilizamos la codificación de tal manera que es el EA el que hace las cosas, podemos actuar de una determinada manera; sin embargo, al hacer lo mismo utilizando un indicador, tenemos que adaptarnos a otra forma. Esto se debe a que el indicador opera en un hilo que, si se ve afectado, puede impactar a todos los demás en el gráfico.

No querrás que el indicador se bloquee en ningún momento, por ello, tendremos que abordar las cosas de manera ligeramente diferente.

Dicho esto, echemos un vistazo al código del indicador que aparece a continuación. Está completo y sin resumir.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property description "This is an indicator for graphical studies using the mouse."
004. #property description "This is an integral part of the Replay / Simulator system."
005. #property description "However it can be used in the real market."
006. #property version "1.40"
007. #property icon "/Images/Market Replay/Icons/Indicators.ico"
008. #property link "https://www.mql5.com/pt/articles/11624"
009. #property indicator_chart_window
010. #property indicator_plots 0
011. #property indicator_buffers 1
012. //+------------------------------------------------------------------+
013. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
014. #include <Market Replay\Auxiliar\InterProcess.mqh>
015. //+------------------------------------------------------------------+
016. C_Terminal *Terminal  = NULL;
017. C_Study    *Study     = NULL;
018. //+------------------------------------------------------------------+
019. input C_Study::eStatusMarket user00 = C_Study::eAuction;   //Market Status
020. input color user01 = clrBlack;                             //Price Line
021. input color user02 = clrPaleGreen;                         //Positive Study
022. input color user03 = clrLightCoral;                        //Negative Study
023. //+------------------------------------------------------------------+
024. C_Study::eStatusMarket m_Status;
025. int m_posBuff = 0;
026. double m_Buff[];
027. //+------------------------------------------------------------------+
028. int OnInit()
029. {
030.    if (!CheckPass("Indicator Mouse Study")) return INIT_FAILED;
031.            
032.    Terminal = new C_Terminal();
033.    Study = new C_Study(Terminal, user01, user02, user03);
034.    if ((*Terminal).GetInfoTerminal().szSymbol != def_SymbolReplay)
035.    {
036.            MarketBookAdd((*Terminal).GetInfoTerminal().szSymbol);
037.            OnBookEvent((*Terminal).GetInfoTerminal().szSymbol);
038.            m_Status = C_Study::eCloseMarket;
039.    }else
040.            m_Status = user00;
041.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
042.    ArrayInitialize(m_Buff, EMPTY_VALUE);
043.    
044.    return INIT_SUCCEEDED;
045. }
046. //+------------------------------------------------------------------+
047. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
048. {
049.    m_posBuff = rates_total - 4;
050.    (*Study).Update(m_Status);      
051.    
052.    return rates_total;
053. }
054. //+------------------------------------------------------------------+
055. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
056. {
057.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
058.    SetBuffer();
059.    
060.    ChartRedraw();
061. }
062. //+------------------------------------------------------------------+
063. void OnBookEvent(const string &symbol)
064. {
065.    MqlBookInfo book[];
066.    C_Study::eStatusMarket loc = m_Status;
067.    
068.    if (symbol != (*Terminal).GetInfoTerminal().szSymbol) return;
069.    MarketBookGet((*Terminal).GetInfoTerminal().szSymbol, book);
070.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
071.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
072.            if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
073.    if (loc != m_Status) (*Study).Update(m_Status);
074. }
075. //+------------------------------------------------------------------+
076. void OnDeinit(const int reason)
077. {
078.    if (reason != REASON_INITFAILED)
079.    {
080.            if ((*Terminal).GetInfoTerminal().szSymbol != def_SymbolReplay)
081.                    MarketBookRelease((*Terminal).GetInfoTerminal().szSymbol);
082.            delete Study;
083.            delete Terminal;
084.    }
085. }
086. //+------------------------------------------------------------------+
087. bool CheckPass(const string szShortName)
088. {
089.    IndicatorSetString(INDICATOR_SHORTNAME, szShortName + "_TMP");
090.    if (ChartWindowFind(ChartID(), szShortName) != -1)
091.    {
092.            ChartIndicatorDelete(ChartID(), 0, szShortName + "_TMP");
093.            Print("Only one instance is allowed...");
094.            
095.            return false;
096.    }
097.    IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
098.    
099.    return true;
100. }
101. //+------------------------------------------------------------------+
102. inline void SetBuffer(void)
103. {
104.    uCast_Double Info;
105.    
106.    m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff);
107.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
108.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
109.    m_Buff[m_posBuff + 1] = Info.dValue;
110.    Info._int[0] = (*Study).GetInfoMouse().Position.X;
111.    Info._int[1] = (*Study).GetInfoMouse().Position.Y;
112.    m_Buff[m_posBuff + 2] = Info.dValue;
113.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
114.    m_Buff[m_posBuff + 3] = Info.dValue;
115. }
116. //+------------------------------------------------------------------+


Código fuente del indicador

Al observar el código fuente del indicador y compararlo con el código visto en el artículo "Desarrollo de un sistema de repetición (Parte 39): Pavimentando el terreno (III)", notarás muchas similitudes.

Si has estado siguiendo esta serie de artículos sobre el sistema de repetición/simulador, no te costará mucho entender la mayor parte del código mencionado. Sin embargo, hay varios puntos que merecen ser destacados y explicados con más detalle, ya que muchas personas no serán capaces de comprender lo que ocurre con solo mirar el código. Quiero que la idea, el motivo y el interés por hacer las cosas de esta manera queden lo más claros posible, dado que la correcta comprensión de estos y otros códigos fuente será de gran importancia para aquellos que realmente quieren entender cómo se está aplicando el sistema.

Comencemos viendo que entre las líneas 2 y 8 tenemos información que cambiará con el tiempo. Esta información vincula el código al artículo en el que se está creando, modificando o presentando. Aunque no es trivial, está ahí para orientar a los usuarios menos experimentados.

Entre las líneas 9 y 11, le indicamos al compilador cómo se proyectará realmente el indicador. Todos los códigos de indicadores tendrán al menos una línea 9 y una línea 10. TODO. La línea 11 nos informa que usaremos un buffer, y que cualquier proceso que quiera leer algo a través de la función CopyBuffer puede hacerlo accediendo al buffer interno del indicador.

Las líneas 13 y 14 son 'includes', que indican al compilador qué archivos de cabecera vamos a utilizar. Aunque ya hemos hecho algunos cambios en estos archivos, tendremos que hacer otros. Por el momento, podemos utilizar los archivos de cabecera proporcionados sin preocupación.

En las líneas 16 y 17, declaramos dos de las variables globales internas del indicador. Estas variables, que son punteros, nos servirán para tener un acceso controlado a las clases. Normalmente, una variable global no se inicializa por defecto, ya que el compilador lo hace automáticamente. Pero aquí quiero dejar claro que, aunque estas variables (punteros) no apuntan a ningún lugar de la memoria específico, si no lo declaras explícitamente, debes recordar que el compilador lo hará implícitamente por defecto.

Entre las líneas 19 y 22 tenemos las variables de configuración externas. A partir de ahora, te sugiero que dejes de ver estas variables simplemente como una forma para que el usuario acceda o configure el indicador. Si piensas así, terminarás con una visión muy limitada y no podrás visualizar todo lo que realmente puedes hacer con ellas.

Ahora, comprendamos una cosa: ningún programa, absolutamente ningún programa, debe considerarse puro y simple. Siempre hay que pensar en un programa como en una especie de función, ya que recibe información, la procesa de una determinada manera y ofrece algún tipo de resultado. ¿Qué elemento se utiliza en programación que tenga este mismo comportamiento? UNA FUNCIÓN. Así que empecemos a ver las cosas de una manera más amplia. Dejemos de imaginar o soñar con posibilidades y empecemos a ver las cosas como realmente son: los programas son funciones, no importa lo que puedas hacer. Los programas son funciones.

En la línea 19, tenemos un parámetro que, cuando el indicador es colocado en el gráfico por el usuario, no tiene sentido. Este parámetro no existe para que el usuario lo configure; existe para que otro programa pueda utilizar este indicador que estamos creando. ¿Cómo es posible? No entiendo 🤔. No te preocupes por eso ahora. Recuerda: este es el primer artículo de esta segunda fase de construcción del sistema de repetición/simulador. Hay varias cosas que se mostrarán en los siguientes artículos que te harán comprender por qué existe la línea 19. Tómatelo con calma.

Las líneas 20 a 22, en cambio, son parámetros que realmente tienen sentido porque se utilizan para configurar los colores utilizados por el ratón en el gráfico.

Ahora tenemos una pregunta: ¿Por qué hay una línea 24? ¿Por qué tenemos una variable global del mismo tipo que la que vemos en la línea 19? La razón es que el parámetro de la línea 19 no se considera realmente una variable, sino una constante. Por esto, necesitamos declarar una variable que se utilizará y configurará durante el trabajo del indicador. Esta es la razón de la declaración en la línea 24.

La variable declarada en la línea 25 sirve como una forma de memoria, pero con un propósito más noble que el de ser simplemente un recuerdo. Volveremos a esto en breve. La línea 26 es donde declaramos nuestro buffer para que sirva como salida del indicador. Atención: hemos declarado el buffer, pero aún no funciona. Si intentas hacer algo en él, recibirás un aviso de error en tiempo de ejecución o, si intentas leerlo, no podrás hacerlo.

A partir de este punto, llegaremos finalmente a la interacción entre el indicador y MetaTrader 5. Recuerda: el indicador responde a los eventos reportados por MetaTrader 5, no importa cómo esté estructurado tu código, ni qué trabajo tenga que hacer. Siempre responderá a estos eventos. Algunos de estos eventos pueden ser de interacción, mientras que otros son de actividad.

Entre estos eventos se encuentra el evento Init, que al activarse genera una llamada a la función OnInit. Esta función comienza en la línea 28 y no recibe ningún parámetro. Pero podemos hacer que los parámetros de interacción, los declarados entre las líneas 19 y 22, se utilicen como parámetros en la función OnInit.

Veamos cómo funciona la función OnInit. Lo primero que hacemos es registrarnos, lo cual se realiza en la línea 30, donde llamamos a una función. A continuación, el flujo de ejecución se desvía a la línea 87. Analicemos esta línea para entender de qué tipo de procedimiento se trata y por qué, si falla la función OnInit, devuelve un fallo al inicializar el indicador.

La función en la línea 87 toma un argumento, que es el nombre corto del indicador. Este nombre debe definirse siempre para poder garantizar ciertas condiciones de uso o de trabajo. Lo primero que hacemos, y esto ocurre en la línea 89, es dar al indicador un nombre temporal. Una vez que el indicador tiene un nombre temporal, podemos continuar con la fase de registro. Lo siguiente que hacemos es buscar en la línea 90 nuestro indicador en el gráfico. Si ya está presente en el gráfico, MetaTrader 5 nos indicará dónde. Si no, obtendremos un retorno de -1.

Así que la prueba que se realiza en la línea 90 nos dice si el indicador ya está presente o no en el gráfico. Si está presente, se ejecutará la línea 92, que eliminará el indicador temporal, es decir, el que estamos intentando colocar en el gráfico. Imprimimos un mensaje advirtiendo de ello y en la línea 95 volvemos a la persona que ha llamado, informándole de que el registro no ha tenido éxito.

Si la prueba de la línea 90 informa de que el indicador no está presente, ejecutaremos la línea 97, que le dirá a MetaTrader 5 cómo se llamará realmente el indicador. Y en la línea 99, volvemos a la persona que llama, informándole de que el indicador se puede colocar con éxito en el gráfico. Pero esto no significa que se haya colocado realmente en el gráfico, solo le informa de que MetaTrader 5 no lo ha encontrado en el gráfico, pero ahora puede ponerse en marcha.

Esto nos lleva de nuevo a la línea 30, donde se tomará una decisión. Si tiene éxito, pasaremos a la línea 32. En caso de fallo, MetaTrader 5 activará un evento para llamar a la función en la línea 76. Sin embargo, como la línea 30 nos informa de que se ha producido un fallo, en cuanto se ejecute la línea 78, impedirá que se ejecute el código entre las líneas 80 y 83. Esto es importante para evitar información de fallos en la salida del indicador.

Pero volvamos a la línea 32, donde realmente empezamos a poner el indicador a trabajar. En la línea 32, iniciamos el puntero para acceder a la clase C_Terminal. A partir de ahora, podemos utilizar las funciones de la clase C_Terminal. Justo después, en la línea 33, iniciamos el puntero para acceder a la clase C_Study. Esta clase nos permitirá utilizar el ratón y generar estudios gráficos. Como necesita tener acceso a la clase C_Terminal, es necesario seguir esta secuencia de acciones.

En la línea 34, tenemos una nueva prueba que definirá si estamos utilizando el activo de repetición/simulación, o si estamos utilizando un activo diferente. Si el activo es un activo de repetición/simulación, el contenido del parámetro de configuración de entrada del indicador, declarado en la línea 19, se colocará en nuestra variable global. Esto ocurre en la línea 40.

Si la línea 34 indica que estamos trabajando con otro tipo de activo, se ejecutarán las líneas 36 a 38. Estas líneas inicializan el uso del libro de órdenes, diciéndole a MetaTrader 5 que queremos recibir los eventos que han ocurrido en el libro de órdenes. Además, en la línea 38, indicamos que el mercado está cerrado. Esta condición es temporal, ya que recibiremos orientación del libro de órdenes para saber cuál será la información correcta.

Ahora viene una línea importantísima para el indicador, la línea 41. Recordemos que el hecho de que declaremos el buffer en las líneas 11 y 26 no garantiza que se pueda acceder a él y que si se intentara hacerlo, se generaría un error de ejecución. Sin la línea 41, el indicador es casi completamente inútil para nosotros. En esta línea, hemos introducido el índice, la matriz y su utilidad. Normalmente empezamos con el valor cero en el campo índice. En el campo array, indicamos qué variable se utiliza como buffer. En cuanto al tercer campo, que nos dice para qué se usa el buffer, podemos usar una de las enumeraciones ENUM_INDEXBUFFER_TYPE, pero como vamos a guardar datos utilizamos INDICATOR_DATA. Sin embargo, nada impide utilizar otro tipo en este código específico.

Una vez hecho esto, procedemos a la línea 42. Esto podría, en cierto modo, considerarse innecesario. Pero quiero que el buffer esté limpio. Esto es lo que estamos haciendo ahora. Podríamos añadir más aquí, pero no veo la necesidad. La idea es simplemente crear una especie de caché para los datos del ratón, como verás en los próximos artículos.

Esta fue la explicación de cómo se inicializa el indicador. Ahora necesitamos saber cómo responderá a los eventos desencadenados por MetaTrader 5. Para explicar esta parte, empecemos por revisar el código del evento libro de órdenes en la función OnBookEvent. Este procedimiento, que comienza en la línea 63, es llamado por MetaTrader 5 cada vez que algo sucede en el libro de órdenes.

Si te fijas bien, verás que el contenido de este procedimiento, que se encuentra entre las líneas 70 y 72, es prácticamente el mismo que el de la clase C_Study. Entonces, ¿por qué no dejé este procedimiento en la clase C_Study? Es difícil explicar el motivo aquí y ahora, pero créeme, existe una razón que se comprenderá mejor más adelante. Este fragmento ya debería ser conocido por ti, siempre que, por supuesto, sigas esta secuencia de artículos y los estudies. Pero echemos un vistazo rápido a esta función que contiene código extra.

En la línea 66, declaramos una variable local y temporal que almacenará temporalmente el estado del mercado, ya que este puede cambiar en las líneas 70 o 72. Si esto ocurre, en la línea 73 tendremos una llamada para actualizar cómodamente la información del gráfico. Antes de analizar esta cuestión de la actualización, echemos un vistazo rápido a las otras líneas.

En la línea 68, filtramos los eventos BOOK solo a aquellos que realmente forman parte del activo en el gráfico. MetaTrader 5 no hace tal distinción; por lo tanto, si cualquier activo en la ventana de vigilancia del mercado emite un evento de libro de órdenes, MetaTrader 5 activará un evento de libro de órdenes. Por ello, tenemos que hacer una distinción in situ.

Una vez hecho esto, en la línea 69, capturaremos los datos presentes en el libro de órdenes para poder analizarlos según nuestras necesidades. Luego, en la línea 70, informamos al indicador si el mercado está cerrado o abierto, refiriéndonos al mercado físico, es decir, si el servidor de negociación está o no dentro de la ventana de negociación permitida. Para averiguarlo, comprobamos si el conjunto de libro de órdenes está vacío o no.

Ahora, en la línea 71, entramos en un bucle que comprobará la matriz punto por punto para ver si hay alguna posición que indique que el activo está en subasta. El activo estará en estado de subasta si se cumple alguna de las dos condiciones verificadas en la línea 72. Como no sabemos exactamente dónde están el BID y el ASK en el libro de órdenes, utilizamos el bucle de la línea 71. Sin embargo, si el activo que estás utilizando tiene una posición fija de BID y ASK, puedes saltarte el bucle eliminando la línea 71 y simplemente indicando el punto del array donde se encuentran el BID y el ASK. Si alguno de los dos indica que el bien está en subasta, se actualizará el estado.

Volvamos a la cuestión de la actualización. Esta información no siempre se transmite; solo tendremos una llamada de actualización del estado del mercado si el valor almacenado en la línea 66 difiere del nuevo estado.

La razón para hacer esto aquí, en la línea 73, se debe a que cuando un activo sale a subasta o está en estado de suspensión durante la ventana de negociación, solo el evento libro de órdenes tendrá realmente esta información y la noción adecuada de un cambio de estado. Si esperáramos a que ocurriera otra cosa, podríamos acabar en una situación en la que no sabríamos realmente qué está pasando.

El segundo punto donde se produce la actualización de estado es precisamente el próximo acontecimiento que vamos a ver: la función OnCalculate.

Cada vez que el precio se actualiza o aparece una nueva barra en el gráfico, MetaTrader 5 activa un evento. Este evento llama a la función OnCalculate en el código del indicador. Normalmente, como es natural, siempre queremos que este código se ejecute lo más rápido posible para evitar que se pierdan acontecimientos de interés. A diferencia del evento de libro de órdenes, que informa del cambio de estado en cuanto se produce, el evento OnCalculate solo actúa cuando algo afecta al precio.

Aún así, puedes observar que en la línea 49 almacenamos y ajustamos un valor que no se utiliza allí. Si tenemos una llamada a una función, necesitamos utilizar este valor. ¿Por qué ocurre esto 🤔? De nuevo, necesitamos que la función OnCalculate se ejecute lo más rápido posible, y no tiene sentido seguir actualizando el búfer con cada movimiento del precio o con cada nueva barra que aparece. No olvides que estamos trabajando con el ratón, no con el precio ni las barras del gráfico.

Así que es el siguiente evento desencadenado por MetaTrader 5, responsable de actualizar el búfer: la función OnChartEvent.

Esta función OnChartEvent, que comienza en la línea 55, es bastante simple y directa. En ella, tenemos una llamada en la línea 57 para que los eventos sean manejados correctamente por la clase de estudio. Esto, a su vez, hará el trabajo interno necesario para manejar completamente el evento del ratón. Para más detalles al respecto, consulte "Desarrollo de un sistema de repetición (Parte 31): Proyecto EA - Clase C_Mouse (V)". Independientemente de esto, habremos llamado a un procedimiento y el flujo de ejecución se detendrá en la línea 102.

Antes de pasar a la línea 102, echemos un vistazo rápido al evento de la línea 76, que ya se ha mencionado antes al explicar el proceso de inicialización. Ahora veamos cómo se comportará este mismo código cuando el indicador se inicialice correctamente. Cuando esto ocurra, se ejecutarán las líneas entre 80 y 83. En la línea 80, comprobaremos para qué activo se está utilizando el indicador. La razón es que si el activo es uno de los que podemos vigilar para eventos de libro de órdenes, tenemos que informar a MetaTrader 5 que ya no deseamos recibir tales eventos; esto se hace en la línea 81. Las líneas 82 y 83 simplemente destruirán la clase, lo que garantizará que el indicador se elimine correctamente del gráfico.

Dado que el código a partir de la línea 102 es, de hecho, lo que queremos y necesitamos entender en este artículo, merece ser tratado en un tema aparte. Comienza justo debajo:


Procedimiento SetBuffer: Donde ocurre la magia.

Si has seguido bien y has estudiado los artículos de esta secuencia, Es posible que haya notado que en el artículo "Desarrollo de un sistema de repetición (Parte 39): Pavimentando el terreno (III)" , hay un tema entero dedicado exclusivamente a explicar cómo colocar valores en el búfer para enviar información del indicador a otro proceso.

En ese tema expliqué que había que poner la información en un punto muy concreto, pero, sobre todo, hice hincapié en el hecho de que la información debía estar sometida a un doble tipo de tutela. Quizá en ese artículo no te diste cuenta de lo que podemos hacer, pero el hecho es que podemos ir mucho más lejos de lo que la mayoría ha hecho hasta ahora.

Si miras el código entre las líneas 102 y 115, verás que estoy haciendo las cosas de una manera. Pero, ¿por qué lo hago así?

Así no tienes que seguir desplazándote hacia abajo para seguir la explicación. ¿Qué tal si acercamos estas líneas a la explicación? Así te será más fácil seguirla.

102. inline void SetBuffer(void)
103. {
104.    uCast_Double Info;
105.    
106.    m_posBuff = (m_posBuff < 0  ? 0 : m_posBuff);
107.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
108.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
109.    m_Buff[m_posBuff + 1] = Info.dValue;
110.    Info._int[0] = (*Study).GetInfoMouse().Position.X;
111.    Info._int[1] = (*Study).GetInfoMouse().Position.Y;
112.    m_Buff[m_posBuff + 2] = Info.dValue;
113.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
114.    m_Buff[m_posBuff + 3] = Info.dValue;
115. }

Empecemos entendiendo lo siguiente: en la línea 49, indico que voy a colocar cuatro valores dobles en el búfer. ¿Pero dónde exactamente en el búfer? Este punto se explica en el artículo mencionado anteriormente. Utilizaremos la posición rates_total, exactamente cuatro posiciones atrás desde este punto. Si estamos en la repetición o simulación, podríamos estar empezando desde la posición cero, porque somos nosotros quienes iniciamos. Esta inicialización se realizó en la línea 25.

Para facilitar las conversiones que haremos, y que necesitaremos hacer, usamos la línea 104 para declarar una variable local. Ahora, presta atención al hecho de que necesitamos realizar una prueba en la línea 106. Esto evita que ocurra algo extraño, principalmente debido a la línea 49, que está presente en la función OnCalculate. En el caso de un activo real, es poco probable que veamos un valor negativo en m_posBuff. Pero en el activo de repetición o simulación, podríamos tener un valor negativo. Así que la línea 106 corrige esto haciendo que m_posBuff apunte a una posición válida.

Si m_posBuff estuviera apuntando a un índice menor que cero, el indicador se rompería. Observa que en la línea 107, comenzamos a actualizar los datos del indicador. Este punto en particular es la parte fácil. Ahora viene la parte difícil, presente en las líneas 109, 112 y 114. En ellas, colocamos otros valores en el búfer, partiendo siempre de la posición calculada inicialmente.

Puede que te estés preguntando: ¿Podríamos publicar estos mismos valores en otros puntos?  La respuesta es un rotundo NO. ¿Podríamos colocarlos en un orden diferente? En este caso, la respuesta es un muy amistoso. Pero si cambias el orden, tendrás que resolver o más bien corregir, los códigos futuros para disponer de la información correcta. Si se cambia el orden, los valores se contabilizarán de manera diferente y deberás hacer correcciones aquí o en cualquier otro código que se derive de este indicador. O cualquier otra que quieras ensamblar.

Por lo tanto, es una buena idea empezar a desarrollar algún tipo de metodología. De lo contrario, las cosas se volverán extremadamente confusas y no serás capaz de aprovechar lo que MetaTrader 5 tiene para ofrecer. Así pues, la metodología que utilizan aquí es la siguiente:

  • En primer lugar, y en la posición CERO, almacenaremos el precio donde se encuentra la línea del ratón;
  • En la posición UM, almacenaremos el valor de tiempo donde se encuentra el puntero del ratón;
  • En la posición DOS, almacenaremos los valores en términos de posición en pantalla, es decir, coordenadas X e Y. Primero el valor X y después el valor Y;
  • En la posición TRES, almacenaremos otras cosas adicionales. Actualmente el estado de los botones del ratón. Se colocará en el byte menos significativo ( LSB ). Es decir, en el byte cero;

La metodología que utilizamos aquí acompañará a este indicador durante el resto de su vida útil, hasta que sufra algún tipo de modificación o actualización.


Conclusión

Aún hay otras cuestiones por resolver. Por eso, quiero que sigas los artículos para que entiendas lo que se expone en el código, porque a menudo el código en el archivo adjunto puede ser diferente al explicado en el artículo. Pero hay una razón para ello: el código adjunto es estable y perfectamente funcional, mientras que el código que se colocará en el artículo podría sufrir algún tipo de cambio. Tendrás que seguir los artículos para poder entender el código cuando sea estable. Si no lo haces, no podrás entender el código cuando sea estable...

Sin embargo, para aquellos que no pueden compilar código complejo, que implica varias aplicaciones diferentes, no se preocupen. De vez en cuando, adjuntaré el código compilado, así todo el mundo podrá seguir el desarrollo del sistema. Además, el código ya compilado que estará en algunos artículos te ayudará a comprender qué aplicaciones surgirán una vez que todo esté perfectamente compilado. Pero si ya tienes un poco más de experiencia y quieres mejorar tus conocimientos de programación, puedes añadir lo que se muestra en los artículos. Así aprenderás a modificar un sistema para poder aplicar cualquier otra cosa que desees hacer en el futuro. Sin embargo, si decides hacerlo, te sugiero que procedas con cuidado y no olvides probar siempre lo que añadas al código.

De todos modos, aún no hemos terminado. En el próximo artículo, continuaremos con este tema y resolveremos algunas cuestiones pendientes.

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/11624

Archivos adjuntos |
Anexo.zip (420.65 KB)
Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con integración RestAPI  (Parte 4): Organización de funciones en clases en MQL5 Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con integración RestAPI (Parte 4): Organización de funciones en clases en MQL5
Este artículo examina la transición de la codificación procedimental a la programación orientada a objetos (POO) en MQL5, enfocándose en la integración con REST APIs. Discutimos la organización de funciones de solicitudes HTTP (GET y POST) en clases y destacamos ventajas como el encapsulamiento, la modularidad y la facilidad de mantenimiento. La refactorización de código se detalla, y se muestra la sustitución de funciones aisladas por métodos de clases. El artículo incluye ejemplos prácticos y pruebas.
Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 3): Creación de jugadas automáticas y scripts de prueba en MQL5 Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 3): Creación de jugadas automáticas y scripts de prueba en MQL5
Este artículo explora la implementación de jugadas automáticas en el juego del tres en raya de Python, integrado con funciones de MQL5 y pruebas unitarias. El objetivo es mejorar la interactividad del juego y asegurar la robustez del sistema a través de pruebas en MQL5. La exposición cubre el desarrollo de la lógica del juego, la integración y las pruebas prácticas, y finaliza con la creación de un entorno de juego dinámico y un sistema integrado confiable.
Desarrollo de un sistema de repetición (Parte 41): Inicio de la segunda fase (II) Desarrollo de un sistema de repetición (Parte 41): Inicio de la segunda fase (II)
Si hasta ahora todo te ha parecido correcto, significa que no estás pensando realmente a largo plazo. Donde empiezas a desarrollar aplicaciones y, con el tiempo, ya no necesitas programar nuevas aplicaciones. Solo tienes que conseguir que trabajen juntos. Veamos cómo terminar de montar el indicador del ratón.
Validación cruzada y fundamentos de la inferencia causal en modelos CatBoost, exportación a formato ONNX Validación cruzada y fundamentos de la inferencia causal en modelos CatBoost, exportación a formato ONNX
En este artículo veremos un método de autor para crear bots utilizando el aprendizaje automático.