English Русский Deutsch 日本語 Português
preview
Desarrollo de un sistema de repetición (Parte 29): Proyecto Expert Advisor — Clase C_Mouse (III)

Desarrollo de un sistema de repetición (Parte 29): Proyecto Expert Advisor — Clase C_Mouse (III)

MetaTrader 5Probador | 28 febrero 2024, 10:45
163 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, Desarrollo de un sistema de repetición (Parte 28): Proyecto Expert Advisor — Clase C_Mouse (II), demostré cómo crear códigos más legibles. Aunque ese modelo es bastante interesante, por hacer el programa más claro, notarás que programar siguiendo este enfoque puede ser más demorado. Esto no ocurre porque la programación se vuelva confusa, todo lo contrario. El desafío es que esta forma de hacer el programa más legible presenta sus limitaciones. Una de ellas es la propia sintaxis de cualquier lenguaje de programación. A pesar de que la sintaxis está diseñada para tener un formato y estructura específicos, recurrir a definiciones ayuda, pero actúa como una muleta, limitándonos en otros aspectos. Aun así, considero válido mostrar esto en un código real, desde mi punto de vista.

Mantendremos la sintaxis original. Pero, si deseas estudiar el código de la manera que mostramos, siéntete libre de crear las definiciones que consideres necesarias y adaptar el código para que puedas comprenderlo más fácilmente. Esto te ayudará a aprender algunas técnicas bastante interesantes. Fue así como aprendí a programar en varios otros lenguajes. Es un proceso trabajoso, pero gratificante, pues existen técnicas e incluso algoritmos que son más simples de ser implementados en un lenguaje que en otro. Pero, si consigues leer el código en el lenguaje original, serás capaz de realizar tareas que otros no pueden. Piensa en esto como un trabajo de traducción, en el cual serás el intérprete entre dos mundos distintos. Para tener éxito, tu comprensión necesita ser mucho más amplia que la de alguien que siempre se comunica usando los mismos signos, símbolos y términos.

Expande tu mente, mira más allá de lo convencional, y un universo entero se desplegará ante ti.

Pero vayamos a lo que nos trae a este artículo. Aquí mostraré cómo puedes, sin modificar una clase y sin utilizar el sistema de herencia, lograr ampliar de forma controlada, segura y robusta las capacidades de un sistema, independientemente de cuál sea. La tarea puede parecer simple a primera vista, pero ofrecerá un conocimiento profundo sobre el funcionamiento de las cosas, mucho más allá de lo que se obtiene construyendo siempre de la misma manera.

En el artículo de hoy, presentaré una manera de expandir el sistema de estudio de un activo. Usaremos la clase C_Mouse, junto con lo que heredó de la clase C_Terminal, a fin de generar otro nicho de análisis. Pero lo haremos de una manera bastante interesante: crearemos una nueva clase que utilizará el contenido de la clase C_Mouse, pero sin heredar directamente de ella. Esta nueva clase podrá, entonces, ser añadida o no al código final, dependiendo, claro, de tus objetivos. Pero, independientemente de eso, aprenderás cómo crear un modelo de estudio personalizado, sin comprometer la integridad de un código previamente creado y probado. Este es el real objetivo de este artículo.


Preparamos el terreno para la expansión

Antes de iniciar la programación de una clase que no heredará la clase C_Mouse, pero que expandirá o, mejor dicho, modificará la funcionalidad de la clase C_Mouse, es necesario ajustar algunos detalles en la clase original C_Mouse. No es que haya algún problema con ella, pero necesitamos hacer algunas adiciones y una pequeña alteración, que facilitarán cualquier tipo de expansión. Así, cualquier modificación en la funcionalidad se volverá bastante práctica. Porque si algo sale mal, podremos simplemente volver a usar nuestra clase original sin ningún tipo de problema. Las alteraciones que realizaremos son pocas, simples, pero importantes. Inicialmente, añadiremos una nueva variable al código de la clase C_Mouse.

class C_Mouse : public C_Terminal
{
   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
      struct st_Mouse
      {
         struct st00
         {
            int     X,
                    Y;
            double  Price;
            datetime dt;
         }Position;
         uint    ButtonStatus;
         bool    ExecStudy;
      };

Esta variable posibilitará la expansión o modificación del funcionamiento de la clase C_Mouse sin necesidad de herencia o uso de polimorfismo. Aunque estos son los métodos más comunes, optaremos por un enfoque diferente. De hecho, la técnica que demostraré permite aplicar esta estrategia en cualquier clase, proporcionando un funcionamiento adaptado a nuestras necesidades específicas. Y aquí reside el aspecto importante: todo esto será hecho sin alterar siquiera una línea del código original de la clase. Antes de implementar los cambios que efectivamente ampliarán las capacidades de actuación, necesitamos añadir una simple línea de código en la clase C_Mouse, en el siguiente procedimiento. Algo simple.

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj, const color cor)
   {
      ObjectCreate(GetInfoTerminal().ID, szName, obj, 0, 0, 0);
      ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, cor);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_ZORDER, -1);
   }

Esta línea específica asegura que, incluso si la línea de precio del mouse está configurada como una línea de primer plano, ella no recibirá eventos en caso de que esté superponiendo algún objeto en el cual estamos intentando clicar. La línea de precio solo capturará eventos de clic si no hay ningún objeto bajo el foco del cursor del ratón. Es importante resaltar que la adición de esta línea no bloquea la generación de estudios, incluso si el clic ocurre sobre un objeto. Ya que esta línea de código impide que el objeto reciba directamente el clic, pero no obstruye la activación y captura del evento CHARTEVENT_MOUSE_MOVE por la clase C_Mouse.

Nota importante: En el pasado, tuve problemas con algo precisamente por la falta de esta línea de código. En el artículo, Haciendo el gráfico más interesante: Adicionando un fondo de pantalla, existe una falla, que en ese momento no pude resolver, por más que lo intentase la falla era persistente. Para solucionar la falla que impedía el acceso a objetos presentes en el gráfico, en el objeto utilizado para insertar el fondo en el gráfico, debes añadir esta misma línea destacada aquí. Podría haber compartido esta sugerencia anteriormente, pero quiero recompensar de alguna manera a aquellos que realmente leen los artículos. Así, ahora ya sabes cómo resolver el problema mencionado en el artículo.

A continuación, haremos pequeños cambios en la siguiente función:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
      {
         int w = 0;
         static double memPrice = 0;
                                
         C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
         switch (id)
         {
            case (CHARTEVENT_CUSTOM + ev_HideMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
               break;
            case (CHARTEVENT_CUSTOM + ev_ShowMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
               break;
            case CHARTEVENT_MOUSE_MOVE:
               ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
               ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price));
               m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
               ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
               if (m_Info.Study != eStudyNull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
               m_Info.Data.ButtonStatus = (uint) sparam;
               if (CheckClick(eClickMiddle) && ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
                  ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price);
                  m_Info.Study = eStudyExecute;
               }
               if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
               m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
               break;
            case CHARTEVENT_OBJECT_DELETE:
               if (sparam == def_NameObjectLineH) CreateLineH();
               break;
         }
      }

Los cambios mencionados no benefician directamente a la clase C_Mouse, sino al programa entero que será construido sobre la clase C_Mouse. Quizás no se perciba ninguna diferencia en el código al compararlo con los artículos anteriores y los actuales, lo que ocurre debido a la sutileza y especificidad del cambio, que, por sí mismo, no altera nada en el código. Pero estas alteraciones traen diversos beneficios en términos de usabilidad y posibilidades de ajuste. La diferencia puede no ser fácilmente notada, pero las tres líneas añadidas al código son de gran ayuda. Vamos a ver qué hace cada una:

  1. Esta línea ajustará el valor del tiempo para que podamos de hecho usar cualquier objeto en el gráfico no solo usando coordenadas de pantalla (X e Y), sino también coordenadas del activo (Precio y Tiempo). Esto era una cuestión que desde hace mucho tiempo estaba buscando una forma de obtener. Dado el hecho de que es mucho más interesante trabajar en coordenadas del activo que usando coordenadas de pantalla en algunos momentos. El nivel de libertad que esto nos da, salta a la vista. Pero puedes notar que estamos haciendo una llamada y esta está en la clase C_Terminal por motivos prácticos.
  2. Se añadió esta llamada a la función ChartTimePriceToXY, de modo que las coordenadas del activo sean convertidas a coordenadas de pantalla.
  3. Y el último punto fue justamente este. Donde indicamos si la clase C_Mouse está o no en modo de estudio. Atención a la sintaxis para no confundir las cosas.

Esas son todas las modificaciones realizadas en la clase C_Mouse. Pero como mencionado, la clase C_Terminal ahora incluye una nueva función en su portafolio. Vamos a examinar esa función para entender de qué se trata. La función añadida se presenta a continuación:

inline datetime AdjustTime(const datetime arg) const
   {
      int nSeconds= PeriodSeconds();
      datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
                                
      return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
   }

Si esta función te parece extremadamente extraña y confusa, no te sientas mal por esto. Ella puede ser confusa a primera vista y, al primer análisis, parecer sin sentido. Sin embargo, en el fondo, ofrece algo maravilloso y extremadamente interesante. Para entender la magia detrás de esta función, necesitas comprender una cosa sobre la conversión de tipos, además de algunos detalles adicionales. Primero, vamos a abordar la conversión de tipos.

Cuando esta función es llamada, recibe como parámetro un valor datetime. Es importante no ver ese valor solo como datetime, sino como un valor ulong, pues eso es lo que realmente es: un valor de 8 bytes. Primero, entiende esto. La próxima cosa que debes entender es que, por ser datetime un valor ulong, la información sobre la fecha y hora están compactadas de una manera bastante específica dentro de la variable. Lo que realmente nos interesa son los bits menos significativos (LSB), donde están localizados los valores de segundo, minuto, hora, día, mes y año, en este orden, del bit menos significativo al más significativo.

Es importante notar que nSeconds, contiene el valor en segundos del período utilizado en el gráfico. Esto es determinado por la función PeriodSeconds, que proporciona esa información. Ahora, la variable dt contiene el valor de creación de la última barra presente en el gráfico, independientemente del activo en uso. Este detalle es relevante para nosotros, pues, si el valor de dt, es menor que el valor del parámetro de la llamada, esto indicará en qué punto del tiempo estamos, es decir, en el futuro. Así, la posición del tiempo en relación a la coordenada gráfica de la pantalla (X e Y) indica dónde una barra futura estará o será creada. En este momento, no es posible saber, usando la función iTime, dónde estará esto, pues el gráfico aún no ha sido construido hasta ese punto. No obstante, incluso así, necesitamos saber dónde estará de hecho, especialmente si estamos realizando un estudio relacionado con el futuro.

Para saber las coordenadas de pantalla (X e Y), aprovechamos el hecho de que datetime, es, de hecho, un valor ulong. Al dividir ese valor por el número de segundos nSeconds, obtenemos un valor double, es decir, un valor fraccionario. A continuación, viene la parte importante: si multiplicamos ese valor fraccionario por el número de nSeconds, recuperaremos el valor original. Por ejemplo, si dividimos 10 por 3 y multiplicamos el resultado de esa división por 3, obtendremos 10 de nuevo. Sin embargo, al realizar la conversión de tipos, y aquí está la clave del asunto, transformamos el valor double en un valor ulong, o mejor, un valor del tipo datetime. Este no contiene la parte fraccionaria. Por lo tanto, al multiplicar el valor por nSeconds, tendremos el valor en el futuro y ya ajustado. Esta es la parte interesante de la cosa. Sin embargo, este método presenta un problema

cuando miramos hacia el pasado, especialmente para series continuas, es decir, cuando no hay un intervalo de tiempo ausente. Este enfoque no es adecuado para analizar el pasado, principalmente en activos que presentan estos intervalos, algo común en activos negociados dentro de ciertos horarios o días. Esto se aplica a activos de bolsa de valores, donde las barras se forman solo dentro de una ventana de tiempo específica, estando el mercado cerrado fuera de ese período. Para manejar esta situación, adoptamos un ajuste un poco diferente, buscando identificar en qué barra se encuentra el valor especificado. Hacemos esto al calcular el número de barras entre la barra actual y la barra en el momento indicado, lo que nos proporciona un valor que utilizamos como desplazamiento para capturar exactamente el punto en el tiempo. Así, conseguimos también ajustar el tiempo pasado, independientemente de lo que haya ocurrido.

Puedes pensar que esta función es completamente innecesaria. ¿Y por qué tomarse la molestia de desarrollarla? Pero, al usar esta función, puedes transformar coordenadas de activo (Precio y Tiempo) en coordenadas de pantalla (X y Y). Así lograremos usar cualquier tipo de objeto gráfico, sin quedarnos más limitados a los objetos de coordenadas de pantalla o de activo. Tendremos la habilidad de convertir un tipo de coordenada en el otro, y para eso utilizaremos las llamadas: ChartXYToTimePrice (para convertir coordenadas de pantalla en coordenadas de activo) y ChartTimePriceToXY (para convertir coordenadas de activo en coordenadas de pantalla). Recuerda este importante hecho: en algunos tipos de estudios, necesitamos una información lo más precisa posible. Cuando queremos que la exacta barra sea usada como punto de indicación para algo, esta conversión se hace esencial. Además, esto nos proporciona un tipo de información bastante interesante, que mostraré más adelante.


Creamos la clase C_Studys

Ahora que hemos mejorado la clase C_Mouse, podemos concentrarnos en crear una clase destinada a establecer una base totalmente nueva de estudios. Como mencioné al inicio del artículo, no utilizaremos herencia o polimorfismo para crear esta nueva clase. En cambio, vamos a modificar, o mejor, agregar nuevos objetos a la línea de precio. Esto es lo que haremos en este primer momento, y en el próximo artículo, mostraré cómo cambiar los estudios. Pero, realizaremos esto sin cambiar el código de la clase C_Mouse. Reconozco que, en la práctica, esto sería más fácilmente logrado mediante herencia o polimorfismo. No obstante, existen otras técnicas para alcanzar el mismo resultado, ya que ofrecen flexibilidad sin causar trastornos significativos si el nuevo código presenta defectos.

Este enfoque nos permite eliminar el código problemático, corregir los errores y reintegrarlo sin necesidad de ajustes en el código previamente probado. Recuerda: a menudo, la corrección de fallos puede hacer que un código sea incompatible con la estructura de clases existente, impidiendo el uso de herencia o polimorfismo para incorporar nuevas adiciones. Por lo tanto, dominar estas técnicas alternativas es importante, especialmente cuando se desea implementar nuevas funcionalidades en un programa ya finalizado y probado, sin la necesidad de reestructurar extensivamente el código existente.

El primer paso consiste en crear un nuevo archivo, aunque pueden ser varios archivos distintos con su propia jerarquía entre las clases involucradas. Esto no será un problema, ya que estos archivos, o archivo, no integrarán la jerarquía de la clase principal. De esta manera, podremos establecer una jerarquía independiente, que podrá ser alterada o mejorada según la necesidad, de varias maneras. Esta flexibilidad se deriva del hecho de que no tiene involucramiento directo con la jerarquía principal en desarrollo, actuando casi como un proyecto aparte.

Iniciaremos con un sistema más simple, pero planeamos explorar enfoques más complejos en el futuro. El archivo se inicia de la siguiente manera:

#include "..\C_Mouse.mqh"
#include "..\..\..\Service Graphics\Support\Interprocess.mqh"
//+------------------------------------------------------------------+
#define def_ExpansionPrefix "Expansion1_"
#define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
#define def_ExpansionBtn2 def_ExpansionPrefix + "B2"
#define def_ExpansionBtn3 def_ExpansionPrefix + "B3"
//+------------------------------------------------------------------+
#define def_InfoTerminal (*mouse).GetInfoTerminal()
#define def_InfoMousePos (*mouse).GetInfoMouse().Position
//+------------------------------------------------------------------+

A partir de esta introducción al código, nos damos cuenta de que el proceso será intensivo. Observen la inclusión de dos archivos de encabezado ubicados en posiciones distintas de este archivo. Este procedimiento ya ha sido discutido anteriormente. A continuación, nombramos algunos objetos que utilizaremos, lo cual es importante para evitar confusiones al acceder a los objetos correctos posteriormente. También definimos un tipo de alias para simplificar nuestra codificación, ya que trabajaremos con algo muy similar a punteros, uno de los recursos más poderosos y, al mismo tiempo, más arriesgados disponibles. Pero debido a la manera en que planeamos programar, el riesgo asociado a este recurso estará bastante controlado, permitiendo su uso de formas bastante interesantes.

Este tipo de definición (alias) es bastante común cuando queremos acceder a ciertas cosas, pero no queremos correr el riesgo de teclear algo incorrecto durante la codificación. Siempre es un recurso muy interesante de ser utilizado.

Concluida esta introducción, daremos inicio al código de la clase, que se presenta de la siguiente manera:

class C_Studys
{
   protected:
   private :
//+------------------------------------------------------------------+
      enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
//+------------------------------------------------------------------+
      C_Mouse *mouse;
//+------------------------------------------------------------------+
      struct st00
      {
         eStatusMarket   Status;
         MqlRates        Rate;
         string          szInfo;
         color           corP,
                         corN;
         int             HeightText;
      }m_Info;

En esta fase, nos enfocamos exclusivamente en las variables privadas de la clase. Observen atentamente una de ellas, que actúa como una especie de puntero hacia la clase C_Mouse. Los punteros están entre los recursos más poderosos en programación, pero requieren una atención especial debido a los desafíos que pueden surgir en su utilización. Por lo tanto, es fundamental proceder con cautela al usar punteros, incluso si en MQL5 no presentan las mismas características encontradas en C/C++. La cautela es esencial para evitar complicaciones. Nunca subestimes los punteros. El resto del código, por ahora, no requiere una atención especial.

La primera de las rutinas dentro de la clase se introduce a continuación:

const datetime GetBarTime(void)
   {
      datetime dt = TimeCurrent();
                                
      if (m_Info.Rate.time <= dt)
         m_Info.Rate.time = iTime(def_InfoTerminal.szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds();

      return m_Info.Rate.time - dt;
   }

Aquí, presentamos una rutina bastante simple, que calcula el tiempo restante hasta el surgimiento de una nueva barra. El cálculo propiamente dicho ocurre solo en ese punto específico, sin embargo, siempre que una prueba detecta que el tiempo límite de la barra ha sido alcanzado, procedemos a la lectura y al ajuste necesarios para llevar el cálculo hasta la próxima barra. Así, esta llamada a la función iTime ocurre solo una vez para cada barra creada, y no en cada interacción o llamada de procedimiento. La función siguiente ya es bien conocida y tiene la función de crear los objetos de manera estandarizada.

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj)
   {
      ObjectCreate(def_InfoTerminal.ID, szName, obj, 0, 0, 0);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_ZORDER, -1);
   }

Prácticamente realizamos el mismo proceso que ocurre en la función encontrada en C_Mouse, lo que sugiere que, pronto, podríamos considerar la unificación de ambas en una función aún más genérica. Inmediatamente después, introducimos un procedimiento que, para algunos, puede ser interesante para realizar pruebas.

int CreateBTNInfo(const string szExample, int x, string szName, color backColor, string szFontName, int FontSize)
   {
      int w;
                                
      CreateObjectBase(szName, OBJ_BUTTON);
      TextGetSize(szExample, w, m_Info.HeightText);
      m_Info.HeightText += 5;
      w += 5;
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_STATE, true);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BGCOLOR, backColor);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_FONT, szFontName);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_FONTSIZE, FontSize);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XSIZE, w); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_YSIZE, m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, x);
                                
      return w;
   }

Este procedimiento es capaz de ajustar el tamaño del objeto basado en el tamaño y tipo de la fuente utilizada, así como en el texto más largo que será impreso dentro del objeto. Este ajuste es posible gracias a la función TextGetSize, que, con base en la información proporcionada, nos da una estimación del tamaño del texto. Sin embargo, para asegurar que el texto no quede apretado dentro del objeto, añadimos un poco a sus dimensiones. Esto crea un pequeño espacio entre el texto y los límites del objeto, permitiendo, por diversas razones, la devolución del valor calculado y, así, la creación de una línea de objetos.

A continuación, tenemos la rutina que traza la información en nuestro gráfico.

void Draw(void)
   {
      double v1;
                                
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo);
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / def_InfoMousePos.Price) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(def_InfoTerminal.szSymbol, PERIOD_D1, 0)) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
   }

Esta es precisamente el análisis que genera un tipo de estudio, siempre presente y que, de alguna manera, acompaña a la línea de precio. Así, conseguimos entender algunas cosas fácilmente. El tipo de información y lo que se informará varían conforme el interés de cada uno. Para fines de demostración, abordaremos tres tipos de información. Entonces, vamos a entender lo que se informará aquí.

En este momento, podemos recibir información diferente, dependiendo de cómo el programa interactúa con la plataforma y con el servidor de trading. Pero, en esencia, podríamos ser informados sobre los siguientes puntos:

  • Tiempo restante hasta que la próxima barra aparezca en el gráfico;
  • Información diciendo que el mercado se encuentra cerrado;
  • Información diciendo que estamos lidiando con una repetición;
  • Información diciendo que el activo se encuentra en subasta;
  • Y en casos excepcionalmente raros, un mensaje de error.

Noten que no es algo extraordinario lo que estamos haciendo. No obstante, esto sirve solo para demostrar la técnica que utilizaremos. A continuación, presentamos el destructor.

~C_Studys()
   {
      ObjectsDeleteAll(def_InfoTerminal.ID, def_ExpansionPrefix);
   }

El código arriba tiene como función específica indicar a la plataforma que deseamos remover los elementos creados por la clase. Observen que no nos preocupamos si esto provocará eventos por parte de la plataforma, pues no tenemos la intención de recrear tales objetos, en caso de que sean removidos.

El próximo punto a ser examinado es la rutina mostrada a continuación:

void Update(void)
   {
      switch (m_Info.Status)
      {
         case eCloseMarket: m_Info.szInfo = "Closed Market";                         break;
         case eAuction   : m_Info.szInfo = "Auction";                                break;
         case eInTrading : m_Info.szInfo = TimeToString(GetBarTime(), TIME_SECONDS); break;
         case eInReplay  : m_Info.szInfo = "In Replay";                              break;
         default         : m_Info.szInfo = "ERROR";
      }
      Draw();
   }

Un detalle importante de esta rutina es que no opera aisladamente. Tenemos otra con nombre muy similar, presentada justo a continuación:

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();
   }

Pero cómo es eso... ¿tenemos dos rutinas con el nombre de Update?! ¿Es esto posible?! Sí, podemos declarar funciones con el mismo nombre aparente. Esto es conocido como sobrecarga. Aunque los nombres sean similares, para el compilador, el nombre de ambas rutinas es diferente. Esto se debe a los parámetros. Es posible sobrecargar rutinas y procedimientos de esta manera, pero la única regla es que los parámetros deben ser diferentes. Observen, en la segunda rutina, que hacemos una llamada a la primera, utilizando el método donde ningún parámetro es necesario. Este tipo de práctica es bastante común al programar métodos que sufren sobrecarga, buscando crear algo que facilite la depuración.

Ahora, tenemos un punto interesante: la forma en que podemos saber si un activo está o no en subasta. Normalmente, tendremos información de precio siendo suministrada en el libro de órdenes. Sin embargo, puede ocurrir que el libro de órdenes del activo contenga uno de esos valores específicos, y cuando esto sucede, significa que el activo está en subasta. Presten atención a esto, pues puede ser un recurso interesante para añadir en un Expert Advisor automático. Ya abordé esto en otra serie de artículos:  Cómo construir un EA que opere automáticamente (Parte 14): Automatización (VI), pero no detallé cómo saber si el activo entró o no en subasta. El objetivo no era cubrir detalladamente todos los aspectos involucrados en el proceso de trading con un EA automático.

Para responder a algunos eventos de la plataforma, contamos con la siguiente función:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {               
      switch (id)
      {
         case CHARTEVENT_MOUSE_MOVE:
            Draw();
            break;
      }
   }

Simple, pero eficaz, ya que los objetos deben seguir el ratón, y el análisis del ratón queda a cargo de la clase C_Mouse. Nuevamente, modificamos el funcionamiento de la clase C_Mouse, sin recurrir a la herencia o al polimorfismo. Entonces, ajustar y corregir el posicionamiento del ratón es una responsabilidad de la clase C_Mouse, que utilizaremos solo para acceder a los datos.

Ya hemos explorado prácticamente todo el código. Pero necesitamos ver dónde realmente ocurre la magia. Comenzaremos con el código del Expert Advisor, que puede verse íntegramente justo abajo:

//+------------------------------------------------------------------+
#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_Studys.mqh>
//+------------------------------------------------------------------+
input group "Mouse";
input color     user00 = clrBlack;      //Price Line
input color     user01 = clrPaleGreen;  //Positive Study
input color     user02 = clrLightCoral; //Negative Study
//+------------------------------------------------------------------+
C_Mouse *mouse = NULL;
C_Studys *extra = NULL;
//+------------------------------------------------------------------+
int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra = new C_Studys(mouse, user01, user02);
                
   OnBookEvent(_Symbol);
   EventSetMillisecondTimer(500);

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   MarketBookRelease(_Symbol);
   EventKillTimer();
        
   delete extra;
   delete mouse;
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
   (*extra).Update();
}
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{

       MqlBookInfo book[];
   

       if (mouse.GetInfoTerminal().szSymbol == def_SymbolReplay) ArrayResize(book, 1, 0); else
   {
      if (symbol != (*mouse).GetInfoTerminal().szSymbol) return;
      MarketBookGet((*mouse).GetInfoTerminal().szSymbol, book);
   }
   (*extra).Update(book);
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    (*mouse).DispatchMessage(id, lparam, dparam, sparam);
    (*extra).DispatchMessage(id, lparam, dparam, sparam);
        
    ChartRedraw();
}
//+------------------------------------------------------------------+

Note que estoy proporcionando a este Expert Advisor la capacidad de operar tanto en un activo real como en uno simulado, utilizando las mismas herramientas. Esto es posible gracias a este tipo de prueba. En este momento y en esta fase de desarrollo, tendremos información diferente dependiendo de lo que el Expert Advisor identifique en el gráfico.

Pasamos al constructor de la clase el puntero creado durante la inicialización de la clase C_Mouse. De esta forma, la clase C_Studys no necesita heredar directamente de la clase C_Mouse para utilizar su contenido, eliminando la necesidad de inicializar C_Mouse dentro de C_Studys. Este método de modelado se muestra valioso cuando deseamos compartir información o interactuar con elementos sin recurrir a la herencia o al polimorfismo. Si la intención es eliminar, adaptar o crear una nueva funcionalidad para la clase C_Studys, que de alguna manera se relacione con la clase C_Mouse, esto puede hacerse con facilidad, sin necesidad de alterar la clase C_Mouse.

La gran ventaja de este modelo es permitir el desarrollo paralelo de componentes. En caso de que la clase C_Studys no termine integrando el proyecto final, basta con remover su código sin preocupaciones por complicaciones o pendientes típicas de la herencia. Así, C_Studys se convierte en una clase paralela, independiente del sistema de clases principal del código final.

Ahora, vamos a analizar el código del constructor para comprender cómo se implementa esta transferencia de información.

C_Studys(C_Mouse *arg, color corP, color corN)
   {
#define def_FontName "Lucida Console"
#define def_FontSize 10
      int x;
                                        
      mouse = arg;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      TextSetFont(def_FontName, -10 * def_FontSize, FW_NORMAL);
      CreateBTNInfo("Closed Market", 2, def_ExpansionBtn1, clrPaleTurquoise, def_FontName, def_FontSize);
      x = CreateBTNInfo("99.99%", 2, def_ExpansionBtn2, clrNONE, def_FontName, def_FontSize);
      CreateBTNInfo("99.99%", x + 5, def_ExpansionBtn3, clrNONE, def_FontName, def_FontSize);
      Draw();
#undef def_FontSize
#undef def_FontName
   }

Como pueden observar, este código presenta peculiaridades, incluyendo un parámetro algo inusual:  un puntero hacia una clase. Esto demuestra cómo los elementos se conectan, formando el sistema que necesitamos desarrollar. Sin embargo, hay otra característica notable y que puede parecer extraña a primera vista: la función TextSetFont. Es esencial para ajustar las dimensiones de los objetos conforme al tipo de información que pretendemos exhibir. Es importante notar que estamos realizando una factorización aquí. Pero, ¿por qué esta factorización es tan inusual, utilizando un número negativo? Para aclarar, veamos la explicación proporcionada en la documentación:

El tamaño de la fuente se define usando valores positivos o negativos. Este hecho define la dependencia del tamaño del texto a partir de las configuraciones del sistema operativo (escala de tamaño).

  • Si el tamaño se especifica por un número positivo, este tamaño se transforma en unidades de medidas físicas de un dispositivo (píxeles) cuando se cambia de una fuente lógica a una física, y este tamaño corresponde a la altura de los símbolos glifos elegidos entre las fuentes disponibles. Este caso no es recomendado cuando los textos exhibidos por la función TextOut() y los exhibidos por OBJ_LABEL ("Etiquetas"), donde objetos gráficos están siendo utilizados conjuntamente en el gráfico.
  • Si el tamaño se determina por un número negativo, este número debe ser definido en décimos de un punto lógico (-350 es igual a 35 puntos lógicos) dividido en 10. Un valor obtenido es entonces transformado en unidades de medidas físicas de un dispositivo (píxeles) y corresponde al valor absoluto de la altura de un símbolo elegido a partir de las fuentes disponibles. Multiplicar el tamaño de fuente determinado en las propiedades del objeto por -10 para hacer que el tamaño de un texto en pantalla sea similar al de un objeto OBJ_LABEL.

 Por este motivo, esta factorización se realiza de esta manera. Si no haces este ajuste aquí, tendrás problemas al usar la función TextGetSize, que se usa en la rutina de creación de los objetos. Esto sucede porque la fuente utilizada o sus dimensiones pueden no corresponder exactamente a las que pretendes emplear.


Conclusión

No dejes de probar el adjunto proporcionado, que ofrece acceso al sistema actual. Es recomendable realizar experimentaciones tanto en el replay/simulador como en una cuenta operando en el mercado (DEMO o REAL),

para obtener una comprensión amplia. Pero NO modificaré ni una sola línea del código de la clase principal. Esa es una promesa.

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

Archivos adjuntos |
Files_-_FUTUROS.zip (11397.51 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_BOLSA.zip (1358.24 KB)
Redes neuronales: así de sencillo (Parte 58): Transformador de decisión (Decision Transformer-DT) Redes neuronales: así de sencillo (Parte 58): Transformador de decisión (Decision Transformer-DT)
Continuamos nuestro análisis de los métodos de aprendizaje por refuerzo. Y en el presente artículo, presentaremos un algoritmo ligeramente distinto que considera la política del Agente en un paradigma de construcción de secuencias de acciones.
Teoría de categorías en MQL5 (Parte 22): Una mirada distinta a las medias móviles Teoría de categorías en MQL5 (Parte 22): Una mirada distinta a las medias móviles
En el presente artículo intentaremos simplificar los conceptos tratados en esta serie centrándonos en solo un indicador, el más común y probablemente el más fácil de entender: la media móvil. También veremos el significado y las posibles aplicaciones de las transformaciones naturales verticales.
Desarrollo de un sistema de repetición (Parte 30): Proyecto Expert Advisor — Clase C_Mouse (IV) Desarrollo de un sistema de repetición (Parte 30): Proyecto Expert Advisor — Clase C_Mouse (IV)
Aquí te mostraré una técnica que puede ayudarte mucho en varios momentos de tu vida como programador. En contra de lo que muchos dicen, lo limitado no es la plataforma, sino los conocimientos del individuo que lo dice. Lo que se explicará aquí es que con un poco de sentido común y creatividad, se puede hacer que la plataforma MetaTrader 5 sea mucho más interesante y versátil, sin tener que crear programas locos ni nada por el estilo puedes crear un código sencillo, pero seguro y fiable. Utiliza tu ingenio para domar el código con el fin de modificar algo que ya existe, sin eliminar ni añadir una sola línea al código original.
Integración de modelos ML con el simulador de estrategias (Conclusión): Implementación de un modelo de regresión para la predicción de precios Integración de modelos ML con el simulador de estrategias (Conclusión): Implementación de un modelo de regresión para la predicción de precios
Este artículo describe la implementación de un modelo de regresión de árboles de decisión para predecir precios de activos financieros. Se realizaron etapas de preparación de datos, entrenamiento y evaluación del modelo, con ajustes y optimizaciones. Sin embargo, es importante destacar que el modelo es solo un estudio y no debe ser usado en operaciones reales.