English Русский Deutsch 日本語 Português
preview
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)

MetaTrader 5Probador | 29 febrero 2024, 09:28
191 1
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior "Desarrollo de un sistema de repetición (Parte 29): Proyecto EA - Clase C_Mouse (III) desarrollamos la clase C_Mouse de tal forma que fuera posible ampliar la funcionalidad de los estudios, sin que esto comprometiera en modo alguno ninguna parte de nuestro código. Dado que confías y utilizas una técnica de programación que te permitirá crear código en paralelo, tendrás un proyecto principal que crecerá de forma organizada. Al mismo tiempo, si lo deseamos, podemos añadir funcionalidades adicionales al sistema. Para que, cuando se implementen estas funcionalidades, nuestro código principal no se vea comprometido de ninguna manera, ni tenga cuelgues durante el uso indiscriminado de la herencia.

Aunque la programación orientada a objetos (POO) es una forma excelente de programar, es mucho más adecuada para proyectos de producción en los que queremos tener un control estricto de lo que ocurre para no generar errores extraños a medida que crece el sistema. A veces tenemos que desarrollar parte del proyecto de forma paralela. Suena extraño decir esto, pero cuando estamos añadiendo algún tipo de función o método a un programa, no es muy apropiado que pongas la función justo al principio (fase de pruebas) en medio de un código que ya está en una fase avanzada. O mejor dicho, no conviene añadir una función no probada a un código ya probado y en funcionamiento, ya que esto podría provocar fallos que no estaban totalmente previstos.

Este tipo de fallos suelen ser los responsables de que todo un proyecto vuelva a las primeras fases de desarrollo, porque no podemos eliminar fácilmente la función recién añadida. Ya que puede estar tan incrustada en el código que eliminarla es mucho más complejo que reiniciar el proyecto en una fase anterior. Aunque mucha gente, especialmente los programadores que están empezando, en realidad no hacen uso de una estructura de directorios con el fin de tener un punto de retorno a las etapas anteriores de desarrollo, podemos, incluso sin utilizar este tipo de estructuras de directorios, hacer uso de algún tipo de técnica que nos permita devolver el sistema a un punto en el que el recurso añadido no forme parte realmente del proyecto terminado.

De este modo, puede trabajarse en paralelo, mientras el proyecto final avanza sin problemas. Para hacer uso de esto, en el artículo anterior te mostré cómo crear un enfoque utilizando punteros. Ahora vayamos un paso más allá, hasta el punto de poder generar un estudio más elaborado a partir de un modelo básico. Si este estudio o recurso resulta adecuado para el proyecto final, una vez que se encuentre en una fase más avanzada de pruebas y se considere lo suficientemente estable y sólido, podrá incorporarse al sistema principal de la clase. De este modo, lo que antes se consideraba un proyecto secundario pasa a formar parte del proyecto final, heredando y siendo heredado dentro del sistema de clases.

Para demostrarlo, crearemos una modificación de la clase C_Mouse, pero sin utilizar la herencia ni el polimorfismo. Al mismo tiempo, tendremos un modelo de estudio completamente diferente del sistema original presente en la clase C_Mouse. Para ello, crearemos una nueva clase que puede heredar o no de la clase C_Estudios, que vimos en el artículo anterior. Heredar o no la clase C_Studys es más una cuestión personal que práctica. A decir verdad, de un modo u otro, un proyecto no tendrá nada que ver con el otro, ya que pueden trabajarse en paralelo. Independientemente de esto, cualquier código perteneciente al sistema principal heredará la clase C_Mouse hasta que el código que amplíe la clase pueda considerarse lo suficientemente estable e interesante como para ser utilizado en el proyecto final.

Antes de entrar en la parte de programación, es importante que sepas que el sistema puede tomar dos caminos diferentes. La elección de seguir un camino u otro depende de lo que quieras hacer y de lo lejos que quieras llegar. Como tenemos dos caminos y la diferencia entre ellos es muy pequeña, te mostraré los dos. En el código adjunto, tendrás acceso a una de las dos rutas. Pero si lo deseas, puedes hacer los cambios necesarios para tomar otra ruta, y nada te impide tomar tu propia decisión en cuanto a qué ruta tomar.

La idea aquí siempre será demostrar lo que puedes hacer dentro de la plataforma, no cómo debes hacerlo.


Adiciones a la clase C_Terminal

Aunque el sistema que estamos programando con fines demostrativos no requiere ni necesita más añadidos al código principal, por razones prácticas y para empezar a probar la creación de objetos, que de hecho utilizaremos mucho durante el desarrollo del código, vamos a añadir algo de código genérico de creación de objetos a al gráfico del activo. De esta manera podemos empezar a probarlo y mejorarlo desde el principio. El código para hacer esto existe desde hace tiempo y, en cierto modo, ya se estaba considerando una ubicación más adecuada. Hasta que surja una ubicación más adecuada, la rutina de creación estará en la clase C_Terminal, como se muestra a continuación:

inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor, const int zOrder = -1)
   {
      ObjectCreate(m_Infos.ID, szName, obj, 0, 0, 0);
      ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor);
      ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false);
      ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder);
   }

Esta rutina será la rutina genérica de creación de los objetos que se mostrarán en el gráfico. En varias ocasiones verás que aparece en el código con sólo 2 elementos declarados. Esto se debe a que este elemento se está declarando con un valor por defecto, por lo que en realidad no necesita ser declarado durante la llamada, a menos que su valor resulte ser diferente por una razón u otra. Aparte de esto, siempre veremos que se llama a la rutina con dos elementos en su declaración. El detalle es que esta propiedad del objeto OBJPROP_ZORDER se utiliza para resolver un problema que vamos a ver que ocurre en algunos momentos. Sin esta propiedad correctamente configurada, tendremos serios problemas con los objetos colocados en el gráfico del activo cuando utilicemos el programa, ya sea utilizando la repetición/simulador, u operando en una cuenta demo o real. Ahora que hemos visto el cambio que ha sufrido el código principal, podemos empezar a entender cómo utilizar el sistema de una forma diferente a la que veremos cuando utilicemos sólo el código original. Pero para separar mejor las cosas, veámoslo por temas.


Primer camino: Utilizar la herencia

Para ir por el primer camino, utilizaremos la herencia, pero no heredando la clase C_Mouse, sino heredando la clase C_Study que vimos en el artículo anterior. Nuestro archivo de cabecera C_StudyS2.mqh tendrá el siguiente código:

//+------------------------------------------------------------------+
#include "C_StudyS1.mqh"
//+------------------------------------------------------------------+

// ... Definições locais ....

//+------------------------------------------------------------------+

// ... Alias Locais ...

//+------------------------------------------------------------------+
class C_StudyS2 : public C_StudyS1
{
   protected:
   private :

// ... Código e procedimentos internos ...

//+------------------------------------------------------------------+
   public  :
//+------------------------------------------------------------------+
      C_StudyS2(C_Mouse *arg, color corP, color corN)
         :C_StudyS1(arg, corP, corN)
      {                               
// ... Código interno ....

      }
//+------------------------------------------------------------------+
virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
      {
         double v1, v2;
         int w, h;
         string sz1;
                                
         C_StudyS1::DispatchMessage(id, lparam, dparam, sparam);

// ... Código interno ...

      }
//+------------------------------------------------------------------+
};

Aquí vemos que la clase del artículo anterior se está utilizando siguiendo los principios de la herencia, en los que heredamos la clase y añadimos conocimientos y propiedades. En muchos casos, será la mejor opción, pero en otros no. Es importante ser consciente de las partes que son diferentes y complementarias entre los caminos. Así, cuando utilices esta técnica, podrás sacarle el máximo partido. Presta atención a todos los puntos resaltados en el fragmento de código anterior. No te preocupes, te explicaré cómo trabajar con él para generar algo interesante.

Lógicamente, ya que estamos utilizando la herencia, el código del Expert Advisor (EA) para este caso, que no necesariamente tiene que ser un EA, sino que podría ser un indicador o script, será ligeramente diferente del caso en el que no utilizamos la herencia. Para entender estas diferencias, veamos primero el código del EA para este primer caso. Puedes ver el código completo a continuación:

#property copyright "Daniel Jose"
#property description "Generic EA for use on Demo account, replay system/simulator and Real account."
#property description "This system has means of sending orders using the mouse and keyboard combination."
#property description "For more information see the article about the system."
#property version   "1.30"
#property icon "../../Images/Icons/Replay - EA.ico"
#property link "https://www.mql5.com/en/articles/11372"
//+------------------------------------------------------------------+
#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_StudyS2.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_StudyS2 *extra = NULL;
//+------------------------------------------------------------------+
int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra = new C_StudyS2(mouse, user01, user02);
                
   MarketBookAdd((*mouse).GetInfoTerminal().szSymbol);
   OnBookEvent((*mouse).GetInfoTerminal().szSymbol);
   EventSetMillisecondTimer(500);

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   MarketBookRelease((*mouse).GetInfoTerminal().szSymbol);
   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();
}
//+------------------------------------------------------------------+

Nótese que las únicas diferencias con el código del artículo anterior son precisamente las partes resaltadas. Esto se debe a que utilizamos el sistema de herencia. Como sabes, el sistema de herencia funciona muy bien cuando quieres un sistema que crezca de forma cohesionada y sin demasiados imprevistos. Pero aquí podemos acabar teniendo otros problemas relacionados que nos entorpecen o, como mínimo, nos hacen la vida más difícil. A veces necesitamos utilizar un formulario ligeramente distinto, que es precisamente el que estará disponible en el anexo. Si quieres utilizar el sistema como un modelo basado en la herencia, está bien. Sólo tienes que acordarte de hacer los cambios que se han resaltado y todo irá sobre ruedas.


Segundo camino: Usar de punteros.

En esta segunda ruta, veremos la explicación detallada del código de la clase. Primero, veamos cómo es el código del EA. En este punto tendremos grandes diferencias con el código de la clase, que prácticamente sólo sufrirá los añadidos vistos en el tema anterior. Pues bien, el código del EA, para seguir este segundo camino, puede verse completo a continuación:

#property copyright "Daniel Jose"
#property description "Generic EA for use on Demo account, replay system/simulator and Real account."
#property description "This system has means of sending orders using the mouse and keyboard combination."
#property description "For more information see the article about the system."
#property version   "1.30"
#property icon "../../Images/Icons/Replay - EA.ico"
#property link "https://www.mql5.com/en/articles/11372"
//+------------------------------------------------------------------+
#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_StudyS1.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_StudyS2.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_StudyS1 *extra1 = NULL;
C_StudyS2 *extra2 = NULL;
//+------------------------------------------------------------------+
int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra1 = new C_StudyS1(mouse, user01, user02);
   extra2 = new C_StudyS2(mouse, user01, user02);
                
   MarketBookAdd((*mouse).GetInfoTerminal().szSymbol);
   OnBookEvent((*mouse).GetInfoTerminal().szSymbol);
   EventSetMillisecondTimer(500);

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   MarketBookRelease((*mouse).GetInfoTerminal().szSymbol);
   EventKillTimer();
        
   delete extra1;
   delete extra2;
   delete mouse;
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
   (*extra1).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);
   }
   (*extra1).Update(book);
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   (*mouse).DispatchMessage(id, lparam, dparam, sparam);
   (*extra1).DispatchMessage(id, lparam, dparam, sparam);
   (*extra2).DispatchMessage(id, lparam, dparam, sparam);
        
   ChartRedraw();
}
//+------------------------------------------------------------------+

Aquí muestro los puntos en los que estamos utilizando el sistema de clases principal, marcado en amarillo. El sistema de clases de expansión, que se vio en el artículo anterior, está marcado en verde. El sistema que producirá otro tipo de estudio, pero que también podría ser otra cosa, se muestra en naranja. Ten en cuenta que el hecho de que no estamos utilizando la herencia nos obliga a declarar más código en el EA. Pero, al mismo tiempo, nos permite liberar más código paralelo, para probar qué tipo de cosas tendremos en una versión finalizada. Lo bueno es que si este código paralelo empieza a mostrar algún defecto o fallo, podemos eliminarlo del código sin demasiada dificultad. Sin embargo, aquí hay otra cuestión: tanto el código en naranja como el código en verde pueden ser tratados de forma polimórfica. Esto nos permite probar más aspectos del sistema, que se está desarrollando en paralelo. Pero esta cuestión sobre el polimorfismo la dejaremos para otra ocasión. Porque si hablamos de ello ahora, complicaremos demasiado la explicación, de modo que los entusiastas que sigan la secuencia pueden no seguir realmente todo el razonamiento que implica el uso del polimorfismo.

Una vez hechas estas consideraciones, podemos pasar al código de la clase. Recuerda que tanto el código de la primera ruta como el de la segunda son prácticamente idénticos. A excepción, claro está, de los puntos indicados en el tema del primer camino.


Diseccionamos el código de la clase C_StudyS2

En cierto modo, cualquier código relacionado con el sistema de estudio será muy similar entre sí, con pequeñas excepciones. Aun así, hay varios puntos que hacen que el código de generación de estudios sea interesante de analizar y comprender. Pero veámoslo más claramente. Recuerda que el código que aparece aquí es sólo para fines de demostración, no es en absoluto un método finalizado. El archivo C_StudyS2.mqh comienza de la siguiente manera:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "..\C_Mouse.mqh"
#include "..\..\..\Service Graphics\Support\Interprocess.mqh"
//+------------------------------------------------------------------+
#define def_ExpansionPrefix "Expansion2_"
#define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
#define def_ExpansionFibo def_ExpansionPrefix + "FB"
//+------------------------------------------------------------------+
#define def_InfoTerminal (*mouse).GetInfoTerminal()
#define def_InfoMousePos (*mouse).GetInfoMouse().Position
//+------------------------------------------------------------------+
class C_StudyS2
{
   protected:
   private :
//+------------------------------------------------------------------+
      C_Mouse *mouse;
//+------------------------------------------------------------------+
      struct st00
      {
         bool            ExecStudy,
                         ClearStudy;
         double          MemPrice;
         datetime        MemDT;
         color           corP,
                         corN;
       }m_Info;
//+------------------------------------------------------------------+

Aquí tenemos la declaración de los archivos que se incluirán en el sistema. Ten en cuenta que las rutas son relativas a la ruta donde se encuentra este archivo C_StudyS2.mqh. Esto facilitará el traslado del proyecto a otros directorios, siempre que se mantenga la estructura. A continuación definimos algunos nombres para los objetos que utilizaremos durante el proceso de estudio. Luego están las declaraciones de alias, para facilitar el proceso de programación, ya que se utilizarán en muchos puntos durante la codificación. Y como último punto a ver en este fragmento, tenemos una estructura a la que se accederá a través de una variable global privada.

Lo siguiente que veremos en esta clase es el fragmento de abajo:

#define def_FontName "Lucida Console"
#define def_FontSize 10
       void GetDimensionText(const string szArg, int &w, int &h)
          {
             TextSetFont(def_FontName, -10 * def_FontSize, FW_NORMAL);
             TextGetSize(szArg, w, h);
             h += 5;
             w += 5;
          }
//+------------------------------------------------------------------+
       void CreateBTNInfo(int x, int w, int h, string szName, color backColor)
          {
             (*mouse).CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE);
             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, def_FontName);
             ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_FONTSIZE, def_FontSize);
             ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
             ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, x);
          }
#undef def_FontSize
#undef def_FontName

Aquí tenemos dos declaraciones que sólo serán localizadas y utilizadas en este lugar, por lo que definimos y eliminamos la definición tan pronto como sea innecesaria para el resto del código. En este punto, ya estamos llamando a la rutina que crea los objetos que se utilizarán en el gráfico de forma genérica. El objetivo aquí es crear el objeto y luego ajustar algunas de sus propiedades según sea necesario para que se trace como se espera. Sin embargo, si te fijas, estamos utilizando un botón como si fuera una ventana, donde tendremos texto de sólo lectura. Quizás sería más apropiado utilizar aquí un objeto OBJ_LABEL u OBJ_EDIT. Pero como sólo se trata de demostrar una manera de conseguir el resultado más adecuado. Así que podemos permitirnos utilizar otro objeto que coloque los datos en el gráfico por nosotros.

El gran punto fuerte de esta clase son los dos procedimientos que contiene. El primer procedimiento puede verse a continuación. La otra se verá hacia el final del artículo. Así que vamos a ver cómo esta clase crea el estudio que se presenta en el vídeo 01, que utiliza el objeto Fibonacci. El código para crear el objeto se puede ver a continuación:

void CreateStudy(void)
   {
      const double FiboLevels[] = {0, 0.236, 0.382, 0.50, 0.618, 1, 1.618, 2};
      ENUM_LINE_STYLE ls;
      color cor;
                                
      ObjectDelete(def_InfoTerminal.ID, def_ExpansionFibo);
      ObjectDelete(def_InfoTerminal.ID, "MOUSE_TB");
      ObjectDelete(def_InfoTerminal.ID, "MOUSE_TI");
      ObjectDelete(def_InfoTerminal.ID, "MOUSE_TT");
      (*mouse).CreateObjectGraphics(def_ExpansionFibo, OBJ_FIBO, clrNONE);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_HIDDEN, false);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_RAY_LEFT, false);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELS, ArraySize(FiboLevels));
      for (int c0 = 0, c1 = ArraySize(FiboLevels); c0 < c1; c0++)
      {
         ls = ((FiboLevels[c0] == 0) || (FiboLevels[c0] == 1) || (FiboLevels[c0] == 2)  ? STYLE_SOLID : STYLE_DASHDOT);
         ls = (FiboLevels[c0] == 0.5 ? STYLE_DOT : ls);
         switch (ls)
         {
            case STYLE_DOT    : cor = clrBlueViolet;  break;
            case STYLE_DASHDOT: cor = clrViolet;      break;
            default           : cor = clrIndigo;
         }
         ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELSTYLE, c0, ls);
         ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELCOLOR, c0, cor);                                  
         ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELWIDTH, c0, 1);
         ObjectSetString(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELTEXT, c0, (string)NormalizeDouble(FiboLevels[c0] * 100, 2));
      }
      ObjectSetDouble(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_PRICE, 1, m_Info.MemPrice = def_InfoMousePos.Price);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_TIME, 1, m_Info.MemDT = def_InfoMousePos.dt);
      CreateBTNInfo(def_InfoMousePos.X, 50, 18, def_ExpansionBtn1, clrNONE);
      m_Info.ExecStudy = true;
      m_Info.ClearStudy = false;
   }

Aunque este código pueda parecer complicado y poco claro a primera vista, en realidad consta de 3 partes. En cada una de ellas, hacemos algo específico para poder crear el estudio utilizando el objeto OBJ_FIBO.

  1. En la primera parte, eliminamos los objetos "indeseables" que creaba la clase C_Mouse cuando recibía un evento de la plataforma indicando que el usuario iniciaba un estudio sobre el gráfico del activo. Al eliminar estos objetos, hay que tener cuidado de no quitar nada que no sea realmente indispensable. De esta forma podemos configurar un estudio muy particular, prescindiendo de todo aquello que no sea necesario ver en el estudio que vamos a crear aquí. Observen también que hemos eliminado el objeto del antiguo estudio, esto es para permitirnos hacer un estudio basado en unos criterios específicos. La razón de esto también podría ser que desee utilizar una combinación de teclas para crear un estudio sobre una variación del objeto OBJ_FIBO. Estas variaciones pueden ser OBJ_FIBOTIMES, OBJ_FIBOFAN, OBJ_FIBOARC, OBJ_FIBOCHANNEL y OBJ_EXPANSION. Todos ellas siguen los mismos principios que se muestran aquí.
  2. En la segunda parte creamos y definimos las propiedades del objeto. Aquí hay algunos puntos interesantes: es en este momento en el que le decimos a la plataforma que el objeto será visible en la lista de objetos. Aquí, le indicamos qué niveles contendrá el objeto. Es cierto que aquí he utilizado niveles estáticos, pero nada te impide utilizar niveles dinámicos en tu propio sistema o utilizar niveles diferentes. En esta zona te contamos cómo será cada uno de los niveles, tanto en color como en el tipo de línea que se utilizará para construirlo. Puedes modificarlo a tu antojo, para tener un nivel de visualización adecuado, ya que cuando hacemos un estudio lo que realmente queremos es que se entienda rápidamente, para poder beneficiarnos de él.
  3. Y en la tercera y última parte, empezamos a construir el objeto directamente sobre el gráfico, es decir, aquí se empezará a trazar. También le decimos lo que pasa mediante variables. Esto es para que, durante el procedimiento que veremos más adelante, podamos hacer las cosas del modo correcto.

Básicamente, así es como vamos a crear un estudio basado en un sistema ya producido y probado, que está en la clase C_Mouse. Es decir, no vamos a construir algo desde cero, sino que vamos a reutilizar y adaptar lo que ya tenemos para conseguir algo diferente. Sin embargo, todo el tema se entenderá mejor cuando estudiemos realmente el segundo procedimiento. Pero sigamos, ahora viendo el constructor y el destructor de la clase. Puedes verlos en el siguiente fragmento:

C_StudyS2(C_Mouse *arg, color corP, color corN)
   {                               
      mouse = arg;
      ZeroMemory(m_Info);
      m_Info.corP = corP;
      m_Info.corN = corN;
   }
//+------------------------------------------------------------------+
~C_StudyS2()
   {
      ObjectsDeleteAll(def_InfoTerminal.ID, def_ExpansionPrefix);
   }

Fíjate bien en estos dos procedimientos. Su objetivo es utilizar el sistema basado en la segunda ruta. Para utilizar el modelo de herencia, debes añadir las líneas que se están utilizando en el tema sobre el primer camino. Lo mismo debe hacerse en el último procedimiento visto en clase. Esto es precisamente lo que permite interactuar con la plataforma. Echa un vistazo al código completo de este procedimiento, que de hecho permite crear el estudio en el fragmento siguiente:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {
      double v1, v2;
      int w, h;
      string sz1;
                                
      switch (id)
      {
         case CHARTEVENT_KEYDOWN:
            if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE) && (m_Info.ExecStudy)) m_Info.ClearStudy = true;
            break;
         case CHARTEVENT_MOUSE_MOVE:
            if (mouse.GetInfoMouse().ExecStudy)
            {
               if (!m_Info.ExecStudy) CreateStudy();
               v1 = def_InfoMousePos.Price - m_Info.MemPrice;
               v2 = MathAbs(100.0 - ((m_Info.MemPrice / def_InfoMousePos.Price) * 100.0));
               sz1 = StringFormat(" %." + (string)def_InfoTerminal.nDigits + "f [ %d ] %02.02f%% ", MathAbs(v1), Bars(def_InfoTerminal.szSymbol, PERIOD_CURRENT, m_Info.MemDT, def_InfoMousePos.dt) - 1, v2);
               GetDimensionText(sz1, w, h);
               ObjectSetDouble(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_PRICE, 0, def_InfoMousePos.Price);
               ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_TIME, 0, def_InfoMousePos.dt);
               ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_COLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
               ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_TEXT, sz1);                                                                                                                             
               ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
               ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_XSIZE, w);
               ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_YSIZE, h);
               ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_XDISTANCE, def_InfoMousePos.X - w);
               ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, def_InfoMousePos.Y - (v1 < 0 ? 1 : h));
            }else if (m_Info.ExecStudy)
            {
               ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_COLOR, clrNONE);
               ObjectDelete(def_InfoTerminal.ID, def_ExpansionBtn1);
               if (m_Info.ClearStudy) ObjectDelete(def_InfoTerminal.ID, def_ExpansionFibo);
               m_Info.ExecStudy = false;
            }
            break;
         }
      }

Es un código curioso, ¿verdad? Fíjate que en ningún momento estamos mirando lo que hace el ratón. Estamos viendo lo que se hace en la clase C_Mouse. Esto se hace cuando revisamos estas pruebas desde aquí. Mientras la clase C_Mouse esté indicando que estamos haciendo algún tipo de estudio, esta clase seguirá esta orientación, realizando así el estudio siguiendo lo que la clase C_Mouse esté ordenando. En cuanto la clase C_Mouse deje de utilizarse en un estudio, eliminaremos el objeto utilizado para colocar el texto informativo. Pero si se pulsa la tecla ESC durante el estudio, también se eliminará el objeto de estudio. Una cosa que notarás es que el objeto usado para darnos el texto tiene sus dimensiones calculadas dinámicamente, es decir, puede ser más grande o más pequeño dependiendo del caso, todo lo cual se controla en esta región de aquí, así como la cuestión de los colores y la colocación de los objetos.

Hay una parte interesante de este código, que merece una mejor explicación. Así que vamos a eliminarla y a enfatizarla en el fragmento siguiente. Así puedo explicar con detalle lo que presentamos y por qué los valores se ven de esa manera concreta.

v1 = def_InfoMousePos.Price - m_Info.MemPrice;
v2 = MathAbs(100.0 - ((m_Info.MemPrice / def_InfoMousePos.Price) * 100.0));
sz1 = StringFormat(" %." + (string)def_InfoTerminal.nDigits + "f [ %d ] %02.02f%% ", MathAbs(v1), Bars(def_InfoTerminal.szSymbol, PERIOD_CURRENT, m_Info.MemDT, def_InfoMousePos.dt) - 1, v2);

Para entender estas 3 líneas de factorización y formatación de la información que se presentará, es necesario ser capaz de ver que el sistema se está adaptando dinámicamente al activo en el que se ejecuta el código. Algunos activos pueden necesitar 4 dígitos para representar sus valores, mientras que otros necesitan 5 dígitos. En varios activos bursátiles, tendremos activos que necesitan 2 dígitos. Para que el sistema se adapte de forma sencilla, utilizamos este tipo de modelado visto anteriormente. Suena confuso, pero en realidad es inusual.

Para empezar, factorizamos la diferencia entre el precio al que comenzó el estudio y el punto en el que se encuentra la línea de precios. De esta forma tendremos un valor, ya sea en puntos, pips o valor financiero, del desplazamiento entre la posición con la que iniciamos el estudio y la posición actual donde se encuentra el ratón. Para presentarlo correctamente, necesitamos saber cuántos dígitos se necesitan. Para ello, utilizamos la siguiente técnica: Sabemos que utilizando el carácter de porcentaje ( % ) podemos definir el tipo de información que se convertirá en una string. Pues bien, con el siguiente formato < %.2f> tendremos un valor que contiene dos decimales, con <%.4f> tendremos un valor que contiene 4 decimales y así sucesivamente. Pero necesitamos que esto se defina en RUN-TIME

De esta forma, la propia función StringFormat creará el formato adecuado. Sé que parece confuso hacer esto, pero en cuanto se coloque el valor que hemos calculado con la diferencia, se colocará siguiendo exactamente el formato que hemos creado. Esto nos dará un número de decimales adecuado al valor a mostrar. Para entender realmente cómo funciona esto en la práctica, tendrás que utilizar este mismo código en activos con un número diferente de dígitos, para que quede más claro. Otra cuestión, que puedes ver aquí y que en realidad es bastante interesante para aquellos que quieren operar durante ciertos tipos de movimiento, es conocer de forma sencilla el número de barras desde un punto dado.

Algunas plataformas tienen un indicador que cuenta las barras y lo hace visible en el gráfico. Tú también puedes crear un indicador de este tipo fácilmente. Pero esto pondrá cada vez más información en el gráfico, dificultando a menudo su lectura, porque acaba estando extremadamente contaminado con una enorme cantidad de información, mucha de la cual a veces no es necesaria la mayor parte del tiempo. Pero utilizando el propio lenguaje MQL5, de una forma un poco más exótica, podemos contar cuántas barras hay en la zona donde se está ejecutando el estudio y mostrar este valor en tiempo real directamente en el gráfico. Una vez terminado el estudio, el gráfico continuará sólo con la información que necesitamos.

Para poder hacer este tipo de análisis, utilizamos esta función con estos parámetros. Pero cuidado, si el estudio se realiza en una región en la que no hay barras, el valor indicado será -1 y si el estudio se realiza en una sola barra, el valor será cero. Si deseas cambiar esto, ya que el número de barras se referirá al número presente en la región de estudio, basta con eliminar este valor de -1 de la factorización. De ese modo, el valor siempre será el número real de barras, incluida aquella en la que comenzó el estudio. Puede ser un detalle para algunos, pero para otros puede ser realmente interesante saber por qué a veces se obtiene el valor -1 en el estudio.

Como también queremos informar un porcentaje de desviación, utilizamos este cálculo para generar el porcentaje. Para facilitar la comprensión de la visualización, utilizamos este formato, de modo que podemos imprimir el símbolo del porcentaje junto con el resto de la información.


Conclusión

Aquí muestro una técnica que puede ayudarte mucho en varios momentos de tu vida como programador. He demostrado que, contrariamente a lo que muchos dicen, no es la plataforma lo que está limitado, sino el conocimiento del individuo que dice que la plataforma o el lenguaje no nos permiten crear cosas. Lo que se ha explicado aquí prueba 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. Y 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. Así que si en algún momento ese código que creaste y usaste durante un tiempo realmente te resultó útil, puedes añadirlo permanentemente y con muy poco trabajo al código que tienes como seguro. Para ello se utiliza el concepto de clases. Donde simplemente se crea una jerarquía de herencia de código.

No hay trabajo que no se pueda hacer. Hay trabajo que algunas personas no pueden hacer. Pero esto no significa que la tarea no pueda llevarse a cabo.

En el apéndice encontrarás el código completo de lo que se ha mostrado en estos artículos. En el próximo artículo seguiremos desarrollando el sistema. Pero sin el código de estudio. Tal vez con parte de ella integrada en la clase C_Mouse. Y si esto ocurre, no entraré en detalles. Porque todo el código se ha explicado en los últimos artículos. Hasta entonces.


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

Archivos adjuntos |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Desarrollo de un sistema de repetición (Parte 31): Proyecto Expert Advisor — Clase C_Mouse (V) Desarrollo de un sistema de repetición (Parte 31): Proyecto Expert Advisor — Clase C_Mouse (V)
Desarrollar una manera de poner un cronómetro, de modo que durante una repetición/simulación, éste pueda decirnos cuánto tiempo falta, puede parecer a primera vista una tarea simple y de rápida solución. Muchos simplemente intentarían adaptar y usar el mismo sistema que se utiliza cuando tenemos el servidor comercial a nuestro lado. Pero aquí reside un punto que muchos quizás no consideran al pensar en tal solución. Cuando estás haciendo una repetición, y esto para no hablar del hecho de la simulación, el reloj no funciona de la misma manera. Este tipo de cosa hace complejo construir tal sistema.
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.
Desarrollo de un sistema de repetición (Parte 32): Sistema de órdenes (I) Desarrollo de un sistema de repetición (Parte 32): Sistema de órdenes (I)
De todas las cosas desarrolladas hasta ahora, esta, como seguramente también notarás y con el tiempo estarás de acuerdo, es la más desafiante de todas. Lo que tenemos que hacer es algo simple: hacer que nuestro sistema simule lo que hace un servidor comercial en la práctica. Esto de tener que implementar una forma de simular exactamente lo que haría el servidor comercial parece simple. Al menos en palabras. Pero necesitamos hacer esto de manera que, para el usuario del sistema de repetición/simulación, todo suceda de la manera más invisible o transparente posible.
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)
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.