English Русский Español Português
preview
Neuronale Netze im Handel: Integration der Chaostheorie in die Zeitreihenprognose (Attraos)

Neuronale Netze im Handel: Integration der Chaostheorie in die Zeitreihenprognose (Attraos)

MetaTrader 5Handelssysteme |
56 1
Dmitriy Gizlyk
Dmitriy Gizlyk

Einführung

Finanzmarktzeitreihen sind Abfolgen von Preisdaten, Handelsvolumina und anderen Wirtschaftsindikatoren, die sich im Laufe der Zeit unter dem Einfluss zahlreicher Faktoren entwickeln. Sie spiegeln komplexe dynamische Prozesse wider, darunter Markttrends, Zyklen und kurzfristige Schwankungen, die von Wirtschaftsnachrichten, dem Verhalten der Marktteilnehmer und den makroökonomischen Bedingungen abhängen. Die genaue Vorhersage von Finanzzeitreihen ist für das Risikomanagement, die Entwicklung von Handelsstrategien, die Portfoliooptimierung und den algorithmischen Handel von entscheidender Bedeutung. Vorhersagefehler können zu erheblichen finanziellen Verlusten führen, weshalb die Entwicklung präziserer Zeitreihenanalysemethoden für Analysten, Händler und Finanzinstitute eine Priorität darstellt.

Bei modernen Ansätzen zur Prognose von Finanzzeitreihen wird häufig maschinelles Lernen eingesetzt, darunter neuronale Netze und Deep-Learning-Modelle. Die meisten traditionellen Methoden basieren jedoch auf statistischen Verfahren und linearen Modellen, die bei der Analyse hochvolatiler und chaotischer Daten, wie sie für die Finanzmärkte charakteristisch sind, Probleme bereiten. Marktprozesse weisen häufig nichtlineare Abhängigkeiten, Empfindlichkeit gegenüber Anfangsbedingungen und eine komplexe Dynamik auf, was die Vorhersage zu einer schwierigen Aufgabe macht. Herkömmliche Modelle haben auch Schwierigkeiten, plötzliche Marktereignisse wie Krisen, abrupte Liquiditätsverschiebungen oder durch Panik der Anleger ausgelöste Massenverkäufe von Vermögenswerten zu berücksichtigen. Daher ist die Entwicklung von Ansätzen, die sich an die komplexe Dynamik der Finanzmärkte anpassen lassen, eine wichtige Forschungsrichtung.

Um diesen Herausforderungen zu begegnen, integrieren die Autoren des Attraos-Frameworks in „Attractor Memory for Long-Term Time Series Forecasting: A Chaos Perspective“ die Prinzipien der Chaostheorie und betrachten Zeitreihen als niedrigdimensionale Projektionen mehrdimensionaler chaotischer dynamischer Systeme. Mit diesem Ansatz können verborgene nichtlineare Abhängigkeiten zwischen Marktvariablen erfasst werden, was die Prognosegenauigkeit verbessert. Die Anwendung von Methoden der chaotischen Dynamik in der Zeitreihenanalyse ermöglicht die Identifizierung persistenter Strukturen in Marktdaten und deren Einbeziehung in Vorhersagemodelle.

Das Attraos-Framework löst zwei zentrale Probleme. Erstens modelliert es verborgene dynamische Prozesse mithilfe von Methoden der Phasenraumrekonstruktion. Auf diese Weise lassen sich latente Muster erkennen und nichtlineare Wechselwirkungen zwischen Marktvariablen wie Korrelationen zwischen Vermögenswerten, makroökonomischen Indikatoren und Marktliquidität berücksichtigen. Zweitens verwendet Attraos eine Strategie der lokalen Evolution im Frequenzbereich, die eine Anpassung an veränderte Marktbedingungen ermöglicht und die Differenzierung der Attraktoren verbessert. Im Gegensatz zu traditionellen Modellen, die auf festen Annahmen über die Datenverteilung beruhen, passt sich Attraos dynamisch an die sich entwickelnden Marktstrukturen an und liefert genauere Prognosen über verschiedene Zeithorizonte.

Ferner verfügt das Modell über ein dynamisches Speichermodul mit mehreren Auflösungen, das es ihm ermöglicht, sich an historische Preisbewegungsmuster zu erinnern und diese zu berücksichtigen, während es sich gleichzeitig an veränderte Marktbedingungen anpasst. Dies ist besonders wichtig auf den Finanzmärkten, wo dieselben Muster in verschiedenen Zeitintervallen, aber mit unterschiedlichen Amplituden und Intensitäten wiederkehren können. Die Fähigkeit des Modells, aus Daten auf mehreren Granularitätsebenen zu lernen, bietet einen bedeutenden Vorteil gegenüber traditionellen Ansätzen, die oft die multiskalige Natur von Marktprozessen vernachlässigen.

Die von den Autoren des Frameworks durchgeführten Experimente zeigen, dass Attraos die traditionellen Zeitreihenvorhersagemethoden übertrifft und genauere Vorhersagen mit weniger trainierbaren Parametern liefert. Die Nutzung der chaotischen Dynamik ermöglicht es dem Modell, langfristige Abhängigkeiten besser zu erfassen und die Auswirkungen kumulativer Fehler abzuschwächen, was für mittel- und langfristige Investitionsstrategien entscheidend ist.


Der Algorithmus von Attraos

In der Chaostheorie können mehrdimensionale dynamische Systeme, die sich mit der Zeit entwickeln, ein Verhalten zeigen, das zufällig und komplex erscheint. Eine detaillierte Analyse offenbart jedoch verborgene Regelmäßigkeiten, die ihre Dynamik bestimmen.

Nach dem Theorem von Taken kann der Phasenraum eines deterministischen Systems aus den Beobachtungen einer einzigen Variablen rekonstruiert werden. Dieses Theorem beruht auf der Idee, dass mit einer ausreichend langen Zeitreihe die mehrdimensionale Trajektorie des Systems wiederhergestellt werden kann, selbst wenn nur eindimensionale Daten verfügbar sind. Mit diesem Ansatz können verborgene Attraktoren identifiziert werden, die Regionen im Phasenraum darstellen, zu denen die Systemtrajektorien im Laufe der Zeit konvergieren.

Auf den Finanzmärkten enthalten Zeitreihen wie Aktienkurse, Währungspaare und andere Vermögensdaten Informationen über die interne Dynamik des Marktsystems. Trotz des scheinbaren äußeren Chaos kann das Marktverhalten deterministischen Gesetzen folgen, die durch Methoden der Phasenraumrekonstruktion aufgedeckt werden können. Die Anwendung dieser Methoden ermöglicht es den Analysten, charakteristische Attraktoren zu identifizieren, die dann als Grundlage für die Vorhersage künftiger Kursbewegungen dienen können.

Das Attraos-Framework baut auf der Chaostheorie und den Prinzipien der Phasenraumrekonstruktion auf und ermöglicht die Erstellung von topologisch äquivalenten Modellen dynamischer Systeme ohne vorherige Kenntnis ihrer zugrunde liegenden Struktur. Die zentrale Idee besteht darin, eine Zeitreihe in einem mehrdimensionalen Phasenraum darzustellen, um verborgene Muster aufzudecken. Dies gelingt durch die Auswahl von Schlüsselparametern – der Einbettungsdimension d und Zeitverzögerung –, die die mehrdimensionale Darstellung der Zeitreihe bilden:

Dieser Ansatz reduziert den Einfluss des Rauschens, hebt strukturelle Elemente des Prozesses hervor und verbessert die Prognosegenauigkeit. Die Rekonstruktion des Phasenraums ermöglicht die Erstellung eines stabilen Phasenporträts, das die Systemdynamik über die Zeit widerspiegelt und die Analyse und Modellierung ermöglicht.

Die Datenverarbeitung in Attraos beginnt mit dem Modul PSR (Phase Space Reconstruction), das für die Auswahl der optimalen Werte von d und τ zur korrekten Darstellung der Systemdynamik zuständig ist. Dieser Schritt ist entscheidend, da falsch gewählte Parameter das rekonstruierte Phasenporträt erheblich verzerren können. Anschließend unterteilt die Multidimensional Data Management Unit (MDMU) die Daten mithilfe mehrdimensionaler Tensoren in nicht überlappende Segmente. Dies reduziert die Rechenkomplexität, beschleunigt die Modellkonvergenz und bildet das dynamische Gedächtnis des Systems, indem es wichtige evolutionäre Muster erfasst. Folglich werden die Prognosen stabiler und robuster gegenüber potenziellen Datenverschlechterungen. MDMU implementiert zusätzlich eine adaptive Auswahl signifikanter Zeitreihenkomponenten, wobei uninformative Elemente dynamisch ausgeschlossen und Schlüsselfaktoren, die die Systementwicklung am stärksten beeinflussen, hervorgehoben werden.

Ein wesentliches Element der Attraos-Architektur ist das Modul Linear Matrix Approximation (LMA), das eine lineare Matrix-Approximationsmethode implementiert. Es verwendet polynomiale Projektionen, um die wichtigsten Merkmale der Systemdynamik zu extrahieren, wobei parametrisierte diagonale Matrizen verwendet werden, die „Messfenster“ definieren. Dies ermöglicht eine präzise Extraktion lokaler dynamischer Merkmale und eine adaptive Modellkorrektur bei sich ändernden Eingangsdaten. Die Verwendung diagonaler Matrizen verringert die Rechenkomplexität und ermöglicht einen effizienten Umgang mit mehrdimensionalen Datenstrukturen. Die Systementwicklung in diesem Modul ist formalisiert als:

wobei M die parametrisierte, diagonale Zustandsmatrix ist und ϵ den zufälligen Modellierungsfehler darstellt. Verschiedene Evolutionsvarianten, einschließlich adaptiver nichtlinearer Projektionen, werden eingesetzt, um komplexe Systemtransformationen zu erfassen und die Prognosegenauigkeit zu verbessern.

Für die Bearbeitung dynamischer Prozesse diskretisiert das Modul Discrete Projection (DP) das System mit kombinierten Methoden. Insbesondere wird eine exponentielle Matrixdarstellung verwendet, die eine hohe Genauigkeit bei der sequenziellen Datenanalyse bietet. Dieser Ansatz gewährleistet eine korrekte Annäherung an die Systementwicklung und mildert die Auswirkungen kumulativer Fehler, was besonders bei langen Zeitreihen wichtig ist. Das Modul umfasst auch eine adaptive Datenquantisierung, die die Genauigkeit der Annäherung ohne großen Rechenaufwand optimiert.

Zu den zusätzlichen adaptiven Mechanismen in Attraos gehört eine lokale Evolutionsstrategie mit Frequenzverstärkung, die die spektralen Eigenschaften der Zeitreihen reguliert und durch stochastische Prozesse verursachte Verzerrungen ausgleicht. Dies wird durch die Filterung hochfrequenter Komponenten und die Kontrolle der spektralen Dichte der Daten erreicht. Außerdem wird eine mehrstufige Darstellung der dynamischen Struktur implementiert, bei der das Analysefenster schrittweise erweitert wird, um Projektionsfehler zu reduzieren und die Annäherungsgenauigkeit zu verbessern. Dies ermöglicht es dem Framework, die komplexe topologische Organisation von Attraktoren auf verschiedenen Skalen zu erfassen, was für komplexe dynamische Systeme entscheidend ist. Hybride Lernmethoden, die klassische numerische Techniken mit modernen Algorithmen des maschinellen Lernens kombinieren, verbessern die Anpassungsfähigkeit und Verallgemeinerbarkeit der Modelle weiter.

Die Stabilität von Attraos wird durch Fehlerminimierung und Kontrolle der Attraktorabweichung gewährleistet. Die Abstände zwischen den Attraktoren werden berechnet und auf Veränderungen hin überwacht, und die Systemtrajektorien werden korrigiert, wenn erhebliche Abweichungen festgestellt werden. Diese Maßnahmen stabilisieren die Vorhersagen und gewährleisten eine hohe Modellgenauigkeit auch bei instabiler Dynamik. Statistische Überwachungsmethoden identifizieren automatisch instabile Regionen und passen das Modell in Echtzeit an. Aktive Parameterkontrollmechanismen ändern die Optimierungsparameter dynamisch auf der Grundlage des Systemzustands, was die Anpassungsfähigkeit und die Vorhersagegenauigkeit weiter verbessert.

Die Originalabbildung des Attraos-Frameworks ist nachstehend dargestellt.



Implementierung mit MQL5

Nachdem wir die theoretischen Aspekte des Attraos-Frameworks erörtert haben, gehen wir nun zum praktischen Teil des Artikels über, in dem wir unsere Interpretation der vorgeschlagenen Ansätze mit MQL5 umsetzen.

Wir beginnen mit der Vorbereitung der wichtigsten Prozesse, die für das effektive Funktionieren des Attraos-Algorithmus erforderlich sind. Der Algorithmus stützt sich in hohem Maße auf diagonale Matrizen, die bei den Berechnungen eine entscheidende Rolle spielen, die Datenverarbeitung vereinfachen und den Speicherbedarf verringern.

Eine Diagonalmatrix ist eine quadratische Matrix, in der alle Elemente außer der Hauptdiagonalen null sind. In der traditionellen linearen Algebra werden solche Matrizen als n×n zweidimensionale Arrays gespeichert. Dies ist jedoch ineffizient, da die große Mehrheit der Elemente null ist. Eine optimale Lösung besteht darin, nur die Diagonalelemente, die nicht null sind, in einem eindimensionalen Feld der Länge n zu speichern. Dadurch wird der Speicherverbrauch erheblich reduziert und die Berechnung beschleunigt, da Operationen auf Nullelementen vermieden werden.

Die Verwendung einer Vektordarstellung von Diagonalmatrizen erfordert einen speziellen Algorithmus für lineare Algebra-Operationen, insbesondere für die Multiplikation einer Diagonalmatrix mit einer beliebigen Matrix. Standard-Matrixmultiplikationsalgorithmen gehen von einer expliziten Speicherung aller Elemente aus, was zu unnötigen Berechnungen führt. In diesem Fall reicht es aus, jedes Element des Diagonalvektors mit der entsprechenden Zeile der anderen Matrix zu multiplizieren. Dies vereinfacht den Algorithmus und erhöht die Effizienz.

Um die Leistung zu maximieren, wird dieser Prozess in dem Kontext von OpenCL implementiert, der die parallelen Berechnungsmöglichkeiten von GPUs nutzt. Der Kerngedanke besteht darin, dass jedes Element der sich ergebenden Matrix unabhängig berechnet wird, wobei nur das relevante diagonale Vektorelement verwendet wird. Dadurch wird die Komplexität der Berechnungen verringert und die Ausführung beschleunigt.

Diagonale Matrixmultiplikation


Der Multiplikationsalgorithmus ist im DiagMatMult-Kernel implementiert, der in einem dreidimensionalen Aufgabenraum arbeitet. Die ersten beiden Dimensionen entsprechen den Dimensionen der zweiten Matrix, während die dritte Dimension die Anzahl der unabhängigen Matrizen widerspiegelt, die zur Verarbeitung von Projektionen von Einheitssequenzen aus den analysierten mehrdimensionalen Zeitreihen verwendet werden.

Wie bereits erwähnt, wird bei der Multiplikation der Vektordarstellung einer Diagonalmatrix mit einer beliebigen Matrix jedes Element des Diagonalvektors mit der entsprechenden Zeile der zweiten Matrix multipliziert. Zur Optimierung der GPU-Ausführung gruppieren wir Berechnungs-Threads in Arbeitsgruppen. Innerhalb jeder Arbeitsgruppe greift nur ein Thread auf den globalen Speicher zu, um das erforderliche diagonale Vektorelement zu holen, und speichert es im lokalen Speicher. Die übrigen Threads verwenden diesen Wert aus dem lokalen Speicher, um die erforderlichen Berechnungen ohne zusätzlichen globalen Speicherzugriff durchzuführen. Dadurch werden die Ausführungsgeschwindigkeit der Algorithmen erheblich verbessert und der Overhead durch Datenübertragungen zwischen dem globalen Speicher und den Verarbeitungseinheiten verringert.

Der DiagMatMult-Kernel erhält Zeiger auf drei Datenpuffer als Parameter. Zwei davon enthalten Eingabedaten, während der dritte die Ergebnisse der Operation speichert. Wir haben auch eine Option eingebaut, um eine Aktivierungsfunktion auf die Multiplikationsergebnisse anzuwenden.

__kernel void DiagMatMult(__global const float * diag,
                          __global const float * matr,
                          __global float * result,
                          int activation)
  {
   size_t row = get_global_id(0);
   size_t col = get_local_id(1);
   size_t var = get_global_id(2);
   size_t rows = get_global_size(0);
   size_t cols = get_local_size(1);

Innerhalb des Kernelkörpers besteht der erste Schritt darin, den aktuellen Thread in allen drei Dimensionen des Aufgabenraums zu identifizieren. Dann speichert der erste Thread der Arbeitsgruppe das Diagonalelement im lokalen Speicher, und die Threads werden über eine Barriere synchronisiert.

   __local float local_diag[1];
   if(cols==0)
      local_diag[0] = diag[row + var * rows];
   barrier(CLK_LOCAL_MEM_FENCE);

Als Nächstes berechnen wir den Versatz im Puffer der beliebigen Matrix zum gewünschten Element.

   int shift = (row  + var * rows) * cols + col;

Es ist wichtig zu beachten, dass die beliebige Matrix und die Ergebnismatrix identische Dimensionen haben. Daher gilt der berechnete Versatz auch für die Ergebnismatrix.

Dann extrahieren wir das gewünschte Element aus dem Puffer für eine beliebige Matrix und multiplizieren es mit dem zuvor im lokalen Speicher abgelegten Diagonalelement. Das Ergebnis wird mit der vorgesehenen Aktivierungsfunktion aktiviert. Die Ausgabe wird dann im Ergebnismatrixpuffer gespeichert.

   float res = local_diag[0] * matr[shift];
//---
   result[shift] = Activation(res, activation);
  }

Der oben beschriebene Kernel implementiert den Vorwärtsdurchlauf für die Multiplikation einer Diagonalmatrix mit einer beliebigen Matrix. Dies ist jedoch nur die Hälfte des Prozesses. Während des Modelltrainings müssen wir auch den Fehlergradienten durch diesen Vorgang weitergeben. Um dies zu erreichen, erstellen wir einen zusätzlichen Kernel DiagMatMultGrad, der einen etwas komplexeren Algorithmus beinhaltet.

In diesem Kernel werden den Parametern Puffer zur Speicherung der entsprechenden Fehlergradienten hinzugefügt. Aber wir schließen den Aktivierungsfunktionszeiger aus. Es wird davon ausgegangen, dass die Ergebnisgradienten bereits durch die Ableitung der entsprechenden Aktivierungsfunktion korrigiert wurden.

__kernel void DiagMatMultGrad(__global const float *diag,
                              __global float *grad_diag,
                              __global const float *matr,
                              __global float * grad_matr,
                              __global const float * grad_result)
  {
   size_t row = get_global_id(0);
   size_t col = get_local_id(1);
   size_t var = get_global_id(2);
   size_t rows = get_global_size(0);
   size_t cols = get_local_size(1);
   size_t vars = get_global_size(2);

Im Kernelkörper wird der aktuelle Thread im dreidimensionalen Aufgabenraum identifiziert, wobei der gleiche Ansatz wie beim Kernel des Vorwärtsdurchlaufs verwendet wird.

Ähnlich wie beim Kernel des Vorwärtsdurchlaufs holt nur ein Thread pro Arbeitsgruppe das erforderliche diagonale Matrixelement in ein lokales Array.

   __local float local_diag[LOCAL_ARRAY_SIZE];
   if(cols==0)
      local_diag[0] = diag[row + var * rows];
   barrier(CLK_LOCAL_MEM_FENCE);

Wir synchronisieren auch Threads innerhalb der Arbeitsgruppe.

Als Nächstes berechnen wir den Versatz in den Puffern für die beliebige Matrix und die der Ergebnisse.

   int shift = (row  + var * rows) * cols + col;
//---
   float grad = grad_result[shift];
   float inp = matr[shift];

Wir speichern die erforderlichen Matrixelemente in lokalen Variablen.

Zu diesem Zeitpunkt ist die Vorbereitung abgeschlossen. Wir können den Fehlergradienten in Bezug auf die beliebige Matrix berechnen. Dies geschieht durch Multiplikation des Fehlergradienten des entsprechenden Elements in der Ergebnismatrix mit dem zuvor im lokalen Array gespeicherten Diagonalelement.

   grad_matr[shift] = IsNaNOrInf(local_diag[0] * grad, 0);
   barrier(CLK_LOCAL_MEM_FENCE);

Der resultierende Gradient wird im entsprechenden globalen Speicherpuffer gespeichert, und die Threads werden innerhalb der Arbeitsgruppe synchronisiert.

Als Nächstes bestimmen wir den Fehlergradienten für die diagonalen Matrixelemente. Hier müssen wir Werte aus allen Elementen der entsprechenden Zeile im Ergebnispuffer aggregieren. Dazu müssen die Werte aller Threads in der Arbeitsgruppe addiert werden.

Um dies zu erreichen, implementieren wir eine parallele Reduktionsschleife für die lokalen Array-Elemente.

   int loc = col % LOCAL_ARRAY_SIZE;
#pragma unroll
   for(int c = 0; c < cols; c += LOCAL_ARRAY_SIZE)
     {
      if(c <= col && (c + LOCAL_ARRAY_SIZE) > col)
        {
         if(c == 0)
            local_diag[loc] = IsNaNOrInf(grad * inp, 0);
         else
            local_diag[loc] += IsNaNOrInf(grad * inp, 0);
        }
      barrier(CLK_LOCAL_MEM_FENCE);
     }

Die maximale Anzahl der aktiven Threads pro Iteration ist auf die Anzahl der Elemente im lokalen Array begrenzt. Nach jeder Iteration stellen wir sicher, dass die Threads der Arbeitsgruppe synchronisiert werden, bevor wir mit der nächsten Schleifeniteration für die nächste Gruppe aktiver Threads fortfahren.

Als Nächstes fügen wir eine Schleife zur parallelen Summierung der Elemente des lokalen Arrays hinzu.

   int count = min(LOCAL_ARRAY_SIZE, (int)cols);
   int ls = count;
#pragma unroll
   do
     {
      count = (count + 1) / 2;
      if((col + count) < ls)
        {
         local_diag[col] += local_diag[col + count];
         local_diag[col + count] = 0;
        }
      barrier(CLK_LOCAL_MEM_FENCE);
     }
   while(count > 1);

Die Anzahl der aktiven Threads wird in jeder Iteration reduziert. Die Synchronisierung wird jedoch bei jedem Schritt beibehalten, um eine korrekte Berechnung zu gewährleisten.

Um den endgültigen aggregierten Wert im globalen Speicher zu speichern, wird nur ein Thread pro Arbeitsgruppe benötigt.

   if(col == 0)
      grad_diag[row + var * rows] = IsNaNOrInf(local_diag[0], 0);
  }

Dieser Ansatz minimiert kostspielige globale Speicherzugriffe und maximiert die parallele Ausführung über Threads hinweg, was den gesamten Trainingsaufwand reduziert.

Algorithmus für paralleles Scannen


Wir setzen die Arbeit auf der Seite von OpenCL fort und gehen nun zur Implementierung der Algorithmen des Attraos-Frameworks über. Die nächste Aufgabe besteht darin, den parallelen Scan-Algorithmus zu implementieren, der zur effizienten Aktualisierung des Eingabedatenfelds X unter Berücksichtigung der Interaktionskoeffizientenmatrizen A und der Normalisierungsfaktoren H verwendet wird. Die Hauptidee des Algorithmus ist die iterative Berechnung von Präfixsummen unter Verwendung der binären Zerlegung, wodurch die Rechenkomplexität von O(L), typisch für sequenzielle Methoden, auf O(log L) reduziert wird.

Das Array X = {x0, x1, ..., xL-1} wird unter Verwendung benachbarter Elemente gemäß der folgenden Rekursion aktualisiert:

wobei θ1 und θ2 während des Algorithmus iterativ bestimmt werden. Der Vektor A stellt die Matrix der Interaktionskoeffizienten zwischen benachbarten Elementen dar, und H normalisiert die berechneten Ergebnisse. Diese Parameter definieren eine adaptive Gewichtsverteilung, die es dem Algorithmus ermöglicht, die Struktur der Daten zu berücksichtigen und komplexe Abhängigkeiten effektiv zu modellieren.

Dieser Prozess ist im PScan-Kernel implementiert. Zu den Kernelparametern gehören Zeiger auf vier Datenpuffer: drei für die Eingabe und einer für das Schreiben der Ergebnisse.

__kernel void PScan(__global const float* A,
                    __global const float* X,
                    __global const float* H,
                    __global float* X_out)
  {
   const size_t idx = get_local_id(0);
   const size_t dim = get_global_id(1);
   const size_t L = get_local_size(0);
   const size_t D = get_global_size(1);

Der Kernel wird in einem zweidimensionalen Aufgabenraum ausgeführt, wobei die Threads entlang der ersten Dimension gruppiert sind. Die Thread-Identifikation und die Dimensionen des Task-Raums werden im Kernel bestimmt.

Die Anzahl der Iterationen num_steps wird als binärer Logarithmus der Sequenzlänge berechnet:

   const int num_steps = (int)log2((float)L);

Die Verwendung des binären Logarithmus gewährleistet eine minimale Anzahl von Iterationen für einen vollständigen Durchlauf der Daten und damit eine optimale Nutzung der Rechenressourcen.

Um kostspielige globale Speicherzugriffe zu reduzieren, werden lokale Arrays erstellt, um Werte vorübergehend zu speichern.

   __local float local_A[1024];
   __local float local_X[1024];
   __local float local_H[1024];

Jeder Thread lädt Daten aus dem globalen Speicher in den lokalen Speicher, wodurch die Latenzzeit für nachfolgende Operationen verringert und die Gesamtleistung verbessert wird.

//--- Load data to local memory
   int offset = dim + idx * D;
   local_A[idx] = A[offset];
   local_X[idx] = X[offset];
   local_H[idx] = H[offset];
   barrier(CLK_LOCAL_MEM_FENCE);

Nach dem Laden der Daten synchronisieren wir die Threads innerhalb der Arbeitsgruppe und stellen sicher, dass die Daten korrekt gelesen und geschrieben werden, bevor wir mit den Berechnungen fortfahren.

Anschließend wird die Hauptphase der Operationen durchgeführt. Es handelt sich um die parallele Summierung von Werten. Die Anzahl der aktiven Threads wird bei jeder Iteration der Schleife halbiert.

//--- Scan
#pragma unroll
   for(int step = 0; step < num_steps; step++)
     {
      int halfT = L >> (step + 1);
      if(idx < halfT)
        {
         int base = idx * 2;
         local_X[base + 1] += local_A[base + 1] * local_X[base];
         local_X[base + 1] *= local_H[base + 1];
         local_A[base + 1] *= local_A[base];
        }
      barrier(CLK_LOCAL_MEM_FENCE);
     }

Innerhalb der Schleife werden die Werte der Eingabefelder summiert, mit H normalisiert und die Interaktionskoeffizienten A aktualisiert, wobei die Reihenfolge der Berechnungen während der iterativen Aktualisierungen beibehalten wird. Die Optimierung mit #pragma unroll ermöglicht es dem Compiler, die Schleife frühzeitig aufzurollen, was den Verzweigungs-Overhead reduziert und eine effizientere Datenverarbeitung ermöglicht.

Nach Abschluss der Schleifeniterationen werden die resultierenden Werte aus dem lokalen Speicher in den globalen Ergebnispuffer übertragen.

//--- Save result
   X_out[offset] = local_X[idx];
  }

Dieser Ansatz beschleunigt die Datenverarbeitung erheblich und ermöglicht paralleles Scannen bei optimaler Nutzung der Rechenressourcen.

Der nächste Schritt besteht darin, den Backpropagation-Algorithmus für die parallele Abtastung zu konstruieren. Wir erstellen einen neuen Kernel, PScan_CalcHiddenGradient, dessen Hauptaufgabe darin besteht, Parameter durch Reverse Scanning zu differenzieren.

Zu den Kernelparametern gehören Zeiger auf Puffer zur Speicherung der entsprechenden Fehlergradienten.

__kernel void PScan_CalcHiddenGradient(__global const float* A,
                                       __global float*  grad_A,
                                       __global const float* X,
                                       __global float*  grad_X,
                                       __global const float* H,
                                       __global float*  grad_H,
                                       __global const float* grad_X_out)
  {
   const size_t idx = get_local_id(0);
   const size_t dim = get_global_id(1);
   const size_t L = get_local_size(0);
   const size_t D = get_global_size(1);
   const int num_steps = (int)log2((float)L);

Der Algorithmus beginnt mit der Identifizierung von Threads in einem Aufgabenraum, der dem im Feedforward-Durchlauf verwendeten ähnlich ist. Da das parallele Scannen in mehreren Iterationen erfolgt, wird die Anzahl der Schritte als binärer Logarithmus der Sequenzlänge berechnet, wodurch der Prozess in eine hierarchische Struktur mit sequenzieller Zusammenführung von Werten zerlegt werden kann.

Ein wichtiger Aspekt dieser Implementierung ist die Minimierung des Zugriffs auf den globalen Speicher, was durch die Verwendung lokaler Speicherarrays erreicht wird. Dies verbessert die Leistung erheblich, da der lokale Speicher einen schnelleren Zugriff auf die Daten ermöglicht als der globale Speicher. Um die Originaldaten und die Zwischenwerte der Fehlergradienten zu speichern, werden entsprechende Puffer deklariert.

   __local float local_A[1024];
   __local float local_X[1024];
   __local float local_H[1024];
   __local float local_grad_X[1024];
   __local float local_grad_A[1024];
   __local float local_grad_H[1024];

Nachdem wir lokale Arrays deklariert haben, übertragen wir die Quelldaten aus globalen Puffern in diese Arrays. So kann jeder Thread das Element laden, das ihm entspricht. Dann synchronisieren wir Threads innerhalb der lokalen Gruppe, um Zugriffskonflikte zu vermeiden.

//--- Load data to local memory
   int offset = idx * D + dim;
   local_A[idx] = A[offset];
   local_X[idx] = X[offset];
   local_H[idx] = H[offset];
   local_grad_X[idx] = grad_X_out[offset];
   local_grad_A[idx] = 0.0f;
   local_grad_H[idx] = 0.0f;
   barrier(CLK_LOCAL_MEM_FENCE);

Als Nächstes kommt die wichtigste Phase des Algorithmus – das Reverse Scanning. Diese Phase wird in einem iterativen Prozess durchgeführt. Bei jeder Iteration wird die Größe des zu verarbeitenden Arrays reduziert.

//--- Reverse Scan (Backward)
#pragma unroll
   for(int step = num_steps - 1; step >= 0; step--)
     {
      int halfT = L >> (step + 1);
      if(idx < halfT)
        {
         int base = idx * 2;
         // Compute gradients
         float grad_next = local_grad_X[base + 1] * local_H[base + 1];
         local_grad_H[base + 1] = local_grad_X[base + 1] * local_X[base];
         local_grad_A[base + 1] = local_grad_X[base + 1] * local_X[base];
         local_grad_X[base] += local_A[base + 1] * grad_next;
        }
      barrier(CLK_LOCAL_MEM_FENCE);
     } 

Im Schleifenkörper berechnen wir zunächst die Anzahl der aktiven Threads, die an einer bestimmten Iteration teilnehmen (halfT). Dann bestimmen wir den Fehlergradienten (grad_next), der durch Multiplikation des aktuellen Wertes mit dem entsprechenden Normierungskoeffizienten H ermittelt wird. Als Nächstes berechnen wir die Ableitungen nach den Normalisierungskoeffizienten H und den Wechselwirkungen A unter Verwendung des aktuellen Werts X. Um den Fehler korrekt rückwärts zu übertragen, wird der Gradientenwert X unter Berücksichtigung des Wechselwirkungskoeffizienten angepasst. Und wir müssen die Threads innerhalb der Arbeitsgruppe bei jeder Iteration der Schleife synchronisieren.

Am Ende der Kerneloperationen müssen die aktualisierten Fehlergradienten wieder in den globalen Speicher übertragen werden.

//--- Save gradients
   grad_A[offset] = local_grad_A[idx];
   grad_X[offset] = local_grad_X[idx];
   grad_H[offset] = local_grad_H[idx];
  }

Diese Datenverarbeitungsmethode bietet eine hohe Recheneffizienz aufgrund der Verwendung von lokalem Speicher und einer minimalen Anzahl von globalen Speicherzugriffen.

Damit ist unsere Implementierungsarbeit auf der Seite von OpenCL abgeschlossen. Den vollständigen Programmcode für OpenCL finden Sie im Anhang.

Der nächste Schritt unserer Arbeit ist die Konstruktion von Algorithmen auf der Seite des Hauptprogramms. Da wir uns jedoch der Begrenzung des Artikelformats nähern, werden wir eine kurze Pause einlegen und den Aufbau des Attraos-Frameworks im nächsten Teil fortsetzen.


Schlussfolgerung

In diesem Artikel haben wir das Attraos-Framework untersucht, das einen auf der Chaostheorie basierenden Algorithmus für Zeitreihenprognosen vorschlägt. Zeitreihen werden als Projektionen mehrdimensionaler chaotischer dynamischer Systeme interpretiert, was die Identifizierung verborgener Muster ermöglicht, die für herkömmliche statistische oder Regressionsmodelle unzugänglich sind. Attraos implementiert Mechanismen zur Rekonstruktion des Phasenraums und des dynamischen Speichers, die die Erkennung stabiler nichtlinearer Abhängigkeiten in Marktdaten ermöglichen und die Prognosegenauigkeit verbessern.

Im Gegensatz zu traditionellen linearen Modellen, die komplexe mehrdimensionale Wechselwirkungen zwischen Variablen nicht berücksichtigen, arbeitet Attraos mit der internen Struktur chaotischer Attraktoren und gewährleistet auf diese Weise eine hohe Prognosegenauigkeit und Anpassungsfähigkeit an sich ändernde Marktbedingungen. Dieser Ansatz ermöglicht die Erkennung deterministischer Komponenten in Prozessen, die zunächst zufällig erscheinen, was besonders für die Analyse von Hochfrequenzdaten und kurzfristige Finanzprognosen wichtig ist.

Im praktischen Teil begannen wir mit der Umsetzung unserer Vision der vorgeschlagenen Methoden unter Verwendung von MQL5 mit OpenCL-Technologie, was die Berechnungen durch parallele Datenverarbeitung auf GPUs erheblich beschleunigte. Dies ermöglicht die Anwendung der Attraos-Methode in realen Handelssystemen und automatisierten Analyseplattformen, die eine Hochgeschwindigkeitsverarbeitung großer Datensätze und eine schnelle Anpassung an sich verändernde Marktbedingungen ermöglichen.

Im nächsten Artikel werden wir mit der Umsetzung dieser Ansätze fortfahren und sie mit der Validierung an realen historischen Daten abschließen.


Liste der Referenzen


In diesem Artikel verwendete Programme

# Name Typ Beschreibung
1 Research.mq5 Expert Advisor Expert Advisor für die Probenahme
2 ResearchRealORL.mq5
Expert Advisor
Expert Advisor für die Probenahme mit der Methode Real-ORL
3 Study.mq5 Expert Advisor Expert Advisor für das Training des Modells
4 Test.mq5 Expert Advisor Expert Advisor für Modelltests
5 Trajectory.mqh Klassenbibliothek Struktur der Beschreibung des Systemzustands und der Modellarchitektur
6 NeuroNet.mqh Klassenbibliothek Eine Bibliothek von Klassen zur Erstellung eines neuronalen Netzes
7 NeuroNet.cl Code-Bibliothek OpenCL-Programmcode

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/17351

Beigefügte Dateien |
MQL5.zip (2509.49 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Tbor Yorgonson
Tbor Yorgonson | 5 März 2026 in 20:50
Ich weiß, dass die Theorie anders ist, aber ich denke, dass Kalman-Filterung sogar irgendwo in einigen Experimenten verwendet werden könnte, die von dieser Idee abzweigen.
Risikomanagement (Teil 4): Fertigstellung der Methoden der Hauptklasse Risikomanagement (Teil 4): Fertigstellung der Methoden der Hauptklasse
Dies ist Teil 4 unserer Serie über Risikomanagement in MQL5, in der wir fortgeschrittene Methoden zum Schutz und zur Optimierung von Handelsstrategien erforschen. Nachdem wir in früheren Artikeln wichtige Grundlagen gelegt haben, werden wir uns nun darauf konzentrieren, alle verbleibenden, in Teil 3 verschobenen Methoden zu vervollständigen, einschließlich der Funktionen zur Überprüfung, ob bestimmte Gewinn- oder Verlustniveaus erreicht wurden. Ferner werden wir neue Schlüsselereignisse einführen, die ein genaueres und flexibleres Risikomanagement ermöglichen.
Marktsimulation (Teil 13): Sockets (VII) Marktsimulation (Teil 13): Sockets (VII)
Wenn wir etwas in xlwings oder einem anderen Paket entwickeln, das das Lesen und Schreiben direkt in Excel ermöglicht, müssen wir beachten, dass alle Programme, Funktionen oder Prozeduren ausgeführt werden und dann ihre Aufgabe beenden. Sie bleiben nicht in einer Schleife, egal wie sehr wir uns bemühen, die Dinge anders zu machen.
Marktsimulation (Teil 15): Sockets (IX) Marktsimulation (Teil 15): Sockets (IX)
In diesem Artikel besprechen wir eine der möglichen Lösungen für das, was wir versucht haben zu demonstrieren, nämlich wie man es einem Excel-Nutzer ermöglicht, eine Aktion in MetaTrader 5 auszuführen, ohne Aufträge zu senden oder Positionen zu öffnen oder zu schließen. Die Idee ist, dass der Nutzer Excel verwendet, um eine fundamentale Analyse eines bestimmten Symbols durchzuführen. Und allein mit Excel lässt sich ein in MetaTrader 5 laufender Expert Advisor anweisen, eine bestimmte Position zu eröffnen oder zu schließen.
Trend-Kriterien. Abschluss Trend-Kriterien. Abschluss
In diesem Artikel werden wir uns mit den Besonderheiten der Anwendung einiger Trendkriterien in der Praxis befassen. Wir werden auch versuchen, mehrere neue Kriterien zu entwickeln. Der Schwerpunkt wird auf der Effizienz der Anwendung dieser Kriterien auf die Analyse von Marktdaten und den Handel liegen.