Neuronale Netze im Trading: Effektive Merkmalsextraktion für präzise Klassifizierung (Mantis)
Einführung
In einer Welt, in der Millisekunden und kleinste Kursschwankungen entscheidend sind, suchen Trader nach Instrumenten, die nicht nur die weitere Kursentwicklung prognostizieren, sondern auch komplexeste Muster in den Charts präzise klassifizieren können. In diesem Zusammenhang schlägt das in dem Artikel „Mantis: Lightweight Calibrated Foundation-Model for User-Friendly Time Series Classification“ vorgestellte Foundation-Model Mantis ein neues Kapitel in der Zeitreihenanalyse auf. Dank seines leichtgewichtigen Designs, seiner durchdachten Architektur und seiner hervorragenden Kalibrierung ermöglicht Mantis die schnelle Integration des Klassifikators in Handelssysteme und liefert zuverlässige Konfidenzschätzungen für das Risikomanagement.
Die Autoren weisen darauf hin, dass traditionelle Ansätze, die darauf trainiert sind, nachfolgende Werte vorherzusagen, oft zu träge sind: Sie erfordern eine sorgfältige Feinabstimmung für jede neue Strategie, und große Volatilitätsspitzen können ihre Leistung erheblich beeinträchtigen. Dennoch haben Foundation-Modelle für die Zeitreihenprognose in den letzten Jahren beeindruckende Ergebnisse geliefert, sind jedoch im Wesentlichen eher für Regression als für die Klassifizierung von Marktregimen konzipiert. Genau diese Lücke haben die Autoren durch die Einführung des Mantis-Frameworks mit kontrastivem Vortraining geschlossen.
Die Idee ist einfach und elegant: Teilen Sie die Zeitreihe in eine Reihe von Patches auf und wenden Sie dann, ähnlich wie beim Vision Transformer in der Computer Vision, hybride Attention an. Der klassische globale Aufmerksamkeitsmechanismus weist jedoch eine quadratische Komplexität auf, was bei hochfrequenten Daten zu einem Engpass wird. Um diesen Effekt abzumildern, verwendet Mantis gleichzeitig lokale Token – die durch Faltungsschichten und Pooling über kleine Fenster gewonnen werden – und globale Token, die Trendinformationen über das gesamte Segment aggregieren. Diese Kombination bietet eine nahezu lineare Skalierbarkeit in Bezug auf die Serienlänge und bewahrt die Fähigkeit, sowohl kleine Schwankungen als auch langfristige Muster zu erfassen.
Kontrastives Vortraining ist bei Mantis das Geheimrezept, das verschiedene Augmentierungen desselben Zeitsegments zusammenführt und gleichzeitig radikal unterschiedliche Zeitreihen voneinander trennt. Infolgedessen gruppieren sich Einbettungen desselben Musters mit unterschiedlichen Variationen im Raum, während sich unterschiedliche Ereignisse voneinander entfernen. Diese Strategie ermöglicht es dem Modell, stabile Merkmale verschiedener Muster zu erlernen, auch wenn sich die Spitzen und Täler in Amplitude und Zeit verschieben können.
Ein zentraler Aspekt für einen Trader ist die Einschätzung, ob es sicher ist, eine Position zu eröffnen: Es reicht nicht aus, einfach zu sagen: „Dies ist eine Trendumkehr“; man muss verstehen, wie sicher sich das Modell in seiner Schlussfolgerung ist. Mantis löst dieses Problem mit einem integrierten Modul zur Temperaturskalierung: Es passt die Ausgabe-Logits an, sodass die A-posteriori-Wahrscheinlichkeiten die empirische Klassenwahrscheinlichkeit besser widerspiegeln. Wenn ein Signal mit „80 % Umkehrwahrscheinlichkeit“ gekennzeichnet ist, kann ein Trader infolgedessen erwarten, dass das Signal in etwa vier von fünf Fällen korrekt ist.
Die Autoren widmeten auch der multivariaten Analyse besondere Aufmerksamkeit: Der algorithmische Handel beinhaltet oft die gleichzeitige Verwendung von Dutzenden Indikatoren (gleitende Durchschnitte, RSI, Handelsvolumina, Währungspaar-Korrelationen usw.). Die einfache Zusammenführung aller Kanäle zu einer einzigen hochdimensionalen Repräsentation führt zu einer Explosion der Parameteranzahl und einem erhöhten Speicherbedarf, während die Analyse univariater Sequenzen Informationen über kanalübergreifende Abhängigkeiten unberücksichtigt lässt. Daher haben die Entwickler des Frameworks leichtgewichtige Adapter integriert, die kanalübergreifende Interaktionen in einen kompakten Raum verdichten, ohne Informationen über die Beziehungen zwischen verschiedenen Indikatoren zu verlieren.
Um den praktischen Wert von Mantis zu veranschaulichen, stellen Sie sich das Szenario eines Flash-Crash auf dem Kryptowährungsmarkt vor: Innerhalb einer Sekunde stürzt der Preis um mehrere Prozent ab und erholt sich dann ebenso schnell wieder. Ein klassisches Modell könnte diese Anomalie als Rauschen interpretieren und das Einstiegssignal ignorieren. Mantis versteht dank des Vortrainings mit Millionen von realen und synthetischen Beispielen, dass solche Ereignisse entweder „falsch“ oder „echt“ sein können: Es klassifiziert solche Ereignisse entweder als „kurzfristige Anomalie“ oder „echte Umkehr“ mit unterschiedlichen Konfidenzschätzungen, was es dem Trader ermöglicht, eine geeignete Strategie zur Kapitalerhaltung zu entwickeln.
Ein weiteres Beispiel aus der Praxis ist die Klassifizierung von Marktregimen für Technologieaktien. Während eines Aufwärtstrends bewegen sich die Preise aufgrund des allgemeinen Nachrichtenhintergrunds fast synchron, während sich jeder Vermögenswert während einer Korrektur auf seine eigene Weise verhält. Mantis erkennt dank seiner Adapter diese kanalübergreifenden Diskrepanzen: Eine geringe Korrelation zwischen ähnlichen Vermögenswerten wird zu einem Signal für den Beginn eines Korrekturzyklus, das das Modell zusammen mit einer Konfidenzschätzung meldet.
Mantis-Algorithmus
Das Mantis-Framework zeigt, wie eine durchdachte Architektur und ein gründliches Training eine Reihe von Ideen in ein zuverlässiges Werkzeug verwandeln können. Der von den Autoren des Frameworks vorgestellte Foundation-Model-Ansatz stützt sich auf vier Hauptpfeiler: Zeitreihen-Tokenisierung, hybride Attention, Adapter für multivariate Daten sowie selbstüberwachtes kontrastives Vortraining mit anschließender Kalibrierung.
Die Grundidee hinter Mantis besteht darin, sich von der traditionellen Praxis zu lösen, eine Zeitreihe in feste Fenster zu unterteilen. Stattdessen wird die Sequenz in eine feste Anzahl von Patches unterteilt, was die Unabhängigkeit von der Länge der Eingabesequenz gewährleistet und den Rechenaufwand stabilisiert. Beispielsweise werden Reihen der Längen 1024 und 2048 in die gleiche Anzahl von Patches umgewandelt – 32. Dieser Ansatz ist für die groß angelegte Verarbeitung heterogener Zeitreihen von entscheidender Bedeutung.
Das Embedding wird in mehreren Stufen gebildet. Zunächst wird eine Faltungsschicht mit 256 Ausgangskanälen angewendet. Diese Schicht transformiert die Zeitreihe in eine kompaktere latente Repräsentation. Anschließend wird jeder der 32 Patches durch Mittelung (Mean Pooling) aggregiert, was zu einem Tensor der Dimension (32, 256) führt. Jeder Patch kodiert die lokalen Merkmale der Zeitreihe, einschließlich Spitzen, Schwankungen und der Mikrostruktur von Preisbewegungen.
Parallel dazu wird ein zweiter Datenstrom – der Differenzstrom – erstellt. Er basiert auf den ersten Differenzen zwischen benachbarten Werten in der Zeitreihe. Diese Transformation hilft dabei, langfristige Trends zu eliminieren und Signale zu verstärken, die sich auf kurzfristige Dynamiken beziehen. Sie ist besonders nützlich in Situationen, in denen Abweichungen von einem stabilen Niveau oder scharfe Bewegungen in der Nähe von Unterstützungs- und Widerstandsniveaus von Interesse sind.
Beide Ströme durchlaufen dieselben Verarbeitungsschritte: Faltungsschicht, Mittelung und Normalisierung. Das Ergebnis sind zwei Sätze von Patches, die jeweils 32 Token der Dimension 256 enthalten. Dies versorgt das Modell mit ausgewogenen Informationen sowohl über die Form des Signals als auch über dessen Veränderungen im Zeitverlauf.
Zusätzlich werden zwei weitere Arten von Informationen extrahiert: Niveau und Volatilität. Dazu wird die Zeitreihe in 32 gleiche Fenster unterteilt und für jedes Fenster werden der Mittelwert sowie die Standardabweichung berechnet. Diese Statistiken werden mithilfe des Multi-Scaled Scalar Encoders kodiert, der es dem Modell ermöglicht, die Hintergrundmerkmale des Signals zu erfassen. Somit erhält das Modell vier Datenströme gleichzeitig: normalisierte Werte, Differenzen, Mittelwerte und Standardabweichungen.
Die vier Datenströme werden durch Konkatenation kombiniert. Davor werden sie durch individuelle lineare Projektoren geleitet, um die dimensionale Konsistenz sicherzustellen. Anschließend durchlaufen die kombinierten Einbettungen eine Projektionsschicht, gefolgt von Layer Normalization, was zu einer endgültigen Repräsentation der Zeitreihe als 32 Token der Dimension 256 führt. Diese Token sind universelle Informationsträger, die verhaltensbezogene und statistische Informationen über den Markt enthalten.
Der nächste Schritt in der Architektur besteht darin, ein Klassen-Token hinzuzufügen – einen trainierbaren Vektor, dessen Aufgabe es ist, Informationen aus allen Token zu aggregieren. Dies ermöglicht es dem Modell, die endgültige Repräsentation der Reihe zu bilden, wobei sowohl lokale als auch globale Muster berücksichtigt werden. Um Informationen über die Reihenfolge der Patches zu bewahren, wird eine sinusförmige Positionskodierung angewendet und anschließend dem Transformer-Block zugeführt.
Der Transformer besteht aus sechs Schichten. Jede Schicht enthält einen 8-Kopf-Multi-Head Attention-Mechanismus, eine Normalisierungsschicht und einen zweischichtigen Feed-Forward-Block mit GELU-Aktivierung. Während der Pre-Training-Phase wird Dropout mit einer Wahrscheinlichkeit von 10 % angewendet, was dazu beiträgt, Overfitting zu verhindern. Nachdem alle Schichten durchlaufen wurden, wird der Class-Token-Output-Vektor verwendet, um das finale Sequenz-Embedding zu bilden.
Mantis wird unter Verwendung eines selbstüberwachten Paradigmas trainiert, das kontrastives Lernen einbezieht. Das Modell wendet eine Vielzahl von Zeitreihen-Augmentierungen auf die Quelldaten an. Konkret werden bei der RandomCropResize-Methode 0 bis 20 % der Sequenz zufällig entfernt und der verbleibende Teil wieder auf die ursprüngliche Länge gestreckt. Dies bewahrt die Gesamtstruktur des Signals, ohne die Abfolge der Ereignisse zu verändern.
Für jedes Beispiel xi im Batch werden zwei Augmentierungen zufällig ausgewählt. Die resultierenden Repräsentationen werden durch einen Projektions-Head geleitet und mittels Kosinus-Ähnlichkeit verglichen.

Anschließend wird der Cross-Entropy-Loss berechnet.

In ihrer Arbeit trainierten die Autoren des Frameworks das Modell auf einer kombinierten Stichprobe aus 10 Datensätzen, die mehr als 7 Millionen Zeitreihen umfasste. Das Modell wurde 100 Epochen lang trainiert. Es wurde eine Batch-Größe von 2048 verwendet, zusammen mit vier Tesla V100-32GB GPUs.
Während des Fine-Tunings wurde ein Klassifikations-Head an das Embedding angehängt. Die resultierenden Prognosewerte unterliegen einem Temperature Scaling, das den erwarteten Kalibrierungsfehler minimiert und es ermöglicht, die Ergebnisse des Modells probabilistisch zu interpretieren.
Die multivariate Natur von Zeitreihen bleibt jedoch eine der größten Herausforderungen. Unterschiedliche Aufgaben können eine unterschiedliche Anzahl von Kanälen beinhalten, was eine Anpassung des Modells erfordert. Wie andere Foundation-Modelle wird Mantis in einer univariaten Form trainiert und wendet denselben Mechanismus auf jeden Kanal an. Dies erhöht nicht nur die Belastung der Rechenressourcen, sondern ignoriert auch kanalübergreifende Korrelationen.
Um diese Einschränkungen zu überwinden, schlagen die Autoren des Frameworks vor, einen Kanaladapter zu verwenden – dabei handelt es sich um die Funktion a, die die ursprünglichen d Kanäle in dnew umwandelt. Dieser Ansatz ermöglicht es, die Eingabedaten an das verfügbare Rechenbudget anzupassen, die zeitliche Struktur beizubehalten und die Kompatibilität mit jedem Modell zu gewährleisten.
Hier betrachten die Autoren des Mantis-Frameworks fünf Adaptervarianten. Die ersten vier sind klassische Methoden der Dimensionsreduktion:
- PCA (Principal Component Analysis): findet einen orthogonalen Raum, in dem die meiste Varianz in einer kleineren Anzahl von Komponenten konzentriert ist;
- Truncated SVD: ähnlich wie PCA, jedoch auf die nicht zentrierte Datenmatrix angewendet;
- Random Projection: eine zufällige lineare Abbildung in einen Raum mit niedrigerer Dimension;
- Variance-Based Selector: wählt die Kanäle mit der höchsten Varianz aus.
Die Daten für diese Adapter werden in die Form (n*t, d) umgeformt, wobei n die Anzahl der Beispiele ist. Die Projektionsmatrix W führt die Kanalabbildung durch.
Der fünfte Adapter – Differentiable Linear Combiner (LComb) – wird zusammen mit dem Hauptmodell trainiert, indem seine Parameter während des Backward-Passes angepasst werden. Dies ermöglicht es, den Kontext der Aufgabe zu berücksichtigen und eine höhere Genauigkeit zu erzielen.
Dadurch ist Mantis für eine Vielzahl von Aufgaben geeignet, ohne die zeitliche Struktur der Daten oder die Beziehungen zwischen den Kanälen zu verlieren. Das Modell bleibt vielseitig und effizient, selbst in realen multivariaten Szenarien.
Nachfolgend ist die von den Autoren erstellte Visualisierung des Mantis-Frameworks dargestellt.

Implementierung mit MQL5
Nach einer gründlichen Analyse der theoretischen Aspekte des Mantis-Frameworks ist es an der Zeit, zum interessantesten Teil überzugehen – seiner praktischen Implementierung. Hier nehmen die Ideen in Form von Code konkrete Gestalt an, und abstrakte Prinzipien beginnen, messbare Ergebnisse zu liefern. Unser Fokus liegt auf der Softwareimplementierung der wichtigsten architektonischen Entscheidungen des Frameworks unter Verwendung der Sprache MQL5. Trotz der hohen Komplexität der Konzepte stehen wir vor einer klaren und ehrgeizigen Herausforderung: ein Modell zu bauen, das in der Lage ist, reale Marktdaten effektiv zu verarbeiten, sich an Volatilität anzupassen und gleichzeitig so ressourceneffizient wie möglich zu bleiben.
Diskussion der Ansätze
Bevor wir mit dem Programmieren beginnen, müssen wir, wie in der Technik üblich, eine Strategie entwickeln. Ohne einen klaren Plan kann keine Architektur den Anforderungen der realen Märkte standhalten. Unsere Reise beginnt mit einer systematischen Analyse: Was genau wir implementieren wollen, welche Rollen die einzelnen Komponenten spielen und wo potenzielle Engpässe verborgen sein könnten. Es ist wichtig, nicht nur die Ideen der Autoren zu kopieren, sondern sie an unsere Aufgabe und die MQL5-Umgebung anzupassen, die ihre eigenen Einschränkungen und einzigartigen Merkmale aufweist.
Im Mittelpunkt der Implementierung steht die Token-Bildung. Hier werden die Grundlagen für den zukünftigen Erfolg des Modells gelegt, da Transformer die Rohdaten nicht effektiv verarbeiten können. Sie benötigen Eingaben in Form von Token-Sequenzen, von denen jede eine komprimierte und dennoch ausdrucksstarke Darstellung eines Teils der Zeitreihe enthält.
Die ursprüngliche Mantis-Architektur schlägt die Verwendung eines Mehrkanalschemas vor, das Abweichungsreihen, Segmentmittelwerte und Segmentstandardabweichungen umfasst. Bei normalisierten Eingaben, mit denen unsere Modelle meist arbeiten, nimmt jedoch der Informationswert der Mittelwert- und Standardabweichungsströme ab. Wir verwerfen diese Kanäle bewusst und konzentrieren uns stattdessen auf zwei Hauptströme: die ursprüngliche Zeitreihe und ihre erste Differenz. Diese Kombination ermöglicht es, sowohl globale Niveaus als auch lokale Veränderungen gleichzeitig zu berücksichtigen – eine Art Kompromiss zwischen Stabilität und Sensitivität.
Der Prozess der Vorbereitung dieser Ströme beginnt mit der Normalisierung der ursprünglichen Zeitreihe. Dies ist ein wichtiger Schritt, der dazu beiträgt, großskalige Schwankungen auszugleichen und die Widerstandsfähigkeit des Modells gegenüber Ausreißern zu verbessern. Als Nächstes wird eine Differenzierungsoperation angewendet, um die Differenz zwischen benachbarten Beobachtungen zu berechnen. Die resultierende Reihe reagiert sehr empfindlich auf plötzliche Veränderungen, was besonders wertvoll ist, wenn man Marktimpulse oder Umkehrungen erfassen möchte. Danach werden die beiden Reihen zu einem Zweikanal-Tensor kombiniert, wobei jeder Kanal einen separaten Informationsstrom darstellt.
Das einfache Kombinieren reicht jedoch nicht aus. Trotz der Fülle an Informationen, die in einer Mehrkanal-Zeitreihe enthalten sind, fehlen dem Modell immer noch entscheidende Informationen über den zeitlichen Kontext. Wie bekannt ist, verfügen Transformer nicht über eine eingebaute Fähigkeit, die Reihenfolge der Elemente in der Eingabesequenz zu verstehen; daher benötigen sie zusätzliche Informationen, die die Position jeder Beobachtung im Verhältnis zu den anderen beschreiben.
In der ursprünglichen Mantis-Implementierung wurde dieses Problem durch das Hinzufügen einer sinusförmigen Positionskodierung gelöst, ähnlich der in „Attention Is All You Need“ vorgeschlagenen. Dieser Ansatz ist aufgrund seiner Vielseitigkeit und Portabilität praktisch: Er erfordert kein Training und funktioniert bei Aufgaben verschiedenster Art gut. Gleichzeitig wird die Kodierung erst hinzugefügt, nachdem die Kanäle kombiniert und in Patches segmentiert wurden.
Wir schlagen jedoch einen anderen Ansatz vor. Bevor wir die Sequenz in Fragmente aufteilen, führen wir eine zeitliche Kodierung ein, ähnlich dem Mamba4Cast-Framework. Diese Kodierung gibt nicht nur die Position eines Punktes auf dem Zeitraster an, sondern vermittelt auch Informationen über die zeitliche Struktur der Zeitreihe – sie ermöglicht es dem Modell, verborgene Saisonalität, wiederkehrende Muster, Asymmetrien und Rhythmen in Marktdaten zu erkennen. Im Gegensatz zur sinusförmigen Kodierung ist die zeitliche Kodierung empfindlicher gegenüber der Mikrostruktur des Marktes und kann Muster erfassen, die über das aktuelle Analysefenster hinausgehen.
Erst nach dem Hinzufügen dieser Kodierung können wir zum nächsten logischen Schritt übergehen – der Kanalsegmentierung. Dieser Schritt fungiert als eine Art Filter, der zeitlich angereicherte Informationen in eine für das Modell geeignete Form umwandelt. Mit anderen Worten: Die Daten tragen zu diesem Zeitpunkt bereits nicht nur den Wert und die Ableitung, sondern auch eine zusätzliche Kontextebene, die ihre Verhaltensdynamik im Zeitverlauf offenbart. Dies vereinfacht die Aufgabe des Modells, komplexe Zusammenhänge zu identifizieren, erheblich und macht seine Leistung widerstandsfähiger gegenüber Marktgeräuschen.
Ich schlage vor, dass wir die Änderungen am Segmentierungsalgorithmus etwas später besprechen, wenn wir zur eigentlichen Implementierung kommen. Vorerst stellen wir die Beschreibungen der anderen Module bewusst zurück, um uns auf die Grundlage zu konzentrieren – die Transformation einer Zeitreihe in eine informative, standardisierte und strukturierte Sequenz. In dieser Phase wird der Grundstein für den Erfolg des gesamten Modells gelegt. Gut aufbereitete Daten ermöglichen es, Muster effektiv zu identifizieren, die Reihenfolge von Ereignissen korrekt zu berücksichtigen und wichtige Muster im Marktverhalten zu unterscheiden.
Nachdem der konzeptionelle Rahmen steht und der Fahrplan skizziert wurde, ist es an der Zeit, von Worten zu Taten überzugehen. In dieser Phase beginnt die eigentliche Implementierung des Modells – der Moment, in dem Theorie in Codezeilen umgewandelt wird. Beginnen wir mit dem grundlegendsten – und dennoch äußerst wichtigen – Teil: der Vorverarbeitung der Zeitreihen, die zu einer Mehrkanal-Darstellung der Daten führt. Hier findet die anfängliche Merkmalsextraktion statt, die anschließend bestimmt, was Transformer sehen und wie sie es interpretieren. Die Aufgabe besteht darin, zwei Ströme zu extrahieren: die ursprüngliche Reihe und ihre erste Differenz, und diese dann zu einem einzigen Tensor zu kombinieren. Wir implementieren all dies unter Verwendung von OpenCL.
Änderungen am OpenCL-Programm
OpenCL wird eingesetzt, um die Berechnungen zu beschleunigen, insbesondere bei einer großen Anzahl von Zeitreihen. Die GPU ermöglicht es, Operationen über mehrere Variablen hinweg zu parallelisieren und die Datenvorbereitung zu beschleunigen, ohne den Hauptprozessor zu belasten. Daher implementieren wir als Erstes einen spezialisierten ConcatDiff-Kernel. Sein Zweck ist es, die erste Differenz zu berechnen und das Ergebnis gleichzeitig mit den ursprünglichen Daten zu verketten, wodurch ein Tensor mit zwei Kanälen für jedes Zeitintervall gebildet wird.
Nur zwei Zeiger auf Datenpuffer werden als Parameter an diesen Kernel übergeben – ein bewährter minimalistischer Ansatz. Der erste ist das Eingabe-Array data, das die zu analysierenden Zeitreihendaten enthält, und der zweite ist der Ausgabepuffer output, in dem die Berechnungsergebnisse gespeichert werden. Dieser Ansatz macht die Kernel-Schnittstelle so einfach und intuitiv wie möglich und eliminiert unnötigen Overhead.
Der zusätzliche Parameter ist die Konstante step – die Schrittweite, die zur Berechnung der Differenz verwendet wird. Diese Ergänzung macht den Algorithmus deutlich flexibler. Jetzt können wir sowohl die klassische erste Differenz (zwischen benachbarten Punkten) als auch beispielsweise die Differenz zwischen dem Wert „jetzt“ und dem Wert „fünf Schritte voraus“ berechnen. Letzteres kann besonders im Finanzkontext nützlich sein: Dieser Parameter ermöglicht es, Merkmale vorzubereiten, die Veränderungen über verschiedene Zeithorizonte hinweg widerspiegeln.
__kernel void ConcatDiff(__global const float* data, __global float* output, const int step) { const size_t i = get_global_id(0); const size_t v = get_local_id(1); const size_t inputs = get_local_size(0); const size_t variables = get_local_size(1);
Jedem Work-Item wird ein eindeutiges Paar von Indizes zugewiesen – ein Zeitindex i und ein Variablenindex v. Somit ist jedes Work-Item für die Verarbeitung eines einzelnen spezifischen Wertes in der Quelldatenmatrix verantwortlich.
Das Quelldaten-Array data ist eine normalisierte Zeitreihe, die in linearisierter Form vorliegt, wobei Zeit- und Kanaldimensionen entfaltet sind.
Als Nächstes berechnen wir innerhalb des Kernels shift – den Offset im data-Array, der der aktuellen Zeit-Kanal-Position entspricht.
const int shift = i * variables; const float d = data[shift + v];
Nachdem wir den ursprünglichen Wert d erhalten haben, fahren wir mit der Berechnung seiner ersten Differenz entlang der Zeitachse fort. Dazu bilden wir die Differenz zwischen dem aktuellen Wert und dem Wert, der step Schritte vorausliegt. Dieser Ansatz ist für die Vorbereitung von Merkmalen gedacht, die lokale Dynamiken widerspiegeln: Wachstum oder Rückgang, Beschleunigung oder Verlangsamung der Veränderung. Dabei muss der Index auf Gültigkeit geprüft werden: Wir dürfen die Grenzen des Arrays nicht überschreiten.
float diff = 0; if(step > 0 && (i + step) < inputs) diff = IsNaNOrInf(d - data[shift + step * variables + v], 0);
Die Differenzberechnung endet mit der Schutzfunktion IsNaNOrInf, die das Ergebnis auf Null setzt, wenn NaN oder Unendlichkeit auftritt – dies ist besonders wichtig, wenn fehlende Werte oder Datendefekte vorhanden sind.
Als Nächstes folgt die entscheidende Stufe – die Bildung des Ausgabe-Arrays output. Hier findet eine interessante Operation statt: Die Daten werden paarweise geschrieben. Zuerst wird der ursprüngliche Wert aufgezeichnet, gefolgt vom entsprechenden Wert der ersten Differenz. Da wir eine verdoppelte Variablen-Schrittweite (doppelt so viele Kanäle) verwenden, enthält der resultierende Tensor für jeden Zeitschritt zwei Werte für jede Variable. Somit hat die Output-Struktur eine Größe von (T * 2 * V), wobei T die Anzahl der Zeitschritte und V die Anzahl der Variablen ist.
output[2 * shift + v] = d; output[2 * shift + v + variables] = diff; }
Dieser Ansatz ermöglicht es, die Daten in einem einzigen Durchgang für die Einspeisung in das Modell vorzubereiten: Der Tensor enthält bereits sowohl statische als auch dynamische Marktmerkmale.
Die Verwendung von OpenCL bietet in diesem Fall einen zweifachen Vorteil: Sie spart Zeit (durch parallele Verarbeitung) und vereinfacht die Codelogik, da alle Operationen mit Indizes und Arrays innerhalb des Kernels ausgeführt werden. Anstelle von umständlichen Schleifen auf der CPU-Seite haben wir einen kompakten und effizienten Block, der leicht skaliert werden kann, um Daten jeder Länge und Breite zu verarbeiten.
Dieses Modul dient als erster Baustein für das Fundament des gesamten Modells. Es implementiert ein Schlüsselkonzept: die anfängliche Zerlegung einer Zeitreihe in zwei komplementäre Ströme. Die ursprünglichen Werte ermöglichen es, Niveaus und Trends zu erkennen, während die erste Differenz Sensitivität gegenüber Änderungen bietet – eine Art Ableitung, die Wendepunkte signalisiert. Zusammen bilden sie eine ausgewogene Darstellung, die robust gegenüber Rauschen und anpassungsfähig an schnelle Änderungen ist.
Es ist erwähnenswert, ein architektonisches Merkmal dieser Stufe hervorzuheben: Der ConcatDiff-Kernel enthält keine trainierbaren Parameter. Sein Zweck ist rein funktional – einfache lineare Operationen durchzuführen (Werte zu extrahieren und deren Differenzen zu berechnen) und die Ergebnisse in einem einzigen Array zu kombinieren. All dies macht es außergewöhnlich gut geeignet für die Implementierung in OpenCL, wo massiv parallele Verarbeitungsoperationen ohne komplexe Zustandslogik besonders effektiv sind.
Angesichts der spezifischen Natur von Finanzzeitreihen ist es zudem absolut logisch, die Eingabedaten selbst innerhalb einer einzelnen Vorbereitungsiteration als konstant zu behandeln. Genau deshalb implementieren wir in diesem Fall keine Kernel für die Backpropagation des Fehlergradienten oder für die Aktualisierung von Parametern. Solche Aufgaben sind nur für die trainierbaren Schichten relevant, aus denen das Modell besteht. Hier haben wir es jedoch mit der Vorverarbeitung zu tun – einer Stufe, bei der Geschwindigkeit, Zuverlässigkeit und vollständig deterministische Berechnungen entscheidend sind.
Den vollständigen Code für OpenCL ist im Anhang enthalten.
Objekt zur Differenzberechnung
Der nächste Schritt in unserer Arbeit besteht darin, den ConcatDiff-Kernel in die Struktur des Hauptprogramms zu integrieren. Dazu erstellen wir ein spezialisiertes Objekt, das als CNeuronConcatDiff-Klasse implementiert ist. Dieses Objekt dient als Schnittstelle zwischen der Logik des neuronalen Netzwerkmodells auf hoher Ebene und dem OpenCL-Code auf niedriger Ebene, wobei sichergestellt wird, dass die Parameter ordnungsgemäß vorbereitet, der Kernel gestartet und das Ergebnis empfangen wird.
class CNeuronConcatDiff: public CNeuronBaseOCL { protected: uint iUnits; uint iVariables; uint iStep; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override { return true; } virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronConcatDiff(void) : iUnits(0), iVariables(1), iStep(1) { activation = None; } ~CNeuronConcatDiff(void) {}; virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint units_count, uint step, uint variables, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronConcatDiff; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; };
Die Klasse selbst enthält mehrere Schlüsselvariablen:
- iUnits – die Anzahl der Zeitschritte,
- iVariables – die Anzahl der Kanäle oder Variablen in den Quelldaten,
- iStep – ein Verschiebungsparameter, der die Schrittweite für die Berechnung der Differenz angibt. Dieser Wert macht die Komponente vielseitig – sie kann an jede Zeitfensterbreite oder Abtastrate angepasst werden.
Der Klassenkonstruktor initialisiert die Schlüsselparameter mit Standardwerten: ein Kanal (iVariables = 1), eine Schrittweite von eins (iStep = 1) und eine deaktivierte Aktivierungsfunktion (activation = None), was sinnvoll ist – schließlich arbeitet unsere Komponente in der Vorverarbeitungsphase und sollte keine Nichtlinearitäten einführen.
Die Klasse implementiert außerdem eine Objektinitialisierungsmethode namens Init, deren Parameter Konstanten erhalten, die eine eindeutige Interpretation der Architektur des zu erstellenden Objekts ermöglichen.
bool CNeuronConcatDiff::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint units_count, uint step, uint variables, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, 2 * units_count * variables, optimization_type, batch)) return false; if(step<=0 || step >= units_count) return false; //--- iUnits = units_count; iVariables = variables; iStep = step; //--- return true; }
Die Logik hinter der Methode Init ist so einfach und prägnant wie möglich. In ihrem Hauptteil wird die gleichnamige Funktion der Basisklasse aufgerufen, wobei die Besonderheiten der Datenstruktur berücksichtigt werden. Da wir die Eingabedaten und die ersten Differenzwerte im Ausgabe-Array kombinieren, muss die Größe des Ausgabepuffers verdoppelt werden.
Nachdem die Basisklasse initialisiert wurde, werden alle als Eingabe erhaltenen Parameter in den internen Variablen der Klasse gespeichert. Dies ermöglicht deren Verwendung bei der Bildung der Parameter für OpenCL.
Dieser Ansatz macht das Objekt flexibel, unabhängig und wiederverwendbar.
Besondere Aufmerksamkeit gilt der virtuellen feedForward-Methode, die für den Vorwärtsdurchlauf und folglich für den Aufruf des OpenCL-Kernels verantwortlich ist.
In den Methodenparametern erhalten wir einen Zeiger auf das Quelldatenobjekt und überprüfen sofort dessen Gültigkeit.
bool CNeuronConcatDiff::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!OpenCL || !NeuronOCL || !Output) return false; if(getOutputIndex() < 0) return false;
Nach erfolgreichem Bestehen der Prüfungen wird der ConcatDiff-Kernel zur Ausführung in die Warteschlange gestellt. Dies verwendet den Algorithmus, mit dem Sie bereits vertraut sind. Die zuvor in internen Variablen gespeicherten Werte werden verwendet, um die Dimensionalität des Aufgabenraums festzulegen.
{
uint global_work_offset[2] = {0};
uint global_work_size[2] = {iUnits, iVariables};
const int kernel = def_k_ConcatDiff;
if(!OpenCL.SetArgumentBuffer(kernel, def_k_concdiff_data, NeuronOCL.getOutputIndex()))
{
printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
GetLastError(), __LINE__);
return false;
}
if(!OpenCL.SetArgumentBuffer(kernel, def_k_concdiff_output, getOutputIndex()))
{
printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
GetLastError(), __LINE__);
return false;
}
if(!OpenCL.SetArgument(kernel, def_k_concdiff_step, iStep))
{
printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
GetLastError(), __LINE__);
return false;
}
//---
if(!OpenCL.Execute(kernel, global_work_size.Size(), global_work_offset, global_work_size))
{
printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
GetLastError(), __LINE__);
return false;
}
}
Die feedForward-Methode endet jedoch nicht dort. Der nächste Schritt besteht darin, den resultierenden Tensor zu normalisieren – im Wesentlichen die Daten auf eine einheitliche Skala zu bringen, was beim Training eines neuronalen Netzwerks von entscheidender Bedeutung ist. Darüber hinaus führt diese Implementierung eine separate Normalisierung für Merkmale (die ursprünglichen Werte der Variablen werden normalisiert) und für Differenzen (die Werte der ersten Differenz werden separat verarbeitet) durch. Dies ermöglicht es dem Modell, empfindlich auf scharfe Änderungen in der Zeitreihe zu reagieren, ohne seine Stabilität bei großflächigen Schwankungen zu verlieren.
{
uint global_work_offset[1] = {0};
uint global_work_size[1] = {2 * iUnits};
const int kernel = def_k_Normilize;
if(!OpenCL.SetArgumentBuffer(kernel, def_k_norm_buffer, getOutputIndex()))
{
printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
GetLastError(), __LINE__);
return false;
}
if(!OpenCL.SetArgument(kernel, def_k_norm_dimension, iVariables))
{
printf("Error of set parameter kernel %s: %d; line %d", OpenCL.GetKernelName(kernel),
GetLastError(), __LINE__);
return false;
}
if(!OpenCL.Execute(kernel, global_work_size.Size(), global_work_offset, global_work_size))
{
string error;
CLGetInfoString(OpenCL.GetContext(), CL_ERROR_DESCRIPTION, error);
printf("Error of execution kernel %s: %d -> %s", OpenCL.GetKernelName(kernel),
GetLastError(), error);
return false;
}
}
//---
return true;
}
Diese Art der Nachbearbeitung verbessert die Trainingsstabilität erheblich und beschleunigt die Modellkonvergenz durch Standardisierung des Bereichs der Eingabewerte.
Es lohnt sich auch, ein paar Worte zur Methode Gradientenrückpropagation zu sagen – calcInputGradients. Obwohl wir keinen Kernel für den Rückwärtsdurchlauf für diese Schicht erstellen, ist die entsprechende Methode dennoch implementiert.
Warum? Die Antwort ist einfach. Dies ermöglicht es uns, dieses Objekt auf jeder Ebene des Modells einzubetten, ohne befürchten zu müssen, die Kette der Fehlerrückführung zu unterbrechen. Auch wenn diese Schicht ihre eigenen Gradienten nicht berechnen muss, muss sie das Fehlersignal korrekt an die vorhergehenden Objekte weiterleiten. Andernfalls kann das Training des gesamten Modells an dieser Schicht fehlschlagen, was zu einer Stagnation des Gradientenflusses führt und infolgedessen die Optimierung der Parameter der unteren Schichten unmöglich macht.
Der Algorithmus hinter der Methode ist einfach und zuverlässig. Wir erhalten einen Zeiger auf das Quelldatenobjekt (NeuronOCL) und überprüfen sofort dessen Gültigkeit.
bool CNeuronConcatDiff::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) return false;
Wir leiten den im aktuellen Objekt akkumulierten Fehlergradienten entlang des Forward-Passes der Quelldaten weiter und verwenden ihn als Signal für die vorherige Schicht. Dieser Prozess ist eine reine Dekonkatenation ohne jegliche rechnerischen Tricks.
if(!DeConcat(NeuronOCL.getGradient(), getPrevOutput(), getGradient(), iVariables, iVariables, iUnits)) return false;
Bei Bedarf (abhängig von der Architektur) kann der Fehlergradient mithilfe der Ableitung der Aktivierungsfunktion angepasst werden.
if(NeuronOCL.Activation() != None) if(!DeActivation(NeuronOCL.getOutput(), NeuronOCL.getGradient(), NeuronOCL.getGradient(), NeuronOCL.Activation())) return false; //--- return true; }
Dieser Ansatz gewährleistet die Kompatibilität und Flexibilität der Architektur. Das Objekt CNeuronConcatDiff stört die Gradientenfortpflanzung nicht und kann innerhalb komplexer Modelle verwendet werden – sowohl auf unteren Ebenen (eingangsnahen Ebenen) als auch tief innerhalb des Berechnungsgraphen.
Damit schließen wir unsere Diskussion der Algorithmen ab, die zur Implementierung der Methoden der Klasse CNeuronConcatDiff verwendet werden. Den vollständigen Code für diese Klasse und alle ihre Methoden finden Sie im Anhang.
Wir haben allmählich die Grenzen dessen erreicht, was eine angemessene Länge für diesen Artikel darstellt. Trotzdem liegt der Großteil der Arbeit noch vor uns. Wir haben lediglich das Fundament gelegt. Das ist jedoch noch nicht das Ende der Geschichte. Um den Leser nicht zu überfordern und die Lesbarkeit des Artikels zu wahren, machen wir eine kurze Pause und setzen die Implementierung im nächsten Artikel dieser Serie fort. Dort erwartet uns eine ebenso interessante wie technisch anspruchsvolle Phase.
Schlussfolgerung
In diesem Artikel haben wir das Mantis-Framework untersucht, das leichtgewichtiges Design mit hoher Genauigkeit kombiniert. Die Autoren des Frameworks schlugen einen innovativen Ansatz zur Tokenisierung vor, der auf Faltungsschicht und Mean-Pooling basiert, was eine effiziente Darstellung von Zeitreihen als 32 Token der Dimension 256 ermöglicht und die Rechenkosten im Vergleich zu herkömmlichen Methoden reduziert. Kontrastives Vortraining an Beispielen aus verschiedenen Datensätzen erzeugte eine robuste und übertragbare Merkmalsrepräsentation, die andere Ansätze in Zero-Shot- und Fine-Tuning-Szenarien übertraf und einen rekordverdächtig niedrigen Kalibrierungsfehler erreichte.
Der praktische Teil präsentiert eine Implementierung der wichtigsten Datenvorverarbeitungsschicht unter Verwendung von OpenCL und MQL5, was eine parallele und deterministische Vorbereitung der Eingabetensoren gewährleistet. Im nächsten Artikel werden wir die Implementierung unserer eigenen Vision der von den Autoren des Mantis-Frameworks vorgeschlagenen Ansätze fortsetzen.
Links
- Mantis: Lightweight Calibrated Foundation-Model for User-Friendly Time Series Classification
- Weitere Artikel aus dieser Serie
Im Artikel verwendete Software
| # | Name | Typ | Beschreibung |
|---|---|---|---|
| 1 | Research.mq5 | Expert Advisor | Expert Advisor zum Sammeln von Beispielen |
| 2 | ResearchRealORL.mq5 | Expert Advisor | Expert Advisor für das Sammeln von Beispielen nach der Methode Real-ORL |
| 3 | StudyContrast.mq5 | Expert Advisor | Expert Advisor für kontrastives Lernen von Encodern |
| 4 | Study.mq5 | Expert Advisor | Expert Advisor für das Offline-Modelltraining |
| 5 | StudyOnline.mq5 | Expert Advisor | Expert Advisor für das Online-Modelltraining |
| 6 | Test.mq5 | Expert Advisor | Expert Advisor für Modelltests |
| 7 | Trajectory.mqh | Klassenbibliothek | Struktur zur Beschreibung des Systemzustands und der Modellarchitektur |
| 8 | NeuroNet.mqh | Klassenbibliothek | Eine Klassenbibliothek zum Aufbau neuronaler Netze |
| 9 | NeuroNet.cl | Bibliothek | Code-Bibliothek für OpenCL |
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/18246
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.
Marktsimulation: Positionsansicht (V)
Entwicklung eines neuronalen Trading-Roboters auf Basis von Mamba und Selective State Space Models
Implementierung eines Breakeven-Mechanismus in MQL5 (Teil 2): ATR- und RRR-basierter Breakeven
Marktsimulation: Positionsansicht (IV)
- 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.