English 日本語
preview
Selbstoptimierende Expert Advisors in MQL5 (Teil 13): Eine sanfte Einführung in die Kontrolltheorie mit Hilfe der Matrixfaktorisierung

Selbstoptimierende Expert Advisors in MQL5 (Teil 13): Eine sanfte Einführung in die Kontrolltheorie mit Hilfe der Matrixfaktorisierung

MetaTrader 5Beispiele |
147 2
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Die Finanzmärkte sind oft schwer zu planen oder zu prognostizieren. Die Stimmung unter den Anlegern ist oft fragil und kann sich je nach dem globalen Klima und den drängenden Problemen, die das Tagesgeschehen beherrschen, schnell ändern. Daher können Handelsstrategien, die in einem historischen Kontext profitabel erscheinen, oft scheitern, wenn sie auf Echtzeitmärkten eingesetzt werden.

Es gibt viele Gründe, die dieses Verhalten bei Handelsanwendungen erklären können. Eine wichtige Erkenntnis ist jedoch, dass das Verhalten unserer Anwendungen, sobald sie einmal entwickelt und eingesetzt wurden, feststeht und in der Regel nicht mehr ohne menschliches Zutun geändert werden kann. Das bedeutet, dass unsere Strategien anfällig dafür sind, dieselben Fehler immer und immer wieder zu wiederholen, ohne jemals vom Scheitern zu profitieren oder aus vergangenen Fehlern zu lernen.

Abbildung 1: Der normale Handelsaufbau, der für den Einsatz von Handelsanwendungen auf den Finanzmärkten verwendet wird

Für dieses immer wiederkehrende Problem gibt es viele Lösungsvorschläge. Eine Lösung, die ein großes Potenzial hat, kommt jedoch aus dem Bereich der Kontrolltheorie. Die Kontrolltheorie befasst sich in erster Linie mit der Korrektur des Verhaltens eines Systems, das in einer dynamischen oder chaotischen Umgebung arbeitet, mit dem Ziel, das System auf ein bestimmtes Ziel auszurichten.

Indem wir in regelmäßigen Abständen eine Kopie der Leistung unserer Strategie an einen Feedback-Controller zurückgeben – einen Controller, der aufzeichnet und beobachtet, wie die Strategie mit den Märkten interagiert – können wir möglicherweise die Beziehung zwischen dem Verhalten unserer Strategie und den Marktergebnissen annähern. Dieser Controller zielt darauf ab, dominante Muster zu identifizieren, die sowohl mit Verlustgeschäften als auch mit Gewinngeschäften korrelieren. Wenn eine solche Struktur existiert und wir daraus lernen können, dann sollte der Feedback-Controller theoretisch in der Lage sein, die Dynamik unseres Handelssystems zu modifizieren und in Richtung Profitabilität zu lenken, selbst unter chaotischen und sich ständig ändernden Marktbedingungen. 

Dadurch ändert sich die Einsatzstruktur der Strategie gegenüber dem in Abbildung 1 dargestellten Schema. In Abbildung 2 führen wir den Leser in die einfache Notation ein, die in der Literatur für Kontrolltheorien verwendet wird, und bezeichnen die Eingangssignale des Marktes als (M) und die Ausgangssignale unserer Strategie als (S).

Abbildung 2: Wir können unsere Handelsanwendung neu definieren, indem wir eine Kurzschrift verwenden, um die Marktinputs (M) und den Strategieoutput (S) darzustellen.

In den meisten unserer bisherigen Erörterungen zur Matrixfaktorisierung konzentrierten wir uns in erster Linie auf die Erstellung von Regressions- und Klassifizierungsmodellen zur Vorhersage künftiger Kursniveaus oder Veränderungen technischer Indikatoren. In dieser Diskussion lehnen wir uns jedoch an die Regelungstheorie an, insbesondere an einen Zweig, der als Feedback-Regler bekannt ist. Dieser Bereich wird bei Diskussionen über numerisch gesteuerte Handelsanwendungen oft übersehen, obwohl die grundlegenden Theorien für unsere Bedürfnisse als Händler erweitert werden können.

Unser Ziel ist es, zu beweisen, dass Rückkopplungsregler ein wertvolles Maß an Feinsteuerung für Handelssysteme bieten. Bei sorgfältiger Einrichtung können diese Controller das System korrigieren und es auf Kurs halten, um Handelsgeschäfte mit Gewinn zu tätigen.

Wir begannen mit der Basisversion unserer Handelsstrategie, bei der zwei gleitende Durchschnitte verwendet wurden: einer für den Höchstkurs und einer für den Tiefstkurs. Beide gleitenden Durchschnitte hatten den gleichen Zeitraum, wodurch ein gleitender Durchschnittskanal entstand. Wenn der Kurs über diesen Kanal ausbrach, kauften wir; wenn der Kurs unter den Kanal ausbrach, verkauften wir. Diese Basisversion legte die Gewinnschwelle fest, die wir mit unserem Feedback-Controller überschreiten wollten. Unsere Handelsstrategie wird unten in Abbildung 3 dargestellt.

Abbildung 3: Visualisierung unserer Handelsstrategie auf dem EURUSD-Tageschart

Unser Feedback-Controller beobachtete zunächst die Leistung unserer Strategie über einen Zeitraum von 90 Tagen, bevor er eingreifen durfte. Das heißt, dass der Kontrolleur in den ersten 90 Tagen keine Eingaben an das System machte und nur Beobachtungen sammelte. Der Leser sollte beachten, dass dieser 90-Tage-Zeitraum ein Einstellungsparameter ist, wobei der geeignete Wert nicht immer im Voraus bekannt ist. Wir haben 90 Tage willkürlich gewählt, weil wir davon ausgingen, dass dieser Zeitraum mit den Geschäftszyklen der Finanzinstitute, die die Devisenmärkte beherrschen, übereinstimmen würde. Es steht den Lesern jedoch frei, mit diesem Parameter nach eigenem Ermessen zu experimentieren.

Im Laufe dieser 90 Tage zeichnete der Controller mehrere Schlüsselvariablen auf: angebotene Marktpreise, Kontostand, Kontokapital, Indikatorwerte und die Arten von Positionen, die wir eröffneten. Diese Beobachtungen wurden in einer Matrix, dem so genannten Snapshot, gespeichert. Anschließend haben wir ein lineares Modell erstellt, das die Entwicklung der Schnappschüsse im Laufe der Zeit erfasst. Mit anderen Worten: Die ersten 89 Schnappschüsse wurden auf die letzten 89 Schnappschüsse abgebildet, um dem System beizubringen, wie sich der Zustand der Strategie im Laufe der Zeit verändert hat.

Nach 90 Tagen erfasste das lineare System alle Systemzustände und sagte voraus, ob der nächste Handel profitabel oder unprofitabel sein würde. Wurde der nächste Handel als unrentabel prognostiziert, pausierte das System einfach den Handel, bis bessere Marktbedingungen zu erwarten waren.

Die Aufgabe unseres rückgekoppelten Controllers besteht also darin, die Ausgänge unseres Systems (S) zu beobachten und eine neue Kontrollfunktion (F) zu erlernen, die das Verhalten unseres Systems (FS) verändert, um unser System wieder in die Gewinnzone zu führen, damit unsere Strategie nicht direkt vom Markt (M) gesteuert wird. Nach Ablauf unseres 90-tägigen Beobachtungszeitraums wird unsere Strategie nicht mehr nur durch den Markt (M) gesteuert, sondern durch eine Kombination aus den Marktinputs und den Vorschlägen unseres Feedback-Controllers (FS + M)

Abbildung 4: Veranschaulichung, wie genau unser Feedback-Controller das Verhalten unserer Handelsanwendung nach der Bereitstellung verändern wird

Wir haben beide Systeme über fünf Jahre mit den Tages-Daten von EUR/USD einem Backtest unterzogen. Der Feedback-Regler verbesserte die Rentabilität des Systems um 82 %. Unsere Basisstrategie brachte anfangs einen Gewinn von 134 $. Nach Hinzufügen des Rückkopplungsreglers stieg der Gewinn auf 245 $. Darüber hinaus erzielte das verbesserte System diese Gewinne bei einer geringeren Anzahl von Handelsgeschäften: Die Anzahl der Positionen sank von 180 auf 152 – eine Verringerung der Handelsaktivität um 15 %. Dies bedeutete, dass unser neues System höhere Gewinne bei geringerem Risiko erwirtschaftete – eine äußerst wünschenswerte Eigenschaft für jede Handelsanwendung.

Außerdem wurde das Gesamtrisiko verringert. Das Basissystem wies einen Bruttoverlust von -$1.092 auf, während das rückkopplungsgesteuerte System den Bruttoverlust auf -$838 reduzierte, während der Bruttogewinn nahezu konstant blieb. Dies ist ein weiteres sehr wünschenswertes Ergebnis.

Die Sharpe Ratio des Basissystems betrug 0,34. Nach Einführung des Feedback-Controllers stieg er auf 0,68 – eine Verbesserung um 100 %, was für einen so schwierigen Markt wie EUR/USD bemerkenswert ist. Der Anteil der gewinnbringenden Geschäfte stieg ebenfalls um 6 %, von 53,89 % auf 57,24 %. Folglich sank der Anteil der Verlustgeschäfte in gleichem Maße, was zeigt, dass der Feedback-Controller erfolgreich dominante Muster erlernte, die Verlustgeschäfte von Gewinngeschäften unterscheiden. Schließlich stieg die erwartete Auszahlung von 0,75 auf 1,61, was einer Verbesserung von 114 % entspricht.

Es liegt auf der Hand, dass Rückkopplungsregler bei numerisch gesteuerten Handelsanwendungen eine entscheidende Rolle spielen können. Wenn sie richtig konfiguriert sind, können sie zuverlässig lernen, einzugreifen und zu verhindern, dass eine Strategie die gleichen Fehler wiederholt. Der Aufbau eines Controllers direkt aus Momentaufnahmen ist eine Technik, die als Systemidentifikation bekannt ist. 

Diese Familie von Algorithmen wurde ursprünglich von Ingenieuren entwickelt, die sich für Strömungsdynamik interessieren. Diese Ingenieure versuchten oft, Steuerungen für die Komponenten zu entwerfen, aus denen die Tragflächen von Flugzeugen bestehen, aber es gibt keine expliziten Formeln für die Vorhersage oder Korrektur der Auswirkungen von Turbulenzen. Natürlich mussten sie Methoden finden, um aus den Beobachtungen des Systemverhaltens und der Eingaben, die dieses Verhalten hervorgerufen haben, optimale Steuerungseingaben zu lernen. 

In unserer Diskussion haben wir ein lineares Modell des Systems erstellt, daher wird dieser Ansatz auch als lineare Systemidentifikation bezeichnet. Die hier vorgestellten Ergebnisse motivieren uns, mehr Zeit in die Zuweisung zusätzlicher Aufgaben an den Rückkopplungsregler zu investieren und in Zukunft nichtlineare Systemidentifikatoren zu untersuchen. Lassen Sie uns beginnen.


Erste Schritte in MQL5

Wie bei den meisten unserer Handelsanwendungen beginnen wir mit der Definition wichtiger Systemdefinitionen. Für unsere Basisstrategie benötigen wir nur eine Systemdefinition, die den gemeinsamen gleitenden Durchschnittszeitraum festlegt. 

//+------------------------------------------------------------------+
//|                                         Closed Loop Feedback.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System definitions                                               |
//+------------------------------------------------------------------+
#define MA_PERIOD 10

Der nächste wichtige Aspekt unseres Systementwurfs ist die Menge der globalen Variablen. Für diese spezielle Basislinie benötigen wir nur eine Handvoll globaler Variablen, die mit den technischen Indikatoren verknüpft sind, auf die wir uns stützen. Insbesondere werden wir Handler für die beiden Indikatoren des gleitenden Durchschnitts und einen weiteren Handler für die ATR haben, die wir zum Setzen von Stop-Losses verwenden. Für jeden dieser Indikatoren ist auch ein eigener Puffer erforderlich.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_h_handler,ma_l_handler,atr_handler;
double ma_h[],ma_l[],atr[];

Fast alle Handelsanwendungen, die wir in diesen Diskussionen erstellen, haben Abhängigkeiten, da wir nicht immer jeden Code von Grund auf neu schreiben. In dieser Anwendung greifen wir auf die Handelsbibliothek sowie auf zwei nutzerdefinierte Bibliotheken zurück: eine, um die Bildung neuer Kerzen zu verfolgen, und eine weitere, um wichtige Preisinformationen wie die Geld- und Briefkurse abzurufen. 

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>

CTrade      Trade;
Time        *DailyTimeHandler;
TradeInfo   *TradeInfoHandler;

Wenn das System initialisiert ist, beginnen wir mit der Erstellung neuer Instanzen unserer nutzerdefinierten Klassen. Wir erstellen auch neue Instanzen unserer Indikatoren. 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   DailyTimeHandler = new Time(Symbol(),PERIOD_D1);
   TradeInfoHandler = new TradeInfo(Symbol(),PERIOD_D1);
   ma_h_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   atr_handler      = iATR(Symbol(),PERIOD_D1,14);
//---
   return(INIT_SUCCEEDED);
  }

Wenn die Anwendung nicht mehr verwendet wird, löschen wir die dynamischen Objekte, die wir zur effizienten Verwaltung des Speichers erstellt haben, und wir geben Indikatoren frei, die nicht mehr verwendet werden, um die Ressourcen sicher zu verwalten.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   delete DailyTimeHandler;
   delete TradeInfoHandler;
   IndicatorRelease(ma_h_handler);
   IndicatorRelease(ma_l_handler);
  }

Wenn ein neues Preisniveau empfangen wird, wird OnTick aufgerufen. Dieser Handler verwendet unsere eigene Bibliothek, um zu prüfen, ob sich eine neue Tageskerze gebildet hat. Wenn eine neue Kerze erkannt wird, aktualisieren wir die in unseren Puffern gespeicherten Indikatorwerte und speichern eine Kopie des aktuellen Schlusskurses. Wenn es keine offenen Positionen gibt, wenden wir unsere Handelsregeln an, um zu entscheiden, ob wir kaufen oder verkaufen wollen: Wenn der Schlusskurs über dem Hoch unseres gleitenden Durchschnittskanals liegt, kaufen wir; wenn er unter dem Tief unseres gleitenden Durchschnittskanals liegt, verkaufen wir. 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(DailyTimeHandler.NewCandle())
     {
      CopyBuffer(ma_h_handler,0,0,1,ma_h);
      CopyBuffer(ma_l_handler,0,0,1,ma_l);
      CopyBuffer(atr_handler,0,0,1,atr);

      double c = iClose(Symbol(),PERIOD_D1,0);

      if(PositionsTotal() == 0)
        {
         if(c > ma_h[0])
            Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");

         if(c < ma_l[0])
            Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
        }
     }
  }

Wenn alle Vorgänge abgeschlossen sind, werden die Systemdefinitionen, die wir zu Beginn der Anwendung erstellt haben, gelöscht. Dies beschreibt die Basisversion unseres Handelssystems.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
//+------------------------------------------------------------------+

Wir können nun damit beginnen, unsere Anwendung anhand historischer Daten zu testen. Für diesen Test wählen wir fünf Jahre täglicher EUR/USD-Daten aus und wenden unsere Basisanwendung an.

Abbildung 5: Backtesting unserer Handelsanwendung mit historischen EUR/USD-Marktdaten aus 5 Jahren

Um den Test realistischer zu gestalten, führen wir auch zufällige Verzögerungen ein, da die realen Marktbedingungen unvorhersehbar sind und Verzögerungen helfen, diese Unsicherheit zu simulieren.

Abbildung 6: Auswahl von realistischen Backtest-Bedingungen für unsere Analyse

Von dort aus können wir eine detaillierte Zusammenfassung der Leistung unserer Anwendung erstellen. In der Einleitung zu diesem Artikel habe ich bereits einen Überblick über die Backtest-Ergebnisse gegeben. Hier sehen wir, dass die Sharpe Ratio des Systems niedrig ist und bei 0,34 liegt, und dass der Durchschnitt der Handelsgeschäfte mit Verlust größer ist als der Durchschnitt derer mit Gewinn. Der Fairness halber sei angemerkt, dass selbst in unserer verbesserten Version der Anwendung die Zahl der Verlustgeschäfte im Durchschnitt immer noch etwas größer war als die der Gewinngeschäfte. Unser geschlossenes Rückmeldesystem konnte diese Diskrepanz jedoch verringern.

Abbildung 7: Detaillierte Statistiken, die die Leistung unserer Handelsanwendung zusammenfassen

Wenn wir außerdem die Kapitalkurve unseres Basissystems untersuchen, sehen wir, dass sie tatsächlich einen Aufwärtstrend aufweist. Allerdings mangelt es ihr an Beständigkeit, da sie oft monatelang in einer Spanne verharrt und in Zyklen Gewinne und Verluste einfährt, sodass die Strategie im Wesentlichen über längere Zeiträume an einer Stelle verharrt. Diese Inkonsistenz ist genau das Verhalten, das wir mit unserem Feedback-Controller korrigieren wollen, um das Verhalten des Systems zu ändern.

Abbildung 8: Die von unserer Handelsanwendung erstellte Kapitalkurve ist aufgrund ihrer instabilen Form nicht zufriedenstellend


Die Verbesserung unserer ersten Ergebnisse

Wir können nun damit beginnen, unsere ersten Ergebnisse mit Hilfe des Rückkopplungsreglers zu verbessern. Um unnötige Wiederholungen zu vermeiden, habe ich Teile des Codes, die unverändert bleiben, weggelassen und werde mich hauptsächlich auf die Änderungen konzentrieren, die an der ursprünglichen Version unserer Anwendung vorgenommen wurden.

Wie Sie schnell feststellen können, ist die Zahl der für unsere Anwendung erforderlichen Systemdefinitionen gestiegen. Unsere ursprüngliche Anwendung benötigte nur eine Systemdefinition, aber die aktualisierte Version ist auf vier angewiesen. Diese neuen Definitionen sind an Folgendes gebunden: (1) die Gesamtzahl der Beobachtungen, die wir sammeln wollen, bevor der Feedback-Controller online geht, (2) die Anzahl der Merkmale, die wir verfolgen wollen – in diesem Beispiel zwölf Merkmale, die die Leistung unserer Handelsstrategie erfassen – und (3) einen Vektor, der die Art der derzeit gehaltenen Position erfasst.

//+------------------------------------------------------------------+
//|                                         Closed Loop Feedback.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System definitions                                               |
//+------------------------------------------------------------------+
#define MA_PERIOD    10
#define OBSERVATIONS 90
#define FEATURES     12
#define ACCOUNT_STATES 3

Auch die globalen Variablen werden immer komplexer, je mehr unsere Anwendung wächst. Wir benötigen nun Matrizen, um Momentaufnahmen zu speichern, Vektoren, um Vorhersagen aus unserem linearen System aufzuzeichnen, und boolesche Flaggen, um zu bestimmen, wann das System bereit ist, von der Beobachtung zum Live-Handel überzugehen.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_h_handler,ma_l_handler,atr_handler,scenes,b_matrix_scenes;
double ma_h[],ma_l[],atr[];
matrix snapshots,OB_SIGMA,OB_VT,OB_U,b_vector,b_matrix;
vector S,prediction;
vector account_state;
bool predict,permission;

Während der Initialisierung werden mehrere Vorverarbeitungsschritte durchgeführt. Die ersten Schritte sind bekannt: Wir richten unsere technischen Indikatoren ein. Danach initialisieren wir die Snapshot-Matrix mit 12 Zeilen und 90 Spalten. In diesem Stadium werden alle Einträge auf Null gesetzt. Das Flag „permission“ wird mit „true“ initialisiert, was bedeutet, dass das System mit der Erlaubnis zum Handel beginnt. Nach Ablauf des 90-tägigen Beobachtungszeitraums wird dieses Flag jedoch auf „false“ und das Flag „predict“ auf „true“ gesetzt. Zu diesem Zeitpunkt handelt das System nicht mehr bedingungslos, sondern muss erst die Genehmigung des linearen Modells einholen, bevor es tätig wird.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   DailyTimeHandler  = new Time(Symbol(),PERIOD_D1);
   TradeInfoHandler  = new TradeInfo(Symbol(),PERIOD_D1);
   ma_h_handler      = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler      = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   atr_handler       = iATR(Symbol(),PERIOD_D1,14);
   snapshots         = matrix::Ones(FEATURES,OBSERVATIONS);
   scenes            = 0;
   b_matrix_scenes   = 0;
   account_state     = vector::Zeros(3);
   b_matrix          = matrix::Zeros(1,1);
   prediction        = vector::Zeros(2);
   predict           = false;
   permission        = true;
//---
   return(INIT_SUCCEEDED);
  }

Der OnTick-Handler hat sich gegenüber dem Basissystem erheblich verändert. Eine wichtige Anpassung besteht darin, dass der Kontostand jetzt in einem Vektor mit drei Einträgen gespeichert wird. Dieser Vektor verwendet einen One-Hot-Codierungsansatz: Wenn eine Kaufposition offen ist, wird der erste Eintrag auf 1 gesetzt; wenn eine Verkaufsposition offen ist, wird der zweite Eintrag auf 1 gesetzt; wenn keine Position offen ist, wird der dritte Eintrag auf 1 gesetzt. Dadurch werden dem linearen System kategoriale Informationen in einem strukturierten Format zur Verfügung gestellt.

Die eigentliche Handelslogik bleibt dieselbe: Liegt der Schlusskurs über dem Hoch des gleitenden Durchschnittskanals, ziehen wir einen Kauf in Betracht; liegt er unter dem Tief, ziehen wir einen Verkauf in Betracht. Während der ersten 90 Tage, in denen das Flag „predict“ falsch ist, hat das System die uneingeschränkte Erlaubnis zum Handel. Danach müssen alle Handelsentscheidungen anhand des linearen Systems validiert werden. Sobald die erforderliche Anzahl von Beobachtungen gesammelt wurde, wird die Snapshot-Matrix angepasst, um die neuen Daten aufzunehmen, und das von uns identifizierte lineare System filtert unsere Handelsentscheidungen, bevor wir auf den Markt gehen. 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(DailyTimeHandler.NewCandle())
     {
      CopyBuffer(ma_h_handler,0,0,1,ma_h);
      CopyBuffer(ma_l_handler,0,0,1,ma_l);
      CopyBuffer(atr_handler,0,0,1,atr);
      double c = iClose(Symbol(),PERIOD_D1,0);

      if(PositionsTotal() == 0)
        {
         account_state = vector::Zeros(ACCOUNT_STATES);

         if(c > ma_h[0])
           {
            if(!predict)
              {
               if(permission)
                  Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");
              }

            account_state[0] = 1;
           }

         else
            if(c < ma_l[0])
              {
               if(!predict)
                 {
                  if(permission)
                     Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
                 }

               account_state[1] = 1;
              }

            else
              {
               account_state[2] = 1;
              }
        }

      if(scenes < OBSERVATIONS)
        {
         take_snapshots();
        }

      else
        {
         matrix temp;
         temp.Assign(snapshots);
         snapshots = matrix::Ones(FEATURES,scenes+1);

         //--- The first row is the intercept and must be full of ones
         for(int i=0;i<FEATURES;i++)
            snapshots.Row(temp.Row(i),i);

         take_snapshots();
         fit_snapshots();

         predict = true;
         permission = false;
        }
      scenes++;
     }
  }

In dieser Phase erfasst das System eine vollständige Momentaufnahme der relevanten Merkmale: die gestrigen Eröffnungs-, Höchst-, Tiefst- und Schlusskurse, den Stand der technischen Indikatoren, die Kontoinformationen und den mit einem Hot-Code kodierten Kontostatusvektor. Durch die Einbeziehung dieser Variablen kann das System lernen, die Beziehung zwischen unserer Strategie und dem Markt zu modellieren. Wir sind nicht mehr nur an der Vorhersage künftiger Kursniveaus interessiert, sondern auch an der Vorhersage des künftigen Saldos unseres Handelskontos. Und wenn diese Beziehung besteht und erlernt werden kann, dann wird unser Feedback-Controller hoffentlich lernen, das Verhalten der Strategie ohne menschliches Eingreifen zu ändern.

//+------------------------------------------------------------------+
//| Record the current state of our system                           |
//+------------------------------------------------------------------+
void take_snapshots(void)
  {
   snapshots[1,scenes] = iOpen(Symbol(),PERIOD_D1,1);
   snapshots[2,scenes] = iHigh(Symbol(),PERIOD_D1,1);
   snapshots[3,scenes] = iLow(Symbol(),PERIOD_D1,1);
   snapshots[4,scenes] = iClose(Symbol(),PERIOD_D1,1);
   snapshots[5,scenes] = AccountInfoDouble(ACCOUNT_BALANCE);
   snapshots[6,scenes] = AccountInfoDouble(ACCOUNT_EQUITY);
   snapshots[7,scenes] = ma_h[0];
   snapshots[8,scenes] = ma_l[0];
   snapshots[9,scenes] = account_state[0];
   snapshots[10,scenes] = account_state[1];
   snapshots[11,scenes] = account_state[2];
  }
//+------------------------------------------------------------------+

Wir passen dann ein lineares System mit zwei Matrizen an: X für Eingaben und y für Ziele. Es handelt sich um ein System mit mehreren Ausgängen, das mehrere Ergebnisse gleichzeitig vorhersagt. Die y-Matrix ist um einen Zeitschritt versetzt, sodass die Eingaben den vorherigen 89 Momentaufnahmen und die Ausgaben den nachfolgenden 89 Momentaufnahmen entsprechen. Wenn mehr Beobachtungen gesammelt werden, passt sich diese Schleife natürlich an den wachsenden Datensatz an, indem sie die insgesamt verstrichenen Szenen verfolgt.

Eine Szene ist der Zeitraum zwischen zwei aufeinander folgenden Schnappschüssen. In unserer Diskussion sammeln wir täglich Schnappschüsse des Systems, daher kann die Anzahl der Szenen auch an die Vorlieben des Lesers angepasst werden. Mit Hilfe der Pseudo-Inversionslösung (der Funktion PInv() in MQL5) lösen wir die optimalen Koeffizienten, die X auf y abbilden. Diese Funktion wurde in früheren Artikeln unserer Serie ausführlich erläutert. Alle Leser, die mit der Bedeutung der Funktion PInv nicht vertraut sind, sollten jedoch die hier verlinkte Diskussion lesen, da die Funktion PInv() ein leistungsfähiges Werkzeug ist, das wir verwenden.

Nach der Anpassung gibt das System die Schnappschüsse, die Eingaben und Ziele, die gelernten Koeffizienten und seine Vorhersagen aus. Wir interpretieren diese Vorhersagen dann in Echtzeit. Wenn das Modell ein Wachstum des Kontosaldos vorhersagt, wird die Erlaubnis zum Handel erteilt. Wenn das Modell eine Aufwärtsdynamik des Hochs des gleitenden Durchschnitts vorhersagt, während eine Kaufposition in Erwägung gezogen wird, wird ebenfalls eine Genehmigung erteilt. Ähnlich verhält es sich, wenn das Modell ein Abwärtsmomentum für das Tief des gleitenden Durchschnitts vorhersagt und eine Verkaufsposition in Betracht gezogen wird: Die Genehmigung wird erteilt. In allen anderen Fällen verweigert das System die Genehmigung.

Wenn die Genehmigung erteilt wird und keine Positionen offen sind, führt das System den gewünschten Handel aus. Nach Beendigung dieses Vorgangs druckt das Programm den aktuellen Saldo, den voraussichtlichen Saldo und ob die Genehmigung erteilt wurde. Damit sind die Änderungen abgeschlossen, die notwendig sind, um erste Veränderungen in der Leistung unserer Strategie zu sehen. Der Code, den wir geschrieben haben, weist den Controller nicht explizit an, wann er kaufen oder verkaufen soll, sondern wir definieren für den Controller eine Reihe von Aktionen, die er auf der Grundlage von Schlussfolgerungen, die er aus den gesammelten Beobachtungen zieht, durchführen kann.

//+------------------------------------------------------------------+
//| Fit our linear model to our collected snapshots                  |
//+------------------------------------------------------------------+
void fit_snapshots(void)
  {
   matrix X,y;
   X.Reshape(FEATURES,scenes);
   y.Reshape(FEATURES-1,scenes);

   for(int i=0;i<scenes;i++)
     {
      X[0,i] = snapshots[0,i];
      X[1,i] = snapshots[1,i];
      X[2,i] = snapshots[2,i];
      X[3,i] = snapshots[3,i];
      X[4,i] = snapshots[4,i];
      X[5,i] = snapshots[5,i];
      X[6,i] = snapshots[6,i];
      X[7,i] = snapshots[7,i];
      X[8,i] = snapshots[8,i];
      X[9,i] = snapshots[9,i];
      X[10,i] = snapshots[10,i];
      X[11,i] = snapshots[11,i];

      y[0,i] = snapshots[1,i+1];
      y[1,i] = snapshots[2,i+1];
      y[2,i] = snapshots[3,i+1];
      y[3,i] = snapshots[4,i+1];
      y[4,i] = snapshots[5,i+1];
      y[5,i] = snapshots[6,i+1];
      y[6,i] = snapshots[7,i+1];
      y[7,i] = snapshots[8,i+1];
      y[8,i] = snapshots[9,i+1];
      y[9,i] = snapshots[10,i+1];
      y[10,i] = snapshots[11,i+1];
     }

//--- Find optimal solutions
   b_vector = y.MatMul(X.PInv());
   Print("Day Number: ",scenes+1);
   Print("Snapshot");
   Print(snapshots);
   Print("Input");
   Print(X);
   Print("Target");
   Print(y);
   Print("Coefficients");
   Print(b_vector);
   Print("Prediciton");
   Print(y.Col(scenes-1));
   prediction = b_vector.MatMul(snapshots.Col(scenes-1));

   if(prediction[4] > AccountInfoDouble(ACCOUNT_BALANCE))
      permission = true;

   else
      if((account_state[0] == 1) && (prediction[6] > ma_h[0]))
         permission = true;

      else
         if((account_state[1] == 1) && (prediction[7] < ma_l[0]))
            permission = true;

         else
            permission = false;

   if(permission)
     {
      if(PositionsTotal() == 0)
        {
         if(account_state[0] == 1)
            Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");

         else
            if(account_state[1] == 1)
               Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
        }
     }

   Print("Current Balabnce: ",AccountInfoDouble(ACCOUNT_BALANCE)," Predicted Balance: ",prediction[4]," Permission: ",permission);
  }
//+------------------------------------------------------------------+

Mit diesem Rahmen sind wir nun bereit, unsere Anwendung unter denselben historischen Bedingungen wie zuvor zu testen. Um Redundanz zu vermeiden, werde ich die Testparameter hier nicht wiederholen, sondern darauf hinweisen, dass sie mit den in Abbildung 6 verwendeten identisch sind. 

Abbildung 9: Vorbereitung auf den Test der verbesserten Version unserer Handelsstrategie

Die detaillierte Analyse zeigt einen deutlichen Unterschied zwischen dem Grundsystem und dem rückkopplungsgesteuerten System. Der Nettogewinn ist erheblich gestiegen, während der Bruttogewinn nahezu unverändert geblieben ist – ein Zeichen dafür, dass der Controller mit einigem Geschick direkt auf Verlustgeschäfte abzielt. Die Sharpe Ratio hat sich deutlich erhöht, und die durchschnittliche Größe der gewinnbringenden und der verlustbringenden Geschäfte ist nun fast gleich, was eine bemerkenswerte Verbesserung gegenüber der Ausgangssituation darstellt. 

Abbildung 10: Eine detaillierte Analyse der Ergebnisse unserer Handelsanwendung zeigt, welche Verbesserungen wir vorgenommen haben

Die Kapitalkurve zeigt eine geringere Volatilität und einen stärkeren, beständigeren Aufwärtstrend. Dies ist das gewünschte Ergebnis, das wir durch die Implementierung eines geschlossenen Regelkreises in unsere Handelsstrategie zu beobachten hofften.

Abbildung 11: Die Kapitalkurve, die sich aus unserer überarbeiteten Handelsstrategie ergibt, weist im Laufe der Zeit eine geringere Volatilität und stabilere Erträge auf.

Ich habe auch einen Screenshot der Schnappschüsse beigefügt, die unser System während des Backtests gemacht hat. Leider passten nicht alle 12 Funktionen in unseren einzigen Screenshot. Der Leser kann jedoch sehen, dass die letzte Zeile der Snapshot-Matrix denselben Saldo von 500 Dollar enthält, mit dem unsere simulierte Handelsstrategie begann (siehe Abbildung 6). Dann verfolgen wir, wie das Kapital und der Kontostand durch die Marktbedingungen und den Entscheidungsfindungsprozess unserer Handelsstrategie beeinflusst werden.

Abbildung 12: Die Schnappschüsse der Leistung unseres Systems, die wir zur Überwachung und Korrektur unserer Strategie aufzeichnen

Ein Schnappschuss der Ausgaben im Journal bestätigt dieses Verhalten. Zum Beispiel wurde am Tag 1645 die Erlaubnis-Flag auf false gesetzt, weil das lineare System einen Verlust erwartete und den Handel blockierte. 

Abbildung 13: Unser linearer Systemcontroller verweigerte unserer Handelsstrategie die Zulassung zum Handel, weil er ungünstige Marktbedingungen analysierte

Bereits am nächsten Tag, als es einen Gewinn erwartete, erteilte das System die Genehmigung und führte den Handel aus. Alles in allem funktioniert die verbesserte Anwendung von Anfang bis Ende so. Dies zeigt, dass es viele Anwendungsfälle für Prognosemodelle gibt, die über die normale Preisvorhersage hinausgehen. Wir können diese gelernten Modelle verwenden, um zu testen, ob es eine Struktur gibt, aus der wir lernen können und die unsere Gewinn- und Verlustgeschäfte dominiert. Damit wir lernen, die Fehler der Vergangenheit nicht zu wiederholen.

Abbildung 14: Wenn die Marktbedingungen mit unserer Strategie in Einklang zu stehen scheinen, geben uns unsere Feedback-Kontrolleure die Erlaubnis, den Handel fortzusetzen



Schlussfolgerung

Zusammenfassend lässt sich sagen, dass Feedback-Controller uns die Möglichkeit geben, Handelssysteme zu entwickeln, die nicht nur profitabel, sondern auch anpassungsfähig sind. Indem sie aus ihren eigenen Leistungen lernen, können diese Systeme dazu beitragen, dass wir Fehler der Vergangenheit nicht wiederholen und auch unter veränderten Marktbedingungen effektiv bleiben. Unsere Ergebnisse zeigen deutliche Verbesserungen in Bezug auf Rentabilität, Effizienz und Risikominderung, was beweist, dass die Kontrolltheorie einen echten praktischen Wert für den Handel hat. Mit Blick auf die Zukunft motiviert uns diese Arbeit, über lineare Modelle hinauszugehen und nichtlineare Rückkopplungssteuerungen zu erforschen, die noch reichhaltigere Muster im Marktverhalten erfassen können. Mit diesen Fortschritten können wir die Stabilität und Rentabilität unserer Systeme in Zeiten der Unsicherheit weiter steigern.

Bevor wir jedoch unser lineares System hinter uns lassen, gibt es noch wertvolle Verbesserungen, die wir vornehmen können. Wenn wir den Wert jeder dieser möglichen Verbesserungen an einem einfachen linearen System messen, erhalten wir einen zuverlässigen Maßstab für jedes andere nichtlineare System, das wir aufbauen wollen.

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (2)
Majeed Odubela
Majeed Odubela | 20 Sept. 2025 in 14:12
Sie haben die Include-Dateien volatilityDoctor/Time..Trade nicht beigefügt. Ihre Einreichung kann ohne die beiden Include-Dateien nicht getestet werden.
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 23 Sept. 2025 in 14:17
Majeed Odubela #:
Sie haben die Include-Dateien volatilityDoctor/Time..Trade nicht beigefügt. Ohne die beiden Include-Dateien kann Ihr Beitrag nicht getestet werden.
Majeed Es tut mir leid, von Ihren Erfahrungen zu hören.

Sie müssen jedoch auch verstehen, dass dieser Artikel Teil einer größeren Familie von verwandten Serien ist, die aufeinander aufbauen.

Die Klasse, nach der Sie suchen, wurde in einem früheren Artikel von Grund auf neu erstellt und hinzugefügt.
Vom Neuling zum Experten: Animierte Schlagzeilen mit MQL5 (X) – Multiple Symbol Chart View für den Nachrichtenhandel Vom Neuling zum Experten: Animierte Schlagzeilen mit MQL5 (X) – Multiple Symbol Chart View für den Nachrichtenhandel
Heute werden wir ein System zur Darstellung mehrerer Charts mit Hilfe von Chartobjekten entwickeln. Ziel ist es, den Nachrichtenhandel durch die Anwendung von MQL5-Algorithmen zu verbessern, die dazu beitragen, die Reaktionszeit des Händlers in Zeiten hoher Volatilität, wie z. B. bei wichtigen Nachrichten, zu verkürzen. In diesem Fall bieten wir Händlern eine integrierte Möglichkeit, mehrere wichtige Symbole mit einem einzigen All-in-One-Tool für den Nachrichtenhandel zu überwachen. Unsere Arbeit entwickelt sich mit dem News Headline EA kontinuierlich weiter. Er verfügt nun über eine wachsende Anzahl von Funktionen, die sowohl für Händler, die vollautomatische Systeme verwenden, als auch für diejenigen, die den manuellen Handel mit Hilfe von Algorithmen bevorzugen, einen echten Mehrwert darstellen. Klicken Sie sich durch und beteiligen Sie sich an dieser Diskussion, um mehr Wissen, Einblicke und praktische Ideen zu erhalten.
Automatisieren von Handelsstrategien in MQL5 (Teil 30): Erstellen eines harmonischen AB-CD-Preisaktionsmusters mit visuellem Feedback Automatisieren von Handelsstrategien in MQL5 (Teil 30): Erstellen eines harmonischen AB-CD-Preisaktionsmusters mit visuellem Feedback
In diesem Artikel entwickeln wir einen AB=CD Pattern EA in MQL5, der harmonische Auf- und Abwärtsmuster von AB=CD mit Hilfe von Umkehrpunkten und Fibonacci-Ratios identifiziert und Trades mit präzisen Einstiegs-, Stop-Loss- und Take-Profit-Levels ausführt. Wir verbessern den Einblick des Händlers mit visuellem Feedback durch Chart-Objekte.
Automatisieren von Handelsstrategien in MQL5 (Teil 31): Erstellung eines Price Action 3 Drives Harmonic Pattern Systems Automatisieren von Handelsstrategien in MQL5 (Teil 31): Erstellung eines Price Action 3 Drives Harmonic Pattern Systems
In diesem Artikel entwickeln wir ein 3 Drives Pattern System in MQL5, das steigende und fallende harmonische Muster der 3 Drives mit Umkehrpunkten und Fibonacci-Verhältnissen identifiziert und Handelsgeschäfte mit anpassbaren Einstiegs-, Stop-Loss- und Take-Profit-Levels basierend auf vom Nutzer ausgewählten Optionen ausführt. Wir verbessern den Einblick des Händlers mit visuellem Feedback durch Chart-Objekte.
Vom Neuling zum Experten: Detaillierte Handelsberichte mit Reporting EA beherrschen Vom Neuling zum Experten: Detaillierte Handelsberichte mit Reporting EA beherrschen
In diesem Artikel befassen wir uns mit der Verbesserung der Details von Handelsberichten und der Übermittlung des endgültigen Dokuments per E-Mail im PDF-Format. Dies stellt eine Weiterentwicklung unserer bisherigen Arbeit dar, da wir weiterhin erforschen, wie wir die Leistungsfähigkeit von MQL5 und Python nutzen können, um Handelsberichte in den bequemsten und professionellsten Formaten zu erstellen und zu planen. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr über die Optimierung der Erstellung von Handelsberichten innerhalb des MQL5-Ökosystems.