Marktsimulation: Erste Schritte mit SQL in MQL5 (IV)
Einführung
Willkommen zu einem weiteren Artikel in der Serie über die Erstellung eines Replay-/Simulationssystems.
Im vorherigen Artikel Marktsimulation: Erste Schritte mit SQL in MQL5 (III) haben wir gezeigt, wie ein SQL-Skript in eine in MQL5 erstellte ausführbare Datei eingebettet werden kann. Wir sind jedoch nicht dabei stehen geblieben. Wir haben auch einige Änderungen am Klassencode vorgenommen, um die Verwendung erheblich zu erleichtern, und natürlich die Art und Weise, wie der Code implementiert ist, ein wenig angepasst.
Obwohl der im vorherigen Artikel vorgestellte Klassencode sehr nützlich für die Erstellung einer Datenbank unter Verwendung des integrierten SQLite in MetaTrader 5 ist (was wir später besprechen werden), erlaubt uns dieser Code nicht, Abfragen aus MQL5 auszuführen.
Daher ist das Hauptziel dieses Artikels, so klar wie möglich zu erklären, wie diese Klasse modifiziert werden kann, um den für die Ausführung von Abfragen erforderlichen Code hinzuzufügen. Mit anderen Worten, wir werden hier ein System implementieren, um eine SQLite-Datenbank abzufragen und die Ergebnisse abzurufen.
Bevor wir sehen, wie das geht, müssen wir etwas sehr Wichtiges verstehen, wenn wir das verwenden, was wir in dieser Serie zeigen. Genau wegen solcher Nuancen verwenden wir SQLite noch nicht zusammen mit dem Code des Replay-/Simulationssystems, da wir so klar wie möglich erklären und zeigen wollen, wie die in MetaTrader 5 integrierte SQLite-Unterstützung tatsächlich funktioniert.
Viele denken vielleicht, dass das in MetaTrader 5 enthaltene SQLite genauso funktioniert wie die Version, auf die über eine DLL-Datei zugegriffen wird. Obwohl beide Optionen im Wesentlichen auf die gleiche Weise funktionieren, gibt es einige Besonderheiten bei der Verwendung der integrierten SQLite-Version in MetaTrader 5. Die Entwickler könnten dies in Zukunft ändern, aber man sollte nichts als selbstverständlich ansehen. Wenn Sie Zweifel haben, testen Sie zuerst und untersuchen Sie dann, wie Sie eventuell auftretende Probleme bei der Verwendung von SQLite, das in eine bestimmte Anwendung eingebettet ist, lösen können. Ich sage nicht, dass das Problem in einer bestimmten Anwendung liegt. Ich sage lediglich, dass Sie zuerst alles überprüfen und erst dann Schlussfolgerungen darüber ziehen sollten, ob die verwendete SQLite-Version nicht die ist, die Sie erwartet haben.
Das Problem verstehen
Zum Zeitpunkt der Erstellung dieses Artikels tritt das Problem in Version 5.00 Build 3815 vom 22. Juni 2023 auf. Ja, dieser Code wurde vor geraumer Zeit erstellt. Wenn Sie diese Zeilen lesen, versichere ich Ihnen, dass MetaTrader 5 bereits aktualisiert wurde und all dies veraltet sein wird. Ignorieren Sie dennoch nicht den Inhalt dieser Artikel, da sie Ihnen Dinge vermitteln könnten, die Sie vielleicht nicht einmal für möglich gehalten hätten.
Kommen wir nun zum Problem. Werfen Sie einen Blick auf das SQL-Skript unten.
01. PRAGMA FOREIGN_KEYS = ON; 02. 03. DROP TABLE IF EXISTS tb_Symbols; 04. DROP TABLE IF EXISTS tb_Quotes; 05. 06. CREATE TABLE IF NOT EXISTS tb_Symbols 07. ( 08. id PRIMARY KEY, 09. symbol NOT NULL UNIQUE 10. ); 11. 12. CREATE TABLE IF NOT EXISTS tb_Quotes 13. ( 14. of_day NOT NULL, 15. price NOT NULL, 16. fk_id NOT NULL, 17. FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id) 18. );
Code in SQLite
Dieses SQL-Skript erstellt zwei Tabellen, in denen eine Beziehung zwischen den Tabellen tb_Symbols und tb_Quotes besteht. Dies wird durch den in Zeile 17 definierten Fremdschlüssel garantiert. Wenn Sie sich nicht sicher sind, wovon wir sprechen, lesen Sie die vorherigen Artikel dieser Serie.
Betrachten Sie nun die Zeilen 3 und 4 desselben SQL-Skripts. Sie löschen Tabellen aus der SQL-Datenbankdatei. Egal, ob Sie es in MetaTrader 5 ausführen oder die SQLite-DLL-Bibliothek verwenden, wir erhalten das gleiche Ergebnis. Dies ist jedoch nicht immer der Fall. Der Grund liegt genau in Zeile 17, die die Beziehung zwischen den in tb_Symbols und tb_Quotes vorhandenen Werten herstellt.
„Warten Sie, was meinen Sie damit, wenn Sie sagen, dass wir nicht immer das gleiche Ergebnis erhalten?“ Funktioniert SQLite in MetaTrader 5 nicht genauso wie bei der Verwendung über eine DLL-Bibliothek? Warum sagen Sie, dass die Ausführung dieses Skripts zu unterschiedlichen Ergebnissen führt?“ Vielleicht habe ich mich nicht sehr gut ausgedrückt oder zumindest die Details nicht richtig erklärt. Tatsächlich führt die Ausführung zum gleichen Ergebnis; wenn jedoch Werte vorhanden sind, die eine Beziehung zwischen tb_Symbols und tb_Quotes herstellen, kann das integrierte SQLite in MetaTrader 5 die Tabellen nicht löschen und es tritt ein Fehler auf, wie Sie mit dem folgenden Test überprüfen können.
Führen Sie das untenstehende Skript zweimal hintereinander aus. Der erste Durchlauf wird fehlerfrei beendet. Aber beim zweiten Durchlauf erhalten wir das Bild, das den Code begleitet.
01. PRAGMA FOREIGN_KEYS = ON; 02. 03. DROP TABLE IF EXISTS tb_Symbols; 04. DROP TABLE IF EXISTS tb_Quotes; 05. 06. CREATE TABLE IF NOT EXISTS tb_Symbols 07. ( 08. id PRIMARY KEY, 09. symbol NOT NULL UNIQUE 10. ); 11. 12. CREATE TABLE IF NOT EXISTS tb_Quotes 13. ( 14. of_day NOT NULL, 15. price NOT NULL, 16. fk_id NOT NULL, 17. FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id) 18. ); 19. 20. INSERT INTO tb_Symbols (id, symbol) VALUES (1, 'PETR4'); 21. 22. INSERT INTO tb_Quotes (of_day, price, fk_id) VALUES ('2023-07-10', '22.00', 1);
Code in SQLite

Beachten Sie, dass die Ergebnisse bei einem Intervall von weniger als drei Sekunden zwischen den Ausführungen völlig unterschiedlich waren. Beim ersten Versuch wurde das SQL-Skript erfolgreich ausgeführt; unmittelbar danach, als dasselbe Skript erneut ausgeführt wurde, endete der Vorgang mit einem Fehler. Der gemeldete Fehlercode weist eindeutig darauf hin, dass das Problem mit der Beziehung zwischen den Tabellen zusammenhängt, und diese Beziehung wird genau in Zeile 17 des Skripts erstellt.
Der Fehler wurde jedoch nicht spezifisch durch Zeile 17 verursacht. Achten Sie auf das Bild oben: Der Code unserer Klasse, der für die Ausführung von SQL-Skripten vorgesehen ist, meldet, dass das Problem beim Ausführen von Zeile 3 in SQLite auftrat.
Dennoch muss ein Punkt hervorgehoben werden. Wenn wir dasselbe SQL-Skript direkt in SQLite ausführen, läuft es fehlerfrei durch. Wir haben dies bei der Besprechung der SQL-Programmierung demonstriert; falls Sie Zweifel haben, überprüfen Sie die vorherigen Artikel, in denen ich dies visuell gezeigt habe.
Man könnte meinen, das Problem liege beim in MetaTrader 5 integrierten SQLite, aber ich möchte keine Grundsatzdebatte schüren. Vielleicht enthält diese integrierte SQLite-Version eine eingebaute Schutzmaßnahme, die das Löschen von Tabellen mit aktiven Fremdschlüsseln verhindert. Ich kenne den genauen Grund für diese offensichtliche Asymmetrie zwischen dem eigenständig ausgeführten SQLite und dem in MetaTrader 5 integrierten SQLite nicht. Und dies ist nicht der einzige Unterschied: Sie werden auch Diskrepanzen feststellen, zum Beispiel beim Versuch, einen Trigger mit der integrierten SQLite-Bibliothek zu erstellen.
Daher werde ich es Ihnen nicht verübeln, wenn Sie sich entscheiden, SQLite über eine DLL zu verwenden. Dennoch ermöglichen meine Tests zufolge das Lösen vieler Aufgaben mit dem integrierten SQLite auch ohne die Verwendung einer DLL. Sollten die Verhaltensunterschiede zu kritisch werden, werden wir einen anderen Weg finden. Vorerst können wir mit der integrierten SQLite-Version weiterarbeiten.
Wenn wir also das integrierte SQLite in MetaTrader 5 verwenden und Datenbanktabellen löschen müssen, ist der einfachste Weg, die Datenbankdatei zu löschen. Wenn das in die ausführbare Datei eingebettete Skript erneut ausgeführt wird, treten keine Konflikte auf.
Also gut. Wenn alles oben Genannte klar ist, lassen Sie uns zum nächsten Schritt übergehen – der interessanteste Teil beginnt genau jetzt. Ich empfehle Ihnen, sich so gut wie möglich auf die Erklärung zu konzentrieren.
In dem Artikel Marktsimulation (Teil 25): Erste Schritte mit SQL in MQL5 (I) erwähnten wir, dass nur sechs der in MQL5 verfügbaren Funktionen ausreichen, um mit SQL zu arbeiten. In den vorherigen Artikeln haben wir bereits drei davon untersucht, und sie sind bereits in der Klasse C_DB_SQL implementiert. Jetzt ist es an der Zeit zu lernen und zu verstehen, warum wir nur die anderen drei Funktionen benötigen. Um die Erklärungen ordnungsgemäß zu trennen, werden wir als Nächstes ein neues Thema behandeln.
Die letzten drei erforderlichen Funktionen
Was ich als Nächstes erkläre, sollte Ihnen keinesfalls das Gefühl geben, unzureichend zu sein, weil Sie es früher nicht verstanden haben, egal ob Sie bereits Datenbanken in MQL5 verwenden oder gerade erst damit anfangen. Ich zeige einfach, wie ich es mache: Es ist weder der korrekteste noch der am wenigsten geeignete Weg, sondern lediglich meine Herangehensweise an die Arbeit.
Wenn ich sage, dass nur sechs MQL5-Funktionen ausreichen, um auf SQL zuzugreifen, behaupte ich nicht, dass Sie falsch liegen, wenn Sie mehr Funktionen oder Prozeduren verwenden. Im Gegenteil, ich bewundere diejenigen, die eine Sprache oder ein Werkzeug eingehend studieren. Mein Ansatz besteht darin, mich auf das Nötigste zu beschränken.
Ich merke mir normalerweise nicht jedes Detail jeder Funktion in jeder Sprache oder jedem Werkzeug, das ich verwende; das würde Zeit kosten, und die verfügbaren Fristen sind oft begrenzt. Ich ziehe es vor, die Aufgabe pünktlich zu erledigen, unabhängig von ihrem Komplexitätsgrad.
Um zu überprüfen, ob mein Ansatz korrekt ist, werden wir erneut Änderungen am Datenbankskript vornehmen. Ich schlage vor, dies anders zu machen, um unflexible Techniken zu vermeiden: Erstellen Sie ein neues Skript und nennen Sie es script 02.sql. Wenn Sie möchten, fügen Sie dessen Inhalt an das Ende der Datei script 01.sql an; Sie erhalten genau das gleiche Ergebnis.
Der einzige Unterschied, wenn Sie meinem Rat folgen, besteht darin, dass Sie die Hauptdatei leicht modifizieren müssen. Da nicht jeder genau weiß, wo die Änderungen vorgenommen werden müssen, werden wir uns diese Lösung im Detail ansehen und zeigen, wie sie implementiert wird, um das korrekte Ergebnis zu erhalten.
Das Ziel hierbei ist es, Daten in die Datenbank einzufügen, damit später Abfragen ausgeführt werden können. Betrachten Sie die folgenden SQL-Skripte.
01. PRAGMA FOREIGN_KEYS = ON; 02. 03. CREATE TABLE IF NOT EXISTS tb_Symbols 04. ( 05. id PRIMARY KEY, 06. symbol NOT NULL UNIQUE 07. ); 08. 09. CREATE TABLE IF NOT EXISTS tb_Quotes 10. ( 11. of_day NOT NULL, 12. price NOT NULL, 13. fk_id NOT NULL, 14. FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id) 15. );
Code in SQLite
01. INSERT INTO tb_Symbols (id, symbol) VALUES
02. (2, 'PETR4'),
03. (1, 'ITUB3'),
04. (3, 'VALE3');
05.
06. INSERT INTO tb_Quotes (of_day, price, fk_id) VALUES
07. ('2023-07-10', '22.00', 1),
08. ('2023-07-11', '22.20', 1),
09. ('2023-07-12', '22.40', 1),
10. ('2023-07-13', '22.30', 1),
11. ('2023-07-14', '22.60', 1),
12. ('2023-07-10', '26.00', 2),
13. ('2023-07-11', '26.20', 2),
14. ('2023-07-12', '26.40', 2),
15. ('2023-07-13', '26.30', 2),
16. ('2023-07-14', '26.60', 2),
17. ('2023-07-10', '62.00', 3),
18. ('2023-07-11', '62.20', 3),
19. ('2023-07-12', '62.40', 3),
20. ('2023-07-13', '62.30', 3),
21. ('2023-07-14', '62.60', 3); Code in SQLite
Obwohl es zwei davon gibt, können Sie deren Inhalte zusammenführen und das Programm mit der bereits gezeigten MQL5-Hauptdatei ausführen. Wenn Sie sich entscheiden, sie getrennt zu halten, müssen Sie den MQL5-Hauptcode wie unten gezeigt ändern.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Basic script for SQL database written in MQL5" 04. #property version "1.00" 05. #property script_show_inputs 06. //+------------------------------------------------------------------+ 07. #resource "\\Files\\Script 01.sql" as string SQL_Create 08. #resource "\\Files\\Script 02.sql" as string SQL_Insert 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\SQL\C_DB_SQL.mqh> 11. //+------------------------------------------------------------------+ 12. input string user01 = "DataBase01"; //Database File Name 13. //+------------------------------------------------------------------+ 14. C_DB_SQL *SQL; 15. //+------------------------------------------------------------------+ 16. const string ExecScripts(void) 17. { 18. string szMsg = (*SQL).ExecResourceSQL(SQL_Create); 19. if (szMsg != NULL) return szMsg; 20. return (*SQL).ExecResourceSQL(SQL_Insert); 21. }; 22. //+------------------------------------------------------------------+ 23. void OnStart() 24. { 25. string szMsg; 26. 27. SQL = new C_DB_SQL(user01); 28. 29. szMsg = ExecScripts(); 30. Print(szMsg == NULL ? "Result of executing the SQL script: Success" : szMsg); 31. 32. delete SQL; 33. } 34. //+------------------------------------------------------------------+
Der MQL5-Code
Sie können sehen, dass alles sehr einfach, praktisch und klar ist. Das erste SQL-Skript erstellt die erforderlichen Tabellen; das zweite fügt Daten in die Datenbank ein, indem es einige Werte hinzufügt. Da diese Skripte bereits zuvor erschienen sind, wissen Sie wahrscheinlich, was jedes von ihnen tut und wie sie interagieren, um die Datensätze anzulegen, mit denen wir arbeiten werden.
Alles klar, betrachten Sie nun den Code in MQL5. Bitte beachten Sie, dass wir Zeile 8 hinzugefügt und den Namen der vorhandenen Ressource in einen passenderen geändert haben. Aufgrund einiger Besonderheiten von Zeile 29 rufen wir die Funktion auf, die sich in Zeile 16 befindet. Ihr Zweck ist es, zwei SQL-Skripte auszuführen, die letztlich als interne Ressourcen der in MQL5 erzeugten ausführbaren Datei eingebunden werden. Zuerst fordern wir in Zeile 18 die Ausführung des SQL-Skripts an, das die Tabellen erstellt. Dann überprüfen wir in Zeile 19 das Ergebnis. Wenn das erste SQL-Skript erfolgreich ausgeführt wird, führen wir in Zeile 20 das zweite aus.
Die Aufgabe des zweiten SQL-Skripts besteht darin, Daten in die Datenbank einzufügen. Dies ist eine einfache Aufgabe und sie erfordert keinen großen Aufwand. Damit haben wir alles Notwendige für die nächste Implementierungsphase: die Entwicklung des Teils, der Abfragen an die SQL-Datenbank aus dem MQL5-Code heraus ausführt. Aber lassen Sie uns zuerst den Inhalt der Datenbankdatei untersuchen; hierfür können wir bequem MetaEditor verwenden, wie unten dargestellt:

Sie können sehen, dass der erwartete Inhalt tatsächlich zur Datenbank hinzugefügt wurde. Nachdem wir dies bestätigt haben, lassen Sie uns zum nächsten Schritt übergehen. Lassen Sie uns zur Header-Datei C_DB_SQL.mqh zurückkehren. Denken Sie daran, dass die Klasse in ihrer ursprünglichen Form keine Abfrageergebnisse zurückgeben kann. Obwohl es möglich ist, SELECT-Befehle auszuführen, wie wir vor Beginn der Arbeit mit dem MQL5-Code erklärt haben, ist dies von geringem Nutzen, wenn wir die Ergebnisse nicht direkt in MQL5 selbst verwenden können.
Die MQL5-Entwickler haben mehrere Funktionen bereitgestellt, mit denen sich SQL-Abfrageergebnisse abrufen lassen. Lassen Sie uns noch einmal betonen, dass bei allem, was wir als Nächstes betrachten werden, die Verwendung des integrierten SQLite in MetaTrader 5 vorausgesetzt wird. Wenn Sie SQLite über eine DLL verwenden, ändert sich das Verfahren leicht; vielleicht erklären wir später, wie das geht. Vorerst werden wir mit der integrierten SQLite-Datenbank arbeiten. Ebenso erfordert der Zugriff auf eine Datenbank über Sockets einen anderen Ansatz. Verwechseln Sie diese nicht: Jede Situation erfordert ihre eigene Lösung, auch wenn das Endergebnis gleich erscheinen mag.
Vor diesem Hintergrund können wir uns auf den Code selbst konzentrieren. Wir müssen sicherstellen, dass Abfragen ausgeführt und ihre Ergebnisse bei Bedarf leicht interpretiert werden können. Dazu müssen wir zwei neue Funktionen verwenden. Die erste ist DatabasePrepare, die für die Ausführung von Datenbankabfragen vorgesehen ist. Beachten Sie, dass wir DatabaseExecute nicht verwenden sollten, wie viele vielleicht annehmen oder denken. Der Grund dafür ist, dass DatabaseExecute uns nur mitteilt, ob der SQL-Befehl erfolgreich ausgeführt wurde oder nicht. Es bietet keine Möglichkeit, die von der Abfrage zurückgegebene Ergebnismenge zu untersuchen.
Genau deshalb verwenden wir DatabasePrepare, da diese Funktion es uns ermöglicht, die Daten zu untersuchen, die von der Abfrage an unsere MQL5-Anwendung zurückgegeben werden. Dies ist der erste Teil und die erste Funktion, die wir benötigen. Darüber hinaus benötigen wir eine weitere Funktion, die zum Lesen der von einer SQL-Abfrage zurückgegebenen Daten vorgesehen ist. Doch zunächst schauen wir uns an, wie der Klassencode geändert wurde, um DatabasePrepare einzubeziehen. Der Klassencode ist unten dargestellt.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Service Graphics\Support\C_Array.mqh" 005. //+------------------------------------------------------------------+ 006. class C_DB_SQL 007. { 008. private : 009. C_Array m_Arr; 010. int m_handleDB, 011. m_Request; 012. //+------------------------------------------------------------------+ 013. void Convert(const char &buff[], const int size) 014. { 015. string sz0 = ""; 016. bool b0, b1, bs1, bs2, bc0, bc1, bc; 017. int nLine = 1; 018. 019. b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false; 020. for (int count = 0, nC0 = nLine; count < size; count++) 021. { 022. switch (buff[count]) 023. { 024. case '\t': 025. sz0 += (bs1 || bs2 ? "\t" : ""); 026. break; 027. case '\n': 028. nC0++; 029. case '\r': 030. bc0 = false; 031. break; 032. case ';': 033. b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true); 034. default: 035. switch (buff[count]) 036. { 037. case '"': 038. bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1); 039. break; 040. case '\'': 041. bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2); 042. break; 043. } 044. if (((count + 1) < size) && (!bs1) && (!bs2)) 045. { 046. if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true; 047. if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true; 048. if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false; 049. if (bc) 050. { 051. count += 1; 052. bc = false; 053. continue; 054. } 055. } 056. if (!(bc0 || bc1)) 057. { 058. if ((!b1) && (buff[count] > ' ')) 059. { 060. b1 = true; 061. nLine = nC0; 062. } 063. sz0 += (b1 ? StringFormat("%c", buff[count]) : ""); 064. } 065. } 066. if (b0) 067. { 068. m_Arr.Add(sz0, nLine); 069. sz0 = ""; 070. b0 = b1 = false; 071. } 072. } 073. } 074. //+------------------------------------------------------------------+ 075. const string ExecSQL(void) 076. { 077. string szCmd; 078. 079. for (int count = 0, nLine; count >= 0; count++) 080. { 081. szCmd = m_Arr.At(count, nLine); 082. if (nLine < 0) break; 083. if (!ExecCommandSQL(szCmd)) 084. return StringFormat("Execution of line %d of the SQL script failed...", nLine); 085. } 086. 087. return NULL; 088. } 089. //+------------------------------------------------------------------+ 090. public : 091. //+------------------------------------------------------------------+ 092. C_DB_SQL(const string szFileName = ":memory:") 093. { 094. m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE); 095. m_Request = INVALID_HANDLE; 096. } 097. //+------------------------------------------------------------------+ 098. ~C_DB_SQL() 099. { 100. DatabaseClose(m_handleDB); 101. } 102. //+------------------------------------------------------------------+ 103. const string ExecResourceSQL(const string szResource) 104. { 105. char buff[]; 106. int size; 107. 108. ArrayResize(buff, size = StringLen(szResource)); 109. StringToCharArray(szResource, buff); 110. Convert(buff, size); 111. ArrayFree(buff); 112. 113. return ExecSQL(); 114. } 115. //+------------------------------------------------------------------+ 116. const string ExecScriptSQL(const string szFileName) 117. { 118. int file, size; 119. char buff[]; 120. 121. if ((file = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE) 122. return StringFormat("Unable to open script file: %s", szFileName); 123. ArrayResize(buff, size = (int) FileSize(file)); 124. FileReadArray(file, buff); 125. FileClose(file); 126. Convert(buff, size); 127. ArrayFree(buff); 128. 129. return ExecSQL(); 130. } 131. //+------------------------------------------------------------------+ 132. bool ExecCommandSQL(const string szCmd) 133. { 134. return (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szCmd)); 135. } 136. //+------------------------------------------------------------------+ 137. bool ExecRequestOfData(const string szCmd) 138. { 139. if (m_Request != INVALID_HANDLE) DatabaseFinalize(m_Request); 140. return ((m_Request = DatabasePrepare(m_handleDB, szCmd)) != INVALID_HANDLE); 141. } 142. //+------------------------------------------------------------------+ 143. }; 144. //+------------------------------------------------------------------+
C_DB_SQL.mqh Code
Bitte beachten Sie, dass wir in Zeile 11 eine neue Variable hinzugefügt haben. Diese Variable wird verwendet, um die von SQL als Abfrageergebnis zurückgegebenen Daten zu lesen. Wie alle Variablen wird sie in Zeile 95 initialisiert. Achten Sie auf den Wert, den wir verwenden. Gehen wir nun zu Zeile 137, wo die Funktion ExecRequestOfData implementiert ist. Dies ist die Funktion, die wir verwenden werden, um einen Abfragebefehl an SQL zu senden. Normalerweise handelt es sich dabei um einen SELECT-FROM-Befehl, aber wie wir später sehen werden, kann er hier recht spezifisch sein.
Bitte beachten Sie in jedem Fall, dass wir in Zeile 139 prüfen, ob die Variable m_Request einen anderen Wert als INVALID_HANDLE hat. Wenn dies der Fall ist, bedeutet dies, dass noch eine zuvor vorbereitete Abfrage bzw. Anweisung offen ist. Mit anderen Worten: Wir möchten die vorherige vorbereitete Anweisung abschließen und ihre Ressourcen freigeben, bevor wir eine neue ausführen. Dafür verwenden wir eine weitere Funktion: DatabaseFinalize. Ihr Zweck ist es, die vorherige vorbereitete Anweisung abzuschließen und ihre Ressourcen freizugeben; damit stellt sie die dritte Funktion dar, die wir in dieser Phase der Erweiterung der Klasse C_DB_SQL benötigen werden.
Nachdem die vorherige vorbereitete Anweisung abgeschlossen wurde, falls eine vorhanden war, kann in Zeile 140 eine neue Abfrage gesendet werden. Beachten Sie, dass der Datenbankname nicht angegeben werden muss. Dies geschah bei der Erstellung der Klasse. Wir müssen lediglich angeben, welcher Befehl verwendet werden soll, und der Befehl selbst kommt vom Aufrufer, was wir uns später ansehen werden. Wenn die Abfrage erfolgreich ist, gibt DatabasePrepare einen numerischen Wert zurück. Diese Daten werden zur späteren Verwendung in der Variablen m_Request gespeichert. Wenn die Abfrage fehlschlägt, wird INVALID_HANDLE zurückgegeben. Folglich gibt ExecRequestOfData false zurück und teilt dem Aufrufer mit, dass die Abfrageausführung fehlgeschlagen ist.
In Ordnung, aber es gibt hier eine Nuance: Warum geben wir die Daten nicht direkt aus der Funktion ExecRequestOfData selbst zurück? Warum geben wir nur Feedback darüber, ob die Abfrage erfolgreich war oder fehlgeschlagen ist? Der Grund ist ganz einfach. Aus dem einen oder anderen Grund möchten wir möglicherweise eine Abfrage durchführen, die bestimmte Werte oder Tabellenfelder zurückgibt. Je nach den Besonderheiten eines bestimmten Bereichs können wir unterschiedliche Richtungen wählen oder unterschiedliche Entscheidungen treffen. Dies würde uns dazu zwingen, eine neue Abfrage an die Datenbank auszuführen. Obwohl dies kein Fehler ist, führt ein solcher Ansatz zu redundanten Datenbankabfragen, was besonders in einem Client-Server-Szenario kritisch ist.
Alle erforderlichen Felder können mit einer einzigen Abfrage abgerufen werden. Neben der Optimierung von SQL-Abfragen an die Datenbank hilft uns dies auch dabei, den Hauptcode ein wenig zu beschleunigen. Aus diesem Grund wurde das Lesen von Daten in eine dedizierte Funktion ausgelagert, die ausschließlich für diesen Zweck vorgesehen ist.
Dies mag wie völliger Unsinn und sogar etwas unpraktisch erscheinen. Bedenken Sie jedoch, dass der Hauptcode sehr einfach angepasst werden kann, um ein bestimmtes Feld zu lesen, gerade weil die Abfrageausführung vom Lesen des erhaltenen Ergebnisses getrennt ist. Dies mag sehr schwer zu erklären sein, da wir beim Ausführen einer SQL-Abfrage mehrere Rückgabewerte erhalten können, obwohl wir davon unter Umständen nur eine einzige Spalte aus einer der Tabellen nutzen. Dies wird in der vorherigen Animation im selben Artikel gezeigt.
Aber wenn die Abfrage korrekt formuliert und gezielt aufgebaut ist, erhalten wir oft ein einzelnes Ergebnis. Obwohl es auch möglich ist, überhaupt keines zu erhalten. Dies geschieht, weil die in der Abfrage verwendeten Kriterien nicht in der Datenbank gefunden wurden.
Genau hier ergibt sich eine Nuance, die nur wenige Menschen wirklich verstehen. Bevor ich die Datenlesefunktion zeige, möchte ich die Gelegenheit nutzen, diesen Punkt zu erläutern, da dieses Thema für Sie von Interesse sein könnte.
Viele Menschen neigen dazu, SQL zu unterschätzen oder es gar nicht zu verwenden, weil sie nicht vollständig verstehen, wie es tatsächlich funktioniert. Wenn wir eine SQL-Datenbank abfragen, suchen wir nicht immer nach einer allgemeinen Antwort. In einigen Fällen benötigen wir möglicherweise eine sehr spezifische und praktische Antwort. Wenn eine Datenbank mit einer geeigneten Struktur und einem geeigneten Datenmodell erstellt wird, kann fast jede Art von Information darin integriert werden. Dies ist ziemlich interessant, da Menschen eine Datenbank oft nur als eine Menge von Zahlen und alphanumerischen Zeichen zum Speichern von Daten über Verkäufe, Kunden, Produkte oder Ähnliches betrachten.
Aber wenn Sie richtig verstehen, wie SQL funktioniert und wie man damit arbeitet, können Sie ein System, oder genauer gesagt einen kleinen Roboter, erstellen, der lernen kann, mit einem bestimmten Symbol zu arbeiten. Und das alles, ohne ein Handelsmodell erstellen zu müssen. Wir speichern einfach eine Reihe von Kursen in der Datenbank und kombinieren SQL-Abfragen mit MQL5-Berechnungen, die über die Möglichkeiten von SQL hinausgehen. So entsteht ein Mechanismus, der lernen kann, vollautomatisch am Markt zu handeln. Dieser Mechanismus wird eigenständig lernen, mögliche zukünftige Bewegungen dieses Symbols vorherzusagen.
Ich habe dies bereits vor einiger Zeit in derselben Artikelserie erwähnt, die sich mit dem Replay-/Simulationssystem befasst. Vielleicht haben Sie noch nicht gesehen oder bemerkt, wie viel mit diesem einfachen System erreicht werden kann, dessen Erstellungsprozess ich jetzt demonstriere. Aber wenn Sie dieses Thema richtig studieren, können Sie genau den Mechanismus erstellen, über den wir gesprochen haben. Natürlich müssen wir bestimmte mathematische Methoden und Programmierkenntnisse einsetzen, um das gewünschte Ergebnis zu erzielen. Genau deshalb geben wir das Abfrageergebnis zum Zeitpunkt der Abfrageausführung nicht direkt von SQL an MQL5 zurück, wie im obigen Code gezeigt.
Es kann sich herausstellen, dass diese Abfrage so geschrieben wurde, dass wir die Ergebnisse auf eine völlig andere Weise verwenden können, als viele erwarten würden. Da die MQL5-Entwickler uns die Verwendung dieses Mechanismus erlaubt haben, halte ich es nicht für sinnvoll, seine Funktionsweise zu ändern. Und da es ziemlich schwierig ist, in wenigen Worten genau zu erklären, wie der Lesevorgang durchgeführt wird, werden wir sowohl diese Erklärung als auch die Änderungen, die am Hauptcode vorgenommen werden müssen, für den nächsten Artikel zurückstellen.
Abschließende Überlegungen
In diesem Artikel haben wir besprochen, wie wir bestimmte Dinge tun können. Wir haben die Tatsache erwähnt, dass es möglich ist, einen Roboter zu erstellen, der lernen kann zu handeln, ohne dass dafür ein Handelsmodell entworfen werden muss. Wir haben das Problem besprochen, das bei dem Versuch auftritt, einige SQL-Funktionen direkt im integrierten SQLite in MetaTrader 5 zu verwenden, und gezeigt, wie man es umgehen kann. Trotz alledem haben wir jedoch noch nicht gezeigt, wie man Abfrageergebnisse tatsächlich mit MQL5 selbst erhält. Der Grund dafür ist, dass wir eine Funktion erstellen können, die sich automatisch an das erwartete Abfrageergebnis anpasst. Dies dient dazu, dass Sie verstehen, warum wir gesagt haben, dass wir nur sechs Funktionen benötigen, die MQL5 uns bei der Arbeit mit SQL zur Verfügung stellt.
Und ein letztes Detail: MQL5 verfügt bereits über Mechanismen zur Verwendung von ONNX-Modellen. Die Verwendung von ONNX-Modellen ist weitaus besser geeignet als SQL, um einen Roboter zu erstellen, der eigenständig das Handeln am Markt erlernen kann. Aber nichts hindert Sie daran, für diesen Zweck eine SQL-basierte Modellierung zu verwenden. Die Hauptsache sind das Lernen und die korrekte Anwendung des gewonnenen Wissens.
| Datei | Beschreibung |
|---|---|
| Experts\Expert Advisor.mq5 | Zeigt die Interaktion zwischen Chart Trade und dem Expert Advisor (für die Interaktion ist „Mouse Study“ erforderlich). |
| Indicators\Chart Trade.mq5 | Erstellt ein Fenster zur Konfiguration des zu versendenden Auftrags (für die Interaktion ist „Mouse Study“ erforderlich). |
| Indicators\Market Replay.mq5 | Erstellt Steuerelemente für die Interaktion mit dem Replay-/Simulationsdienst (Mouse Study für die Interaktion erforderlich). |
| Indicators\Mouse Study.mq5 | Ermöglicht die Interaktion zwischen grafischen Steuerelementen und dem Benutzer (sowohl für das Replay-/Simulationssystem als auch für den realen Markt erforderlich). |
| Services\Market Replay.mq5 | Erstellt und verwaltet den Markt-Replay-/Simulationsdienst (die Hauptdatei des gesamten Systems). |
| Code VS C++\Servidor.cpp | Erstellt und verwaltet einen in C++ entwickelten Server-Socket (MiniChat-Version). |
| Code in Python\Server.py | Erstellt und verwaltet einen Python-Socket für die Kommunikation zwischen MetaTrader 5 und Excel. |
| Indicators\Mini Chat.mq5 | Ermöglicht die Implementierung eines Mini-Chats über einen Indikator (erfordert die Verwendung eines Servers). |
| Experts\Mini Chat.mq5 | Ermöglicht die Implementierung eines Mini-Chats mithilfe eines Expert Advisors (ein Server ist erforderlich). |
| Scripts\SQLite.mq5 | Demonstriert die Verwendung eines SQL-Skripts mit MQL5. |
| Files\Script 01.sql | Demonstriert die Erstellung einer einfachen Tabelle mit einem Fremdschlüssel. |
| Files\Script 02.sql | Zeigt, wie man Werte in eine Tabelle einträgt. |
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/13110
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Von den Grundlagen zur Mittelstufe: Umgang von Mausereignissen
Neuronale Netze im Trading: Akteur–Direktor–Kritiker
Marktsimulation (Teil 23): Positionsansicht (I)
Extremaloptimierung (EO)
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.