English Русский
preview
Risikomanager für den algorithmischen Handel

Risikomanager für den algorithmischen Handel

MetaTrader 5Handel | 9 Oktober 2024, 16:30
45 0
Aleksandr Seredin
Aleksandr Seredin

Inhalt


Einführung

In diesem Artikel werden wir eine Risikomanager-Klasse entwickeln, um die Risiken im algorithmischen Handel zu kontrollieren. Ziel dieses Artikels ist es, die Grundsätze des kontrollierten Risikos an den algorithmischen Handel anzupassen und sie in einer eigenen Klasse umzusetzen, damit jeder die Wirksamkeit des Ansatzes der Risikostandardisierung beim Intraday-Handel und bei Investitionen auf den Finanzmärkten überprüfen kann. Die hier vorgestellten Materialien nutzen und ergänzen die im vorherigen Artikel „Risikomanager für den manuellen Handel“ zusammengefassten Informationen. Im vorangegangenen Artikel haben wir gesehen, dass die Risikokontrolle die Handelsergebnisse selbst einer profitablen Strategie erheblich verbessern und Anleger vor großen Drawdowns in einem kurzen Zeitraum schützen kann.

In Anlehnung an die Kommentare zum vorangegangenen Artikel wird in dieser Veröffentlichung zusätzlich auf die Kriterien für die Auswahl der Durchführungsmethoden eingegangen, um den Artikel für Anfänger verständlicher zu machen. Wir werden auch Definitionen von Handelskonzepten in Kommentaren zum Code behandeln. Erfahrene Entwickler wiederum können die vorgestellten Materialien nutzen, um den Code an ihre Architekturen anzupassen.

Weitere in diesem Artikel verwendete Begriffe und Definitionen:

High\low sind Höchst- und Tiefstpreis des Symbols über einen bestimmten Zeitraum, der durch einen Balken oder eine Kerze angezeigt wird.

Stop Loss ist der Grenzpreis für den Ausstieg aus einer Position mit Verlust. Wenn sich der Kurs in die entgegengesetzte Richtung der von uns eröffneten Position entwickelt, begrenzen wir die Verluste der offenen Position, indem wir sie schließen, bevor der Verlust die Werte übersteigt. Diese Werte werden zum Zeitpunkt der Eröffnung der Position berechnet.

Take Profit ist der Grenzpreis für den Ausstieg aus einer Position mit Gewinn. Dieser Preis wird festgelegt, um die Position zu beenden und den erhaltenen Gewinn zu sichern. Er wird in der Regel so festgelegt, dass er den geplanten Gewinn auf der Einlage oder in dem Bereich erfasst, in dem die tägliche Volatilität des Instruments voraussichtlich ausgeschöpft wird. Das heißt, wenn klar wird, dass das Instrument in einem bestimmten Zeitraum kein weiteres Bewegungspotenzial hat, und dann ist eine Korrektur in die entgegengesetzte Richtung wahrscheinlicher.

Der technische Stop-Loss ist der Wert des Stop-Loss, der auf der Grundlage der technischen Analyse festgelegt wird, z. B. für Kerzen-Hoch/Tief, Ausbruch, Fraktal usw., je nach der verwendeten Handelsstrategie. Das Hauptunterscheidungsmerkmal dieser Methode ist, dass wir den Stop-Loss in Chartpunkten auf der Grundlage einer bestimmten Formation festlegen. In diesem Fall kann sich der Einstiegspunkt ändern, während die Stop-Loss-Werte gleich bleiben können. In diesem Fall gehen wir davon aus, dass, wenn der Kurs den Stop-Loss-Wert erreicht, die technische Formation als durchbrochen gilt und die Richtung des Instruments somit nicht mehr relevant ist. 

Der berechnete Stop-Loss ist der Stop-Loss, der auf der Grundlage eines bestimmten berechneten Wertes der Volatilität des Symbols für einen bestimmten Zeitraum festgelegt wird. Der Unterschied besteht darin, dass er nicht an eine Formation auf dem Chart gebunden ist. Bei diesem Ansatz ist es besonders wichtig, den Einstiegspunkt für den Handel zu finden und nicht, wo sich die Stopp-Position im Muster befindet.

Getter ist eine Klassenmethode für den Zugriff auf den Wert eines geschützten Feldes einer Klasse oder Struktur. Sie wird verwendet, um den Wert der Klasse innerhalb der vom Klassenentwickler implementierten Logik zu kapseln, ohne dass die Möglichkeit besteht, ihre Funktionalität oder den Wert eines geschützten Feldes mit der angegebenen Zugriffsebene bei der weiteren Verwendung zu ändern.

Der Slippage (Schlupf) tritt auf, wenn der Broker einen Auftrag zu einem Preis eröffnet, der von dem ursprünglich gewünschten Preis abweicht. Diese Situation kann auftreten, wenn Sie nach Märkten handeln. So wird z.B. bei der Übermittlung einer Order das Positionsvolumen auf der Grundlage des für den Handel festgelegten Risikos in der Einzahlungswährung und des berechneten/technischen Stop Loss in Punkten berechnet. Wenn dann eine Position vom Broker eröffnet wird, kann es vorkommen, dass die Position zu anderen Kursen eröffnet wurde als denen, zu denen der Stop-Loss in Punkten berechnet wurde. So kann er beispielsweise 150 statt 100 betragen, wenn der Markt instabil ist. Solche Fälle von Positionseröffnungen sollten überwacht werden, und wenn das daraus resultierende Risiko für einen offenen Auftrag viel größer wird (für die Parameter des Risikomanagers) als erwartet, sollte ein solches Geschäft frühzeitig geschlossen werden, um höhere Verluste zu vermeiden.

Der Intraday-Handel ist ein Handelsstil, bei dem die Handelsgeschäfte nur innerhalb eines Tages durchgeführt werden. Nach diesem Ansatz werden offene Positionen nicht über Nacht, d.h. auf den nächsten Handelstag, übertragen. Bei diesem Handelsansatz müssen die Risiken nicht berücksichtigt werden, die mit morgendlichen Gaps, zusätzlichen Gebühren für die Positionsübertragung, wechselnden Trends am nächsten Tag usw. verbunden sind. Wenn offene Positionen auf den nächsten Tag übertragen werden, kann dieser Handelsstil in der Regel als mittelfristig angesehen werden.

Positional Trading ist ein Handelsstil, bei dem eine Position für ein Instrument auf dem Konto gehalten wird, ohne dass das Volumen erhöht oder verringert wird und ohne dass zusätzliche Einstiegsgeschäfte getätigt werden. Bei diesem Ansatz berechnet der Händler bei Erhalt eines Signals für ein Instrument sofort das gesamte Risiko auf der Grundlage des Signals und verarbeitet es. Andere Signale werden erst berücksichtigt, wenn die vorherige Position vollständig geschlossen ist. 

Das Momentum eines Handelssymbols ist eine unidirektionale, nicht rückwärts gerichtete Bewegung eines Symbols in einem bestimmten Zeitrahmen. Der Ausgangspunkt des Impulses ist der Ausgangspunkt der Bewegung in dieselbe Richtung ohne Richtungsänderung. Kehrt der Kurs zum Ausgangspunkt des Impulses zurück, spricht man in der Regel von einem erneuten Test. Der Wert der No-Rollback-Bewegung des Instruments in Punkten hängt unter anderem von der aktuellen Marktvolatilität, wichtigen Nachrichten oder wichtigen Preisniveaus für das Instrument selbst ab.


Unterklasse für algorithmischen Handel

Die Basisklasse RiskManagerBase, die wir im vorangegangenen Artikel beschrieben haben, enthält alle notwendigen Funktionen, um die Risikokontrolllogik für eine sicherere Arbeit während des aktiven Intraday-Handels bereitzustellen. Um all diese Funktionen nicht zu duplizieren, werden wir eines der wichtigsten Prinzipien der objektorientierten MQL5-Programmierung verwenden – die Vererbung. Auf diese Weise können wir den bereits geschriebenen Code verwenden und ihn um die Funktionen ergänzen, die für die Einbettung unserer Klasse in einen beliebigen Handelsalgorithmus erforderlich sind.

Die Projektarchitektur wird auf den folgenden Grundsätzen aufbauen:

  • Zeitersparnis durch Vermeidung des erneuten Schreibens der gleichen Funktionalität
  • Einhaltung der Grundsätze der Prinzipien der SOLID-Programmierung
  • Erleichterte Arbeit mit unserer Architektur für mehrere Entwicklungsteams
  • Die Möglichkeit, unser Projekt auf jede beliebige Handelsstrategie auszuweiten

Der erste Punkt soll, wie bereits erwähnt, zu einer erheblichen Zeitersparnis bei der Entwicklung führen. Wir werden die Vererbungsfunktionalität nutzen, um die zuvor erstellte Logik von Operationen mit Grenzwerten und Ereignissen zu erhalten und keine Zeit mit dem Kopieren und Testen von neuem Code zu verschwenden.

Der zweite Punkt betrifft die Grundprinzipien der Konstruktion von Klassen in der Programmierung. Zunächst einmal werden wir nach dem Prinzip der „offenen Geschlossenheit“ vorgehen: Unsere Klasse wird erweitert, ohne dass die grundlegenden Prinzipien und Ansätze der Risikokontrolle verloren gehen. Jede einzelne Methode, die wir hinzufügen, wird es uns ermöglichen, das Prinzip der einzigen Verantwortung für eine einfache Entwicklung und ein logisches Verständnis des Codes zu gewährleisten. Dies führt zu dem unter dem nächsten Punkt beschriebenen Prinzip.

Der dritte Punkt besagt, dass wir sicherstellen, dass Dritte die Logik verstehen können. Die Verwendung separater Dateien für jede Klasse macht es für ein Team von Entwicklern einfacher, gleichzeitig zu arbeiten.

Außerdem werden wir die Vererbung von unserer Klasse RiskManagerAlgo nicht durch den Spezifizierer final einschränken, um ihre weitere Verbesserung durch die Möglichkeit der weiteren Vererbung zu ermöglichen. Dies ermöglicht eine flexible Anpassung unserer Unterklasse an nahezu jedes Handelssystem.

Mit den oben genannten Prinzipien wird unsere Klasse wie folgt aussehen:

//+------------------------------------------------------------------+
//|                       RiskManagerAlgo                            |
//+------------------------------------------------------------------+
class RiskManagerAlgo : public RiskManagerBase
  {
protected:
   CSymbolInfo       r_symbol;                     // instance
   double            slippfits;                    // allowable slippage per trade
   double            spreadfits;                   // allowable spread relative to the opened stop level
   double            riskPerDeal;                  // risk per trade in the deposit currency

public:
                     RiskManagerAlgo(void);        // constructor
                    ~RiskManagerAlgo(void);        // destructor

   //---getters
   bool              GetRiskTradePermission() {return RiskTradePermission;};


   //---interface implementation
   virtual bool      SlippageCheck() override;  // checking the slippage for an open order
   virtual bool      SpreadMonitor(int intSL) override;           // spread control
  };
//+------------------------------------------------------------------+

Zusätzlich zu den Feldern und Methoden der bestehenden Basisklasse RiskManagerBase haben wir in unserer Unterklasse RiskManagerAlgo die folgenden Elemente bereitgestellt, um zusätzliche Funktionalität für algorithmische Expert Advisors (EA) zu bieten. Zunächst benötigen wir einen Getter, um die Daten des geschützten Feldes der abgeleiteten Klasse RiskTradePermission von der Basisklasse RiskManagerBase zu erhalten. Diese Methode wird die primäre Methode sein, um vom Risikomanager die Erlaubnis zu erhalten, neue Positionen algorithmisch im Bereich der Auftragsbedingungen zu eröffnen. Das Funktionsprinzip ist ganz einfach: Wenn diese Variable den Wert „true“ enthält, kann der EA weiterhin Aufträge gemäß den Signalen seiner Handelsstrategie platzieren; ist sie „false“, können keine Aufträge platziert werden, selbst wenn die Handelsstrategie einen neuen Einstiegspunkt anzeigt.

Wir werden auch eine Instanz der Standardklasse CSymbolInfo des MetaTrader 5-Terminals für die Arbeit mit Symbolfeldern bereitstellen. Die Klasse CSymbolInfo bietet einen einfachen Zugang zu den Eigenschaften des Symbols, was es uns auch ermöglicht, den EA-Code visuell zu verkürzen, damit er besser wahrgenommen wird und die weitere Pflege der Klassenfunktionalität erleichtert.

Wir wollen in unserer Klasse zusätzliche Funktionen für Slippage- und Spread-Kontrollbedingungen vorsehen. Im Feld „slippfits“ wird der Kontrollstatus der nutzerdefinierten Slippage-Bedingung gespeichert, und die Größe des Spread (Spanne) wird in der Variablen „spreadfits“ gespeichert. Die dritte erforderliche Variable enthält die Risikogröße pro Handel in der Einzahlungswährung. Es ist anzumerken, dass eine separate Variable speziell zur Kontrolle der Auftragsabweichung deklariert wurde. Beim Intraday-Handel gibt das Handelssystem in der Regel viele Signale. Es besteht also keine Notwendigkeit, sich auf ein Geschäft mit der Risikogröße für den ganzen Tag zu beschränken. Das bedeutet, dass der Händler vor dem Handel im Voraus weiß, welche Signale für welche Symbole er verarbeitet, und dass er das Risiko pro Handel mit dem Risiko pro Tag gleichsetzt, wobei er die Anzahl der Wiedereinstiege in die Position berücksichtigt.

Demnach sollte die Summe aller Risiken für alle Einträge das Tagesrisiko nicht überschreiten. Nun, wenn es nur einen Eintrag pro Tag gibt, dann könnte dies das gesamte Risiko sein. Dies ist jedoch ein seltener Fall, da es normalerweise viel mehr Einträge gibt. Lassen Sie uns den folgenden Code auf globaler Ebene deklarieren. Der Einfachheit halber haben wir ihn zuvor mit dem Schlüsselwort group in einen benannten Block „verpackt“.

input group "RiskManagerAlgoClass"
input double inp_slippfits    = 2.0;  // inp_slippfits - allowable slippage per open deal
input double inp_spreadfits   = 2.0;  // inp_spreadfits - allowable spread relative to the stop level to open
input double inp_risk_per_deal   = 100;  // inp_risk_per_deal - risk per trade in the deposit currency

Mit diesem Eintrag können Sie die Überwachung der offenen Positionen flexibel nach den vom Nutzer festgelegten Bedingungen gestalten.

Im öffentlichen Abschnitt unserer Klasse RiskManagerAlgo deklarieren wir die virtuellen Funktionen unserer Schnittstelle, die wir wie folgt außer Kraft setzen:

//--- implementation of the interface
   virtual bool      SlippageCheck() override;  // checking the slippage for an open order
   virtual bool      SpreadMonitor(int intSL) override;           // spread control

Hier haben wir das Schlüsselwort „virtual“ verwendet, das als Funktionsspezifizierer dient und einen Mechanismus zur dynamischen Auswahl eines geeigneten Mitglieds der Funktionen unserer Basisklasse RiskManagerBase und der abgeleiteten Klasse RiskManagerAlgo zur Laufzeit bietet. Ihr gemeinsamer Elternteil wird unsere rein virtuelle Funktionsschnittstelle sein.

Wir führen die Initialisierung im Konstruktor der RiskManagerAlgo-Unterklasse durch, indem wir die vom Nutzer über die Eingabeparameter eingegebenen Werte in die entsprechenden Felder der Klasseninstanz kopieren:

//+------------------------------------------------------------------+
//|                        RiskManagerAlgo                           |
//+------------------------------------------------------------------+
RiskManagerAlgo::RiskManagerAlgo(void)
  {
   slippfits   = inp_slippfits;           // copy slippage condition
   spreadfits  = inp_spreadfits;          // copy spread condition
   riskPerDeal  = inp_risk_per_deal;      // copy risk per trade condition
  }

An dieser Stelle sei angemerkt, dass die direkte Initialisierung von Klassenfeldern manchmal praktischer sein kann. In diesem Fall macht es jedoch keinen großen Unterschied, sodass wir die Initialisierung durch Kopieren der Einfachheit halber beibehalten. Im Gegenzug können Sie den folgenden Code verwenden:

//+------------------------------------------------------------------+
//|                        RiskManagerAlgo                           |
//+------------------------------------------------------------------+
RiskManagerAlgo::RiskManagerAlgo(void):slippfits(inp_slippfits),
                                       spreadfits(inp_spreadfits),
                                       rispPerDeal(inp_risk_per_deal)
  {

  }

Im Destruktor der Klasse brauchen wir den Speicher nicht „manuell“ aufzuräumen, also lassen wir den Funktionskörper leer:

//+------------------------------------------------------------------+
//|                         ~RiskManagerAlgo                         |
//+------------------------------------------------------------------+
RiskManagerAlgo::~RiskManagerAlgo(void)
  {

  }

Da nun alle erforderlichen Funktionen in der Klasse RiskManagerAlgo deklariert sind, können wir eine Methode zur Implementierung unserer Schnittstelle für die Arbeit mit einem engen Stop-Loss für offene Positionen auswählen.


Schnittstelle für die Arbeit mit einem engen Stop-Loss

Die Programmiersprache mql5 ermöglicht eine flexible Entwicklung und Nutzung der notwendigen Funktionalität in optimalen Implementierungen. Ein Teil dieser Funktionalität wurde aus C++ portiert, ein anderer Teil wurde ergänzt und erweitert, um die Entwicklung zu erleichtern. Zur Implementierung der Funktionalität, die sich auf die Kontrolle von Positionen bezieht, die mit einem engen Stop-Loss eröffnet wurden, benötigen wir ein verallgemeinerndes Objekt, das wir als übergeordnetes Objekt verwenden können, nicht nur für die Vererbung in unserer Risikomanager-Klasse, sondern auch für die Vererbung in anderen EA-Architekturen.

Um einen generischen Datentyp zu deklarieren, der zur Implementierung und Verbindung bestimmter Funktionen erstellt wurde, können wir sowohl abstrakte Klassen im C++-Stil als auch einen separaten Datentyp, wie z. B. eine Schnittstelle, verwenden. 

Die abstrakten Klassen sowie Schnittstellen dienen dazu, verallgemeinerte Einheiten zu schaffen, auf deren Grundlage eine spezifischere abgeleitete Klasse erstellt wird. In unserem Fall handelt es sich um eine Klasse für die Arbeit mit Positionen mit einem engen Stop-Loss. Eine abstrakte Klasse ist eine Klasse, die nur als Basisklasse für eine Unterklasse verwendet werden kann; es ist also nicht möglich, ein Objekt vom Typ einer abstrakten Klasse zu erstellen. Wenn wir diese verallgemeinerte Entität verwenden müssen, wird der Code unserer Klasse wie folgt aussehen:

//+------------------------------------------------------------------+
//|                         CShortStopLoss                           |
//+------------------------------------------------------------------+
class CShortStopLoss
  {
public:
                     CShortStopLoss(void) {};         // the class will be abstract event if at least one function in it is virtual
   virtual          ~CShortStopLoss(void) {};         // the same applies to the destructor

   virtual bool      SlippageCheck()         = NULL;  // checking slippage for the open order
   virtual bool      SpreadMonitor(int intSL)= NULL;  // spread control
  };

Die Programmiersprache MQL5 bietet eine spezielle Datentypschnittstelle zur Generalisierung von Entitäten. Ihre Notation ist viel kompakter und einfacher, daher werden wir diesen Typ verwenden, da es keinen Unterschied in der Funktionalität gibt. In der Tat ist eine Schnittstelle auch eine Klasse, die keine Mitglieder/Felder und keinen Konstruktor und/oder Destruktor haben kann. Alle in der Schnittstelle deklarierten Methoden sind rein virtuell, auch ohne explizite Definition, was ihre Verwendung eleganter und kompakter macht. Eine Implementierung über eine generische Entität wie eine Schnittstelle würde folgendermaßen aussehen:

interface IShortStopLoss
  {
   virtual bool   SlippageCheck();           // checking the slippage for an open order
   virtual bool   SpreadMonitor(int intSL);  // spread control
  };

Nachdem wir uns nun für den Typ der generischen Entität entschieden haben, müssen wir nun alle notwendigen Funktionen der bereits in der Schnittstelle deklarierten Methoden für unsere Unterklasse implementieren.


Slippage-Kontrolle für die Eröffnung von Positionen

Um die Methode SlippageCheck(void) zu implementieren, müssen wir zunächst die Daten für das Symbol des Charts aktualisieren. Dazu verwenden wir die Methode Refresh() unserer Instanz der Klasse CSymbolInfo. Sie aktualisiert alle Felder, die das Symbol für weitere Operationen charakterisieren:

   r_symbol.Refresh();                                                  // update symbol data

Bitte beachten Sie, dass die Methode Refresh() alle Felddaten in der Klasse CSymbolInfo aktualisiert, im Gegensatz zu der ähnlichen Methode derselben Klasse RefreshRates(void), die nur die Daten zu den aktuellen Preisen des angegebenen Symbols aktualisiert. Die Methode Refresh() in dieser Implementierung wird bei jedem Tick aufgerufen, um sicherzustellen, dass bei jeder Iteration unseres EA aktuelle Informationen verwendet werden.

Im Bereich der Variablen der Methode Refresh() werden wir dynamische Variablen benötigen, um die Daten der Eigenschaften der offenen Position zu speichern, wenn wir über alle offenen Positionen iterieren, um den möglichen Slippage beim Öffnen zu berechnen. Die Informationen über offene Stellen werden in der folgenden Form gespeichert:

   double PriceClose = 0,                                               // close price for the order
          PriceStopLoss = 0,                                            // stop loss price for the order
          PriceOpen = 0,                                                // open price for the order
          LotsOrder = 0,                                                // order lot volume
          ProfitCur = 0;                                                // current order profit

   ulong  Ticket = 0;                                                   // order ticket
   string Symbl;                                                        // symbol

Um Daten über den Tick-Wert im Falle eines Verlustes zu erhalten, verwenden wir die Methode TickValueLoss() der Instanz der Klasse CSymbolInfo, die innerhalb unserer Klasse RiskManagerAlgo deklariert ist. Der daraus resultierende Wert gibt an, um wie viel sich der Kontostand ändert, wenn sich der Kurs um einen Mindestpunkt für ein Standard-Lot ändert. Wir werden diesen Wert später verwenden, um den potenziellen Verlust zu den tatsächlich offenen Preisen der Position zu berechnen. Wir verwenden hier den Begriff „potenziell“, da diese Methode bei jedem Tick und unmittelbar nach Eröffnung einer Position funktioniert. Das bedeutet, dass wir sofort beim nächsten empfangenen Tick prüfen können, wie viel wir mit einem Geschäft verlieren können, obwohl der Kurs noch näher am Eröffnungskurs als am Stop-Loss-Kurs liegt. 

   double lot_cost = r_symbol.TickValueLoss();                          // get tick value
   bool ticket_sc = 0;                                                  // variable for successful closing

Hier wird auch eine Variable deklariert, die notwendig ist, um die Ausführung eines Auftrags zur Schließung einer offenen Position zu überprüfen, wenn die Berechnung ergibt, dass die Position aufgrund des Slippage geschlossen werden muss. Dies ist eine boolsche Variable „ticket_sc“.

Jetzt können wir alle offenen Positionen im Rahmen unserer Slippage-Kontrollmethode durchgehen. Wir werden über die offenen Positionen iterieren, indem wir eine for-Schleife organisieren, die durch die Anzahl der offenen Positionen im Terminal begrenzt ist. Um den Wert der Anzahl der offenen Positionen zu erhalten, wird eine vordefinierte Terminalfunktion PositionsTotal() verwendet. Wir werden die Positionen nach Index auswählen, indem wir die Methode SelectByIndex() der Standardterminalklasse CPositionInfo verwenden.

r_position.SelectByIndex(i)

Sobald eine Position ausgewählt wurde, können wir die Eigenschaften dieser Position mit Hilfe der Standardterminalklasse CPositionInfo abfragen. Zunächst müssen wir jedoch prüfen, ob die ausgewählte Position dem Symbol entspricht, auf dem diese EA-Instanz ausgeführt wird. Dies kann mit folgendem Code in der Schleife geschehen:

         Symbl = r_position.Symbol();                                   // get the symbol
         if(Symbl==Symbol())                                            // check if it's the right symbol

Erst wenn wir uns vergewissert haben, dass die durch den Index ausgewählte Position zu unserem Chart gehört, können wir mit der Abfrage anderer Eigenschaften fortfahren, um die offene Position zu überprüfen. Weitere Abfragen von Positionseigenschaften werden auch mit einer Instanz der Standardterminalklasse CPositionInfo wie folgt durchgeführt:

            PriceStopLoss = r_position.StopLoss();                      // remember its stop loss
            PriceOpen = r_position.PriceOpen();                         // remember its open price
            ProfitCur = r_position.Profit();                            // remember financial result
            LotsOrder = r_position.Volume();                            // remember order lot volume
            Ticket = r_position.Ticket();

Bitte beachten Sie, dass die Prüfung nicht nur anhand des Symbols, sondern auch anhand der magischen Zahl in der Struktur von MqlTradeRequest durchgeführt werden kann, die bei der Eröffnung der ausgewählten Position verwendet wird. Dieser Ansatz wird häufig verwendet, um die von verschiedenen Strategien ausgeführten Handelsoperationen innerhalb eines einzigen Kontos zu trennen. Wir werden diesen Ansatz nicht verwenden, da es meiner Meinung nach besser ist, getrennte Konten für getrennte Strategien zu verwenden, um die Analyse zu erleichtern und die Computerressourcen zu nutzen. Wenn Sie andere Methoden anwenden, teilen Sie sie bitte in den Kommentaren zu diesem Artikel mit. Kommen wir nun zur Implementierung unserer Methode.

Unsere Methode umfasst das Schließen von Positionen. Kaufpositionen werden durch einen Verkauf zum Geldkurs (bid) und Verkaufspositionen zum Briefkurs (ask) geschlossen. Wir müssen also die Logik zur Ermittlung des Schlusskurses in Abhängigkeit von der Art der ausgewählten offenen Position implementieren:

            int dir = r_position.Type();                                // define order type

            if(dir == POSITION_TYPE_BUY)                                // if it is Buy
              {
               PriceClose = r_symbol.Bid();                             // close at Bid
              }
            if(dir == POSITION_TYPE_SELL)                               // if it is Sell
              {
               PriceClose = r_symbol.Ask();                             // close at Ask
              }

Auf die Logik der teilweisen Schließung wird hier nicht eingegangen, obwohl das Terminal dies technisch ermöglicht, sofern Ihr Broker dies zulässt. Diese Art von Abschluss ist jedoch nicht in unserer Methodenlogik enthalten.

Nachdem wir uns vergewissert haben, dass die ausgewählte Position den Anforderungen entspricht und alle erforderlichen Merkmale aufweist, können wir mit der Berechnung des tatsächlichen Risikos für diese Position fortfahren. Zunächst muss die Größe des resultierenden Stopp-Levels in Mindestpunkten berechnet werden, wobei der tatsächliche Eröffnungskurs zu berücksichtigen ist, um ihn dann mit dem ursprünglich erwarteten Kurs zu vergleichen.

Wir berechnen die Größe als die absolute Differenz zwischen dem Eröffnungskurs und dem Stop-Loss. Dazu verwenden wir die vordefinierte Terminalfunktion MathAbs(). Um einen ganzzahligen Wert der Punkte aus einem reellen Preiswert zu erhalten, wird der von MathAbs() erhaltene Wert durch den reellen Wert des Punktes geteilt. Um den Wert eines Punktes zu ermitteln, verwenden wir die Methode Point() einer Instanz unserer Standardterminalklasse CPositionInfo.

int curr_sl_ord = (int) NormalizeDouble(MathAbs(PriceStopLoss-PriceOpen)/r_symbol.Point(),0); // check the resulting stop

Um nun den tatsächlichen potenziellen Verlustwert für die ausgewählte Position zu erhalten, müssen wir nur den erhaltenen Stoppwert in Punkten mit der Positionsgröße in Lots und einem Tick für den Wert des Positionssymbols multiplizieren. Dies geschieht wie folgt:

double potentionLossOnDeal = NormalizeDouble(curr_sl_ord * lot_cost * LotsOrder,2); // calculate risk upon reaching the stop level

Lassen Sie uns nun überprüfen, ob der erhaltene Wert innerhalb der vom Nutzer eingegebenen Risikoabweichung für den Handel liegt. Dieser Wert wird in der Variablen „slippfits“ angegeben. Liegt der Wert außerhalb dieses Bereichs, so wird die ausgewählte Position geschlossen:

             if(
                  potentionLossOnDeal>NormalizeDouble(riskPerDeal*slippfits,0) &&   // if the resulting stop exceeds risk per trade given the threshold value
                  ProfitCur<0                                                  &&   // and the order is at a loss
                  PriceStopLoss != 0                                                // if stop loss is not set, don't touch
               )

In diesem Bedingungspaket haben wir zwei weitere Prüfungen hinzugefügt, die die folgende Logik für die Verarbeitung von Handelssituationen enthalten.

Erstens stellt die Bedingung „ProfitCur<0“ sicher, dass der Slippage nur in der Verlustzone einer offenen Position verarbeitet wird. Dies ist auf die folgenden Bedingungen der Handelsstrategie zurückzuführen. Da der Slippage in der Regel bei hoher Marktvolatilität auftritt, wird das Handelsgeschäft mit Slippage in Richtung Take Profit eröffnet, wodurch der Stop erhöht und das Ergebnis durch Take-Profit reduziert werden. Dies verringert das erwartete Risiko/Ertragsverhältnis des Handels und erhöht den potenziellen Verlust im Vergleich zum geplanten Verlust, erhöht aber gleichzeitig die Wahrscheinlichkeit, einen Take-Profit zu erreichen, da das Momentum, durch das unsere Position „abgerutscht“ ist, höchstwahrscheinlich im Moment anhält. Diese Bedingung bedeutet, dass wir die Position nur dann schließen, wenn das Momentum den Slippage verursacht und vor dem Erreichen des Take-Profits gestoppt hat, während wir in die Verlustzone zurückkehren.

Die zweite Bedingung „PriceStopLoss != 0“ ist notwendig, um die folgende Logik zu implementieren: Wenn der Händler keinen Stop-Loss gesetzt hat, schließen wir diese Position NICHT, da das Risiko für diese Position nicht begrenzt ist. Das heißt, wenn Sie eine Position eröffnen, wissen Sie, dass diese Position möglicherweise Ihr gesamtes Risiko für den Tag abdecken kann, wenn der Kurs gegen Sie läuft. Dies birgt ein sehr hohes Risiko, da es möglicherweise nicht genügend Limits für alle Symbole gibt, die Sie für den Tag planen, während diese Symbole potenziell positiv sein und Gewinn bringen könnten. Eine Position ohne Stop-Loss kann diesen Einstieg einfach unmöglich machen. Sie sollten auf der Grundlage Ihrer persönlichen Handelsstrategie selbst entscheiden, ob Sie diese Bedingung einbeziehen wollen oder nicht. In unserer Implementierung werden wir nicht mit mehreren Instrumenten gleichzeitig handeln, daher werden wir keine Positionen ohne Stop-Loss-Niveau löschen.

Wenn alle Bedingungen zur Identifizierung von Slippage bei einer Position erfüllt sind, schließen wir die Position mit der Methode PositionClose() der Standardklasse CTrade, die in unserer Basisklasse RiskManagerBase deklariert ist. Als Eingabeparameter übergeben wir die zuvor gespeicherte Ticketnummer der zu schließenden Position. Das Ergebnis des Aufrufs der Abschlussfunktion wird in der Variablen ticket_sc gespeichert, um die Auftragsausführung zu steuern.

ticket_sc = r_trade.PositionClose(Ticket);                        // close order

Der gesamte Code der Methode lautet wie folgt:

//+------------------------------------------------------------------+
//|                         SlippageCheck                            |
//+------------------------------------------------------------------+
bool RiskManagerAlgo::SlippageCheck(void) override
  {
   r_symbol.Refresh();                                                  // update symbol data

   double PriceClose = 0,                                               // close price for the order
          PriceStopLoss = 0,                                            // stop loss price for the order
          PriceOpen = 0,                                                // open price for the order
          LotsOrder = 0,                                                // order lot volume
          ProfitCur = 0;                                                // current order profit

   ulong  Ticket = 0;                                                   // order ticket
   string Symbl;                                                        // symbol
   double lot_cost = r_symbol.TickValueLoss();                          // get tick value
   bool ticket_sc = 0;                                                  // variable for successful closing

   for(int i = PositionsTotal(); i>=0; i--)                             // start loop through orders
     {
      if(r_position.SelectByIndex(i))
        {
         Symbl = r_position.Symbol();                                   // get the symbol
         if(Symbl==Symbol())                                            // check if it's the right symbol
           {
            PriceStopLoss = r_position.StopLoss();                      // remember its stop loss
            PriceOpen = r_position.PriceOpen();                         // remember its open price
            ProfitCur = r_position.Profit();                            // remember financial result
            LotsOrder = r_position.Volume();                            // remember order lot volume
            Ticket = r_position.Ticket();

            int dir = r_position.Type();                                // define order type

            if(dir == POSITION_TYPE_BUY)                                // if it is Buy
              {
               PriceClose = r_symbol.Bid();                             // close at Bid
              }
            if(dir == POSITION_TYPE_SELL)                               // if it is Sell
              {
               PriceClose = r_symbol.Ask();                             // close at Ask
              }

            if(dir == POSITION_TYPE_BUY || dir == POSITION_TYPE_SELL)
              {
               int curr_sl_ord = (int) NormalizeDouble(MathAbs(PriceStopLoss-PriceOpen)/r_symbol.Point(),0); // check the resulting stop

               double potentionLossOnDeal = NormalizeDouble(curr_sl_ord * lot_cost * LotsOrder,2); // calculate risk upon reaching the stop level

               if(
                  potentionLossOnDeal>NormalizeDouble(riskPerDeal*slippfits,0) &&   // if the resulting stop exceeds risk per trade given the threshold value
                  ProfitCur<0                                                  &&   // and the order is at a loss
                  PriceStopLoss != 0                                                // if stop loss is not set, don't touch
               )
                 {
                  ticket_sc = r_trade.PositionClose(Ticket);                        // close order

                  Print(__FUNCTION__+", RISKPERDEAL: "+DoubleToString(riskPerDeal));                  //
                  Print(__FUNCTION__+", slippfits: "+DoubleToString(slippfits));                      //
                  Print(__FUNCTION__+", potentionLossOnDeal: "+DoubleToString(potentionLossOnDeal));  //
                  Print(__FUNCTION__+", LotsOrder: "+DoubleToString(LotsOrder));                      //
                  Print(__FUNCTION__+", curr_sl_ord: "+IntegerToString(curr_sl_ord));                 //

                  if(!ticket_sc)
                    {
                     Print(__FUNCTION__+", Error Closing Orders №"+IntegerToString(ticket_sc)+" on slippage. Error №"+IntegerToString(GetLastError())); // output to log
                    }
                  else
                    {
                     Print(__FUNCTION__+", Orders №"+IntegerToString(ticket_sc)+" closed by slippage."); // output to log
                    }
                  continue;
                 }
              }
           }
        }
     }
   return(ticket_sc);
  }
//+------------------------------------------------------------------+

Damit ist das Überschreiben der Slippage-Kontrollmethode abgeschlossen. Gehen wir nun zur Beschreibung der Methode zur Kontrolle der Spread-Größe vor der Eröffnung einer neuen Position über.


Spread-Kontrolle für die Eröffnung von Positionen

Die Spread-Kontrolle in unserer Implementierung der SpreadMonitor()-Methode besteht aus einem vorläufigen Vergleich des aktuellen Spreads kurz vor der Eröffnung eines Handels mit einem berechneten/technischen Stop-Loss, der als ganzzahliger Parameter an die Methode übergeben wird. Die Funktion gibt true zurück, wenn der aktuelle Spread innerhalb des vom Nutzer akzeptierten Bereichs liegt. Andernfalls, wenn der Spread diesen Bereich verlassen hat, gibt die Methode false zurück.

Das Ergebnis der Funktion wird in einer logischen bool-Variablen gespeichert, die standardmäßig mit true initialisiert wird:

   bool SpreadAllowed = true;

Den Wert des aktuellen Spreads für das Symbol erhalten wir mit der Methode Spread() der Klasse CSymbolInfo:

   int SpreadCurrent = r_symbol.Spread();

Nachstehend finden Sie den logischen Vergleich, der für diese Prüfung verwendet wird:

if(SpreadCurrent>intSL*spreadfits)

Das heißt, wenn der aktuelle Symbol-Spread größer ist als das Produkt aus dem gewünschten Stop-Loss und dem nutzerdefinierten Koeffizienten, sollte die Methode false zurückgeben, und dies sollte verhindern, dass die Position mit der aktuellen Spread-Größe bis zum nächsten Tick geöffnet wird. Hier ist die Beschreibung der Methode:

//+------------------------------------------------------------------+
//|                          SpreadMonitor                           |
//+------------------------------------------------------------------+
bool RiskManagerAlgo::SpreadMonitor(int intSL)
  {
//--- spread control
   bool SpreadAllowed = true;                                           // allow spread trading and check ratio further
   int SpreadCurrent = r_symbol.Spread();                               // current spread values

   if(SpreadCurrent>intSL*spreadfits)                                   // if the current spread is greater than the stop and the coefficient
     {
      SpreadAllowed = false;                                            // prohibit trading
      Print(__FUNCTION__+IntegerToString(__LINE__)+
            ". Spread is to high! Spread:"+
            IntegerToString(SpreadCurrent)+", SL:"+IntegerToString(intSL));// notify
     }
   return SpreadAllowed;                                                // return result
  }
//+------------------------------------------------------------------+

Wenn Sie mit dieser Methode arbeiten, müssen Sie berücksichtigen, dass der EA keine Positionen eröffnet, wenn die Spread-Bedingung sehr strikt ist, und dass die entsprechenden Informationen ständig im Journal des EAs protokolliert werden. In der Regel wird ein Koeffizient von mindestens 2 verwendet, d. h. wenn der Spread die Hälfte des Stopps beträgt, müssen Sie entweder auf einen kleineren Spread warten oder sich weigern, mit einem so engen Stopp-Loss einzusteigen, denn je näher das Stopp-Niveau an der Größe des Spreads liegt, desto größer ist die Wahrscheinlichkeit, dass Sie mit einer solchen Position einen Verlust erleiden.


Implementierung der Schnittstelle

Die Schnittstelle wird der erste Elternteil unserer Basisklasse sein, da mql5 keine Mehrfachvererbung unterstützt. Aber in diesem Fall ist dies keine Einschränkung für uns, da wir ein konsistentes Vererbungsschema für unser Projekt implementieren können. 

Dazu müssen wir unsere Basisklasse RiskManagerBase um die Vererbung der zuvor beschriebenen Schnittstelle IShortStopLoss ergänzen:

//+------------------------------------------------------------------+
//|                        RiskManagerBase                           |
//+------------------------------------------------------------------+
class RiskManagerBase:IShortStopLoss                        // the purpose of the class is to control risk in terminal

Diese Notation ermöglicht es uns, die erforderliche Funktionalität an die Unterklasse RiskManagerAlgo zu übertragen. In dieser Situation ist die Zugriffsebene der Vererbung nicht wichtig, da unsere Schnittstelle rein virtuelle Funktionen hat und weder Felder noch einen Konstruktor oder Destruktor enthält.

Die endgültige Vererbungsstruktur unserer nutzerdefinierten Klasse RiskManagerAlgo, die die Kapselung der öffentlichen Methoden zur Bereitstellung der vollen Funktionalität zeigt, ist in Abbildung 1 dargestellt.

Abbildung 1. Vererbungshierarchie der Klasse RiskManagerAlgo

Abbildung 1. Vererbungshierarchie der Klasse RiskManagerAlgo

Bevor wir nun unseren Algorithmus zusammenstellen, müssen wir nur noch das Entscheidungsinstrument implementieren, um die beschriebene Funktionalität der algorithmischen Risikokontrolle zu testen.


Implementierung des Handelsblocks

Im vorangegangenen Artikel Risikomanager für den manuellen Handel bestand ein Handelsblock aus einer recht einfachen TradeModel-Entität für die grundlegende Verarbeitung der von Fraktalen erhaltenen Inputs. Da es in diesem Artikel im Gegensatz zum vorherigen um den algorithmischen Handel geht, wollen wir auch eine algorithmische Entscheidungshilfe auf der Grundlage von Fraktalen entwickeln. Es wird auf der gleichen Logik basieren, aber jetzt werden wir einfach alles in Code implementieren, anstatt Signale manuell zu erzeugen. Ein zusätzlicher Bonus ist, dass wir testen können, was in einem größeren Zeitraum historischer Daten passiert, da wir die erforderlichen Eingaben nicht mehr manuell erstellen müssen.

Deklarieren wir die Klasse CFractalsSignal, die für den Empfang von Signalen von Fraktalen zuständig sein wird. Die Logik bleibt dieselbe: Wenn der Kurs das obere Fraktal des Tagescharts durchbricht, kauft der EA; wenn der aktuelle Kurs das untere Fraktal, ebenfalls aus dem Tageschart, durchbricht, erscheint ein Verkaufssignal. Die Handelsgeschäfte werden am Ende des Handelstages auf der Basis des Tages, an dem sie eröffnet wurden, geschlossen.

Unsere Klasse CFractalsSignal wird ein Feld enthalten, das Informationen über den verwendeten Zeitrahmen enthält, anhand dessen wir Fraktalausbruchsmuster analysieren werden. Wir können also zwischen dem Zeitrahmen, in dem Fraktale analysiert werden, und dem Zeitrahmen, in dem der EA läuft, unterscheiden, um die Nutzung zu erleichtern. Deklarieren wir eine Enum-Variable des Typs ENUM_TIMEFRAMES:

ENUM_TIMEFRAMES   TF;                     // timeframe used

Als Nächstes deklarieren wir einen Variablenzeiger auf die Standard-Terminal-Klasse für die Arbeit mit dem technischen Indikator, CiFractals, die praktischerweise alle von uns benötigten Funktionen implementiert, sodass wir sie nicht noch einmal schreiben müssen:

   CiFractals        *cFractals;             // fractals

Wir müssen auch Daten über Signale und deren Verarbeitung durch den EA speichern. Wir werden die nutzerdefinierte Struktur von TradeInputs verwenden, die wir im vorherigen Artikel nutzt haben. Letztes Mal haben wir sie jedoch manuell erstellt, und jetzt wird die Klasse CFractalsSignal sie für uns erzeugen:

//+------------------------------------------------------------------+
//|                         TradeInputs                              |
//+------------------------------------------------------------------+

struct TradeInputs
  {
   string             symbol;                                           // symbol
   ENUM_POSITION_TYPE direction;                                        // direction
   double             price;                                            // price
   datetime           tradedate;                                        // date
   bool               done;                                             // trigger flag
  };

Wir deklarieren die internen Variablen unserer Struktur getrennt für Kauf- und Verkaufssignale, sodass sie gleichzeitig berücksichtigt werden können, da wir nicht im Voraus wissen können, welcher Preis zuerst erreicht wird:

   TradeInputs       fract_Up, fract_Dn;     // current signal

Wir müssen lediglich Variablen deklarieren, die die aktuellen Werte speichern, die wir von der Klasse CiFractals erhalten, um Daten über neu gebildete Fraktale im Tageschart zu erhalten.

Um die notwendige Funktionalität bereitzustellen, benötigen wir mehrere Methoden im öffentlichen Bereich der Klasse CFractalsSignal, die für die Überwachung der letzten aktuellen fraktalen Kursausbruchsmuster, die Ausgabe eines Signals zur Eröffnung von Positionen und die Überwachung des Erfolgs der Verarbeitung dieser Signale verantwortlich sind.

Unsere Methode zur Steuerung der Aktualisierung des Datenzustands der Klasse ist Process(). Sie gibt nichts zurück und nimmt keine Parameter entgegen, sondern führt einfach bei jedem eingehenden Tick eine Aktualisierung des Datenstatus durch. Die Methoden für den Empfang eines Kauf- und Verkaufssignals heißen BuySignal() und SellSignal(). Sie nehmen keine Parameter entgegen, geben aber einen boolschen Wert zurück, wenn es notwendig ist, eine Position in der entsprechenden Richtung zu öffnen. Die Methoden BuyDone() und SellDone() müssen aufgerufen werden, nachdem die Serverantwort des Brokers über die erfolgreiche Eröffnung der entsprechenden Position überprüft wurde. Die Beschreibung unserer Klasse sieht wie folgt aus:

//+------------------------------------------------------------------+
//|                       CFractalsSignal                            |
//+------------------------------------------------------------------+
class CFractalsSignal
  {
protected:
   ENUM_TIMEFRAMES   TF;                     // timeframe used
   CiFractals        *cFractals;             // fractals

   TradeInputs       fract_Up, fract_Dn;     // current signal

   double            FrUp;                   // upper fractals
   double            FrDn;                   // lower fractals

public:
                     CFractalsSignal(void);  // constructor
                    ~CFractalsSignal(void);  // destructor

   void              Process();              // method to start updates

   bool              BuySignal();            // buy signal
   bool              SellSignal();           // sell signal

   void              BuyDone();              // buy done
   void              SellDone();             // sell done
  };

Im Konstruktor der Klasse müssen wir das Feld für den Zeitrahmen TF auf das Tagesintervall PERIOD_D1 initialisieren, da die Niveaus aus dem Tageschart stark genug sind, um uns das nötige Momentum zum Erreichen des Take-Profits zu geben, und gleichzeitig viel häufiger auftreten als die stärkeren Niveaus aus den Wochen- und Monatscharts. Hier können wir jedem die Möglichkeit geben, kleinere Zeitrahmen zu testen, aber wir werden uns auf die täglichen Zeitrahmen konzentrieren. Wir werden auch Instanzen des Klassenobjekts erstellen, um mit dem fraktalen Indikator unserer Klasse zu arbeiten, und standardmäßig alle erforderlichen Felder in der folgenden Reihenfolge initialisieren:

//+------------------------------------------------------------------+
//|                        CFractalsSignal                           |
//+------------------------------------------------------------------+
CFractalsSignal::CFractalsSignal(void)
  {
   TF  =  PERIOD_D1;                                                    // timeframe used

//--- fractal class
   cFractals=new CiFractals();                                          // created fractal instance
   if(CheckPointer(cFractals)==POINTER_INVALID ||                       // if instance not created OR
      !cFractals.Create(Symbol(),TF))                                   // variant not created
      Print("INIT_FAILED");                                             // don't proceed
   cFractals.BufferResize(4);                                           // resize fractal buffer
   cFractals.Refresh();                                                 // update

//---
   FrUp = EMPTY_VALUE;                                                  // leveled upper at start
   FrDn = EMPTY_VALUE;                                                  // leveled lower at start

   fract_Up.done  = true;                                               //
   fract_Up.price = EMPTY_VALUE;                                        //

   fract_Dn.done  = true;                                               //
   fract_Dn.price = EMPTY_VALUE;                                        //
  }

Löschen wir im Destruktor den Speicher des Fraktalindikatorobjekts, das wir im Konstruktor erstellt haben:

//+------------------------------------------------------------------+
//|                        ~CFractalsSignal                          |
//+------------------------------------------------------------------+
CFractalsSignal::~CFractalsSignal(void)
  {
//---
   if(CheckPointer(cFractals)!=POINTER_INVALID)                         // if instance was created,
      delete cFractals;                                                 // delete
  }

In der Datenaktualisierungsmethode rufen wir nur die Methode Refresh() der Klasseninstanz CiFractals auf, um die Daten zu den Fraktalpreisen zu aktualisieren, die sie von einer der Elternklassen CIndicator geerbt hat.

//+------------------------------------------------------------------+
//|                         Process                                  |
//+------------------------------------------------------------------+
void CFractalsSignal::Process(void)
  {
//---
   cFractals.Refresh();                                                 // update fractals
  }

An dieser Stelle möchte ich anmerken, dass es eine Möglichkeit zur weiteren Optimierung dieses Ansatzes gibt, da wir diese Daten nicht unbedingt jeden Tick aktualisieren müssen, da die Ebenen der fraktalen Ausbruchsmuster aus dem Tageschart stammen. Wir könnten zusätzlich eine Ereignismethode für das Auftreten eines neuen Balkens im Tageschart implementieren und diese Daten nur dann aktualisieren, wenn sie ausgelöst wird. Wir werden diese Implementierung jedoch beibehalten, da sie keine große zusätzliche Belastung für das System darstellt und die Implementierung zusätzlicher Funktionen zusätzliche Kosten bei einem eher geringen Leistungsgewinn erfordern würde.

In der Methode BuySignal(void), die ein Kaufsignal öffnet, wird zunächst der letzte aktuelle Ask abgefragt:

   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);                  // Buy price

Als Nächstes wird der aktuelle Wert des oberen Fraktals mit der Methode Upper() der Klasse CiFractals abgefragt, wobei der Index des gewünschten Balkens als Parameter übergeben wird:

   FrUp=cFractals.Upper(3);                                             // request the current value

Wir übergeben dieser Methode den Wert „3“, weil wir nur die vollständig geformten, fraktalen Ausbruchsmuster verwenden werden. Da in Zeitreihen der Puffer von den jüngsten (0) zu den älteren in aufsteigender Richtung gezählt wird, bedeutet der Wert „3“ auf dem Tageschart den Tag vor vorgestern. Damit werden fraktale Ausbruchsmuster ausgeschlossen, die sich zu einem bestimmten Zeitpunkt auf dem Tages-Chart bilden und dann erreicht der Kurs am selben Tag ein neues Hoch/Tief und das fraktale Ausbruchsmuster verschwindet.

Lassen Sie uns nun eine logische Prüfung durchführen, um das aktuelle Fraktalausbruchsmuster zu aktualisieren, wenn sich der Preis des letzten aktuellen Bruchs im Tageschart geändert hat. Wir vergleichen den aktuellen Wert des Fraktalindikators, der oben in der Variable FrUp aktualisiert wurde, mit dem letzten aktuellen Wert des oberen Fraktals, der im Preisfeld unserer nutzerdefinierten Struktur TradeInputs gespeichert ist. Damit das Feld „Preis“ immer den Wert des letzten aktuellen Preises speichert, ohne zurückgesetzt zu werden, wenn keine Daten vom Indikator zurückgegeben werden (wenn kein Ausbruch erkannt wird), fügen wir eine weitere Prüfung auf einen leeren Indikatorwert FrUp != EMPTY_VALUE hinzu. Die Kombination dieser beiden Bedingungen ermöglicht es uns, nur die signifikanten Preis des letzten Fraktals zu aktualisieren (mit Ausnahme der Nullwerte, die EMPTY_VALUE im Indikator entsprechen) und diese Variable nicht mit einem leeren Wert neu zu schreiben. Die Kontrollen sind nachstehend aufgeführt:

   if(FrUp != fract_Up.price           &&                               // if the data has been updated
      FrUp != EMPTY_VALUE)                                              // skip empty value

Am Ende der Methode steht die folgende Logik zur Überprüfung, ob ein Kaufsignal empfangen wurde:

   if(fract_Up.price != EMPTY_VALUE    &&                               // skip zero values
      ask            >= fract_Up.price &&                               // if the buy price is greater than or equal to the fractal
      fract_Up.done  == false)                                          // the signal has not been processed yet
     {
      return true;                                                      // generate a signal to process
     }

In diesem Block wird auch zuerst geprüft, ob die Variable des letzten aktuellen Fraktals fract_Up Null ist — dies geschieht beim ersten Start des EA nach der ersten Initialisierung dieser Variable im Klassenkonstruktor. Die nächste Bedingung überprüft, ob der aktuelle Marktkaufpreis den letzten aktuellen Wert des Fraktals durchbrochen hat: ask >= fract_Up.price. Dies kann als die wichtigste logische Bedingung für diese Methode angesehen werden. Schließlich müssen wir prüfen, ob diese fraktale Ebene bereits für dieses Signal verarbeitet wurde. Der Punkt ist, dass die Signale für Fraktal-Breaks aus dem Tages-Chart kommen, und wenn der aktuelle Markt-Kaufpreis den erforderlichen Wert erreicht hat, müssen wir dieses Signal einmal pro Tag verarbeiten, da unser Handel, obwohl intraday, positionell ist, ohne Positionen zu erhöhen oder gleichzeitig zusätzliche zu eröffnen. Wenn alle drei Bedingungen erfüllt sind, wird unsere Methode „true“ zurückgeben, damit unser EA dieses Signal verarbeiten kann.

Die vollständige Implementierung der Methode mit der oben beschriebenen Logik ist unten dargestellt:

//+------------------------------------------------------------------+
//|                         BuySignal                                |
//+------------------------------------------------------------------+
bool CFractalsSignal::BuySignal(void)
  {
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);                  // Buy price

//--- check fractals update
   FrUp=cFractals.Upper(3);                                             // request the current value

   if(FrUp != fract_Up.price           &&                               // if the data has been updated
      FrUp != EMPTY_VALUE)                                              // skip empty value
     {
      fract_Up.price = FrUp;                                            // process the new fractal
      fract_Up.done = false;                                            // not processed
     }

//--- check the signal
   if(fract_Up.price != EMPTY_VALUE    &&                               // skip zero values
      ask            >= fract_Up.price &&                               // if the buy price is greater than or equal to the fractal
      fract_Up.done  == false)                                          // the signal has not been processed yet
     {
      return true;                                                      // generate a signal to process
     }

   return false;                                                        // otherwise false
  }

Wie bereits erwähnt, sollte die Methode, die ein Kaufsignal erhält, mit der Methode zusammenarbeiten, die die Verarbeitung dieses Signals durch den Server des Brokers überwacht. Die Methode, die aufgerufen wird, wenn ein Kaufsignal verarbeitet wird, ist recht kompakt:

//+------------------------------------------------------------------+
//|                         BuyDone                                  |
//+------------------------------------------------------------------+
void CFractalsSignal::BuyDone(void)
  {
   fract_Up.done = true;                                                // processed
  }

Die Logik ist sehr einfach: Beim Aufruf dieser öffentlichen Methode wird ein Flag für die erfolgreiche Signalverarbeitung in das entsprechende letzte Signal der fract_Up-Strukturinstanz, in das Feld „done“, gesetzt. Dementsprechend wird diese Methode im EA-Hauptcode nur dann aufgerufen, wenn die Prüfung auf erfolgreiche Positionseröffnung durch den Server des Brokers bestanden ist.

Die Logik für die Verkaufsmethode ist ähnlich. Der einzige Unterschied besteht darin, dass wir den Geldkurs (bid) und nicht den Briefkurs (ask) verlangen. Die Bedingung für den aktuellen Preis wird dementsprechend auf „weniger als der Fraktalbruch“ für den Verkauf geprüft.

Im Folgenden wird die entsprechende Methode für das Verkaufssignal beschrieben:

//+------------------------------------------------------------------+
//|                         SellSignal                               |
//+------------------------------------------------------------------+
bool CFractalsSignal::SellSignal(void)
  {
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);                  // bid price

//--- check fractals update
   FrDn=cFractals.Lower(3);                                             // request the current value

   if(FrDn != EMPTY_VALUE        &&                                     // skip empty value
      FrDn != fract_Dn.price)                                           // if the data has been updated
     {
      fract_Dn.price = FrDn;                                            // process the new fractal
      fract_Dn.done = false;                                            // not processed
     }

//--- check the signal
   if(fract_Dn.price != EMPTY_VALUE    &&                               // skip empty value
      bid            <= fract_Dn.price &&                               // if the ask price is less than or equal to the fractal AND
      fract_Dn.done  == false)                                          // signal has not been processed
     {
      return true;                                                      // generate a signal to process
     }

   return false;                                                        // otherwise false
  }

Die Verarbeitung eines Verkaufssignals folgt einer ähnlichen Logik. In diesem Fall wird das Feld „done“ entsprechend der Instanz der Struktur fract_Dn ausgefüllt, die für das letzte aktuelle Fraktal für den Verkauf zuständig ist:

//+------------------------------------------------------------------+
//|                        SellDone                                  |
//+------------------------------------------------------------------+
void CFractalsSignal::SellDone(void)
  {
   fract_Dn.done = true;                                                // processed
  }
//+------------------------------------------------------------------+

Damit ist die Implementierung unserer Methode zur Erzeugung von Eingaben aus täglichen fraktalen Ausbruchsmuster abgeschlossen, und wir können zur allgemeinen Montage des Projekts übergehen.


Zusammenstellen und Testen des Projekts

Wir beginnen mit dem Zusammenbau des Projekts, indem wir alle oben beschriebenen Dateien verbinden, einschließlich des erforderlichen Codes am Anfang der Hauptdatei des Projekts. Dazu verwenden wir den Präprozessor-Befehl #include. Die Dateien <RiskManagerAlgo.mqh>, <TradeModel.mqh> und <CFractalsSignal.mqh> sind unsere nutzerdefinierten Klassen, über die wir in den vorherigen Kapiteln gesprochen haben. Die beiden anderen Notationen <Indicators\BillWilliams.mqh> und <Trade\Trade.mqh> sind Standardterminalklassen für die Arbeit mit Fraktalen bzw. Handelsoperationen.

#include <RiskManagerAlgo.mqh>
#include <Indicators\BillWilliams.mqh>
#include <Trade\Trade.mqh>
#include <TradeModel.mqh>
#include <CFractalsSignal.mqh>

Um die Methode der Slippage-Kontrolle einzurichten, führen wir eine weitere Integer-Variable vom Typ int der Klasse der Eingangsspeicher ein. Der Nutzer gibt den akzeptablen Stoppwert in der Anzahl der Mindestpunkte für das Instrument an:

input group "RiskManagerAlgoExpert"
input int inp_sl_in_int       = 2000;  // inp_sl_in_int - a stop loss level for a separate trade

In detaillierteren vollautomatischen Integrationen oder Implementierungen ist es bei der Verwendung dieser Slippage-Kontrollmethode besser, die Übergabe dieses Parameters nicht durch Nutzereingabeparameter zu implementieren, sondern durch Rückgabe des Stop-Wertes von der Klasse, die für die Festlegung des technischen Stop-Levels verantwortlich ist, oder des berechneten Stops von der Klasse, die mit der Volatilität arbeitet. In dieser Implementierung lassen wir dem Nutzer eine zusätzliche Möglichkeit, diese Einstellung je nach seiner spezifischen Strategie zu ändern.

Nun deklarieren wir die notwendigen Zeiger auf die Klassen Risikomanager, Position und Fraktal:

RiskManagerAlgo *RMA;                                                   // risk manager
CTrade          *cTrade;                                                // trade
CFractalsSignal *cFract;                                                // fractal

Wir werden die Zeiger in der Ereignisbehandlungsfunktion der Initialisierung OnInit() unseres EAs initialisieren:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   RMA = new RiskManagerAlgo();                                         // algorithmic risk manager

//---
   cFract =new CFractalsSignal();                                       // fractal signal

//--- trade class
   cTrade=new CTrade();                                                 // create trade instance
   if(CheckPointer(cTrade)==POINTER_INVALID)                            // if instance not created,
     {
      Print(__FUNCTION__+IntegerToString(__LINE__)+" Error creating object!");   // notify
     }
   cTrade.SetTypeFillingBySymbol(Symbol());                             // fill type for the symbol
   cTrade.SetDeviationInPoints(1000);                                   // deviation
   cTrade.SetExpertMagicNumber(123);                                    // magic number
   cTrade.SetAsyncMode(false);                                          // asynchronous method

//---
   return(INIT_SUCCEEDED);
  }

Beim Einrichten des Objekts CTrade geben wir im Parameter der Methode SetTypeFillingBySymbol() das aktuelle Symbol an, auf dem der EA läuft. Das aktuelle Symbol, auf dem der EA läuft, wird mit der vordefinierten Methode Symbol() zurückgegeben. Für die maximal akzeptable Abweichung vom gewünschten Preis in der Methode SetDeviationInPoints() geben wir einen etwas größeren Wert an. Da dieser Parameter für unsere Studie nicht so wichtig ist, werden wir ihn nicht als Eingabe implementieren, sondern ihn fest einkodieren lassen. Wir registrieren auch statisch die magische Zahl für alle vom EA eröffneten Positionen.

Im Destruktor wird das Objekt gelöscht, wobei der Speicher über den Zeiger freigegeben wird, sofern der Zeiger gültig ist:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   if(CheckPointer(cTrade)!=POINTER_INVALID)                            // if there is an instance,
     {
      delete cTrade;                                                    // delete
     }

//---
   if(CheckPointer(cFract)!=POINTER_INVALID)                            // if an instance is found,
     {
      delete cFract;                                                    // delete
     }

//---
   if(CheckPointer(RMA)!=POINTER_INVALID)                               // if an instance is found,
     {
      delete RMA;                                                       // delete
     }
  }

Beschreiben wir nun den Hauptteil unseres EA am Eintrittspunkt des neuen Tick-Ankunftsereignisses OnTick(). Zunächst müssen wir die wichtigste Ereignisüberwachungsmethode ContoMonitor() der Basisklasse RiskManagerBase, die wir in der Unterklasse nicht überschrieben haben, in der Instanz der Unterklasse wie folgt ausführen:

   RMA.ContoMonitor();                                                  // run the risk manager

Als Nächstes rufen wir die Slippage-Kontrollmethode SlippageCheck() auf, die, wie bereits erwähnt, jeden neuen Tick verarbeitet und prüft, ob das tatsächliche Risiko der offenen Positionen mit dem geplanten Risiko übereinstimmt (relativ zum gesetzten Stop-Loss):

   RMA.SlippageCheck();                                                 // check slippage

Da unser fraktales Entscheidungsfindungsinstrument keine tiefe Implementierung impliziert und eher als Demonstration der Fähigkeiten des Risikomanagers dient, wird es keine Stops setzen, sondern lediglich Positionen am Ende des Handelstages schließen, und daher wird diese Methode alle Transaktionen zulassen, die an sie weitergeleitet werden. Damit diese Methode in Ihrer Implementierung vollständig funktioniert, können Sie Aufträge nur mit einem Stop-Loss-Wert ungleich Null an den Server des Brokers senden.

Als Nächstes müssen wir die Daten des Fraktalausbruchsmuster-Indikators über unsere nutzerdefinierte Klasse CFractalsSignal mit der öffentlichen Methode Process() aktualisieren:

   cFract.Process();                                                    // start the fractal process

Nachdem nun alle Ereignismethoden aller Klassen im Code enthalten sind, können wir zum Block für die Überwachung des Auftretens von Signalen zur Auftragserteilung übergehen. Die Überprüfung von Kauf- und Verkaufssignalen wird auf die gleiche Weise getrennt wie die entsprechenden Methoden unserer CFractalsSignal-Klasse zur Entscheidungsfindung im Handel. Beschreiben wir zunächst die Kaufprüfung anhand der folgenden zwei Bedingungen:

   if(cFract.BuySignal() &&
      RMA.SpreadMonitor(inp_sl_in_int))                                 // if there is a buy signal

Zunächst prüfen wir das Vorhandensein eines Kaufsignals mit der Methode BuySignal() der Instanz der Klasse CFractalsSignal. Wenn es dieses Signal gibt, prüfen wir, ob der Risikomanager bestätigt, dass der Spread mit dem vom Nutzer erlaubten Wert übereinstimmt, und zwar mit der Methode SpreadMonitor(). Als einzigen Parameter der Methode SpreadMonitor() übergeben wir die Nutzereingabe inp_sl_in_int.

Wenn beide beschriebenen Bedingungen erfüllt sind, werden die Aufträge in der folgenden vereinfachten logischen Struktur erteilt:

      if(cTrade.Buy(0.1))                                               // if Buy executed,
        {
         cFract.BuyDone();                                              // the signal has been processed
         Print("Buy has been done");                                    // notify
        }
      else                                                              // if Buy not executed,
        {
         Print("Error: buy");                                           // notify
        }

Wir erteilen einen Auftrag mit der Methode Buy() der Instanz der Klasse CTrade und übergeben als Parameter einen Losgröße von 0,1. Für eine objektivere Bewertung der Tätigkeit des Risikomanagers werden wir diesen Wert nicht ändern, um die Statistiken für den Volumenparameter zu „glätten“. Das bedeutet, dass alle Eingaben in der Statistik der Arbeit unseres EA das gleiche Gewicht haben.

Wenn die Methode Buy() korrekt ausgeführt wurde, d. h. eine positive Antwort vom Broker empfangen und das Geschäft eröffnet wurde, rufen wir sofort die Methode BuyDone() auf, um unsere CFractalsSignal-Klasse zu informieren, dass das Signal erfolgreich verarbeitet wurde und kein weiteres Signal zu diesem Preis erforderlich ist. Wenn eine Kauforder nicht platziert werden konnte, informieren wir den EA darüber im Journal und rufen die Methode der erfolgreichen Signalverarbeitung nicht auf, um einen erneuten Versuch der Eröffnung zu ermöglichen.

Wir werden eine ähnliche Logik für Verkaufsaufträge implementieren, indem wir die Methoden aufrufen, die den Verkäufen in der Codesequenz entsprechen.

Den Block für die Schließung von Aufträgen werden wir am Ende des Handelstages aus dem vorherigen Artikel unverändert übernehmen:

   MqlDateTime time_curr;                                               // current time structure
   TimeCurrent(time_curr);                                              // request current time

   if(time_curr.hour >= 23)                                             // if end of day
     {
      RMA.AllOrdersClose();                                             // close all positions
     }

Die Hauptaufgabe dieses Codes ist es, alle offenen Positionen um 23:00 Uhr zu schließen, entsprechend der letzten bekannten Serverzeit, da wir hier die Logik des Intraday-Handels implementieren, ohne Übernachtpositionen. Die Logik dieses Codes wird im vorangegangenen Artikel ausführlicher beschrieben. Wenn Sie Positionen über Nacht belassen wollen, können Sie diesen Block im EA-Code einfach auskommentieren oder sogar löschen.

Außerdem müssen wir den aktuellen Stand der Risikomanagerdaten über die vordefinierte Terminalfunktion Comment() auf dem Bildschirm anzeigen, indem wir ihr die Methode Message() der Risikomanagerklasse übergeben:

   Comment(RMA.Message());                                              // display the data state in a comment
Nachfolgend finden Sie den Code für die Verarbeitung des neuen Tick-Ereignisses:
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   RMA.ContoMonitor();                                                  // run the risk manager

   RMA.SlippageCheck();                                                 // check slippage

   cFract.Process();                                                    // start the fractal process

   if(cFract.BuySignal() &&
      RMA.SpreadMonitor(inp_sl_in_int))                                 // if there is a buy signal
     {
      if(cTrade.Buy(0.1))                                               // if Buy executed,
        {
         cFract.BuyDone();                                              // the signal has been processed
         Print("Buy has been done");                                    // notify
        }
      else                                                              // if Buy not executed,
        {
         Print("Error: buy");                                           // notify
        }
     }

   if(cFract.SellSignal())                                              // if there is a sell signal
     {
      if(cTrade.Sell(0.1))                                              // if sell executed,
        {
         cFract.SellDone();                                             // the signal has been processed
         Print("Sell has been done");                                   // notify
        }
      else                                                              // if sell failed,
        {
         Print("Error: sell");                                          // notify
        }
     }

   MqlDateTime time_curr;                                               // current time structure
   TimeCurrent(time_curr);                                              // request current time

   if(time_curr.hour >= 23)                                             // if end of day
     {
      RMA.AllOrdersClose();                                             // close all positions
     }

   Comment(RMA.Message());                                              // display the data state in a comment
  }
//+------------------------------------------------------------------+

Jetzt können wir das Projekt erstellen und mit historischen Daten testen. Als Testbeispiel nehmen wir das Paar USDJPY und testen es im Jahr 2023 mit den folgenden Eingaben (siehe. Tabelle 2):

# Einstellung Wert
 1  EA  RiskManagerAlgo.ex5
 2  Symbol  USDJPY
 3  Chart-Zeitrahmen  M15
 4  Zeitspanne  2023.01.01 - 2023.12.31
 5  Vorwärts-Test  NO
 6  Verzögerungen  Keine Verzögerungen, perfekte Leistung
 7  Simulation  Jeder Tick (Every Tick)
 8  Ursprüngliche Einlage  10.000 USD
 9  Hebel  1:100
 10  Optimierung  Langsamer kompletter Algorithmus 

Tabelle 1. Einstellungen des Strategietesters für den RiskManagerAlgo EA

Für die Optimierung im Strategietester stellen wir Parameter nach dem Prinzip des kleinsten Schritts ein, um die Trainingszeit zu reduzieren, aber auch, um die im vorherigen Artikel diskutierte Abhängigkeit nachvollziehen zu können: ob der Risikomanager es erlaubt, die Handelsergebnisse selbst profitabler Strategien zu verbessern. Die Eingangsparameter für die Optimierung sind in Tabelle 2 aufgeführt:

#
Name des Parameters Start Schritt Stop-Zeichen
 1  inp_riskperday  0.1  0.5  1
 2  inp_riskperweek  0.5  0.5  3
 3  inp_riskpermonth  2  1  8
 4  inp_plandayprofit  0.1  0.5  3
 5  dayProfitControl  false  -  true

Tabelle 2. Parameter des Strategieoptimierers für den RiskManagerAlgo EA

Zu den Optimierungsparametern gehören nicht diejenigen Parameter, die nicht direkt von der Wirksamkeit der Strategie abhängen und die Modellierung im Tester nicht beeinflussen. So hängen beispielsweise die inp_slippfits hauptsächlich von der Qualität der Auftragsausführung des Brokers und nicht von unseren Eingaben ab. Der Parameter inp_spreadfits hängt direkt von der Spread-Größe ab, die von vielen Faktoren abhängt, u. a. vom Typ des Brokerkontos, dem Zeitpunkt wichtiger Nachrichtenmeldungen usw. Jede Person kann diese Parameter unabhängig voneinander optimieren, je nachdem, bei welchem Maklerunternehmen sie handelt.

Die Optimierungsergebnisse sind in den Abbildungen 2 und 3 dargestellt.

Abbildung 2. Optimierungsergebnisse des RiskManagerAlgo EA

Abbildung 2. Optimierungsergebnisse des RiskManagerAlgo EA

Die Grafik der Optimierungsergebnisse zeigt, dass die meisten Ergebnisse im Bereich der positiven mathematischen Erwartung liegen. Dies hängt mit der Logik der Strategie zusammen, bei der sich ein großes Volumen an Positionen der Marktteilnehmer auf starke fraktale Ausbruchsmuster konzentriert und dementsprechend das Testen dieser Ausbruchsmuster durch den Preis eine erhöhte Marktaktivität hervorruft, die dem Instrument Schwung verleiht.

Um die These vom Vorhandensein einer Dynamik beim Handel mit fraktalen Ebenen zu bestätigen, können Sie die besten und schlechtesten Iterationen für eine bestimmte Menge mit den oben genannten Parametern vergleichen. Unser Risikomanager ermöglicht es uns, diese Dynamik in Bezug auf die Risiken beim Eingehen einer Position zu standardisieren. Um die Rolle des Risikomanagers bei der Standardisierung des Risikos im Verhältnis zur Marktdynamik besser zu verstehen, betrachten wir die Beziehung zwischen den Parametern für das tägliche Risiko und den geplanten täglichen Gewinn in Abbildung 3.

Abbildung 3. Diagramm des täglichen Risikos gegenüber dem geplanten Tagesgewinn

Abbildung 3. Diagramm des täglichen Risikos gegenüber dem geplanten täglichen Gewinn

Abbildung 3 zeigt einen Knick im täglichen Risikoparameterwert, bei dem die Wirksamkeit der fraktalen Ausbruchsstrategie zunächst zunimmt, wenn dieser Wert steigt, und dann zu sinken beginnt. Dies wird der Extrempunkt (knick in der Funktionslinie) unseres Modells für diese beiden Parameter sein. Das Vorhandensein eines solchen Moments im Modell, in dem ein Anstieg des Risikoparameters pro Tag die Gewinne zu senken beginnt, anstatt sie zu erhöhen, beweist, dass der Marktimpuls im Verhältnis zu den Risiken, die wir in unser Modell aufgenommen haben, kleiner wird. Die Kosten des Risikos sind im Verhältnis zum erwarteten Gewinn deutlich zu hoch. Dieser Knick ist im Diagramm deutlich sichtbar und erfordert keine zusätzlichen mathematischen Berechnungen, wie z. B. die Ableitung von Funktionen.

Um nun wirklich sicher zu gehen, dass es eine Marktdynamik gibt, lassen Sie uns die Parameter der besten und der schlechtesten Iteration der Optimierungsergebnisse unseres EAs getrennt betrachten, um das Risiko-Ertrags-Verhältnis zu bewerten. Wenn der erwartete Gewinn beim Einstieg um ein Vielfaches höher ist als das geplante Risiko, dann hat offensichtlich ein Momentum stattgefunden: eine unidirektionale, nicht rückwärts gerichtete Bewegung des Instruments in eine Richtung. Wenn der Risikowert unseres Niveaus gleich oder größer ist als die Rendite, dann gibt es keine Dynamik mehr.

Der Knick des Risikowertes für den Tag ist natürlich der optimale Punkt für die Optimierung des besten Durchgangs mit den folgenden Parametern, die in Tabelle 3 dargestellt sind, was uns der Optimierer gezeigt hat:

#
Name des Parameters Parameterwert
 1  inp_riskperday  0.6
 2  inp_riskperweek  3
 3  inp_riskpermonth  8
 4  inp_plandayprofit  3.1
 5  dayProfitControl  true
 6  inp_slippfits  2
 7  inp_spreadfits  2
 8  inp_risk_per_deal  100
 9  inp_sl_in_int  2000

Tabelle 3. Parameter des besten Durchgangs des Strategieoptimierers für den RiskManagerAlgo EA

Es zeigt sich, dass der geplante Gewinn von 3,1 fünfmal höher ist als die zu seiner Erreichung erforderlichen Risikokosten von 0,6. Mit anderen Worten: Wir riskieren 0,6 % der Einlage und verdienen 3,1 %. Dies weist eindeutig auf das Vorhandensein einer Preisdynamik auf den täglichen Ebenen der fraktalen Ausbruchsmuster hin, die eine positive mathematische Erwartung ergeben.

Das Diagramm der besten Iteration ist in Abbildung 4 dargestellt.

Abbildung 4. Diagramm der besten Iteration des Strategieoptimierers

Abbildung 4. Der Graph der besten Iteration des Strategieoptimierers

Die Grafik des Einlagenwachstums zeigt, dass die Strategie mit Risikokontrolle eine ziemlich glatte Kurve bildet, bei der jeder neue Rollback nicht den Mindestwert des vorherigen Rollbacks überschreibt. Dies ist das Ergebnis der Nutzung der Risikostandardisierung und unseres Risikomanagers für die Investitionssicherheit. Um die These vom Vorhandensein der Dynamik und der Notwendigkeit, die Risiken in Bezug darauf zu standardisieren, endgültig zu bestätigen, sehen wir uns nun die Ergebnisse des schlechtesten Optimierungslaufs an.

Das Risiko-Ertrags-Verhältnis für den schlechtesten Lauf mit den folgenden Parametern kann anhand der Daten in Tabelle 4 geschätzt werden:

#
Name des Parameters
Parameterwert
 1  inp_riskperday  1.1
 2  inp_riskperweek  0.5
 3  inp_riskpermonth  2
 4  inp_plandayprofit  0.1
 5  dayProfitControl  true
 6  inp_slippfits  2
 7  inp_spreadfits  2
 8  inp_risk_per_deal  100
 9  inp_sl_in_int  2000

Tabelle 4. Parameter des schlechtesten Durchlaufs des Strategieoptimierers für den RiskManagerAlgo EA

Die Daten in Tabelle 4 zeigen, dass die schlechteste Iteration genau in der Ebene von Abbildung 3 liegt, in der wir das Risiko im Verhältnis zum Momentum nicht standardisieren. Das heißt, wir befinden uns in einem Bereich, in dem das maximale Risiko nicht die erforderliche Rendite erbringt, und wir nutzen das potenziell eingegangene Risiko nicht bis zum Maximum aus, während wir einen großen Teil der Einlage für diese Einträge ausgeben.

Das Diagramm der schlechtesten Iteration ist in Abbildung 5 dargestellt:

Abbildung 5. Diagramm der schlechtesten Iteration des Strategieoptimierers

Abbildung 5. Graph der schlechtesten Iteration des Strategieoptimierers

Aus Abbildung 5 geht klar hervor, dass ein unausgewogenes Verhältnis zwischen Risiko und potenzieller Rendite zu großen Rückschlägen auf dem Konto führen kann, sowohl in Bezug auf den Saldo als auch auf die Mittel. Die in diesem Kapitel des Artikels vorgestellten Optimierungsergebnisse lassen den Schluss zu, dass der Einsatz eines Risikomanagers für die Risikokontrolle unerlässlich ist. Es ist unerlässlich, logisch begründete Risiken im Verhältnis zu den Möglichkeiten Ihrer Handelsstrategie auszuwählen. Lassen Sie uns nun zu den allgemeinen Schlussfolgerungen unseres Artikels kommen.


Schlussfolgerung

Auf der Grundlage der in diesem Artikel vorgestellten Materialien, Modelle, Argumente und Berechnungen können die folgenden Schlussfolgerungen gezogen werden. Es reicht nicht aus, eine profitable Anlagestrategie oder einen Algorithmus zu finden. Selbst in diesem Fall können Sie aufgrund der unangemessenen Anwendung von Risiko und Kapital Geld verlieren. Selbst bei einer gewinnbringenden Strategie ist die Einhaltung des Risikomanagements der Schlüssel zu einer effizienten und sicheren Tätigkeit auf den Finanzmärkten. Voraussetzung für effizientes und sicheres, langfristig stabiles Arbeiten ist die Standardisierung des Risikos entsprechend den Möglichkeiten der eingesetzten Strategie. Ich empfehle auch dringend, nicht mit echten Konten zu handeln, wenn der Risikomanager deaktiviert ist und für jede offene Position kein Stop-Loss gesetzt wurde. 

Natürlich können nicht alle Risiken an den Finanzmärkten kontrolliert und minimiert werden, aber sie sollten immer gegen die erwartete Rendite abgewogen werden. Die standardisierten Risiken können Sie jederzeit mit Hilfe eines Risikomanagers kontrollieren. Wie in den vorangegangenen Artikeln empfehle ich auch in diesem die Anwendung der Grundsätze des Geld- und Risikomanagements.

Wenn Sie unsystematisch und ohne Risikokontrolle handeln, können Sie jede gewinnbringende Strategie in eine verlustbringende Strategie verwandeln. Auf der anderen Seite kann manchmal eine verlustreiche Strategie in eine gewinnbringende umgewandelt werden, wenn man ein angemessenes Risikomanagement anwendet. Wenn die in diesem Artikel vorgestellten Materialien dazu beitragen, die Einlagen von mindestens einer Person zu retten, dann war die Arbeit nicht umsonst.

Ich würde mich über Ihr Feedback in den Kommentaren zu diesem Artikel freuen. Ich wünsche Ihnen allen profitable Handelsgeschäfte!


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

Beigefügte Dateien |
IShortStopLoss.mqh (1.21 KB)
RiskManagerAlgo.mq5 (10.53 KB)
RiskManagerAlgo.mqh (16.59 KB)
RiskManagerBase.mqh (61.49 KB)
TradeModel.mqh (12.99 KB)
CFractalsSignal.mqh (13.93 KB)
Entwicklung eines Expertenberaters für mehrere Währungen (Teil 10): Erstellen von Objekten aus einer Zeichenkette Entwicklung eines Expertenberaters für mehrere Währungen (Teil 10): Erstellen von Objekten aus einer Zeichenkette
Der EA-Entwicklungsplan umfasst mehrere Stufen, wobei die Zwischenergebnisse in der Datenbank gespeichert werden. Sie können von dort nur als Zeichenketten oder Zahlen wieder abgerufen werden, nicht als Objekte. Wir brauchen also eine Möglichkeit, die gewünschten Objekte im EA anhand der aus der Datenbank gelesenen Strings neu zu erstellen.
Entwicklung eines Replay Systems (Teil 46): Chart Trade Projekt (V) Entwicklung eines Replay Systems (Teil 46): Chart Trade Projekt (V)
Sind Sie es leid, Zeit mit der Suche nach genau der Datei zu verschwenden, die Ihre Anwendung zum Funktionieren braucht? Wie wäre es, alles in die ausführbare Datei aufzunehmen? Auf diese Weise müssen Sie nicht nach den Dingen suchen. Ich weiß, dass viele Menschen diese Form der Verteilung und Speicherung nutzen, aber es gibt einen viel geeigneteren Weg. Zumindest was die Verteilung von ausführbaren Dateien und deren Speicherung betrifft. Die hier vorgestellte Methode kann sehr nützlich sein, da Sie den MetaTrader 5 selbst als hervorragenden Assistenten verwenden können, ebenso wie MQL5. Außerdem ist es nicht so schwer zu verstehen.
Schildkrötenpanzer-Evolutionsalgorithmus (TSEA) Schildkrötenpanzer-Evolutionsalgorithmus (TSEA)
Dies ist ein einzigartiger Optimierungsalgorithmus, der von der Evolution des Schildkrötenpanzers inspiriert wurde. Der TSEA-Algorithmus emuliert die allmähliche Bildung keratinisierter Hautbereiche, die optimale Lösungen für ein Problem darstellen. Die besten Lösungen werden „härter“ und befinden sich näher an der Außenfläche, während die weniger erfolgreichen Lösungen „weicher“ bleiben und sich im Inneren befinden. Der Algorithmus verwendet eine Gruppierung der Lösungen nach Qualität und Entfernung, wodurch weniger erfolgreiche Optionen erhalten bleiben und Flexibilität und Anpassungsfähigkeit gewährleistet werden.
MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 31): Auswahl der Verlustfunktion MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 31): Auswahl der Verlustfunktion
Die Verlustfunktion ist die wichtigste Kennzahl für Algorithmen des maschinellen Lernens, die eine Rückmeldung für den Trainingsprozess liefert, indem sie angibt, wie gut ein bestimmter Satz von Parametern im Vergleich zum beabsichtigten Ziel funktioniert. Wir untersuchen die verschiedenen Formate dieser Funktion in einer nutzerdefinierten MQL5-Assistenten-Klasse.