Implementação em MQL5 de módulos úteis de outras linguagens (Parte 1): Criação de uma biblioteca SQLite3 semelhante à do Python
Seções
- Introdução
- O que é o módulo sqlite3 do Python
- Conexão com um banco de dados SQLite
- Execução de instruções SQL
- Manipulação de valores de texto (strings) do banco de dados
- Gerenciamento de transações no banco de dados
- Outros métodos do módulo sqlite3
- Registro de operações de negociação no banco de dados
- Conclusão
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
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Arbitragem estatística com ações cointegradas (Parte 1): testes de cointegração de Engle-Granger e Johansen
Desenvolvimento de ferramentas para análise de Price Action (Parte 30): 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
Recursos do Assistente MQL5 que você precisa conhecer (Parte 72): Uso de padrões MACD e OBV com aprendizado supervisionado
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso

