English Русский Español 日本語 Português
preview
Neuronale Netze im Trading: Adaptive Erkennung von Marktanomalien (Abschlussteil)

Neuronale Netze im Trading: Adaptive Erkennung von Marktanomalien (Abschlussteil)

MetaTrader 5Handelssysteme |
31 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Einführung

Im vorangegangenen Artikel haben wir die theoretischen Grundlagen des DADA-Frameworks (Adaptive Bottlenecks and Dual Adversarial Decoders) untersucht, das zur Erkennung von Anomalien in Zeitreihen mithilfe von Deep Learning-Techniken entwickelt wurde. Dieses Tool ermöglicht eine effektive Datenanalyse und die Identifizierung von anormalen Marktbedingungen, was besonders in hochvolatilen Umgebungen von entscheidender Bedeutung ist. Durch den Einsatz adaptiver Informationsverarbeitungsmethoden kann sich das Modell flexibel an die sich verändernde Marktdynamik anpassen, was es zu einer vielseitigen Lösung für die Analyse einer breiten Palette von Zeitreihen macht.

Die Architektur des DADA-Frameworks besteht aus drei Schlüsselkomponenten, die jeweils einen bestimmten Zweck erfüllen. Das erste ist das Modul Adaptive Bottlenecks, das den Grad der auf die Eingabedaten angewandten Kompression dynamisch anpassen kann. Dieser Ansatz trägt dazu bei, die wichtigsten Merkmale der Marktdaten zu erhalten und gleichzeitig den Informationsverlust zu minimieren, der die analytische Genauigkeit beeinträchtigen könnte. Im Gegensatz zu herkömmlichen Modellen mit festen Kompressionsparametern passt sich dieses System in Echtzeit an die aktuellen Marktbedingungen an.

Die zweite Hauptkomponente ist ein Paar adversarischer Decoder. Der erste Decoder rekonstruiert normale Marktzustände, wodurch das Modell typische Verhaltensmuster besser erfassen kann. Der zweite Decoder konzentriert sich auf anomale Daten und ermöglicht eine klare Trennung zwischen normalen und anomalen Szenarien. Durch diese doppelte Dekodierung werden Fehlalarme reduziert und die Robustheit des Modells insgesamt verbessert.

Das dritte Schlüsselelement ist der Patching- und Maskierungsmechanismus, der eine wesentliche Rolle bei der Verarbeitung von Zeitreihendaten spielt. Er ermöglicht es dem Modell, kritische Segmente dynamisch hervorzuheben, Rauschen zu unterdrücken und die Qualität der Datendarstellung zu verbessern. Durch Patching werden die Daten in kleinere Segmente unterteilt, sodass das Modell lokale Muster besser analysieren kann. Zufallsmaskierung verbessert das Training. Es zwingt das Modell, verborgene Teile zu rekonstruieren und dadurch latente Abhängigkeiten in den Daten zu lernen. Dies verbessert seine Fähigkeit, komplexe Strukturen und Muster zu erkennen. Zusammen erhöhen diese Techniken die Genauigkeit der Erkennung von Anomalien und machen das Modell widerstandsfähiger gegenüber Marktschwankungen. Die Maskierung verbessert auch die Verallgemeinerung, indem sie eine Überanpassung an bestimmte Segmente der Marktdaten verhindert.

Einer der Hauptvorteile von DADA ist seine Anpassungsfähigkeit. Im Gegensatz zu herkömmlichen Algorithmen, die bei veränderten Marktbedingungen neu trainiert werden müssen, kann DADA seine Parameter automatisch anpassen. Dies ist besonders wichtig im Hochfrequenzhandel, wo Entscheidungen in Sekundenbruchteilen getroffen werden müssen. Die Fähigkeit zur dynamischen Anpassung ermöglicht es dem Modell, in einem breiten Spektrum von Marktszenarien – von stabilen Trends bis hin zu plötzlichen Volatilitätsspitzen – effektiv zu arbeiten.

Die ursprüngliche Visualisierung des DADA-Frameworks ist nachstehend dargestellt.

Im praktischen Teil des vorangegangenen Artikels haben wir ein Objekt CNeuronMultiWindowsConvOCL für eine Faltungsschicht mit mehreren Fenstern implementiert. Erwähnenswert ist, dass sich diese Komponente nicht direkt aus der ursprünglichen Beschreibung der DADA-Architektur ergibt. In unserer Implementierung spielt sie jedoch eine Schlüsselrolle innerhalb des Moduls Adaptive Bottlenecks. Insbesondere ermöglicht es eine dynamische Anpassung des Datenkomprimierungsgrads.


Modul für Adaptive Bottlenecks

Der nächste große Schritt ist die eigentliche Konstruktion des Moduls Adaptive Bottlenecks. Dieses Modul ist ein leistungsfähiges Werkzeug zur dynamischen Verarbeitung von Eingabedaten, das eine effiziente Analyse komplexer Zeitreihen und die Erkennung von Anomalien in ihrem Verhalten ermöglicht.

Wie bereits erwähnt, ähnelt es konzeptionell dem Modul Mixture of Experts(MoE), das zuvor im Objekt CNeuronMoE implementiert war. Beide Ansätze stützen sich auf mehrere kleinere Modelle, die parallel arbeiten, um die Eingabedaten zu analysieren. Das System wählt dynamisch die k relevantesten Mini-Modelle für die Bearbeitung jedes Segments auf der Grundlage seines Kontexts aus. Dies verbessert sowohl die Anpassungsfähigkeit als auch die Genauigkeit, da sich die Berechnung auf die relevantesten Muster konzentriert.

Das entscheidende Merkmal von Adaptive Bottlenecks ist die Verwendung einer Reihe von Autoencodern als diese Mini-Modelle, jedes mit einem anderen Kompressionsgrad im latenten Raum. Dadurch kann sich das Modell flexibel an veränderte Bedingungen anpassen und den Detaillierungsgrad der Datendarstellung je nach den Merkmalen der Zeitreihe einstellen. Das Modul Adaptive Bottlenecks reduziert Redundanzen, unterdrückt Rauschen und verbessert die Erkennung anomaler Muster.

Unsere Implementierung von Adaptive Bottlenecks ist in das CNeuronAdaBN-Objekt integriert. Wie erwartet, erbt es von CNeuronMoE. Dieser Ansatz ermöglicht es uns, Schlüsselmechanismen von Mixture of Experts wie dynamische Lastverteilung und adaptive Expertenauswahl wiederzuverwenden, die beide auf natürliche Weise mit dem Konzept der Adaptive Bottlenecks übereinstimmen. Dies spiegelt sich in der Struktur des neuen Objekts wider.

class CNeuronAdaBN   :  public CNeuronMoE
  {
public:
                     CNeuronAdaBN(void) {};
                    ~CNeuronAdaBN(void) {};
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_out, uint units_count,
                          uint &bottlenecks[], uint top_k, uint variables,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override   const   {  return defNeuronAdaBN; }
  };

Wie Sie sehen können, überschreiben wir nur die Initialisierungsmethode, ohne zusätzliche interne Objekte einzuführen.

Die Elternklasse CNeuronMoE enthält einen Gating-Mechanismus (cGates) zur Auswahl der besten k Experten sowie ein dynamisches Array (cExperts) mit Zeigern auf die internen Modellobjekte. Auf den ersten Blick mag die Idee der parallelen Experten im Widerspruch zu den traditionellen sequenziellen Modellen stehen. Wir verwenden jedoch eine Folge von Faltungsschichten, die unabhängige Sequenzen parallel verarbeiten können. So lassen sich spezialisierte Mini-MLPs aufbauen, die parallel ausgeführt werden und jeweils über eigene trainierbare Parameter verfügen.

Diese architektonische Entscheidung verbessert die Anpassungsfähigkeit erheblich, da jeder Experte auf seine eigene Teilaufgabe spezialisiert ist, was eine effektivere Erkennung komplexer Muster in Zeitreihendaten ermöglicht. Darüber hinaus gewährleistet der dynamische Auswahlmechanismus, dass nur die relevantesten Experten in die Analyse einbezogen werden, was die Vorhersagegenauigkeit verbessert.

class CNeuronMoE  :  public CNeuronBaseOCL
  {
protected:
   CNeuronTopKGates     cGates;
   CLayer               cExperts;
   //---
   ..........
   ..........
   ..........
  };

Der abgeleitete Mechanismus zur Auswahl der besten k Experten entspricht voll und ganz den Anforderungen des Moduls CNeuronAdaBN. Wir verwenden ihn daher unverändert weiter. Wir müssen lediglich das abgeleitete dynamische Array mit einer neuen, auf unsere Aufgabe zugeschnittenen Folge von Objekten auffüllen. Die Elternklasse übernimmt auch die Vorwärts- und Rückwärtspropagation, sodass in dieser Hinsicht keine zusätzliche Implementierung erforderlich ist.

Die Abfolge der internen Objekte definieren wir in der überschriebenen Methode Init.

bool CNeuronAdaBN::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                        uint window, uint window_out, uint units_count,
                        uint &bottlenecks[], uint top_k, uint variables,
                        ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count * variables,
                                                                 optimization_type, batch))
      return false;

Wie üblich enthalten die Methodenparameter eine Reihe von Konstanten, die die Architektur der Klasse definieren. Ein wichtiger Parameter ist das Bottlenecks-Array, das die latenten Dimensionen der zu erstellenden Autoencoder angibt.

Innerhalb der Methode rufen wir zunächst die Initialisierungsmethode der Basisschicht der vollständig verbundenen neuronalen Schicht auf. Erinnern Sie sich, dass diese Klasse als gemeinsamer Vorfahre für alle Objekte der neuronalen Schicht in unserer Bibliothek dient. Wir überspringen absichtlich die Initialisierung der direkten Elternklasse, da wir ihren Expertenpool nicht initialisieren wollen. Diese Entscheidung erfordert jedoch, dass wir alle geerbten Komponenten manuell initialisieren.

Nach erfolgreicher Initialisierung der Basisschnittstellen fahren wir mit der Initialisierung der von der Elternklasse geerbten Komponenten fort. Zunächst initialisieren wir das Modul, das für die Auswahl der besten k Experten zuständig ist. Die Gesamtzahl der Experten wird durch die Größe des Arrays bottlenecks bestimmt. Die übrigen Parameter werden vom externen Programm übergeben.

   int index = 0;
   if(!cGates.Init(0, index, OpenCL, window, units_count * variables, bottlenecks.Size(), top_k, optimization, iBatch))
      return false;

Als Nächstes bereiten wir ein dynamisches Array und lokale Variablen vor, um vorübergehend Zeiger auf die internen Objekte des Moduls Adaptive Bottlenecks zu speichern.

   cExperts.Clear();
   cExperts.SetOpenCL(OpenCL);
   CNeuronConvOCL *conv = NULL;
   CNeuronMultiWindowsConvOCL *mwconv = NULL;
   CNeuronTransposeRCDOCL *transp = NULL;

Anschließend wird eine Schleife zur Berechnung der Gesamtgröße aller latenten Zustände in den Autoencodern ausgeführt.

   uint bn_size = 0;
   for(uint i = 0; i < bottlenecks.Size(); i++)
      bn_size += bottlenecks[i];

Nachdem die Vorbereitungen abgeschlossen sind, geht es an die Konstruktion der Sequenz von Autoencoder-Komponenten.

Interessanterweise ist die erste Schicht, die wir erstellen, eine Standard-Faltungsschicht. Auf den ersten Blick mag dies angesichts der Komplexität der Architektur des Adaptive Bottlenecks eine ungewöhnliche Entscheidung sein. Sie spielt jedoch eine wichtige Rolle in der Verarbeitungspipeline.

Der Grundgedanke ist, dass die Anzahl der Filter in dieser Faltungsschicht gleich der Summe aller latenten Dimensionen der Autoencoder gesetzt wird. Da jeder Autoencoder dieselben Eingabedaten verarbeitet, hängt der Grad der Komprimierung von der latenten Größe ab. Jeder Filter arbeitet unabhängig und erzeugt ein einzelnes Element im Ausgangspuffer. Konzeptionell kann dies als eine große Anzahl von Mini-Modellen betrachtet werden, die die Eingabe in einzelne skalare Werte komprimieren. Diese Ausgaben können dann in Segmente beliebiger Größe gruppiert werden, die den latenten Räumen der verschiedenen Autoencoder entsprechen. Die einzige Bedingung ist, dass die Gesamtzahl der Elemente konstant bleibt.

   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, window, window, bn_size, units_count, variables, optimization, iBatch) ||
      !cExperts.Add(conv))
     {
      delete conv;
      return false;
     }
   conv.SetActivationFunction(SoftPlus);

Wir werden diese Eigenschaft im Trainingsprozess verwenden. Als Nächstes stellen wir die Mehrfenster-Faltungsschicht vor. Jedes Faltungsfenster verarbeitet seine eigene Gruppe von Elementen und bildet die latente Repräsentation eines bestimmten Autoencoders. Dann rekonstruiert es die Daten in der gewünschten Größe.

   index++;
   mwconv = new CNeuronMultiWindowsConvOCL();
   if(!mwconv ||
      !mwconv.Init(0, index, OpenCL, bottlenecks, window_out, units_count, variables, optimization, iBatch) ||
      !cExperts.Add(mwconv))
     {
      delete conv;
      return false;
     }
   mwconv.SetActivationFunction(SoftPlus);

Anschließend fügen wir dem Decoderteil des Autoencoders eine weitere Schicht hinzu. Jeder Autoencoder muss seine eigene Schicht mit eindeutigen trainierbaren Parametern haben. Die Ausgabe der Multi-Fenster-Faltungsschicht kann jedoch als 4D-Tensor betrachtet werden: [Variable, Units, Autoencoder, Dimension]. Eine Standard-Faltungsschicht würde es uns nicht erlauben, jedem Autoencoder eindeutige Parameter zuzuweisen. Dies lässt sich jedoch leicht bewerkstelligen, indem man die Autoencoder-Dimension an die erste Stelle setzt. Als Nächstes deklarieren wir eine Transpositionsschicht.

   transp = new CNeuronTransposeRCDOCL();
   index++;
   if(!transp ||
      !transp.Init(0, index, OpenCL, units_count * variables, bottlenecks.Size(), window_out, optimization, iBatch) ||
      !cExperts.Add(transp))
     {
      delete transp;
      return false;
     }
   transp.SetActivationFunction((ENUM_ACTIVATION)conv.Activation());

Danach fügen wir eine Faltungsschicht hinzu, wobei die Anzahl der Variablen der Anzahl der Autoencoder entspricht. Damit wird der Schicht signalisiert, dass sie für jeden Autoencoder eine eigene Gewichtsmatrix initialisieren soll.

   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, window_out, window_out, window, units_count * variables, bottlenecks.Size(),
                                                                                       optimization, iBatch) ||
      !cExperts.Add(conv))
     {
      delete conv;
      return false;
     }
   conv.SetActivationFunction(None);

Schließlich fügen wir eine weitere Transponierungsebene hinzu, um das ursprüngliche Datenlayout wiederherzustellen.

   transp = new CNeuronTransposeRCDOCL();
   index++;
   if(!transp ||
      !transp.Init(0, index, OpenCL, bottlenecks.Size(), units_count * variables, window, optimization, iBatch) ||
      !cExperts.Add(transp))
     {
      delete transp;
      return false;
     }
   transp.SetActivationFunction((ENUM_ACTIVATION)conv.Activation());
//---
   return true;
  }

Damit ist die Methode abgeschlossen. Abschließend gibt die Methode einen booleschen Wert an das aufrufende Programm zurück, um den erfolgreichen Abschluss zu signalisieren.

Damit ist unsere Diskussion über den Implementierungsansatz für das Modul Adaptive Bottlenecks abgeschlossen. Wie bereits erwähnt, werden Vorwärts- und Rückwärtspropagation durch die abgeleitete Funktionalität der Elternklasse gehandhabt. Der vollständige Quellcode für dieses Objekt und alle seine Methoden ist in den beigefügten Materialien verfügbar.


Modellarchitektur

In dieser Iteration haben wir uns entschieden, nicht das gesamte DADA-Framework in einem einzigen monolithischen Objekt zu bündeln. Stattdessen stützen wir uns auf gut getestete Komponenten aus unserer Bibliothek, integrieren das zuvor entwickelte Modul Adaptive Bottlenecks und fügen alles zu einer flexiblen, linearen Architektur für das trainierbare Modell zusammen.

Dieser Ansatz gibt uns wesentlich mehr Freiheit. Erstens wird das System dadurch modularer – einzelne Komponenten können ersetzt oder optimiert werden, ohne dass die gesamte Codebasis umgeschrieben werden muss. Zweitens entfallen die Beschränkungen für das Design von Encodern und Decodern. Wir sind nicht mehr an eine starre Struktur gebunden und können experimentieren, das Modell an bestimmte Aufgaben anpassen und nach optimalen Konfigurationen suchen.

Dieser modulare Aufbau macht die Modellbeschreibung etwas komplexer, aber das ist ein vernünftiger Kompromiss für Flexibilität und Anpassungsfähigkeit.

In diesem Experiment trainieren wir drei Modelle gleichzeitig, die jeweils eine Schlüsselrolle im gesamten Analyse- und Entscheidungssystem spielen. Damit entsteht ein mehrstufiges intelligentes System, das in der Lage ist, Daten zu analysieren, sich an die wechselnde Marktdynamik anzupassen, Preisbewegungen vorherzusagen und die Strategie auf der Grundlage einer umfassenden Datenanalyse zu verfeinern.

Das erste Modell ist der Environment State Encoder, der auf der Architektur des DADA-Frameworks mit einem Normalzustandsdecoder aufbaut. Er wird als klassischer Autoencoder trainiert, dessen primäres Ziel es ist, die Eingabedaten aus dem latenten Raum mit minimalem Verlust zu rekonstruieren. Auf diese Weise kann das System die informativste komprimierte Darstellung der Daten erlernen und gleichzeitig wesentliche Merkmale der Umgebung beibehalten. Über die einfache Dimensionalitätsreduktion hinaus deckt dieser Mechanismus verborgene Abhängigkeiten in den Daten auf, die für den Aufbau eines hochpräzisen analytischen Modells entscheidend sind.

Die zweite Schlüsselkomponente ist der Actor, der den anomalen Decoder der ursprünglichen DADA-Architektur ersetzt. Dieses Modul ist für die aktive Interaktion mit dem Marktumfeld zuständig. Seine Hauptziele sind die Identifizierung stabiler Trends, die Erkennung potenzieller Umkehrpunkte und das Treffen von Entscheidungen zur Optimierung der Handelsstrategie.

Der Actor verarbeitet multidimensionale Eingangsdaten, erkennt wiederkehrende Muster und die sich entwickelnde Marktdynamik und generiert auf dieser Grundlage Handelssignale. Dadurch wird das System von einem reinen Analyseinstrument zu einem adaptiven Instrument, das nicht nur in der Lage ist, die Daten zu verstehen, sondern auch auf Veränderungen zu reagieren und optimale Ein- und Ausstiegspunkte zu ermitteln.

Doch selbst mit einem starken Analyse- und Signalgenerierungsmechanismus bleibt die Vorhersagegenauigkeit entscheidend. An dieser Stelle kommt das dritte Modell ins Spiel. Seine Hauptfunktion besteht darin, die wahrscheinlichste Richtung der künftigen Kursbewegung abzuschätzen. Diese Komponente ergänzt den Actor und dient als zusätzlicher Filter für Handelsentscheidungen.

Die Architekturen aller drei Modelle werden mit der Methode CreateDescriptions definiert. Diese Methode erhält drei Zeiger auf dynamische Datenpuffer, in denen wir die Architekturbeschreibungen der Modelle konstruieren.

bool CreateDescriptions(CArrayObj *&encoder, CArrayObj *&actor, CArrayObj *&probability)
  {
//---
   CLayerDescription *descr;
//---
   if(!encoder)
     {
      encoder = new CArrayObj();
      if(!encoder)
         return false;
     }
   if(!actor)
     {
      actor = new CArrayObj();
      if(!actor)
         return false;
     }
   if(!probability)
     {
      probability = new CArrayObj();
      if(!probability)
         return false;
     }

Zu Beginn der Methode werden die Zeiger überprüft und bei Bedarf neue Instanzen erstellt.

Wir beginnen mit der Definition der Architektur des Environment State Encoders. Wie erwartet, benötigt dieses Modell als Eingabe einen Tensor, der den aktuellen Umgebungszustand beschreibt. Um die rohen Eingabedaten zu verarbeiten, führen wir zunächst eine voll verbundene Schicht von ausreichender Größe ein.

//--- Encoder
   encoder.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Die Eingabe besteht aus den Rohdaten des Terminals. Es liegt auf der Hand, dass die verschiedenen Variablen innerhalb dieser multimodalen Darstellung unterschiedlichen Verteilungen folgen. Dies erschwert die Analyse. Um dies zu beheben, wenden wir eine Batch-Normalisierungsschicht an, um die Daten auf eine vergleichbare Skala zu bringen.

//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Als Nächstes folgen wir dem DADA-Framework und wenden Patching und Maskierung an. Für die zufällige Maskierung von 20 % der Daten verwenden wir eine Dropout-Schicht.

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronDropoutOCL;
   descr.count = prev_count;
   descr.probability = 0.2f;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Im ursprünglichen Ansatz von DADA erfolgt das Patchen auf der Grundlage von Einheitsfolgen. In unserem Fall transponieren wir die Daten, um optimale Bedingungen für die Verarbeitung solcher Sequenzen zu schaffen.

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTransposeOCL;
   descr.window = BarDescr;
   prev_count = descr.count = HistoryBars;
   descr.activation = SoftPlus;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Anschließend verwenden wir Faltungsschichten, um Segmente einer bestimmten Größe unabhängig voneinander zu verarbeiten. Konkret wenden wir zwei aufeinander folgende Faltungsschichten an, die beide nicht überlappende Segmente parallel kodieren und als Encoder für das Umgebungsmodell fungieren.

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.window = HistoryBars / Segments;
   prev_count = descr.count = (HistoryBars + descr.window - 1) / descr.window;
   descr.step = descr.window;
   descr.layers = BarDescr;
   descr.activation = SoftPlus;
   int prev_wout = descr.window_out = EmbeddingSize;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = prev_count;
   descr.window = prev_wout;
   descr.step = prev_wout;
   prev_wout = descr.window_out = EmbeddingSize;
   descr.layers = BarDescr;
   descr.activation = TANH;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Es folgt das Modul Adaptive Bottlenecks, in dem wir 15 kleine Autoencoder mit latenten Dimensionen, die ein Vielfaches von 8 sind, erstellen. Für jedes Segment wählen wir die 3 am besten geeigneten Autoencoder für die Kodierung aus.

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronAdaBN;
   descr.window = prev_wout;
   descr.count = prev_count;
   descr.window_out = 256;
   descr.step = 3; // Top K
   descr.layers = BarDescr; // Variables
     {
      int temp[15];
      for(uint i = 0; i < temp.Size(); i++)
         temp[i] = int(i + 1) * 8;
      if(ArrayCopy(descr.windows, temp) < (int)temp.Size())
         return false;
     }
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Zur Rekonstruktion der Segmente verwenden wir einen Decoder, der aus zwei aufeinanderfolgenden Faltungsschichten besteht und die Struktur des Encoders widerspiegelt.

//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = prev_count * BarDescr;
   descr.window = prev_wout;
   descr.step = prev_wout;
   prev_wout = descr.window_out = EmbeddingSize / 2;
   descr.activation = SoftPlus;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 8
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = prev_count * BarDescr;
   descr.window = prev_wout;
   descr.step = prev_wout;
   prev_wout = descr.window_out = HistoryBars / Segments;
   descr.activation = TANH;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Beachten Sie, dass der Decoderausgang die Aktivierungsfunktion des hyperbolischen Tangens (tanh) verwendet. Dies ist beabsichtigt. Nach der Batch-Normalisierung sind die Daten um Null zentriert und haben eine Varianz nahe Eins. Nach der Drei-Sigma-Regel liegen etwa 68 % der normalverteilten Werte innerhalb einer Standardabweichung vom Mittelwert. Bei einer Standardabweichung von 1 entspricht dies dem Bereich [-1, 1]. Es ist genau der Bereich der tanh-Funktion. Dadurch wird sichergestellt, dass der Decoderausgang im Bereich der wahrscheinlichsten Werte bleibt und Ausreißer unterdrückt werden.

Um die Qualität der Rekonstruktion zu bewerten, vergleichen wir die Decoderausgabe mit den Originaldaten. Da wir die Daten für die Verarbeitung zuvor transponiert haben, überführen wir sie zunächst durch umgekehrte Transposition in ihr ursprüngliches Format.

//--- layer 9
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTransposeOCL;
   descr.count = BarDescr;
   descr.window = HistoryBars;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Die so erhaltenen Werte werden dann in die Verteilung der ursprünglichen Daten zurückgeführt.

//--- layer 10
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronRevInDenormOCL;
   descr.count = HistoryBars * BarDescr;
   descr.layers = 1;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Die beiden anderen Modelle arbeiten mit der latenten Repräsentation, die durch das Modul Adaptive Bottlenecks erzeugt wird. Deshalb speichern wir die Parameter dieses Moduls in einer lokalen Variable.

//--- Latent
   CLayerDescription *latent = encoder.At(LatentLayer);
   if(!latent)
      return false;

Als Nächstes definieren wir die Architektur des Actors. Der Actor analysiert das Marktumfeld im Zusammenhang mit dem Kontostand und den offenen Positionen und bestimmt dann die optimale Handelsaktion. Um dies zu ermöglichen, nimmt das Modell als Eingabe einen Vektor, der den Kontostand beschreibt. Wir verarbeiten diese Eingabe zunächst mit einer voll verknüpften Schicht.

//--- Actor
   actor.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = AccountDescr;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Die Daten werden normalisiert.

//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Dann wenden wir eine Verkettungsschicht an, die zwei Datenquellen kombiniert: den Kontostatus und die komprimierte Umgebungsdarstellung aus dem latenten Raum des Encoders. Dadurch kann das Modell bei der Entscheidungsfindung sowohl finanzielle Kennzahlen als auch abstrakte Umgebungsmerkmale berücksichtigen.

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConcatenate;
   descr.count = LatentCount;
   descr.window = prev_count;
   descr.step = latent.count * latent.window * latent.layers;
   descr.batch = 1e4;
   descr.activation = SoftPlus;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Die kombinierten Daten werden durch ein Entscheidungsfindungsmodul geleitet, das aus drei vollständig verbundenen Schichten besteht. Dieses Modul extrahiert Schlüsselmuster und erzeugt die endgültige Aktionsausgabe.

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.batch = 1e4;
   descr.activation = TANH;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.activation = TANH;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = NActions;
   descr.activation = SIGMOID;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Das dritte Modell, das für die Vorhersage der Wahrscheinlichkeit künftiger Preisbewegungen zuständig ist, hat den einfachsten Aufbau. Es arbeitet ausschließlich mit der komprimierten Darstellung des Umgebungszustands. Wir beginnen mit einer vollständig verknüpften Schicht, um die latente Eingabe zu verarbeiten.

   probability.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = latent.count * latent.window * latent.layers;
   descr.activation = latent.activation;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      delete descr;
      return false;
     }

Darauf folgt ein Entscheidungsmodul, das aus drei vollständig verknüpften Schichten besteht.

//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.activation = TANH;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.activation = TANH;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = NActions / 3;
   descr.activation = SoftPlus;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      delete descr;
      return false;
     }

Schließlich wird die Ausgabe mithilfe der Funktion SoftMax in Wahrscheinlichkeiten umgewandelt.

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronSoftMaxOCL;
   prev_count = descr.count = prev_count;
   descr.step = 1;
   descr.activation = None;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   if(!probability.Add(descr))
     {
      delete descr;
      return false;
     }
//---
   return true;
  }

Eine vollständige Beschreibung der Architektur der Modelle finden Sie im Anhang.


Modelltraining

Nachdem wir die Modellarchitekturen definiert haben, gehen wir zur nächsten Phase über – dem Training. Der Trainingsalgorithmus ist in dem Expert Advisor „...\DADA\Study.mq5“ implementiert. Da wir drei Modelle parallel trainieren müssen, waren einige Anpassungen in der Logik des EA erforderlich. In diesem Artikel werden wir nicht den gesamten Code im Detail durchgehen. Wir werden uns auf die Kerntrainingsmethode Train konzentrieren.

Zu Beginn der Methode nehmen wir einige grundlegende Einstellungen vor, indem wir mehrere lokale Variablen deklarieren.

void Train(void)
  {
//---
   vector<float> probability = vector<float>::Full(Buffer.Size(), 1.0f / Buffer.Size());
//---
   vector<float> result, target, state;
   matrix<float> fstate = matrix<float>::Zeros(1, NForecast * BarDescr);
   bool Stop = false;
//---
   uint ticks = GetTickCount();

Das eigentliche Training findet innerhalb einer verschachtelten Schleifenstruktur statt. Die äußere Schleife iteriert über Trainings-Batches. Für jeden Batch wird eine zufällige Trajektorie aus dem Erfahrungswiedergabepuffer ausgewählt und ein Startpunkt für das Training innerhalb dieser Bahn festgelegt.

   for(int iter = 0; (iter < Iterations && !IsStopped() && !Stop); iter += Batch)
     {
      int tr = SampleTrajectory(probability);
      int start = (int)((MathRand() * MathRand() / MathPow(32767, 2)) * (Buffer[tr].Total - 2 - NForecast - Batch));
      if(start <= 0)
        {
         iter -= Batch;
         continue;
        }
      if(!Encoder.Clear() ||
         !Actor.Clear())
        {
         PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
         Stop = true;
         break;
        }
      result = vector<float>::Zeros(NActions);

In der inneren Schleife werden die Modelle an aufeinanderfolgenden Zuständen innerhalb einer einzigen Charge trainiert.

Dabei ist zu beachten, dass die im Rahmen dieses Artikels entwickelten Modelle keine wiederkehrenden Blöcke enthalten. In der Regel werden solche Modelle mit völlig zufälligen Zuständen trainiert, die aus dem Erfahrungswiedergabepuffer extrahiert werden. In diesem Fall wird das Training jedoch auf „fast idealen“ Trajektorien durchgeführt, deren Aktionen direkt vom Algorithmus dieses EA auf der Grundlage der verfügbaren Informationen über die nachfolgenden Umgebungszustände generiert werden. Im Gegensatz zum Training von Echtzeitmodellen verfügen wir bei der Arbeit mit einem Wiederholungspuffer über Daten zu den nachfolgenden Umgebungszuständen für alle gespeicherten Datensätze außer dem letzten. Dies ermöglicht eine genauere Steuerung des Trainingsprozesses.

Aber es gibt noch eine andere Seite der Medaille. In diesem Fall liegen uns keine Informationen über offene Positionen vor. Es ist jedoch wichtig, dass wir das Modell nicht nur für die Eröffnung von Positionen trainieren, sondern auch für deren Verwaltung, indem wir den optimalen Ausstiegspunkt suchen. Daher werden wir während des Trainingsprozesses kleine Trainingschargen bilden und dabei auch „optimale“ Positionen bilden.

      for(int i = start; i < MathMin(Buffer[tr].Total, start + Batch); i++)
        {
         if(!state.Assign(Buffer[tr].States[i].state) ||
            MathAbs(state).Sum() == 0 ||
            !bState.AssignArray(state))
           {
            iter -= Batch + start - i;
            break;
           }
         //---
         bTime.Clear();
         double time = (double)Buffer[tr].States[i].account[7];
         double x = time / (double)(D'2024.01.01' - D'2023.01.01');
         bTime.Add((float)MathSin(x != 0 ? 2.0 * M_PI * x : 0));
         x = time / (double)PeriodSeconds(PERIOD_MN1);
         bTime.Add((float)MathCos(x != 0 ? 2.0 * M_PI * x : 0));
         x = time / (double)PeriodSeconds(PERIOD_W1);
         bTime.Add((float)MathSin(x != 0 ? 2.0 * M_PI * x : 0));
         x = time / (double)PeriodSeconds(PERIOD_D1);
         bTime.Add((float)MathSin(x != 0 ? 2.0 * M_PI * x : 0));
         if(bTime.GetIndex() >= 0)
            bTime.BufferWrite();
         //--- Account
         float PrevBalance = Buffer[tr].States[MathMax(i - 1, 0)].account[0];
         float PrevEquity = Buffer[tr].States[MathMax(i - 1, 0)].account[1];
         float profit = float(bState[0] / _Point * (result[0] - result[3]));
         bAccount.Clear();
         bAccount.Add(1);
         bAccount.Add((PrevEquity + profit) / PrevEquity);
         bAccount.Add(profit / PrevEquity);
         bAccount.Add(MathMax(result[0] - result[3], 0));
         bAccount.Add(MathMax(result[3] - result[0], 0));
         bAccount.Add((bAccount[3] > 0 ? profit / PrevEquity : 0));
         bAccount.Add((bAccount[4] > 0 ? profit / PrevEquity : 0));
         bAccount.Add(0);
         bAccount.AddArray(GetPointer(bTime));
         if(bAccount.GetIndex() >= 0)
            bAccount.BufferWrite();

Im Hauptteil der Schleife extrahieren wir zunächst Informationen aus dem Replay-Puffer und bilden die Quelldatenobjekte für die zu trainierenden Modelle. Danach rufen wir die Feedforward-Methoden des Encoders für die Umgebungszustände auf.

         //--- Feed Forward
         if(!Encoder.feedForward((CBufferFloat*)GetPointer(bState), 1, false, (CBufferFloat*)NULL))
           {
            PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
            Stop = true;
            break;
           }

Anschließend rufen wir ähnliche Methoden der beiden anderen Modelle auf und übergeben ihnen einen Zeiger auf das Encoder-Objekt, das den Umgebungszustand enthält.

         if(!Actor.feedForward((CBufferFloat*)GetPointer(bAccount), 1, false, GetPointer(Encoder), LatentLayer))
           {
            PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
            Stop = true;
            break;
           }
         if(!Probability.feedForward(GetPointer(Encoder), LatentLayer, (CBufferFloat*)NULL))
           {
            PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
            Stop = true;
            break;
           }

Danach folgt der Prozess der Erstellung der „optimalen Handelsoperation“. Diese Logik wurde vollständig aus einem früheren Artikel über „Multi-Task-Lernen“ übernommen. Wir werden sie hier nicht wiederholen, da Sie die vollständige Beschreibung unter diesem Link finden.

Sobald die Zielwerte feststehen, optimieren wir die Modellparameter, um die Abweichung von diesen Zielwerten zu minimieren. Das Training beginnt mit dem Encoder des Umgebungszustands. Für den Zieltensor übergeben wir ihm den Vektor, der den analysierten Umgebungszustand beschreibt, den wir auch im FeedForward-Durchlauf verwendet haben.

         //--- State Encoder
         if(!Encoder.backProp(GetPointer(bState), (CBufferFloat*)NULL, NULL))
           {
            PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
            Stop = true;
            break;
           }

Als Nächstes wird der Actor trainiert, wobei die Abweichung von der „optimalen Handelsoperation“ minimiert wird.

         //--- Actor Policy
         if(!Actor.backProp(GetPointer(bActions), (CNet*)GetPointer(Encoder), LatentLayer)
            || !Encoder.backPropGradient((CBufferFloat*)NULL, (CBufferFloat*)NULL, LatentLayer, true)
            )
           {
            PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
            Stop = true;
            break;
           }

Beim dritten Modell bestimmen wir die Richtung der künftigen Kursbewegung anhand der Farbe der nächsten Kerze.

         target = vector<float>::Zeros(NActions / 3);
         if(fstate[0, 0] > 0)
            target[0] = 1;
         else
            if(fstate[0, 0] < 0)
               target[1] = 1;
         if(!Result.AssignArray(target) ||
            !Probability.backProp(Result, (CBufferFloat*)NULL)
            || !Encoder.backPropGradient((CBufferFloat*)NULL, (CBufferFloat*)NULL, LatentLayer)
           )
           {
            PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
            Stop = true;
            break;
           }

Schließlich protokollieren wir den Trainingsfortschritt und gehen zur nächsten Iteration der Schleife über.

         //---
         if(GetTickCount() - ticks > 500)
           {
            double percent = double(iter + i - start) * 100.0 / (Iterations);
            string str = StringFormat("%-13s %6.2f%% -> Error %15.8f\n", "Encoder",
                                         percent, Encoder.getRecentAverageError());
            str += StringFormat("%-14s %6.2f%% -> Error %15.8f\n", "Actor", percent,
                                                     Actor.getRecentAverageError());
            str += StringFormat("%-13s %6.2f%% -> Error %15.8f\n", "Probability", 
                                      percent, Probability.getRecentAverageError());
            Comment(str);
            ticks = GetTickCount();
           }
        }
     }

Sobald alle Trainingsiterationen abgeschlossen sind, geben wir die Ergebnisse in das Protokoll aus und leiten das Herunterfahren des Expert Advisors ein.

   Comment("");
//---
   PrintFormat("%s -> %d -> %-15s %10.7f", __FUNCTION__, __LINE__, "Actor", Actor.getRecentAverageError());
   PrintFormat("%s -> %d -> %-15s %10.7f", __FUNCTION__, __LINE__, "Probability", Probability.getRecentAverageError());
   ExpertRemove();
//---
  }

Der vollständige Quellcode des EA ist im Anhang zu finden und steht zum Selbststudium zur Verfügung. Die Anhang enthält außerdem Programme zur Interaktion mit der Umgebung und zum Testen der trainierten Modelle.


Tests

Nach der Implementierung der Konzepte des DADA-Frameworks in MQL5 und ihrer Integration in trainierbare Modelle ist der nächste kritische Schritt die Bewertung ihrer Leistung auf realen historischen Daten. Auf diese Weise können wir ihre Praxistauglichkeit unter realen Handelsbedingungen beurteilen.

Für das Training haben wir einen Datensatz von Zufallsläufen mit dem MetaTrader 5-Strategietester auf historischen EURUSD-Daten (M1-Zeitrahmen) für 2024 erstellt. Die Daten wurden mit Standard-Indikatoreinstellungen erfasst, um ein sauberes Experiment zu gewährleisten und externe Einflüsse auszuschließen.

Die trainierten Modelle wurden dann an historischen Daten von Januar bis Februar 2025 getestet. Alle Versuchsparameter wurden unverändert beibehalten, um eine objektive Bewertung des erlernten Verhaltens des Actors zu gewährleisten. Das Testen mit Daten, die beim Training nicht verwendet wurden, ist ein entscheidender Validierungsschritt, da er zeigt, wie das Modell unter nahezu realen Bedingungen funktioniert.

Die Testergebnisse sind unten dargestellt.

Während des Testzeitraums führte das Modell 57 Trades aus, von denen über 35 % mit Gewinn abgeschlossen wurden. Trotz dieser relativ bescheidenen Gewinnquote war der durchschnittliche Gewinn pro gewonnenem Trade etwa dreimal so hoch wie der durchschnittliche Verlust, sodass das Modell einen Gesamtgewinn und einen Gewinnfaktor von 1,53 erzielen konnte.

Es ist jedoch zu beachten, dass der größte Teil des Gewinns in der ersten Januarhälfte erzielt wurde. Während des restlichen Testzeitraums schwankte die Kapitalkurve innerhalb einer engen Bandbreite. Dies deutet darauf hin, dass eine weitere Optimierung des Modells erforderlich sein könnte.

Es sei auch darauf hingewiesen, dass während der Umsetzung mehrere Änderungen an der DADA-Architektur vorgenommen wurden. Dies bedeutet, dass die Ergebnisse spezifisch für diese spezielle Version des DADA-Frameworks sind.


Schlussfolgerung

In dieser Arbeit haben wir den DADA-Framework untersucht, der einen innovativen Ansatz durch die Kombination von adaptiven Bottlenecks mit dualen parallelen Decodern für eine genauere Zeitreihenanalyse einführt. Ein wesentlicher Vorteil dieser Methode ist ihre Fähigkeit, sich dynamisch an unterschiedliche Datenstrukturen anzupassen, ohne dass eine vorherige Anpassung erforderlich ist.

Wir haben unsere eigene Version des vorgeschlagenen Ansatzes mit MQL5 implementiert. Wir haben diese Version in trainierbare Modelle integriert. Diese Modelle wurden anhand echter historischer Daten trainiert und haben bei Tests ihre Rentabilität bewiesen. Die Kapitalkurve zeigte jedoch keinen stabilen Aufwärtstrend, was darauf hindeutet, dass eine weitere Verfeinerung und Optimierung der Strategie erforderlich ist.


Verwandte Links


In diesem Artikel verwendete Programme

# Name Typ Beschreibung
1 Research.mq5 Expert Advisor Expert Advisor für die Erfassung von Datensätzen
2 ResearchRealORL.mq5
Expert Advisor
Expert Advisor für die Erfassung von Datensätzen mit der Methode Real-ORL
3 Study.mq5 Expert Advisor Expert Advisor für das Modelltraining
4 Test.mq5 Expert Advisor Expert Advisor für die 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 Bibliothek OpenCL-Programmcode

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

Beigefügte Dateien |
MQL5.zip (2565.75 KB)
Entwicklung eines Multi-Currency Expert Advisors (Teil 26): Informer für Handelsinstrumente Entwicklung eines Multi-Currency Expert Advisors (Teil 26): Informer für Handelsinstrumente
Bevor wir mit der Entwicklung von Mehrwährungs-EAs fortfahren, wollen wir versuchen, ein neues Projekt mit der entwickelten Bibliothek zu erstellen. In diesem Beispiel wird gezeigt, wie man die Speicherung von Quellcode am besten organisiert und wie das neue Code-Repository von MetaQuotes uns dabei helfen kann.
Von der Grundstufe bis zur Mittelstufe: Struktur (IV) Von der Grundstufe bis zur Mittelstufe: Struktur (IV)
In diesem Artikel werden wir untersuchen, wie man sogenannten strukturierten Code erstellt, bei dem der gesamte Kontext und die Methoden zur Manipulation von Variablen und Informationen in eine Struktur eingebettet sind, um einen geeigneten Kontext für die Implementierung beliebiger Programmteile zu schaffen. Daher werden wir die Notwendigkeit untersuchen, einen privaten Bereich des Codes zu verwenden, um zu trennen, was öffentlich ist und was nicht, um so die Regel der Kapselung einzuhalten und den Kontext zu bewahren, für den die Datenstruktur erstellt wurde.
Eindimensionale Singularspektralanalyse Eindimensionale Singularspektralanalyse
Der Artikel untersucht die theoretischen und praktischen Aspekte der Methode der singulären Spektralanalyse (SSA), einer effizienten Methode der Zeitreihenanalyse, die es ermöglicht, die komplexe Struktur einer Reihe als Zerlegung in einfache Komponenten, wie Trend, saisonale (periodische) Schwankungen und Rauschen, darzustellen.
Korallenriff-Optimierung (CRO) Korallenriff-Optimierung (CRO)
Der Artikel stellt eine umfassende Analyse des Korallenriff-Optimierungsalgorithmus (CRO) vor, einer metaheuristischen Methode, die von den biologischen Prozessen der Entstehung und Entwicklung von Korallenriffen inspiriert ist. Der Algorithmus modelliert Schlüsselaspekte der Korallenevolution: Broadcast Spawning (Massenlaichen), Brooding (interne Larvenentwicklung), Larvenansiedlung, ungeschlechtliche Fortpflanzung und Wettbewerb um den begrenzten Platz im Riff. Besondere Aufmerksamkeit gilt der verbesserten Version des Algorithmus.