English 日本語
preview
Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 01): Aufbau der SQLite3-Bibliothek, inspiriert von Python

Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 01): Aufbau der SQLite3-Bibliothek, inspiriert von Python

MetaTrader 5Handelssysteme |
98 0
Omega J Msigwa
Omega J Msigwa

Inhalt


Einführung

Ist es Ihnen schon einmal passiert, dass Sie ein oder zwei Ihrer bevorzugten Module, Bibliotheken, Frameworks usw. in einer anderen Programmiersprache als MQL5 innerhalb von MQL5 haben wollten? Das passiert mir sehr oft.

Es gibt eine große Anzahl von Entwicklern in der MQL5-Gemeinschaft, die aus verschiedenen Programmierbereichen kommen; einige kommen aus der Web-Entwicklung wie ich, andere aus der Android-Entwicklung und viele weitere Programmierbereiche sind heute vertreten. Das bedeutet, dass die meisten Programmierer mit verschiedenen Sprachen wie JavaScript, Java, Python, C++, C#, um nur einige zu nennen, vertraut sind.

In diesen verschiedenen Sprachen stoßen Programmierer auf verschiedene Kodierwerkzeuge (Module), nützliche Module, die wir einfach überall verwenden wollen. Ich verwende zum Beispiel gerne das in Python angebotene NumPy-Modul für mathematische Berechnungen, und zwar so sehr, dass ich eine ähnliche Bibliothek einmal in MQL5, in diesem Artikel, implementieren musste.

Der Versuch, ein Modul, ein Werkzeug, ein Framework usw. von einer Sprache in eine andere – in diesem Fall in MQL5 – zu implementieren, könnte zwar aufgrund der unterschiedlichen Natur der Programmiersprachen zu leicht unterschiedlichen Funktionen und Ergebnissen führen, aber eine ähnliche Syntax oder Erfahrung könnte ausreichen, um die Produktentwicklung in MQL5 für Entwickler, die mit anderen Sprachen vertraut sind, einfach und unterhaltsam zu gestalten. Ganz zu schweigen davon, dass wir dabei vielleicht einige wertvolle Informationen lernen, die unsere Programmierkenntnisse im Allgemeinen festigen könnten.

In dieser neuen Artikelserie werden wir nicht jedes Modul aus anderen Sprachen implementieren, sondern jedes in MQL5 praktische Modul aus einer anderen Sprache. Zum Beispiel Module für mathematische Berechnungen, Datenspeicherung, Datenanalyse usw.

Angefangen mit dem Modul Modulsqlite3, das in der Programmiersprache Python enthalten ist.


Was ist Sqlite3, das in Python angeboten wird?

Lassen Sie uns zunächst eine SQLite-Datenbank verstehen:

Eine SQLite-Datenbank ist eine leichtgewichtige, in sich geschlossene, serverlose SQL-Datenbank-Engine. Es wird häufig in Anwendungen eingesetzt, die eine einfache, eingebettete, lokale Datenspeicherlösung benötigen. Es handelt sich um eine dateibasierte Datenbank, die (Schema, Tabellen, Indizes und Daten) alle in einer einzigen Datei, entweder .sqlite oder .db, speichert.

Im Gegensatz zu den Datenbanken MySQL oder PostgreSQL, die eine gewisse Einrichtung, einen Server und Verwaltungskonfigurationen erfordern, ist SQLite sofort einsatzbereit, da es direkt auf der Festplatte liest und schreibt.

Die Programmiersprache MQL5 verfügt über integrierte Funktionen für die Arbeit mit einer SQLite-Datenbank; diese integrierten Funktionen sind ausreichend. Sie sind jedoch nicht so einfach zu nutzen, wie wenn Sie das sqlite3-Modul in Python verwenden würden.

Wenn Sie zum Beispiel versuchen, eine einfache „Beispiel“-Datenbank zu erstellen und einige Informationen in eine Tabelle mit dem Namen „users“ einzufügen, wird die Tabelle automatisch erstellt, wenn sie nicht existiert.

Verwendung des Python-Moduls 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

Verwendung nativer MQL5-Funktionen

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
 }

Wir sind uns alle einig, dass die Verwendung von sqlite3 in Python unseren Code sauberer gemacht hat als die Verwendung nativer MQL5-Funktionen, bei denen wir mit Fehlern, den zurückgegebenen Informationen oder dem Hinzufügen zur Datenbank umgehen müssen.

Das Modul sqlite3 übernimmt viele unnötige Schritte bei der Ausführung eines Befehls und der Verwaltung von Transaktionen, sodass es für Nutzer viel einfacher ist, Daten in die und aus der SQLite-Datenbank zu erhalten und einzufügen, ohne viel Aufwand zu betreiben.

Lassen Sie uns also versuchen, ein ähnliches Modul in MQL5 zu implementieren.


Verbinden mit einer SQLite-Datenbank

Die Methode connect in sqlite3 Python erstellt eine neue Datenbank, wenn der angegebene Name für die Datenbank nicht existiert, und gibt ein Verbindungsobjekt zurück, das die Verbindung zur On-Disk-Datenbank darstellt.

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

In MQL5 ist diese Verbindung dem Datenbank-Handle ähnlich, sodass wir technisch gesehen kein Handle in unserer MQL5-Bibliothek zurückgeben müssen, da wir es in allen Funktionen unserer Klasse verwenden werden.

class CSqlite3
  {
protected:

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

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

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

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

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

MQL5 gibt uns die Möglichkeit, die Daten entweder aus dem Datenpfad des Terminals (Ordner) oder dem Datenpfad Common (Ordner) zu speichern. Wenn das Argument common auf true gesetzt ist , wird die Datenbank aus dem gemeinsamen Datenpfad statt aus einem regulären Datenpfad gelesen.

Wir können dem Nutzer auch die Möglichkeit geben, zu wählen, ob eine Datenbank im Speicher (RAM) oder auf der Festplatte geöffnet werden soll, wenn also die Variable database_in_memory auf true gesetzt ist. Die Datenbank wird im RAM statt auf der Festplatte erstellt und umgekehrt.

Wir ignorieren ein Flag, nämlich DATABASE_OPEN_READONLY, um die Datenbank im Modus ReadOnly zu öffnen. Der Grund dafür ist einfach: Wenn Sie nicht in eine Datenbank schreiben wollen, werden Sie die „INSERT“-Abfragen gar nicht erst ausführen. Macht das Sinn? 


Ausführen von SQL-Anweisungen

Dies ist eine der wichtigsten Funktionen, die wir bei der Arbeit mit SQLite-Datenbanken häufig verwenden. Mit dieser Funktion können wir die Informationen abrufen, die Werte in der Datenbank einfügen, aktualisieren, löschen, ändern usw.

Diese Funktion führt SQL-Anweisungen und Befehle direkt in unserer Datenbank aus.

In sqlite3 Python funktioniert die Funktion execute() nahtlos und mühelos. Es kann jeden beliebigen Befehl zum Einfügen oder Abrufen von Informationen entgegennehmen und weiß automatisch, wann und was es zurückgeben soll und was nicht.

In MQL5 haben wir eine eingebaute Funktion namens DatabaseExecute, die ähnlich wie execute() in sqlite3 Python ist. Sie führen beide eine Anfrage an eine bestimmte Datenbank oder Tabelle aus. Diese MQL5-Methode ist jedoch für die Ausführung aller Anfragen geeignet, außer für alle Anfragen mit dem Schlüsselwort „SELECT“ zum Lesen der Informationen aus der Datenbank. 

Um effektiv Informationen aus der Datenbank zu lesen, verwenden wir die Funktion DatabasePrepare, da sie ein Handle für eine Anfrage erstellt, die dann mit DatabaseRead ausgeführt werden kann.

Um eine ähnlich flexible Funktion in MQL5 zur Ausführung von Funktionen unabhängig vom Abfragetyp zu erstellen, müssen wir die Typen von SQL-Abfragen unterscheiden und die richtigen Informationen mit der richtigen Abfrage zurückgeben.

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

Ähnlich wie bei der Funktion execute in Python wird bei allen Anweisungen, die kein SELECT sind, implizit eine Transaktion mit der Funktion begin eröffnet.

Nachdem Sie die Funktion execute aufgerufen und einige Informationen in die Datenbank eingefügt haben, müssen Sie die Methode commit aufrufen, um die Änderungen in der Datenbank zu speichern. Wir werden diese Funktion später in diesem Beitrag besprechen.

Wenn die Funktion mit einer Anweisung vom Typ Nicht-SELECT oder -Abfrage aufgerufen wird, gibt sie keinen Wert zurück, da sie wahrscheinlich die Informationen in der Datenbank aktualisiert, ändert und einfügt. Wenn die Funktion jedoch mit einer Abfrage vom Typ SELECT aufgerufen wird, gibt sie eine Datenstruktur zurück. Lassen Sie uns diese Struktur im Detail besprechen.

1. Die Methode fetchone

Das in Python angebotene Modul sqlite3 ist in der Lage, dynamisch einen Teil oder die Gesamtheit der von der SQL-Anweisung erhaltenen Informationen zurückzugeben.

Angefangen mit der Möglichkeit, eine Zeile von Informationen aus der Datenbank zurückzugeben.

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

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

Obwohl die SQL-Anweisung alle in der SQLite-Datenbank verfügbaren Informationen anfordert, schränkt die Funktion fetchone die Funktion execute ein, mehr als eine Datenzeile aus der Datenbank zurückzugeben.

Datenbank.

Ausgabe:

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

Wir brauchen eine ähnliche Funktion in unserer MQL5-Klasse, innerhalb der Struktur namens execute_res_structure, die von der Funktion execute zurückgegeben wird.

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

 //... Other functions
}

Aus einer ähnlichen Datenbank in MetaEditor.

Im Folgenden wird eine Abfrage gesendet, die alle in der Datenbanktabelle verfügbaren Informationen anfordert und dann die Menge der zurückgegebenen Informationen auf eine einzige Datenzeile beschränkt.

#include <sqlite3.mqh>

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

Ausgabe:

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]

Sieht toll aus! Sie können nun mit minimalem Aufwand eine einzelne Zeile mit Werten aus der Datenbank abrufen. Die aktuelle Funktion ignoriert jedoch alle binären Werte und verarbeitet oder kodiert keine Zeichenketten, weshalb alle „TEXT“- oder Zeichenketten-Datentypen in einem zurückgegebenen Vektor auf Null gesetzt wurden.

Im Gegensatz zu Python-Arrays, die Werte verschiedener Datentypen enthalten können, können MQL5-Vektoren und -Arrays dies nicht; wir werden später sehen, wie wir damit umgehen können.

2. Die fetchall-Methode

Im Gegensatz zur Methode fetchone erhält diese Methode alle Informationen, die in der SQL-Anweisung der execute-Methode angefordert werden.

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
}

Diesmal geben wir eine Matrix anstelle eines Vektors zurück, um die gesamte Tabelle, die zweidimensional ist, unterbringen zu können.

Der knifflige Teil dieser Funktion besteht darin, die Größe der resultierenden Matrix dynamisch zu verändern. Datenbanktabellen können manchmal riesig sein (mit mehr als 100.000 Zeilen), und es ist schwierig, die Größe der Datenbank oder der Tabellen in Form von Zeilen zu kennen. Die Größenänderung der sich ergebenden Matrix bei jeder Iteration kann diese Funktion also extrem langsam machen, je mehr wir die Zeilen in einer Schleife durchlaufen, denn die Funktion Resize ist eine der rechenintensivsten Funktionen in MQL5.

Aus diesem Grund habe ich mich entschieden, die Größe der Matrix nach jeweils 1000 Iterationen in der obigen Funktion zu ändern, um die Anzahl der Aufrufe der Resize-Methode zu reduzieren.

Verwendung der Funktion.

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

Ausgaben in der Registerkarte Experten von 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]]

Alle zwölf in der Datenbank verfügbaren Zeilen wurden von der Ausführungsfunktion zurückgegeben.

Auch hier gilt, dass MQL5-Arrays keine inhomogenen Variablen in einem einzigen Array enthalten können, sodass wir am Ende Nullen für jede Zeile mit Strings aus der Datenbanktabelle haben. Zumindest im Moment.

3. Die Methode fetchmany

Ähnlich wie die Methode fetchall gibt diese Funktion eine Matrix zurück, die Werte aus der Tabelle enthält, aber diese Methode gibt Ihnen die Kontrolle über die Anzahl der Zeilen, die Sie zurückgeben möchten.

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
}

Verwendung der Funktion.

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

Ausgabe:

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

Obwohl diese drei Funktionen in der Lage sind, die Menge der in einer Matrix zu speichernden Daten zu steuern, sind sie immer noch von Ihrer SQL-Anweisung abhängig; sie ist diejenige, die in erster Linie steuert, welche Informationen zurückgegeben werden sollen. Die oben genannten 3 Funktionen sind nur Gateways für den Empfang der von der SQL-Anweisung angeforderten Daten.

4. Zusätzliche Methode, Prüfung des Status der Ausführungsfunktion

Standardmäßig gibt diese Funktion eine Struktur mit Funktionen zurück, die für die Rückgabe der nach einer SQL-Anweisung vom Typ „SELECT“ erhaltenen Daten nützlich sind. Allerdings kann es vorkommen, dass die Execute-Methode für eine Nicht-SELECT-Anweisung in SQL aufgerufen wird, die keine Auswahl ist, sodass diese Funktion keine Daten zurückgibt, die zur Überprüfung des Erfolgs verwendet werden könnten. 

Um dies zu erreichen, müssen wir die Variable boolean in der Struktur verwenden, die von der Funktion execute zurückgegeben wird. 

{
    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!");
 }

Die boolesche Variable ist eine Variable vom Typ bool, sie wird wahr, wenn die Funktion erfolgreich war, und falsch, wenn sie fehlgeschlagen ist.

Ausgabe:

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!

Um zu überprüfen, ob die Funktion erfolgreich war, wenn sie mit einer „SELECT“-Abfrage gegeben wird, die einige Daten zurückgibt, müssen Sie die Größe der zurückgegebenen Matrix oder des Vektors bewerten.

Wenn sie leer sind (rows==0 für eine Matrix und size==0 für einen Vektor), bedeutet dies, dass die Ausführungsfunktion fehlgeschlagen ist.


Arbeiten mit Text (strings) aus der Datenbank

Wie im vorangegangenen Abschnitt erläutert, ist die vereinfachte Ausführungsfunktion nicht in der Lage, String-Werte in einer Matrix oder einem Vektor zurückzugeben, und das wissen wir; Strings oder String-Werte können genauso nützlich sein wie andere Variablen, sodass Sie, nachdem Sie alle in einer Matrix gespeicherten Werte aus der Datenbank erhalten haben, alle Spalten mit String-Werten separat extrahieren müssen. 

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

Ausgehend von diesem Array von String-Werten können Sie Wege finden, sie in Variablen zu kodieren, die von der Matrix akzeptiert werden (double, float usw.), und sie wieder in die Matrix einzufügen.

Ausgabe:

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" 


Steuerung von Datenbanktransaktionen

Das Modul sqlite3 in Python bietet mehrere Methoden zur Steuerung, ob, wann und wie Datenbanktransaktionen geöffnet und geschlossen werden. Wir können uns in unserer Klasse an das gleiche Format anpassen, was die Handhabung von Transaktionen bei der Arbeit mit SQLite-Datenbanken erleichtert.

FunktionBeschreibungHinweis
bool CSqlite3::commit(void)
Bestätigt die aktuelle Transaktion und macht alle Änderungen an der Datenbank dauerhaft.
Sie ist nach den SQL-Abfragetypen INSERT, UPDATE oder DELETE erforderlich, wenn autocommit auf false gesetzt ist.
bool CSqlite3::rollback(void) 
Macht die aktuelle Transaktion rückgängig, wobei alle nicht bestätigten Änderungen rückgängig gemacht werden.Nützlich für die Fehlerbehandlung.
bool CSqlite3::begin(void)
Beginnt die Transaktion, die später in die Datenbank übertragen wird. Bevor Änderungen an der Datenbank vorgenommen werden, wird eine explizitere Syntax verwendet. Die Funktion execute() ruft sie automatisch auf, wenn autocommit auf true gesetzt ist.
bool CSqlite3::in_transaction()
Eine boolesche Funktion zur Überprüfung, ob die Transaktion aktiv ist oder nicht.Sie gibt true zurück, wenn die Transaktion gerade aktiv ist.
CSqlite3(bool autocommit=false)

Optional – nicht empfohlen – können Sie den Wert autocommit innerhalb eines Klassenkonstruktors ändern, um das automatische Commit der Transaktion innerhalb der Ausführungsfunktion zu aktivieren oder zu deaktivieren.

Die drei Funktionen (begin, rollback und commit) bauen auf den in MQL5 eingebauten Funktionen zur Behandlung von Datenbanktransaktionen auf.

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


Andere Methoden im Modul sqlite3

Diese Funktionen dieses Moduls werden zwar weniger häufig verwendet, sind aber dennoch für verschiedene Aufgaben nützlich.

1. Die Methode executemany

Mit dieser Funktion können Sie mit einem einzigen Funktionsaufruf viele Zeilen in eine Datenbanktabelle einfügen.

Angenommen, Sie haben eine Matrix mit mehreren Zeilen, wobei jede Spalte eine bestimmte Art von Indikatorwerten enthält, die Sie auf einmal in die Datenbank einfügen möchten – dies ist eine „goto“-Funktion.

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

Ausgabe:

Die Anzahl der Fragezeichen nach dem Schlüsselwort VALUES muss gleich der Anzahl der Spalten in der params-Matrix sein, damit diese Funktion fehlerfrei funktioniert.

Beachten Sie, dass Sie aufgrund von Matrixbeschränkungen auf homogene Variablen innerhalb einer einzigen Matrix mit dieser Funktion nicht gleichzeitig verschiedene Datentypen zu Ihrer Datenbank hinzufügen können, z. B.. Sie können nicht gleichzeitig eine Zeichenkette und eine Doppelspalte in die Datenbank einfügen.

2. Die Methode executescript

Diese Funktion ist praktisch, wenn Sie mehrere SQL-Anweisungen in einem Durchgang ausführen möchten. Erstellen Sie zum Beispiel Tabellen, fügen Sie Zeilen ein usw. in einer einzigen Anweisung. Die wichtigsten Merkmale dieser Funktion sind folgende.

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

  • Sie nimmt eine Zeichenkette aus mehreren SQL-Anweisungen, die durch Semikolons „;“ getrennt sind.
  • Sie führt sie alle auf einmal aus, nicht parametrisiert und nicht geparst wie execute() oder executemany().
  • Sie gibt keine Ergebnisse zurück, sondern führt nur die Befehlsgruppe aus.

Ausgabe:

Die Funktion executescript überträgt automatisch alle offenen Transaktionen, sodass Sie die Funktion commit in der Regel nicht explizit aufrufen müssen, es sei denn, dies ist zur Kontrolle erforderlich.

3. Zusätzliche Methode, die print_table Methode

Um die Funktionalität ähnlich wie cursor.description des sqlite3-Moduls in Python zu erhalten, das die Struktur einer SQL-Abfrage, einer Datenbank oder einer Tabelle zurückgibt.

Wir können die in MQL5 integrierte Funktion DatabasePrint verwenden, um ein ähnliches Ergebnis zu erzielen.

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

Verwendung:

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

Ausgabe:

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     


Protokollierung von Handelsgeschäften in der Datenbank

Wir haben einige einfache Beispiele gesehen, wie Sie SQL-Anweisungen ausführen, Werte in die Datenbank eingeben und vieles mehr. Tun wir endlich etwas Sinnvolles; wir fügen alle Handelsgeschäfte in der MetaTrader 5 Historie in eine SQLite Datenbank ein.

Dieses Beispiel stammt aus diesem Artikel.

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

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

Das Ergebnis.


Abschließende Überlegungen

Die Nachbildung des sqlite3-Moduls von Python in MQL5 war eine lohnende Herausforderung, die sowohl die Flexibilität als auch die Grenzen von MQL5 im Vergleich zu Python aufzeigt. Während MQL5 die eingebaute Unterstützung für fortgeschrittene Funktionen wie Autorisierer oder Kontextmanager für eine SQLite-Datenbank fehlt, kann man mit sorgfältiger Abstraktion und Methodenentwurf eine sehr ähnliche Entwicklererfahrung erreichen.

Diese nutzerdefinierte Klasse CSqlite3 ermöglicht es MQL5-Entwicklern nun, mit SQLite-Datenbanken auf strukturierte, auf Python-Art zu interagieren – komplett mit Unterstützung für Abfragen, Transaktionen, Commit/Rollback-Kontrolle und Fetch-Operationen wie fetchone(), fetchmany(), fetchall(), etc.

Wenn Sie von Python kommen, sollte Ihnen dieses Modul vertraut vorkommen und hoffentlich auch Spaß machen.

Peace out.


Tabelle der Anhänge

DateinameBeschreibung und Verwendung
Include\errordescription.mqhEnthält Beschreibungen aller von MQL5 und MetaTrader 5 erzeugten Fehlercodes
Include\sqlite3.mqhEnthält die Klasse CSqlite3 für die Arbeit mit SQLite-Datenbanken in einer Python-ähnlichen Weise.
Scripts\sqlite3 test.mq5 Ein Skript zum Testen der Klasse CSqlite3. 

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18640

Beigefügte Dateien |
Attachments.zip (12.89 KB)
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 72): Verwendung der Muster von MACD und OBV mit überwachtem Lernen MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 72): Verwendung der Muster von MACD und OBV mit überwachtem Lernen
Wir knüpfen an unseren letzten Artikel an, in dem wir das Indikatorpaar MACD und OBV vorgestellt haben, und untersuchen, wie dieses Paar durch maschinelles Lernen verbessert werden kann. MACD und OBV ergänzen sich in Bezug auf Trend und Volumen. Unser Ansatz des maschinellen Lernens verwendet ein neuronales Faltungsnetzwerk, das bei der Feinabstimmung der Prognosen dieses Indikatorpaares den Exponential-Kernel bei der Dimensionierung seiner Kerne und Kanäle einsetzt. Wie immer wird dies in einer nutzerdefinierten Signalklassendatei durchgeführt, die mit dem MQL5-Assistenten arbeitet, um einen Expert Advisor zusammenzustellen.
Beherrschung von Protokollaufzeichnungen (Teil 9): Implementierung des Builder-Musters und Hinzufügen von Standardkonfigurationen Beherrschung von Protokollaufzeichnungen (Teil 9): Implementierung des Builder-Musters und Hinzufügen von Standardkonfigurationen
Dieser Artikel zeigt, wie man die Verwendung der Logify-Bibliothek mit dem Builder-Muster und automatischen Standardkonfigurationen drastisch vereinfachen kann. Es erklärt die Struktur der spezialisierten Builder, wie man sie mit intelligenter Autovervollständigung verwendet und wie man ein funktionierendes Protokoll auch ohne manuelle Konfiguration sicherstellt. Es umfasst auch Anpassungen für MetaTrader 5 Build 5100.
Automatisieren von Handelsstrategien in MQL5 (Teil 21): Verbesserung des Handels mit neuronalen Netzen durch adaptive Lernraten Automatisieren von Handelsstrategien in MQL5 (Teil 21): Verbesserung des Handels mit neuronalen Netzen durch adaptive Lernraten
In diesem Artikel verbessern wir eine Handelsstrategie mit neuronalen Netzen in MQL5 mit einer adaptiven Lernrate, um die Genauigkeit zu erhöhen. Wir entwerfen und implementieren diesen Mechanismus und testen anschließend seine Leistungsfähigkeit. Der Artikel schließt mit Optimierungserkenntnissen für den algorithmischen Handel.
Senden von Nachrichten von MQL5 an Discord, Erstellen eines Discord-Bots für MetaTrader 5 Senden von Nachrichten von MQL5 an Discord, Erstellen eines Discord-Bots für MetaTrader 5
Ähnlich wie Telegram ist Discord in der Lage, Informationen und Nachrichten im JSON-Format über seine Kommunikations-APIs zu empfangen. In diesem Artikel werden wir untersuchen, wie Sie Discord-APIs verwenden können, um Handelssignale und Updates von MetaTrader 5 an Ihre Discord-Handelsgemeinschaft zu senden.