Português
preview
Simulación de mercado: Iniciando SQL en MQL5 (III)

Simulación de mercado: Iniciando SQL en MQL5 (III)

MetaTrader 5Probador |
40 0
Daniel Jose
Daniel Jose

Introducción

Hola a todos y bienvenidos a un nuevo artículo de la serie sobre cómo construir un sistema de repetición/simulación.

En el artículo anterior, Simulación de mercado: Iniciando SQL en MQL5 (II), vimos cómo podríamos desarrollar una clase en MQL5 capaz de darnos cierto soporte. Su finalidad es precisamente permitirnos colocar el código SQL dentro de un archivo de script. De este modo, no necesitaríamos escribir ese mismo código SQL como un string dentro del código MQL5. Aunque esa solución es funcional, contiene algunos detalles que podemos y debemos mejorar, para que las cosas resulten más agradables o, al menos, más cómodas cuando vayamos a hacer un uso más intensivo de SQL.

Sin embargo, en este artículo no solo haremos esas mejoras, ya que esa tarea será bastante rápida y no traerá mayores inconvenientes. Además, veremos cómo podemos integrar los archivos de script SQL en un ejecutable creado en MQL5. Esto se debe a que muchos quizá quieran que el código SQL forme parte del ejecutable final y no sea algo que un usuario pueda modificar o incluso eliminar sin saber realmente de qué se trata.

Además, cuando integramos el archivo, que en este caso será un archivo de script SQL, dentro del ejecutable, tenemos un problema menos al entregar el programa a otro usuario. Esto se debe a que el propio ejecutable ya contendrá los datos esperados y necesarios para que la tarea propuesta se cumpla realmente. No voy a decirte que no hagas algo así, ni seré yo quien te diga que no lo hagas. La decisión de incluir o no, de forma integrada, el archivo, que en este caso será el script SQL, en el ejecutable depende única y exclusivamente de ti, como programador. Porque cada caso es distinto y exige una solución específica para resolverse correctamente.

Saber cómo integrar y utilizar scripts SQL en un ejecutable creado en MQL5 no es una tarea compleja. Pero implica conocer algunos detalles para que todo funcione perfectamente bien. Muy bien, entonces no vamos a extendernos demasiado en explicaciones ni preparativos. Vamos directo a lo que interesa, porque tendremos mucho trabajo en este artículo para dejar las cosas listas para los próximos.


Modificando la estructura de clases

Aunque, a primera vista, el esquema creado en el artículo anterior resulte interesante, contiene algunos problemas que dificultan un uso más intensivo de archivos de script en SQL. Aunque no llegues realmente a usar archivos de script y te limites a usar solo líneas de comando en SQL. La forma en que se estructuraron las cosas en el artículo anterior dificulta hacer ciertas cosas. Una de ellas es cambiar, de forma simple, la dirección que se toma durante la implementación del código principal. Para dejar esto más claro, piensa un momento en lo siguiente: el esquema de las clases y la forma en que funcionan no nos permiten usar algunos recursos de programación. Esto se debe a que todo estará dirigido al terminal o al uso con SQLite, que forma parte de MetaTrader 5.

Sin embargo, si deseas crear una versión particular de SQLite, usando para ello el código fuente disponible en la web, y hacer que ese SQLite particular se use junto con MetaTrader 5, tendrás algunas dificultades con la forma en que se presentó el código. Pero también tenemos otro problema. Si deseas usar un cuadro de mensaje tipo MessageBox, disponible en Windows, para informar algo al usuario, tendrás muchos problemas al usar el esquema presentado en el artículo anterior. Pero resolver esto, en este momento, es una tarea bastante simple. Y como en este punto todavía estamos estructurando lo que realmente se usará cuando vayamos a usar SQL en el sistema de repetición/simulador, necesitamos realizar las modificaciones en esta etapa del desarrollo, para no tener demasiados problemas durante la portabilidad al sistema de repetición/simulador.

Así, lo primero que habrá que hacer será eliminar el archivo de encabezado C_DB_SQLite.mqh. Esto se debe a que vamos a estructurar las cosas de manera que, en el futuro, podamos usar otra implementación de SQL. Una que sea de tipo cliente-servidor. Pero eso es algo que se hará más adelante. Por ahora, seguiremos trabajando con SQLite.

Muy bien. Teniendo esto en cuenta, se cambió el nombre de la clase C_ScriptSQL, así como el nombre del archivo, a C_DB_SQL. El nombre del archivo siempre acompañará al nombre de la clase, así que el archivo pasó a llamarse C_DB_SQL.mqh y estará en la misma ubicación que vimos en el artículo anterior. Sin embargo, el código tuvo algunos cambios, como puedes ver a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Service Graphics\Support\C_Array.mqh"
005. //+------------------------------------------------------------------+
006. class C_DB_SQL
007. {
008.     private    :
009.         C_Array    m_Arr;
010.         int        m_handleDB;
011. //+------------------------------------------------------------------+
012.         void Convert(const char &buff[], const int size)
013.         {
014.             string sz0 = "";
015.             bool b0, b1, bs1, bs2, bc0, bc1, bc;
016.             int nLine = 1;
017.             
018.             b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false;
019.             for (int count = 0, nC0 = nLine; count < size; count++)
020.             {
021.                 switch (buff[count])
022.                 {
023.                     case '\t':
024.                         sz0 += (bs1 || bs2 ? "\t" : "");
025.                         break;
026.                     case '\n':
027.                         nC0++;
028.                     case '\r':
029.                         bc0 = false;
030.                         break;
031.                     case ';':
032.                         b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true);
033.                     default:
034.                         switch (buff[count])
035.                         {
036.                             case '"':
037.                                 bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1);
038.                                 break;
039.                             case '\'':
040.                                 bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2);
041.                                 break;
042.                         }
043.                         if (((count + 1) < size) && (!bs1) && (!bs2))
044.                         {
045.                             if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true;
046.                             if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true;
047.                             if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false;
048.                             if (bc)
049.                             {
050.                                 count += 1;
051.                                 bc = false;
052.                                 continue;
053.                             }
054.                         }
055.                         if (!(bc0 || bc1))
056.                         {
057.                             if ((!b1) && (buff[count] > ' '))
058.                             {
059.                                 b1 = true;
060.                                 nLine = nC0;
061.                             }
062.                             sz0 += (b1 ? StringFormat("%c", buff[count]) : "");
063.                         }
064.                 }
065.                 if (b0)
066.                 {
067.                     m_Arr.Add(sz0, nLine);
068.                     sz0 = "";
069.                     b0 = b1 = false;
070.                 }
071.             }
072.         }
073. //+------------------------------------------------------------------+
074.     public    :
075. //+------------------------------------------------------------------+
076.         C_DB_SQL(const string szFileName = ":memory:")
077.         {
078.             m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE);
079.         }
080. //+------------------------------------------------------------------+
081.         ~C_DB_SQL()
082.         {
083.             DatabaseClose(m_handleDB);
084.         }
085. //+------------------------------------------------------------------+
086.         const string ExecScriptSQL(const string szFileName)
087.         {
088.             int file, size;
089.             char buff[];
090.             string szCmd;
091.             
092.             if ((file = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE)
093.                 return StringFormat("Unable to open script file: %s", szFileName);
094.             ArrayResize(buff, size = (int) FileSize(file));
095.             FileReadArray(file, buff);                
096.             FileClose(file);
097.             Convert(buff, size);
098.             ArrayFree(buff);
099.             for (int count = 0, nLine; count >= 0; count++)
100.             {
101.                 szCmd = m_Arr.At(count, nLine);
102.                 if (nLine < 0) break;
103.                 if (!ExecCommandSQL(szCmd))
104.                     return StringFormat("Execution of line %d of the SQL script failed...", nLine);
105.             }
106.             
107.             return NULL;
108.         }
109. //+------------------------------------------------------------------+
110.         bool ExecCommandSQL(const string szCmd)
111.         {
112.             return (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szCmd));
113.         }
114. //+------------------------------------------------------------------+
115. };
116. //+------------------------------------------------------------------+

Código fuente de C_DB_SQL.mqh

Estoy mostrando estos cambios antes de continuar con lo siguiente para que comprendas adecuadamente lo que se está haciendo. Los cambios no fueron tantos, pero algunos merecen especial atención, empezando por el constructor de la clase. Observa que, en la línea 76, donde declaramos el constructor, tenemos la posibilidad de usar un nombre predeterminado. Ese nombre que ves en el código nos indica, y esto puede verse en la documentación, que la base de datos se abrirá y se creará en la memoria RAM. Este tipo de recurso es bastante interesante cuando queremos probar algunas cosas.

Para más detalles sobre cuál debe ser el valor que se pase al constructor, consulta la documentación de la función DatabaseOpen. Allí tenemos la explicación correcta del valor que debe usarse para crear o abrir una base de datos en disco. De todos modos, aquí vamos a crear o abrir una base de datos. Así, el constructor hará su trabajo.

Esto nos lleva al destructor de la clase, que viene justo a continuación y puede verse en la línea 81. Observa que aquí todo lo que realmente haremos será cerrar la base de datos. Entonces, mientras la clase esté en uso, o pueda utilizarse, la base de datos permanecerá abierta. Recuerda esto si intentas acceder a la base de datos mientras alguna aplicación la está utilizando dentro de MetaTrader 5, ya que esto podría generar errores de acceso.

Como sabemos que la base de datos permanecerá abierta durante todo el tiempo, aquí en la clase podemos trabajar de otro modo. Por eso se hizo un cambio en la forma de ejecutar el archivo de script SQL. Fíjate en que, en la línea 86, ahora tenemos una función que devolverá un string. Entonces, ya no forzaremos la impresión de nada en el terminal. Devolveremos un mensaje de texto al autor de llamada. Este tipo de recurso facilita la implementación de algo que pueda usar una ventana propia, o especialmente diseñada, para informar algo al usuario.

Ten en cuenta que aquí solo cambiamos algunos detalles. Abriremos, traduciremos y enviaremos los comandos presentes en el script para que SQL los ejecute. Esto se hace en la línea 103, que llama precisamente a la función ubicada en la línea 110. Aunque esta función tenga solo una línea, que es la línea 112, aquí estamos preparando la clase, así como el resto de la implementación, para que en el futuro podamos usar una implementación cliente-servidor de SQL. Si esto llega a ocurrir más adelante, necesitaremos modificar lo menos posible el código. Pero cualquier cambio se hará única y exclusivamente en este código de la clase.

Volviendo a la línea 103. En caso de que la ejecución del comando SQL falle, puedes ver que, en la línea 104, formateamos un string. Su objetivo es devolver al autor de llamada una información que indique en qué línea del script SQL no obtuvimos una ejecución correcta. Sin embargo, si todo ocurre perfectamente, tendremos como retorno un valor NULL, como puede verse en la línea 107. De esta manera, tomando como base el script SQL que vimos en el artículo anterior, podremos probar toda la clase de una sola vez. Para ello, usaremos la nueva versión del script en MQL5, que puede verse a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Basic script for SQL database written in MQL5"
04. #property version   "1.00"
05. #property script_show_inputs
06. //+------------------------------------------------------------------+
07. #include <Market Replay\SQL\C_DB_SQL.mqh>
08. //+------------------------------------------------------------------+
09. input string user01 = "DataBase01";       //Database File Name
10. input string user02 = "Script 01.sql";    //SQL Script File Name
11. //+------------------------------------------------------------------+
12. void OnStart()
13. {
14.     C_DB_SQL *SQL;
15.     string szMsg;
16.     
17.     SQL = new C_DB_SQL(user01);
18.     
19.     szMsg = (*SQL).ExecScriptSQL(user02);
20.     Print(szMsg == NULL ? "Result of executing the SQL script: Success" : szMsg);
21.     
22.     delete SQL;
23. }
24. //+------------------------------------------------------------------+

Código fuente del script MQL5

Creo que nadie tendrá problemas para entender el código anterior, dada su extrema simplicidad. Como resultado, en caso de éxito, verás en el terminal de MetaTrader 5 el resultado que se muestra en la imagen inferior.

Si existe alguna falla, puedes probarlo modificando el script SQL para provocar un error en él. Obtendrás el resultado que se muestra en la imagen inferior.

Fíjate en cómo funciona todo. Usando un solo script SQL, acabamos de probar íntegramente toda la clase C_DB_SQL. Para garantizar que podremos tanto enviar comandos mediante la llamada ExecCommandSQL como ejecutar un script SQL usando la llamada ExecScriptSQL. Y todo esto probado de una sola vez. Este tipo de cosas que acabamos de hacer es lo que todo programador intenta hacer. Es decir, creamos un string de rutinas en la que cada una, individualmente, es relativamente corta y muy concreta. Y intentamos hacer que ejecuten una tarea muy específica, pero que, de alguna manera, haga que todas se ejecuten y, al mismo tiempo, se prueben. Cuando realmente consigues hacer algo así, aumentarás exponencialmente la velocidad de programación, porque pasarás a necesitar probar menos rutinas, o a probarlas con menor frecuencia, ya que todas se ejecutarán con bastante frecuencia, eliminando así posibles fallos mucho más rápido.

Muy bien. Ya terminamos la parte inicial. Pero muchos quizá estén pensando que esto de usar un archivo externo es un verdadero tormento. Ya que siempre tendremos que asegurarnos de que el usuario no lo cambie de directorio. O, como mínimo, de que el usuario no llegue a editar ni modificar el script SQL que será necesario ejecutar.

Debo reconocer, en teoría, que estoy de acuerdo contigo, mi querido lector. Sin embargo, discrepo en una cuestión. No necesitas usar necesariamente un archivo externo. Puedes integrar este archivo en el ejecutable creado en MQL5. Y, de esta forma, tener la garantía de que el usuario no modificará ni manipulará el script SQL que deba ejecutarse. Además, tendrás la garantía de que la sintaxis del script SQL será correcta. Como dije en el artículo anterior, colocar los comandos SQL en un string dentro del ejecutable es muy arriesgado. Ya que puedes terminar, sin darte cuenta, escribiendo algo incorrecto. Y solo notarás realmente el fallo mucho tiempo después, cuando, muchas veces, la base de datos ya se encuentre en una etapa de uso intensivo.

De esta forma, la mejor solución es crear el script SQL en un editor, para que la sintaxis pueda analizarse fácilmente. Guardar el script en un archivo y tomar ese archivo e integrarlo en el ejecutable que más adelante utilizará algún usuario. Existen diversas formas de hacer este tipo de cosas. Algunas más simples y otras un poco más complejas. Pero aquí, usando MQL5, existe una manera bastante simple y eficaz de integrar el script SQL, o cualquier otro tipo de archivo, en el ejecutable final.

La forma de hacer esto se verá en el siguiente tema. Lo haré así para separar las cosas. De esta manera, no habrá confusión entre lo que vimos hasta aquí y lo que veremos a partir de este momento.


Integrando el script SQL en el ejecutable

Lo primero que hay que hacer es garantizar que el script SQL que se integrará en el ejecutable realmente funcione. Prueba esto usando el propio SQL. No uses el código que ejecutará el script dentro del ejecutable final. Aunque parezca una tontería, debes tener la garantía de que el script realmente funciona. Y, para hacerlo, deberás ejecutarlo antes que nada dentro del entorno SQL. En este punto, hay algo a lo que debes prestar atención. No sé si leíste los artículos anteriores de esta serie sobre cómo construir un sistema de repetición/simulador. Esto se debe a que, a lo largo de algunos artículos de esta secuencia, expliqué un poco cómo trabajar con SQL para que tú, querido lector, si no tenías el conocimiento necesario sobre el tema, pudieras aprender un poco sobre el entorno.

Como había dicho en aquel momento, de verdad no debes pensar que usar SQLite será diferente de usar otra implementación de SQL. Sí, existen algunas pocas diferencias. Pero, en general, si entiendes SQL, podrás desarrollar lo que se hará, o lo que deba hacerse, usando SQLite dentro de MetaTrader 5. Esto incluso a partir de código probado en MySQL o SQL Server, por ejemplo. No hay grandes diferencias, siempre que, por supuesto, entiendas realmente lo que se está haciendo.

Una vez que hayas comprobado que el script funciona, guárdalo y colócalo en algún lugar dentro del directorio MQL5. Para ejemplificar, usaremos el script que venimos utilizando desde el artículo anterior. Por lo tanto, estará en la misma ubicación indicada en el artículo anterior. Dicho esto, primero vamos a modificar el archivo de encabezado C_DB_SQL.mqh para poder ejecutar el script. Esto se debe a que, a diferencia de leer un archivo externo, cuando usamos el script como un recurso dentro del ejecutable, las cosas suelen hacerse de otra manera. De este modo, el nuevo código del archivo de encabezado puede verse a continuación.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Service Graphics\Support\C_Array.mqh"
005. //+------------------------------------------------------------------+
006. class C_DB_SQL
007. {
008.     private    :
009.         C_Array    m_Arr;
010.         int        m_handleDB;
011. //+------------------------------------------------------------------+
012.         void Convert(const char &buff[], const int size)
013.         {
014.             string sz0 = "";
015.             bool b0, b1, bs1, bs2, bc0, bc1, bc;
016.             int nLine = 1;
017.             
018.             b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false;
019.             for (int count = 0, nC0 = nLine; count < size; count++)
020.             {
021.                 switch (buff[count])
022.                 {
023.                     case '\t':
024.                         sz0 += (bs1 || bs2 ? "\t" : "");
025.                         break;
026.                     case '\n':
027.                         nC0++;
028.                     case '\r':
029.                         bc0 = false;
030.                         break;
031.                     case ';':
032.                         b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true);
033.                     default:
034.                         switch (buff[count])
035.                         {
036.                             case '"':
037.                                 bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1);
038.                                 break;
039.                             case '\'':
040.                                 bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2);
041.                                 break;
042.                         }
043.                         if (((count + 1) < size) && (!bs1) && (!bs2))
044.                         {
045.                             if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true;
046.                             if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true;
047.                             if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false;
048.                             if (bc)
049.                             {
050.                                 count += 1;
051.                                 bc = false;
052.                                 continue;
053.                             }
054.                         }
055.                         if (!(bc0 || bc1))
056.                         {
057.                             if ((!b1) && (buff[count] > ' '))
058.                             {
059.                                 b1 = true;
060.                                 nLine = nC0;
061.                             }
062.                             sz0 += (b1 ? StringFormat("%c", buff[count]) : "");
063.                         }
064.                 }
065.                 if (b0)
066.                 {
067.                     m_Arr.Add(sz0, nLine);
068.                     sz0 = "";
069.                     b0 = b1 = false;
070.                 }
071.             }
072.         }
073. //+------------------------------------------------------------------+
074.         const string ExecSQL(void)
075.         {
076.             string szCmd;
077.             
078.             for (int count = 0, nLine; count >= 0; count++)
079.             {
080.                 szCmd = m_Arr.At(count, nLine);
081.                 if (nLine < 0) break;
082.                 if (!ExecCommandSQL(szCmd))
083.                     return StringFormat("Execution of line %d of the SQL script failed...", nLine);
084.             }
085.             
086.             return NULL;
087.         }
088. //+------------------------------------------------------------------+
089.     public    :
090. //+------------------------------------------------------------------+
091.         C_DB_SQL(const string szFileName = ":memory:")
092.         {
093.             m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE);
094.         }
095. //+------------------------------------------------------------------+
096.         ~C_DB_SQL()
097.         {
098.             DatabaseClose(m_handleDB);
099.         }
100. //+------------------------------------------------------------------+
101.         const string ExecResourceSQL(const string szResource)
102.         {
103.             char buff[];
104.             int size;
105.             
106.             ArrayResize(buff, size = StringLen(szResource));
107.             StringToCharArray(szResource, buff);
108.             Convert(buff, size);
109.             ArrayFree(buff);
110.             
111.             return ExecSQL();
112.         }
113. //+------------------------------------------------------------------+
114.         const string ExecScriptSQL(const string szFileName)
115.         {
116.             int file, size;
117.             char buff[];
118.             
119.             if ((file = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE)
120.                 return StringFormat("Unable to open script file: %s", szFileName);
121.             ArrayResize(buff, size = (int) FileSize(file));
122.             FileReadArray(file, buff);                
123.             FileClose(file);
124.             Convert(buff, size);
125.             ArrayFree(buff);
126.             
127.             return ExecSQL();
128.         }
129. //+------------------------------------------------------------------+
130.         bool ExecCommandSQL(const string szCmd)
131.         {
132.             return (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szCmd));
133.         }
134. //+------------------------------------------------------------------+
135. };
136. //+------------------------------------------------------------------+

Código fuente de C_DB_SQL.mqh

Observa que hicimos un pequeño cambio. El primero se encuentra en la función ExecScriptSQL, donde, en la línea 127, eliminamos el código que antes se ejecutaba allí. Lo trasladamos a otro lugar. Puedes comprobarlo mirando la versión anterior, que aparece en el tema anterior. Observa que el mismo código que antes estaba aquí, en la función ExecScriptSQL, ahora está en la función ExecSQL. Esta se encuentra en la línea 74 y es una función privada de la clase. Es decir, no podrás llamar a este procedimiento fuera del cuerpo de la clase. Esto se debe a que, fuera del cuerpo de la clase C_DB_SQL, esta función ExecSQL no tiene ningún sentido. Pero aquí sí tiene pleno sentido.

Como el código presente en esta función ya se había probado anteriormente, sabemos que la función ExecScriptSQL seguirá funcionando de la misma manera. Esto es algo que, en el mundo de la programación orientada a objetos, se conoce como encapsulación. Es decir, cambiamos la forma interna de implementar la función ExecScriptSQL, pero, para cualquier código externo que use la clase C_DB_SQL, no habrá cambiado nada. Todo seguirá igual que antes.

Ahora viene la parte interesante. Como separamos la parte responsable de ejecutar realmente los comandos SQL, como si vinieran de un archivo externo que contiene un script SQL, podemos usar este mismo procedimiento ExecSQL para ejecutar algo colocado directamente dentro del ejecutable final. En este caso, será el archivo de script SQL. Pero las cosas no se limitan a eso. Podemos hacer mucho más que eso.

Sin embargo, para no complicar demasiado la explicación, ya que, si piensas un poco, acabarás entendiendo que podemos hacer mucho más de lo que voy a explicar, no veo necesidad de dar muchos detalles ni pormenores sobre el alcance de lo que acabamos de hacer. No obstante, observa la línea 101. Allí tenemos una función que se parece mucho a la función ExecScriptSQL. Pero aquí decidí llamarla ExecResourceSQL para que no entrara en conflicto con la versión anterior de ExecScriptSQL.

Ahora veamos el interior de esta función de la línea 101. Observa que comenzamos declarando dos variables y que, en la línea 106, reservamos espacio en memoria. Ese espacio tendrá el tamaño del string que vamos a recibir. Presta atención a esto. El código estará en un string, igual que ocurría en el caso del archivo. Solo que aquí el string que se proporcionará no vendrá de un archivo, al menos no directamente. Después entenderás mejor esto. Entonces, como el string no viene de un archivo en forma binaria, necesitamos convertirlo en un array binario. Así, usamos la línea 107, que convierte el string en su representación como un gran array de caracteres.

En este punto, estamos ante algo equivalente a lo que ocurriría si hiciéramos la lectura mediante archivo. Eso representa exactamente lo que sucede en la línea 122. Pues bien, una vez que ya tenemos el array de caracteres, lo siguiente que hay que hacer es convertir ese array en instrucciones SQL. Esto se hace en la línea 108. Después, devolvemos la memoria al sistema operativo, en la línea 109, y, en la línea 111, hacemos exactamente lo mismo que cuando la información del script SQL venía de un archivo externo. Es decir, llamamos a la función ExecSQL, que se encuentra en la línea 74.

Por eso dije que necesitas garantizar que el script funcione. Porque, a diferencia del archivo externo, donde puedes seguir modificándolo hasta hacer que las cosas funcionen, aquí, cuando usamos esta función ExecResourceSQL, no existe esa posibilidad. Esto se debe a que los datos ya estarán, de alguna manera, dentro del ejecutable, en forma de un gran string, equivalente al archivo externo.

Bien. Pero, ¿cómo haremos para usar esta función ExecResourceSQL? ¿Tendremos que copiar y pegar todo el contenido presente en el archivo de script SQL? En realidad, incluso podrías hacerlo. Pero sería un trabajo tedioso y completamente innecesario. Podemos hacerlo mejor. Vamos a pedirle a MQL5 que copie el archivo por nosotros. Así, si necesitamos modificar algo, lo modificaremos y, en el momento en que volvamos a compilar el ejecutable final, el propio MQL5 garantizará que todo funcione adecuadamente. De ese modo, el archivo correcto se integrará en el ejecutable de la mejor manera posible, y siempre con el contenido más actual, independientemente del tiempo que haya pasado. MQL5 hará el trabajo pesado por nosotros.

Así, para obtener esta facilidad al usar MQL5, deberás modificar el archivo principal como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Basic script for SQL database written in MQL5"
04. #property version   "1.00"
05. #property script_show_inputs
06. //+------------------------------------------------------------------+
07. #resource "\\Files\\Script 01.sql" as string SQL_01
08. //+------------------------------------------------------------------+
09. #include <Market Replay\SQL\C_DB_SQL.mqh>
10. //+------------------------------------------------------------------+
11. input string user01 = "DataBase01";        //Database File Name
12. //+------------------------------------------------------------------+
13. void OnStart()
14. {
15.     C_DB_SQL *SQL;
16.     string szMsg;
17.     
18.     SQL = new C_DB_SQL(user01);
19.     
20.     szMsg = (*SQL).ExecResourceSQL(SQL_01);
21.     Print(szMsg == NULL ? "Result of executing the SQL script: Success" : szMsg);
22.     
23.     delete SQL;
24. }
25. //+------------------------------------------------------------------+

Código fuente del script MQL5

Observa la línea siete en el código anterior. Fíjate en que en ella estamos diciendo que queremos incorporar un recurso al código del ejecutable final.

Ahora, la parte a la que realmente tú, querido lector, debes prestar atención es a lo que viene después del nombre entre comillas dobles (" "). Ahí es donde se encuentra uno de los trucos, o formas, de usar recursos en MQL5 y, además, de hacer otras cosas.

Puede que incluso pienses que esta línea siete es una tontería. Pero, para el compilador y, en consecuencia, para todo el código, esta línea siete está definiendo una constante de tipo string llamada SQL_01, cuyo contenido es exactamente lo que se encuentra dentro del archivo indicado entre comillas dobles. En resumen: sería como si tú, manualmente, tomaras todo el código contenido en el archivo, que en este caso es el Script 01.sql, y lo colocaras dentro de este código. Pero no lo pondrías en una variable, sino en una constante de tipo string, cuyo nombre sería SQL_01.

No sé si de verdad lograste entender lo que acabo de decir. Pero, y aquí es donde el compilador de MQL5 realmente nos ayudará, si modificas el contenido del archivo Script 01.sql y lo mantienes en el mismo directorio esperado por este código principal, en el momento en que vuelvas a compilar el código, el compilador de MQL5 buscará el contenido presente en el archivo Script 01.sql y lo pondrá a disposición como un string constante cuyo nombre será SQL_01.

Este tipo de cosas nos facilita bastante la vida. En caso de que el archivo Script 01.sql no exista en la ubicación indicada, el compilador de MQL5 emitirá un error durante la compilación, indicando que el archivo Script 01.sql no existe en el lugar indicado. Sin embargo, una vez que el código haya sido compilado, ya no necesitarás preocuparte por el archivo Script 01.sql. Esto se debe a que estará integrado dentro del ejecutable.

Entonces, en el momento en que se ejecute la línea 20 del código anterior, el programa tomará el string constante SQL_01 y lo transferirá a la función ExecResourceSQL, de modo que se interprete como si fuera el código de un archivo binario recién leído. Así es como ocurre la magia.

No tiene sentido que tengas que escribir comandos y más comandos para hacer cosas que estarían en un archivo de script. Usa directamente el archivo de script. Si algo sale mal, podrás encontrar el fallo con facilidad, sin necesidad de recurrir a todo el trabajo de analizar strings y más strings dentro de un código, buscando errores, ya sean de escritura o por cualquier otro motivo, pero que estén haciendo que SQL se comporte de forma distinta de lo que esperabas que SQL ejecutara realmente.

Tal vez este sea uno de los motivos por los que muchos no se interesan en aprender SQL. Precisamente porque, si fueras a hacer las cosas como todos suelen hacerlas, tendrías un trabajo enorme entre manos. En códigos pequeños y simples, el trabajo quizá no sería tan grande. Pero, a medida que el código se vuelve cada vez más complejo, la cosa puede convertirse en una pesadilla. O, al menos, en un trabajo enorme, que acaba desanimándonos a seguir adelante y a hacer las cosas de la forma más eficaz posible.


Consideraciones finales

Al mirar el código de la clase C_DB_SQL, puede que te preguntes algo: ¿por qué necesitamos tres funciones diferentes, y aparentemente iguales, para ejecutar código SQL? ¿No tendría más sentido tener una sola función? Yo mismo llegué a plantearme eso. ¿De verdad no habría una forma de hacer esta clase C_DB_SQL un poco más simple? Pero, cuando llevamos el código para que otros lo analicen, terminamos viendo algo. Aunque tenemos tres funciones, aparentemente iguales o muy parecidas, destinadas a ejecutar código SQL, en realidad no son tan iguales.

Esto se debe a que cada una de ellas está orientada a cubrir una situación determinada. Pero, de cualquier manera, el código está disponible de forma abierta y gratuita. Tú, como programador, decidirás qué debe hacerse y qué no. Aquí, la intención es siempre mostrar una de tantas soluciones posibles. Así que siéntete libre de realizar las modificaciones necesarias para cubrir tu caso específico.

Sin embargo, en este artículo todavía no cubrimos otra parte relacionada con SQL. Esa parte a la que me refiero es precisamente la obtención de las respuestas que devuelve SQL durante una consulta realizada por nuestro código escrito en MQL5. Esta parte será realmente muy interesante de explicar y abordar, ya que tenemos distintas formas de tratar la misma cuestión. No obstante, como aquí, en este momento, estaremos centrados en usar SQLite y dado que estará integrado en MetaTrader 5, la forma en que accederemos a los resultados de las consultas en la base de datos se tratará en el próximo artículo.

Pero lo que voy a mostrar allí no es, en absoluto, la única ni la definitiva forma de hacer las cosas. Podemos hacerlas de muchas maneras diferentes, como ya se hizo en este artículo. Así que procura siempre estudiar y aprender cosas nuevas.

ArchivoDescripción
Experts\Expert Advisor.mq5
Demuestra la interacción entre Chart Trade y el Asesor Experto (es necesario el Mouse Study para la interacción)
 Indicators\Chart Trade.mq5Crea la ventana para configurar la orden a ser enviada (es necesario el Mouse Study para la interacción)
 Indicators\Market Replay.mq5Crea los controles para la interacción con el servicio de reproducción/simulador (es necesario el Mouse Study para la interacción)
 Indicators\Mouse Study.mq5Permite la interacción entre los controles gráficos y el usuario (necesario tanto para operar el sistema de repetición/simulador como en el mercado real)
 Services\Market Replay.mq5Crea y mantiene el servicio de repetición y simulación de mercado (archivo principal de todo el sistema)
 Code VS C++\Servidor.cppCrea y mantiene un socket servidor desarrollado en C++ (versión MiniChat)
 Code in Python\Server.pyCrea y mantiene un socket en Python para la comunicación entre MetaTrader 5 e Excel
 Indicators\Mini Chat.mq5Permite implementar un minichat mediante un indicador (requiere el uso de un servidor para funcionar)
 Experts\Mini Chat.mq5Permite implementar un minichat mediante un Asesor Experto (requiere el uso de un servidor para funcionar)
 Scripts\SQLite.mq5Demuestra el uso de un script SQL mediante MQL5
 Files\Script 01.sqlDemuestra la creación de una tabla simple, con clave foránea
 Files\Script 02.sqlDemuestra la adición de valores en una tabla

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

Archivos adjuntos |
Anexo.zip (571.71 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Del básico al intermedio: Objetos (III) Del básico al intermedio: Objetos (III)
En este artículo veremos cómo podemos implementar un sistema de interacción muy atractivo e interesante, sobre todo para quienes están empezando a practicar programación en MQL5. No se trata de algo realmente nuevo. La forma en que abordaré el tema hará que todo sea mucho más fácil de entender, ya que veremos, en la práctica, cómo se desarrolla una programación estructural con un objetivo bastante divertido.
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.
Simulación de mercado (Parte 23): Iniciando SQL (VI) Simulación de mercado (Parte 23): Iniciando SQL (VI)
En este artículo, exploraremos cómo realizar la visualización y, en consecuencia, entender cómo está estructurada una base de datos. Esto se hizo al observar el diagrama interno de la base de datos. Aunque este tipo de cosa parezca algo innecesario, puede ser bastante válido si tú pretendes, de hecho, convertirte en un administrador de bases de datos. Y sí, hay personas que viven de hacer mantenimiento y creación de bases de datos.