Português
preview
Desarrollo de un sistema de repetición (Parte 44): Proyecto Chart Trade (III)

Desarrollo de un sistema de repetición (Parte 44): Proyecto Chart Trade (III)

MetaTrader 5Ejemplos | 16 julio 2024, 16:10
20 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior Desarrollo de un sistema de repetición (Parte 43): Proyecto Chart Trade (II), expliqué cómo puedes manipular los datos de la plantilla para usarlos en un OBJ_CHART. Allí solo introduje el tema sin entrar en muchos detalles, ya que en esa versión el trabajo se hizo de una manera muy simplificada. Sin embargo, se hizo de esa forma precisamente para facilitar la explicación del contenido. Pues, a pesar de parecer simple hacer ciertas cosas, algunas no son tan evidentes, y sin comprender la parte más simple y básica, no entenderás realmente lo que estoy haciendo.

Entonces, a pesar de que ese código funcione, como se pudo ver, no nos permite hacer algunas cosas. Mejor dicho, hacer algunas cosas será mucho más complicado si no se realiza algún tipo de mejora en el modelado de los datos. Dicha mejora pasa precisamente por una codificación un poco más elaborada. Pero el concepto utilizado será el mismo. Solo que el código será un poco más complejo.

Además de este pequeño hecho, vamos a resolver otra cuestión. Si te diste cuenta, y además mencioné en el propio artículo, ese código no es muy eficiente, ya que tenemos, a mi parecer, un exceso de llamadas para ajustar las cosas. Entonces, para resolver esto, haremos algunos pequeños cambios en el código. Estos cambios resultarán en una drástica reducción de las llamadas, al mismo tiempo que promoverán un modelado más adecuado de los datos.


Nace una nueva clase C_AdjustTemplate

Para promover las mejoras que necesitamos, tendremos que crear una nueva clase. Su código puede verse íntegramente a continuación:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "../Auxiliar/C_Terminal.mqh"
05. //+------------------------------------------------------------------+
06. class C_AdjustTemplate
07. {
08.     private :
09.             string m_szName[];
10.             string m_szFind[];
11.             string m_szReplace[];
12.             string m_szFileName;
13.             int m_maxIndex;
14.             int m_FileIn;
15.             int m_FileOut;
16.             bool m_bSimple;
17.     public  :
18. //+------------------------------------------------------------------+
19.             C_AdjustTemplate(const string szFileNameIn, string szFileNameOut = NULL)
20.                     :m_maxIndex(0),
21.                      m_szFileName(szFileNameIn),
22.                      m_FileIn(INVALID_HANDLE),
23.                      m_FileOut(INVALID_HANDLE)
24.                     {
25.                             ResetLastError();
26.                             if ((m_FileIn = FileOpen(szFileNameIn, FILE_TXT | FILE_READ)) == INVALID_HANDLESetUserError(C_Terminal::ERR_FileAcess);
27.                             if (_LastError == ERR_SUCCESS)
28.                             {
29.                                     if (!(m_bSimple = (StringLen(szFileNameOut) > 0))) szFileNameOut = szFileNameIn + "_T";                                                 
30.                                     if ((m_FileOut = FileOpen(szFileNameOut, FILE_TXT | FILE_WRITE)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess);
31.                             }
32.                     }
33. //+------------------------------------------------------------------+
34.             ~C_AdjustTemplate()
35.                     {
36.                             FileClose(m_FileIn);
37.                             if (m_FileOut != INVALID_HANDLE)
38.                             {
39.                                     FileClose(m_FileOut);
40.                                     if ((!m_bSimple) && (_LastError == ERR_SUCCESS)) FileMove(m_szFileName + "_T", 0, m_szFileName, FILE_REWRITE);
41.                                     if ((!m_bSimple) && (_LastError != ERR_SUCCESS)) FileDelete(m_szFileName + "_T");
42.                             }
43.                             ArrayResize(m_szName, 0);
44.                             ArrayResize(m_szFind, 0);
45.                             ArrayResize(m_szReplace, 0);
46.                     }
47. //+------------------------------------------------------------------+
48.             void Add(const string szName, const string szFind, const string szReplace)
49.                     {
50.                             m_maxIndex++;
51.                             ArrayResize(m_szName, m_maxIndex);
52.                             ArrayResize(m_szFind, m_maxIndex);
53.                             ArrayResize(m_szReplace, m_maxIndex);
54.                             m_szName[m_maxIndex - 1] = szName;
55.                             m_szFind[m_maxIndex - 1] = szFind;
56.                             m_szReplace[m_maxIndex - 1] = szReplace;
57.                     }
58. //+------------------------------------------------------------------+
59.             string Get(const string szName, const string szFind)
60.                     {
61.                             for (int c0 = 0; c0 < m_maxIndex; c0++) if ((m_szName[c0] == szName) && (m_szFind[c0] == szFind)) return m_szReplace[c0];
62.                             
63.                             return NULL;
64.                     }
65. //+------------------------------------------------------------------+
66.             void Execute(void)
67.                     {
68.                             string sz0, tmp, res[];
69.                             int count0 = 0, i0;
70.                             
71.                             if ((m_FileIn != INVALID_HANDLE) && (m_FileOut != INVALID_HANDLE)) while ((!FileIsEnding(m_FileIn)) && (_LastError == ERR_SUCCESS))
72.                             {
73.                                     sz0 = FileReadString(m_FileIn);
74.                                     if (sz0 == "<object>") count0 = 1;
75.                                     if (sz0 == "</object>") count0 = 0;
76.                                     if (count0 > 0) if (StringSplit(sz0, '=', res) > 1)
77.                                     {
78.                                             i0 = (count0 == 1 ? 0 : i0);
79.                                             for (int c0 = 0; (c0 < m_maxIndex) && (count0 == 1); i0 = c0, c0++) count0 = (res[1] == (tmp = m_szName[c0]) ? 2 : count0);
80.                                             for (int c0 = i0; (c0 < m_maxIndex) && (count0 == 2); c0++) if ((res[0] == m_szFind[c0]) && (tmp == m_szName[c0]))
81.                                             {
82.                                                     if (StringLen(m_szReplace[c0])) sz0 =  m_szFind[c0] + "=" + m_szReplace[c0];
83.                                                     else m_szReplace[c0] = res[1];
84.                                             }
85.                                     }
86.                                     FileWriteString(m_FileOut, sz0 + "\r\n");
87.                             };
88.                     }
89. //+------------------------------------------------------------------+
90. };
91. //+------------------------------------------------------------------+

Código fuente: C_AdjustTemplate

Este código contiene exactamente lo que necesitamos. Si observas este código aquí, y miras el código de la clase C_ChartFloatingRAD del artículo anterior, notarás que el contenido presente entre las líneas 38 y 90 de la clase C_ChartFloatingRAD está aquí, pero no con la misma apariencia. Esto se debe al hecho de que el modelado de los datos en esta clase C_AdjustTemplate se hace de manera que promueva una ejecución más eficiente. Notarás esto cuando más adelante en este artículo se muestre el nuevo código de la clase C_ChartFloatingRAD. Pero eso quedará para más adelante. Primero, vamos a entender qué está pasando en esta clase C_AdjustTemplate.

Aunque parezca complejo y difícil de comprender, este código de la clase C_AdjustTemplate es bastante simple. No obstante, está diseñado para ser ejecutado como si fuera una única función, a pesar de contar con más funciones. Para que comprendas realmente la idea, olvida que estás trabajando con código, con MetaTrader, o con MQL5. Piensa como si estuvieras trabajando con partes de una máquina, así será más sencillo comprender las cosas. La clase C_AdjustTemplate debe ser vista como si fuera un template. Sí, exactamente. Piensa en ella como si fuera un archivo template. Ese mismo archivo mencionado en el artículo anterior.

Si piensas así, comprenderás lo que realmente está sucediendo y por qué debemos trabajar con esta clase de la manera que lo haremos más adelante. Entonces, cuando usas el constructor de la clase, en realidad estás abriendo el template para manipular lo que está dentro de él. Cuando usas el destructor, estás diciendo: Listo, ahora MetaTrader, puedes usar el template. Las demás funciones sirven como herramientas para ajustar el template.

Así que, basándonos en esto, vamos a entender cómo funciona cada parte de esta clase. Comenzaremos con el constructor. Este comienza en la línea 19, donde podemos ver que, obligatoriamente, tendremos que proporcionar una cadena de texto, pero opcionalmente podremos proporcionar una segunda cadena de texto. ¿Por qué hacerlo así? El motivo es simple: Sobrecarga. Si la sobrecarga no fuera posible, tendríamos que codificar dos constructores, pero dado que es posible, hacemos uso de ella. Sin embargo, esta sobrecarga no es de hecho la convencional. Está diseñada para hacerse de esta manera.

Hecho esto, entre las líneas 20 y 23, inicializamos anticipadamente algunas variables. Esto es importante para nosotros, a pesar de que el compilador realiza una inicialización implícita, siempre es mejor hacerla de forma explícita. Así sabremos exactamente cuál es el valor de cada variable.

Ahora, atención al siguiente hecho: En la línea 25, estamos "reiniciando" la constante _LastError. Entonces, si existe algún fallo antes de que el constructor sea llamado, deberás verificarlo; de lo contrario, perderás el valor informado en la constante de error. Ya he explicado en artículos pasados sobre por qué hacer las cosas así. Léelos para más detalles.

En la línea 26, intentamos abrir el archivo de origen, que debe ser indicado obligatoriamente. Si esta tentativa falla, informaremos esto en la constante _LastError. Si tenemos éxito al abrir el archivo, la constante _LastError contendrá el valor ERR_SUCCESS, y con esto, la prueba realizada en la línea 27 pasará, permitiéndonos ejecutar la siguiente etapa.

En esta etapa, verificaremos en la línea 29 si se está proporcionando un nombre para el archivo de destino. Si este nombre no ha sido informado, entonces trabajaremos con un archivo temporal. Este tendrá el nombre basado en el nombre del archivo de origen. Teniendo un nombre para el archivo de destino, podemos entonces ejecutar la línea 30, que intentará crear el archivo de destino. Si esta tentativa falla, informaremos esto en la constante _LastError. Si todo ocurre bien, la constante _LastError contendrá el valor ERR_SUCCESS, y tendremos los identificadores de los archivos. Estos serán usados para manipular los archivos, por lo tanto, no debemos intentar hacer nada externamente en los archivos hasta que los identificadores (handles) sean cerrados. Piensa en lo siguiente: La máquina está abierta; si la enciendes, puede dar problemas o el famoso B.O.

Muy bien, sigamos el código en su orden de edición. De esta forma, llegamos a la línea 34, donde comienza el código del destructor de la clase. Lo primero que hacemos, en la línea 36, es cerrar el archivo de entrada. Un detalle: este archivo solo será cerrado si su handle es válido. Es decir, el archivo deberá estar abierto. En la línea 37, verificamos si el archivo de salida está abierto. El motivo de hacer esto es precisamente para evitar ejecutar innecesariamente las siguientes líneas.

Entonces, si el archivo de destino está abierto, verificamos en la línea 40 si se ha proporcionado un nombre para él y si no ocurrió ningún error durante el proceso de ajuste. Si todo está bien, renombraremos el archivo para que represente el archivo esperado por el resto del indicador. De cualquier manera, en la línea 41, eliminaremos el archivo temporal utilizado si algo salió mal.

Entre las líneas 43 y 45, liberamos la memoria asignada. Este tipo de cosa es bastante importante, muchos se olvidan de hacerlo. Pero, como una buena práctica de programación, si asignas memoria, siempre debes liberarla. Así, MetaTrader 5 no consumirá recursos de forma excesiva e innecesaria.

A continuación, tenemos un procedimiento muy básico y simple, que comienza en la línea 50, donde incrementamos el contador para, inmediatamente después, asignar memoria para almacenar los datos que usaremos después. Esta asignación se realiza entre las líneas 51 y 53. Pero observen en las líneas 54 a 56 la forma en que estaremos almacenando las informaciones. Por ser un procedimiento simple, no entraremos en más detalles. Pero en la línea 59, tenemos una función curiosa.

Esta función, que comienza en la línea 59, aunque muy simple, es bastante curiosa. No por lo que hace, sino por cómo lo hace. Observen en la línea 60, que de hecho es la única línea de esta función. Aquí tenemos un bucle donde recorreremos todas las posiciones que fueron añadidas durante el procedimiento presente en la línea 50. La pregunta es: ¿Por qué, tú como programador, querrías leer una información que grabaste en la clase, usando para esto el procedimiento de la línea 50? Esto parece no tener sentido. De hecho, si solo miras el código de la clase, no tiene sentido. Pero, sin embargo, existe un pequeño detalle en esta historia, y este detalle comienza en la línea 66.

Este procedimiento Execute, que comienza en la línea 66, es un procedimiento extremadamente delicado. El motivo de esto es que pueden ocurrir diversas cosas mal. Básicamente, podemos tener los siguientes errores:

  • El archivo de entrada no se puede leer;
  • El archivo de salida puede no estar accesible;
  • La función StringSplit puede fallar.

Cualquiera de estas cosas hará que la constante de error cambie. Si esto ocurre, el bucle while, que está en la línea 71, finalizará anticipadamente, haciendo que todo el indicador falle al ser colocado en el gráfico. Recuerda el siguiente hecho: Si la constante _LastError contiene un valor diferente de ERR_SUCCESS, en el momento en que se ejecute el destructor, el template no será actualizado. Y si es la primera llamada, no será creado. Si no existe, el indicador no será colocado en el gráfico. Por esto, este procedimiento Execute es tan delicado.

Sin embargo, pensemos que todo esté funcionando perfectamente. Entonces veamos qué ocurre dentro del bucle while.

En la línea 73, leemos una cadena del archivo de origen. Esta cadena estará compuesta por una línea completa. Leyendo de esta forma, será mucho más simple probar las demás cosas. Entonces, en la línea 74, probamos si estamos entrando en una definición de algún objeto. Ya en la línea 75, probamos si la definición del objeto ha terminado.

Estas pruebas son importantes para agilizar el proceso de lectura y ajuste del template. Ya que, si no estamos tratando con algún objeto, podemos simplemente grabar la información en el archivo de destino. Esta grabación se hace en la línea 86. Presta atención a esto. Porque ahora veremos cómo el template de origen será manipulado y ajustado para generar lo que deseamos.

Cuando estamos dentro de un objeto, tenemos la prueba de la línea 76, permitiendo que hagamos una "división" de la cadena. Esta "división" se hará justo en el signo de igualdad (=), lo que representa que estamos definiendo algún parámetro para alguna propiedad del objeto. Bien, si esto es verdadero, que estamos definiendo una propiedad, estas pruebas pasarán, permitiéndonos ejecutar la línea 78. Esta simplemente ajustará la memoria temporal. Pero la cuestión en sí se da en las próximas líneas.

En la línea 79, recorreremos todos los datos que fueron añadidos durante la ejecución de las llamadas Add (procedimiento de la línea 48). Si por algún acaso, encontramos el valor del parámetro en el template, esto indica que estamos tratando con el objeto que buscamos. Con esto, almacenamos temporalmente el nombre del objeto e indicamos que haremos un segundo nivel de análisis. Debido a este hecho, de que estamos haciendo un segundo nivel, esta línea 79 no será ejecutada nuevamente dentro del mismo objeto. Esto nos obliga a garantizar que el template debe tener la misma estructura que se ve en el artículo anterior, Desarrollo de un sistema de repetición (Parte 43): Proyecto Chart Trade (II). Es decir, el archivo tiene que ser exactamente ese. Si lo cambias, asegúrate de que permanezca lo más parecido a ese.

Pues bien, ya que estamos en el segundo nivel, en la línea 80, tendremos otro bucle. Importante: El bucle de las líneas 79 y 80 nunca se ejecutan juntos. Siempre se ejecutará uno u otro, nunca ambos. Excepto en la primera llamada. Esto de que ambas líneas 79 y 80 no se ejecuten parece extraño, pero de hecho es lo que ocurre. Pero si la línea 80 llega a ejecutarse, dentro del bucle tendremos una prueba para verificar si encontramos la propiedad deseada del objeto. Observa que el nombre del objeto es importante, por lo tanto, lo grabamos temporalmente durante el bucle de la línea 79.

Si esta prueba indica que se encontró la propiedad del objeto, haremos una segunda prueba, esta en la línea 82. En este punto, tendremos la justificación de la presencia de la función encontrada en la línea 59. Si durante la programación, que veremos más adelante, tú le dices a la clase C_AdjustTemplate que no sabes cuál es el parámetro a usar en la propiedad del objeto, esta prueba en la línea 82 hará que se ejecute la línea 83, capturando así el valor presente en la propiedad del objeto. Si indicas el valor que deseas utilizar, este se grabará en el template.

Este tipo de funcionalidad es lo que hace a la clase C_AdjustTemplate tan interesante. Ya que puedes pedirle que te diga cuál es el valor de la propiedad, o informar cuál deberá ser el valor a usar.

De esta manera, terminamos la explicación de la clase C_AdjustTemplate. Ahora veamos cómo quedó la clase C_ChartFloatingRAD. Pues como puedes imaginar, la misma fue modificada y se volvió aún más interesante.


Una nueva cara para la clase C_ChartFloatingRAD

Aunque no mostraré el código final de esta clase en este artículo, y el motivo es que quiero explicar cada detalle con calma. Notarás que ahora estará mucho más compleja que el código visto en el artículo anterior. Sin embargo, gran parte del código se mantuvo. De esta manera, es recomendable que sigas la secuencia de artículos, de lo contrario, perderás algunos detalles que pueden hacer la diferencia en la comprensión general.

A continuación, puedes ver el código completo de la clase C_ChartFloatingRAD, hasta el momento actual.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "../Auxiliar/C_Mouse.mqh"
005. #include "C_AdjustTemplate.mqh"
006. //+------------------------------------------------------------------+
007. class C_ChartFloatingRAD : private C_Terminal
008. {
009.    private :
010.            enum eObjectsIDE {MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL};
011.            struct st00
012.            {
013.                    int     x, y;
014.                    string  szObj_Chart,
015.                            szFileNameTemplate;
016.                    long    WinHandle;
017.                    double  FinanceTake,
018.                            FinanceStop;
019.                    int     Leverage;
020.                    bool    IsDayTrade,
021.                            IsMaximized;
022.                    struct st01
023.                    {
024.                            int x, y, w, h;
025.                    }Regions[MSG_NULL];
026.            }m_Info;
027. //+------------------------------------------------------------------+
028.            C_Mouse  *m_Mouse;
029. //+------------------------------------------------------------------+
030.            void CreateWindowRAD(int x, int y, int w, int h)
031.                    {
032.                            m_Info.szObj_Chart = (string)ObjectsTotal(GetInfoTerminal().ID);
033.                            ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0);
034.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = x);
035.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = y);
036.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w);
037.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h);
038.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false);
039.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false);
040.                            m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID);
041.                    };
042. //+------------------------------------------------------------------+
043. inline void UpdateChartTemplate(void)
044.                    {
045.                            ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate);
046.                            ChartRedraw(m_Info.WinHandle);
047.                    }
048. //+------------------------------------------------------------------+
049. inline double PointsToFinance(const double Points)
050.                    {                               
051.                            return Points * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade;
052.                    };
053. //+------------------------------------------------------------------+
054. inline void AdjustTemplate(const bool bFirst = false)
055.                    {
056. #define macro_AddAdjust(A) {                         \
057.              (*Template).Add(A, "size_x", NULL);     \
058.              (*Template).Add(A, "size_y", NULL);     \
059.              (*Template).Add(A, "pos_x", NULL);      \
060.              (*Template).Add(A, "pos_y", NULL);      \
061.                            }
062. #define macro_GetAdjust(A) {                                                                          \
063.              m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x"));  \
064.              m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y"));  \
065.              m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \
066.              m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \                                               
067.                            }
068.                            
069.                            C_AdjustTemplate *Template;
070.                            
071.                            if (bFirst)
072.                            {
073.                                    Template = new C_AdjustTemplate("Chart Trade/IDE_RAD.tpl", m_Info.szFileNameTemplate = StringFormat("Chart Trade/%u.tpl", GetInfoTerminal().ID));
074.                                    for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0));
075.                            }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate);
076.                            Template.Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol);
077.                            Template.Add("MSG_LEVERAGE_VALUE", "descr", (string)m_Info.Leverage);
078.                            Template.Add("MSG_TAKE_VALUE", "descr", (string)m_Info.FinanceTake);
079.                            Template.Add("MSG_STOP_VALUE", "descr", (string)m_Info.FinanceStop);
080.                            Template.Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0"));
081.                            Template.Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0"));
082.                            Template.Execute();
083.                            if (bFirst) for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0);
084.                            
085.                            delete Template;
086.                            
087.                            UpdateChartTemplate();
088.                                                            
089. #undef macro_AddAdjust
090. #undef macro_GetAdjust
091.                    }
092. //+------------------------------------------------------------------+
093.            eObjectsIDE CheckMousePosition(const int x, const int y)
094.                    {
095.                            int xi, yi, xf, yf;
096.                            
097.                            for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++)
098.                            {
099.                                    xi = m_Info.x + m_Info.Regions[c0].x;
100.                                    yi = m_Info.y + m_Info.Regions[c0].y;
101.                                    xf = xi + m_Info.Regions[c0].w;
102.                                    yf = yi + m_Info.Regions[c0].h;
103.                                    if ((x > xi) && (y > yi) && (x < xf) && (y < yf) && ((c0 == MSG_MAX_MIN) || (c0 == MSG_TITLE_IDE) || (m_Info.IsMaximized))) return c0;
104.                            }
105.                            return MSG_NULL;
106.                    }
107. //+------------------------------------------------------------------+
108.    public  :
109. //+------------------------------------------------------------------+
110.            C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const int Leverage, const double FinanceTake, const double FinanceStop)
111.                    :C_Terminal()
112.                    {
113.                            m_Mouse = MousePtr;
114.                            m_Info.Leverage = (Leverage < 0 ? 1 : Leverage);
115.                            m_Info.FinanceTake = PointsToFinance(FinanceToPoints(MathAbs(FinanceTake), m_Info.Leverage));
116.                            m_Info.FinanceStop = PointsToFinance(FinanceToPoints(MathAbs(FinanceStop), m_Info.Leverage));
117.                            m_Info.IsDayTrade = true;
118.                            m_Info.IsMaximized = true;
119.                            if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown);
120.                            CreateWindowRAD(115, 64, 170, 210);
121.                            AdjustTemplate(true);
122.                    }
123. //+------------------------------------------------------------------+
124.            ~C_ChartFloatingRAD()
125.                    {
126.                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Chart);
127.                            FileDelete(m_Info.szFileNameTemplate);
128.                            
129.                            delete m_Mouse;
130.                    }
131. //+------------------------------------------------------------------+
132.            void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
133.                    {
134.                            static int sx = -1, sy = -1;
135.                            int x, y, mx, my;
136.                            static eObjectsIDE it = MSG_NULL;
137.    
138.                            switch (id)
139.                            {
140.                                    case CHARTEVENT_MOUSE_MOVE:
141.                                            if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft))
142.                                            {
143.                                                    switch (it = CheckMousePosition(x = (int)lparam, y = (int)dparam))
144.                                                    {
145.                                                            case MSG_TITLE_IDE:
146.                                                                    if (sx < 0)
147.                                                                    {
148.                                                                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
149.                                                                            sx = x - m_Info.x;
150.                                                                            sy = y - m_Info.y;
151.                                                                    }
152.                                                                    if ((mx = x - sx) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = mx);
153.                                                                    if ((my = y - sy) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = my);
154.                                                                    break;
155.                                                    }
156.                                            }else
157.                                            {
158.                                                    if (it != MSG_NULL)
159.                                                    {
160.                                                            switch (it)
161.                                                            {
162.                                                                    case MSG_MAX_MIN:
163.                                                                            m_Info.IsMaximized = (m_Info.IsMaximized ? false : true);
164.                                                                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6));
165.                                                                            break;
166.                                                                    case MSG_DAY_TRADE:
167.                                                                            m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true);
168.                                                                            break;
169.                                                            }
170.                                                            it = MSG_NULL;
171.                                                            AdjustTemplate();
172.                                                    }
173.                                                    if (sx > 0)
174.                                                    {
175.                                                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                                                
176.                                                            sx = sy = -1;
177.                                                    }
178.                                            }
179.                                            break;
180.                            }
181.                    }
182. //+------------------------------------------------------------------+
183. };
184. //+------------------------------------------------------------------+

Código fuente de la clase C_ChartFloatingRAD

Sé que este código parece muy confuso y complejo, especialmente para quienes están empezando. Pero si has estado siguiendo esta secuencia desde el inicio, ya deberías haber aprendido bastante sobre programación en MQL5. Aun así, este código es mucho más complicado de lo que muchos suelen crear.

Si comparas este código con el presente en el artículo anterior, verás que es aún más complicado. Sin embargo, gran parte de la complicación fue transferida a la clase C_AdjustTemplate, que se explicó en el tema anterior. Pero vamos a entender qué hace este código. Pues es aquí donde reside la magia del indicador Chart Trade. Esto porque el código del indicador, visto en el artículo anterior, permanece igual. Pero este cambió, y cambió lo suficiente como para que se añadieran nuevas funcionalidades al indicador.

Comenzando la explicación, en la línea 10, tenemos una enumeración, esta nos ayudará a acceder más fácilmente a los objetos presentes en el template. Pero en esta misma enumeración tenemos un valor, MSG_NULL, este valor está ahí por un motivo de control, pero esto se verá a lo largo de la explicación.

Revisando el código, vemos entre las líneas 22 y 25 una estructura, esta estará siendo usada por una variable que es un array. Pero observando el número de elementos en el array, ¿qué es esto? No tenemos un valor, sino otra cosa. Podrías estar pensando: No encuentro en ningún lugar lo que esta cosa significa. Calma, calma, no necesitas entrar en pánico. Este dato, que indica el número de elementos del array, es precisamente el último dato de la enumeración hecha en la línea 10. Pero hay un detalle aquí. Este último valor no representa realmente un elemento o objeto. Si lo representara, la declaración debería ser diferente. Pero como no lo representa, podemos usarlo de la forma en que se está haciendo.

La próxima línea que merece alguna explicación se encuentra en el punto 54. Aquí es el punto donde realmente accederemos al template. Pero antes de entrar en esta explicación, veamos otra cosa. Este procedimiento está siendo accedido en dos lugares. El primero es en la línea 121, que es el constructor, y el segundo es en la línea 171, que está en el procedimiento de manejo de mensajes. ¿Y por qué es importante saber esto? El motivo es lo que haremos y queremos hacer en el template.

En el primer caso, que ocurre en el constructor, queremos montar el template de manera que funcione de una forma específica. En el segundo caso, trabajaremos con lo que ya tenemos; no cambiaremos el template, pero haremos que se ajuste de forma precisa a lo que queremos.

Quizás esta explicación haya sido un poco confusa, pero veamos el funcionamiento del procedimiento de la línea 54. Tal vez esto te ayude a entender mejor las cosas. Entre las líneas 56 y 67, tenemos la definición de dos macros. Existen allí para ayudarnos y facilitar la programación. Así como las líneas 89 y 90 sirven para eliminar tales macros. Normalmente, me gusta usar macros cuando repetiré el mismo código o algún parámetro varias veces. En este caso específico, lo que se repite es el parámetro. Pero el código de las macros es bastante simple.

La primera, que está entre las líneas 56 y 61, añadirá cosas para que la clase C_AdjustTemplate nos informe de vuelta. En la segunda macro, que está entre las líneas 62 y 67, tomamos los valores que la clase C_AdjustTemplate nos informa y los transformamos en un valor, que se almacena en el array declarado en la línea 25. Entonces, ten esto en mente. No estamos simplemente adivinando dónde están los objetos. Estamos preguntando al template dónde están.

Con esto, en la línea 71, verificamos si estamos iniciando el ajuste del template. Si este no es el caso, ejecutaremos la llamada presente en la línea 75. Por otra parte, si es la primera llamada, le diremos a la clase C_AdjustTemplate cuáles serán los nombres a usar. Esto se hace en la línea 73. Ahora presta mucha atención a la línea 74. Ve que en esta línea usaremos la enumeración para decirle a la clase C_AdjustTemplate cuáles son los objetos de los que queremos saber los datos. Usamos un bucle para esto. Así la clase C_AdjustTemplate sabrá cuáles son las propiedades que deberán capturarse.

De todas formas, entre las líneas 76 y 81, estaremos informando al template de los valores que se deben usar en las propiedades de los objetos. Cada línea indica un objeto, la propiedad que se va a modificar y el valor que se va a utilizar.

Finalmente, en la línea 82, le decimos a la clase C_AdjustTemplate que puede ajustar el template según lo informado. Esto se hace como se vio en el tema anterior. Cuando se haya ejecutado todo el trabajo, verificaremos en la línea 83 si se está haciendo la primera llamada. Si esto es cierto, ajustaremos los valores del array declarado en la línea 25. Esto se hace mediante un bucle que le dice a la clase C_AdjustTemplate cuáles son los objetos y la propiedad cuyo valor deseamos conocer.

Después de terminar este trabajo, usamos la línea 85 para cerrar la clase C_Template. Y para finalizar, en la línea 87, pedimos que el objeto OBJ_CHART sea actualizado. De esta manera, veremos la magia suceder, tal como puedes ver en el video de demostración, que se encuentra más al final del artículo.

Observa que aquí no estamos comprobando ningún tipo de error aquí. Presumimos que todo está y ocurrirá bien. Pero verificaremos la presencia de errores en el código del indicador. Entonces, si ocurre alguna falla, no se tratará aquí, sino en el código del indicador.

Ahora veamos algo diferente: La línea 93 inicia una función bastante interesante, que podría colocarse directamente donde se utilizaría. Pero para hacer el código un poco más legible, se creó esta función. Esta función cuenta con un bucle, que comienza en la línea 97 y recorrerá cada uno de los objetos presentes en el OBJ_CHART. Recuerda lo siguiente: El objeto OBJ_CHART contiene el template, y este contiene los objetos que estaremos verificando. Durante este recorrido, crearemos un rectángulo que es la caja o región de clic de cada objeto. Esto se hace entre las líneas 99 y 102.

Una vez que tenemos esta región de clic, podemos compararla con la región informada como parámetros de la llamada. Esta comparación se realiza justamente en la línea 103. Ahora observa que, además de la región, también existen algunas condiciones extra. Si todo está en orden, se devolverá el índice de los objetos. De lo contrario, devolveremos un valor MSG_NULL. Es precisamente por este motivo que necesitamos que la enumeración, definida al principio, tenga este valor. Si no existiera, no sería posible informar que el clic fue en un objeto inválido en el indicador Chart Trade.

Lo próximo a explicar está en la línea 132, que es precisamente el manejador de eventos. Este ahora contiene algunas partes nuevas. Pero son estas partes nuevas las que hacen posible lo que se ve en el video de demostración. Entonces, entendamos con mucha atención lo que está ocurriendo. Y observa que, en ningún momento hasta ahora, hemos creado ningún otro objeto que no sea el OBJ_CHART. Y aun así, tenemos el funcionamiento esperado.

Gran parte del código se parece bastante a lo que se vio en el artículo anterior. Pero aun así, hay pequeñas diferencias que merecen ser comentadas, esto para que los menos experimentados puedan entender lo que está sucediendo. En las líneas 134 a 136 definimos algunas variables. La variable definida en la línea 136 es la que nos interesa en este artículo, ya que las demás ya fueron explicadas.

Esta variable presente en la línea 136 nos servirá de memoria. Esto es porque no podremos contar con ninguna ayuda extra de MetaTrader 5 para solucionar la cuestión de los clics. Normalmente, cuando hay objetos en el gráfico, MetaTrader 5 nos informará el nombre del objeto que recibió el clic. Esto se hace mediante el evento CHARTEVENT_OBJECT_CLICK. Pero aquí, no tenemos ningún objeto real, además del OBJ_CHART. Entonces, cualquier clic dado en el área del indicador Chart Trade será informado por MetaTrader 5 como un clic en el OBJ_CHART.

Pero el único evento que estamos tratando es el CHARTEVENT_MOUSE_MOVE. Esto porque es más que suficiente para nosotros. Sin embargo, los clics se tratarán solo y únicamente si el Indicador Mouse no está en estado de estudio. Esto se prueba en la línea 141. Si el Indicador Mouse está en estado de estudio, o algo más ha ocurrido, iremos a la línea 156. Y aquí surge una cuestión. Si la variable declarada en la línea 136 tiene un valor diferente, algo deberá suceder. Pero primero veamos cuándo y dónde esta variable recibirá su valor.

Cando el indicador mouse esté libre y ocurra un clic, la prueba realizada en la línea 141 permitirá verificar dónde y quién recibió el clic. Esto se hace en la línea 143. En ese momento, informaremos a la función de análisis dónde se encuentra el mouse en el momento del clic. Sin embargo, existe una pequeña falla aquí. Pero no entraré en detalles ahora, ya que será corregida en el próximo artículo, así como otras pequeñas cosas que aún faltan hacer. Bien, al mismo tiempo que la función verifica, devuelve el nombre del objeto que recibió el clic, y este se graba en la variable estática.

Ahora probamos, pero por cuestiones prácticas, probamos solo el objeto de título. Si recibió el clic, la línea 145 permitirá que se ejecute el código de arrastre. Podríamos colocar otros objetos aquí. Sin embargo, hacer esto complicaría, al menos en este momento, la lógica de prueba. Ya que mientras el botón del mouse esté presionado, el objeto seguiría recibiendo mensajes de clic.

Como se mencionó, podemos mejorar esto. Pero por ahora quiero dejar el código más simple, haciendo los cambios poco a poco. Ya que el concepto mostrado aquí es muy diferente de lo que muchos normalmente programan.

Pero volvamos ahora a la línea 156. Cuando se ejecute esta línea, dentro de esta condición realizaremos dos pruebas. La primera verificará si algún objeto presente en el OBJ_CHART recibió algún clic. Si esto ha sucedido, tendremos la misma condición que se realizaría si el objeto realmente existiera en el gráfico y MetaTrader 5 generara el evento CHARTEVENT_OBJECT_CLICK. Es decir, estamos "emulando" el funcionamiento de un sistema ya existente. Pero para dar un comportamiento adecuado a todo el Indicador.

Así, si la prueba presente en la línea 158 pasa, primero haremos el tratamiento adecuado del evento. Esto se hace entre las líneas 160 a 169, y luego, en la línea 170, eliminamos la indicación de objeto, y en la línea 171, hacemos una actualización del estado presente en el template. Así, todo el indicador será actualizado, permitiendo que tengas la ilusión de que los objetos están presentes en el gráfico. Siendo que el único objeto real presente es el OBJ_CHART.

Todo el resto del código de la clase C_ChartFloatingRAD ya fue explicado en el artículo anterior. De esta manera, no veo necesidad de comentarlo nuevamente aquí.

Video de demostración.

Conclusión

Como puedes ver, en este artículo, presenté una forma de usar templates en un OBJ_CHART, de manera que podamos tener un comportamiento muy similar al que tendríamos si objetos reales estuvieran en el gráfico. Tal vez la mayor ventaja de hacer uso de lo que estoy mostrando es la agilidad para construir una interfaz, usando elementos presentes en el propio MetaTrader 5, esto sin hacer uso de una pesada programación vía MQL5.

Aunque parece bastante confuso y complejo lo que estoy mostrando, esto se debe a que es algo completamente nuevo para ti, querido lector. Con el tiempo y un poco de práctica, notarás que podemos hacer un uso masivo de este conocimiento en diversos tipos de escenarios y momentos. Pero debo admitir que el sistema aún no está completo. Además, cuenta con una pequeña falla. Pero esto se resolverá en el próximo artículo, donde finalmente permitiremos que el usuario ingrese directamente los valores en el Chart Trade. Esta cuestión será muy interesante de resolver. Así que no te pierdas el próximo artículo de esta serie.


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

Archivos adjuntos |
Anexo.zip (420.65 KB)
Desarrollo de un sistema de repetición (Parte 45): Proyecto Chart Trade (IV) Desarrollo de un sistema de repetición (Parte 45): Proyecto Chart Trade (IV)
Lo principal en este artículo es precisamente la presentación y explicación de la clase C_ChartFloatingRAD. Tenemos el indicador Chart Trade, que funciona de una manera bastante interesante. No obstante, si te das cuenta, aún tenemos un número bastante reducido de objetos en el gráfico. Y aun así, tenemos exactamente el comportamiento esperado. Se pueden editar los valores presentes en el indicador. La pregunta es: ¿Cómo es esto posible? En este artículo comenzarás a entenderlo.
Desarrollo de un sistema de repetición (Parte 43): Proyecto Chart Trade (II) Desarrollo de un sistema de repetición (Parte 43): Proyecto Chart Trade (II)
Gran parte de las personas que quieren, o desean aprender a programar, no tienen en realidad idea de lo que están haciendo. Lo que hacen es intentar crear las cosas de una determinada manera. Sin embargo, cuando programamos no estamos realmente intentando crear una solución. Si intentas hacerlo de esta manera, generarás más problemas que soluciones. Aquí haremos algo un poco más avanzado, y por consecuencia diferente.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 6): Dos indicadores RSI se cruzan entre sí Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 6): Dos indicadores RSI se cruzan entre sí
Por asesor multidivisa en este artículo nos referimos a un asesor o robot comercial que utiliza dos indicadores RSI con líneas de intersección: un RSI rápido que se cruza con uno lento.