Simulación de mercado: iniciando SQL en MQL5 (II)
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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Del básico al intermedio: Eventos de mouse
Herramientas de trading de MQL5 (Parte 1): Creación de una herramienta interactiva de asistencia para operaciones con órdenes pendientes
Particularidades del trabajo con números del tipo double en MQL4
Simulación de mercado: Iniciando SQL en MQL5 (I)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso