Simulación de mercado: Iniciando SQL en MQL5 (IV)
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 (III), mostré cómo puedes incrustar el archivo de script SQL en un ejecutable creado dentro de MQL5. Sin embargo, no nos limitamos a eso. También introdujimos algunos cambios en el código de la clase para que resultara mucho más fácil de usar y, por supuesto, ajustamos ligeramente la forma de implementar el código.
Ahora bien, aunque el código de esa clase, presentado en el artículo anterior, es muy útil para generar una base de datos utilizando SQLite integrado en MetaTrader 5 (sobre lo cual hablaré más adelante), dicho código no nos permite de ningún modo ejecutar consultas desde MQL5.
Por ello, el objetivo principal de este artículo es explicar del modo más claro posible cómo puedes modificar esa clase para añadir el código necesario para ejecutar consultas. En otras palabras, aquí implementaremos un sistema con el que podremos consultar la base de datos basada en SQLite.
Antes de ver cómo hacerlo, necesitamos comprender algo que resulta muy, pero muy importante cuando utilices lo que muestro en esta serie. Precisamente por cuestiones como la que voy a exponer todavía no estamos empleando SQLite junto con el código del sistema de repetición/simulador, ya que queremos explicar y dejar muy claro cómo funciona realmente el SQLite que viene integrado en MetaTrader 5.
Muchos podrían pensar que el SQLite incluido en MetaTrader 5 funciona igual que la versión a la que se accede mediante una DLL. Aunque ambos funcionen, en esencia, de la misma forma, existen algunas particularidades en el uso del SQLite integrado en MetaTrader 5. Tal vez los desarrolladores cambien esto en el futuro; aun así, no debes dar nada por hecho. En caso de duda, prueba primero y, después, busca cómo resolver cualquier problema que pueda surgir al utilizar el SQLite integrado en una aplicación concreta. No estoy diciendo que el problema resida en una u otra aplicación. Te digo que pruebes las cosas antes de asumir que el SQLite utilizado no es el que esperabas.
Entendiendo el problema
El problema, en el momento de escribir este artículo, se presenta en la versión 5.00 Build 3815 del 22 de junio de 2023. Sí, este código se creó hace bastante tiempo. Cuando leas estas líneas, puedo asegurarte que MetaTrader 5 ya habrá sido actualizado y todo esto estará obsoleto. Aun así, no ignores el contenido de estos artículos, porque pueden enseñarte algo que quizá no imagines posible.
Bien, vayamos al problema. Revisa el script SQL que se muestra a continuación.
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
Este script SQL crea un conjunto de tablas en el que existe una relación entre la tabla tb_Symbols y la tabla tb_Quotes. Esto queda garantizado por la clave externa definida en la línea 17. Si no sabes a qué me refiero, revisa los artículos anteriores de esta serie para comprender lo que explicaremos dentro de un momento.
Ahora bien, fíjate en las líneas 3 y 4 de ese mismo script SQL. Lo que hacen es eliminar las tablas del archivo de la base de datos SQL. Si lo ejecutas en MetaTrader 5 o mediante una DLL de SQLite, obtendrás el mismo resultado. Sin embargo, no siempre. El motivo es precisamente la línea 17, que crea una relación entre los valores presentes en tb_Symbols y tb_Quotes.
Un momento, ¿cómo que no siempre obtendremos el mismo resultado? ¿Acaso SQLite no es el mismo tanto en MetaTrader 5 como cuando lo usamos a través de una DLL? ¿Por qué dices que la ejecución de este script ofrece resultados distintos? Tal vez me haya expresado mal o, como mínimo, haya hablado antes de explicar los detalles. En realidad, la ejecución produce el mismo resultado; no obstante, cuando existe algún valor que establece la relación entre tb_Symbols y tb_Quotes, el SQLite integrado en MetaTrader 5 no consigue eliminar las tablas y se genera un error, algo que puedes confirmar con la siguiente prueba.
Ejecuta el script que aparece a continuación dos veces seguidas. La primera se completará sin errores. Pero, cuando lo ejecutes por segunda vez, obtendrás la imagen que acompaña al 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. ); 19. 20. INSERT INTO tb_Symbols (id, symbol) VALUES (1, 'PETR4'); 21. 22. INSERT INTO tb_Quotes (of_day, price, fk_id) VALUES ('2023-07-10', '22.00', 1);
Código en SQLite

Observa que, en menos de tres segundos entre una ejecución y otra, los resultados fueron completamente diferentes. En el primer intento el script SQL se ejecutó correctamente; inmediatamente después, al ejecutar el mismo script, la operación falló. El código de error reportado indica con claridad que el problema se debe a la relación entre las tablas, relación que surge precisamente en la línea 17 del script.
Sin embargo, el error no se debió directamente a la línea 17. Observa la imagen superior: el código de nuestra clase, pensado para ejecutar scripts SQL, informa que el problema surgió cuando la línea 3 se envió a SQLite para su ejecución.
Aun así, quiero subrayar algo. Si envías este mismo script SQL para que lo ejecute SQLite de forma independiente, el código se ejecutará y no aparecerá ningún error. Lo mostré al presentar cómo programar en SQL; si tienes dudas, repasa los artículos anteriores, donde lo demuestro con claridad.
Tal vez pienses que el problema está en el SQLite integrado en MetaTrader 5, pero no pretendo avivar la polémica. Es posible que esta versión integrada incorpore un mecanismo de seguridad que impida eliminar tablas con claves externas activas. Desconozco la causa exacta de esta aparente asimetría entre SQLite autónomo y el que viene con MetaTrader 5. Y no es la única diferencia: también notarás discrepancias, por ejemplo, al intentar crear un gatillo mediante el SQLite integrado.
Por eso no te culparía si decides usar SQLite vía DLL. Sin embargo, según mis pruebas, con el SQLite integrado podemos hacer mucho sin recurrir a la DLL; si la asimetría se vuelve demasiado grande, buscaremos otra vía. De momento podemos seguir trabajando con la versión integrada.
Así que, si utilizas el SQLite integrado en MetaTrader 5 y necesitas eliminar las tablas de la base de datos, lo más sencillo es borrar el archivo de la base. Cuando el script incrustado en el ejecutable se ejecute de nuevo, no encontrará conflictos.
Muy bien. Si todo lo anterior quedó claro, pasemos al siguiente paso: la parte interesante empieza ahora. Te recomiendo concentrarte al máximo en la explicación.
En el artículo Simulación de mercado (Parte 25): Iniciando SQL en MQL5 (I) mencioné que solo necesitamos seis funciones de las disponibles en MQL5 para trabajar con SQL. En los artículos previos ya vimos tres de ellas, que ya están implementadas en la clase C_DB_SQL. Ahora ha llegado el momento de conocer y entender por qué solo necesitamos otras tres funciones. Para separar bien la explicación, abordaremos un nuevo tema a continuación.
Las últimas tres funciones necesarias
Con lo que explicaré a continuación no pretendo que te sientas menospreciado por no haberlo comprendido antes, tanto si ya usas bases de datos desde MQL5 como si estás empezando. Simplemente mostraré la forma en que yo lo hago: no es la más correcta ni la menos adecuada, solo mi manera de proceder.
Cuando afirmo que basta con seis funciones de MQL5 para acceder a SQL, no digo que estés equivocado si empleas más funciones o procedimientos. Al contrario, admiro a quienes exploran a fondo un lenguaje o una herramienta. Mi enfoque consiste en usar lo mínimo imprescindible.
No suelo memorizar cada detalle de todas las funciones de cada lenguaje o herramienta que utilizo; para hacerlo necesitaría tiempo, y a menudo el plazo disponible es limitado. Prefiero cumplir el objetivo dentro del plazo, independientemente de la dificultad.
Para comprobar si mi planteamiento es válido, modificaremos de nuevo el script de la base de datos. Te propongo hacerlo de otro modo para evitar rutinas poco flexibles: crea un script nuevo y llámalo script 02.sql. Si lo prefieres, añade su contenido al final de script 01.sql; obtendrás exactamente el mismo resultado.
El único detalle, si sigues mi sugerencia, es que deberás retocar ligeramente el archivo principal. Como no todo el mundo sabe dónde intervenir, me centraré en esa solución y mostraré cómo hacerlo para obtener el resultado correcto.
El objetivo aquí es insertar datos en la base para poder efectuar consultas más adelante. Observa los scripts SQL siguientes.
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 en SQLite
01. INSERT INTO tb_Symbols (id, symbol) VALUES
02. (2, 'PETR4'),
03. (1, 'ITUB3'),
04. (3, 'VALE3');
05.
06. INSERT INTO tb_Quotes (of_day, price, fk_id) VALUES
07. ('2023-07-10', '22.00', 1),
08. ('2023-07-11', '22.20', 1),
09. ('2023-07-12', '22.40', 1),
10. ('2023-07-13', '22.30', 1),
11. ('2023-07-14', '22.60', 1),
12. ('2023-07-10', '26.00', 2),
13. ('2023-07-11', '26.20', 2),
14. ('2023-07-12', '26.40', 2),
15. ('2023-07-13', '26.30', 2),
16. ('2023-07-14', '26.60', 2),
17. ('2023-07-10', '62.00', 3),
18. ('2023-07-11', '62.20', 3),
19. ('2023-07-12', '62.40', 3),
20. ('2023-07-13', '62.30', 3),
21. ('2023-07-14', '62.60', 3);Código en SQLite
Aunque son dos, puedes combinar su contenido y ejecutarlo con el archivo principal en MQL5 ya mostrado. Si decides mantenerlos separados, tendrás que modificar el código principal de MQL5 como indico más abajo.
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_Create 08. #resource "\\Files\\Script 02.sql" as string SQL_Insert 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\SQL\C_DB_SQL.mqh> 11. //+------------------------------------------------------------------+ 12. input string user01 = "DataBase01"; //Database File Name 13. //+------------------------------------------------------------------+ 14. C_DB_SQL *SQL; 15. //+------------------------------------------------------------------+ 16. const string ExecScripts(void) 17. { 18. string szMsg = (*SQL).ExecResourceSQL(SQL_Create); 19. if (szMsg != NULL) return szMsg; 20. return (*SQL).ExecResourceSQL(SQL_Insert); 21. }; 22. //+------------------------------------------------------------------+ 23. void OnStart() 24. { 25. string szMsg; 26. 27. SQL = new C_DB_SQL(user01); 28. 29. szMsg = ExecScripts(); 30. Print(szMsg == NULL ? "Result of executing the SQL script: Success" : szMsg); 31. 32. delete SQL; 33. } 34. //+------------------------------------------------------------------+
Código en MQL5
Verás que todo es muy sencillo, práctico y directo. El primer script SQL crea las tablas que necesitaremos; el segundo inserta datos en la base con algunos valores. Como estos mismos scripts ya aparecieron antes, seguramente sabes qué hace cada uno y cómo colaboran para generar los registros que vamos a crear.
Bien, ahora mira el código en MQL5. Observa que añadimos la línea 8 y cambiamos el nombre del recurso que existía por otro más apropiado. Debido a ciertos detalles en la línea 29, llamamos a la función situada en la línea 16. Su propósito es ejecutar los dos scripts SQL, que en realidad serán recursos internos del ejecutable final generado por MQL5. Primero, en la línea 18, solicitamos la ejecución del script SQL que crea las tablas. Después, en la línea 19, comprobamos el resultado. Si el primer script SQL se ejecuta correctamente, ejecutamos el segundo en la línea 20.
Este segundo script SQL tiene la misión de insertar datos en la base. Es algo sencillo y sin grandes dificultades de ejecución. Con esto ya contamos con lo necesario para la siguiente fase de la implementación: desarrollar la parte que realizará consultas dentro de la base de datos SQL desde código MQL5. Pero antes revisemos el contenido del archivo de la base de datos; para ello podemos utilizar cómodamente MetaEditor, como se muestra a continuación.

Verás que el contenido esperado se añadió efectivamente a la base. Con esa certeza, pasemos al siguiente paso. Regresemos al archivo de cabecera C_DB_SQL.mqh. Recuerda que, en su estado original, la clase definida allí no puede devolver ningún resultado de consulta. Aunque es posible lanzar comandos SELECT, como explicamos antes de empezar con el código MQL5, de poco sirve si no podemos usar los resultados directamente en el propio MQL5.
De forma muy acertada, los desarrolladores de MQL5 ofrecen varias funciones para obtener los resultados de consultas en bases de datos SQL. Una vez más, lo que veremos a continuación parte de que usarás el SQLite integrado en MetaTrader 5. Si usas SQLite a través de una DLL, el procedimiento cambia ligeramente; quizá más adelante explique cómo hacerlo. Por ahora trabajaremos con el SQLite integrado. Del mismo modo, cuando se accede a la base mediante sockets se requiere otro enfoque. No los confundas: cada situación necesita su propia solución, aunque el resultado final pueda parecer el mismo.
Muy bien, dicho esto, podemos centrarnos en el código en sí. Necesitamos lograr que las consultas se ejecuten y que, cuando sea necesario, sus resultados puedan interpretarse con facilidad. Para ello, necesitamos usar dos funciones nuevas. La primera es DatabasePrepare, cuya finalidad es ejecutar algún tipo de consulta en la base de datos. Observa que no debemos usar DatabaseExecute, como muchos podrían suponer o considerar. El motivo es que DatabaseExecute solo nos indica si el comando SQL se ejecutó correctamente o no. No nos ofrece ninguna forma de ver qué devuelve SQL cuando hacemos una consulta.
Por eso usamos DatabasePrepare, ya que esta función nos permite ver qué devolverá SQL a nuestra aplicación en MQL5. Esa es la primera parte, y la primera función que necesitamos. Además de ella, necesitaremos otra función, cuyo objetivo es leer los datos devueltos por SQL. Pero antes veamos cómo se modificó el código de la clase para incorporar DatabasePrepare. El código de la clase se muestra 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. m_Request; 012. //+------------------------------------------------------------------+ 013. void Convert(const char &buff[], const int size) 014. { 015. string sz0 = ""; 016. bool b0, b1, bs1, bs2, bc0, bc1, bc; 017. int nLine = 1; 018. 019. b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false; 020. for (int count = 0, nC0 = nLine; count < size; count++) 021. { 022. switch (buff[count]) 023. { 024. case '\t': 025. sz0 += (bs1 || bs2 ? "\t" : ""); 026. break; 027. case '\n': 028. nC0++; 029. case '\r': 030. bc0 = false; 031. break; 032. case ';': 033. b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true); 034. default: 035. switch (buff[count]) 036. { 037. case '"': 038. bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1); 039. break; 040. case '\'': 041. bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2); 042. break; 043. } 044. if (((count + 1) < size) && (!bs1) && (!bs2)) 045. { 046. if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true; 047. if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true; 048. if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false; 049. if (bc) 050. { 051. count += 1; 052. bc = false; 053. continue; 054. } 055. } 056. if (!(bc0 || bc1)) 057. { 058. if ((!b1) && (buff[count] > ' ')) 059. { 060. b1 = true; 061. nLine = nC0; 062. } 063. sz0 += (b1 ? StringFormat("%c", buff[count]) : ""); 064. } 065. } 066. if (b0) 067. { 068. m_Arr.Add(sz0, nLine); 069. sz0 = ""; 070. b0 = b1 = false; 071. } 072. } 073. } 074. //+------------------------------------------------------------------+ 075. const string ExecSQL(void) 076. { 077. string szCmd; 078. 079. for (int count = 0, nLine; count >= 0; count++) 080. { 081. szCmd = m_Arr.At(count, nLine); 082. if (nLine < 0) break; 083. if (!ExecCommandSQL(szCmd)) 084. return StringFormat("Execution of line %d of the SQL script failed...", nLine); 085. } 086. 087. return NULL; 088. } 089. //+------------------------------------------------------------------+ 090. public : 091. //+------------------------------------------------------------------+ 092. C_DB_SQL(const string szFileName = ":memory:") 093. { 094. m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE); 095. m_Request = INVALID_HANDLE; 096. } 097. //+------------------------------------------------------------------+ 098. ~C_DB_SQL() 099. { 100. DatabaseClose(m_handleDB); 101. } 102. //+------------------------------------------------------------------+ 103. const string ExecResourceSQL(const string szResource) 104. { 105. char buff[]; 106. int size; 107. 108. ArrayResize(buff, size = StringLen(szResource)); 109. StringToCharArray(szResource, buff); 110. Convert(buff, size); 111. ArrayFree(buff); 112. 113. return ExecSQL(); 114. } 115. //+------------------------------------------------------------------+ 116. const string ExecScriptSQL(const string szFileName) 117. { 118. int file, size; 119. char buff[]; 120. 121. if ((file = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE) 122. return StringFormat("Unable to open script file: %s", szFileName); 123. ArrayResize(buff, size = (int) FileSize(file)); 124. FileReadArray(file, buff); 125. FileClose(file); 126. Convert(buff, size); 127. ArrayFree(buff); 128. 129. return ExecSQL(); 130. } 131. //+------------------------------------------------------------------+ 132. bool ExecCommandSQL(const string szCmd) 133. { 134. return (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szCmd)); 135. } 136. //+------------------------------------------------------------------+ 137. bool ExecRequestOfData(const string szCmd) 138. { 139. if (m_Request != INVALID_HANDLE) DatabaseFinalize(m_Request); 140. return ((m_Request = DatabasePrepare(m_handleDB, szCmd)) != INVALID_HANDLE); 141. } 142. //+------------------------------------------------------------------+ 143. }; 144. //+------------------------------------------------------------------+
Código de C_DB_SQL.mqh
Observa que añadimos una nueva variable en la línea 11. Esta será la variable utilizada para leer los datos que SQL nos devuelva en una consulta. Como toda variable, se inicializará en la línea 95. Fíjate bien en el valor que estamos utilizando. Ahora pasemos a la línea 137, donde se implementa la función ExecRequestOfData. Esta función es la que usaremos para enviar el comando de consulta a SQL. Normalmente, este comando implica el uso de SELECT FROM, pero, como verás más adelante, aquí esto puede ser bastante específico.
De todos modos, observa atentamente que, en la línea 139, comprobamos si la variable m_Request tiene un valor distinto de INVALID_HANDLE. Si esto se confirma, significa que tenemos una consulta anterior en caché. Es decir, queremos descartar los datos de una consulta anterior para poder realizar una nueva. Para ello usamos otra función: DatabaseFinalize. Su finalidad es descartar los datos de una consulta anterior y, de este modo, constituye la tercera función que necesitaremos en esta fase de ampliación de la clase C_DB_SQL.
Una vez finalizada la consulta anterior, en caso de que existiera, podemos enviar en la línea 140 la solicitud de una nueva consulta. Observa que no necesitamos indicar el nombre de la base de datos. Esto se hizo en el momento en que se creó la clase. Todo lo que necesitamos es indicar qué comando debe utilizarse, y este vendrá del autor de llamada, que veremos después. Si la consulta se ejecuta correctamente, DatabasePrepare devolverá un valor numérico. Este se almacenará en la variable m_Request para su uso posterior. Si la consulta falla, se devolverá INVALID_HANDLE. Y, en consecuencia, la función ExecRequestOfDate devolverá false, indicando al autor de llamada que se produjo un fallo en la consulta solicitada.
Muy bien, pero ahora tenemos un detalle: ¿por qué no devolvemos directamente los datos aquí, en la función ExecRequestOfData? ¿Por qué devolvemos solo si la consulta tuvo éxito o no? El motivo es pura simplicidad. Tal vez, por un motivo u otro, quieras hacer una consulta que devuelva determinados valores o campos de la tabla. Y, según lo que haya en un campo u otro, podríamos tomar direcciones o decisiones diferentes. Eso nos obligaría a hacer una nueva consulta en la base de datos. Aunque esto no sea incorrecto, hace que la base de datos, sobre todo en el caso de una base de datos cliente-servidor, sea consultada innecesariamente.
Podemos obtener todos los campos necesarios en una sola consulta. Esto, además de optimizar las consultas SQL sobre la base, también nos ayuda a agilizar un poco nuestro código principal. Por esta razón, la lectura de los datos se coloca en otra función, dedicada únicamente a este propósito.
Esto puede parecer una auténtica tontería, e incluso poco práctico. Pero ten en cuenta que podrás adaptar muy fácilmente el código principal para leer un campo concreto, precisamente porque la consulta estará separada de la lectura del resultado obtenido. Esto puede parecer algo muy complicado de explicar, ya que, cuando hacemos una consulta en SQL, podemos obtener varios valores devueltos usando solo una de las columnas de una de las tablas. Tal como se mostró en la animación anterior de este mismo artículo.
Pero, si la consulta está bien planteada y orientada correctamente, muchas veces obtendrás un único resultado. Aunque también puede que no obtengas ninguno. Esto se debe a que los criterios utilizados en la consulta no se encontraron en la base de datos.
Aquí surge un hecho que pocos llegan a comprender de verdad. Y antes de mostrar la función de lectura de datos, me gustaría aprovechar la oportunidad para explicarlo, porque puede que el tema te interese.
Muchos suelen infrautilizar SQL, o incluso no utilizarlo, porque no comprenden bien cómo funciona en realidad. Cuando consultamos una base de datos SQL, no siempre buscamos una respuesta genérica. En algunos casos, podemos estar buscando una respuesta muy objetiva y práctica. Si tú creas una base de datos con cierta estructuración y modelado, podrás introducir prácticamente cualquier tipo de información en ella. Esto es bastante curioso, ya que muchas veces la gente suele pensar en una base de datos como si fuera solo un conjunto de números y caracteres alfanuméricos destinado a almacenar datos de ventas, clientes, productos o cosas similares.
Pero, si entiendes adecuadamente cómo funciona SQL y cómo hacer las cosas con él, podrás crear un sistema o, mejor dicho, un pequeño robot capaz de aprender a operar un símbolo determinado. Esto sin que necesites crear un modelo operativo. Simplemente introduces una serie de cotizaciones en la base de datos y, usando SQL para consultar, junto con el propio MQL5 para ejecutar los cálculos que SQL no puede realizar, creas un mecanismo capaz de aprender de forma totalmente automática cómo operar en el mercado. Ese mecanismo aprenderá por sí solo a crear una forma de prever posibles movimientos futuros de un símbolo determinado.
Mencioné esto hace algún tiempo, en esta misma secuencia sobre el sistema de repetición/simulador. Tal vez no hayas visto, o no hayas notado, hasta dónde podemos llegar con este sencillo sistema que estoy mostrando cómo crear. Pero, si lo estudias adecuadamente, podrás crear ese mecanismo que mencioné.Claro que necesitarás usar algunas técnicas matemáticas y de programación para obtener el resultado adecuado. Y de ahí viene el motivo por el que no devolvemos directamente el resultado de una consulta en la base de datos SQL cuando la ejecutamos desde MQL5, como se muestra en el código anterior.
Puede ocurrir que esa consulta se haya hecho de tal manera que podamos usar los resultados de una forma completamente distinta de la que muchos esperarían. Como los desarrolladores de MQL5 nos permitieron usar ese mecanismo, no creo prudente cambiar la forma en que funciona. Y, como explicar cómo se hará esa lectura es algo un tanto complejo de resumir en pocas palabras, dejaré para el próximo artículo tanto esa explicación como los cambios que habrá que hacer en el código principal.
Consideraciones finales
En este artículo hablé de cómo podemos hacer algunas cosas. Mencioné el hecho de que puedes crear un robot capaz de aprender a operar sin que necesites crear un modelo operativo para ello. Hablé del problema que existe cuando intentamos usar algunas funciones de SQL directamente en el SQLite integrado en MetaTrader 5 y mostré cómo puedes sortearlo. Pero, a pesar de todo, no mostré cómo puede obtenerse realmente la lectura de los resultados de una consulta usando el propio MQL5. El motivo es que podemos crear una función que se adapte automáticamente a lo que se espera como resultado de una consulta. Esto para que puedas comprender por qué dije que solo necesitamos seis funciones que MQL5 nos permite usar cuando trabajamos con SQL.
Un último detalle: MQL5 ya cuenta con mecanismos para usar modelado ONNX. El modelado ONNX es mucho más adecuado que SQL para crear un robot capaz de aprender a operar en el mercado por sí solo. Pero nada te impide usar un modelado basado en SQL con ese propósito. Todo es cuestión de aprender y aplicar el conocimiento de la manera correcta.
| 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/13110
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 en objetos (I)
Del básico al intermedio: Objetos (IV)
Del básico al intermedio: Eventos en Objetos (II)
Determinación de los tipos de cambio justos en PPA usando los datos del FMI
- 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