English Русский Português
preview
Desarrollo de un sistema de repetición (Parte 39): Pavimentando el terreno (II)

Desarrollo de un sistema de repetición (Parte 39): Pavimentando el terreno (II)

MetaTrader 5Ejemplos | 5 junio 2024, 10:11
125 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, Desarrollo de un sistema de repetición (Parte 38): Pavimentando el terreno (II), expliqué y demostré cómo enviar información, en nuestro caso del EA al indicador. Con el fin de configurar el indicador para que pueda devolvernos algún tipo de información plausible.

Pues bien, ahora ha llegado el momento de ir en el sentido contrario, es decir, hacer que el indicador le diga al solicitante, que en nuestro caso es el EA, algo que tenga algún tipo de sentido. Esto es para que podamos saber cómo, o de qué forma, deberemos proceder.

Existen diversas cosas aquí. Algunas más simples de explicar, otras no tanto. Algunas demandan tiempo, mientras que otras son relativamente rápidas de demostrar. Pero, de una forma u otra, aquí, en este artículo, crearemos las bases necesarias para poder construir nuestro Chart Trader. El Chart Trader que se desarrollará aquí, en el sistema de repetición/simulación, se parecerá mucho al que se vio en el artículo pasado.

Sin embargo, diferente a aquel visto en el artículo que puedes ver consultando en este enlace:  Desarrollo de un EA de trading desde cero (Parte 30): ¿¡El CHART TRADE ahora como indicador?! Lo que haremos aquí hará que aquel parezca un juego de niños debido a la forma en que haremos ese mismo Chart Trader. Pero no voy a arruinar la sorpresa. De lo contrario, no tendrá gracia. Para simplificar la explicación de lo que se hará, primero es necesario que entiendas algunas cosas más, y tales cosas se verán aquí, en este artículo.


Construcción inicial del modelo

A pesar de que lo visto en los dos últimos artículos sea algo válido y todo aquello esté funcionando, como me propuse mostrar, tenemos una cuestión aquí: el hecho de que ya estamos solo enviando datos del EA al indicador. Bien, entonces existe algún tipo de comunicación, pero aún no estamos leyendo los datos del indicador. En este caso, necesitamos organizarnos para leer los datos del indicador. 

Para facilitar nuestra vida, haremos algunas cosas extras en el código visto hasta el momento. 

Lo primero que haremos, para facilitar inmensamente nuestra vida, es crear un archivo de cabecera. Este puede ser visto a continuación:

1. #property copyright "Daniel Jose"
2. #property link      ""
3. //+------------------------------------------------------------------+
4. #define def_ShortName        "SWAP MSG"
5. //+------------------------------------------------------------------+

Archivo de cabecera

Este archivo de cabecera se llamará Defines.mqh. Debe ser guardado en el directorio Includes, en la subcarpeta Mode Swap. La única línea que de hecho tiene algún valor para nosotros es la línea 4. En ella definimos un nombre para ser usado tanto en el EA como en el indicador, con el fin de facilitar la portabilidad, ya que podrías poner un nombre en el indicador y olvidarte de poner el mismo nombre en el EA. Y no me refiero al nombre del archivo. Me refiero al nombre con el cual el indicador será reconocido por MetaTrader 5.

Para que esto quede más claro, vamos ver el código del indicador. Este puede ser visto a continuación:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 0
07. //+------------------------------------------------------------------+
08. #include <Mode Swap\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_ShortNameTmp    def_ShortName + "_Tmp"
11. //+------------------------------------------------------------------+
12. input double user00 = 0.0;
13. //+------------------------------------------------------------------+
14. long m_id;
15. //+------------------------------------------------------------------+
16. int OnInit()
17. {
18.     m_id = ChartID();
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
20.     if (ChartWindowFind(m_id, def_ShortName) != -1)
21.     {
22.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
23.             Print("Only one instance is allowed...");
24.             return INIT_FAILED;
25.     }
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Print("Indicator configured with the following value:", user00);
28.     
29.     return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
33. {
34.     return rates_total;
35. }
36. //+------------------------------------------------------------------+

Código fuente del Indicador

Presta atención al código presente en la línea 08. En ella incluimos, o mejor dicho, pedimos al compilador que incluya el código del archivo de cabecera que vimos hace poco. No se ha hecho ninguna modificación en el código, salvo el hecho de que ahora no tenemos más esa definición realizada aquí en el código del indicador.

De esta manera, toda y cualquier explicación hecha antes sigue siendo válida. Pero ahora vamos a ver el código del EA. Este puede ser visto a continuación:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #include <Mode Swap\Defines.mqh>
06. //+------------------------------------------------------------------+
07. #define def_SWAP "Mode Swap\\Swap MSG.ex5"
08. #resource "\\Indicators\\" + def_SWAP
09. //+------------------------------------------------------------------+
10. input double user00 = 2.2;
11. //+------------------------------------------------------------------+
12. int m_handle;
13. long m_id;
14. //+------------------------------------------------------------------+
15. int OnInit()
16. {   
17.     m_id = ChartID();
18. 
19.     EraseIndicator();
20.     m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
21.     ChartIndicatorAdd(m_id, 0, m_handle);
22.     
23.     Print("Indicator loading result:", m_handle != INVALID_HANDLE ? "Success" : "Failed");
24.     
25.     return INIT_SUCCEEDED;
26. }
27. //+------------------------------------------------------------------+
28. void OnDeinit(const int reason)
29. {
30.     EraseIndicator();
31. }
32. //+------------------------------------------------------------------+
33. void OnTick()
34. {
35. }
36. //+------------------------------------------------------------------+
37. void EraseIndicator(void)
38. {
39.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE) return;
40.     ChartIndicatorDelete(m_id, 0, def_ShortName);
41.     IndicatorRelease(m_handle);
42. }
43. //+------------------------------------------------------------------+

Código fuente del EA

Aquí tenemos algo MUY DIFERENTE. Pero no tanto, si eres un buen observador. Tal vez la única y verdadera diferencia sea de hecho la existencia de un procedimiento llamado EraseIndicator. Este puede ser visto en la línea 37. Este procedimiento es llamado en dos momentos. El primero es en la línea 19 y el segundo es en la línea 30. Creo que nadie de verdad dudará del por qué sea llamado en la línea 30. Pero, ¿y la llamada de la línea 19? ¿Sabes el motivo de esta llamada?

El motivo de la llamada en la línea 19 es precisamente promover la sincronización entre el valor informado en el EA y el valor que se enviará al indicador. Pero hay algo que debe ser observado con calma. Ve la línea 39 del código del EA. Observa que cuando se llame al procedimiento, ya sea en la línea 19 o en la línea 30, verificaremos si el indicador está en el gráfico. Si no está, el código finalizará su ejecución. Y tanto la línea 40, que elimina el indicador del gráfico, como la línea 41, que libera el handle, no se ejecutarán.

Pero, ¿por qué necesitamos eliminar el indicador del gráfico usando la línea 40? El motivo no está en el código del EA, sino en el código del indicador.

Al observar el código del indicador, notarás que si está en el gráfico, en el momento en que el EA intente cambiar su valor, la línea 20 bloqueará el indicador. Evitará que se actualice, ya que el sistema detectará que se está intentando colocar un nuevo indicador en el gráfico. Aunque esto no sea realmente lo que esté ocurriendo, la línea 20 lo verá de esta forma. Por esto necesitamos eliminar el indicador del gráfico.

Pero volviendo al código del EA. ¿Tienes idea de lo que representan las líneas 07 y 08? ¿Y por qué la línea 20 es diferente a la vista en el artículo anterior?

Es en este punto donde comenzamos a salir de lo básico y empezamos a entrar en otro terreno. La línea 07 crea una definición, pero esta definición indica dónde se encuentra el código del indicador. De esta forma, cuando el compilador cree el ejecutable del EA y encuentre la línea 08, buscará el ejecutable indicado en la línea 07. Si este no existe, el compilador se encargará de compilar el código responsable de generar tal ejecutable. Es decir, en la línea 08, hacemos que el indicador sea un recurso interno del EA.

Esto es algo bastante interesante. Tiene sus pros y sus contras. Una de las ventajas que puedo mencionar es que: una vez compilado el EA, puedes eliminar el ejecutable del indicador. Pero calma. No hagas esto todavía. Hay un detalle que debes considerar para poder hacerlo. Una desventaja que también puedo mencionar es que: Una vez compilado el EA, si tienes problemas en el indicador, tendrás que recompilar todo el código del EA. Aunque el problema sea solo en el indicador. Pero nuevamente calma. Hay un detalle que debe ser tenido en cuenta.

¿Y cuál sería ese detalle? Bueno, es algo bastante sutil. Casi pasa desapercibido. Por esto, todo buen programador también es un buen observador. Entonces vamos al detalle. Observa la línea 20 del código del EA. Fíjate que en la declaración del tercer parámetro aparece una pequeña cadena ( :: ). Esta pequeña cadena que viene antes de la definición es un operador de resolución de ámbito.

El hecho de que este operador esté ahí indica que usaremos algo, no exactamente como podemos imaginar. Cada vez que este operador aparece, estamos de alguna forma, informando explícitamente al compilador algo a ser hecho. Normalmente se tratará de algún tipo de decisión que el compilador deberá tomar, pero queremos que sepa explícitamente cuál decisión tomar.

En el caso específico, visto en el código, estamos informando al compilador que use el indicador que se encuentra como un recurso en el ejecutable del EA. En este caso, podremos simplemente eliminar el ejecutable del indicador, una vez que el EA haya sido compilado con éxito. Y aun así el EA podrá usar el indicador correcto.

Si este operador, el llamado de resolución de ámbito, no se añade, como se muestra en el fragmento a continuación, aunque el compilador incluya el indicador como un recurso del EA, el EA usará de hecho el ejecutable que se encuentra en el directorio informado en la definición. Es decir, el indicador tendrá que ser transportado junto con el EA, siendo así un archivo separado.

20.     m_handle = iCustom(NULL, PERIOD_CURRENT, def_SWAP, user00);

Este simple detalle hace toda la diferencia. En el caso de la línea 20, en el código completo, una vez que el EA haya sido compilado, podrás eliminar el ejecutable del indicador. Hay un detalle más: si el indicador se añade y usa como recurso del EA, antes de compilar el EA, preferiblemente debes eliminar el ejecutable del indicador. Esto para garantizar que el último código presente en el indicador sea de hecho compilado. Normalmente, en sistemas donde usamos compilaciones múltiples, contamos con una ayuda. Un archivo normalmente llamado MAKE.

Al usar este tipo de ayuda, cada vez que vayas a compilar un código, el MAKE eliminará adecuadamente los ejecutables. Esto de forma totalmente automática. Compara el momento de la última compilación con el tiempo de la última modificación de algunos de los archivos usados en el código fuente del ejecutable. Si ha habido alguna edición, el MAKE eliminará el ejecutable, haciendo que el compilador cree uno nuevo. En el momento actual, MQL5 aún no cuenta con este recurso. Pero quién sabe, en el futuro los mantenedores decidan agregar tal recurso para nosotros, los desarrolladores.

Esta explicación dada arriba muestra lo que podemos hacer, cómo debemos proceder y cómo procederemos en el futuro. Pero aún no tenemos la lectura de ningún tipo de dato del indicador.

Vamos a implementar una versión muy, pero muy simple de comunicación. Para explicar esto con más detalles y con más calma, vamos a un nuevo tema.


Leyendo los datos del indicador.

Básicamente, solo existe una forma de leer cualquier tipo de información presente en el indicador. Leyendo el buffer del mismo. Pero aquí tenemos un pequeño detalle: La función CopyBuffer no nos permite trabajar con cualquier tipo de dato presente en el buffer. No de forma natural, y observando la declaración de esta función, entenderás a qué me refiero.

int  CopyBuffer(
   int       indicator_handle,     // handle do indicador
   int       buffer_num,           // número do buffer do indicador
   int       start_pos,            // posição de início
   int       count,                // quantidade para copiar
   double    buffer[]              // array destino para copiar
   );

Definición de la función CopyBuffer

Para explicar mejor. Puedes ver arriba cómo se declara una de las variantes de la función CopyBuffer. Digo una, porque existen tres, pero lo que realmente nos importa es la última variable presente en la declaración. Noten que es del tipo double, es decir, solo podremos retornar valores del tipo double. Esto en principio, porque en realidad podemos retornar cualquier cosa. Sin embargo, en el artículo Desarrollo de un EA de trading desde cero (Parte 17): Acceso a los datos en la web (III), mostré una forma de burlar esto.

Usaremos algo muy parecido aquí en esta secuencia también, pero en un nivel diferente. Tal vez un poco más grande y más complicado, esto para quienes están comenzando a programar. Pero la idea será la misma usada en el artículo mencionado. Si deseas saber más detalles, lee el artículo para entender lo que realmente haremos. Aquí simplemente lo haremos, y no explicaré los detalles que se muestran en ese artículo.

Así que está bien, ya sabemos con qué lidiar. Vamos a modificar el código fuente del indicador para algo visto abajo. Detalle: haré las cosas poco a poco para que puedas seguir y comprender lo que se estará haciendo.

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 1
07. //+------------------------------------------------------------------+
08. #include <Mode Swap\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_ShortNameTmp    def_ShortName + "_Tmp"
11. //+------------------------------------------------------------------+
12. input double user00 = 0.0;
13. //+------------------------------------------------------------------+
14. long m_id;
15. double m_Buff[];
16. //+------------------------------------------------------------------+
17. int OnInit()
18. {
19.     m_id = ChartID();
20.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
21.     if (ChartWindowFind(m_id, def_ShortName) != -1)
22.     {
23.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
24.             Print("Only one instance is allowed...");
25.             return INIT_FAILED;
26.     }
27.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
28.     Print("Indicator configured with the following value:", user00);
29.     
30.     SetIndexBuffer(0, m_Buff, INDICATOR_CALCULATIONS);
31.     ArrayInitialize(m_Buff, EMPTY_VALUE);
32. 
33.     return INIT_SUCCEEDED;
34. }
35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     m_Buff[rates_total - 1] = user00 * 2.0;
39.     
40.     return rates_total;
41. }
42. //+------------------------------------------------------------------+

Código fuente del indicador

Si comparas el código fuente visto arriba con el código fuente visto al inicio de este artículo, notarás que hay solo algunas pocas diferencias. Como dije, iré despacio. Básicamente, tenemos el cambio en la línea 06, donde decimos que existirá un buffer dentro del indicador. Este buffer será visible fuera del indicador. Pero después veremos esto. Primero, entendamos lo que está sucediendo en el indicador.

Después de esto, tenemos en la línea 15 la declaración de una variable global. Pero esta será vista solo por el código del indicador, o por las partes que pertenezcan al indicador. Esta variable, que es un buffer, necesita ser inicializada. Esto se hace en las líneas 30 y 31. Ahora ya tenemos nuestro buffer inicializado y accesible con algún tipo de información.

Sin embargo, si lo lees, podrías obtener solo basura. Así que necesitamos alguna forma útil de volcar datos en el buffer. Podemos hacerlo de diversas maneras, pero técnicamente, la mejor y más segura manera es como se está haciendo. Por esta razón tenemos la línea 38.

Observa con bastante atención esta línea. Notarás que no apunto a ninguna región en el buffer. ¿Pero por qué?

El motivo es que necesitamos estandarizar las cosas de alguna forma. Si colocas la información, en este caso el resultado de la multiplicación del valor informado por el EA por 2.0, podría ser difícil saber dónde buscar la información realmente útil. Recuerda, estoy avanzando con calma. La cosa se complicará mucho más adelante.

Entonces, usamos el propio sistema de cálculo del indicador para colocar la información siempre en una misma posición.

Para comprender mejor, veamos el código fuente del EA. Este puede verse ahora a continuación:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #include <Mode Swap\Defines.mqh>
06. //+------------------------------------------------------------------+
07. #define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5"
08. #resource "\\" + def_SWAP
09. //+------------------------------------------------------------------+
10. input double user00 = 2.2;
11. //+------------------------------------------------------------------+
12. int m_handle;
13. long m_id;
14. //+------------------------------------------------------------------+
15. int OnInit()
16. {   
17.     double Buff[];
18.     
19.     m_id = ChartID();
20. 
21.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE)
22.     {
23.             m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
24.             ChartIndicatorAdd(m_id, 0, m_handle);
25.     }
26.             
27.     Print ("Buffer reading:", (m_handle == INVALID_HANDLE ? "Error..." : CopyBuffer(m_handle, 0, 0, 1, Buff) > 0 ?  (string)Buff[0] : " Failed."));
28.     
29.     return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.     ChartIndicatorDelete(m_id, 0, def_ShortName);
35.     IndicatorRelease(m_handle);
36. }
37. //+------------------------------------------------------------------+
38. void OnTick()
39. {
40. }
41. //+------------------------------------------------------------------+

Código fuente del EA

Noten que el código no cambió mucho respecto al visto antes. Recordemos que aquí el Indicador será usado como recurso. Así que puedes eliminar el ejecutable del indicador después de compilar el EA. Veamos qué se añadió al código.

Primero, ahora tenemos la declaración de una variable en la línea 17. Este será nuestro buffer de retorno, o de lectura del indicador. Además de esta línea 17, también tenemos una nueva línea, la línea 27.

A muchos les parecerá que esta línea 27 es un auténtico caos. Pero básicamente son dos operadores ternarios anidados. Primero, verificamos si el handle del indicador es válido; si no lo es, se imprimirá un mensaje en la caja de mensajes del terminal. Si el handle es válido, leeremos una posición del buffer del indicador. ¿Y cuál posición será leída? La primera. No entiendo. Tranquilo, relajado, continuemos y luego explicaré mejor esto. Si tenemos éxito en la lectura del buffer, imprimiremos el valor que contiene. Si no tenemos éxito en la lectura, se imprimirá otro mensaje de error. Pero este será diferente del primero, para indicar que la causa del fallo fue otra.

Ahora, entendamos por qué, cuando leemos el buffer de la forma mostrada en la línea 27, estamos leyendo la primera y no la última posición.


Entendamos la escritura y lectura del buffer.

Hay una imagen, que tomaré prestada de la documentación de MQL5, que expresa claramente lo que explicaré. Esta puede verse a continuación:

Figura 01

Figura 01 - Escritura y lectura de buffer de indicadores

Esta imagen casi habla por sí sola. Pero aun así, entendamos cómo se escribe en el buffer del indicador y cómo se lee fuera de él.

Cuando escribes en el buffer, en el indicador, debes tener cuidado de siempre escribir en una posición determinada. Conforme aparecen nuevas barras, nuevas posiciones van surgiendo automáticamente en el buffer. El problema es que en cada marco temporal tendrás un buffer de tamaño diferente.

¿Y dónde está el problema en esto? No es solo un problema. Hay más de uno. Para empezar, supongamos que en el indicador decides escribir en la posición cero usando algo parecido al código a continuación:

35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     m_Buff[0] = user00 * 3.0;
39.     
40.     return rates_total;
41. }
42. //+------------------------------------------------------------------+

Fragmento de código - Modelo 01

Si modificas las líneas en el código del indicador visto en el tema anterior por este fragmento de código, estarás literalmente escribiendo en la posición Cero del buffer, como se puede notar en la línea 38. Pero hay un problema al hacer esto.

El problema no estará en escribir, sino cuando intentes leer el buffer fuera del código del indicador. Por ejemplo, en el EA.

Cuando ejecutas una lectura de buffer, normalmente y frecuentemente, el MetaTrader 5 retornará solo un máximo de 2000 posiciones. Es decir, caemos en la cuestión de la figura 01. Si, por casualidad, la cantidad de datos presentes en el indicador supera estas 2000 posiciones, tendrás problemas. Recuerda que la escritura se dio en la posición cero, pero la posición cero NO ES la misma posición cero a la que CopyBuffer se refiere. Para CopyBuffer, esta posición cero, que está en el código del indicador, puede ser en realidad la posición 2001, y si este es el caso, no podrás leer la posición cero del buffer del indicador. Ese valor estará atrapado en el indicador.

Por esta razón, renunciamos a escribir en una posición iniciada en cero y comenzamos a escribir a partir de una posición que se da al final del buffer. ¿Aún no comprendes?

La posición cero en el buffer del indicador debe siempre pensarse como la posición rates_total - 1. Por esta razón, escribimos en esta posición en el código del indicador visto en el tema anterior. Y justamente por este hecho, cuando leemos el buffer del indicador vía CopyBuffer, al imprimir el valor, usamos de hecho el índice cero.

Tal vez esto aún no esté del todo claro en tu mente. Para comprenderlo mejor, veamos otro ejemplo de código, donde pasaremos un dato al indicador. Pero a diferencia de lo hecho en el tema anterior, esta vez retornaremos no uno, sino una cantidad mayor de valores. Siendo uno de ellos una simple string.

Un detalle: Puedes escribir al revés en el buffer o puedes escribir de manera convencional. O mejor dicho, siguiendo el orden: Primera información, primer valor. Segunda información, segundo valor. Si se hiciera al revés, sería: Primera información, último valor. Segunda información, último valor menos una posición, y así sucesivamente. Pero para facilitar la interpretación, no en la escritura, sino en la lectura, hagamos las cosas de forma convencional.

Para simplificar nuestra vida, vamos a modificar el archivo de cabecera, para lo que se puede ver a continuación:

01. #property copyright "Daniel Jose"
02. //+------------------------------------------------------------------+
03. #define def_ShortName       "SWAP MSG"
04. //+------------------------------------------------------------------+
05. union uCharDouble
06. {
07.     double  dValue;
08.     char    cInfo[sizeof(double)];
09. };
10. //+------------------------------------------------------------------+

Código fuente del archivo Defines.mqh

Lo que tenemos aquí, entre las líneas 05 y 09, es una unión. Esta unión permitirá pasar datos del tipo texto usando un valor del tipo double. Si ves esto por primera vez, puede parecerte extraño. Pero ya hemos hecho esto en otros momentos. Un ejemplo de esto se ve en el artículo: Desarrollo de un EA de trading desde cero (Parte 17): Acceso a los datos en la web (III). Pero volvamos a nuestra cuestión. Ahora tenemos una forma de enviar una pequeña string del indicador al EA. Y el motivo de usar un valor double es porque no podemos enviar, vía CopyBuffer, otro tipo de valor. Obligatoriamente, debemos usar el tipo double.

Habiendo hecho este cambio en el archivo Defines.mqh, podemos pasar al código fuente del indicador.

01. #property copyright "Daniel Jose"
02. #property version   "1.00"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. #property indicator_buffers 1
06. //+------------------------------------------------------------------+
07. #include <Mode Swap\Defines.mqh>
08. //+------------------------------------------------------------------+
09. #define def_ShortNameTmp    def_ShortName + "_Tmp"
10. //+------------------------------------------------------------------+
11. input double user00 = 0.0;
12. //+------------------------------------------------------------------+
13. long m_id;
14. double m_Buff[];
15. //+------------------------------------------------------------------+
16. int OnInit()
17. {
18.     m_id = ChartID();
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
20.     if (ChartWindowFind(m_id, def_ShortName) != -1)
21.     {
22.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
23.             Print("Only one instance is allowed...");
24.             return INIT_FAILED;
25.     }
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Print("Indicator configured with the following value:", user00);
28.     
29.     SetIndexBuffer(0, m_Buff, INDICATOR_CALCULATIONS);
30.     ArrayInitialize(m_Buff, EMPTY_VALUE);
31. 
32.     return INIT_SUCCEEDED;
33. }
34. //+------------------------------------------------------------------+
35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
36. {
37.     uCharDouble info;
38.     int pos = rates_total - 3;
39.     
40.     StringToCharArray("Config", info.cInfo);
41.     m_Buff[pos + 0] = info.dValue;
42.     m_Buff[pos + 1] = user00 * 2.0;
43.     m_Buff[pos + 2] = user00 * 2.5;
44.             
45.     return rates_total;
46. }
47. //+------------------------------------------------------------------+

Código fuente del indicador - Actualizado para escribir en más de una posición

Observa con atención este código arriba. Nota que solo a partir de la línea 37 tenemos algún tipo de cambio respecto al código visto al inicio del artículo. ¡¿Y por qué así?! El motivo es que ahora vamos a estructurar una información más amplia para ser retornada vía CopyBuffer. Ninguna otra parte del código necesitó ser modificada. Solo el código presente a partir de la línea 37.

Entonces, entendamos lo que está sucediendo. En la línea 37, declaramos una variable que se usará para transformar una string en un valor double. Detalle: la string está limitada a 8 caracteres; si la información tiene más caracteres, debes proporcionar un array para esto, siempre contando la información en bloques de 8.

En la línea 38, declaramos una variable que se utilizará para que la información pueda ser escrita de forma convencional. Es decir, sería como si estuvieras escribiendo un texto de izquierda a derecha. En el caso del árabe, escribirías de derecha a izquierda. Pero creo que se entiende la idea. En esta misma línea 38, indicamos la cantidad de valores double que publicaremos. En este caso serán 3 valores.

En la línea 40, convertimos la string en un array de caracteres. Al hacer esto, tendremos el valor a ser usado en nuestra primera posición. Entonces, en la línea 41, volcamos este valor en el buffer. 

En las líneas 42 y 43, efectuamos un cálculo simple, solo para tener algún tipo de dato para ser leído. Así será posible mostrar cómo se dará la lectura después, cuando sea necesario acceder a más posiciones en el buffer.

Básicamente, la cuestión sobre el indicador es esta. En este momento concreto, no hay nada más que decir sobre cómo nos comunicamos. Veamos ahora cómo procederemos para saber y leer el buffer creado en el indicador. Para esto, recurriremos al código del EA. Este puede verse ahora, ya actualizado, a continuación:

01. #property copyright "Daniel Jose"
02. #property version   "1.00"
03. //+------------------------------------------------------------------+
04. #include <Mode Swap\Defines.mqh>
05. //+------------------------------------------------------------------+
06. #define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5"
07. #resource "\\" + def_SWAP
08. //+------------------------------------------------------------------+
09. input double user00 = 2.2;
10. //+------------------------------------------------------------------+
11. int m_handle;
12. long m_id;
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {   
16.     double Buff[];
17.     uCharDouble Info;
18.     int iRet;
19.     string szInfo;
20.     
21.     m_id = ChartID();
22.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE)
23.     {
24.             m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
25.             ChartIndicatorAdd(m_id, 0, m_handle);
26.     }
27.     ArraySetAsSeries(Buff, false);
28.     if (m_handle == INVALID_HANDLE) szInfo = "Invalid handler to read the buffer.";
29.     else
30.     {
31.             if ((iRet = CopyBuffer(m_handle, 0, 0, 3, Buff)) < 3) szInfo = "Buffer reading failed.";
32.             else
33.             {
34.                     Info.dValue = Buff[0];
35.                     szInfo = CharArrayToString(Info.cInfo) + " [ " + (string)Buff[1] + " ] [ " + (string)Buff[2] + " ]";
36.             }
37.     }
38.     Print("Return => ", szInfo);
39.             
40.     return INIT_SUCCEEDED;
41. }
42. //+------------------------------------------------------------------+
43. void OnDeinit(const int reason)
44. {
45.     ChartIndicatorDelete(m_id, 0, def_ShortName);
46.     IndicatorRelease(m_handle);
47. }
48. //+------------------------------------------------------------------+
49. void OnTick()
50. {
51. }
52. //+------------------------------------------------------------------+

Código fuente del EA - Actualizado para leer más de una posición.

En este código actualizado del EA, que puede verse arriba. No es muy diferente de lo que hemos visto desde el inicio. Pero aquí tenemos algunas cosas nuevas que merecen algún tipo de explicación.

Entre las líneas 17 y 19, tenemos la declaración de nuevas variables que utilizaremos para decodificar la información que se encuentra en el buffer. Puede parecer extraño decir esto, pero de hecho la información presente en el buffer está codificada. Ya que estamos repitiendo una string en una de las posiciones.

Pero nuestro interés está realmente entre las líneas 27 y 38. En este punto, es donde estamos leyendo el buffer. Empecemos por partes.

En la línea 27, tenemos un código que en algunos momentos puede no ser visto realmente. Incluso cuando estamos leyendo varias posiciones del buffer. El motivo es que por defecto, la lectura se realizará de forma directa. Es decir, mira la figura 01. Observa que el array está en secuencia directa. Sin embargo, hay momentos en que la lectura debe hacerse de forma inversa. En este caso, en lugar de hacer la indexación contraria en el acceso al array, usamos la función definida en la línea 27 para indicar que la lectura será invertida. En este caso, y al contrario de lo que se está viendo, el valor pasado a la función será false. Así, podemos usar una indexación de acceso como si fuera un acceso directo.

Aunque la línea 27 no tenga mucho sentido en nuestro código actual, dado el hecho de que estamos escribiendo de manera directa, se añadió para mencionar y explicar lo que acabo de decir.

No hay mucho que explicar en la mayoría de las líneas, ya que muchas de ellas son autoexplicativas. Pero saltemos a la línea 31. Aquí tenemos algo que comprender.

Cuando escribimos en el buffer, allí en el indicador, sabemos cuántas posiciones de información estamos enviando. Esta línea 31 hace exactamente esto: lee el número de posiciones esperadas. Lo más adecuado sería hacer una mejor combinación usando el archivo de cabecera para esto. Entonces, si el número de posiciones con información aumentara, tanto el EA como el indicador estarían siempre al tanto de esto. Pero como aquí solo estamos ejemplificando las cosas para explicar un concepto que se usará después, esto puede ignorarse por ahora.

Si la cantidad de posiciones leídas es menor de lo esperado, esto será un error y se informará en el terminal. Ahora, si el número es el esperado, en la línea 34 traduciremos la información que se da como un double a una string. Así recuperamos de vuelta la información colocada en el buffer por el indicador.

Observa que el índice en este caso es cero, de la misma forma como se expresa en el indicador. Si la escritura se hubiera hecho al revés en el indicador, aún podríamos usar la misma indexación en el EA. Bastaría con cambiar el valor en la línea 27, y el EA entendería la información usando la misma forma de indexar.

Prueba hacer esto para entender cómo sucede realmente. Es importante entender estos detalles para comprender los próximos artículos de esta serie.

Una vez hecha la conversión, usamos la línea 35 para montar el mensaje que se imprimirá en el terminal. Así de simple.


Conclusión

En esta breve pausa que se hizo en el proyecto del sistema de repetición/simulador, mostré las bases de lo que veremos en los próximos artículos.

Aquí se mostró solo la parte más simple de todo lo que está por venir. Aún no hemos agregado ni implementado algunas cosas que formarán parte del sistema de repetición/simulación. Sin embargo, el contenido mostrado aquí será de gran importancia para los próximos artículos. Sin entender cómo transferir datos entre indicadores y otros procesos, estarás un poco perdido. Todavía no he mostrado cómo ocultar el indicador en la ventana de indicadores para evitar que el usuario lo elimine. Tampoco hemos añadido algunos elementos que complican mucho la comprensión en diversas combinaciones posibles.

Espero que realmente comprendas los conceptos y el interés real detrás de estos últimos tres artículos. De ahora en adelante, las cosas se complicarán bastante.

Como quiero que desarrolles una memoria mecánica, no habrá ningún anexo aquí. Si quieres experimentar y aprender las bases mostradas aquí, necesitarás escribir los códigos. Pero como son super cortos, esto no será un problema.

Nos vemos en el próximo artículo, donde empezaremos a colocar el Chart Trader para ser usado en el sistema de repetición/simulador.



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

Archivos adjuntos |
EA.mq5 (1.81 KB)
swap.mq5 (1.72 KB)
Defines.mqh (0.24 KB)
Redes neuronales: así de sencillo (Parte 67): Utilizamos la experiencia adquirida para afrontar nuevos retos Redes neuronales: así de sencillo (Parte 67): Utilizamos la experiencia adquirida para afrontar nuevos retos
En este artículo, seguiremos hablando de los métodos de recopilación de datos en una muestra de entrenamiento. Obviamente, en el proceso de entrenamiento será necesaria una interacción constante con el entorno, aunque con frecuencia se dan situaciones diferentes.
Algoritmos de optimización de la población: Algoritmo de recocido simulado (Simulated Annealing, SA). Parte I Algoritmos de optimización de la población: Algoritmo de recocido simulado (Simulated Annealing, SA). Parte I
El algoritmo de recocido simulado es una metaheurística inspirada en el proceso de recocido de los metales. En nuestro artículo, realizaremos un análisis exhaustivo del algoritmo y mostraremos cómo muchas percepciones comunes y mitos que rodean a este método de optimización (el más popular y conocido) pueden ser incorrectos e incompletos. Anuncio de la segunda parte del artículo: "¡Conozca el algoritmo de recocido Isotrópico Simulado (Simulated Isotropic Annealing, SIA) del propio autor!"
Análisis cuantitativo en MQL5: implementamos un algoritmo prometedor Análisis cuantitativo en MQL5: implementamos un algoritmo prometedor
Hoy veremos qué es el análisis cuantitativo, cómo lo utilizan los grandes jugadores y crearemos uno de los algoritmos de análisis cuantitativo en MQL5.
Marcado de datos en el análisis de series temporales (Parte 4): Descomposición de la interpretabilidad usando el marcado de datos Marcado de datos en el análisis de series temporales (Parte 4): Descomposición de la interpretabilidad usando el marcado de datos
En esta serie de artículos, presentaremos varias técnicas de marcado de series temporales que pueden producir datos que se ajusten a la mayoría de los modelos de inteligencia artificial (IA). El marcado dirigido de datos puede hacer que un modelo de IA entrenado resulte más relevante para las metas y objetivos del usuario, mejorando la precisión del modelo y ayudando a este a dar un salto de calidad.