Português
preview
Simulación de mercado: iniciando SQL en MQL5 (II)

Simulación de mercado: iniciando SQL en MQL5 (II)

MetaTrader 5Probador |
40 0
Daniel Jose
Daniel Jose

Introducción

Hola a todos y bienvenidos a otro 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 (I), empezamos a utilizar SQL junto con código MQL5. Aunque muchos creen que podemos incluir sin problemas código SQL dentro de otro código, por lo general esto no es así. El motivo es que el código SQL siempre se incorpora al ejecutable como un string. Y este hecho de colocar el código SQL como string, si bien no genera inconvenientes en fragmentos pequeños, puede terminar provocándonos bastantes dolores de cabeza.

Esto sucede porque, si mientras transcribes el código SQL a un string cometes algún error de escritura, probablemente no te darás cuenta. No existe una forma sencilla y eficaz de verificar visualmente si el string —es decir, el código SQL— está realmente correcto, ya que la sintaxis, al quedar dentro de una string, no aparece resaltada con colores distintos.

Este tipo de inconveniente puede resultarnos muy molesto. Yo mismo pasé malos ratos intentando entender por qué un código SQL no funcionaba correctamente dentro de un ejecutable. Tras revisar el código con calma, me topé con un pequeño error tipográfico que impedía a SQL establecer las relaciones entre las tablas como debía. Perdí bastante tiempo solo para averiguar la causa y, cuando finalmente la encontré, sentí que podría haber aprovechado mejor ese tiempo. Desde entonces, cuando desarrollo códigos más complejos, trabajo de una manera totalmente distinta.

Y ese es el objetivo de este artículo: mostrarte, estimado lector, una alternativa para utilizar código SQL junto a un ejecutable creado en MQL5. Si de verdad piensas emplear SQL en MetaTrader 5, te recomiendo considerar seriamente el método que voy a presentar; incluso podrás adaptarlo a otras necesidades. Si pretendes realizar algo realmente elaborado con SQL, evita incrustar el código directamente en strings dentro del ejecutable. Las probabilidades de que algo falle y luego pierdas mucho tiempo averiguando por qué son enormes. Si, en cambio, el código es corto y sencillo, no veo motivos de preocupación.


Comenzando a usar scripts SQL

Para empezar a utilizar scripts SQL dentro del ejecutable escrito en MQL5, lo primero es comprender la sintaxis de SQL. Si estudias cómo se construyen los comandos, verás que todos terminan en punto y coma (;). Esto ya nos sugiere cuál será el delimitador que debemos buscar para capturar cada comando completo en una línea de SQL.

Aquí surge algo curioso. Aunque solemos escribir los comandos SQL en varias líneas, en realidad se trata de un único string largo. El comando empieza al principio de la línea y termina precisamente en el punto y coma (;). Sin embargo, leerlo así puede resultar confuso, porque en algunos casos es muy extenso. Para SQL esto da igual: siempre considerará que el comando es una sola línea. Eso ya indica lo que debemos hacer.

Lo siguiente que debemos abordar son los comentarios. No es raro que un script SQL incluya muchos comentarios, algo muy útil cuando el archivo es largo o contiene secciones que requieren atención. Como no queremos perder tiempo enviando al intérprete partes que no procesará, tendremos que eliminar esos comentarios antes de enviar el comando a SQL.

El comentario de final de línea, marcado con dos guiones (--), es sencillo de gestionar. El de varias líneas, en cambio, puede complicarnos, porque dentro del bloque podrían aparecer palabras que SQL interpretaría como comandos. Y eso es justo lo que queremos evitar, así que tendremos que tratar este caso de forma explícita.

Hay otro inconveniente. En SQL también existen strings y, si dentro de un string aparecen los indicadores de comentario, estos no deben interpretarse como inicio o fin del comentario. Esa situación requiere un tratamiento aparte y puede resultar confusa para quien está empezando a programar. Para facilitar el aprendizaje, dejaremos la gestión de comentarios para más adelante; por ahora, asumiremos que los scripts SQL no contienen comentarios, así evitamos complicar el trabajo que haremos en MQL5.

Empecemos con lo siguiente: en el artículo anterior creamos una clase para manejar SQL. Esa clase aún está en una fase temprana, pero podemos aprovecharla para crear otra clase responsable de gestionar el script. ¿Cómo lo haremos? Parece complicado, pero, si has seguido mis artículos, ya sabes que siempre intento plantear la solución más sencilla posible, y esta vez no será distinto. Lo primero será separar la clase de SQL del archivo principal, algo que se consigue fácilmente como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_DB_SQLite
05. {
06.     private   :
07.         int    m_handleDB;
08.     public    :
09. //+------------------------------------------------------------------+
10.         C_DB_SQLite(const string szFileName)
11.             :m_handleDB(INVALID_HANDLE)
12.         {
13.             if ((m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE)) == INVALID_HANDLE)
14.             {
15.                 Print("Unable to create or open the file ", szFileName);
16.                 return;
17.             }
18.         }
19. //+------------------------------------------------------------------+
20.         ~C_DB_SQLite()
21.         {
22.             DatabaseClose(m_handleDB);
23.             Print("Closing Database...");
24.         }
25. //+------------------------------------------------------------------+
26.         bool Command(const string szRequestSQL)
27.         {
28.             bool ret = (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szRequestSQL));
29.             Print("Request execution: ", (ret ? "Success" : "Failed"), "...");
30.             return ret;
31.         }
32. //+------------------------------------------------------------------+
33. };
34. //+------------------------------------------------------------------+

Código fuente de C_DB_SQLite.mqh

El código que acabas de ver era el que residía en el archivo principal. Es decir, quitamos del script en MQL5 la parte correspondiente a la clase y creamos un archivo de cabecera llamado C_DB_SQLite.mqh, que guardamos en la carpeta Include\Market Replay\SQL. De esta forma, cualquier script futuro que necesite aplicar los mismos principios de trabajo con SQL podrá hacerlo mediante ese archivo de cabecera. Con este cambio, el antiguo script MQL5 quedó 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. #include <Market Replay\SQL\C_DB_SQLite.mqh>
08. //+------------------------------------------------------------------+
09. input string user01 = "DataBase01";        //FileName
10. //+------------------------------------------------------------------+
11. void OnStart()
12. {
13.     C_DB_SQLite *DB;
14.     
15.     DB = new C_DB_SQLite(user01 + ".sqlite");
16.     
17.     (*DB).Command("PRAGMA FOREIGN_KEYS = ON;");    
18.     (*DB).Command("CREATE TABLE IF NOT EXISTS tb_Symbols"
19.                   "("
20.                     "id PRIMARY KEY,"
21.                     "symbol NOT NULL UNIQUE"
22.                   ");");
23.     (*DB).Command("CREATE TABLE IF NOT EXISTS tb_Quotes"
24.                       "("
25.                    "of_day NOT NULL,"
26.                    "price NOT NULL,"
27.                    "fk_id NOT NULL,"
28.                    "FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id)"
29.                  ");");    
30.     delete DB;
31. }
32. //+------------------------------------------------------------------+

Código fuente del script en MQL5

Fíjate en la línea siete: allí añadimos un #include que permite al script acceder al código del archivo de cabecera como si estuviese escrito directamente en el archivo principal. Así es como iremos organizando el proyecto con varios archivos de cabecera. Observa también que en esa misma línea indico la ubicación del archivo, exactamente la que mencioné hace un momento. No hizo falta escribir el nombre de la carpeta Include, porque MQL5 busca los archivos de cabecera en ese directorio por defecto.

El archivo no tiene por qué estar obligatoriamente allí, pero no entraremos en detalles para evitar confusiones; lo recomendable es dejar cada cosa en su sitio. Puede parecer trivial, sin embargo algunas personas no conseguían que el código compilase y tuve que explicárselo más de una vez, sin saber si realmente lo comprendieron.

Como este ejemplo es muy sencillo, lo uso para ilustrar cómo funcionan los archivos de cabecera. Disculpa si resulta obvio: quiero que todo el mundo entienda el proceso. Dado que decidí no adjuntar los archivos a los artículos, los entusiastas deben saber cómo obtener el código completo y colocar cada archivo en la ruta correcta para generar el ejecutable final.

Ya disponemos de la estructura base que necesitamos para trabajar con scripts SQL guardados en un archivo aparte. Conviene mencionar algo importante: aunque la idea inicial sea mantener el script SQL en un archivo independiente, puedes, mediante directivas de compilación, incrustar ese archivo dentro del ejecutable generado en MQL5. Me parece una solución mucho más limpia que llenar el código MQL5 de strings con SQL. Pero antes de ver cómo lograrlo, vamos a preparar el manejo de ese script SQL para que el código MQL5 lo utilice. Para empezar, crearemos una nueva clase, como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_DB_SQLite.mqh"
05. //+------------------------------------------------------------------+
06. class C_ScriptSQL : private C_DB_SQLite
07. {
08.     private   :
09.     public    :
10. //+------------------------------------------------------------------+
11.         C_ScriptSQL(const string szFileScript, const string szFileDataBase)
12.             :C_DB_SQLite(szFileDataBase)
13.             {
14.             }
15. //+------------------------------------------------------------------+
16.         ~C_ScriptSQL()
17.             {
18.             }
19. //+------------------------------------------------------------------+
20.         bool Execute(void)
21.             {
22.                 return false;
23.             }
24. //+------------------------------------------------------------------+
25. };
26. //+------------------------------------------------------------------+

Código Inicial del archivo C_ScriptSQL.mqh

Bien. Veamos qué tenemos aquí al principio. Observa que, en la línea cuatro, se declara una directiva #include. Esta nos indica que estamos incluyendo el archivo C_DB_SQLite.mqh en este archivo de cabecera, al que llamaremos C_ScriptSQL. Ahora presta atención al siguiente detalle. Es algo sutil, pero importante. Observa que el nombre C_DB_SQLite.mqh aparece entre comillas dobles. Esto indica que el archivo C_ScriptSQL que estamos creando debe estar en el mismo directorio que el archivo C_DB_SQLite.mqh.

Siempre que encuentres en un código MQL5 o C/C++ lo mismo que ves en esta línea cuatro, debes pensar en colocar o buscar el archivo actual en una ruta relativa al archivo indicado en la #include. Es decir, podemos navegar entre carpetas distintas simplemente indicando eso en la #include. Por lo tanto, este archivo C_ScriptSQL.mqh deberá guardarse en la carpeta Include\Market Replay\SQL, que es la misma carpeta del archivo C_DB_SQLite.mqh. Si se hace así, el código compilará perfectamente.

Ahora veamos algunas cosas que pueden parecer un poco complicadas. Observa que, en la línea seis, declaramos la clase C_ScriptSQL, y esta hereda de forma privada de la clase C_DB_SQLite. El motivo de hacerlo así se entenderá mejor más adelante. Debido a esta herencia, necesitamos definir el constructor para inicializar la clase C_DB_SQLite. Por eso tenemos la línea 11, donde declaramos el constructor de la clase C_ScriptSQL. Observa que aquí recibiremos dos parámetros. Entonces, para inicializar el constructor de C_DB_SQLite, usamos la línea 12 y pasamos uno de los parámetros al constructor de la clase C_DB_SQLite, para que la clase quede inicializada. El otro parámetro lo usaremos después.

Como quiero dejar la clase ya preparada, en la línea 16 declaro su destructor. Y, en la línea 20, un procedimiento que usaremos en el código principal. Pero, como este procedimiento debe devolver algún valor, ya declaro en la línea 22 que el valor devuelto es falso. Es decir, si llego a probar el código antes incluso de que esté terminado, me aseguro de que cualquier procedimiento que deba devolver un valor devuelva siempre false.

Bien, pero ¿qué hará realmente esta clase C_ScriptSQL? La idea es que esta clase reciba el nombre del archivo del script SQL y el nombre de la base de datos, y ejecute el script sobre la base de datos, quizás incluso permitiéndonos devolver algún valor si el script realiza alguna consulta. Por ahora, la idea es quitar el código que puedes ver en el código principal de MQL5. Ese código SQL puede observarse al revisar el script en MQL5.

Como hay muchas cosas por hacer, debemos dividir los problemas en tareas e ir implementando la solución poco a poco, tarea por tarea, hasta obtener lo que realmente necesitamos. Así, la primera tarea consiste en quitar el script SQL del código principal escrito en MQL5 y guardar ese script en un archivo en disco. Como MQL5, por seguridad, no nos permite acceder a archivos fuera de su directorio, necesitamos guardar el archivo en una ubicación accesible y fácil de usar. Por simplicidad, lo dejaré en el directorio MQL5\Files. Es decir, en el mismo directorio que usaremos para guardar nuestra base de datos de forma predeterminada. Así, guardaremos el archivo con un nombre específico y, para diferenciarlo, utilizaremos la extensión .SQL. De este modo nace el archivo que se muestra a continuación.

01. PRAGMA FOREIGN_KEYS = ON;
02. 
03. CREATE TABLE IF NOT EXISTS tb_Symbols
04. (
05.     id PRIMARY KEY,
06.     symbol NOT NULL UNIQUE
07. );
08. 
09. CREATE TABLE IF NOT EXISTS tb_Quotes
10. (
11.     of_day NOT NULL,
12.     price NOT NULL,
13.     fk_id NOT NULL,
14.     FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id)
15. );

Código fuente del script en SQL

En la siguiente imagen puedes verlo resaltado, junto con su ubicación.

Perfecto. Ya tenemos lo que necesitamos para empezar. Fíjate en el nombre que le di al archivo del script SQL, porque lo usaremos dentro de poco. Ahora observa lo que mencioné antes: cada comando termina en ( ; ) en el script SQL. Por lo tanto, vamos a implementar el sistema para poder ejecutar exactamente el script SQL mostrado antes. En este momento no vamos a preocuparnos por los comentarios, ya que, como puedes ver, no hay ninguno en el script SQL. Así, el código que debe colocarse en el archivo C_ScriptSQL puede verse a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_DB_SQLite.mqh"
05. #include "..\Service Graphics\Support\C_Array.mqh"
06. //+------------------------------------------------------------------+
07. class C_ScriptSQL : private C_DB_SQLite
08. {
09.     private    :
10.         C_Array    m_Arr;
11.         char       m_Buff[];
12.         int        m_Size;
13. //+------------------------------------------------------------------+
14.         void Convert(void)
15.         {
16.             string sz0 = "";
17.             bool b0 = false, b1 = false;
18.             int nLine = 1;
19.             
20.             for (int count = 0, nC0 = nLine; count < m_Size; count++)
21.             {
22.                 switch (m_Buff[count])
23.                 {
24.                     case '\t':
25.                         break;
26.                     case '\n':
27.                         nC0++;
28.                     case '\r':
29.                         break;
30.                     case ';':
31.                         b0 = true;
32.                     default:
33.                         if ((!b1) && (m_Buff[count] > ' '))
34.                         {
35.                             b1 = true;
36.                             nLine = nC0;
37.                         }
38.                         sz0 += (b1 ? StringFormat("%c", m_Buff[count]) : "");
39.                 }
40.                 if (b0)
41.                 {
42.                     m_Arr.Add(sz0, nLine);
43.                     sz0 = "";
44.                     b0 = b1 = false;
45.                 }
46.             }
47.         }
48. //+------------------------------------------------------------------+
49.     public    :
50. //+------------------------------------------------------------------+
51.         C_ScriptSQL(const string szFileScript, const string szFileDataBase)
52.             :C_DB_SQLite(szFileDataBase),
53.              m_Size(-1)
54.         {
55.             int handle;
56.                 
57.             if ((handle = FileOpen(szFileScript, FILE_READ | FILE_BIN)) == INVALID_HANDLE)
58.             {
59.                 Print("Unable to open script file: ", szFileScript);
60.                 return;
61.             }
62.             ArrayResize(m_Buff, m_Size = (int) FileSize(handle));
63.             FileReadArray(handle, m_Buff);
64.             FileClose(handle);
65.             Convert();
66.         }
67. //+------------------------------------------------------------------+
68.         ~C_ScriptSQL()
69.         {
70.             ArrayFree(m_Buff);
71.         }
72. //+------------------------------------------------------------------+
73.         bool Execute(void)
74.         {
75.             int nLine;
76.             string szInfo;
77.                 
78.             for (int c = 0; c >= 0; c++)
79.             {
80.                 szInfo = m_Arr.At(c, nLine);
81.                 if (nLine > 0)
82.                     Print(nLine, ">>", szInfo);
83.                 else break;
84.             }
85.             return false;
86.         }
87. //+------------------------------------------------------------------+
88. };
89. //+------------------------------------------------------------------+

Código de C_ScriptSQL.mqh

Observa que el código ya ha crecido bastante. Pero aquí ya tenemos implementado casi todo lo que necesitamos. Aun así, hagamos un repaso rápido para entender qué tenemos aquí. En la línea cinco indicamos que necesitamos usar un archivo de cabecera. Ese archivo ya existe en el código original del sistema de repetición/simulador. Debes buscar este archivo de cabecera en los artículos anteriores de esta serie. Si has seguido los artículos y has ido actualizando el código como vengo mostrando, no tendrás de qué preocuparte, porque ya tendrás el archivo correcto que necesitamos.

Muy bien, en las líneas 10 a 12 tenemos algunas variables privadas de esta clase. En la línea 14, en cambio, tenemos un procedimiento cuyo objetivo es volcar el contenido de la variable m_Buff en un array de líneas. Este procedimiento es bastante interesante, ya que hará todo el trabajo pesado por nosotros. Básicamente, aquí recorreremos el búfer carácter por carácter para capturar una línea de comando SQL.

Observa que cada case comprobará la presencia de algún carácter. En la línea 24 comprobamos el carácter de tabulación. Por ahora, este carácter simplemente se ignorará. En la línea 26 comprobamos la presencia del carácter de nueva línea. Cuando se encuentra este carácter, se incrementa el conteo de líneas, algo que se hace en la línea 27. Observa que, en la línea 28, analizamos la presencia del carácter de retorno de carro.

Ambos caracteres, tanto el de retorno de carro como el de nueva línea, reciben un tratamiento común en la línea 29. Sin embargo, solo el de nueva línea recibe un tratamiento distinto, porque incrementa el conteo de líneas. Si el editor que uses para crear el script SQL no utiliza el carácter de nueva línea, algo poco habitual, tendrás que usar el carácter de retorno de carro para incrementar el conteo de líneas. De todos modos, no creo que sea necesario cambiar este punto.

Ya en la línea 30 tenemos exactamente el carácter que esperábamos para indicar el final de un comando SQL. Observa que allí usamos una variable para indicar que se encontró un comando. Pero, como ese mismo carácter también debe estar presente cuando enviemos el comando a SQL, no cerramos su bloque con un break. Así, entrará en el bloque default. Este bloque se encargará de cualquier carácter que no se haya tratado antes. Veamos entonces qué ocurre aquí.

En la línea 33 comprobamos si se encontró algún carácter distinto del espacio. Esto se debe a que no necesitamos espacios al comienzo del comando SQL. Cuando aparezca un carácter distinto del espacio, lo marcaremos en la línea 35 y, justo después, anotaremos el número de la línea en la que empieza el comando SQL.

En la línea 38 capturamos el carácter del buffer. Pero hay un detalle. El string del comando solo recibirá algún carácter si ya hay alguno presente en el comando. De lo contrario, esperará hasta que aparezca algún carácter. Para terminar, en la línea 40, cuando tengamos la indicación de que un comando se capturó por completo, se almacenará y se reinicializará el proceso para buscar el siguiente comando SQL.

En cuanto al constructor y al destructor, en mi opinión tienen códigos bastante simples, así que no hace falta comentarlos de forma especial. Sin embargo, el código de la línea 73 contiene algunos puntos que merecen explicación. Vamos a verlos. Observa que, en la línea 78, tenemos un bucle for bastante extraño. Esto se debe a que empieza su conteo en cero y va incrementándose. Sin embargo, no es infinito, como muchos podrían pensar. Termina en el momento en que la variable se vuelva negativa. Y esto ocurrirá en algún momento, ya que el tipo de la variable es un entero con signo.

Pero, antes de que eso ocurra, tenemos en la línea 80 la forma de recuperar los comandos que almacenamos antes. Observa que, en la línea 81, comprobamos si la línea indicada es negativa. Cuando esto ocurra, se ejecutará la línea 83 y, con ello, finalizará el bucle de la línea 78. Si tenemos algo almacenado, lo imprimiremos en la línea 82. Y así es como funciona.

Observa que, si en lugar de imprimir el comando en el terminal lo enviáramos a la clase C_DB_SQLite, en este punto podríamos ejecutar el comando colocado en el archivo del script SQL. Algo realmente muy práctico. Pero, antes de ver esto en funcionamiento, veamos cómo quedó el código principal en MQL5. 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_ScriptSQL.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_ScriptSQL *SQL;
15.     
16.     SQL = new C_ScriptSQL(user02, user01);
17.     
18.     (*SQL).Execute();
19.     
20.     delete SQL;
21. }
22. //+------------------------------------------------------------------+

Código del script MQL5

Al ejecutar este archivo en terminal MetaTrader 5, obtendrás el resultado que se ve en la siguiente imagen.

Observa que se informa exactamente el número de la línea en la que empieza el comando SQL, así como el comando que capturó el sistema. Es decir, ya tenemos lo que queremos y, por tanto, podemos enviar los comandos a la clase C_DB_SQLite en lugar de imprimirlos en el terminal. Pero, antes, veamos cómo tratar el tema de los comentarios en el archivo del script SQL. Para poder tratar los comentarios permitidos en SQL, necesitamos volver al procedimiento Convert, incluido en la clase C_ScriptSQL, y hacer allí algunas modificaciones y añadidos.

Básicamente, el problema que tenemos entre manos es saber cuándo estamos, o no, dentro de un string. Parece algo simple. Sin embargo, cuando se trata de SQL, esto se complica un poco. El motivo es que SQL permite usar tanto comillas simples ( ' ) como comillas dobles ( " ). Y no hay forma de saber con certeza si el script usará comillas simples o dobles para delimitar los strings. Por eso, necesitamos cubrir ambos casos. Pero esa no es la parte más complicada. En realidad, el problema principal es otro. Aun así, veamos cómo quedó el código para poder cumplir el objetivo deseado. Esto puede verse a continuación.

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

Código fuente de C_ScriptSQL.mqh

Observa que el código es mucho más complejo. Por eso antes mostré cómo hacer las cosas, porque entender este código para tratar los comentarios es bastante más confuso si no entiendes el código anterior. Fíjate en que ahora se añadieron muchas más variables. Y que el carácter de tabulación, que antes se ignoraba, ya no se ignorará en todos los casos. Solo se ignorará en casos específicos, es decir, cuando no estemos dentro de un string. Cuando sí lo estemos, el carácter de tabulación se registrará.

También fue necesario añadir algo que puedes ver en la línea 31. Allí, cuando se encuentra una nueva línea, restablecemos el valor usado para indicar que estamos dentro de un comentario. Ahora observa que comprobamos si estamos dentro de un comentario o de un string cuando encontramos un carácter de punto y coma ( ; ). Esto es para evitar falsos finales de comando SQL. Observa también que, en la línea 36, añadimos un switch. Su función es manejar las comillas. ¿Pero por qué no dejé esto en el cuerpo principal? El motivo es que, si este tratamiento se hiciera en el cuerpo principal, tendríamos trabajo extra para mantener intacto el script SQL.

Pero en lo que realmente quiero que te fijes, estimado lector, es en el código de la línea 45. Porque es allí donde empezamos a analizar la presencia de comentarios en el código SQL. Cada una de las líneas comprueba una condición de comentario. Y todas tienen algo en común: la variable bc. Cuando esta variable sea verdadera, indicará que tenemos un comentario y que cualquier contenido deberá ignorarse. Veamos entonces cómo funciona esto. En la línea 47 comprobamos la presencia de un doble guion ( -- ), que en SQL indica un comentario hasta el final de la línea actual. En la línea 48 comprobamos el inicio de un comentario de varias líneas. Y en la línea 49 comprobamos si aparece el final de un comentario de varias líneas.

Todas esas comprobaciones son muy sencillas. Pero observa que siempre las ignoraremos cuando estemos dentro de un string. También ignoraremos la detección de strings cuando estemos dentro de un comentario. Esto hace, básicamente, que el script se lea y se limpie de todo lo que no sea un comando. Prueba a crear scripts SQL con comentarios y strings. Ejecuta el código del script MQL5 en MetaTrader 5 para comprobar si todo está como se espera. Observa cada detalle para asegurarte de que el código esté siendo capturado correctamente por esta rutina de la clase C_ScriptSQL. Esto se debe a que, una vez que este análisis se realice correctamente, podrás sustituir el código de la línea 100 por el que aparece a continuación. Esto hará que el script, en lugar de mostrarse en el terminal de MetaTrader 5, se ejecute realmente. De este modo, cumplimos nuestro objetivo.

099. //+------------------------------------------------------------------+
100.         bool Execute(void)
101.         {
102.             int nLine;
103.             string szInfo;
104.                 
105.             for (int c = 0; c >= 0; c++)
106.             {
107.                 szInfo = m_Arr.At(c, nLine);
108.                 if (nLine < 0) return c > 0;
109.                 if (!this.Command(szInfo)) return false;
110.             }
111.             return false;
112.         }
113. //+------------------------------------------------------------------+

Fragmento de C_ScriptSQL.mqh

No olvides actualizar también el archivo principal con el código que 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. #include <Market Replay\SQL\C_ScriptSQL.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_ScriptSQL *SQL;
15.     
16.     SQL = new C_ScriptSQL(user02, user01);
17.     
18.     Print("Result of executing the SQL script: ", (*SQL).Execute() ? "Success" : "Failed", "...");
19.     
20.     delete SQL;
21. }
22. //+------------------------------------------------------------------+

Código fuente del script en MQL5

Y, si quieres hacer muchas pruebas con el script SQL, actualízalo con lo que se muestra en el siguiente código.

01. PRAGMA FOREIGN_KEYS = ON;
02. 
03. DROP TABLE IF EXISTS tb_Symbols;
04. DROP TABLE IF EXISTS tb_Quotes;
05. 
06. CREATE TABLE IF NOT EXISTS tb_Symbols
07. (
08.     id PRIMARY KEY,
09.     symbol NOT NULL UNIQUE
10. );
11. 
12. CREATE TABLE IF NOT EXISTS tb_Quotes
13. (
14.     of_day NOT NULL,
15.     price NOT NULL,
16.     fk_id NOT NULL,
17.     FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id)
18. );

Código en SQLite


Consideraciones finales

En este artículo mostré cómo tú, con un poco de conocimiento de MQL5 y SQL, puedes hacer que el código SQL se ejecute mediante un script. O, mejor dicho, puedes usar un archivo que contiene el código SQL y, proporcionando la información adecuada, ejecutar exactamente el código contenido en ese archivo de script SQL.

Es cierto que muchos pueden considerar que usar un archivo externo es un problema. Esto se debe a que siempre tendrás que proporcionar al cliente el archivo SQL. Y tendrás que confiar en que el cliente no llegue a tocar ni modificar el código presente en el archivo SQL. Eso es, efectivamente, un problema si no sabes exactamente cómo manejar esa situación. Pero, aunque te haya gustado lo que viste aquí y te preocupe que alguien pueda tocar o modificar el código que SQL va a ejecutar, eso no debe ser algo de lo que tengas que preocuparte.

Si no sabes cómo incrustar este archivo, que contiene el código SQL, en el ejecutable creado por MQL5, no te pierdas el próximo artículo. Allí mostraré cómo hacerlo. Además, veremos todavía más cosas sobre cómo trabajar con SQL desde MQL5.

Archivo Descripció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.mq5 Crea la ventana para configurar la orden a ser enviada (es necesario el Mouse Study para la interacción)
 Indicators\Market Replay.mq5 Crea los controles para la interacción con el servicio de repetición/simulador (es necesario el Mouse Study para la interacción)
 Indicators\Mouse Study.mq5 Permite 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.mq5 Crea y mantiene el servicio de repetición y simulación de mercado (archivo principal de todo el sistema)
 Code VS C++\Servidor.cpp Crea y mantiene un socket servidor desarrollado en C++ (versión MiniChat)
 Code in Python\Server.py Crea y mantiene un socket en Python para la comunicación entre MetaTrader 5 e Excel
 Indicators\Mini Chat.mq5 Permite implementar un minichat mediante un indicador (requiere el uso de un servidor para funcionar)
 Experts\Mini Chat.mq5 Permite implementar un minichat mediante un Asesor Experto (requiere el uso de un servidor para funcionar)
 Scripts\SQLite.mq5 Demuestra el uso de un script SQL mediante MQL5
 Files\Script 01.sql Demuestra la creación de una tabla simple, con clave foránea
 Files\Script 02.sql Demuestra 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/13078

Archivos adjuntos |
Anexo.zip (571.71 KB)
Del básico al intermedio: Eventos de mouse Del básico al intermedio: Eventos de mouse
Este artículo es uno de esos en los que, definitivamente, no basta con ver el código y estudiarlo para entender qué ocurre. De hecho, es necesario crear una aplicación ejecutable y usarla en cualquier gráfico. Esto, para poder entender pequeños detalles que, de otro modo, son muy complicados de comprender. Como, por ejemplo, la combinación del teclado con el mouse para construir ciertos tipos de cosas.
Herramientas de trading de MQL5 (Parte 1): Creación de una herramienta interactiva de asistencia para operaciones con órdenes pendientes Herramientas de trading de MQL5 (Parte 1): Creación de una herramienta interactiva de asistencia para operaciones con órdenes pendientes
En este artículo, presentamos el desarrollo de una herramienta interactiva de asistencia para el trading en MQL5, diseñada para simplificar la colocación de órdenes pendientes en el mercado de divisas. Describimos el diseño conceptual, centrándonos en una interfaz gráfica de usuario (GUI) intuitiva que permite establecer visualmente en el gráfico los niveles de entrada, stop-loss y take-profit. Además, detallamos la implementación en MQL5 y el proceso de backtesting para garantizar la fiabilidad de la herramienta, sentando así las bases para las funciones avanzadas que se describen en las siguientes partes.
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: Iniciando SQL en MQL5 (I) Simulación de mercado: Iniciando SQL en MQL5 (I)
En este artículo, comenzaremos a explorar el uso de SQL dentro de un código MQL5. Veremos cómo podemos crear una base de datos. O, mejor dicho, cómo podemos crear un archivo de base de datos en SQLite, utilizando, para ello, recursos o procedimientos incluidos en el lenguaje MQL5. Veremos también cómo crear una tabla y, después, cómo crear una relación entre tablas mediante una clave primaria y una clave foránea. Todo esto usando, nuevamente, MQL5. Veremos lo sencillo que es crear un código que, en el futuro, podrá portarse a otras implementaciones de SQL, usando una clase que nos ayude a ocultar la implementación creada. Y, lo más importante de todo, veremos que, en diversos momentos, podemos correr el riesgo de que algo no salga bien al usar SQL. Esto se debe a que, dentro del código MQL5, un código SQL siempre se colocará dentro de una STRING.