English Русский Español 日本語 Português
preview
Neuronale Netze leicht gemacht (Teil 41): Hierarchische Modelle

Neuronale Netze leicht gemacht (Teil 41): Hierarchische Modelle

MetaTrader 5Experten | 1 November 2023, 10:08
231 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Einführung

In diesem Artikel werden wir die Anwendung des hierarchischen Verstärkungslernens im Handel untersuchen. Wir schlagen vor, diesen Ansatz zu nutzen, um ein hierarchisches Handelsmodell zu entwickeln, das in der Lage ist, auf verschiedenen Ebenen optimale Entscheidungen zu treffen und sich an unterschiedliche Marktbedingungen anzupassen.

In diesem Artikel werden wir die Architektur des hierarchischen Modells betrachten, einschließlich der verschiedenen Entscheidungsebenen, wie z. B. die Bestimmung von Einstiegs- und Ausstiegspunkten für Handelsgeschäfte. Außerdem stellen wir hierarchische Modelllernmethoden vor, die Verstärkungslernen (reinforcement learning) auf globaler Ebene und Verstärkungslernen auf lokaler Ebene kombinieren.

Der Einsatz von hierarchischem Lernen ermöglicht die Modellierung komplexer Entscheidungsstrukturen und die effektive Nutzung von Wissen auf verschiedenen Ebenen. Dies trägt dazu bei, die Verallgemeinerungsfähigkeit des Modells und seine Anpassungsfähigkeit an sich ändernde Marktbedingungen zu erhöhen.


1. Vorteile der hierarchischen Modelle

In den letzten Jahren hat die Verwendung hierarchischer Modelle im Bereich des Handels immer mehr Aufmerksamkeit erregt und wird zunehmend erforscht. Hierarchisches Lernen ist eine leistungsfähige Methode zur Modellierung komplexer hierarchischer Entscheidungsstrukturen. Für den Handel kann dies mehrere bedeutende Vorteile mit sich bringen.

Der erste Vorteil ist die Fähigkeit des hierarchischen Modells, sich an unterschiedliche Marktbedingungen anzupassen. Das Modell kann makroökonomische Faktoren auf einer höheren Ebene analysieren, wie z. B. politische Ereignisse oder Wirtschaftsindikatoren, und gleichzeitig mikroökonomische Faktoren auf einer niedrigeren Ebene berücksichtigen, wie z. B. die technische Analyse oder anlagespezifische Informationen. Dadurch kann das Modell fundiertere Entscheidungen treffen und sich an unterschiedliche Marktsituationen anpassen.

Der zweite Vorteil liegt in der effizienteren Nutzung der verfügbaren Informationen. Hierarchische Modelle ermöglichen es Ihnen, Informationen auf verschiedenen Ebenen der Hierarchie zu modellieren und zu verwenden. High-Level-Strategien können breite Trends und Tendenzen berücksichtigen, während Low-Level-Strategien präzisere und sich schnell ändernde Daten berücksichtigen können. Dadurch kann sich das Modell ein vollständigeres Bild vom Markt machen und fundiertere Entscheidungen treffen.

Der dritte Vorteil hierarchischer Modelle ist ihre Fähigkeit, Rechenressourcen effizient zuzuweisen. High-Level-Strategien können auf einer größeren Zeitskala trainiert werden, während Low-Level-Strategien empfindlicher auf sich schnell ändernde Daten auf einer kleinen Zeitskala reagieren können. Dies ermöglicht eine effiziente Nutzung von Computerressourcen und beschleunigt den Modellbildungsprozess.

Der vierte Vorteil betrifft die verbesserte Stabilität und Übertragbarkeit von Strategien. Hierarchische Modelle haben eine größere Verallgemeinerungsfähigkeit, da sie in der Lage sind, abstrakte Konzepte und Abhängigkeiten auf verschiedenen Ebenen der Hierarchie zu modellieren. Dies ermöglicht die Entwicklung nachhaltiger Strategien, die unter verschiedenen Bedingungen erfolgreich angewandt und auf unterschiedliche Märkte und Vermögenswerte übertragen werden können.

Der fünfte Vorteil der Verwendung hierarchischer Modelle ist die Möglichkeit, ein komplexes Handelsproblem in einfachere Teilaufgaben zu zerlegen. Dadurch wird die Komplexität der Aufgabe verringert und der Lernprozess vereinfacht. Jede Ebene der Hierarchie kann für bestimmte Aspekte des Handels zuständig sein, z. B. für die Festlegung von Einstiegs- und Ausstiegspunkten für Handelsgeschäfte, das Risikomanagement oder die Portfoliozuweisung. Dies ermöglicht ein effizienteres Modelltraining und verbessert die Qualität der Entscheidungen.

Schließlich trägt die Verwendung von hierarchischen Modellen zu einer besseren Interpretierbarkeit der Ergebnisse und Entscheidungen bei. Da das Modell eine explizite hierarchische Struktur hat, ist es einfacher zu verstehen, welche Faktoren und Variablen die Entscheidungsfindung auf jeder Ebene beeinflussen. Dies ermöglicht es Händlern und Forschern, die Gründe und Ergebnisse ihrer Strategien besser zu verstehen und notwendige Anpassungen vorzunehmen.

So bietet die Verwendung hierarchischer Modelle bei Handelsproblemen eine Reihe von Vorteilen, darunter die Anpassungsfähigkeit an die Marktbedingungen, die effiziente Nutzung von Informationen, die Zuteilung von Rechenressourcen, die Stabilität und Übertragbarkeit von Strategien, die Zerlegung eines komplexen Problems in Teilprobleme und die bessere Interpretierbarkeit der Ergebnisse. Diese Vorteile machen hierarchische Modelle zu einem leistungsfähigen Instrument für die Entwicklung erfolgreicher Handelsstrategien. 

Der Einsatz von hierarchischen Modellen im Handel erfordert spezielle Trainingsansätze. Herkömmliche Trainingsmethoden, die in einstufigen Modellen verwendet werden, sind aufgrund ihrer komplexen Struktur und der Beziehungen zwischen den Ebenen nicht immer für hierarchische Modelle geeignet.

Der Einsatz von hierarchischem Lernen ist einer der spezifischen Ansätze für das Training solcher Modelle. In diesem Fall wird das Modell schrittweise auf verschiedenen Ebenen der Hierarchie trainiert, beginnend auf niedrigeren Ebenen und dann sukzessive auf höheren Ebenen. Während das Modell auf jeder Ebene lernt, nutzt es Informationen, die es auf den vorherigen Ebenen gelernt hat, und kann so abstraktere Abhängigkeiten und Konzepte auf höheren Ebenen der Hierarchie erfassen.

Darüber hinaus ist es wichtig, Verstärkungslernen und überwachtes Lernen zu kombinieren. In diesem Fall wird das Modell auf der Grundlage der Belohnung trainiert, die es während der Verstärkungsaufgabe erhalten hat, sowie auf der Grundlage der Trainingsbeispiele, die auf jeder Ebene der Hierarchie bereitgestellt werden. Dieser Ansatz ermöglicht es dem Modell, von den Erfahrungen anderer Agenten zu lernen und bereits erworbenes Wissen auf höheren Ebenen der Hierarchie zu nutzen.

Ein wichtiger Aspekt beim Training hierarchischer Modelle ist auch ihre Fähigkeit, sich an veränderte Bedingungen anzupassen. Das Modell sollte flexibel sein und sich schnell an neue Marktbedingungen und Datenänderungen anpassen können. Zu diesem Zweck kann dynamisches Lernen eingesetzt werden, das eine periodische Regularisierung des Modells und die Aktualisierung seiner Parameter auf der Grundlage neuer Daten umfasst.

Eines der markantesten Beispiele für Algorithmen zum Training hierarchischer Modelle im Handel ist Scheduled Auxiliary Control (SAC-X).

Der Algorithmus Scheduled Auxiliary Control (SAC-X) ist eine Methode des Reinforcement Learning, die eine hierarchische Struktur für die Entscheidungsfindung verwendet. Es stellt einen neuen Ansatz zur Lösung von Problemen mit verringerter Belohnungen (sparse rewards) dar. Sie beruht auf vier Grundprinzipien:

  1. Jedes Zustands-Aktionspaar wird von einem Belohnungsvektor begleitet, der aus (in der Regel spärlichen) externen Belohnungen und (in der Regel spärlichen) internen Hilfsbelohnungen besteht.
  2. Jedem Belohnungseintrag wird eine Strategie, ein sogenannter Intent, zugewiesen, der lernt, die entsprechende kumulierte Belohnung zu maximieren.
  3. Es gibt einen High-Level-Scheduler, der einzelne Absichten auswählt und ausführt, um die Leistung des externen Aufgabenagenten zu verbessern.
  4. Das Lernen findet außerhalb der Politik statt (asynchron zur Ausführung der Politik), und die Erfahrungen werden zwischen den Absichten ausgetauscht — für eine effektive Nutzung der Informationen.

Der SAC-X-Algorithmus nutzt diese Prinzipien, um verringerte Belohnungsprobleme effizient zu lösen. Belohnungsvektoren ermöglichen das Lernen aus verschiedenen Aspekten einer Aufgabe und schaffen mehrere Absichten, von denen jede ihre eigene Belohnung maximiert. Der Planer verwaltet die Ausführung von Absichten, indem er die optimale Strategie zur Erreichung externer Ziele wählt. Lernen findet außerhalb der Politik statt, sodass Erfahrungen aus unterschiedlichen Intentionen für effektives Lernen genutzt werden können.

Dieser Ansatz ermöglicht es dem Agenten, verringerte Belohnungsprobleme effizient zu lösen, indem er aus externen und internen Belohnungen lernt. Die Verwendung des Planers ermöglicht die Koordinierung von Aktionen. Dazu gehört auch der Austausch von Erfahrungen zwischen den Beteiligten, was die effiziente Nutzung von Informationen fördert und die Gesamtleistung des Agenten verbessert.

SAC-X ermöglicht ein effizienteres und flexibleres Agententraining in Umgebungen mit geringer Belohnung. Ein wesentliches Merkmal von SAC-X ist die Verwendung interner Hilfsbelohnungen, die das Problem der geringen Anzahl von Belohnungen lösen und das Lernen bei Aufgaben mit geringer Belohnung erleichtern.

Im SAC-X-Lernprozess hat jede Absicht ihre eigene Strategie, die die entsprechende Zusatzbelohnung maximiert. Der Planer bestimmt, welche Vorhaben zu einem bestimmten Zeitpunkt ausgewählt und ausgeführt werden. Dies ermöglicht es dem Agenten, aus verschiedenen Aspekten einer Aufgabe zu lernen und verfügbare Informationen effektiv zu nutzen, um optimale Ergebnisse zu erzielen.

Einer der Hauptvorteile von SAC-X ist seine Fähigkeit, eine Vielzahl von externen Anwendungen zu verarbeiten. Der Algorithmus kann so konfiguriert werden, dass er mit verschiedenen Zielfunktionen arbeitet und sich an unterschiedliche Umgebungen und Aufgaben anpasst. Dadurch kann SAC-X in einer Vielzahl von Bereichen eingesetzt werden.

Darüber hinaus fördert der asynchrone Erfahrungsaustausch zwischen den Absichten eine effiziente Nutzung der Informationen. Der Agent kann aus erfolgreichen Absichten lernen und das erworbene Wissen nutzen, um seine Leistung zu verbessern. Dies ermöglicht es dem Agenten, schnell und effizient optimale Strategien für die Lösung komplexer Probleme zu finden.

Insgesamt ist der Scheduled Auxiliary Control (SAC-X) Algorithmus ein innovativer Ansatz für das Training von Agenten in verringerten Belohnungsumgebungen. Es kombiniert den Einsatz externer und interner Hilfsbelohnungen, einen Scheduler und asynchrones Lernen, um eine hohe Leistung und Anpassungsfähigkeit des Agenten zu erreichen. SAC-X bietet neue Möglichkeiten zur Lösung komplexer Probleme und kann für eine Vielzahl von Anwendungen eingesetzt werden, bei denen die verringerte Belohnung eine Herausforderung darstellt.

Der SAC-X-Algorithmus kann wie folgt beschrieben werden:

  1. Initialisierung: Initialisierung der Strategien für jede Absicht und der entsprechenden Belohnungsvektoren. Der Scheduler, der die Absichten auswählt und ausführt, wird ebenfalls initialisiert.
  2. Trainingszyklus:
    1. Sammeln von Erfahrungen: Der Agent interagiert mit der Umgebung und führt auf der Grundlage der gewählten Absicht Aktionen aus. Er sammelt Erfahrungen in Form von Zuständen, Handlungen, erhaltenen externen Belohnungen und internen Zusatzbelohnungen.
    2. Aktualisieren von Absichten: Für jede Absicht wird die entsprechende Politik anhand der gesammelten Erfahrungen aktualisiert. Die Politik wird so angepasst, dass die kumulative Zusatzbelohnung, die dieser Absicht zugewiesen wird, maximiert wird.
    3. Planung: Der Planer (Scheduler) wählt auf der Grundlage des aktuellen Zustands und der zuvor ausgeführten Absichten aus, welche Absichten im nächsten Schritt ausgeführt werden sollen. Das Ziel des Planers ist es, die Gesamtleistung des Agenten bei externen Aufgaben zu verbessern.
    4. Asynchrones Lernen: Aktualisierungen der Richtlinien und des Planers erfolgen asynchron, sodass der Agent die Informationen und Erfahrungen, die er von anderen Intents erhält, effektiv nutzen kann.
  3. Beendigung: Der Algorithmus setzt die Lernschleife fort, bis er ein bestimmtes Abbruchkriterium erreicht, z. B. das Erreichen einer bestimmten Leistung oder Anzahl von Iterationen.

Der SAC-X-Algorithmus ermöglicht es dem Agenten, externe und interne Hilfsbelohnungen effektiv zum Lernen zu nutzen und die besten Absichten auszuwählen, um optimale Ergebnisse bei externen Aufgaben zu erzielen. Dadurch wird das Problem der spärlichen Belohnung überwunden und die Leistung der Agenten in Umgebungen mit geringer Belohnung verbessert.


2. Implementierung mittels MQL5

Der Algorithmus Scheduled Auxiliary Control (SAC-X) ermöglicht ein asynchrones Training der Agenten mit der Möglichkeit eines freien Erfahrungsaustauschs zwischen verschiedenen Agenten. Wie im vorangegangenen Artikel werden wir den gesamten Lernprozess in 2 Phasen unterteilen:

  • Sammeln von Erfahrungen
  • Training von Richtlinien (Strategien des Agentenverhaltens)

Um Erfahrungen zu sammeln, werden wir zunächst 2 Strukturen schaffen. In der ersten Struktur SState wird eine Beschreibung eines eigenen Zustands des Systems eingestellt. Sie enthält nur ein statisches Array zur Speicherung von Fließkommazahlen.

struct SState
  {
   float             state[HistoryBars * 12 + 9];
   //---
                     SState(void);
   //---
   bool              Save(int file_handle);
   bool              Load(int file_handle);
   //--- overloading
   void              operator=(const SState &obj)   { ArrayCopy(state, obj.state); }
  };

Um die Nutzung zu erleichtern, werden wir Methoden für die Arbeit mit Save- und Load-Dateien in der Struktur erstellen. Der Code der Methode ist recht einfach. Sie finden sie in der Anlage.

Die zweite Struktur STrajectory enthält alle Informationen über die gesammelten Erfahrungen des Agenten während eines Durchgangs der Episode. Darin sind 3 statische Arrays zu sehen:

  • States — Array von Zuständen. Dies ist ein Array der oben erstellten Strukturen, in dem alle vom Agenten besuchten Zustände aufgezeichnet werden
  • Actions — Array von Agentenaktionen
  • Revards — eine Reihe von Belohnungen, die von der äußeren Umgebung kommen.

Zusätzlich werden wir 3 Variablen hinzufügen:

  • Insgesamt — Anzahl der besuchten Zustände
  • DiscountFactor — Abzinsungsfaktor
  • CumCounted — Flag, das anzeigt, dass die kumulative Belohnung unter Berücksichtigung des Abzinsungsfaktors neu berechnet wird.

struct STrajectory
  {
   SState            States[Buffer_Size];
   int               Actions[Buffer_Size];
   float             Revards[Buffer_Size];
   int               Total;
   float             DiscountFactor;
   bool              CumCounted;
   //---
                     STrajectory(void);
   //---
   bool              Add(SState &state, int action, float reward);
   void              CumRevards(void);
   //---
   bool              Save(int file_handle);
   bool              Load(int file_handle);
  };

Anders als bei der obigen Struktur zur Beschreibung eines separaten Zustands werden wir für diese Struktur einen Konstruktor erstellen. Wir werden Arrays und Variablen mit Anfangswerten initialisieren.

STrajectory::STrajectory(void)  :   Total(0),
                                    DiscountFactor(0.99f),
                                    CumCounted(false)
  {
   ArrayInitialize(Actions, -1);
   ArrayInitialize(Revards, 0);
  }

Beachten Sie, dass wir im Konstruktor die Gesamtzahl der besuchten Zustände auf „0“ festlegen. Das Flag für die Berechnung der kumulativen Belohnung CumCounted wird auf ‚false‘ gesetzt. Wir werden die kumulative Belohnung direkt berechnen, bevor wir die Daten in einer Datei speichern. Wir benötigen diese Werte beim Training des Modells.

Mit der Add-Methode werden wir der Datenbank Zustands-Aktions-Belohnungs-Sets hinzufügen.

bool STrajectory::Add(SState &state, int action, float reward)
  {
   if(Total + 1 >= ArraySize(Actions))
      return false;
   States[Total] = state;
   Actions[Total] = action;
   if(Total > 0)
      Revards[Total - 1] = reward;
   Total++;
//---
   return true;
  }

Bitte beachten Sie, dass wir die Belohnung für den vorherigen Zustand speichern, da sie für den Übergang vom vorherigen Zustand in den aktuellen erhalten wird, wenn eine vom Agenten im vorherigen Zustand gewählte Aktion ausgeführt wird. Auf diese Weise respektieren wir die Ursache-Wirkung-Beziehung zwischen Handlung und Belohnung.

Die Methode zur Berechnung der kumulativen Belohnungen CumRevards ist recht einfach. Sie sollten jedoch darauf achten, dass das Flag CumCounted für die durchgeführte Berechnung überwacht wird. Das ist eine sehr wichtige Sache. Diese Kontrolle verhindert eine wiederholte Berechnung der kumulativen Belohnung, die die Daten des Trainingssatzes und damit das Training des Modells insgesamt grundlegend verfälschen kann.

void STrajectory::CumRevards(void)
  {
   if(CumCounted)
      return;
//---
   for(int i = Buffer_Size - 2; i >= 0; i--)
      Revards[i] += Revards[i + 1] * DiscountFactor;
   CumCounted = true;
  }

Ich schlage vor, dass Sie sich mit den Methoden für die Arbeit mit Dateien in der Anlage vertraut machen. Kommen wir nun zu unseren unmittelbaren „Arbeitspferden“ — unseren EAs.

Wir werden den ersten EA zum Sammeln von Erfahrungen in der Datei Research.mq5 erstellen. Wir planen, den EA im Optimierungsmodus des Strategietesters zu starten, um durch mehrere Durchläufe des Agenten auf einer Trainingsepisode mit historischen Daten parallel Erfahrungen zu sammeln. Dies ist genau der Ansatz, den wir in Phase 1 des vorherigen Artikels verwendet haben. Wie in der EA „Fasa1.mql5“ werden wir die Methoden OnTester, OnTesterInit, OnTesterPass und OnTesterDeinit verwenden, um Informationen aus verschiedenen Durchläufen zu sammeln und in einem einzigen Erfahrungsspeicher zu speichern. Jetzt verwenden wir unser Modell, um Aktionen auszuwählen, und nicht einen Zufallsgenerator, wie in dem angegebenen EA.

Die externen EA-Parameter werden ohne Änderungen von den vorherigen übernommen. In diesen Parametern geben wir den Arbeitszeitraum und die Parameter der verwendeten Indikatoren an.

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input ENUM_TIMEFRAMES      TimeFrame   =  PERIOD_H1;
//---
input group                "---- RSI ----"
input int                  RSIPeriod   =  14;            //Period
input ENUM_APPLIED_PRICE   RSIPrice    =  PRICE_CLOSE;   //Applied price
//---
input group                "---- CCI ----"
input int                  CCIPeriod   =  14;            //Period
input ENUM_APPLIED_PRICE   CCIPrice    =  PRICE_TYPICAL; //Applied price
//---
input group                "---- ATR ----"
input int                  ATRPeriod   =  14;            //Period
//---
input group                "---- MACD ----"
input int                  FastPeriod  =  12;            //Fast
input int                  SlowPeriod  =  26;            //Slow
input int                  SignalPeriod =  9;            //Signal
input ENUM_APPLIED_PRICE   MACDPrice   =  PRICE_CLOSE;   //Applied price
input int                  Agent=1;

Fügen wir den Parameter Agent hinzu, um den Strategieoptimierer zu starten. Sie wird im EA-Code nicht verwendet und wird nur benötigt, um die Anzahl der Agenten im Optimierer des Strategietesters zu regulieren.

Im Bereich der globalen Variablen wird ein Element der Struktur SState deklariert, um den aktuellen Zustand des Systems zu erfassen. Eine Trajektorienstruktur STrajectory, um die Erfahrungen des aktuellen Agenten zu speichern. Wir deklarieren ein statisches Array von Flugbahnen aus einem Element, das wir verwenden werden, um Erfahrungen zwischen Frames zu übertragen.

SState               sState;
STrajectory          Base;
STrajectory          Buffer[];
STrajectory          Frame[1];
CNet                 Actor;
CFQF                 Schedule;
int                  Models = 1;

Hier werden wir auch die Variablen für die Erstellung von zwei neuronalen Netzmodellen angeben: Agent und Planer. Wir werden mehrere Agenten innerhalb eines Agentenmodells verwenden. Wir werden auf diese Frage bei der Beschreibung der Architektur der Modelle näher eingehen.

In der EA-Initialisierungsmethode gibt es nichts Neues. Wir initialisieren die Indikatorobjekte und die Handelsklasse und laden die vortrainierten Modelle hoch. Wenn es keine solchen Modelle gibt, erstellen wir neue Modelle mit zufälligen Parametern. Den vollständigen Code der Methode finden Sie im Anhang.

Ich möchte auf die CreateDescriptions-Methode zur Beschreibung der Architektur von Modellen eingehen. Wir werden unsere Intentionsagenten mit der Actor-Critic-Methode trainieren. Daher werden wir eine Beschreibung für drei Modelle erstellen:

  • Agent (Akteur)
  • (Critic) Kritiker
  • Scheduler (Planer, oberstes Modell der Hierarchie).

Erschrecken Sie nicht, dass bei der Erstellung der Architekturbeschreibung für 3 Modelle globale Variablen für 2 Modelle deklariert wurden. Tatsache ist, dass wir in der Phase der Datenerfassung keine Modelle trainieren werden. Daher wird die Kritikfunktion nicht verwendet. Aus diesem Grund erstellen wir das Modell nicht. 

Um vergleichbare Modelle zu schaffen, haben wir gleichzeitig eine gemeinsame Methode zur Erklärung der Architektur von Modellen entwickelt. Es wird sowohl in der Phase der Datenerhebung als auch in der Phase der Modellbildung verwendet.

In den Methodenparametern erhalten wir Zeiger auf 3 Objekte, um die Architekturen der erstellten Modelle zu übertragen. Im Hauptteil der Methode prüfen wir die Relevanz der empfangenen Zeiger, um gegebenenfalls neue Objekte zu erstellen.

bool CreateDescriptions(CArrayObj *actor, CArrayObj *critic, CArrayObj *scheduler)
  {
//---
   if(!actor)
     {
      actor = new CArrayObj();
      if(!actor)
         return false;
     }
//---
   if(!critic)
     {
      critic = new CArrayObj();
      if(!critic)
         return false;
     }
//---
   if(!scheduler)
     {
      scheduler = new CArrayObj();
      if(!scheduler)
         return false;
     }

Zunächst erstellen wir eine Beschreibung der Architektur des Actors (Agenten). Wie immer verwenden wir zuerst die vollständig verknüpfte Schicht, gefolgt von einer Schicht zur Datennormalisierung.

//--- Actor
   actor.Clear();
   CLayerDescription *descr;
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   int prev_count = descr.count = (int)(HistoryBars * 12 + 9);
   descr.window = 0;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = 1000;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Als Nächstes habe ich eine weitere vollständig verknüpfte Schicht hinzugefügt.

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 300;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Als Nächstes wird die Faltungsschicht versuchen, bestimmte Muster in den Daten zu erkennen.

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 100;
   descr.window = 3;
   descr.step = 3;
   descr.window_out = 2;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Wir werden seine Ergebnisse mit einer vollständig verbundenen Schicht verarbeiten.

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 100;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Legen wir eine weitere Faltungsschicht dahinter.

//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 50;
   descr.window = 2;
   descr.step = 2;
   descr.window_out = 4;
   descr.activation = SIGMOID;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Bei einem solchen „Schichtkuchen“ reduziert sich die Datenmenge auf 100 Elemente. Diese Architektur wird die Datenvorverarbeitung durchführen.

Als Nächstes müssen wir mehrere Absichtsagenten erstellen. Um die Erstellung mehrerer Modelle zu vermeiden, werden wir unsere Erfahrung nutzen und die Klasse CNeuronMultiModel einer voll verbundenen neuronalen Schicht mit mehreren Modellen anwenden. Zunächst erstellen wir eine vollständig verknüpfte Schicht von ausreichender Größe.

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 1000;
   descr.optimization = ADAM;
   descr.activation = TANH;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Dann erstellen wir 2 versteckte voll verbundene neuronale Schichten mit jeweils 10 Modellen.

//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMultiModels;
   descr.count = 200;
   descr.window = 100;
   descr.step = 10;
   descr.activation = TANH;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 8
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMultiModels;
   descr.count = 50;
   descr.window = 200;
   descr.step = 10;
   descr.activation = TANH;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

In der letzten Phase der Modellierung erstellen wir eine Ergebnisausgabeschicht, die eine eigene Funktion hat. Am Ausgang unseres Akteurs sollten wir eine probabilistische Verteilung von Aktionen erhalten. Bei der Policy-Gradient-Methode haben wir ähnliche Probleme angegangen, indem wir die Ausgabe mit der SoftMax-Funktion für einen einzigen Ergebnisvektor normalisiert haben. Nun müssen wir die Ergebnisse von 10 Modellen normalisieren.

Durch die Verwendung unserer vollständig verknüpften Multi-Modell-Schicht werden die Ergebnisse aller 10 Modelle in einer Matrix gespeichert. Wir können unsere Schicht CNeuronSoftMaxOCL zur Normalisierung der Daten verwenden. Bei der Initialisierung der Schicht geben wir an, dass wir eine aus 10 Zeilen bestehende Matrix normalisieren müssen.

//--- layer 9
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMultiModels;
   descr.count = 4;
   descr.window = 50;
   descr.step = 10;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 10
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronSoftMaxOCL;
   descr.count = 4;
   descr.step = 10;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Wir entwickelten ein Modell mit einer einzigen Datenvorverarbeitungseinheit, gefolgt von 10 parallelen Akteuren (Absichts-Agenten). Jeder Akteur hat eine probabilistische Verteilung von Aktionen am Ausgang.

In ähnlicher Weise wird ein Kritikermodell mit 10 Kritikern als Ausgabe erstellt. Am Ausgang des Kritikers erwarten wir jedoch für jede Aktion den Wert der Funktion „Wert“. Daher verwenden wir die SoftMax-Schicht im Kritikmodell nicht.

Das Modell des Schedulers (Planers) in diesem Algorithmus ist ein klassisches Modell mit einer Ebene. Im Rahmen dieses Algorithmus wählt der Planer jedoch nicht die Aktion des Agenten aus, sondern er wählt einen bestimmten Actor aus unserem Pool aus, um dessen Politik in der aktuellen Situation zu verfolgen. Der Planer hat die Möglichkeit, den aktuellen Zustand des Systems zu bewerten, um einen geeigneten Agenten auszuwählen. Es kann auch die Zustände der Agenten abfragen, um eine Entscheidung zu treffen.

In dieser Implementierung wird vorgeschlagen, dem Scheduler einen konkatenierten Vektor des Zustands des analysierten Systems und einen Vektor der Ergebnisse aus dem Pool der Akteure zu liefern. Auf diese Weise kann der Planer Informationen über den Systemzustand und die Bewertung der Ergebnisse des Actor nutzen, um einen geeigneten Akteur für die Absicht auszuwählen.

Geben Sie in der Beschreibung des Scheduler-Modells die Quelldatenschicht in der entsprechenden Größe an.

//--- Scheduler
   scheduler.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = (int)(HistoryBars * 12 + 9+40);
   descr.window = 0;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }

Es folgt eine Ebene der Normalisierung der Originaldaten.

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

Für die Verarbeitung der Quelldaten wird ein ähnlicher modularer Ansatz wie der zuvor beschriebene verwendet.

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 300;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 100;
   descr.window = 3;
   descr.step = 3;
   descr.window_out = 2;
   descr.activation = LReLU;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 100;
   descr.optimization = ADAM;
   descr.activation = SIGMOID;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 50;
   descr.window = 2;
   descr.step = 2;
   descr.window_out = 4;
   descr.activation = SIGMOID;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }

Der Entscheidungsblock verwendet ein Perceptron mit zwei versteckten Schichten. Es handelt sich um ein mehrschichtiges neuronales Netz, das die Verarbeitung und Analyse von Eingabedaten unter Verwendung mehrerer Abstraktionsschichten und hochrangiger Merkmale ermöglicht. Die Verwendung von zwei verborgenen Schichten verleiht dem Modell eine größere Ausdruckskraft und die Fähigkeit, komplexe Abhängigkeiten zwischen Eingabedaten und Ausgabeentscheidungen zu erfassen.

Am Ausgang dieses Perzeptrons wenden wir eine vollständig parametrisierte Quantil-Funktion an. Mit der Quantilsfunktion lässt sich die bedingte Verteilung einer Zielvariablen auf der Grundlage der Eingabedaten modellieren. Anstatt einen einzelnen Wert vorherzusagen, liefert sie uns Informationen über die Wahrscheinlichkeit, dass der Wert der Zielvariablen innerhalb eines bestimmten Bereichs liegen wird.

Die Größe der Ergebnisschicht im Entscheidungsblock entspricht der Größe unseres Agentenpools. Das bedeutet, dass jedes Element des Ergebnisvektors eine Wahrscheinlichkeit oder Punktzahl für den entsprechenden Agenten im Pool darstellt. Auf diese Weise können wir den besten Agenten oder die beste Kombination von Agenten auf der Grundlage ihrer Punktzahlen und Wahrscheinlichkeiten auswählen.

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 100;
   descr.optimization = ADAM;
   descr.activation = TANH;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = 100;
   descr.optimization = ADAM;
   descr.activation = TANH;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 8
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronFQF;
   descr.count = 10;
   descr.window_out = 32;
   descr.optimization = ADAM;
   if(!scheduler.Add(descr))
     {
      delete descr;
      return false;
     }

Die erstellten Modellarchitekturen bieten eine breite Palette von Möglichkeiten, den aktuellen Zustand des Systems zu bewerten und die optimale Entscheidung zu treffen. Durch den Einsatz mehrschichtiger, neuronaler Netze können die Modelle verschiedene Aspekte der Eingabedaten analysieren und hochrangige Merkmale extrahieren, die mit effektiven Strategien und Entscheidungsfindungen in Verbindung gebracht werden können.

Dadurch können Modelle Probleme mit begrenzten Daten oder spärlichen Belohnungen effizient lösen und sich an veränderte Bedingungen und Szenarien anpassen.

Die Methode OnTick verdient eine zusätzliche Erwähnung. Zu Beginn wird geprüft, ob eine neue Kerze geöffnet ist, und es werden Parameter für den aktuellen Zustand des Systems gesammelt. Dieser Vorgang wiederholt sich für EAs in mehreren Artikeln hintereinander unverändert, und ich will mich nicht weiter damit aufhalten. Anschließend durchlaufen wir direkt die beiden Modelle und wählen auf der Grundlage ihrer Ergebnisse eine Agentenaktion aus.

Wir führen zunächst einen Vorwärtsdurchlauf durch den Pool von Absichts-Agenten durch.

   State1.AssignArray(sState.state);
   if(!Actor.feedForward(GetPointer(State1), 12, true))
      return;

Die erhaltenen Ergebnisse des direkten Durchlaufs der Agenten werden mit der aktuellen Beschreibung des Systemzustands verkettet und zur Auswertung an den Input des Schedulers übermittelt.

   Actor.getResults(Result);
   State1.AddArray(Result);
   if(!Schedule.feedForward(GetPointer(State1),12,true))
      return;

Nach einem Vorwärtsdurchlauf durch beide Modelle verwenden wir Stichproben, um einen bestimmten Absichtsagenten auf der Grundlage ihrer Verteilungen auszuwählen. Dann wird aus dem ausgewählten Agenten eine bestimmte Aktion aus seiner Wahrscheinlichkeitsverteilung ausgewählt.

   int act = GetAction(Result, Schedule.getSample(), Models);

Es ist wichtig zu beachten, dass wir in allen Durchgängen ein Modell mit konstanten Parametern verwenden, ohne zu trainieren. Daher wird eine gierige Wahl des Agenten und der Aktion mit hoher Wahrscheinlichkeit dazu führen, dass sich dieselbe Flugbahn bei jedem Durchgang wiederholt. Durch das Ziehen von Zufallswerten aus Verteilungen können wir die Umgebung erkunden und bei jedem Durchgang unterschiedliche Flugbahnen erhalten. Gleichzeitig ermöglicht die durch die Verteilung auferlegte Beschränkung die Durchführung von Forschungsarbeiten in einer bestimmten Richtung.

Am Ende der Funktion führen wir die ausgewählte Agentenaktion aus und speichern die Daten für späteres Training.

   switch(act)
     {
      case 0:
         if(!Trade.Buy(Symb.LotsMin(), Symb.Name()))
            act = 3;
         break;
      case 1:
         if(!Trade.Sell(Symb.LotsMin(), Symb.Name()))
            act = 3;
         break;
      case 2:
         for(int i = PositionsTotal() - 1; i >= 0; i--)
            if(PositionGetSymbol(i) == Symb.Name())
               if(!Trade.PositionClose(PositionGetInteger(POSITION_IDENTIFIER)))
                 {
                  act = 3;
                  break;
                 }
         break;
     }
//---
   float reward = 0;
   if(Base.Total > 0)
      reward = ((sState.state[240] + sState.state[241]) - 
               (Base.States[Base.Total - 1].state[240] + Base.States[Base.Total - 1].state[241])) / 10;
   if(!Base.Add(sState, act, reward))
      ExpertRemove();
//---
  }

Nach jedem Durchlauf werden die Informationen über die durchgeführten Aktionen, die durchlaufenen Systemzustände und die erhaltene Belohnung in einem einzigen Puffer für das anschließende Training der Modelle gespeichert. Diese Vorgänge werden in den Methoden OnTester, OnTesterInit, OnTesterPass und OnTesterDeinit ausgeführt. Ihr Konstruktionsprinzip wurde in dem Artikel über den Go-Explore-Algorithmus ausführlich beschrieben.

Den vollständigen Code des EA und alle seine Methoden finden Sie im Anhang.

Nachdem wir den EA erstellt haben, um Erfahrungen zu sammeln, starten wir ihn im Optimierungsmodus des Strategietesters und fahren mit der Arbeit am Study.mq5 Model Training EA fort. In den externen Parametern dieses EA geben wir nur die Anzahl der Trainingsiterationen an.

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int                  Iterations     = 100000;

Im Block der globalen Variablen haben wir bereits 3 Modelle angegeben: Actor, Critic und Scheduler. Die Architektur der Modelle wurde oben beschrieben.

STrajectory          Buffer[];
CNet                 Actor;
CNet                 Critic;
CFQF                 Scheduler;

In der Funktion OnInit laden wir zunächst das Trainingsmuster, das der vorherige EA für uns erstellt hat.

int OnInit()
  {
//---
   ResetLastError();
   if(!LoadTotalBase())
     {
      PrintFormat("Error of load study data: %d", GetLastError());
      return INIT_FAILED;
     }

Wir laden bereits trainierte oder erstellen neue Modelle.

//--- load models
   float temp;
   if(!Actor.Load(FileName + "Act.nnw", temp, temp, temp, dtStudied, true) ||
      !Critic.Load(FileName + "Crt.nnw", temp, temp, temp, dtStudied, true) ||
      !Scheduler.Load(FileName + "Sch.nnw", dtStudied, true))
     {
      CArrayObj *actor = new CArrayObj();
      CArrayObj *critic = new CArrayObj();
      CArrayObj *schedule = new CArrayObj();
      if(!CreateDescriptions(actor, critic, schedule))
        {
         delete actor;
         delete critic;
         delete schedule;
         return INIT_FAILED;
        }
      if(!Actor.Create(actor) || !Critic.Create(critic) || !Scheduler.Create(schedule))
        {
         delete actor;
         delete critic;
         delete schedule;
         return INIT_FAILED;
        }
      delete actor;
      delete critic;
      delete schedule;
     }
   Scheduler.getResults(SchedulerResult);
   Models = (int)SchedulerResult.Size();
   Actor.getResults(ActorResult);
   Scheduler.SetUpdateTarget(Iterations);
   if(ActorResult.Size() % Models != 0)
     {
      PrintFormat("The scope of the scheduler does not match the scope of the Agent (%d <> %d)", 
                                                                     Models, ActorResult.Size());
      return INIT_FAILED;
     }

Wir initialisieren das Ereignis zum Start des Trainingsprozesses.

//---
   if(!EventChartCustom(ChartID(), 1, 0, 0, "Init"))
     {
      PrintFormat("Error of create study event: %d", GetLastError());
      return INIT_FAILED;
     }
//---
   return(INIT_SUCCEEDED);
  }

Bei der Train-Methode organisieren wir den direkten Trainingsprozess. Es ist wichtig zu beachten, dass die Trainingsmenge aus mehreren Durchgängen besteht, und in der aktuellen Implementierung speichern wir die Zustände in einer sequenziellen Trajektorienstruktur, anstatt sie alle in einer gemeinsamen Datenbank zu kombinieren. Das bedeutet, dass wir, um einen Zustand des Systems zufällig auszuwählen, zunächst einen Durchgang aus dem Array und dann einen Zustand aus diesem Durchgang auswählen müssen.

Streng genommen assoziieren wir Passagen und Handlungen nicht mit bestimmten Akteuren der Absicht. Stattdessen werden alle Agenten auf einer gemeinsamen Basis von Beispielen trainiert. Dieser Ansatz ermöglicht es uns, austauschbare und konsistente Agentenrichtlinien zu erstellen, wobei jeder Agent die Ausführung einer Richtlinie von jedem Zustand des Systems aus fortsetzen kann, unabhängig davon, welche Richtlinie vor Erreichen dieses Zustands angewendet wurde.

Zu Beginn der Methode leisten wir ein wenig Vorarbeit: Wir bestimmen die Anzahl der Durchläufe in der Beispieldatenbank und speichern den Wert des Tickzählers, um die Zeit des Trainingsprozesses zu kontrollieren.

void Train(void)
  {
   int total_tr = ArraySize(Buffer);
   uint ticks = GetTickCount();

Nach der Durchführung der vorbereitenden Arbeiten organisieren wir einen Zyklus der Modelltrainings.

   for(int iter = 0; (iter < Iterations && !IsStopped()); iter ++)
     {
      int tr = (int)(((double)MathRand() / 32767.0) * (total_tr - 1));
      int i = 0;
      i = (int)((MathRand() * MathRand() / MathPow(32767, 2)) * (Buffer[tr].Total - 2));

In der Trainingsschleife wählen wir, wie bereits erwähnt, zunächst einen Durchgang aus der Trainingsbasis von Beispielen aus. Dann wählen wir zufällig einen Zustand aus dem ausgewählten Durchgang aus. Dieser Zustand wird als Eingabe für den Vorwärtsdurchlauf der Modelle Actor und Critic verwendet.

      State1.AssignArray(Buffer[tr].States[i].state);
      if(IsStopped())
        {
         PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
         ExpertRemove();
         return;
        }
      if(!Actor.feedForward(GetPointer(State1), 12, true) ||
         !Critic.feedForward(GetPointer(State1), 12, true))
         return;

Laden wir die Ergebnisse des direkten Durchlaufs in die entsprechenden Vektoren.

      Actor.getResults(ActorResult);
      Critic.getResults(CriticResult);

Der resultierende Ergebnisvektor aus dem Vorwärtsdurchlauf des Akteurs wird mit dem Systemzustandsvektor verkettet. Dieser kombinierte Vektor wird dann in den Input des Scheduler-Modells zur Analyse und Bewertung eingespeist.

      State1.AddArray(ActorResult);
      if(!Scheduler.feedForward(GetPointer(State1), 12, true))
         return;

Nach einem Vorwärtsdurchlauf des Schedulers wird eine gierige Agentenauswahl durchgeführt.

      Scheduler.getResults(SchedulerResult);
      int agent = Scheduler.getAction();
      if(agent < 0)
        {
         iter--;
         continue;
        }

Es ist wichtig zu beachten, dass zu Beginn des Trainings Stichproben durchgeführt werden können, um die Umgebung so weit wie möglich zu erkunden. Wenn der Planer jedoch lernt und seine Strategie verbessert, gehen wir zu einer gierigen Agentenauswahl über. Der Grund dafür ist, dass der Planer mehr Erfahrung hat und in der Lage ist, die Zustände des Systems genauer einzuschätzen und den besten Agenten auszuwählen, um seine Ziele zu erreichen.

Wir treffen keine Entscheidung über die Wahl einer Aktion, da die Beispieldatenbank bereits Informationen über die durchgeführten Aktionen und die entsprechenden Belohnungen enthält. Aus diesen Daten generieren wir Belohnungsvektoren für jedes Modell und führen den Rückwärtsdurchlauf nacheinander für jedes dieser Modelle durch. Zunächst führen wir einen Rückwärtsdurchlauf des Schedulers durch.

      int actions = (int)(ActorResult.Size() / SchedulerResult.Size());
      float max_value = CriticResult[agent * actions];
      for(int j = 1; j < actions; j++)
         max_value = MathMax(max_value, CriticResult[agent * actions + j]);
      SchedulerResult[agent] = Buffer[tr].Revards[i];
      Result.AssignArray(SchedulerResult);
      //---
      if(!Scheduler.backProp(GetPointer(Result),0.0f,NULL))
         return;

Dann rufen wir die Reverse-Pass-Methode des Kritikers auf.

      int agent_action = agent * actions + Buffer[tr].Actions[i];
      CriticResult[agent_action] = Buffer[tr].Revards[i];
      Result.AssignArray(CriticResult);
      //---
      if(!Critic.backProp(GetPointer(Result)))
         return;

Daran schließt sich das Agentenmodell der Absicht an.

      ActorResult.Fill(0);
      ActorResult[agent_action] = Buffer[tr].Revards[i] - max_value;
      Result.AssignArray(ActorResult);
      //---
      if(!Actor.backProp(GetPointer(Result)))
         return;

Am Ende der Schleifenwiederholungen überprüfen wir die Trainingszeit und zeigen dem Nutzer alle 0,5 Sekunden Informationen über den Trainingsverlauf an.

      if(GetTickCount() - ticks > 500)
        {
         string str = StringFormat("Actor %.2f%% -> Error %.8f\n", 
                                iter * 100.0 / (double)(Iterations), Actor.getRecentAverageError());
         str += StringFormat("Critic %.2f%% -> Error %.8f\n", 
                                iter * 100.0 / (double)(Iterations), Critic.getRecentAverageError());
         str += StringFormat("Scheduler %.2f%% -> Error %.8f\n", 
                                iter * 100.0 / (double)(Iterations), Scheduler.getRecentAverageError());
         Comment(str);
         ticks = GetTickCount();
        }
     }

Nach Abschluss des Modelltrainings protokollieren wir die erzielten Ergebnisse und leiten die Beendigung des EA ein.

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

Den vollständigen Code des Expert Advisors finden Sie im Anhang. Alle Dateien dieses Modells werden im SAC-Verzeichnis archiviert.

Das Modelltraining besteht aus Iterationen, in denen wir Beispiele im Optimierungsmodus sammeln und den Trainingsprozess auf einem Echtzeitdiagramm durchführen. Wenn das Trainingsergebnis nicht unseren Erwartungen entspricht, führen wir die Beispielsammlung erneut durch und trainieren die Modelle erneut. Diese Vorgänge werden so lange wiederholt, bis ein optimales Ergebnis erzielt wird, das unseren Lernzielen entspricht.

Wiederholte Iterationen des Sammelns von Beispielen und des Trainings von Modellen sind ein wesentlicher Bestandteil des Lernprozesses. Sie ermöglichen es uns, Modelle zu verbessern, sie an veränderte Bedingungen anzupassen und optimale Ergebnisse anzustreben. Jede Iteration liefert uns neue Daten und Möglichkeiten zur Verbesserung unserer Modelle, sodass wir Probleme effektiver lösen und unsere Ziele erreichen können.

Es ist wichtig zu beachten, dass der Lernprozess iterativ sein kann und mehrere Zyklen erfordert, bevor wir das gewünschte Ergebnis erreichen. Dies liegt daran, dass die Ausbildung von Modellen ein komplexer Prozess ist, der eine ständige Verfeinerung und Verbesserung erfordert. Wir sollten bereit sein, einen iterativen Ansatz zu verfolgen und die Sammel- und Trainingsmaßnahmen zu wiederholen, bis wir unsere Ziele erreichen und optimale Ergebnisse erzielen.

Ein System, das so angelegt ist, dass die Datenbank mit den Beispielen bei jedem weiteren Durchgang der Beispielsammlung ständig aktualisiert wird, bietet uns einen erheblichen Vorteil. Auf diese Weise können wir eine möglichst vollständige Datenbank mit Beispielen erstellen, was die Ausbildung des Modells und seine Fähigkeit, optimale Entscheidungen zu treffen, erheblich verbessern kann.

Es ist jedoch zu bedenken, dass die Vergrößerung der Beispieldatenbank Folgen hat. Erstens kann die Verarbeitung und Analyse größerer Datenmengen länger dauern und mehr Rechenressourcen erfordern. Dies kann zu einer längeren Iterationszeit beim Modelltraining führen. Zweitens kann eine Vergrößerung der Beispielbasis die Komplexität des Trainings erhöhen, da die Modelle mehr Daten verarbeiten und sich an unterschiedlichere Szenarien anpassen müssen.


3. Test

Die Ergebnisse des Trainings des Modells auf historischen EURUSD H1-Daten für die ersten 4 Monate des Jahres 2023 zeigten, dass das Modell in der Lage ist, sowohl innerhalb als auch außerhalb des Trainingsdaten Gewinne zu erzielen. Es wurden mehr als 10 Iterationen zum Sammeln von Beispielen und zum Trainieren des Modells durchgeführt, einschließlich 8 bis 24 Optimierungsdurchläufen in jeder Iteration. Insgesamt wurden mehr als 200 Durchläufe gesammelt, und der Trainingsprozess umfasste zwischen 100.000 und 10.000.000 Iterationen.

Um die Ergebnisse des Modelltrainings zu überprüfen, wurde der EA Test.mq5 erstellt, der anstelle von Stichproben eine gierige Auswahl eines Agenten und einer Aktion verwendete. Auf diese Weise konnte die Funktionsweise des Modells getestet und der Zufallsfaktor ausgeschlossen werden.

Das nachstehende Diagramm zeigt die Ergebnisse des Modells außerhalb des Trainingssatzes. In kurzer Zeit konnte das Modell einen kleinen Gewinn erwirtschaften. Der Gewinnfaktor lag bei 1,19 und der Rückgewinnungsfaktor bei 0,46.

Es ist jedoch anzumerken, dass die Saldenkurve unrentable Bereiche zeigt, was auf die Notwendigkeit zusätzlicher Iterationen des Modelltrainings hinweisen kann. Dies kann dazu beitragen, die Fähigkeit zur Erzielung von Gewinnen zu verbessern und das Risiko beim Handel zu verringern.

Trainingsergebnisse Trainingsergebnisse


Schlussfolgerung

Wir können die Effizienz der Scheduled Auxiliary Control (SAC-X)-Methode beim Training von Agentenmodellen für Finanzmärkte hervorheben. SAC-X ist eine Weiterentwicklung des klassischen Reinforcement-Learning-Ansatzes, die den Besonderheiten von Finanzdaten und den Anforderungen von Handelsstrategien Rechnung trägt.

Eines der Hauptmerkmale von SAC-X ist die Verwendung mehrerer Modelle (Actor, Critic, Planner), um den Zustand des Systems zu bewerten und Entscheidungen zu treffen. Dies ermöglicht es uns, verschiedene Aspekte des Handels zu berücksichtigen und eine flexiblere und anpassungsfähigere Agentenpolitik zu entwickeln.

Ein weiterer wichtiger Aspekt von SAC-X ist der Einsatz eines Schedulers, der den Zustand des Systems analysiert und den besten Intent-Agenten auswählt. Dies ermöglicht eine effizientere und genauere Entscheidungsfindung sowie konsistentere Handelsergebnisse.

Das Testen von SAC-X mit historischen EURUSD-Daten hat gezeigt, dass das System in der Lage ist, sowohl auf dem Trainingsset als auch außerhalb davon Gewinne zu erzielen. Es ist jedoch zu beachten, dass in einigen Fällen unrentable Zonen auf der Bilanzkarte entdeckt wurden, was auf die Notwendigkeit eines zusätzlichen Trainings des Modells hinweisen könnte.

Die Scheduled Auxiliary Control (SAC-X)-Methode ist ein leistungsfähiges Instrument für das Training von Agentenmodellen in der Finanzindustrie. Es berücksichtigt die Besonderheiten der Marktdaten, ermöglicht die Erstellung adaptiver und flexibler Handelsstrategien und zeigt das Potenzial für einen stabilen und profitablen Handel auf. Weitere Forschungen und Verbesserungen von SAC-X können zu noch besseren Ergebnissen führen und seine Anwendung auf den Finanzmärkten erweitern.


Liste der Referenzen

  • Learning by Playing – Solving Sparse Reward Tasks from Scratch
  • Neuronale Netze leicht gemacht (Teil 29): Der Algorithmus Advantage Actor Critic
  • Neuronale Netze leicht gemacht (Teil 35): Modul für intrinsische Neugierde
  • Neuronale Netze leicht gemacht (Teil 36): Relationales Verstärkungslernen
  • Neuronale Netze leicht gemacht (Teil 37): Sparse Attention (Verringerte Aufmerksamkeit)
  • Neuronale Netze leicht gemacht (Teil 38): Algorithmus der Erkundung durch Unstimmigkeiten
  • Neuronale Netze leicht gemacht (Teil 39): Go-Explore, ein anderer Ansatz zur Erkundung
  • Neuronale Netze leicht gemacht (Teil 40): Verwendung von Go-Explore bei großen Datenmengen


  • Programme, die im diesem Artikel verwendet werden

    # Name Typ Beschreibung
    1 Research.mq5 Expert Advisor Beispielsammlung EA
    2 Study.mql5 Expert Advisor Modelltraining EA
    3 Test.mq5 Expert Advisor Modellversuche EA
    4 Trajectory.mqh Klassenbibliothek Struktur der Systemzustandsbeschreibung
    5 FQF.mqh Klassenbibliothek Klassenbibliothek zur Organisation der Arbeit eines vollständig parametrisierten Modells
    6 NeuroNet.mqh Klassenbibliothek Eine Bibliothek von Klassen zur Erstellung eines neuronalen Netzes
    7 NeuroNet.cl Code Base Die Bibliothek des Programmcodes von OpenCL

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

    Beigefügte Dateien |
    MQL5.zip (219.34 KB)
    Neuronale Netze leicht gemacht (Teil 42): Modell der Prokrastination, Ursachen und Lösungen Neuronale Netze leicht gemacht (Teil 42): Modell der Prokrastination, Ursachen und Lösungen
    Im Kontext des Verstärkungslernens kann die Prokrastination (Zögern) eines Modells mehrere Ursachen haben. Der Artikel befasst sich mit einigen der möglichen Ursachen für Prokrastination bei Modellen und mit Methoden zu deren Überwindung.
    Neuronale Netze leicht gemacht (Teil 40): Verwendung von Go-Explore bei großen Datenmengen Neuronale Netze leicht gemacht (Teil 40): Verwendung von Go-Explore bei großen Datenmengen
    In diesem Artikel wird die Verwendung des Go-Explore-Algorithmus über einen langen Trainingszeitraum erörtert, da die Strategie der zufälligen Aktionsauswahl mit zunehmender Trainingszeit möglicherweise nicht zu einem profitablen Durchgang führt.
    Neuronale Netze leicht gemacht (Teil 43): Beherrschen von Fähigkeiten ohne Belohnungsfunktion Neuronale Netze leicht gemacht (Teil 43): Beherrschen von Fähigkeiten ohne Belohnungsfunktion
    Das Problem des Verstärkungslernens liegt in der Notwendigkeit, eine Belohnungsfunktion zu definieren. Sie kann komplex oder schwer zu formalisieren sein. Um dieses Problem zu lösen, werden aktivitäts- und umweltbasierte Ansätze zum Erlernen von Fähigkeiten ohne explizite Belohnungsfunktion erforscht.
    Neuronale Netze leicht gemacht (Teil 39): Go-Explore, ein anderer Ansatz zur Erkundung Neuronale Netze leicht gemacht (Teil 39): Go-Explore, ein anderer Ansatz zur Erkundung
    Wir setzen die Untersuchung der Umgebung in Modellen des verstärkten Lernens fort. Und in diesem Artikel werden wir uns einen weiteren Algorithmus ansehen – Go-Explore. Er ermöglicht es Ihnen, die Umgebung in der Phase der Modellbildung effektiv zu erkunden.