English Русский
preview
Quantitative Strategien mit niedriger Handelsfrequenz in MetaTrader 5: (Teil 1) Einrichtung eines OLAP-geeigneten Datenspeichers

Quantitative Strategien mit niedriger Handelsfrequenz in MetaTrader 5: (Teil 1) Einrichtung eines OLAP-geeigneten Datenspeichers

MetaTrader 5Handelssysteme |
15 0
Jocimar Lopes
Jocimar Lopes

Einführung

Wir haben gerade eine Artikelserie abgeschlossen, in der die grundlegenden Konzepte der statistischen Arbitrage für den durchschnittlichen Trader vorgestellt wurden, der nur mit einem gewöhnlichen Laptop, einer regulären Internetverbindung und begrenztem Kapital ausgestattet ist. Damit dies möglich ist, konzentrieren wir uns auf Mean-Reversion-Strategien mit niedriger Frequenz und gehen davon aus, dass der Hochfrequenzhandel (HFT) ein kapitalintensiver Bereich bleibt, der normalerweise institutionellen Akteuren vorbehalten ist. Wir gehen davon aus, dass es uns gelungen ist zu zeigen, dass Trader im quantitativen Handel mit niedriger Frequenz erfolgreich sein können, vorausgesetzt, sie verfügen über genügend Daten, da die erforderliche Rechenleistung bereits breit verfügbar ist. Genügend Daten bedeutet die erforderlichen Daten, um Nischenmöglichkeiten zu finden. Genügend Rechenleistung bedeutet Hardware und Software, die in der Lage sind, die Daten in der erforderlichen Zeit zu verarbeiten.

Im letzten Artikel dieser Serie haben wir die Verwendung einer spezialisierten Datenbank für die Datenanalyse vorgeschlagen, eine freie Open-Source-Datenbank, die zusammen mit SQLite in unserer Pipeline verwendet werden soll. Die Hauptidee hinter diesem Vorschlag ist, dass bei der Entwicklung und dem Wachstum unseres Datensatzes ein Online-Transaktionsverarbeitungssystem (OLTP) wie SQLite nicht das beste Werkzeug für Datenanalysezwecke ist, wie es vielleicht der Fall war, als es nur die Anwendung der einführenden Konzepte demonstrierte. Mit anderen Worten: Während wir uns auf ein echtes Handelssystem zubewegen, benötigen wir ein dediziertes Datenanalyse-Tool, wobei das erste und wohl relevanteste eine spezialisierte OLAP-Datenbank ist.

Nun, in dieser neuen Serie, die Sie als ein neues Kapitel derselben Geschichte betrachten können, ist es an der Zeit, mit der Implementierung dieses OLAP-freundlichen Datenanalysesystems zu beginnen. Wenn Sie jedoch dieser Serie gefolgt sind, die Tools ausgeführt und mit den bereitgestellten Skripten und statistischen Methoden experimentiert haben, haben Sie möglicherweise bereits viele Daten in Ihrer SQLite-Datenbank gespeichert, und einige dieser Daten könnten die Ergebnisse früherer Datenanalysen sein, wie die Ausgaben von Kointegrationstests und Bewertungssystemen. Möglicherweise möchten Sie den Teil dieser Daten bewahren, der migriert oder in das neue System konvertiert werden muss. Aber selbst wenn Sie diese Daten für irrelevant halten, könnten Sie zu einem späteren Zeitpunkt mit der Notwendigkeit konfrontiert werden, Daten aus anderen Datenbanksystemen (Postgres, MySQL usw.) einschließlich SQLite zu importieren. Unsere erste Aufgabe in diesem neuen Kapitel ist es also, zu zeigen, wie schnell und unkompliziert diese Migration sein kann.

Nachdem wir alle Altdaten oder ergänzenden Daten in unser neues System portiert haben, besteht der nächste Schritt darin, neue Daten direkt in das neue System zu übernehmen, sie aktuell zu halten und zu bereinigen, ohne Lücken oder Duplikate. Hierfür verwenden wir einen Industriestandard für Dateiformate (Parquet) und ein branchenübliches Dateisystem-Partitionierungsschema (Hive), wodurch sichergestellt wird, dass Ihre Daten zwischen lokalen Systemen und den meisten Cloud-Anbietern portabel, leistungsfähig und einfach abzufragen bleiben.

Sobald wir dieses OLAP-freundliche Setup etabliert haben, können wir mit der Datenanalyse beginnen, um Handelsideen zu bewerten, nach Nischenmustern zu suchen und bekannte Handels-Setups einem Backtest zu unterziehen, um sie für die Entwicklung vollwertiger Expert Advisors zu qualifizieren (oder auch nicht). Das heißt, unsere Datenanalyse sollte uns Zeit sparen und es uns ermöglichen, neue Ideen in Sekunden statt Stunden oder sogar Tagen aufwendiger MQL5-Entwicklung von Expert Advisors für Backtests zu filtern. Durch die Möglichkeit, Daten aus verschiedenen Quellen (Brokern oder kommerziellen Datenanbietern) zusammenzuführen, können wir ungewöhnliche Kombinationen untersuchen, einschließlich nichtfinanzieller Daten wie Frachtgebühren und aggregierter makroökonomischer Datensätze.

Ein Punkt ist zu beachten: Obwohl Hochfrequenzhandel (HFT) normalerweise mit Handel im Bereich von Millisekunden oder sogar Mikrosekunden assoziiert wird, ist für unsere Zwecke hier jede Frequenz unter wenigen Sekunden nicht relevant. In dieser Artikelserie sind wir daran interessiert, Nischenstrategien für beliebige Zeitrahmen zu entwickeln, jedoch niemals unter wenigen Sekunden. Wir wollen nicht konkurrieren, wenn unsere Chancen nahe null liegen. Stattdessen wollen wir untersuchen, wie wir unsere geringe Größe zu unserem Vorteil nutzen können. Geschwindigkeit ist sicherlich relevant, aber die Order-Routing-Geschwindigkeit oder die Ausführungsgeschwindigkeit ist nicht die einzige Geschwindigkeit, auf die es ankommt. Wir können uns auch einen Handelsvorteil verschaffen, indem wir Chancen schneller identifizieren. Wenn wir einen Vorteil finden, können wir ihn in einem blauen Ozean erkunden, ohne um Millisekunden kämpfen zu müssen. 

Denken Sie daran, dass die Prinzipien, die wir hier besprechen (Partitionierung, Dateisystem-Layout, Zero-Copy-Datenlesen und die Verwendung nativer Datenbankfunktionen), für jedes OLAP-System gelten. Der Einfachheit halber verwenden wir dasselbe kostenlose Open-Source-System, das wir in diesem Artikel beschrieben haben, nämlich DuckDB. Falls Sie damit nicht vertraut sind, laden wir Sie herzlich ein, diesen Artikel zu lesen, um die Gründe für diese Wahl besser zu verstehen.



SQLite-Datenbank in Parquet-Dateien konvertieren

Als wir die Ergebnisse unserer Forschung zur Entwicklung eines statistischen Arbitrage-Frameworks für den durchschnittlichen Trader präsentierten, haben wir ein grundlegendes Datenbankschema unter Verwendung der in MetaTrader 5 eingebetteten SQLite erstellt. Dieses Schema entwickelte sich organisch, während wir unser Wissen verbesserten und neue Anforderungen entstanden. In der neuesten Version (statarb-0.5) sah es wie folgt aus:

Abb. 1 - Entity-Relationship-Diagramm für die statarb-0.5 SQLite-Datenbank

Abb. 1. Entity-Relationship-Diagramm für die statarb-0.5 SQLite-Datenbank

Das Schema wurde erfolgreich implementiert, und wir haben diese Datenbank in unseren Experimenten und Backtests verwendet. Darin haben wir nicht nur Marktdaten gespeichert, sondern auch die Ergebnisse vieler statistischer Tests und Experimente mit unserem Bewertungssystem. Je nachdem, wie Sie mit den Konzepten experimentiert haben, sind diese Daten potenziell wertvoll und potenziell umfangreich. Selbst wenn Sie diese Daten als entbehrlich betrachten, die nur für Lernzwecke nützlich sind, sollten Sie bedenken, dass Sie an einem Punkt Ihrer Abläufe mit der Notwendigkeit konfrontiert sein könnten, die bereits in einem OLTP-System gespeicherten Daten wiederzuverwenden. Sie haben möglicherweise bereits bezahlte Daten, die beispielsweise in einer Datenbank wie Postgres oder MySQL gespeichert sind. Wenn dies der Fall ist, müssen Sie diese Daten für die Durchführung skalierbarer Datenanalysen für quantitative Strategien in ein Format konvertieren, das besser für die Aufnahme durch ein OLAP-System geeignet ist. Glücklicherweise ist dieser Bedarf im Bereich des quantitativen Handels nichts Ungewöhnliches und lässt sich mit DuckDB leicht umsetzen.

Unter der Annahme, dass DuckDB bereits auf Ihrem System installiert ist, können Sie über die CLI diesen einfachen Befehl in der DuckDB-Shell ausführen:

INSTALL sqlite;
LOAD sqlite;
ATTACH '<path/to/your/sqlite.db>' AS sqlite_db (TYPE SQLITE);
USE sqlite_db; 
EXPORT DATABASE '<folder_name>' (FORMAT PARQUET);

Der <folder_name> ist Ihre Wahl für den Speicherort der konvertierten Daten in Ihrem lokalen Dateisystem. Dieser Befehl generiert mehrere Parquet-Dateien im gewählten Ordner, eine Datei für jede Datenbanktabelle, einschließlich Systemtabellen, sowie zwei zusätzliche Dateien: schema.sql und load.sql.

Abb. 2 - PowerShell-Ausgabe, die alle in Parquet-Dateien konvertierten SQLite-Tabellen zeigt

Abb. 2. PowerShell-Ausgabe, die alle in Parquet-Dateien konvertierten SQLite-Tabellen zeigt

Die Datei schema.sql enthält die Schema-Anweisungen, die in der Datenbank zu finden sind. Sie enthält alle CREATE SCHEMA-, CREATE TABLE-, CREATE VIEW- und CREATE SEQUENCE-Befehle, die zur Rekonstruktion der Datenbank erforderlich sind.

Die Datei load.sql enthält eine Reihe von COPY-Anweisungen, mit denen die Daten erneut aus den CSV-Dateien eingelesen werden können. Die Datei enthält eine einzelne COPY-Anweisung für jede im Schema gefundene Tabelle. (DuckDB-Dokumentation)

Vielleicht möchten Sie auch nur bestimmte Tabellen konvertieren, was wahrscheinlich in den meisten Fällen der Fall sein wird, da einige Tabellen sich auf den Handel beziehen und nicht auf die Datenanalyse. Zum Beispiel werden die Tabellen trade und strategy verwendet, um Handelsmetadaten zu speichern beziehungsweise um Echtzeit-Strategie-Updates für den Expert Advisor bereitzustellen. 

So können Sie eine einzelne Tabelle über die Befehlszeile konvertieren. Anstatt eine Datenbank zu exportieren, KOPIEREN Sie die gewünschte Tabelle nach output.parquet.

duckdb -c "INSTALL sqlite; LOAD sqlite; 
           ATTACH 'my_database.db' AS sqlite_db (TYPE SQLITE);
           COPY sqlite_db.my_table TO 'output.parquet' (FORMAT PARQUET);"

Sobald Sie Ihre Tabellen in Parquet-Dateien im Ausgabeordner konvertiert haben, können Sie diese wie eine normale Datenbanktabelle mit unserem guten alten SQL abfragen. Zum Beispiel kann auf die Tabelle coint_rank, in der wir die Ergebnisse unseres Bewertungssystems gespeichert haben, wie folgt zugegriffen werden:

SELECT timeframe,lookback,assets,rank_score FROM 'parquet.database/coint_rank.parquet' LIMIT 10;

Die Befehlszeilenausgabe ähnelt der von SQLite.

Abb. 3 - Beispielhafte DuckDB-Ausgabe nach dem Abfragen einer Tabelle aus einer Parquet-Datenbank

Abb. 3. Beispielhafte DuckDB-Ausgabe nach dem Abfragen einer Tabelle aus einer Parquet-Datenbank

Von nun an können Sie reguläre Datenbankoperationen für diese Parquet-Dateien ausführen, wie z. B. das Abfragen vieler Dateien gleichzeitig mithilfe des glob-Musters, das Erstellen von Ansichten und das Durchführen von Joins. Die DuckDB-Website enthält eine umfassende Dokumentation darüber, wie man Parquet-Dateien liest und schreibt.

In dieser Serie werden wir die meiste Zeit Python verwenden, um diese Parquet-Dateien abzufragen. Dies wird durch die Python Relational API unterstützt, die über eine Fülle spezialisierter, verzögert ausgewerteter Analysefunktionen verfügt, mit denen wir die Parquet-Datei sehr schnell scannen können, da sie nur die Dateimetadaten liest, bis eine Methode aufgerufen wird, die die Ausführung auslöst.

Unterscheidet sich dies vom Lesen von CSV-Dateien in SQLite?

Obwohl wir das gleiche Ziel wie in der vorherigen Serie verfolgen, nämlich mathematische oder technische Erklärungen zu vermeiden und uns stattdessen auf die Handelsstrategie zu konzentrieren, ist es wichtig, den Unterschied zwischen der Speicherung in Parquet-Dateien und der Speicherung in CSV-Dateien zu verstehen. 

Metadaten

CSV ist ein menschenlesbares Klartextformat, während Parquet ein Binärformat ist, bei dem zahlreiche Metadaten in jede Datei eingebettet sind. Sie können die eingebetteten Metadaten einfach überprüfen, indem Sie diese Abfrage in der DuckDB-Shell ausführen.

.mode line

select * from parquet_metadata('parquet.database/coint_rank.parquet');

Ersetzen Sie dies durch Ihren eigenen Ordner und Parquet-Dateinamen. (Die erste Zeile dient nur dazu, dass Sie alle Metadaten, ein Element pro Zeile, ohne Kürzung der Ausgabe sehen können.) Nach dem Ausführen dieser Abfrage sollten Sie eine vertikale Liste sehen, die in etwa so aussieht:

file_name = parquet.database/coint_rank.parquet
row_group_id = 0
row_group_num_rows = 7505
row_group_num_columns = 12
row_group_bytes = 533723
column_id = 0
file_offset = 0
num_values = 7505
path_in_schema = tstamp
type = INT64
stats_min = 1760920476215768
stats_max = 1765924659832034
stats_null_count = 0
stats_distinct_count = NULL
stats_min_value = 1760920476215768
stats_max_value = 1765924659832034
compression = SNAPPY
encodings = PLAIN
index_page_offset = NULL
dictionary_page_offset = NULL
data_page_offset = 4
total_compressed_size = 30166
total_uncompressed_size = 60069
key_value_metadata = {}
bloom_filter_offset = NULL
bloom_filter_length = NULL
min_is_exact = true
max_is_exact = true
row_group_compressed_bytes = 1
geo_bbox = NULL
geo_types = NULL

Beachten Sie, dass die SQLite-Tabelle coint_rank, die wir in das Parquet-Format konvertiert haben, folgendes Schema aufweist:

Abb. 4 - Bildschirmaufnahme der Tabelle coint_rank aus dem Diagramm statarb-0.5.db

Abb. 4. Bildschirmaufnahme der Tabelle coint_rank aus dem Diagramm statarb-0.5.db

Um den Unterschied zwischen der Abfrage von Parquet-Dateien mit DuckDB und CSV-Dateien besser zu verstehen, nehmen wir an, wir möchten eine Aggregation oder Sortierung in der Spalte p_value durchführen. Bei einer CSV-Datei müsste das System für jede Zeile die Spalten tstamp, timeframe, lookback und assets lesen, nur um das Feld p_value zu erreichen. In der Parquet-Datei kann das System aufgrund ihres spaltenorientierten Speichermodells zuerst die Spaltenmetadaten lesen. Es liest column_id = 0 für den tstamp. Da jede Spalte separat gespeichert wird und wir nur nach dem p_value fragen, kann das System direkt zur nächsten Spalte springen, bis es die column_id für p_value findet. Dadurch vermeidet es das Lesen einer großen Datenmenge, die zur Beantwortung der Abfrage nicht benötigt wird.

Je nach Abfrage müssen einige Abfragen die Datei gar nicht erst lesen, da wir Statistiken in den eingebetteten Metadaten haben; das System weiß also allein durch das Lesen der Metadaten, welche Dateien es wert sind, gelesen zu werden. Nehmen wir an, wir haben eine Abfrage ausgeführt, die nach Daten aus dem Jahr 2024 sucht. Die Metadaten der Spalte tstamp (oben) enthalten einen Abschnitt namens stats_min und stats_max mit den folgenden Werten:

stats_min = 1760920476215768 (Montag, 20. Oktober 2025, um 00:34:36.215)

stats_max = 1765924659832034 (Dienstag, 16. Dezember 2025, um 22:37:39.832)

Der Minimalwert der Spalte tstamp beginnt weit in der Zukunft. Das System muss diese Datei nicht einmal öffnen, da die Metadaten ihm mitteilen, dass die Daten nicht vorhanden sind. Eine CSV-Datei weiß nicht, was sie enthält, bis sie vollständig gelesen wurde.

Komprimierung

Als Bonus erhalten Sie kostenlos ein optimiertes Speichersystem.

Gesamtgröße unkomprimiert = 60.069 

Gesamtgröße komprimiert = 30.166 

Parquet hat es geschafft, die Dateigröße zu halbieren.

Der Hauptunterschied liegt darin, wie das System Informationen findet. Eine CSV-Datei ist wie ein Buch, bei dem wir jede einzelne Seite lesen müssen, um ein bestimmtes Wort zu finden. Parquet ist wie ein Buch mit einem perfekten Index und einer Zusammenfassung für jedes Kapitel. Ein komprimiertes Buch, könnte man sagen.

Zero-Copy-Parquet-Reader

Neben dem metadatenbasierten Zugriff, der unnötiges vollständiges Einlesen der Datei vermeidet, verbessert noch ein weiteres Merkmal die Leistung deutlich: ein Zero-Copy-Reader. Sie werden diesen Ausdruck häufig finden, wenn Sie zu DuckDB und dem Parquet-Dateiformat recherchieren. Obwohl die Implementierung anspruchsvolle Low-Level-Programmierkenntnisse erfordert, kann sie aus der Sicht des Endbenutzers leicht verstanden werden.

Um eine CSV-Datei zu lesen, muss ein System wie SQLite (und andere RDBMS in dieser Hinsicht) die Daten von der Festplatte in einen Puffer lesen, den CSV-Text parsen und neue Kopien der Daten in seinem eigenen internen Speicherformat erstellen. Die Abfrage kann nur auf dieser zweiten Kopie ausgeführt werden. Es ist wie ein Import: lesen, übersetzen, kopieren und schließlich die Abfrage ausführen.

DuckDB kann Daten mit minimalem Kopieraufwand verarbeiten, indem Parquet-Seiten speicherkartiert werden und die Datei direkt in den Arbeitsspeicher abgebildet wird. Es wird keine sekundäre Kopie erstellt.



Marktdaten für die Analyse speichern

Wie Sie wissen, verfügt MetaTrader 5 über einen sehr effizienten lokalen Speicher-/Cache-Mechanismus und greift nicht auf den Server zu, wenn die Daten bereits angefordert wurden. Wir möchten unsere Marktdaten jedoch immer als Parquet-Dateien verfügbar haben. Diese Praxis stellt die Portabilität unserer historischen Daten sicher (wir möchten vielleicht auf einer anderen Maschine arbeiten oder unsere Daten auf einem externen Laufwerk haben). Darüber hinaus haben Sie möglicherweise ein kostenpflichtiges Abonnement für Marktdaten, dessen Inhalt wahrscheinlich mit den historischen Daten von MetaTrader 5 zusammengeführt werden muss. Aus diesen Gründen werden wir ab jetzt diesen Arbeitsablauf übernehmen, indem wir Daten von MetaTrader 5 anfordern und die Parquet-Dateien lokal speichern. 

Denormalisierung

Die Konvertierung einer Tabelle oder einer ganzen Datenbank in Parquet kann sehr bei der Leistung, der Benutzerfreundlichkeit und der Vereinfachung unserer Abfragen helfen, aber unser Schema ist nicht skalierbar. Derzeit ist unsere Preishistorie in zwei Tabellen aufgeteilt: market_data und symbol. Für SQLite wurde es normalisiert, um die Datenintegrität zu gewährleisten und den Speicherbedarf zu reduzieren, aber OLAP-Schemata müssen die kostspieligen Joins zwischen Tabellen reduzieren, um die Geschwindigkeit zu priorisieren. Aufgrund dieser Anforderung sind sie normalerweise denormalisiert. Da die Preishistorie unsere Hauptkomponente für die Datenanalyse ist, werden wir damit beginnen, sie zu denormalisieren.

Partitionierung

Denormalisierung reicht jedoch nicht aus, damit wir ein skalierbares Speicher-/Datenbanksystem haben, das für große Datensätze geeignet ist. Bald werden wir anfangen, mehr Preisdaten aus der Historie anzufordern, möglicherweise aus mehreren Quellen. Es ist durchaus vernünftig anzunehmen, dass wir in ein paar Wochen, sagen wir, Tick-Daten für fünfzig Symbole und für zehn Zeitrahmen oder mehr haben werden. Dies ist eine beachtliche Datenmenge. Aber für quantitative Strategien ist es nicht ungewöhnlich, wenn wir Tick-Daten für fünfhundert oder fünftausend Symbole in zwanzig Zeitrahmen oder mehr analysieren müssen. Wir benötigen also ein Speicherschema, das skalierbar ist. Partitionierung ist die erste Technik, die wir zum Speichern unserer denormalisierten historischen Daten verwenden werden.

Wir planen nicht, diese Daten zu überschreiben. Es handelt sich um einen schreibgeschützten Speicher. Aus diesem Grund werden wir unsere Daten in Parquet-Dateien speichern und nicht in einer DuckDB-Einzeldatei-Datenbank. Wir können Daten aus verschiedenen Quellen beziehen, sie getrennt und gut organisiert halten und bei Bedarf gezielt zusammenführen. Es ist ein perfektes Szenario für Parquet-Dateien anstelle einer einzelnen Datei in DuckDB. (Eine DuckDB-Einzeldatei-Datenbank wird bald für andere Szenarien nützlich sein, wenn Schreibvorgänge erforderlich sind.)

Aber wie werden wir diese Dateien benennen? Dies ist eine wichtige Frage, die als triviale Aufgabe getarnt ist. Denn wir müssen die Dateibenennung kohärent, konsistent und einfach für Abfragen gestalten. Sollten die Dateinamen den Symbolnamen (Ticker), den Zeitrahmen und den Historienbereich enthalten? Sollten sie die Quelle als Referenz enthalten? Gibt es noch andere relevante Informationen, die ich hier übersehe?

Glücklicherweise ist dies eine bereits gelöste Frage. Natürlich gibt es keine in Stein gemeißelte Regel, aber viele Faustregeln und bewährte Verfahren, die aus praktischen Anforderungen abgeleitet wurden. Wir werden diese gesammelten Erfahrungen nutzen.

Dateisystemordner als virtuelle Spalten

Wir werden unsere Dateisystemordner (Verzeichnisse) als virtuelle Spalten verwenden. Die relevanteste Faustregel für diese Art der Organisation ist, dass die Ordnerstruktur so genau wie möglich die Struktur unserer am häufigsten erwarteten Abfragen widerspiegeln sollte. Diese Struktur kann also je nach Bedarf stark variieren. Sie können die hier gezeigte Struktur als Ausgangspunkt verwenden und Ihre eigene entwickeln, sobald Sie mit dem System vertraut sind und neue Anforderungen entstehen. Diese Art der Datenorganisation wird Hive-Partitionierung genannt. Sie ist sehr leistungsfähig, intuitiv, flexibel und gut dokumentiert in der DuckDB-Dokumentation:

Hive-Partitionierung ist eine Partitionierungsstrategie, die verwendet wird, um eine Tabelle basierend auf Partitionsschlüsseln in mehrere Dateien aufzuteilen. Die Dateien sind in Ordnern organisiert. Innerhalb jedes Ordners hat der Partitionsschlüssel einen Wert, der durch den Namen des Ordners bestimmt wird.

Hive-Partitionierung ist eine bekannte Methode zur Organisation großer Datensätze (und wir erwarten, dass unsere bald groß werden), die DuckDB und andere Systeme, einschließlich Apache Spark und AWS Athena, als virtuelle Spalten verstehen können. Wir betrachten hier die Kompatibilität.

Derzeit haben wir sechs Partitionsschlüssel: 

  1. source = der Name des Datenanbieters oder Broker-Servers
  2. klass = die Asset-Klasse (benannt als klass, da class ein reservierter Python-Bezeichner ist)
  3. ticker = der Symbolname
  4. tf = der Zeitrahmen
  5. year = Jahr
  6. month = Monat

Was zu einer Struktur wie dieser führt:

market_data\source=MetaQuotes-Demo\klass=Forex\ticker=AUDCAD\tf=H1\year=2026\month=2\data_0.parquet

Abb. 5 - Screenshot der Baumstruktur für einen beispielhaften Hive-partitionierten Marktdatenordner

Abb. 5. Screenshot der Baumstruktur für einen beispielhaften Hive-partitionierten Marktdatenordner

Wenn wir DuckDB anweisen, market_data/**/*.parquet zu lesen, fügt es unserem Ergebnissatz basierend auf dem Ordnerpfad automatisch Spalten für alle unsere Schlüssel hinzu. Wenn wir außerdem eine Abfrage wie diese ausführen:

SELECT * FROM market_data WHERE ticker='AUDCAD' AND year=2025

Das System öffnet nur Dateien in den jeweiligen Ordnern. Daten aus dem Jahr 2024 werden dabei gar nicht erst berücksichtigt. Dies macht Abfragen wesentlich schneller. Einige Quellen im Internet und KI-Assistenten sprechen von einer 10- bis 100-fachen Beschleunigung, wenn unsere Historie wächst.

Sie haben vielleicht schon bemerkt, dass diese Art der Organisation auch die Wartung unseres noch jungen Datenspeichers vereinfacht. Wenn wir einen Datenblock (z. B. Jahr oder Monat) löschen oder aktualisieren möchten, können wir dies einfach durch Löschen eines Ordners tun.

Stellen Sie sicher, dass die Spaltennamen in der Parquet-Datei kleingeschrieben sind und keine Leerzeichen enthalten (open, high, low, close, tick_volume). DuckDB ist in vielen Konfigurationen standardmäßig groß-/kleinschreibungssensitiv. Speichern Sie Zeitstempel außerdem immer in UTC. Die Broker-Zeit von MetaTrader 5 variiert, und Zeitumstellungen können dazu führen, dass Ihre Zeitstempel plötzlich nicht mehr übereinstimmen, wenn Sie Offset-bewusste und Offset-naive Zeitstempel mischen. Siehe die abschließende Empfehlung zu Zeitzonen am Ende dieses Artikels.

In der Artikelfußzeile finden Sie ein Python-Skript, das Preisdaten von Ihrem MetaTrader 5-Terminal abruft, basierend auf den oben beschriebenen Schlüsseln virtuelle Spalten hinzufügt und die Daten in eine komprimierte Parquet-Datei in der verschachtelten Ordnerhierarchie exportiert. Es lädt nach Symbol oder nach Symbol-Pfad (Gruppe von Symbolen in einer benannten Klasse in MetaTrader 5) herunter. Außerdem lädt es nur die für unsere Analyse erforderlichen fehlenden Daten herunter, füllt die Lücken und pausiert zwischen den Anfragen, um sie in angemessenem Takt an den Broker-Server zu senden.

Sobald Sie die Daten haben, ist deren Abfrage so einfach wie die Abfrage jeder relationalen Datenbank mit SQL. Um beispielsweise bullische Kerzen in allen lokal verfügbaren EURUSD-Zeitrahmen zu finden:

SELECT * FROM read_parquet('data/**/*.parquet', hive_partitioning = 1)
WHERE ticker = 'EURUSD' 
  AND year = 2026
  AND close > open; -- Find bullish candles

Trotz der Einfachheit und der leichten Abfrage erhalten Sie jedoch alle Vorteile eines OLAP-Systems auf Industrieniveau, zusammen mit spezialisierten nativen statistischen Funktionen, wie dem Pearson-Korrelationskoeffizienten und anderen, die wir verwendet haben. Weitere Informationen finden Sie in der DuckDB-Dokumentation.



Untersuchen Sie Daten auf mögliche Strategien

Vorerst ist es unser Ziel hier, klarzumachen, welche Art von Abfragen wir in unserem OLAP-System durchführen möchten, nicht die Praxistauglichkeit der Handelsstrategie zu bewerten.

Wohl die beste Methode zur Datenanalyse bei der Suche nach Handelsmöglichkeiten ist es, mit einer Hypothese zu beginnen.  Wenn wir etwas finden, das eine Untersuchung wert ist, führen wir einen Backtest durch, mitunter mit vielversprechenden Ergebnissen. Wie in unserer Serie zur statistischen Arbitrage durch kointegrierter Aktien, in der unsere Hypothese lautet, dass wir eine nachhaltige Kointegrationsbeziehung zwischen einigen hochliquiden Aktien aus der Halbleiterindustrie und Nvidia-Aktien finden könnten, werden wir unsere explorative Datenanalyse ebenfalls mit einer Hypothese beginnen. 

Um die Dinge einfach zu halten und zu vermeiden, dass der Fokus auf die Ergebnisse abdriftet, während wir uns stattdessen auf den Prozess konzentrieren, beginnen wir mit einer sehr bekannten Hypothese: Die Golden-Cross-Momentum-Strategie kann bei Forex und Indizes sehr unterschiedliche Renditen liefern. Um diese Hypothese zu testen, führen wir sie für alle auf unserem Broker-Server (in diesem Fall dem MetaQuotes-Demo-Server) verfügbaren Devisen- und Index-Symbole mit einem Rückblickzeitraum von 1250 Tagen aus und überprüfen die zukünftigen 30-Tage-Renditen, um zu sehen, wie sie im Vergleich abschneiden.

Was ist die Golden-Cross-Momentum-Strategie?

Wenn Sie jemals nach Handelsstrategien recherchiert haben, kennen Sie wahrscheinlich bereits die Strategie des Goldenen Kreuzes. Wie dem auch sei, da es Variationen bei den Parametern gibt, die in ihren beiden Indikatoren verwendet werden, lassen Sie uns klarstellen, welche Werte wir hier für dieses Beispiel verwenden.

Ein Goldenes Kreuz ist nichts anderes als ein schicker Name für das Kreuzen zweier einfacher gleitender Durchschnitte, das auf einen langfristigen Horizont angewendet wird. Es identifiziert ein technisches Analysemuster, das auftritt, wenn ein kurzfristiger gleitender Durchschnitt einen langfristigen gleitenden Durchschnitt nach oben kreuzt. Seine Bedeutung ist sehr intuitiv und weist auf eine potenzielle Umkehr von bärischem zu bullischem Momentum hin. Man könnte sagen, dass das Goldene Kreuz darauf hindeutet, dass der Preis einen Tiefpunkt erreicht haben könnte und wir von diesem Punkt an den Beginn eines neuen langfristigen Bullenmarktes erwarten können. Normalerweise verwenden wir einen einfachen gleitenden Durchschnitt (SMA) von 50 Tagen bzw. 200 Tagen für den kurzfristigen bzw. langfristigen Bereich.

In den folgenden Beispielen stellt die rote Linie den kurzfristigen oder schnellen SMA dar, und die blaue Linie stellt den langfristigen oder langsamen SMA dar.

Abb. - USDJPY auf dem Tages-Zeitrahmen mit einem erfolgreichen Goldenen Kreuz am 03. Dez. 2012

Abb. 6. USDJPY auf dem Tages-Zeitrahmen mit einem erfolgreichen Goldenen Kreuz am 03. Dez. 2012

Beachten Sie, dass das Goldene Kreuz, wie jeder Indikator, falsche Signale erzeugen kann. Dies ist üblich, wenn sich der Markt seitwärts bewegt.

Abb. 7 - AUDUSD auf dem Tages-Zeitrahmen mit einem fehlgeschlagenen Goldenen Kreuz am 26. Jan. 2023

Abb. 7. AUDUSD auf dem Tages-Zeitrahmen mit einem fehlgeschlagenen Goldenen Kreuz am 26. Jan. 2023

Das Goldene Kreuz hat seine Wurzeln im Aktienmarkt – wie es bei vielen Handelsstrategien und Indikatoren der Fall ist, die bei Tradern beliebt sind –, wird aber auch für Kryptowährungen und Forex verwendet. In diesem Fall ist es üblich, dass es mit kürzeren SMAs angewendet wird, wie z. B. 20/50-Tage, und mit so vielen Varianten dieser Werte, wie sich Trader ausdenken und experimentieren können.

Das Testskript für das Goldene Kreuz

Das Testen des Goldenen Kreuzes bei einigen wenigen Symbolen ist relativ einfach, aber das Testen bei allen Forex- und Index-Symbolen auf einmal ist genau der Grund, warum wir einen skalierbaren Datenspeicher und ein OLAP-System benötigen: Wir möchten es in Sekunden auf jedem gewöhnlichen Laptop ausführen können. In der Fußzeile dieses Artikels finden Sie ein einfaches Python-Skript, das bei dieser Aufgabe helfen kann. Sie sollten es als Ausgangspunkt für Ihre eigenen Experimente und für die Entwicklung betrachten. 

Dies ist seine Hauptfunktion, die Methode, die die Golden-Cross-Analyse über eine Liste von Symbolen mit dem gewählten Rückblickzeitraum ausführt. Sie können auch einen Namen für das Portfolio wählen – der in den optionalen Diagrammen verwendet werden soll – und Sie können auch wählen, ob die Ergebnisse in eine CSV-Datei exportiert werden sollen oder nicht. Diese CSV-Datei ist für die zukünftige Verwendung durch den Expert Advisor gedacht, wenn wir Backtests durchführen werden.

   def golden_cross(self, symbols, lookback=2500, portfolio_name="Portfolio", plot=True, export_csv=True):

        # 1. Ensure we have data
        self._ensure_data(symbols, "D1", lookback)

        # 2. Query the Hive-partitioned Parquet files
        parquet_path = f"{self.base_path}/**/*.parquet"

 Die Funktion beginnt damit, sicherzustellen, dass wir genügend Preisdaten bereits in unserem Parquet-Datenspeicher gespeichert haben. Falls nicht, füllt sie die Lücken im Speicher, indem sie diese von MT5 anfordert.

Abb. 8 - Bildschirmaufnahme der Ausgabe des Python-Skripts zur Strategieanalyse, die fehlende Daten anfordert

Abb. 8. Bildschirmaufnahme der Ausgabe des Python-Skripts zur Strategieanalyse, die fehlende Daten anfordert

Dies ist der Kern des Skripts, die SQL-Abfrage zur Berechnung des Goldenen Kreuzes über die Dateisystempartitionen, die wir oben erstellen, und deren Test auf die zukünftigen 30-Tage-Renditen.

        query = f"""

        WITH base_ma AS (
            SELECT 
                time, ticker as symbol, close,
                AVG(close) OVER (PARTITION BY ticker ORDER BY time ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) as ma50,
                AVG(close) OVER (PARTITION BY ticker ORDER BY time ROWS BETWEEN 199 PRECEDING AND CURRENT ROW) as ma200
            FROM read_parquet('{parquet_path}', hive_partitioning=1)
            WHERE ticker IN ({str(symbols)[1:-1]})
        ),

        lagged_data AS (
            SELECT 
                *,
                LAG(ma50) OVER (PARTITION BY symbol ORDER BY time) as prev_ma50,
                LAG(ma200) OVER (PARTITION BY symbol ORDER BY time) as prev_ma200,
                LEAD(close, 30) OVER (PARTITION BY symbol ORDER BY time) as future_price
            FROM base_ma
        ),

        signals AS (
            SELECT 
                *,
                ((future_price - close) / close) * 100 as pct_return_30d
            FROM lagged_data
            WHERE prev_ma50 < prev_ma200 AND ma50 > ma200
        )
        SELECT * FROM signals;
        """

Beachten Sie diese Zeile am Ende der Abfrage:

WHERE prev_ma50 < prev_ma200 AND ma50 > ma200

Wie leicht zu erkennen ist, enthält sie die Definition des Goldenen Kreuzes, das Kreuzen der SMAs. Das einfache System, das wir für Ihren gewöhnlichen Laptop entwickeln, kann Millionen von Zeilen historischer Daten scannen, um jeden Zeitpunkt zu finden, an dem dies geschah, und die Gewinnrate für die (dann) nächsten 30 Tage berechnen. Wir verwandeln eine subjektive Chartmuster-Visualisierung in eine objektive statistische Messung, die in wenigen Sekunden die Rentabilität der Strategie für die gewählten Symbole mit dem gewählten Rückblickzeitraum und Zeitrahmen bestätigt oder widerlegt. Sie können eine ganze Gruppe von Aktien, Indizes, Währungen in jeder beliebigen Mischung backtesten, die es Ihrer Meinung nach wert ist, und Sie werden dies viel schneller tun als mit herkömmlichen Python-Schleifen. Erst nachdem Sie etwas Erfolgversprechendes gefunden haben, werden Sie Anstrengungen unternehmen, einen EA zu entwickeln, um ihn in der MT5-Umgebung zu backtesten, zu optimieren und schließlich die Strategie zur endgültigen Überprüfung auf einem Demokonto auszuführen. Sie können Tage oder sogar Wochen harter Arbeit sparen, indem Sie diese Art der vorherigen Analyse nutzen und beherrschen, wenn Sie Ihr quantitatives Handelsvorhaben ernst nehmen.

Am Ende aggregiert das Skript die Ergebnisse nach Symbol.

        results = self.con.execute(query).df()

        if results.empty:
            print("No Golden Cross signals found in the dataset.")
            return

        # 3. CSV Export Logic (Aggregated by Symbol)
        if export_csv:
            summary = results.groupby('symbol').agg(
                frequency=('symbol', 'count'),
                avg_return_30d=('pct_return_30d', 'mean')
            ).sort_values(by='avg_return_30d', ascending=False)
          
            filename = f"golden_cross_summary.csv"
            summary.to_csv(filename)
            print(f"\nSummary exported to {filename}")
            print(summary) # Display table in console for quick check

Dies ist die Ausgabe für die zum Zeitpunkt der Erstellung in MetaTrader 5 verfügbaren Indizes.

Abb. 9 - Bildschirmaufnahme der Ausgabe des Python-Skripts zur Strategieanalyse mit einer Zusammenfassung nach Symbol

Abb. 9. Bildschirmaufnahme der Ausgabe des Python-Skripts zur Strategieanalyse mit einer Zusammenfassung nach Symbol

Die gleichen Daten wie oben werden in der CSV-Datei gespeichert.

Goldenes Kreuz bei Forex vs. Indizes

Wie oben erwähnt, ist unser Ziel bei dieser einfachen Überprüfung, wie stark die Ergebnisse voneinander abweichen, wenn das Signal des Goldenen Kreuzes auf Forex und Indizes angewendet wird, aufzuzeigen, wie der OLAP-freundliche Datenspeicher, den wir aufbauen, bei der Überprüfung der Rentabilität bekannter Handelsstrategien sowie bei der Suche nach neuen Handelsmöglichkeiten helfen kann.

Im Tages-Zeitrahmen, für die letzten 1250 Handelstage oder fast fünf Jahre, waren dies die zukünftigen 30-Tage-Renditen bei Forex und Indizes.

Abb. 10 - Diagramm der zukünftigen 30-Tage-Renditen des Goldenen Kreuzes im D1-Zeitrahmen und 1250 Tagen Rückblickzeitraum bei Forex

Abb. 10. Diagramm der zukünftigen 30-Tage-Renditen des Goldenen Kreuzes im D1-Zeitrahmen und einem Rückblickzeitraum von 1250 Tagen bei Forex

Abb. 11 - Diagramm der zukünftigen 30-Tage-Renditen des Goldenen Kreuzes im D1-Zeitrahmen und einem Rückblickzeitraum von 1250 Tagen bei Indizes

Abb. 11. Diagramm der zukünftigen 30-Tage-Renditen des Goldenen Kreuzes im D1-Zeitrahmen und einem Rückblickzeitraum von 1250 Tagen bei Indizes

Im Forex-Bereich waren unsere Ergebnisse praktisch betrachtet auf Break-even-Niveau (und wir haben die Transaktionskosten noch nicht einbezogen). Andererseits können wir bei den Indizes einige Gewinne feststellen, hauptsächlich bei den acht besten Indizes, die in Abbildung 9 oben dargestellt sind. Wie dem auch sei, es gibt nur sehr wenige Signale im täglichen Zeitrahmen.

Was können die Daten über unsere Hypothese aussagen?

Obwohl das Goldene Kreuz beliebt ist, scheint es bei Indizes besser zu funktionieren. Denken wir daran, dass es im Kontext des Aktienmarktes entwickelt wurde. Bei Forex schneidet es schlecht ab. Zumindest für den täglichen Zeitrahmen und einen Rückblickzeitraum von 1250 Tagen. 

In dieser letzten Aussage liegt das gesamte relevante Verständnis des Themas dieses Artikels: Es ist eine Voraussetzung, dass wir unsere Datenanalyse sehr schnell und einfach durchführen können, da wir sie gegen viele verschiedene Parameter ausführen müssen, wobei wir diese auf unerwartete Weise rekombinieren, um Nischenmöglichkeiten zu finden, und möglicherweise vor der Notwendigkeit stehen, diese Analysen während der Überwachung des Live-Handels durchzuführen. 

Dies ist der entscheidende Punkt bei dem OLAP-freundlichen Datenspeicher, den wir hier aufbauen.



Eine abschließende Empfehlung zu Zeitzonen

Denken Sie daran, dass wir es mit einer Zeitreihe zu tun haben. Konsistenz bei Datum und Uhrzeit ist von größter Bedeutung. Wir führen ständig Tabellen über den Zeitstempel zusammen. Wenn wir falsch ausgerichtete Kurse haben, wird unsere Analyse beeinträchtigt und, was noch schlimmer ist, sie führt im schlimmsten Fall zu unbemerkten Fehlern, wenn keine Schutzmaßnahmen in unserem Code vorhanden sind. Einer der häufigsten Fehler besteht darin, Daten in lokaler Datums- und Uhrzeitangabe zu speichern und/oder sie mit Daten in UTC-Zeit zu mischen. Das Vorhandensein von eventueller Sommerzeit auf den Servern der Datenanbieter ist eine weitere Fehlerquelle. Der beste Weg, diese Probleme zu vermeiden, besteht darin, alles im Datenspeicher in UTC zu führen, diese Konsistenz bei Python-Abfragen zu wahren und unser DuckDB-System immer im UTC-bewussten Modus zu initialisieren.

In DuckDB besteht der Weg zur Gewährleistung der Konsistenz darin, die Datenbank als zeitzonenunabhängig zu behandeln, indem alles in UTC gespeichert und die Zeitzonenumrechnung erst im letzten Schritt unserer Analyse oder für die Visualisierung durchgeführt wird, falls gewünscht. Wir können die gesamte DuckDB-Sitzung zwingen, UTC zu verwenden, indem wir diese Zeile unmittelbar nach der Initialisierung unserer DuckDB-Verbindung hinzufügen:

con = duckdb.connect()
con.execute("SET TimeZone='UTC';") 

Dadurch gibt jede Funktion wie now() oder today() innerhalb unseres SQL UTC anstelle der lokalen Zeit unseres Computers zurück.

Wenn wir unsere Daten jemals mit einer anderen Quelle (wie einem anderen Broker) vergleichen müssen, sollten wir die Syntax AT TIME ZONE verwenden. Dies stellt sicher, dass beide Seiten des Vergleichs auf UTC basieren.

SELECT 
    time AT TIME ZONE 'UTC' as utc_time,
    close
FROM read_parquet('...')

Seien Sie schließlich vorsichtig, wenn Sie ein Python-datetime-Objekt an eine Abfrage übergeben. 

Tun Sie dies nicht:

con.execute("SELECT * FROM df WHERE time > ?", [datetime.now()])

Stellen Sie immer sicher, dass das Python-Objekt zeitzonenbewusst ist, bevor Sie es an DuckDB senden.

con.execute("SELECT * FROM df WHERE time > ?", [datetime.now(timezone.utc)])

Unser unten angehängtes Beispielskript fügt die Zeitzoneneinstellung bereits zur `__init__`-Methode hinzu:

class StrategyAnalyser:

    def __init__(self, base_path="market_data"):
        self.base_path = base_path
        self.con = duckdb.connect(database=':memory:')

        # FORCE UTC AT THE START
        self.con.execute("SET TimeZone='UTC';") 
        self.downloader = DataDownloader(base_path)



Schlussfolgerung

Wir haben ein minimales, aber skalierbares und zukunftssicheres Setup für einen OLAP-freundlichen Datenspeicher vorgeschlagen, das auf einer Sammlung von Parquet-Dateien basiert, die in einem einfach abzufragenden und zu wartenden Hive-partitionierten Dateisystem angeordnet sind. Wir haben eine einfache Methode gezeigt, um zuvor in anderen RDBMS wie SQLite, Postgres oder MySQL gespeicherte Daten in dieses neue System zu konvertieren.

Für unsere zukünftigen Entwicklungen im Verlauf dieser Artikelserie haben wir vorgeschlagen, einen lokal gespeicherten Marktdatensatz historischer Preise aufzubauen, um die Zusammenführung mehrerer Datenquellen zu erleichtern und die Leistung des Datenspeichers zu verbessern.

Abschließend haben wir eine Beispielanalyse präsentiert, in der die bekannte Golden-Cross-Momentum-Strategie bei Anwendung auf zwei verschiedene Anlageklassen, Forex und Indizes, für alle in unserem aktuellen Broker-Server (MetaQuotes-Demo) verfügbaren Symbole verglichen wurde. Dabei wurde deutlich, dass das Ziel dieser Analyse darin besteht, die Art der Untersuchung zu veranschaulichen, die unser OLAP-freundlicher Datenspeicher zusammen mit dem begleitenden System (DuckDB) bereits auf einem gewöhnlichen Laptop mit einer Sammlung von Parquet-Dateien und ein paar Zeilen SQL-Code ausführen kann.

Dateiname Beschreibung
data_downloader.py Python-Skript, um den lokalen Datenspeicher mit den erforderlichen Daten auf dem neuesten Stand zu halten
strategy_analyser.py Python-Skript zur Durchführung der Golden-Cross-Beispielanalyse über eine Liste von Symbolen

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

Beigefügte Dateien |
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Code, Tränen und Algo Forge Code, Tränen und Algo Forge
Dieser Artikel erörtert den Übergang zu MQL5 Algo Forge als modernes und bequemes Format für die Veröffentlichung von Programmcode und Anhängen zu Artikeln. Die Verwendung von Repositories anstelle von herkömmlichen ZIP-Archiven und Quellcode ermöglicht es Ihnen, Projekte auf dem neuesten Stand zu halten, Änderungen schnell vorzunehmen und professionell mit Ihren Lesern zu interagieren. Es werden Empfehlungen für die schnelle Migration bestehender Projekte in die Cloud über die MetaEditor-Schnittstelle gegeben.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Integration von MQL5 mit Datenverarbeitungspaketen (Teil 8): Einsatz von Graph Neural Networks zur Erkennung von Liquiditätszonen Integration von MQL5 mit Datenverarbeitungspaketen (Teil 8): Einsatz von Graph Neural Networks zur Erkennung von Liquiditätszonen
Dieser Artikel zeigt, wie man die Marktstruktur in MQL5 als Graph darstellt, indem Swing-Highs/-Lows in Knoten mit Merkmalen umgewandelt und durch Kanten miteinander verbunden werden. Es wird ein Graph Neural Network trainiert, um potenzielle Liquiditätszonen zu bewerten, das Modell nach ONNX exportiert und für die Echtzeit-Inferenz in einem Expert Advisor verwendet. Die Leser lernen, wie man die Daten-Pipeline aufbaut, das Modell integriert, Zonen im Chart visualisiert und die Signale für eine regelbasierte Handelsausführung nutzt.