Marktsimulation: Erste Schritte mit SQL in MQL5 (II)
Einleitung
Willkommen zu einem weiteren Artikel unserer Reihe über die Entwicklung eines Wiedergabe- und Simulationssystems.
Im vorherigen Artikel „Marktsimulation: Erste Schritte mit SQL in MQL5 (I)“ haben wir damit begonnen, SQL zusammen mit MQL5-Code zu verwenden. Obwohl viele Menschen glauben, dass man SQL-Code problemlos in anderen Code einbetten kann, ist dies in der Regel nicht der Fall. Der Grund dafür ist, dass der SQL-Code als Zeichenkette in der ausführbaren Datei enthalten ist. Das Einbetten von SQL-Code als Zeichenkette ist zwar bei kleinen Codeausschnitten kein Problem, kann uns aber letztlich einiges an Kopfzerbrechen bereiten.
Das liegt daran, dass wir einen Tippfehler bei der Umwandlung von SQL-Code in eine Zeichenkette höchstwahrscheinlich nicht bemerken werden. Es gibt keine einfache und effektive Möglichkeit, visuell zu überprüfen, ob die Zeichenkette – also der SQL-Code – korrekt ist, da die Syntaxhervorhebung nicht auf Code innerhalb einer Zeichenkette angewendet wird.
Solch ein Problem kann sehr frustrierend sein. Ich selbst bin auf Schwierigkeiten gestoßen, als ich versucht habe, herauszufinden, warum SQL-Code innerhalb einer ausführbaren Datei nicht richtig funktioniert. Nachdem ich den Code sorgfältig durchgesehen hatte, stieß ich auf einen kleinen Tippfehler, der verhinderte, dass der SQL-Code die richtigen Beziehungen zwischen den Tabellen herstellte. Ich habe ziemlich viel Zeit allein damit verschwendet, die Ursache herauszufinden, und als ich sie schließlich gefunden hatte, hatte ich das Gefühl, dass ich diese Zeit besser hätte nutzen können. Seitdem verfolge ich beim Schreiben komplexeren Codes einen vollkommen anderen Ansatz.
Genau das ist der Zweck dieses Artikels: Ihnen, lieber Leser, eine alternative Möglichkeit aufzuzeigen, wie Sie SQL-Code mit einer in MQL5 erstellten ausführbaren Datei verwenden können. Wenn Sie wirklich vorhaben, SQL in MetaTrader 5 zu nutzen, empfehle ich Ihnen dringend, die Methode, die wir heute vorstellen werden, ernsthaft in Betracht zu ziehen; Sie können sie sogar für andere Aufgaben anpassen. Wenn Sie vorhaben, wirklich komplexe SQL-Operationen durchzuführen, sollten Sie es vermeiden, Code direkt in Zeichenketten innerhalb der ausführbaren Datei einzubetten. Die Wahrscheinlichkeit ist groß, dass irgendwo ein Fehler auftritt und Sie viel Zeit damit verbringen werden, herauszufinden, woran das liegt. Ist der Code hingegen kurz und einfach, sehe ich keinen Grund zur Sorge.
Erste Schritte mit SQL-Skripten
Um SQL-Skripte in einer in MQL5 geschriebenen ausführbaren Datei verwenden zu können, müssen Sie zunächst die SQL-Syntax verstehen. Wenn man sich die Struktur von SQL-Anweisungen ansieht, stellt man fest, dass sie alle mit einem Semikolon (;) enden. Das lässt bereits erahnen, welches Trennzeichen wir verwenden sollten, um die einzelnen abgeschlossenen SQL-Anweisungen voneinander zu trennen.
Hier gibt es einen interessanten Punkt, den es zu beachten gilt. Obwohl wir SQL-Anweisungen in der Regel über mehrere Zeilen hinweg schreiben, handelt es sich bei jeder einzelnen Anweisung faktisch um eine einzige lange Zeile. Eine Anweisung beginnt am Anfang einer Zeile und endet mit einem Semikolon (;). Allerdings kann es schwierig sein, die Anweisung in dieser Form zu lesen, da sie in manchen Fällen sehr lang ist. Für SQL spielt das keine Rolle: Die Anweisung wird immer als eine einzige Zeile behandelt. Das sagt uns bereits, was wir tun sollten.
Als Nächstes müssen wir uns mit den Kommentaren befassen. Es ist nicht ungewöhnlich, dass ein SQL-Skript zahlreiche Kommentare enthält, was besonders hilfreich ist, wenn die Datei umfangreich ist oder Abschnitte enthält, die besondere Aufmerksamkeit erfordern. Da wir keine Zeit damit verschwenden wollen, dem Interpreter Teile zu übermitteln, die er nicht verarbeitet, müssen wir diese Kommentare entfernen, bevor wir die Anweisung an die SQL-Engine senden.
Ein Zeilenkommentar, der durch zwei Bindestriche (--) gekennzeichnet ist, lässt sich leicht verarbeiten. Blockkommentare hingegen können die Sache verkomplizieren, da innerhalb des Blocks Wörter vorkommen können, die von SQL als Anweisungen interpretiert werden. Genau das wollen wir vermeiden, daher müssen wir diesen Fall gesondert behandeln.
Es gibt noch einen weiteren Nachteil. SQL unterstützt auch Zeichenketten, und wenn Kommentartrennzeichen innerhalb einer Zeichenkette vorkommen, sollten sie nicht als Beginn oder Ende eines Kommentars interpretiert werden. Diese Situation erfordert eine gesonderte Behandlung und kann für jemanden, der gerade erst mit dem Programmieren anfängt, verwirrend sein. Um den Lernprozess zu vereinfachen, werden wir die Behandlung von Kommentaren auf später verschieben; vorerst gehen wir davon aus, dass SQL-Skripte keine Kommentare enthalten, um die Arbeit, die wir in MQL5 durchführen werden, nicht unnötig zu verkomplizieren.
Fangen wir damit an: Im vorherigen Artikel haben wir eine Klasse für die Arbeit mit SQL erstellt. Diese Klasse befindet sich noch in einem frühen Entwicklungsstadium, aber wir können sie nutzen, um eine weitere Klasse zu erstellen, die für die Verarbeitung des Skripts zuständig ist. Wie sollen wir das anstellen? Es mag kompliziert erscheinen, aber wenn Sie meine Artikel verfolgt haben, wissen Sie bereits, dass ich stets versuche, die einfachste Lösung anzubieten, und auch dieses Mal wird das keine Ausnahme sein. Zunächst müssen Sie die SQL-Klasse aus der Hauptdatei auslagern, was sich mit der unten gezeigten Methode ganz einfach bewerkstelligen lässt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class C_DB_SQLite 05. { 06. private : 07. int m_handleDB; 08. public : 09. //+------------------------------------------------------------------+ 10. C_DB_SQLite(const string szFileName) 11. :m_handleDB(INVALID_HANDLE) 12. { 13. if ((m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE)) == INVALID_HANDLE) 14. { 15. Print("Unable to create or open the file ", szFileName); 16. return; 17. } 18. } 19. //+------------------------------------------------------------------+ 20. ~C_DB_SQLite() 21. { 22. DatabaseClose(m_handleDB); 23. Print("Closing Database..."); 24. } 25. //+------------------------------------------------------------------+ 26. bool Command(const string szRequestSQL) 27. { 28. bool ret = (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szRequestSQL)); 29. Print("Request execution: ", (ret ? "Success" : "Failed"), "..."); 30. return ret; 31. } 32. //+------------------------------------------------------------------+ 33. }; 34. //+------------------------------------------------------------------+
Quellcode für C_DB_SQLite.mqh
Der Code, den Sie gerade gesehen haben, befand sich in der Hauptdatei. Mit anderen Worten: Wir entfernen den Teil des MQL5-Skripts, der der Klasse entspricht, und erstellen eine Headerdatei mit dem Namen C_DB_SQLite.mqh, die wir im Ordner „Include\Market Replay\SQL“ speichern. Somit kann jedes zukünftige Skript, das dieselben Prinzipien für die Arbeit mit SQL anwenden muss, dies mithilfe dieser Headerdatei tun. Mit dieser Änderung sieht das alte MQL5-Skript nun wie unten dargestellt aus.
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. #include <Market Replay\SQL\C_DB_SQLite.mqh> 08. //+------------------------------------------------------------------+ 09. input string user01 = "DataBase01"; //FileName 10. //+------------------------------------------------------------------+ 11. void OnStart() 12. { 13. C_DB_SQLite *DB; 14. 15. DB = new C_DB_SQLite(user01 + ".sqlite"); 16. 17. (*DB).Command("PRAGMA FOREIGN_KEYS = ON;"); 18. (*DB).Command("CREATE TABLE IF NOT EXISTS tb_Symbols" 19. "(" 20. "id PRIMARY KEY," 21. "symbol NOT NULL UNIQUE" 22. ");"); 23. (*DB).Command("CREATE TABLE IF NOT EXISTS tb_Quotes" 24. "(" 25. "of_day NOT NULL," 26. "price NOT NULL," 27. "fk_id NOT NULL," 28. "FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id)" 29. ");"); 30. delete DB; 31. } 32. //+------------------------------------------------------------------+
Quellcode des MQL5-Skripts
Schauen Sie sich Zeile 07 an: Dort haben wir eine `#include`-Direktive eingefügt, die es dem Skript ermöglicht, auf den Code in der Headerdatei zuzugreifen, als wäre er direkt in der Hauptdatei geschrieben. Genau so werden wir das Projekt unter Verwendung mehrerer Headerdateien organisieren. Beachten Sie außerdem, dass ich in derselben Zeile genau denselben Dateipfad angebe, den ich zuvor erwähnt habe. Wir mussten den Namen des Ordners „Include“ nicht angeben, da MQL5 standardmäßig in diesem Verzeichnis nach Headerdateien sucht.
Die Datei muss nicht unbedingt genau dort liegen, aber um Verwirrung zu vermeiden, gehen wir nicht näher darauf ein; es ist besser, alles so zu lassen, wie es ist. Das mag trivial erscheinen, aber manche Leute konnten ihren Code nicht kompilieren, und ich musste es ihnen mehr als einmal erklären, ohne sicher zu wissen, ob sie wirklich alles verstanden hatten.
Da dieses Beispiel sehr einfach ist, nutze ich es, um zu veranschaulichen, wie Headerdateien funktionieren. Es tut mir leid, wenn das offensichtlich erscheint, aber ich möchte, dass jeder diesen Ablauf versteht. Da wir uns entschieden haben, den Artikeln keine Dateien beizufügen, müssen Interessierte wissen, wie sie den vollständigen Code erhalten und die einzelnen Dateien an den richtigen Speicherorten ablegen können, um die endgültige ausführbare Datei zu erstellen.
Wir verfügen bereits über die grundlegende Struktur, die für die Arbeit mit SQL-Skripten erforderlich ist; diese ist in einer separaten Datei gespeichert. Ein wichtiger Hinweis: Obwohl die ursprüngliche Idee darin besteht, das SQL-Skript in einer separaten Datei zu speichern, können Sie diese Datei mithilfe von Compilerdirektiven in die in MQL5 generierte ausführbare Datei einbetten. Ich halte das für eine wesentlich übersichtlichere Lösung, als SQL-Anweisungen als Zeichenketten in den MQL5-Code einzubetten. Bevor wir uns jedoch ansehen, wie sich dies umsetzen lässt, richten wir die Verarbeitung dieses SQL-Skripts so ein, dass der MQL5-Code es verwenden kann. Zunächst erstellen wir eine neue Klasse, wie unten gezeigt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_DB_SQLite.mqh" 05. //+------------------------------------------------------------------+ 06. class C_ScriptSQL : private C_DB_SQLite 07. { 08. private : 09. public : 10. //+------------------------------------------------------------------+ 11. C_ScriptSQL(const string szFileScript, const string szFileDataBase) 12. :C_DB_SQLite(szFileDataBase) 13. { 14. } 15. //+------------------------------------------------------------------+ 16. ~C_ScriptSQL() 17. { 18. } 19. //+------------------------------------------------------------------+ 20. bool Execute(void) 21. { 22. return false; 23. } 24. //+------------------------------------------------------------------+ 25. }; 26. //+------------------------------------------------------------------+
Ausgangscode für die Datei „C_ScriptSQL.mqh“
In Ordnung. Schauen wir uns zunächst an, was wir hier haben. Beachten Sie die #include-Direktive in Zeile 04. Das bedeutet, dass wir die Datei „C_DB_SQLite.mqh“ in diese Headerdatei einbinden, die wir „C_ScriptSQL“ nennen werden. Bitte beachten Sie nun den folgenden Punkt. Das ist zwar ein subtiler Punkt, aber ein sehr wichtiger. Bitte beachten Sie, dass der Name „C_DB_SQLite.mqh“ in doppelte Anführungszeichen gesetzt ist. Das bedeutet, dass sich die von uns erstellte C_ScriptSQL-Datei im selben Verzeichnis befinden muss wie die Datei C_DB_SQLite.mqh.
Wann immer wir in MQL5- oder C/C++-Code auf die in Zeile 04 gezeigte Syntax stoßen, sollten wir uns bewusst sein, dass bei dieser #include-Syntax der angegebene Pfad relativ zum Verzeichnis der aktuellen Datei interpretiert wird. Mit anderen Worten: Wir können zwischen verschiedenen Ordnern wechseln, indem wir einfach den Pfad in der `#include`-Direktive angeben. Daher muss diese Datei „C_ScriptSQL.mqh“ im Ordner „Include\Market Replay\SQL“ gespeichert werden, in dem sich auch die Datei „C_DB_SQLite.mqh“ befindet. Wenn Sie es so machen, lässt sich der Code fehlerfrei kompilieren.
Schauen wir uns nun ein paar Dinge an, die vielleicht etwas kompliziert erscheinen mögen. Beachten Sie, dass wir in Zeile 06 die Klasse `C_ScriptSQL` deklarieren, die durch private Vererbung von der Klasse `C_DB_SQLite` abgeleitet wird. Die Gründe für diesen Ansatz werden später deutlich werden. Aufgrund dieser Vererbung müssen wir einen Konstruktor definieren, um die Klasse `C_DB_SQLite` zu initialisieren. Deshalb gibt es Zeile 11, in der wir den Konstruktor für die Klasse „C_ScriptSQL“ deklarieren. Beachten Sie, dass der Konstruktor hier zwei Parameter entgegennimmt. Um also C_DB_SQLite zu initialisieren, übergeben wir in Zeile 12 einen der Parameter an den Konstruktor, damit die Klasse initialisiert wird. Den anderen Parameter werden wir später verwenden.
Da ich die Klassenstruktur bereits vorbereiten möchte, deklariere ich in Zeile 16 den Destruktor der Klasse. Und in Zeile 20 deklarieren wir die Methode, die wir im Hauptcode verwenden werden. Da diese Methode jedoch einen Wert zurückgeben muss, lassen wir sie in Zeile 22 den Wert „false“ zurückgeben. Mit anderen Worten: Wenn ich den Code teste, bevor er fertig ist, stelle ich sicher, dass jede Methode, die einen Wert zurückgeben soll, immer „false“ zurückgibt.
Okay, aber was genau wird diese C_ScriptSQL-Klasse eigentlich tun? Die Idee ist, dass diese Klasse den Namen einer SQL-Skriptdatei und den Namen einer Datenbank entgegennehmen und das Skript dann in der Datenbank ausführen soll – möglicherweise sogar so, dass wir einen Wert zurückgeben können, falls das Skript eine Abfrage ausführt. Vorerst geht es darum, den Code zu entfernen, den Sie im MQL5-Hauptcode sehen können. Sie können diesen SQL-Code sehen, wenn Sie sich das MQL5-Skript ansehen.
Da noch viel zu tun ist, müssen wir die Probleme in einzelne Aufgaben unterteilen und die Lösungen schrittweise umsetzen – eine Aufgabe nach der anderen –, bis wir das erreicht haben, was wir wirklich brauchen. Daher besteht die erste Aufgabe darin, das SQL-Skript aus dem in MQL5 geschriebenen Hauptcode zu extrahieren und es in einer Datei auf der Festplatte zu speichern. Da MQL5 aus Sicherheitsgründen keinen Zugriff auf Dateien außerhalb seines Verzeichnisses zulässt, müssen wir die Datei an einem leicht zugänglichen und einfach zu nutzenden Speicherort ablegen. Der Einfachheit halber belassen wir die Datei im Verzeichnis „MQL5\Files“. Mit anderen Worten: in demselben Verzeichnis, in dem wir standardmäßig unsere Datenbank speichern werden. Auf diese Weise speichern wir die Datei unter einem bestimmten Namen und verwenden die Dateiendung .SQL, um sie zu kennzeichnen. So wurde die unten gezeigte Datei erstellt:
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. );
Quellcode des SQL-Skripts
Auf dem folgenden Bild sehen Sie das hervorgehobene Objekt und seinen Speicherort.

Perfekt. Wir haben bereits alles, was wir brauchen, um loszulegen. Merken Sie sich den Namen, den wir der SQL-Skriptdatei gegeben haben, denn wir werden ihn gleich wieder brauchen. Bitte beachten Sie, was wir zuvor besprochen haben: Jede SQL-Anweisung in einem SQL-Skript endet mit einem Semikolon ( ; ). Daher werden wir das System so implementieren, dass das oben gezeigte SQL-Skript genau so ausgeführt werden kann, wie es geschrieben ist. In dieser Phase werden wir uns noch keine Gedanken über Kommentare machen, da, wie Sie sehen können, im SQL-Skript keine enthalten sind. Der Code, der in die Datei C_ScriptSQL eingefügt werden soll, ist daher unten aufgeführt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_DB_SQLite.mqh" 05. #include "..\Service Graphics\Support\C_Array.mqh" 06. //+------------------------------------------------------------------+ 07. class C_ScriptSQL : private C_DB_SQLite 08. { 09. private : 10. C_Array m_Arr; 11. char m_Buff[]; 12. int m_Size; 13. //+------------------------------------------------------------------+ 14. void Convert(void) 15. { 16. string sz0 = ""; 17. bool b0 = false, b1 = false; 18. int nLine = 1; 19. 20. for (int count = 0, nC0 = nLine; count < m_Size; count++) 21. { 22. switch (m_Buff[count]) 23. { 24. case '\t': 25. break; 26. case '\n': 27. nC0++; 28. case '\r': 29. break; 30. case ';': 31. b0 = true; 32. default: 33. if ((!b1) && (m_Buff[count] > ' ')) 34. { 35. b1 = true; 36. nLine = nC0; 37. } 38. sz0 += (b1 ? StringFormat("%c", m_Buff[count]) : ""); 39. } 40. if (b0) 41. { 42. m_Arr.Add(sz0, nLine); 43. sz0 = ""; 44. b0 = b1 = false; 45. } 46. } 47. } 48. //+------------------------------------------------------------------+ 49. public : 50. //+------------------------------------------------------------------+ 51. C_ScriptSQL(const string szFileScript, const string szFileDataBase) 52. :C_DB_SQLite(szFileDataBase), 53. m_Size(-1) 54. { 55. int handle; 56. 57. if ((handle = FileOpen(szFileScript, FILE_READ | FILE_BIN)) == INVALID_HANDLE) 58. { 59. Print("Unable to open script file: ", szFileScript); 60. return; 61. } 62. ArrayResize(m_Buff, m_Size = (int) FileSize(handle)); 63. FileReadArray(handle, m_Buff); 64. FileClose(handle); 65. Convert(); 66. } 67. //+------------------------------------------------------------------+ 68. ~C_ScriptSQL() 69. { 70. ArrayFree(m_Buff); 71. } 72. //+------------------------------------------------------------------+ 73. bool Execute(void) 74. { 75. int nLine; 76. string szInfo; 77. 78. for (int c = 0; c >= 0; c++) 79. { 80. szInfo = m_Arr.At(c, nLine); 81. if (nLine > 0) 82. Print(nLine, ">>", szInfo); 83. else break; 84. } 85. return false; 86. } 87. //+------------------------------------------------------------------+ 88. }; 89. //+------------------------------------------------------------------+
Code für C_ScriptSQL.mqh
Bitte beachten Sie, dass der Code bereits erheblich an Umfang zugenommen hat. Aber wir haben hier bereits fast alles umgesetzt, was wir brauchen. Schauen wir uns dennoch kurz an, womit wir es hier zu tun haben. In Zeile 05 haben wir festgelegt, dass wir eine Headerdatei verwenden müssen. Diese Datei ist bereits im Quellcode des Wiedergabe- und Simulationssystems vorhanden. Diese Headerdatei finden Sie in den vorherigen Artikeln dieser Reihe. Wenn Sie die Artikel verfolgt und den Code wie von mir gezeigt angepasst haben, brauchen Sie sich keine Sorgen zu machen, denn dann verfügen Sie bereits über die richtige Datei, die wir benötigen.
Okay, in den Zeilen 10 bis 12 sehen wir mehrere private Variablen dieser Klasse. In Zeile 14 hingegen haben wir eine Methode, deren Zweck darin besteht, den Inhalt von m_Buff zu analysieren und in ein Array von Zeichenketten aufzuteilen. Diese Methode ist ziemlich interessant, weil sie uns die ganze schwierige Arbeit abnimmt. Im Wesentlichen durchlaufen wir den Puffer Zeichen für Zeichen, um die Zeichenkette für die SQL-Anweisung zusammenzustellen.
Bitte beachten Sie, dass das System in jedem Fall prüft, ob ein bestimmtes Zeichen vorhanden ist. In Zeile 24 prüfen wir, ob ein Tabulatorzeichen vorhanden ist. Vorerst wird dieses Zeichen einfach ignoriert. In Zeile 26 prüfen wir, ob ein Zeilenvorschubzeichen vorhanden ist. Wenn dieses Zeichen erkannt wird, wird der Zeilenzähler erhöht; dies geschieht in Zeile 27. Bitte beachten Sie, dass wir in Zeile 28 prüfen, ob ein Wagenrücklaufzeichen vorhanden ist.
Sowohl das Wagenrücklaufzeichen als auch das Zeilenvorschubzeichen werden in Zeile 29 auf dieselbe Weise behandelt. Allerdings wird nur das Zeilenvorschubzeichen zusätzlich verarbeitet, da es den Zeilenzähler erhöht. Falls der Editor, den wir zum Erstellen des SQL-Skripts verwenden, kein Zeilenvorschubzeichen verwendet – was recht ungewöhnlich ist –, müssen wir ein Wagenrücklaufzeichen verwenden, um den Zeilenzähler zu erhöhen. Jedenfalls glaube ich nicht, dass es notwendig ist, das zu ändern.
In Zeile 30 stoßen wir auf genau das Zeichen, von dem wir erwartet hatten, dass es das Ende der SQL-Anweisung markiert. Bitte beachten Sie, dass wir dort eine Variable verwenden, um anzuzeigen, dass die Anweisung gefunden wurde. Da dieses Zeichen jedoch auch enthalten sein muss, wenn die Anweisung an SQL gesendet wird, beenden wir den Block nicht mit einer break-Anweisung. Die Ausführung wird daher im Default-Block fortgesetzt. Dieser Block verarbeitet alle Zeichen, die zuvor noch nicht verarbeitet wurden. Sehen wir uns an, wie das abläuft.
In Zeile 33 prüfen wir, ob ein anderes Zeichen als ein Leerzeichen gefunden wurde. Das liegt daran, dass am Anfang einer SQL-Anweisung keine Leerzeichen erforderlich sind. Wenn ein Zeichen auftritt, das kein Leerzeichen ist, kennzeichnen wir dies in Zeile 35 und speichern unmittelbar danach die Zeilennummer, an der die SQL-Anweisung beginnt.
In Zeile 38 fügen wir das aktuelle Zeichen aus dem Puffer an die Zeichenkette an. Es gibt jedoch einen wichtigen Punkt zu beachten: Ein Zeichen wird nur dann an die Zeichenkette der SQL-Anweisung angehängt, wenn diese bereits mindestens ein Zeichen enthält. Andernfalls wartet es, bis ein Zeichen erscheint. Sobald schließlich in Zeile 40 ein Hinweis darauf vorliegt, dass die SQL-Anweisung vollständig erfasst wurde, wird diese gespeichert, und der Vorgang beginnt von Neuem, um nach der nächsten SQL-Anweisung zu suchen.
Was den Konstruktor und den Destruktor angeht, so ist deren Code meiner Meinung nach recht übersichtlich, sodass es nicht notwendig ist, sie besonders zu kommentieren. Allerdings gibt es im Code in Zeile 73 einige Stellen, die einer Klarstellung bedürfen. Schauen wir uns diese einmal an. Beachten Sie, dass in Zeile 78 eine ziemlich seltsame „for“-Schleife zu sehen ist. Das liegt daran, dass die Zählung bei null beginnt und schrittweise ansteigt. Die Schleife läuft jedoch nicht endlos, wie viele vielleicht denken. Der Vorgang endet, sobald der Wert der Variablen negativ wird. Und das wird irgendwann passieren, da es sich bei der Variablen um eine vorzeichenbehaftete Ganzzahl handelt.
Bevor es jedoch so weit ist, können wir in Zeile 80 die zuvor gespeicherten SQL-Anweisungen abrufen. Beachten Sie, dass wir in Zeile 81 prüfen, ob die angegebene Zeilennummer negativ ist. In diesem Fall wird Zeile 83 ausgeführt, wodurch die Schleife in Zeile 78 beendet wird. Falls wir etwas gespeichert haben, protokollieren wir es in Zeile 82. So funktioniert das.
Beachten Sie: Wenn wir die SQL-Anweisung nicht an das Terminal ausgeben, sondern an die Klasse `C_DB_SQLite` übergeben, können wir die in der SQL-Skriptdatei enthaltene Anweisung an dieser Stelle ausführen. Das ist wirklich sehr praktisch. Bevor wir uns das jedoch in der Praxis ansehen, wollen wir uns zunächst einmal anschauen, wie der Hauptcode in MQL5 letztlich aussah. Sie finden ihn unten:
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. #include <Market Replay\SQL\C_ScriptSQL.mqh> 08. //+------------------------------------------------------------------+ 09. input string user01 = "DataBase01"; //Database File Name 10. input string user02 = "Script 01.sql"; //SQL Script File Name 11. //+------------------------------------------------------------------+ 12. void OnStart() 13. { 14. C_ScriptSQL *SQL; 15. 16. SQL = new C_ScriptSQL(user02, user01); 17. 18. (*SQL).Execute(); 19. 20. delete SQL; 21. } 22. //+------------------------------------------------------------------+
MQL5-Skriptcode
Wenn wir diese Datei im MetaTrader 5-Terminal ausführen, erhalten wir folgendes Ergebnis:

Beachten Sie, dass die Ausgabe die Zeilennummer, in der die SQL-Anweisung beginnt, sowie die vom System erkannte Anweisung enthält. Mit anderen Worten: Wir haben bereits das gewünschte Ergebnis, sodass wir SQL-Anweisungen an die Klasse `C_DB_SQLite` senden können, anstatt sie im Terminal auszugeben. Aber zuerst wollen wir klären, wie man mit Kommentaren in einer SQL-Skriptdatei umgeht. Um die von SQL unterstützten Kommentare zu verarbeiten, müssen wir zur Methode „Convert“ in der Klasse „C_ScriptSQL“ zurückkehren und dort einige Änderungen und Ergänzungen vornehmen.
Im Wesentlichen besteht unsere Aufgabe darin, festzustellen, ob wir uns innerhalb oder außerhalb einer Zeichenkette befinden. Das scheint ziemlich einfach zu sein. Wenn es jedoch um SQL geht, wird die Sache etwas komplizierter. Der Grund dafür ist, dass SQL sowohl einfache Anführungszeichen (') als auch doppelte Anführungszeichen (") zulässt. Und man kann nicht mit Sicherheit sagen, ob das Skript zur Abgrenzung von Zeichenketten einfache oder doppelte Anführungszeichen verwendet. Daher müssen wir beide Fälle berücksichtigen. Aber das ist nicht das Schwierigste; das Hauptproblem liegt vielmehr woanders. Schauen wir uns dennoch einmal an, welche Änderungen am Code vorgenommen wurden, um unser Ziel zu erreichen. Das sehen Sie unten:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_DB_SQLite.mqh" 005. #include "..\Service Graphics\Support\C_Array.mqh" 006. //+------------------------------------------------------------------+ 007. class C_ScriptSQL : private C_DB_SQLite 008. { 009. private : 010. C_Array m_Arr; 011. char m_Buff[]; 012. int m_Size; 013. //+------------------------------------------------------------------+ 014. void Convert(void) 015. { 016. string sz0 = ""; 017. bool b0, b1, bs1, bs2, bc0, bc1, bc; 018. int nLine = 1; 019. 020. b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false; 021. for (int count = 0, nC0 = nLine; count < m_Size; count++) 022. { 023. switch (m_Buff[count]) 024. { 025. case '\t': 026. sz0 += (bs1 || bs2 ? "\t" : ""); 027. break; 028. case '\n': 029. nC0++; 030. case '\r': 031. bc0 = false; 032. break; 033. case ';': 034. b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true); 035. default: 036. switch (m_Buff[count]) 037. { 038. case '"': 039. bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1); 040. break; 041. case '\'': 042. bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2); 043. break; 044. } 045. if (((count + 1) < m_Size) && (!bs1) && (!bs2)) 046. { 047. if (bc = ((m_Buff[count] == '-') && (m_Buff[count + 1] == '-'))) bc0 = true; 048. if (bc = ((m_Buff[count] == '/') && (m_Buff[count + 1] == '*'))) bc1 = true; 049. if (bc = ((m_Buff[count] == '*') && (m_Buff[count + 1] == '/'))) bc1 = false; 050. if (bc) 051. { 052. count += 1; 053. bc = false; 054. continue; 055. } 056. } 057. if (!(bc0 || bc1)) 058. { 059. if ((!b1) && (m_Buff[count] > ' ')) 060. { 061. b1 = true; 062. nLine = nC0; 063. } 064. sz0 += (b1 ? StringFormat("%c", m_Buff[count]) : ""); 065. } 066. } 067. if (b0) 068. { 069. m_Arr.Add(sz0, nLine); 070. sz0 = ""; 071. b0 = b1 = false; 072. } 073. } 074. } 075. //+------------------------------------------------------------------+ 076. public : 077. //+------------------------------------------------------------------+ 078. C_ScriptSQL(const string szFileScript, const string szFileDataBase) 079. :C_DB_SQLite(szFileDataBase), 080. m_Size(-1) 081. { 082. int handle; 083. 084. if ((handle = FileOpen(szFileScript, FILE_READ | FILE_BIN)) == INVALID_HANDLE) 085. { 086. Print("Unable to open script file: ", szFileScript); 087. return; 088. } 089. ArrayResize(m_Buff, m_Size = (int) FileSize(handle)); 090. FileReadArray(handle, m_Buff); 091. FileClose(handle); 092. Convert(); 093. } 094. //+------------------------------------------------------------------+ 095. ~C_ScriptSQL() 096. { 097. ArrayFree(m_Buff); 098. } 099. //+------------------------------------------------------------------+ 100. bool Execute(void) 101. { 102. int nLine; 103. string szInfo; 104. 105. for (int c = 0; c >= 0; c++) 106. { 107. szInfo = m_Arr.At(c, nLine); 108. if (nLine > 0) 109. Print(nLine, ">>", szInfo); 110. else break; 111. } 112. return false; 113. } 114. //+------------------------------------------------------------------+ 115. }; 116. //+------------------------------------------------------------------+
Quellcode für C_ScriptSQL.mqh
Bitte beachten Sie, dass der Code wesentlich komplexer ist. Genau aus diesem Grund habe ich zuvor erklärt, wie das Ganze funktioniert: Dieser Code zur Kommentarverarbeitung wäre viel schwerer zu verstehen, wenn man die vorherige Version nicht zuerst verstanden hätte. Bitte schauen Sie genau hin – es wurden nun viele weitere Variablen hinzugefügt. Beachten Sie außerdem, dass das Tabulatorzeichen, das bisher ignoriert wurde, nun nicht mehr in allen Fällen ignoriert wird. Es wird nur in bestimmten Fällen ignoriert – nämlich wenn wir uns außerhalb einer Zeichenkette befinden. Befindet sich das Tabulatorzeichen innerhalb einer Zeichenkette, bleibt es erhalten.
Außerdem musste ich noch etwas hinzufügen, was man in Zeile 31 sehen kann. Wenn dort ein Zeilenvorschubzeichen erkannt wird, setzen wir den Wert zurück, der angibt, dass wir uns innerhalb eines Kommentars befinden. Bitte beachten Sie: Wenn ein Semikolon ( ; ) auftritt, prüfen wir, ob wir uns innerhalb eines Kommentars oder einer Zeichenkette befinden. Dies ist notwendig, um zu verhindern, dass SQL-Anweisungen vorzeitig beendet werden. Bitte beachten Sie außerdem, dass wir in Zeile 36 eine switch-Anweisung hinzugefügt haben. Sie dient dazu, Anführungszeichen zu verarbeiten. Aber warum haben wir das nicht im Hauptcode belassen? Der Grund dafür ist, dass wir, wenn diese Verarbeitung im Hauptcode erfolgen würde, zusätzlichen Aufwand betreiben müssten, um die Integrität des SQL-Skripts zu gewährleisten.
Aber worauf ich Sie, lieber Leser, wirklich aufmerksam machen möchte, ist der Code in Zeile 45. An dieser Stelle beginnen wir damit, den SQL-Code auf Kommentare zu überprüfen. In jeder Zeile wird eine Bedingung geprüft, die einen Kommentar identifiziert. Und sie alle haben eines gemeinsam: die Variable bc. Wenn diese Variable den Wert „true“ annimmt, bedeutet dies, dass es sich um einen Kommentar handelt und der gesamte Inhalt ignoriert werden soll. Schauen wir mal, wie das funktioniert. In Zeile 47 prüfen wir, ob zwei aufeinanderfolgende Bindestriche (--) vorhanden sind, die in SQL einen Kommentar kennzeichnen, der sich bis zum Ende der aktuellen Zeile erstreckt. In Zeile 48 prüfen wir, ob der Anfang eines Blockkommentars vorliegt. In Zeile 49 prüfen wir, ob das Ende eines Blockkommentars vorliegt.
All diese Überprüfungen sind sehr einfach. Es ist jedoch anzumerken, dass wir sie immer ignorieren, wenn wir uns innerhalb einer Zeichenkette befinden. Außerdem ignorieren wir die Erkennung von Zeichenketten, wenn wir uns innerhalb eines Kommentars befinden. Im Wesentlichen sorgt dies dafür, dass das Skript alles liest und verwirft, was nicht Teil einer SQL-Anweisung ist. Versuchen Sie, SQL-Skripte mit Kommentaren und Zeichenketten zu erstellen. Führen Sie den MQL5-Skriptcode in MetaTrader 5 aus, um zu überprüfen, ob alles wie erwartet funktioniert. Prüfen Sie jedes Detail sorgfältig, um sicherzustellen, dass der Code von der Methode in der Klasse „C_ScriptSQL“ korrekt verarbeitet wird. Der Grund dafür ist, dass nach erfolgreichem Abschluss dieser Analyse der Code in Zeile 100 durch den unten gezeigten Code ersetzt werden kann. Infolgedessen wird das SQL-Skript tatsächlich ausgeführt und nicht im MetaTrader 5-Terminal angezeigt. Damit haben wir unser Ziel erreicht.
099. //+------------------------------------------------------------------+ 100. bool Execute(void) 101. { 102. int nLine; 103. string szInfo; 104. 105. for (int c = 0; c >= 0; c++) 106. { 107. szInfo = m_Arr.At(c, nLine); 108. if (nLine < 0) return c > 0; 109. if (!this.Command(szInfo)) return false; 110. } 111. return false; 112. } 113. //+------------------------------------------------------------------+
Auszug aus der Datei „C_ScriptSQL.mqh“
Vergessen Sie nicht, auch die Hauptdatei zu aktualisieren, indem Sie den unten gezeigten Code hinzufügen:
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. #include <Market Replay\SQL\C_ScriptSQL.mqh> 08. //+------------------------------------------------------------------+ 09. input string user01 = "DataBase01"; //Database File Name 10. input string user02 = "Script 01.sql"; //SQL Script File Name 11. //+------------------------------------------------------------------+ 12. void OnStart() 13. { 14. C_ScriptSQL *SQL; 15. 16. SQL = new C_ScriptSQL(user02, user01); 17. 18. Print("Result of executing the SQL script: ", (*SQL).Execute() ? "Success" : "Failed", "..."); 19. 20. delete SQL; 21. } 22. //+------------------------------------------------------------------+
Quellcode des MQL5-Skripts
Und wenn Sie mithilfe eines SQL-Skripts viele Tests ausführen möchten, passen Sie es mit dem unten aufgeführten Code an:
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
Abschließende Gedanken
In diesem Artikel haben wir gezeigt, wie Sie bereits mit geringen Kenntnissen in MQL5 und SQL mithilfe eines Skripts SQL-Code ausführen können. Oder, anders ausgedrückt: Sie können eine Datei mit SQL-Code verwenden und durch Angabe der erforderlichen Informationen genau den Code ausführen, der in dieser SQL-Skriptdatei enthalten ist.
Tatsächlich könnten viele Menschen die Verwendung einer externen Datei als Problem betrachten. Das liegt daran, dass Sie dem Kunden immer eine SQL-Datei zur Verfügung stellen müssen. Und Sie müssen darauf vertrauen, dass der Kunde den in der SQL-Datei enthaltenen Code nicht manipuliert oder verändert. Das ist definitiv ein Problem, wenn Sie nicht genau wissen, wie Sie mit einer solchen Situation umgehen sollen. Aber auch wenn Ihnen gefällt, was Sie hier gesehen haben, und Sie befürchten, dass jemand den Code, der den SQL-Code ausführt, ändern oder anpassen könnte, brauchen Sie sich darüber keine Sorgen zu machen.
Wenn Sie nicht wissen, wie Sie diese Datei mit SQL-Code in eine mit MQL5 erstellte ausführbare Datei einbetten können, sollten Sie den nächsten Artikel nicht verpassen. Ich zeige Ihnen dort, wie es geht. Außerdem werden wir noch weitere Themen im Zusammenhang mit der Arbeit mit SQL in MQL5 behandeln.
| Datei | Beschreibung |
|---|---|
| Experts\Expert Advisor.mq5 | Veranschaulicht die Interaktion zwischen Chart Trade und dem Expert Advisor (für diese Interaktion ist „Mouse Study“ erforderlich). |
| Indicators\Chart Trade.mq5 | Erstellt ein Fenster zur Konfiguration der zu sendenden Order (für diese Interaktion ist „Mouse Study“ erforderlich). |
| Indicators\Market Replay.mq5 | Erstellt Steuerelemente für die Interaktion mit dem Wiedergabe- und Simulationsdienst (für diese Interaktion ist „Mouse Study“ erforderlich) |
| Indicators\Mouse Study.mq5 | Ermöglicht die Interaktion zwischen grafischen Steuerelementen und dem Benutzer (erforderlich sowohl für die Bedienung des Wiedergabe- und Simulationssystems als auch für den Handel am realen Markt). |
| Services\Market Replay.mq5 | Erstellt und verwaltet den Wiedergabe- und Simulationsdienst für den Markt (die Hauptdatei des gesamten Systems) |
| Code VS C++\Servidor.cpp | Erstellt und verwaltet einen in C++ geschriebenen Server-Socket (MiniChat-Version) |
| Code in Python\Server.py | Erstellt und verwaltet einen Socket in Python für die Kommunikation zwischen MetaTrader 5 und Excel |
| Indicators\Mini Chat.mq5 | Ermöglicht die Implementierung eines Mini-Chats über einen Indikator (benötigt einen Server für den Betrieb) |
| Experts\Mini Chat.mq5 | Ermöglicht es Ihnen, mithilfe eines Expert Advisors einen Mini-Chat einzurichten (benötigt einen Server für den Betrieb). |
| Scripts\SQLite.mq5 | Zeigt, wie man ein SQL-Skript mit MQL5 verwendet |
| Files\Script 01.sql | Zeigt, wie man eine einfache Tabelle mit einem Fremdschlüssel erstellt. |
| 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/13078
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.