English 中文 Deutsch 日本語
preview
Внедрение в MQL5 практических модулей из других языков (Часть 1): Создание библиотеки SQLite3 как в Python

Внедрение в MQL5 практических модулей из других языков (Часть 1): Создание библиотеки SQLite3 как в Python

MetaTrader 5Торговые системы |
321 0
Omega J Msigwa
Omega J Msigwa

Разделы


Введение

Случалось ли вам когда-нибудь хотеть, чтобы один или два ваших любимых модуля, библиотеки, фреймворка и т. д., присутствующие в другом языке программирования, были встроены в MQL5? Со мной такое часто случается.

В сообществе MQL5 огромное количество разработчиков с различным опытом программирования; некоторые, как и я, пришли из веб-разработки, другие — из разработки под Android, и из многих других профессий ,связанных с программированием. Это означает, что большинство программистов знакомы с различными языками программирования, такими как JavaScript, Java, Python, C++, C#, и это лишь некоторые из них.

В этих разных языках программирования программисты сталкиваются с различными инструментами (модулями) кодирования — полезными модулями, которые мы просто хотим использовать везде, где это возможно. Например, мне до такой степени нравится использовать для математических вычислений модуль NumPy, предлагаемый в Python, что однажды мне пришлось реализовать аналогичную библиотеку на языке MQL5, описав реализацию в этой статье.

Хотя попытка внедрить модуль, инструмент, фреймворк и т. д. из одного языка в другой — в данном случае в MQL5 — может привести к несколько иным функциональным возможностям и результатам из-за различий в природе языков программирования, наличия схожего синтаксиса или опыта может быть достаточно, чтобы разработка продукта на MQL5 стала легкой и увлекательной для разработчиков, знакомых с разными языками. Не говоря уже о том, что в процессе мы можем получить ценную информацию, которая укрепит наши навыки программирования в целом.

В этой новой серии статей мы будем реализовывать не все модули из других языков, а все модули из другого языка, которые имеют практическое значение в языке MQL5. Например, это могут быть модули для математических вычислений, хранения данных, анализа данных и т. д.

Начиная с модуля sqlite3, который входит в комплект поставки языка программирования Python.


Что такое модуль sqlite3 в Python

Давайте сначала разберемся в базе данных SQLite:

База данных SQLite — это упрощенная, автономная, не требующая серверов система управления базами данных SQL. Она широко используется в приложениях, требующих простого, встроенного, локального решения для хранения данных. Это файловая база данных, которая хранит (схему, таблицы, индексы и данные) всё в одном файле .sqlite или.db.

В отличие от баз данных MySQL илиPostgreSQL, требующих определенной настройки, наличия сервера и административных параметров, SQLite работает сразу же без всего этого, поскольку читает и записывает данные непосредственно на диск.

Язык программирования MQL5 поставляется со встроенными функциями для работы с базой данных SQLite; этих встроенных функций достаточно. Однако, по сравнению с использованием модуля sqlite3 в Python, они не так просты в использовании.

Например, при попытке создать простой «пример» базы данных и вставить некоторую информацию в таблицу с именем users, таблица создается автоматически, если она еще не существует.

Использование модуля Python sqlite

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

Использование встроенных функций 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
 }

Все мы можем согласиться с тем, что использование sqlite3 в языке Python сделало наш код чище, чем использование встроенных функций языка MQL5, которые требуют от нас обработки ошибок, возвращаемой или добавляемой в базу данных информации.

Модуль sqlite3 обрабатывает множество ненужных шагов при выполнении команды и управлении транзакциями, что значительно упрощает пользователям получение и вставку некоторых данных в базу данных SQLite и извлечение их из нее без особых проблем.

Итак, попробуем реализовать аналогичный модуль на языке MQL5.


Подключение к базе данных SQLite

Метод connect в sqlite3 языка Python создает новую базу данных, если заданное имя для базы данных не существует, и возвращает объект соединения, представляющий собой подключение к базе данных на диске.

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

В MQL5 это соединение аналогично элементу handle базы данных, поэтому технически нам не нужно возвращать хэндл в нашей библиотеке MQL5, поскольку мы будем использовать его во всех функциях внутри нашего класса.

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

MQL5 предоставляет нам возможность сохранять данные из любого из двух источников: datapath (папка) или common datapath (папка). Когда аргумент common установлен как true, информация из базы данных будет считываться из источника common datapath, а не из обычного источника datapath.

Кроме того, мы можем предоставить пользователю возможность выбора, открывать базу данных в оперативной памяти (RAM) или на диске, как в случае установки переменной database_in_memory на true. База данных будет создана в оперативной памяти вместо создания ее на диске, и наоборот.

Мы игнорируем один флаг, а именно DATABASE_OPEN_READONLY, при открытии базы данных в режиме ReadOnly. Причина проста: если вы не хотите записывать данные в базу данных, вы вообще не будете выполнять запросы на вставку (INSERT). Логично? 


Выполнение SQL-операторов

Это одна из важнейших функций, которую мы часто используем при работе с базами данных SQLite. Эта функция позволяет получать информацию, вставлять, обновлять, удалять и изменять значения в базе данных и т. д.

Эта функция выполняет SQL-запросы и команды непосредственно в нашей базе данных.

В sqlite3 на языке Python функция execute() работает безупречно и без усилий. Она может принимать любые команды как для вставки, так и для получения информации, и автоматически знает, когда и что возвращать, а что нет.

В MQL5 есть встроенная функция с именем DatabaseExecute, которая похожа на execute() в sqlite3 на языке Python. Оба процесса исполняют запрос к указанной базе данных или таблице. Однако этот метод MQL5 подходит для выполнения всех запросов, кроме тех, которые содержат ключевое слово "SELECT" для чтения информации из базы данных. 

Для эффективного чтения информации из базы данных используем функцию Database Prepare, поскольку это создает хэндл запроса, который затем может быть выполнен с помощью DatabaseRead.

Для создания аналогичной гибкой функции в MQL5, позволяющей исполнять функции независимо от типа запроса, необходимо различать типы SQL-запросов и возвращать правильную информацию с корректным запросом.

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

Аналогичным образом, функция execute в Python, все операторы, кроме SELECT, неявно открывают транзакцию с помощью функции begin, и перед сохранением изменений в базе данных эту транзакцию необходимо подтвердить.

После вызова функции execute и вставки некоторой информации в базу данных необходимо вызвать метод commit для сохранения изменений в базе данных. Мы обсудим данную функцию позже в этой публикации..

Когда функция вызывается при помощи любого оператора или запроса, кроме SELECT, она не возвращает никакого значения, поскольку, вероятно, обновляет, изменяет и вставляет информацию в базу данных. Однако, если функция вызывается с запросом типа SELECT, она возвращает структуру данных. Обсудим эту структуру подробно.

1. Метод fetchone

Модуль sqlite3, предлагаемый в языке Python, способен динамически возвращать всю полученную из SQL-оператора информацию или часть ее.

Начинается все с возможности получать одну строку информации из базы данных.

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

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

Несмотря на запрос всей доступной в базе данных SQLite информации из SQL-оператора, функция fetchone ограничивает функцию execute в возможности возврата более чем одной строки данных из базы данных.

База данных.

Результаты.

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

Нам нужна аналогичная функция в MQL5-классе, внутри структуры с именем execute_res_structure, которую возвращает функция execute.

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

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

Из аналогичной базы данных в MetaEditor.

Ниже показано, как мы отправляем запрос, который запрашивает всю доступную информацию в таблице базы данных, а затем ограничивает объем возвращаемой информации одной строкой данных.

#include <sqlite3.mqh>

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

Результаты.

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]

Выглядит отлично! Теперь вы можете без особых усилий извлечь из базы данных всего одну строку значений. Однако текущая функция игнорирует все двоичные значения и не обрабатывает и не кодирует строки, поэтому всем данным типа "TEXT" или строковым данным в возвращаемом векторе присваивается значение ноль.

Кроме того, в отличие от массивов Python, которые могут хранить значения разных типов данных, векторы и массивы MQL5 этого не могут; позже мы увидим, как с этим справиться.

2. Метод fetchall

В отличие от fetchone, этот метод получает всю информацию, запрошенную в SQL-операторе метода 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
}

На этот раз мы возвращаем матрицу вместо вектора, чтобы обеспечить возможность размещения целой таблицы, которая является двумерной.

Самая сложная часть в этой функции — обработка процесса динамического изменения размера результирующей матрицы. Таблицы в базе данных иногда могут быть огромными (содержащими более 100 000 строк), и определить размер базы данных или таблиц с точки зрения рядов бывает непросто. Таким образом, изменение размера результирующей матрицы на каждой итерации может значительно замедлить работу этой функции, особенно при многократном переборе строк, поскольку функция Resize является одной из наиболее ресурсоемких в MQL5.

Именно поэтому я решил изменять размер матрицы после каждых 1000 итераций в приведенной выше функции, чтобы уменьшить количество вызовов метода Resize.

Использование функций.

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

Результаты отображаются на вкладке «Эксперты» в 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]]

Функция execute вернула все двенадцать строк, имеющихся в базе данных.

Повторюсь, массивы MQL5 не могут содержать неоднородные переменные в одном массиве, поэтому в итоге каждый ряд, содержащий строки из таблицы базы данных, оказывается нулевым. По крайней мере, пока.

3. Метод fetchmany

Аналогично методу fetchall, эта функция возвращает матрицу, содержащую значения из таблицы, но этот метод позволяет вам контролировать количество рядов, которые вы хотите вернуть.

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
}

Использование функций.

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

Результаты.

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

Несмотря на то, что эти три функции способны контролировать объем данных, хранящихся в матрице, они по-прежнему зависят от вашего SQL-оператора; именно он определяет, какую информацию следует вернуть. Три описанные выше функции являются лишь средствами для получения данных, запрашиваемых SQL-оператором.

4. Дополнительный метод: проверка состояния функции execution

По умолчанию эта функция возвращает структуру, содержащую множество полезных функций для возврата данных, полученных после выполнения SQL-запроса типа "SELECT". Однако мы можем часто вызывать метод execute для SQL-оператора, не относящегося к типу SELECT, что заставляет эту функцию воздерживаться от возврата каких-либо данных, которые можно было бы использовать для проверки успешности или неуспешности запроса. 

Для этого нам необходимо использовать переменную с именем boolean в структуре, возвращаемой функцией 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!");
 }

Логическая переменная — это переменная типа bool; она становится истинной, если исполнение функции было успешным, или ложной, если ее исполнение не удалось.

Результаты.

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!

Чтобы проверить, успешно ли прошло исполнение функции, если ей передан запрос типа "SELECT", который возвращает некоторые данные, вам необходимо оценить размер возвращаемой матрицы или вектора.

Если они пусты (rows==0 для матрицы и size==0 для вектора), это означает, что функция execute завершила исполнение с ошибкой.


Работа с текстовыми (строковыми) значениями из базы данных

Как обсуждалось в предыдущем разделе, упрощенная функция execute не способна возвращать строковые значения в матрице или векторе, и нам это известно; строки или строковые значения могут быть столь же полезны, как и другие переменные, поэтому после получения всех значений из базы данных, хранящейся в матрице, необходимо отдельно извлечь все столбцы, содержащие строковые значения. 

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

При наличии такого массива строковых значений можно найти способы закодировать их в переменные, принимаемые матрицей (типы double, float и т. д.), а затем подставить обратно в матрицу.

Результаты.

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" 


Управление транзакциями базы данных

Модуль sqlite3 в Python предоставляет множество методов для управления открытием и закрытием транзакций базы данных, включая время и способ осуществления таких открытий и закрытий. Мы можем адаптировать тот же самый формат и в нашем классе, что упростит обработку транзакций при работе с базами данных SQLite.

Функция Описание Примечание
bool CSqlite3::commit(void)
Подтверждает текущую транзакцию, делая все изменения в базе данных постоянными.
Она требуется после SQL-запросов типа INSERTUPDATE или DELETE, если параметр autocommit установлен в значение false.
bool CSqlite3::rollback(void) 
Отменяет текущую транзакцию, аннулируя все неподтвержденные изменения. Полезно при обработке ошибок.
bool CSqlite3::begin(void)
Начинает транзакцию, которая впоследствии будет зафиксирована в базе данных.  Перед внесением каких-либо изменений в базу данных используется более явный синтаксис. Функция execute() вызывает ее автоматически, когда параметр autocommit установлен в значение true.
bool CSqlite3::in_transaction()
Логическая функция для проверки активности транзакции. Функция возвращает true, если транзакция в данный момент активна.
CSqlite3(bool autocommit=false)

При желании — хотя это и не рекомендуется, — вы можете изменить значение autocommit внутри конструктора класса позволяет включить или отключить автоматическое подтверждение транзакции внутри функции execute.

Три функции (begin, rollback и commit) построены на основе встроенных функций MQL5 для обработки транзакций базы данных.

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


Другие методы в модуле sqlite3

Это менее часто используемые функции данного модуля, но они, тем не менее, полезны для решения различных задач.

1. Метод executemany

Эта функция позволяет вставлять множество строк в таблицу базы данных за один вызов.

Предположим, у вас есть матрица, содержащая несколько строк, и каждый столбец в ней содержит значения индикаторов определенного типа, которые вы хотите одновременно вставить в базу данных, — это функция типа goto.

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

Результаты.

Количество вопросительных знаков после ключевого слова VALUES должно быть равно количеству столбцов в матрице параметров, чтобы эта функция не выдавала ошибок.

Имейте в виду, что из-за ограничений матрицы на однородные переменные внутри одной матрицы, вы не можете с помощью этой функции одновременно добавлять в базу данных различные типы данных, например: вы не сможете вставить в базу данных одновременно строку и значение из столбца типа double.

2. Метод executescript

Эта функция удобна, когда вам нужно выполнить несколько SQL-операторов за один прием. Например, с помощью одного оператора можно создать таблицы, вставить строки и т. д. Ниже перечислены основные особенности этой функции.

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

  • Эта функция принимает строку из нескольких SQL-операторов, разделенных точкой с запятой ";".
  • Она исполняет их все одновременно, без параметров и без разбора, как функции execute() или executemany().
  • Она не возвращает результатов, а просто выполняет пакет команд.

Результаты.

Функция executescript автоматически фиксирует любые открытые транзакции, поэтому обычно нет необходимости в явном виде вызывать функцию commit, если это не требуется для управления.

3. Дополнительный метод: print_table

Для получения функционала, аналогичного атрибуту cursor.description, который предоставляется модулем sqlite3 в языке Python и возвращает структуру SQL-запроса, базы данных или таблицы.

Для получения аналогичного результата мы можем использовать встроенную функцию MQL5, названную 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()));
  }

Применение.

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

Результаты.

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     


Регистрация сделок в базе данных

Мы рассмотрели несколько простых примеров того, как можно выполнять SQL-операторы, добавлять значения в базу данных и совершать многие другие действия. Давайте наконец сделаем что-нибудь полезное: вставим все сделки из истории MetaTrader 5 в базу данных SQLite.

Этот пример взят из этой статьи.

Без 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);

С использованием 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
  }

Результат.


Заключительные мысли

Воссоздание модуля sqlite3 из языка Python на языке MQL5 стало интересным и полезным опытом, который демонстрирует как гибкость, так и ограничения MQL5 по сравнению с Python. Хотя MQL5 не имеет встроенной поддержки таких расширенных функций, как инструменты авторизации (авторизаторы) или менеджеры контекста для базы данных SQLite, при тщательном абстрагировании и продуманном проектировании методов разработчики могут добиться очень похожего опыта.

Этот пользовательский класс CSqlite3 теперь позволяет разработчикам MQL5 взаимодействовать с базами данных SQLite структурированным, «питоноподобным» способом — с поддержкой запросов, транзакций, управления фиксацией/отменой и операций выборки, таких как fetchone(), fetchmany(), fetchall() и т. д.

Если вы раньше работали с Python, этот модуль покажется вам знакомым и, будем надеяться, приятным в использовании.

Всем добра.


Таблица вложений

Имя файла Описание и использование
Include\errordescription.mqh Содержит описания всех кодов ошибок, генерируемых MQL5 и MetaTrader 5
Include\sqlite3.mqh Содержит класс CSqlite3 для работы с базами данных SQLite таким же образом, как в Python.
— скрипты;  Скрипт для тестирования класса CSqlite3. 

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18640

Прикрепленные файлы |
Attachments.zip (12.89 KB)
От начального до среднего уровня: Объекты (I) От начального до среднего уровня: Объекты (I)
В данной статье мы начнём рассматривать, как можно работать с объектами непосредственно на графике. Это делается с помощью кода, специально разработанного для демонстрации. Работа с объектами очень интересна и доставляет немало удовольствия. Поскольку это будет наш первый контакт, начнём с чего-нибудь очень простого.
Возможности Мастера MQL5, которые вам нужно знать (Часть 70): Использование паттернов SAR и RVI с сетью экспоненциального ядра Возможности Мастера MQL5, которые вам нужно знать (Часть 70): Использование паттернов SAR и RVI с сетью экспоненциального ядра
В предыдущей статье мы представили пару индикаторов SAR и RVI. Здесь мы рассмотрим, как их можно расширить с помощью машинного обучения. SAR и RVI представляют собой взаимодополняющую пару, сочетающую в себе тренд и импульс. Наш подход к машинному обучению использует сверточную нейронную сеть (convolution neural network), которая задействует экспоненциальное ядро (Exponential kernel) для определения размеров своих ядер и каналов при настройке прогнозов этой пары индикаторов. Как обычно, это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для создания советника.
Нейросети в трейдинге: Единая архитектура взаимодействия рыночных признаков и торгового контекста (Окончание) Нейросети в трейдинге: Единая архитектура взаимодействия рыночных признаков и торгового контекста (Окончание)
В данной статье мы завершаем перенос ключевых компонентов фреймворка OneTrans в среду MQL5 и показываем их интеграцию в единый вычислительный граф. Основное внимание уделено организации обучения моделей на исторических финансовых данных с использованием Актера и Критика, а также оценке действий через псевдо идеальные сценарии. Результаты тестирования демонстрируют практическую ценность реализованных решений для построения адаптивных стратегий и анализа рыночной динамики.
Знакомство с языком MQL5 (Часть 37): Освоение API и функции WebRequest в языке MQL5 (XI) Знакомство с языком MQL5 (Часть 37): Освоение API и функции WebRequest в языке MQL5 (XI)
В этой статье мы покажем, как с помощью языка MQL5 отправлять аутентифицированные запросы к API Binance, чтобы получать баланс счета по всем активам. Вы узнаете, как использовать свой API-ключ, время сервера и подпись для безопасного доступа к данным аккаунта, а также как сохранять ответ в файл для дальнейшего использования.