English Русский 中文 Deutsch 日本語
preview
Implementación de módulos prácticos de otros lenguajes en MQL5 (Parte 01): Desarrollo de una biblioteca SQLite3 inspirada en Python

Implementación de módulos prácticos de otros lenguajes en MQL5 (Parte 01): Desarrollo de una biblioteca SQLite3 inspirada en Python

MetaTrader 5Sistemas comerciales |
27 0
Omega J Msigwa
Omega J Msigwa

Contenido


Introducción

¿Te ha pasado alguna vez que deseas tener uno o dos de tus módulos, bibliotecas, frameworks, etc. favoritos, presentes en otro lenguaje de programación distinto de MQL5, pero dentro de MQL5? Me pasa muy a menudo.

En la comunidad MQL5 hay una gran cantidad de desarrolladores con diferentes trayectorias en programación; algunos vienen del desarrollo web, como yo, otros del desarrollo de Android, y existen muchas más trayectorias en programación hoy en día. Esto significa que la mayoría de los programadores están familiarizados con diferentes lenguajes como JavaScript, Java, Python, C++, C#, por nombrar solo algunos.

En esos diferentes lenguajes, los programadores se encuentran con diferentes herramientas de programación (módulos), módulos útiles que simplemente queremos usar donde sea posible. Por ejemplo, me encanta utilizar el módulo NumPy de Python para realizar cálculos matemáticos, hasta tal punto que en una ocasión tuve que implementar una biblioteca similar en MQL5, en este artículo.

Si bien el intento de implementar un módulo, herramienta, frameworks, etc., de un lenguaje a otro (en este caso a MQL5) podría producir una funcionalidad y resultados ligeramente diferentes debido a la naturaleza distinta de los lenguajes de programación, tener una sintaxis o experiencia similar podría ser suficiente para que el desarrollo de productos en MQL5 sea fácil y una experiencia divertida para los desarrolladores familiarizados con diferentes lenguajes. Sin mencionar que, en el proceso, podríamos aprender información valiosa que podría consolidar nuestras habilidades de programación en general.

En esta nueva serie de artículos, no vamos a implementar todos los módulos de otros lenguajes, sino todos los módulos de otro lenguaje que resulten útiles en MQL5. Por ejemplo, módulos para cálculos matemáticos, almacenamiento de datos, análisis de datos, etc.

Empezando por el módulo sqlite3 que viene integrado en el lenguaje de programación Python.


¿Qué es el módulo sqlite3 de Python?

Empecemos por entender en qué consiste una base de datos SQLite:

Una base de datos SQLite es un motor de bases de datos SQL ligero, autónomo y sin servidor. Se utiliza ampliamente en aplicaciones que requieren una solución de almacenamiento de datos local, sencilla e integrada. Se trata de una base de datos basada en archivos que almacena (esquema, tablas, índices y datos) todo en un único archivo .sqlite o .db.

A diferencia de las bases de datos MySQL o PostgreSQL, que requieren cierta configuración, un servidor y ajustes de administración, SQLite funciona desde el primer momento sin necesidad de nada de eso, ya que lee y escribe directamente en el disco.

El lenguaje de programación MQL5 incluye funciones integradas para trabajar con una base de datos SQLite; estas funciones integradas son suficientes. Sin embargo, no son tan fáciles de usar en comparación con el módulo sqlite3 de Python.

Por ejemplo, al intentar crear una base de datos sencilla llamada «ejemplo» e introducir algunos datos en una tabla denominada usuarios, la tabla se crea automáticamente si aún no existe.

Uso del módulo sqlite de Python

import sqlite3

conn = sqlite3.connect("example.db")

cursor = conn.cursor()

cursor.execute("""
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        age INTEGER,
        email TEXT UNIQUE
    )
""") # Execute a query

try:    
    cursor.execute("INSERT INTO users (name, age, email) VALUES (?, ?, ?)", 
                   ("Bruh", 30, "bruh@example.com"))

    conn.commit() # commit the transaction | save the new information to a database
    
except sqlite3.DatabaseError as e:
    print("Insert failed:", e)

conn.close() # closing the database

Uso de funciones nativas de MQL5

void OnStart()
  {
//---
    
   int db_handle = DatabaseOpen("example.db", DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE);
   if (db_handle == INVALID_HANDLE)
     {
       printf("Failed to open a database. Error = %s",ErrorDescription(GetLastError()));
       return;
     }
    
    string sql =        
       "   CREATE TABLE IF NOT EXISTS users ("
       "   id INTEGER PRIMARY KEY AUTOINCREMENT,"
       "   name TEXT NOT NULL,"
       "   age INTEGER,"
       "   email TEXT UNIQUE"
       ")";
      
    if (!DatabaseExecute(db_handle, sql)) //Execute a sql query
      {
         printf("Failed to execute a query to the database. Error = %s",ErrorDescription(GetLastError()));
         return;
      }  
    
    if (!DatabaseTransactionBegin(db_handle)) //Begin the transaction
      {
         printf("Failed to begin a transaction. Error = %s",ErrorDescription(GetLastError()));
         return;
      }   
    
    sql = "INSERT INTO users (name, age, email) VALUES ('Bruh', 30, 'bruh@example.com')";
    
    if (!DatabaseExecute(db_handle, sql)) //Execute a query
      {
         printf("Failed to execute a query to the database. Error = %s",ErrorDescription(GetLastError()));
         return;
      }
     
   if (!DatabaseTransactionCommit(db_handle)) //Commit the transaction | push the changes to a database
      {
         printf("Failed to commit a transaction. Error = %s",ErrorDescription(GetLastError()));
         return;
      } 
    
    DatabaseClose(db_handle); //Close the database
 }

Todos estamos de acuerdo en que el uso de sqlite3 en Python ha hecho que nuestro código sea más limpio que si utilizáramos las funciones nativas de MQL5, que nos obligan a gestionar los errores, así como la información devuelta o añadida a la base de datos.

El módulo sqlite3 se encarga de una gran cantidad de pasos innecesarios a la hora de ejecutar un comando y gestionar transacciones, lo que facilita enormemente a los usuarios la obtención e inserción de datos en la base de datos SQLite sin demasiadas complicaciones.

Bueno, intentemos implementar un módulo similar en MQL5.


Conexión a una base de datos SQLite

El método connect de sqlite3 en Python crea una nueva base de datos si el nombre indicado para la misma no existe y devuelve un objeto de conexión que representa la conexión con la base de datos almacenada en el disco.

import sqlite3
con = sqlite3.connect("example.db")

En MQL5, esta conexión es similar al handle de la base de datos, por lo que, técnicamente, no necesitamos devolver un handle en nuestra biblioteca MQL5, ya que lo utilizaremos dentro de todas las funciones de nuestra clase.

class CSqlite3
  {
protected:

   int m_request;
   
public:
                     int m_db_handle;
 //... Other functions
 }

bool CSqlite3::connect(const string db_name, const bool common=false, const bool database_in_memory=false)
 {   
   int flags = DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE;

   if (common) //Open the database from the common folder
      flags |= DATABASE_OPEN_COMMON;

   if (database_in_memory) //Open the database in memory 
      flags |= DATABASE_OPEN_MEMORY;

   m_db_handle = DatabaseOpen(db_name, flags);
   
//---
   
   if (m_db_handle == INVALID_HANDLE)
     {
       printf("func=%s line=%d, Failed to open a database. Error = %s",__FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
       return false;
     }
      
   return true;
 }

MQL5 nos ofrece la opción de guardar los datos bien en la ruta de datos (carpeta) o bien en la ruta de datos común (carpeta). Cuando el argumento common se establece en true, la base de datos se leerá desde la ruta de datos común en lugar de desde una ruta de datos habitual.

También podemos ofrecer al usuario la opción de elegir si desea abrir una base de datos en memoria (RAM) o en disco, de modo que cuando la variable database_in_memory se establezca en true. La base de datos se creará en la memoria RAM en lugar de en el disco, y viceversa.

Estamos omitiendo un indicador, concretamente DATABASE_OPEN_READONLY, que sirve para abrir la base de datos en modo ReadOnly. La razón es sencilla: si no quieres escribir en una base de datos, no ejecutarás en absoluto las consultas «INSERT» en ella. ¿Te parece lógico? 


Ejecución de sentencias SQL

Esta es una de las funciones más importantes que utilizamos con frecuencia al trabajar con bases de datos SQLite. Esta función nos permite obtener información, insertar, actualizar, eliminar y modificar los valores de la base de datos, etc.

Esta función ejecuta sentencias y comandos SQL directamente en nuestra base de datos.

En sqlite3 para Python, la función execute() funciona a la perfección y sin complicaciones. Puede aceptar cualquier comando, ya sea para introducir o para obtener información, y sabe automáticamente cuándo y qué debe devolver y qué no.

En MQL5, disponemos de una función integrada denominada DatabaseExecute, que es similar a execute() en sqlite3 para Python. Ambas ejecutan una consulta en una base de datos o tabla determinada. Sin embargo, este método de MQL5 es adecuado para ejecutar todas las solicitudes, excepto aquellas que contengan la palabra clave «SELECT» para leer la información de la base de datos. 

Para leer eficazmente la información de la base de datos, utilizamos la función DatabasePrepare, ya que crea un identificador de solicitud que luego se puede ejecutar mediante DatabaseRead.

Para crear una función flexible similar en MQL5 que permita ejecutar funciones independientemente del tipo de consulta, debemos distinguir los tipos de consultas SQL y devolver la información adecuada con la consulta correcta.

execute_res_structure CSqlite3::execute(const string sql)
 {
   execute_res_structure res;   
   
   string trimmed_sql = sql;
   StringTrimLeft(trimmed_sql); //Trim the leading white space
   
   string first_word = trimmed_sql;
   
   // Find the index of the first space (to isolate the first SQL keyword)
   int space_index = StringFind(trimmed_sql, " ");
   if (space_index != -1)
      first_word = StringSubstr(trimmed_sql, 0, space_index); // Extract the first word from the query
   
   StringToUpper(first_word); //Convert the first word in the query to uppercase for comparison
   
   if (first_word == "SELECT")
    {
      // SELECT query – prepare and expect data 
      m_request = DatabasePrepare(m_db_handle, sql);
      res.request = m_request;
      
      return res;
    }
   else
    {
      // INSERT/UPDATE/DELETE – execute directly
       if (!m_transaction_active)
         {
            if (!this.begin())
               return res;
            else
                m_transaction_active_auto = true; //The transaction was started automatically by the execute function
         }
         
       ResetLastError();          
       if (!DatabaseExecute(m_db_handle, sql))
         {
            printf("func=%s line=%d, Failed to execute a query to the database. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
         }
    }
   
   return res;
 }

Al igual que la función «execute» en Python, todas las sentencias que no sean SELECT abren implícitamente una transacción mediante la función begin; esta transacción debe confirmarse antes de que los cambios se guarden en la base de datos.

Después de llamar a la función execute e introducir cierta información en la base de datos, debes llamar al método commit para guardar los cambios en la base de datos. Hablaremos de esta función más adelante en esta entrada.

Cuando se invoca la función con una instrucción o consulta que no sea SELECT, no devuelve ningún valor, ya que probablemente esté actualizando, modificando e insertando la información en la base de datos. Sin embargo, cuando se invoca la función con una consulta de tipo SELECT, devuelve una estructura de datos. Analicemos esta estructura con detalle.

1. El método `fetchone`

El módulo sqlite3 de Python permite devolver de forma dinámica toda la información recibida de la instrucción SQL, ya sea en su totalidad o parcialmente.

Empezaremos por la capacidad de recuperar una fila de información de la base de datos.

import sqlite3
conn = sqlite3.connect("example.db")

cursor = conn.cursor()
print(conn.execute("SELECT * FROM users").fetchone())

A pesar de que la instrucción SQL solicita toda la información disponible en la base de datos SQLite, la función `fetchone` impide que la función `execute` devuelva más de una fila de datos de la base de datos.

Base de datos:

Resultados:

(mcm-env) C:\Users\Omega Joctan\OneDrive\Desktop\MCM>C:/Anaconda/envs/mcm-env/python.exe "c:/Users/Omega Joctan/OneDrive/Desktop/MCM/sqlite_test.py"
(1, 'Alice', 30, 'alice@example.com')

Necesitamos una función similar en nuestra clase MQL5, dentro de la estructura denominada execute_res_structure, que devuelve la función execute.

struct execute_res_structure
  {
   int request;
   CLabelEncoder le;
      
   vector fetchone()
      {
         int cols = DatabaseColumnsCount(request);         
         vector row = vector::Zeros(cols);
         
         while (DatabaseRead(request))  // Essential, read the entire database
          {
            string row_string = "(";  
            
            for (int i = 0; i < cols; i++)
             {
               int int_val; //Integer variable
               double dbl_val; //double      
               string str_val; //string variable
      
               ENUM_DATABASE_FIELD_TYPE col_type = DatabaseColumnType(request, i);
               string col_name;
      
               if (!DatabaseColumnName(request, i, col_name))
                {
                  printf("func=%s line=%d, Failed to read database column name. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                  continue;
                }
      
                 switch (col_type) //Detect a column datatype and assign the value read from every row of that column to the suitable variable
                  {
                     case DATABASE_FIELD_TYPE_INTEGER:
                        if (!DatabaseColumnInteger(request, i, int_val))
                           printf("func=%s line=%d, Failed to read Integer. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {
                             row_string += StringFormat("%d", int_val); 
                             row[i] = int_val;
                           }
                        break;
         
                     case DATABASE_FIELD_TYPE_FLOAT:
                        if (!DatabaseColumnDouble(request, i, dbl_val))
                           printf("func=%s line=%d, Failed to read Double. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {
                            row_string += StringFormat("%.5f", dbl_val);
                            row[i] = dbl_val;
                          }
                        break;
         
                     case DATABASE_FIELD_TYPE_TEXT:
                        if (!DatabaseColumnText(request, i, str_val))
                           printf("func=%s line=%d, Failed to read Text. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {                  
                             row_string += "'" + str_val + "'";    
                             row[i] = (double)str_val;
                          }
                        break;
         
                     default:
                             if (MQLInfoInteger(MQL_DEBUG))
                                PrintFormat("%s = <Unknown or Unsupported column Type by this Class>", col_name);
                        break;
                  }
               
               // Add comma if not last element
               if (i < cols - 1)
                  row_string += ", ";
             }
            
            row_string += ")";
            if (MQLInfoInteger(MQL_DEBUG))
               Print(row_string);  // Print the full row once 
               
            break;
          }
         
         DatabaseFinalize(request);
         return row;  // Replace with actual parsed return if needed
      }

 //... Other functions
}

De una base de datos similar en MetaEditor.

A continuación se explica cómo enviamos una consulta que solicita toda la información disponible en la tabla de la base de datos y, a continuación, limita la cantidad de información devuelta a una sola fila de datos.

#include <sqlite3.mqh>

CSqlite3 sqlite3;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    
    sqlite3.connect("example.db"); 
    Print(sqlite3.execute("SELECT * FROM users").fetchone());
    
    sqlite3.close(); 
  }

Resultados:

RJ      0       13:25:04.402    sqlite3 test (XAUUSD,H1)        (1, 'Alice', 30, 'zero@example.com')
DQ      0       13:25:04.402    sqlite3 test (XAUUSD,H1)        [1,0,30,0]

¡Tiene muy buena pinta! Ahora puedes recuperar una sola fila de valores de la base de datos con muy poco esfuerzo. Sin embargo, la función actual ignora todos los valores binarios y no procesa ni codifica las cadenas; por eso, todos los tipos de datos «TEXT» o de cadena se han asignado a cero en el vector devuelto.

Además, a diferencia de las matrices de Python, que pueden contener valores de diferentes tipos de datos, los vectores y las matrices de MQL5 no pueden hacerlo; más adelante veremos cómo podemos solucionar esto.

2. El método «fetchall»

A diferencia del método fetchone, este recibe toda la información solicitada en la instrucción SQL del método execute.

struct execute_res_structure
  {
   int request;
  //... Other functions

   matrix fetchall()
      {
         int cols = DatabaseColumnsCount(request);         
         vector row = vector::Zeros(cols);
         
         int CHUNK_SIZE = 1000; //For optimized matrix handling
         matrix results_matrix = matrix::Zeros(CHUNK_SIZE, cols);  
         int rows_found = 0; //for counting the number of rows seen in the database
         
         while (DatabaseRead(request))  // Essential, read the entire database
          {
            string row_string = "("; //for printing purposes only. Similar to how Python prints
            
            for (int i = 0; i < cols; i++)
             {
               int int_val; //Integer variable
               double dbl_val; //double variable
               string str_val; //string variable
      
               ENUM_DATABASE_FIELD_TYPE col_type = DatabaseColumnType(request, i);
               string col_name;
      
               if (!DatabaseColumnName(request, i, col_name))
                {
                  printf("func=%s line=%d, Failed to read database column name. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                  continue;
                }
      
                 switch (col_type) //Detect a column datatype and assign the value read from every row of that column to the suitable variable
                  {
                     case DATABASE_FIELD_TYPE_INTEGER:
                        if (!DatabaseColumnInteger(request, i, int_val))
                           printf("func=%s line=%d, Failed to read Integer. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {
                             row_string += StringFormat("%d", int_val);  //For printing purposes only
                             row[i] = int_val;
                           }
                        break;
         
                     case DATABASE_FIELD_TYPE_FLOAT:
                        if (!DatabaseColumnDouble(request, i, dbl_val))
                           printf("func=%s line=%d, Failed to read Double. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {
                            row_string += StringFormat("%.5f",dbl_val);  //For printing purposes only
                            row[i] = dbl_val;
                          }
                        break;
         
                     case DATABASE_FIELD_TYPE_TEXT:
                        if (!DatabaseColumnText(request, i, str_val))
                           printf("func=%s line=%d, Failed to read Text. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {  
                             row_string += "'" + str_val + "'";                                
                             row[i] = (double)str_val;
                          }
                        break;
         
                     default:
                             if (MQLInfoInteger(MQL_DEBUG))
                                PrintFormat("%s = <Unknown or Unsupported column Type by this Class>", col_name);
                        break;
                  }
                  
               // Add comma if not last element
               if (i < cols - 1)
                  row_string += ", ";
             }
          
          //---
          
            row_string += ")";
            if (MQLInfoInteger(MQL_DEBUG))
               Print(row_string);  // Print the full row once
            
          //---
            
            rows_found++; //Increment the rows counter
            if (rows_found > (int)results_matrix.Rows()) 
               results_matrix.Resize(results_matrix.Rows()+CHUNK_SIZE, cols); //Resizing the array after 1000 rows
            
            results_matrix.Row(row, rows_found-1); //Insert a row into the matrix
          }
         
         results_matrix.Resize(rows_found, cols); //Resize the matrix according to the number of unknown rows found in the database | Final trim
         
         DatabaseFinalize(request); //Removes a request created in DatabasePrepare().
         return results_matrix;  // return the final matrix
      }

  //... Other lines of code
}

En esta ocasión, devolvemos una matriz en lugar de un vector para poder dar cabida a la tabla completa, que es bidimensional.

Lo complicado de esta función es gestionar el proceso de redimensionamiento dinámico de la matriz resultante. Las tablas de una base de datos pueden llegar a ser enormes en ocasiones (con más de 100 000 filas), y conocer el tamaño de la base de datos o de las tablas en términos de filas resulta complicado. Por lo tanto, cambiar el tamaño de esta matriz resultante en cada iteración puede ralentizar enormemente esta función, sobre todo cuanto más repitamos el bucle por las filas, ya que la función Resize es una de las funciones que más recursos computacionales consume en MQL5.

Por eso he decidido cambiar el tamaño de la matriz cada 1.000 iteraciones en la función anterior, para reducir el número de veces que llamamos al método Resize.

Uso de la función.

#include <sqlite3.mqh>
CSqlite3 sqlite3;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    
    sqlite3.connect("example.db");
    Print(sqlite3.execute("SELECT * FROM users").fetchall());
    
    sqlite3.close(); 
  }

Resultados en la pestaña «Expertos» de MetaTrader 5.

IF      0       13:30:33.649    sqlite3 test (XAUUSD,H1)        (1, 'Alice', 30, 'zero@example.com')
FR      0       13:30:33.649    sqlite3 test (XAUUSD,H1)        (2, 'Alice', 30, 'alice@example.com')
FD      0       13:30:33.649    sqlite3 test (XAUUSD,H1)        (3, 'Alice', 30, 'bro@example.com')
QQ      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (4, 'Alice', 30, 'ishowspeed@example.com')
MO      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (5, 'Alice', 30, 'damn@example.com')
MD      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (6, 'Alice', 30, 'wth@example.com')
QN      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (7, 'Bruh', 30, 'stubborn@example.com')
NO      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (8, 'Bruh', 30, 'whathehelly@example.com')
ED      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (9, 'Bruh', 30, 'huh@example.com')
PO      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (10, 'Bruh', 30, 'whatsgoingon@example.com')
FS      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (11, 'Bruh', 30, 'bruh@example.com')
FF      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        (12, 'Bruh', 30, 'how@example.com')
JO      0       13:30:33.650    sqlite3 test (XAUUSD,H1)        [[1,0,30,0]
RG      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [2,0,30,0]
QN      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [3,0,30,0]
LE      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [4,0,30,0]
KL      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [5,0,30,0]
NS      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [6,0,30,0]
MJ      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [7,0,30,0]
HQ      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [8,0,30,0]
GH      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [9,0,30,0]
OL      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [10,0,30,0]
RE      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [11,0,30,0]
JS      0       13:30:33.650    sqlite3 test (XAUUSD,H1)         [12,0,30,0]]

La función «execute» ha devuelto las doce filas disponibles en la base de datos.

Una vez más, las matrices de MQL5 no pueden contener variables no homogéneas en una misma matriz, por lo que acabamos con ceros en todas las filas que contienen cadenas de la tabla de la base de datos. Al menos por ahora.

3. El método `fetchmany`

Al igual que el método `fetchall`, esta función devuelve una matriz que contiene valores de la tabla, pero este método te permite controlar el número de filas que deseas obtener.

struct execute_res_structure
  {
   int request;

//... other lines of code

   matrix fetchmany(uint size)
      {
         int cols = DatabaseColumnsCount(request);         
         vector row = vector::Zeros(cols);
         
         matrix results_matrix = matrix::Zeros(size, cols);  
         int rows_found = 0;
         
         while (DatabaseRead(request))  // Essential, read the entire database
          {
            string row_string = "("; 
            
            for (int i = 0; i < cols; i++)
             {
               int int_val; //Integer variable
               double dbl_val; //double variable
               string str_val; //string variable
      
               ENUM_DATABASE_FIELD_TYPE col_type = DatabaseColumnType(request, i);
               string col_name;
      
               if (!DatabaseColumnName(request, i, col_name))
                {
                  printf("func=%s line=%d, Failed to read database column name. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                  continue;
                }
      
                 switch (col_type) //Detect a column datatype and assign the value read from every row of that column to the suitable variable
                  {
                     case DATABASE_FIELD_TYPE_INTEGER:
                        if (!DatabaseColumnInteger(request, i, int_val))
                           printf("func=%s line=%d, Failed to read Integer. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {
                             row_string += StringFormat("%d", int_val);
                             row[i] = int_val;
                           }
                        break;
         
                     case DATABASE_FIELD_TYPE_FLOAT:
                        if (!DatabaseColumnDouble(request, i, dbl_val))
                           printf("func=%s line=%d, Failed to read Double. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {
                            row_string += StringFormat("%.5f", dbl_val);
                            row[i] = dbl_val;
                          }
                        break;
         
                     case DATABASE_FIELD_TYPE_TEXT:
                        if (!DatabaseColumnText(request, i, str_val))
                           printf("func=%s line=%d, Failed to read Text. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError()));
                        else
                          {
                             row_string += "'" + str_val + "'";   
                             row[i] = (double)str_val;
                          }
                        break;
         
                     default:
                             if (MQLInfoInteger(MQL_DEBUG))
                                PrintFormat("%s = <Unknown or Unsupported column Type by this Class>", col_name);
                        break;
                  }
             }
            
            results_matrix.Row(row, rows_found);
            rows_found++;
      
            if (rows_found >= (int)size)
               break;
      
            row_string += ")";
            if (MQLInfoInteger(MQL_DEBUG))
               Print(row_string);  // Print the full row once
          }
         
         results_matrix.Resize(rows_found, cols); //Resize the matrix according to the number of unknown rows found in the database | Final trim
         
         DatabaseFinalize(request); //Removes a request created in DatabasePrepare().
         return results_matrix;  // return the final matrix
      }

  //... other functions
}

Uso de la función.

#include <sqlite3.mqh>
CSqlite3 sqlite3;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    
    sqlite3.connect("example.db");
    Print(sqlite3.execute("SELECT * FROM users").fetchmany(5));
    
    sqlite3.close(); 
  }

Resultados:

EG      0       13:45:25.480    sqlite3 test (XAUUSD,H1)        (1'Alice'30'zero@example.com')
IR      0       13:45:25.481    sqlite3 test (XAUUSD,H1)        (2'Alice'30'alice@example.com')
EJ      0       13:45:25.481    sqlite3 test (XAUUSD,H1)        (3'Alice'30'bro@example.com')
NS      0       13:45:25.481    sqlite3 test (XAUUSD,H1)        (4'Alice'30'ishowspeed@example.com')
CD      0       13:45:25.481    sqlite3 test (XAUUSD,H1)        [[1,0,30,0]
KR      0       13:45:25.481    sqlite3 test (XAUUSD,H1)         [2,0,30,0]
LI      0       13:45:25.481    sqlite3 test (XAUUSD,H1)         [3,0,30,0]
QP      0       13:45:25.481    sqlite3 test (XAUUSD,H1)         [4,0,30,0]
EJ      0       13:45:25.481    sqlite3 test (XAUUSD,H1)         [5,0,30,0]]

Aunque estas tres funciones permiten controlar la cantidad de datos que se van a almacenar en una matriz, siguen dependiendo de tu consulta SQL; es esta la que determina, en primer lugar, qué información se va a devolver. Las tres funciones mencionadas anteriormente no son más que puntos de acceso para recibir los datos solicitados por la instrucción SQL.

4. Método adicional: comprobación del estado de la función de ejecución

Por defecto, esta función devuelve una estructura que contiene diversas funciones útiles para devolver los datos obtenidos tras una instrucción SQL del tipo «SELECT». Sin embargo, es posible que a menudo llamemos al método «execute» para un tipo de instrucción SQL que no sea «select», lo que hace que esta función no devuelva ningún dato que pueda utilizarse para comprobar si se ha ejecutado correctamente o no. 

Para ello, debemos utilizar la variable denominada «boolean» en la estructura devuelta por la función «execute». 

{
    sqlite3.connect("indicators.db");
    
    sqlite3.execute(
       "   CREATE TABLE IF NOT EXISTS EURUSD ("
       "   id INTEGER PRIMARY KEY AUTOINCREMENT,"
       "   example_indicator FLOAT,"
       ")"
    ); 
    
    if (!sqlite3.execute(StringFormat("INSERT INTO USDJPY (example_indicator) VALUES (%.5f)",(double)rand())).boolean) //A SQL query with a purposefully placed error
      printf("The execute function failed!");
 }

La variable booleana es una variable de tipo bool; toma el valor «true» si la función se ha ejecutado correctamente y «false» si ha fallado.

Resultados:

QK      2       18:27:00.575    sqlite3 test (XAUUSD,H1)        database error, near ")": syntax error
DN      0       18:27:00.576    sqlite3 test (XAUUSD,H1)        func=CSqlite3::execute line=402, Failed to execute a query to the database. Error = Generic database error
GE      2       18:27:00.578    sqlite3 test (XAUUSD,H1)        database error, no such table: USDJPY
PS      0       18:27:00.578    sqlite3 test (XAUUSD,H1)        func=CSqlite3::execute line=402, Failed to execute a query to the database. Error = Generic database error
IS      0       18:27:00.578    sqlite3 test (XAUUSD,H1)        The execute function failed!

Para comprobar si la función se ha ejecutado correctamente cuando se le pasa una consulta de tipo «SELECT» que devuelve algunos datos, hay que evaluar el tamaño de la matriz o el vector devuelto.

Si están vacíos (rows == 0 en el caso de una matriz y size == 0 en el caso de un vector), significa que la función «execute» ha fallado.


Trabajar con valores de texto (cadenas) de la base de datos

Como se ha comentado en la sección anterior, la función simplificada execute no permite devolver valores de cadena en una matriz o un vector, y eso ya lo sabemos; las cadenas o los valores de cadena pueden resultar tan útiles como cualquier otra variable, por lo que, tras obtener todos los valores de la base de datos y almacenarlos en una matriz, hay que extraer por separado todas las columnas que contengan valores de cadena. 

#include <sqlite3.mqh>
CSqlite3 sqlite3;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    
    sqlite3.connect("example.db");
    
    Print("database matrix:\n",sqlite3.execute("SELECT * FROM users").fetchall());
    
    string name_col[];
    sqlite3.execute("SELECT name FROM users").fetch_column("name", name_col);
    
    ArrayPrint(name_col);
}

Dada esta serie de valores de cadena, puedes buscar formas de codificarlos en variables que la matriz acepte (double, float, etc.) y volver a introducirlos en ella.

Resultados:

LS      0       12:48:12.456    sqlite3 test (XAUUSD,H1)        (1, 'Alice', 30, 'zero@example.com')
KG      0       12:48:12.456    sqlite3 test (XAUUSD,H1)        (2, 'Alice', 30, 'alice@example.com')
KH      0       12:48:12.456    sqlite3 test (XAUUSD,H1)        (3, 'Alice', 30, 'bro@example.com')
OM      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (4, 'Alice', 30, 'ishowspeed@example.com')
GH      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (5, 'Alice', 30, 'damn@example.com')
KH      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (6, 'Alice', 30, 'wth@example.com')
OR      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (7, 'Bruh', 30, 'stubborn@example.com')
LJ      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (8, 'Bruh', 30, 'whathehelly@example.com')
OG      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (9, 'Bruh', 30, 'huh@example.com')
RS      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (10, 'Bruh', 30, 'whatsgoingon@example.com')
PF      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (11, 'Bruh', 30, 'bruh@example.com')
DS      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (12, 'Bruh', 30, 'how@example.com')
NL      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (13, 'John', 83, 'johndoe@example.com')
IE      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (14, 'John', 83, 'johndoe2@example.com')
KP      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (15, 'John', 83, 'johndoe3@example.com')
IN      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        (16, 'John', 83, 'johndoe4@example.com')
KD      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        database matrix:
HP      0       12:48:12.457    sqlite3 test (XAUUSD,H1)        [[1,0,30,0]
PF      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [2,0,30,0]
OM      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [3,0,30,0]
ND      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [4,0,30,0]
MK      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [5,0,30,0]
LR      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [6,0,30,0]
KI      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [7,0,30,0]
JP      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [8,0,30,0]
IG      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [9,0,30,0]
QM      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [10,0,30,0]
LF      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [11,0,30,0]
KO      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [12,0,30,0]
RP      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [13,0,83,0]
MI      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [14,0,83,0]
PR      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [15,0,83,0]
DF      0       12:48:12.457    sqlite3 test (XAUUSD,H1)         [16,0,83,0]]
LQ      0       12:48:12.458    sqlite3 test (XAUUSD,H1)        "Alice" "Alice" "Alice" "Alice" "Alice" "Alice" "Bruh"  "Bruh"  "Bruh"  "Bruh"  "Bruh"  "Bruh"  "John"  "John"  "John"  "John" 


Control de transacciones en bases de datos

El módulo sqlite3 de Python ofrece varios métodos para controlar si se abren y se cierran las transacciones de la base de datos, cuándo y cómo. Podemos adoptar el mismo formato en nuestra clase, lo que facilitará la gestión de las transacciones al trabajar con bases de datos SQLite.

FunciónDescripciónNotas
bool CSqlite3::commit(void)
Confirma la transacción actual, haciendo que todos los cambios en la base de datos sean permanentes.
Es obligatorio tras las consultas SQL de tipo INSERT, UPDATE o DELETE si autocommit está establecido en false.
bool CSqlite3::rollback(void) 
Anula la transacción actual, deshaciendo todos los cambios no confirmados.Útil para la gestión de errores.
bool CSqlite3::begin(void)
Inicia la transacción que posteriormente se confirmará en la base de datos. Antes de realizar cualquier modificación en la base de datos, se utiliza una sintaxis más explícita. La función `execute()` la invoca automáticamente cuando `autocommit` está establecido en `true`.
bool CSqlite3::in_transaction()
Una función booleana para comprobar si la transacción está activa o no.Devuelve «true» si la transacción está activa en ese momento.
CSqlite3(bool autocommit=false)

De forma opcional —aunque no es recomendable—, puedes modificar el valor de autocommit dentro del constructor de una clase para activar o desactivar la confirmación automática de la transacción dentro de la función execute.

Las tres funciones (begin, rollback y commit) se basan en las funciones integradas de MQL5 para gestionar las transacciones de la base de datos.

bool CSqlite3::commit(void)
 {
   if (!DatabaseTransactionCommit(m_db_handle))
      {
         printf("func=%s line=%d, Failed to commit a transaction. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
         return false;
      }
   
   m_transaction_active = false; //Reset the transaction after commit    
   m_transaction_active_auto = false;
   
   return true;
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSqlite3::begin(void) 
  { 
   if (m_transaction_active)
     {
       if (!m_transaction_active_auto) //print only if the user started the transaction not when it was started automatically by the execute() function
         printf("Can not begin, already in a transaction. Call the function rollback() to disregard it, or commit() to save the changes");
       return false;
     }
     
//---

   if (!DatabaseTransactionBegin(m_db_handle))
      {
         printf("func=%s line=%d, Failed to begin a transaction. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
         return false;
      }
   
   m_transaction_active = true;   
   m_transaction_active_auto = false;
   
   return m_transaction_active;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSqlite3::rollback(void) 
 { 
   if (!DatabaseTransactionRollback(m_db_handle))
      {
         printf("func=%s line=%d, Failed to rollback a transaction. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
         return false;
      }
      
   m_transaction_active = false; //Reset the transaction after rollback 
   m_transaction_active_auto = false;
   
   return true;
 }


Otros métodos del módulo sqlite3

Estas son funciones menos utilizadas de este módulo, pero siguen resultando útiles para diversas tareas.

1. El método `executemany`

Esta función te permite insertar varias filas en una tabla de la base de datos con una sola llamada a la función.

Supongamos que tienes una matriz con varias filas, en las que cada columna contiene un tipo específico de valores indicadores que deseas introducir en la base de datos de una sola vez; esta es una función muy útil.

#include <sqlite3.mqh>
CSqlite3 sqlite3;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    
    sqlite3.connect("indicators.db");
    
    sqlite3.execute(
       "   CREATE TABLE IF NOT EXISTS EURUSD ("
       "   id INTEGER PRIMARY KEY AUTOINCREMENT,"
       "   INDICATOR01 FLOAT,"
       "   INDICATOR02 FLOAT,"
       "   INDICATOR03 FLOAT"
       ")"
    ); 
    
    matrix data = {{101, 25, 001},
                   {102, 32, 002},
                   {103, 29, 003}};
    
    sqlite3.executemany("INSERT INTO EURUSD (INDICATOR01, INDICATOR02, INDICATOR03) VALUES (?,?,?)", data);
    sqlite3.commit();
    sqlite3.close();
  }

Resultados:

El número de signos de interrogación que aparecen tras la palabra clave VALUES debe ser igual al número de columnas de la matriz «params» para que esta función funcione sin generar errores.

Ten en cuenta que, debido a las limitaciones de las matrices en cuanto a las variables homogéneas dentro de una misma matriz, no puedes añadir diferentes tipos de datos a la vez a tu base de datos utilizando esta función, por ejemplo: No se puede insertar una cadena y una columna de tipo «double» en la base de datos al mismo tiempo.

2. El método `executescript`

Esta función resulta muy útil cuando se desea ejecutar varias sentencias SQL de una sola vez. Por ejemplo, crear tablas, insertar filas, etc., en una sola instrucción. A continuación se detallan las características principales de esta función.

void OnStart()
  {
//---
     sqlite3.connect("indicators.db");
    
     // Use executescript to log actions
     sqlite3.executescript(
              "CREATE TABLE IF NOT EXISTS logs ("
              "id INTEGER PRIMARY KEY AUTOINCREMENT,"
              "event TEXT NOT NULL"
          ");"
      
          "INSERT INTO logs (event) VALUES ('Users batch inserted');"
      );
    
     sqlite3.close();
  }

  • Acepta una cadena de varias sentencias SQL separadas por punto y coma «;».
  • Las ejecuta todas a la vez, sin parametrizar y sin analizar, a diferencia de execute() o executemany().
  • No devuelve resultados, solo ejecuta el conjunto de comandos.

Resultados:

La función executescript confirma automáticamente cualquier transacción abierta, por lo que normalmente no es necesario llamar explícitamente a la función commit, salvo que sea necesario para fines de control.

3. Método adicional: el método `print_table`

Para obtener una funcionalidad similar a la de cursor.description que ofrece el módulo sqlite3 de Python, que devuelve la estructura de una consulta SQL, una base de datos o una tabla.

Podemos utilizar la función integrada de MQL5 denominada DatabasePrint para obtener un resultado similar.

void CSqlite3::print_table(const string table_name_or_sql, const int flags=0) // Prints a table or an SQL request execution result in the Experts journal.
  {
    if (DatabasePrint(m_db_handle, table_name_or_sql, flags)<0)
       printf("func=%s line=%d, Failed to print the table or query result. Error = %s",__FUNCTION__,__LINE__, ErrorDescription(GetLastError()));
  }

Uso.

sqlite3.print_table("SELECT * FROM users");

Resultados:

CM      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         #| id name  age email                   
PJ      0       13:17:19.028    sqlite3 test (XAUUSD,H1)        --+--------------------------------------
MH      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         1|  1 Alice  30 zero@example.com         
KS      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         2|  2 Alice  30 alice@example.com        
NO      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         3|  3 Alice  30 bro@example.com          
NI      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         4|  4 Alice  30 ishowspeed@example.com   
IL      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         5|  5 Alice  30 damn@example.com         
LO      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         6|  6 Alice  30 wth@example.com          
EE      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         7|  7 Bruh   30 stubborn@example.com     
EM      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         8|  8 Bruh   30 whathehelly@example.com  
ER      0       13:17:19.028    sqlite3 test (XAUUSD,H1)         9|  9 Bruh   30 huh@example.com          
HK      0       13:17:19.028    sqlite3 test (XAUUSD,H1)        10| 10 Bruh   30 whatsgoingon@example.com 
IQ      0       13:17:19.028    sqlite3 test (XAUUSD,H1)        11| 11 Bruh   30 bruh@example.com         
LQ      0       13:17:19.028    sqlite3 test (XAUUSD,H1)        12| 12 Bruh   30 how@example.com          
GG      0       13:17:19.028    sqlite3 test (XAUUSD,H1)        13| 13 John   83 johndoe@example.com      
GK      0       13:17:19.028    sqlite3 test (XAUUSD,H1)        14| 14 John   83 johndoe2@example.com     
NQ      0       13:17:19.028    sqlite3 test (XAUUSD,H1)        15| 15 John   83 johndoe3@example.com     
QF      0       13:17:19.028    sqlite3 test (XAUUSD,H1)        16| 16 John   83 johndoe4@example.com     


Registro de operaciones en la base de datos

Hemos visto algunos ejemplos sencillos sobre cómo se pueden ejecutar sentencias SQL, añadir valores a la base de datos y mucho más. Hagamos por fin algo que valga la pena: insertemos todas las operaciones del historial de MetaTrader 5 en una base de datos SQLite.

Este ejemplo está extraído de este artículo.

Sin sqlite3.

//--- auxiliary variables
   ulong    deal_ticket;         // deal ticket
   long     order_ticket;        // a ticket of an order a deal was executed by
   long     position_ticket;     // ID of a position a deal belongs to
   datetime time;                // deal execution time
   long     type ;               // deal type
   long     entry ;              // deal direction
   string   symbol;              // a symbol a deal was executed for
   double   volume;              // operation volume
   double   price;               // price
   double   profit;              // financial result
   double   swap;                // swap
   double   commission;          // commission
   long     magic;               // Magic number (Expert Advisor ID)
   long     reason;              // deal execution reason or source
//--- go through all deals and add them to the database
   bool failed=false;
   int deals=HistoryDealsTotal();
// --- lock the database before executing transactions
   DatabaseTransactionBegin(database);
   for(int i=0; i<deals; i++)
     {
      deal_ticket=    HistoryDealGetTicket(i);
      order_ticket=   HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
      position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);
      time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME);
      type=           HistoryDealGetInteger(deal_ticket, DEAL_TYPE);
      entry=          HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
      symbol=         HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
      volume=         HistoryDealGetDouble(deal_ticket, DEAL_VOLUME);
      price=          HistoryDealGetDouble(deal_ticket, DEAL_PRICE);
      profit=         HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);
      swap=           HistoryDealGetDouble(deal_ticket, DEAL_SWAP);
      commission=     HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION);
      magic=          HistoryDealGetInteger(deal_ticket, DEAL_MAGIC);
      reason=         HistoryDealGetInteger(deal_ticket, DEAL_REASON);
      //--- add each deal to the table using the following query
      string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON)"
                                       "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d)",
                                       deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason);
      if(!DatabaseExecute(database, request_text))
        {
         PrintFormat("%s: failed to insert deal #%d with code %d", __FUNCTION__, deal_ticket, GetLastError());
         PrintFormat("i=%d: deal #%d  %s", i, deal_ticket, symbol);
         failed=true;
         break;
        }
     }
//--- check for transaction execution errors
   if(failed)
     {
      //--- roll back all transactions and unlock the database
      DatabaseTransactionRollback(database);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
      return(false);
     }
//--- all transactions have been performed successfully - record changes and unlock the database
   DatabaseTransactionCommit(database);

Con sqlite3.

#include <sqlite3.mqh>
CSqlite3 sqlite3;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    sqlite3.connect("Trades_database.db");
    
//--- auxiliary variables

    ulong    deal_ticket;         // deal ticket
    long     order_ticket;        // a ticket of an order a deal was executed by
    long     position_ticket;     // ID of a position a deal belongs to
    datetime time;                // deal execution time
    long     type ;               // deal type
    long     entry ;              // deal direction
    string   symbol;              // a symbol a deal was executed for
    double   volume;              // operation volume
    double   price;               // price
    double   profit;              // financial result
    double   swap;                // swap
    double   commission;          // commission
    long     magic;               // Magic number (Expert Advisor ID)
    long     reason;              // deal execution reason or source
    
//--- go through all deals and add them to the database
   
   HistorySelect(0, TimeCurrent());
   int deals=HistoryDealsTotal();
   
   sqlite3.execute("CREATE TABLE IF NOT EXISTS DEALS ("
                     "ID          INT KEY NOT NULL,"
                     "ORDER_ID    INT     NOT NULL,"
                     "POSITION_ID INT     NOT NULL,"
                     "TIME        INT     NOT NULL,"
                     "TYPE        INT     NOT NULL,"
                     "ENTRY       INT     NOT NULL,"
                     "SYMBOL      CHAR(10),"
                     "VOLUME      REAL,"
                     "PRICE       REAL,"
                     "PROFIT      REAL,"
                     "SWAP        REAL,"
                     "COMMISSION  REAL,"
                     "MAGIC       INT,"
                     "REASON      INT );"
   ); //Creates a table if it doesn't exist
   
   sqlite3.begin(); //Start the transaction
   
// --- lock the database before executing transactions

   for(int i=0; i<deals; i++) //loop through all deals
     {
      deal_ticket=    HistoryDealGetTicket(i);
      order_ticket=   HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
      position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);
      time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME);
      type=           HistoryDealGetInteger(deal_ticket, DEAL_TYPE);
      entry=          HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
      symbol=         HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
      volume=         HistoryDealGetDouble(deal_ticket, DEAL_VOLUME);
      price=          HistoryDealGetDouble(deal_ticket, DEAL_PRICE);
      profit=         HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);
      swap=           HistoryDealGetDouble(deal_ticket, DEAL_SWAP);
      commission=     HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION);
      magic=          HistoryDealGetInteger(deal_ticket, DEAL_MAGIC);
      reason=         HistoryDealGetInteger(deal_ticket, DEAL_REASON);
      
      //--- add each deal to the table using the following query
      string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON)"
                                       "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d)",
                                       deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason);
      
      sqlite3.execute(request_text);
     }
    
    sqlite3.commit(); //Commit all deals to the database at once
    sqlite3.close(); //close the database
  }

Resultado:


Reflexiones finales

Recrear el módulo sqlite3 de Python en MQL5 fue un reto gratificante que pone de manifiesto tanto la flexibilidad como las limitaciones de MQL5 en comparación con Python. Aunque MQL5 no cuenta con soporte integrado para funciones avanzadas como autorizadores o gestores de contexto para una base de datos SQLite, con una abstracción y un diseño de métodos cuidadosos se puede lograr una experiencia de desarrollo muy similar.

Esta clase CSqlite3 personalizada permite ahora a los desarrolladores de MQL5 interactuar con bases de datos SQLite de una forma estructurada y al estilo de Python, con soporte completo para consultas, transacciones, control de confirmación/reversión y operaciones de recuperación como fetchone(), fetchmany(), fetchall(), etc.

Si vienes del mundo de Python, este módulo te resultará familiar y, con suerte, te resultará agradable de usar.

Hasta la próxima.


Tabla de archivos adjuntos

Nombre del archivoDescripción y uso
Include\errordescription.mqhContiene descripciones de todos los códigos de error generados por MQL5 y MetaTrader 5.
Include\sqlite3.mqhContiene la clase CSqlite3 para trabajar con bases de datos SQLite de forma similar a Python.
Scripts\sqlite3 test.mq5 Un script para probar la clase CSqlite3. 

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18640

Archivos adjuntos |
Attachments.zip (12.89 KB)
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 30): Commodity Channel Index (CCI), Zero Line EA Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 30): Commodity Channel Index (CCI), Zero Line EA
La automatización del análisis de la evolución de los precios es el camino a seguir. En este artículo, utilizamos el indicador CCI dual, la estrategia de cruce de la línea cero, la media móvil exponencial (EMA) y la acción del precio para desarrollar una herramienta que genere señales de trading y establezca los niveles de stop-loss (SL) y take-profit (TP) utilizando el ATR. Lea este artículo para conocer cómo abordamos el desarrollo del EA CCI Zero-Line.
Características del Wizard MQL5 que debe conocer (Parte 72): Uso de los patrones del MACD y del OBV con aprendizaje supervisado Características del Wizard MQL5 que debe conocer (Parte 72): Uso de los patrones del MACD y del OBV con aprendizaje supervisado
Damos continuidad a nuestro último artículo, en el que presentamos el par de indicadores formado por el MACD y el OBV, analizando cómo se podría mejorar esta combinación mediante el aprendizaje automático. MACD y OBV forman un emparejamiento complementario en cuanto a tendencia y volumen. Nuestro enfoque de aprendizaje automático utiliza una red neuronal convolucional que emplea un kernel para dimensionar sus núcleos y canales, al tiempo que ajusta las previsiones de este par de indicadores. Como siempre, esto se realiza en un archivo de clase de señal personalizado que funciona con el asistente MQL5 para ensamblar un Asesor Experto.
Cómo creamos la plataforma de trading más potente con aprendizaje automático: crónica del desarrollo de MQL y MetaTrader según los archivos, foros y lanzamientos Cómo creamos la plataforma de trading más potente con aprendizaje automático: crónica del desarrollo de MQL y MetaTrader según los archivos, foros y lanzamientos
Crónica técnica del desarrollo de MQL: desde el MQL/MQL II, con sus limitaciones, pasando por el MQL4 procedimental, hasta el MQL5 orientado a objetos, con compilación nativa, API muy completas y un entorno de ingeniería integral. Se presentan las capacidades clave del lenguaje y las integraciones con Python, OpenCL, ONNX, OpenBLAS, bases de datos, DirectX, el AI Assistant basado en agentes y el Model Context Protocol (MCP), que conecta la IA con el terminal de trading, MetaEditor, los datos de mercado, el trading y las herramientas de desarrollo. Se analizan fuentes de archivo sobre los orígenes y la evolución de MetaQuotes/MetaTrader, el lanzamiento de MQL4.COM y MQL5.COM, los campeonatos, Algo Forge y su impacto en el ecosistema.
Automatización de estrategias de trading en MQL5 (Parte 21): Mejora del trading basado en redes neuronales mediante tasas de aprendizaje adaptativas Automatización de estrategias de trading en MQL5 (Parte 21): Mejora del trading basado en redes neuronales mediante tasas de aprendizaje adaptativas
En este artículo, mejoramos una estrategia de trading basada en redes neuronales en MQL5 mediante una tasa de aprendizaje adaptativa para aumentar la precisión. Diseñamos e implementamos este mecanismo y, a continuación, comprobamos su rendimiento. El artículo concluye con algunas ideas sobre la optimización del trading algorítmico.