English Русский 中文 Español Deutsch 日本語
preview
Implementação em MQL5 de módulos úteis de outras linguagens (Parte 1): Criação de uma biblioteca SQLite3 semelhante à do Python

Implementação em MQL5 de módulos úteis de outras linguagens (Parte 1): Criação de uma biblioteca SQLite3 semelhante à do Python

MetaTrader 5 — Sistemas de negociação |
13 0
Omega J Msigwa
Omega J Msigwa

Seções


Introdução

Você já desejou ter no MQL5 um ou outro de seus módulos, bibliotecas ou frameworks favoritos de outra linguagem de programação? Comigo, isso acontece com frequência.

A comunidade MQL5 reúne muitos desenvolvedores com experiências variadas em programação. Alguns, como eu, vieram do desenvolvimento web; outros, do desenvolvimento para Android ou de diversas outras áreas ligadas à programação. Isso significa que a maioria dos programadores conhece diferentes linguagens, como JavaScript, Java, Python, C++ e C#, entre outras.

Ao trabalhar com essas linguagens, os programadores encontram diversas ferramentas e módulos tão úteis que gostariam de utilizá-los em qualquer ambiente de desenvolvimento. Por exemplo, gosto tanto de usar o módulo NumPy do Python para cálculos matemáticos que acabei implementando uma biblioteca semelhante em MQL5, cuja implementação descrevi neste artigo.

Embora a adaptação de um módulo, ferramenta ou framework de uma linguagem para outra, neste caso, para MQL5, possa resultar em diferenças nas funcionalidades e nos resultados devido às particularidades de cada linguagem, a semelhança sintática entre as linguagens ou a experiência prévia dos desenvolvedores já pode tornar o desenvolvimento em MQL5 mais simples e agradável para quem tem experiência com outras linguagens. Além disso, essa prática nos permite adquirir conhecimentos valiosos e aprimorar nossas habilidades de programação de modo geral.

Nesta nova série de artigos, não pretendemos reproduzir todos os módulos disponíveis em outras linguagens, mas nos concentrar naqueles que oferecem utilidade prática em MQL5. Entre eles estão módulos voltados a cálculos matemáticos, armazenamento de dados, análise de dados e outras aplicações.

Começaremos pelo módulo sqlite3, que faz parte da biblioteca padrão do Python.


O que é o módulo sqlite3 do Python

Primeiro, vamos entender o que é o SQLite:

O SQLite é um sistema de gerenciamento de banco de dados SQL leve, autônomo e que dispensa o uso de um servidor. É amplamente utilizado em aplicações que precisam de uma solução simples, embarcada e local para armazenamento de dados. Trata-se de um banco de dados baseado em arquivos, no qual todo o conteúdo do banco de dados, incluindo o esquema, as tabelas, os índices e os dados, fica armazenada em um único arquivo com extensão .sqlite ou .db.

Ao contrário de bancos de dados como MySQL ou PostgreSQL, que exigem configurações específicas, um servidor e parâmetros administrativos, o SQLite funciona imediatamente, sem precisar de nada disso, pois lê e grava os dados diretamente no disco.

O MQL5 dispõe de funções nativas para trabalhar com bancos de dados SQLite, que são suficientes para essa finalidade. No entanto, seu uso não é tão simples quanto o do módulo sqlite3 do Python.

Por exemplo, ao criar um banco de dados de exemplo e inserir algumas informações em uma tabela chamada users, essa tabela é criada automaticamente caso ainda não exista.

Uso do módulo sqlite3 do 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 das funções nativas do 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
 }

Podemos concordar que o uso do sqlite3 em Python resulta em um código mais limpo do que o uso das funções nativas do MQL5, que exigem tratamento de erros e tratamento dos erros, dos resultados das consultas e dos dados inseridos no banco de dados.

O módulo sqlite3 automatiza várias etapas que, de outra forma, precisaríamos executar manualmente ao enviar comandos e gerenciar transações. Isso simplifica bastante a inserção e a recuperação de dados em bancos de dados SQLite, sem grandes complicações.

Portanto, vamos implementar um módulo semelhante em MQL5.


Conexão com um banco de dados SQLite

O método connect do módulo sqlite3 do Python cria um novo banco de dados caso ainda não exista um com o nome especificado e retorna um objeto de conexão que representa o acesso ao banco de dados armazenado no disco.

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

Em MQL5, essa conexão corresponde a um handle do banco de dados. Portanto, tecnicamente, não precisamos retornar esse handle na nossa biblioteca MQL5, pois ele será utilizado internamente por todas as funções da classe.

class CSqlite3
  {
protected:

   int m_request;
   
public:
                     int m_db_handle;
 //... Остальные функции
 }

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;
 }

O MQL5 permite armazenar dados em dois locais: no diretório de dados (datapath) ou no diretório comum de dados (common datapath). Quando o argumento common é definido como true, os dados do banco são lidos do diretório comum, em vez do diretório de dados padrão.

Também podemos permitir que o usuário escolha entre manter o banco de dados na memória RAM ou armazená-lo no disco. Quando a variável database_in_memory é definida como true, o banco de dados é criado na memória RAM; caso contrário, é criado no disco.

Vamos desconsiderar uma das flags, DATABASE_OPEN_READONLY, utilizada para abrir o banco de dados em modo somente leitura (ReadOnly). O motivo é simples: se você não pretende gravar dados no banco, basta não executar instruções de inserção (INSERT). Faz sentido, não?


Execução de instruções SQL

Esta é uma das funções mais importantes e utilizadas com frequência ao trabalhar com bancos de dados SQLite. Ela permite consultar, inserir, atualizar e excluir dados, além de modificar valores armazenados no banco.

Essa função executa consultas e comandos SQL diretamente no banco de dados.

No módulo sqlite3 do Python, o método execute() funciona de maneira simples e eficiente. Ele aceita tanto comandos de inserção quanto consultas para recuperação de dados, identificando automaticamente quais resultados devem ser retornados e quando não há resultados a retornar.

O MQL5 dispõe de uma função nativa chamada DatabaseExecute, semelhante ao método execute() do módulo sqlite3 do Python. Ambas executam consultas no banco de dados ou na tabela especificada. No entanto, a função do MQL5 é adequada para executar todos os tipos de consultas, exceto aquelas que contêm a palavra-chave "SELECT" para recuperar dados do banco.

Para ler os dados do banco de forma eficiente, utilizamos a função DatabasePrepare, que prepara a consulta e retorna um handle, posteriormente utilizado por DatabaseRead para ler os registros.

Para criar uma função igualmente flexível em MQL5, capaz de executar comandos independentemente do tipo de consulta, precisamos identificar os diferentes tipos de instruções SQL e retornar os dados correspondentes a cada consulta.

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;
 }

Da mesma forma, no Python, todas as instruções executadas por execute, exceto SELECT, iniciam implicitamente uma transação por meio de begin. Essa transação precisa ser confirmada antes que as alterações sejam salvas no banco de dados.

Depois de chamar execute para inserir dados no banco, é necessário chamar o método commit para salvar as alterações. Voltaremos a esse método mais adiante no artigo.

Quando nossa função é chamada com uma instrução diferente de SELECT, ela não retorna nenhum valor, pois, nesses casos, normalmente estamos atualizando, modificando ou inserindo dados no banco. Já quando recebe uma consulta SELECT, retorna uma estrutura de dados, que veremos em detalhes a seguir.

1. Método fetchone

O módulo sqlite3 do Python permite recuperar dinamicamente todos os dados retornados por uma consulta SQL ou apenas parte deles.

Comecemos pela possibilidade de recuperar uma única linha de dados do banco.

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

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

Mesmo que a instrução SQL solicite todos os dados disponíveis no banco SQLite, o método fetchone limita o resultado de execute a uma única linha.

Banco de dados.

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')

Precisamos implementar um método equivalente na estrutura execute_res_structure, retornada pela função execute da nossa classe MQL5.

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
      }

 //... Остальные функции

Usando um banco de dados equivalente no MetaEditor.

A seguir, mostramos como executar uma consulta que solicita todos os dados de uma tabela e, em seguida, limitar o resultado retornado a uma única linha.

#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]

Ótimo! Agora podemos recuperar apenas uma linha de valores do banco de dados com facilidade. No entanto, a função atual ignora todos os valores binários e não processa nem codifica as strings. Por isso, todos os valores do tipo "TEXT" ou dados de texto presentes no vetor retornado recebem o valor zero.

Além disso, diferentemente das listas do Python, que podem armazenar valores de diferentes tipos, os vetores e arrays do MQL5 não oferecem essa possibilidade. Veremos mais adiante como contornar essa limitação.

2. Método fetchall

Ao contrário de fetchone, esse método recupera todos os dados solicitados pela instrução SQL executada por 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
}

Desta vez, retornamos uma matriz em vez de um vetor, permitindo armazenar uma tabela inteira, que possui uma estrutura bidimensional.

A parte mais complexa dessa função é redimensionar dinamicamente a matriz de resultados. As tabelas de um banco de dados podem ser muito grandes, às vezes com mais de 100 mil linhas, e nem sempre é fácil determinar antecipadamente quantas linhas elas contêm. Redimensionar a matriz de resultados a cada iteração pode, portanto, reduzir significativamente o desempenho da função, principalmente quando é necessário percorrer as linhas repetidas vezes, já que Resize é uma das funções que mais consomem recursos no MQL5.

Por isso, na função apresentada acima, optei por redimensionar a matriz a cada 1.000 iterações, reduzindo assim o número de chamadas ao método Resize.

Uso das funções.

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

Os resultados são exibidos na aba "Experts" do 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]]

A função execute retornou todas as 12 linhas existentes no banco de dados.

Como já mencionei, os arrays do MQL5 não permitem armazenar valores de diferentes tipos em um mesmo array. Por isso, os campos de texto das linhas retornadas acabam recebendo o valor zero. Pelo menos por enquanto.

3. Método fetchmany

Semelhante a fetchall, esse método retorna uma matriz com os valores da tabela, mas permite definir quantas linhas serão retornadas.

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 das funções.

#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]]

Embora esses três métodos permitam controlar a quantidade de dados armazenada na matriz, eles continuam dependendo da instrução SQL, pois é ela que determina quais informações serão recuperadas. Os três métodos descritos acima servem apenas para recuperar os dados solicitados pela instrução SQL.

4. Método adicional: verificação do estado da função execution

Por padrão, a função execute retorna uma estrutura com diversos métodos úteis para acessar os dados obtidos após a execução de uma consulta SQL do tipo "SELECT". No entanto, também podemos chamar execute com instruções SQL diferentes de SELECT. Nesses casos, a função não retorna dados que permitam verificar diretamente se a consulta foi executada com sucesso ou se ocorreu algum erro.

Para isso, precisamos utilizar uma variável chamada boolean na estrutura retornada pela função 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!");
 }

Essa variável é do tipo bool e assume o valor true quando a execução da função é bem-sucedida ou false quando ocorre uma falha.

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 verificar se a execução foi bem-sucedida no caso de uma consulta "SELECT" que retorna dados, é necessário avaliar o tamanho da matriz ou do vetor retornado.

Se estiverem vazios (rows==0 para a matriz e size==0 para o vetor), isso significa que a função execute falhou.


Manipulação de valores de texto (strings) do banco de dados

Como vimos na seção anterior, nossa versão simplificada da função execute não consegue retornar valores de texto em uma matriz ou vetor. Sabemos, porém, que as strings podem ser tão úteis quanto os demais tipos de dados. Por isso, depois de recuperar os valores do banco de dados e armazená-los em uma matriz, precisamos extrair separadamente todas as colunas que contêm valores de texto.

#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);
}

Com esse array de valores de texto, podemos encontrar maneiras de codificar esses valores em representações numéricas compatíveis com a matriz, utilizando tipos como double ou float, e depois inseri-los novamente nela.

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" 


Gerenciamento de transações no banco de dados

O módulo sqlite3 do Python oferece diversos métodos para gerenciar a abertura e o encerramento de transações no banco de dados, incluindo o momento e a maneira como essas operações são realizadas. Podemos reproduzir essa interface em nossa classe, simplificando o gerenciamento de transações ao trabalhar com bancos de dados SQLite.

Função Descrição Observação
bool CSqlite3::commit(void)
Confirma a transação atual, tornando permanentes todas as alterações realizadas no banco de dados.
É necessária após instruções SQL como INSERT, UPDATE ou DELETE, quando o parâmetro autocommit está definido como false.
bool CSqlite3::rollback(void) 
Desfaz a transação atual, descartando todas as alterações ainda não confirmadas. Útil para o tratamento de erros.
bool CSqlite3::begin(void)
Inicia uma transação cujas alterações serão posteriormente confirmadas no banco de dados. Permite iniciar explicitamente uma transação antes de modificar o banco de dados. A função execute() a chama automaticamente quando o parâmetro autocommit está definido como true.
bool CSqlite3::in_transaction()
Função booleana que verifica se existe uma transação ativa. Retorna true quando há uma transação em andamento.
CSqlite3(bool autocommit=false)

Embora não seja recomendado, você pode alterar o valor de autocommit no construtor da classe para ativar ou desativar a confirmação automática de transações na função execute.

As três funções, begin, rollback e commit, são implementadas com base nas funções nativas do MQL5 para gerenciamento de transações em bancos de dados.

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;
 }


Outros métodos do módulo sqlite3

Esses métodos são utilizados com menos frequência, mas continuam sendo úteis para diversas tarefas.

1. Método executemany

Esse método permite inserir várias linhas em uma tabela do banco de dados com uma única chamada.

Suponha que você tenha uma matriz com várias linhas, na qual cada coluna contém valores de indicadores de um determinado tipo, e queira inserir todos esses dados no banco de uma só vez. Nesse caso, executemany é o método ideal.

#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.

O número de pontos de interrogação após a palavra-chave VALUES deve corresponder ao número de colunas da matriz de parâmetros para evitar erros durante a execução.

Vale lembrar que, como uma matriz do MQL5 só pode armazenar valores de um mesmo tipo de dado, não é possível utilizar esse método para inserir simultaneamente dados de tipos diferentes no banco. Por exemplo, não podemos inserir, na mesma operação, um valor de texto e um valor de uma coluna do tipo double.

2. Método executescript

Esse método é útil quando precisamos executar várias instruções SQL de uma só vez. Por exemplo, podemos criar tabelas, inserir linhas e realizar outras operações em uma única chamada. Veja suas principais características.

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();
  }

  • O método recebe uma string contendo várias instruções SQL separadas por ponto e vírgula ";".
  • Executa todas essas instruções de uma só vez, sem parâmetros e sem analisá-las individualmente como nas funções execute() ou executemany().
  • Não retorna resultados; apenas executa o conjunto de comandos.

Resultados.

A função executescript confirma automaticamente todas as transações abertas. Portanto, normalmente não é necessário chamar commit explicitamente, a menos que isso seja necessário para controlar as transações.

3. Método adicional: print_table

O módulo sqlite3 do Python oferece o atributo cursor.description, que retorna informações sobre a estrutura de uma consulta SQL, de um banco de dados ou de uma tabela.

Para obter uma funcionalidade semelhante em MQL5, podemos utilizar a função nativa DatabasePrint.

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 operações de negociação no banco de dados

Já vimos alguns exemplos simples de como executar instruções SQL, inserir valores no banco de dados e realizar diversas outras operações. Agora, vamos fazer algo realmente útil: registrar todas as operações de negociação do histórico do MetaTrader 5 em um banco de dados SQLite.

Este exemplo foi retirado deste artigo.

Sem 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);

Com 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.


Considerações finais

Recriar o módulo sqlite3 do Python em MQL5 foi uma experiência interessante e útil, que demonstrou tanto a flexibilidade quanto as limitações do MQL5 em comparação com o Python. Embora o MQL5 não ofereça suporte nativo a recursos avançados, como mecanismos de autorização (autorizadores) ou gerenciadores de contexto para bancos de dados SQLite, uma abstração cuidadosa e métodos bem projetados permitem que os desenvolvedores obtenham uma experiência de uso bastante semelhante.

Com a classe personalizada CSqlite3, os desenvolvedores MQL5 agora podem interagir com bancos de dados SQLite de maneira estruturada, seguindo uma interface semelhante à do Python, com suporte a consultas, transações, gerenciamento de confirmações e reversões de transações, além de métodos de recuperação de dados, como fetchone(), fetchmany() e fetchall(), entre outros.

Se você já trabalhou com Python, este módulo lhe parecerá familiar e, espero, será agradável de usar.

Tudo de bom a todos!


Tabela de arquivos anexados

Nome do arquivo Descrição e utilização
Include\errordescription.mqh Contém as descrições de todos os códigos de erro gerados pelo MQL5 e pelo MetaTrader 5.
Include\sqlite3.mqh Contém a classe CSqlite3, que permite trabalhar com bancos de dados SQLite de maneira semelhante à do Python.
Scripts Script para testar a classe CSqlite3.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18640

Arquivos anexados |
Attachments.zip (12.89 KB)
Arbitragem estatística com ações cointegradas (Parte 1): testes de cointegração de Engle-Granger e Johansen Arbitragem estatística com ações cointegradas (Parte 1): testes de cointegração de Engle-Granger e Johansen
Este artigo apresenta uma introdução acessível aos testes de cointegração mais utilizados, voltada para traders, além de um guia prático para interpretar seus resultados. Os testes de Engle-Granger e Johansen permitem identificar pares ou grupos de ativos com relações estatisticamente significativas e uma dinâmica comum de longo prazo. O teste de Johansen é especialmente útil para carteiras com três ou mais ativos, pois avalia simultaneamente a força dos vetores de cointegração que relacionam todos os instrumentos.
Desenvolvimento de ferramentas para análise de Price Action (Parte 30): EA CCI Zero Line Desenvolvimento de ferramentas para análise de Price Action (Parte 30): EA CCI Zero Line
O futuro está na automação da análise do movimento dos preços. Neste artigo, combinamos o indicador Dual CCI (Commodity Channel Index, Índice do Canal de Commodities), a estratégia de cruzamento da linha zero (Zero Line Crossover), a EMA e a análise de Price Action para desenvolver uma ferramenta que gera sinais de negociação e define os níveis de stop-loss e take-profit com base no ATR. Leia este artigo para conhecer nossa abordagem ao desenvolvimento do EA CCI Zero Line.
Automatização de estratégias de negociação em MQL5 (Parte 22): Desenvolvimento de um sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes Automatização de estratégias de negociação em MQL5 (Parte 22): Desenvolvimento de um sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes
Desenvolveremos um sistema de recuperação por zonas (Zone Recovery System) integrado a uma estratégia de negociação a favor da tendência baseada no indicador Envelopes (Envelopes trend-trading strategy), utilizando MQL5. Também apresentaremos a arquitetura que combina os indicadores RSI e Envelopes para abrir operações e gerenciar a recuperação por zonas, com o objetivo de minimizar as perdas. Por meio da implementação prática e dos testes, demonstraremos como criar um sistema de negociação automatizada eficiente para mercados dinâmicos.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 72): Uso de padrões MACD e OBV com aprendizado supervisionado Recursos do Assistente MQL5 que você precisa conhecer (Parte 72): Uso de padrões MACD e OBV com aprendizado supervisionado
Dando continuidade ao artigo anterior sobre a combinação dos indicadores MACD e OBV, veremos como aprimorá-la com o uso de machine learning. O MACD e o OBV são indicadores complementares que refletem, respectivamente, a tendência e o volume. Nossa abordagem de machine learning utiliza uma rede neural convolucional (convolution neural network, CNN) que emprega um kernel exponencial (Exponential kernel) para definir as dimensões de seus kernels e o número de canais ao configurar as previsões geradas por essa combinação de indicadores. Como de costume, essa implementação é feita em um arquivo personalizado da classe de sinais (signal class), que interage com o Assistente MQL5 para criar um EA.