English Русский 中文 Español 日本語
preview
Implementierung eines Breakeven-Mechanismus in MQL5 (Teil 2): ATR- und RRR-basierter Breakeven

Implementierung eines Breakeven-Mechanismus in MQL5 (Teil 2): ATR- und RRR-basierter Breakeven

MetaTrader 5Beispiele |
44 0
Niquel Mendoza
Niquel Mendoza


Einführung

Willkommen zum zweiten Teil des Artikels über den Breakeven-Mechanismus. Hier werden wir die Programmierung der verschiedenen Breakeven-Typen fortsetzen, deren Implementierung im ersten Teil unvollständig blieb. Zusätzlich erstellen wir eine Hilfsklasse, die dem Benutzer die Wahl des Breakeven-Modus erleichtert. Zuerst betrachten wir den ATR-basierten Breakeven-Typ, dann den RRR-basierten Breakeven (auf Basis des Chance-Risiko-Verhältnisses) und schließlich eine Klasse zur Auswahl und Verwendung des Breakeven-Mechanismus. Am Ende führen wir eine vergleichende Analyse durch, um zu bestimmen, welcher Breakeven-Typ am besten für den Order Blocks Expert Advisor geeignet ist, der ursprünglich im letzten Artikel der Risikomanagement-Serie entwickelt wurde.


Entwicklung der Klasse CBreakEvenAtr basierend auf dem ATR

Der auf ATR basierende Breakeven-Modus bedeutet, wie bereits im vorherigen Artikel erwähnt, dass weder für den Abstand, bei dem der Breakeven aktiviert wird, noch für die Differenz zwischen dem Breakeven-Preis und dem Eröffnungspreis der Position feste Punkte verwendet werden. Stattdessen werden Multiplikatoren verwendet, die auf den ATR-Wert angewendet werden. Dieser Ansatz bietet eine größere Flexibilität bei der Konfiguration sowohl des Abstands bis zur Breakeven-Aktivierung als auch des Niveaus, bei dem er ausgelöst wird.

Ein hoher ATR-Multiplikator bedeutet in der Regel einen größeren Abstand, sodass der Breakeven seltener ausgelöst wird. Umgekehrt kann ein niedrigerer Wert dazu führen, dass der Breakeven häufiger als üblich ausgelöst wird, was sich negativ auf die Rentabilität auswirken kann, wenn man der Position genügend Spielraum lässt.

Um diesen Ansatz in MQL5 zu implementieren, erstellen wir die Klasse CBreakEvenAtr, die von CBreakEvenBase erbt, der Basisklasse für jeden Breakeven-Typ.

//--- class CBreakEvenAtr
class CBreakEvenAtr : public CBreakEvenBase

Um mit ATR zu arbeiten, benötigen wir ein Handle, das es uns ermöglicht, Indikatordaten zu erhalten, das heißt, dessen Werte zu kopieren. Zusätzlich verwenden wir ein Array vom Typ double mit dem Namen atr_buff, in dem die vom Indikator kopierten Daten gespeichert werden.

Wir benötigen außerdem eine Ganzzahlvariable atr_idx, die den Index angibt, ab dem Daten kopiert werden; ein Wert von 0 bedeutet beispielsweise, dass das Kopieren ab dem aktuellen ATR-Wert erfolgt. Bei jedem Zugriff wird genau ein Pufferelement kopiert.

Als Nächstes benötigen wir zwei Multiplikatoren: einen für den Abstand, in dem das Breakeven-Level gesetzt wird, und den anderen für den Abstand, in dem es aktiviert wird.

Private Variablen

private:
  int                atr_handle;
  double             atr_buff[];
  int                atr_idx;
  double             atr_multiplier_be;
  double             atr_multiplier_extra_be;

Konstruktor und Destruktor

Im Konstruktor initialisieren wir die privaten Variablen mit Standardwerten. Zusätzlich bereiten wir das Array atr_buff vor, das den ATR-Wert speichern wird. Wir geben außerdem an, dass die Klasse fünf Parameter erfordert, sodass die Variable num_params den entsprechenden Wert erhält.

Der Konstruktor nimmt drei Parameter entgegen, die zur Initialisierung an die Basisklasse CBreakEvenBase übergeben werden.

  CBreakEvenAtr(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
    :                CBreakEvenBase(symbol_, magic_, mode_), atr_handle(INVALID_HANDLE), atr_idx(0), atr_multiplier_be(1.0), atr_multiplier_extra_be(1.0)
   {                 ArraySetAsSeries(atr_buff, true); this.num_params = 5; }

Im Destruktor wird das Handle des ATR-Indikators freigegeben, ebenso wie das Array, das die ATR-Werte speichert:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CBreakEvenAtr::~CBreakEvenAtr(void)
 {
  ArrayFree(atr_buff);
  if(this.atr_handle != INVALID_HANDLE)
    IndicatorRelease(this.atr_handle);
 }

Funktionen zur Einstellung von Parametern

Um die Definition und Deklaration der auf ATR basierenden Breakeven-Klasse fortzusetzen, ist es an der Zeit, Methoden zur Einstellung der internen Variablen zu definieren. Zuerst definieren wir die SetSimple-Funktionen. Sie werden verwendet, um die von der Klasse benötigten Parameter ohne die Verwendung von MqlParams festzulegen, was ideal ist, wenn bereits bekannt ist, dass der Breakeven-Modus immer auf ATR basiert. Es werden zwei Versionen dieser Funktionen bereitgestellt:

  void               SetSimple(int atr_handle_, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_);
  void               SetSimple(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe);

In der ersten SetSimple-Funktion prüfen wir zunächst, ob Parameter wie die Multiplikatoren gültig sind.

  if(atr_multiplier_extra_be_ >= atr_multiplier_be_)
   {
    printf("%s: Error | The multiplier of the atr to calculate the price be is greater than or equal to the multiplier to set the be", __FUNCSIG__);
    ExpertRemove();
   }

Wir prüfen, ob der Multiplikator, bei dem der Breakeven aktiviert wird, kleiner ist als der Multiplikator, bei dem der Breakeven gesetzt wird. Dies ist notwendig, um Fehler wie ungültige Stop-Level zu vermeiden.

Anschließend stellen wir sicher, dass der ATR-Index immer größer oder gleich null ist.

  if(atr_idx_ < 0)
   {
    printf("%s: Critical error | Atr index is less than 0", __FUNCTION__);
    ExpertRemove();
   }

Abschließend stellen wir sicher, dass das ATR-Indikator-Handle nicht ungültig ist; falls dies der Fall ist, wird der Expert Advisor aus dem Chart entfernt.

  if(atr_handle_ == INVALID_HANDLE)
   {
    printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError());
    ExpertRemove();
   }

Nachdem alle Prüfungen durchgeführt wurden, weisen wir die Parameter den internen Variablen der Klasse zu.

  this.atr_idx = atr_idx_;
  this.atr_multiplier_be = atr_multiplier_be_;
  this.atr_multiplier_extra_be = atr_multiplier_extra_be_;
  this.atr_handle = atr_handle_;

Vollständiger Code

//+------------------------------------------------------------------------------------+
//| Function to set the values ​​of the CBreakEvenAtr class without using MqlParams      |
//| Using the handle instead of period and timeframe                                   |
//+------------------------------------------------------------------------------------+
void CBreakEvenAtr::SetSimple(int atr_handle_, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_)
 {
  if(atr_multiplier_extra_be_ >= atr_multiplier_be_)
   {
    printf("%s: Error | The multiplier of the atr to calculate the price be is greater than or equal to the multiplier to set the be", __FUNCSIG__);
    ExpertRemove();
   }

  if(atr_idx_ < 0)
   {
    printf("%s: Critical error | Atr index is less than 0", __FUNCTION__);
    ExpertRemove();
   }

  if(atr_handle_ == INVALID_HANDLE)
   {
    printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError());
    ExpertRemove();
   }

  this.atr_idx = atr_idx_;
  this.atr_multiplier_be = atr_multiplier_be_;
  this.atr_multiplier_extra_be = atr_multiplier_extra_be_;
  this.atr_handle = atr_handle_;
 }

Die zweite SetSimple-Funktion erfordert einen zusätzlichen Parameter. Anstatt ein Handle zu empfangen, fordert die Funktion die ATR-Periode an, da wir das Handle innerhalb dieser Funktion konfigurieren werden. Dann wird die erste SetSimple-Funktion aufgerufen und das erstellte Handle übergeben.

//+------------------------------------------------------------------------------------+
//| Function to set the values ​​of the CBreakEvenAtr class without using MqlParams      |
//| Configuring the handle with the period and timeframe.                              |
//+------------------------------------------------------------------------------------+
void CBreakEvenAtr::SetSimple(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe)
 {
  TesterHideIndicators(true);
  ResetLastError();
  this.atr_handle = iATR(this.symbol, timeframe, atr_period);
  TesterHideIndicators(false);
  
  SetSimple(atr_handle,atr_idx_,atr_multiplier_extra_be_,atr_multiplier_be_);
 }

Nun definieren wir die Set-Funktion, die von der Basisklasse des Breakeven-Mechanismus erben wird. Diese Funktion wird in der Auswahlklasse CBreakEven verwendet. Wie ich im vorherigen Artikel erwähnt habe, können wir die SetSimple-Funktion in der Basisklasse nicht überschreiben, da nicht bekannt ist, welche Parameter sie empfangen wird. Deshalb ermöglicht uns die Set-Funktion die Arbeit mit dynamischen Parametern.

Da wir für die Set-Funktion zwei Versionen von SetSimple mit unterschiedlicher Anzahl erforderlicher Parameter haben, verwenden wir die Größe des größten Parametersatzes, um die korrekte Eingabegröße zu überprüfen. Um zwischen Fällen zu unterscheiden, in denen Parameter mit einem bereits erstellten Handle übergeben werden, und Fällen ohne ein solches, erstellen wir ein Define namens HANDLE_INSTEAD_OF_PERIOD mit einem Standardwert von 0.

#define HANDLE_INSTEAD_OF_PERIOD 0

Dieses Define befindet sich am letzten Index des params-Arrays. Wenn sein Wert 0 ist, gehen wir davon aus, dass die Konfiguration mit einem bereits erstellten Handle durchgeführt wird; andernfalls wird die SetSimple-Funktion ohne vordefiniertes Handle verwendet.

Entsprechend sieht die Set-Funktion wie folgt aus:

//+------------------------------------------------------------------+
//| Function for setting CBreakEvenAtr variables with MqlParams |
//+------------------------------------------------------------------+
void CBreakEvenAtr::Set(MqlParam &params[])
{
  // Check that params has the correct size; ATR requires 5 parameters
  if(params.Size() < 5)
  {
    printf("%s: Critical error | The size of the MqlParams array %I32u is less than 5", __FUNCTION__, params.Size());
    ExpertRemove();
    return;
  }

  // If the last parameter is HANDLE_INSTEAD_OF_PERIOD, use SetSimple with a handle
  if(params[4].integer_value == HANDLE_INSTEAD_OF_PERIOD)
  {
    SetSimple(
      (int)params[3].integer_value,   // atr_handle
      (int)params[0].integer_value,   // atr_idx
      params[2].double_value,         // atr_multiplier_extra_be
      params[1].double_value          // atr_multiplier_be
    );
  }
  else
  {
    // Otherwise, use SetSimple with period and timeframe
    SetSimple(
      (int)params[4].integer_value,   // atr_period
      (int)params[0].integer_value,   // atr_idx
      params[2].double_value,         // atr_multiplier_extra_be
      params[1].double_value,         // atr_multiplier_be
      (ENUM_TIMEFRAMES)params[3].integer_value // timeframe
    );
  }
}

Funktion zur Implementierung des Breakeven

Um den ATR-basierten Breakeven-Modus zu implementieren, überschreiben wir die .Add-Funktion. Sie wird jedes Mal ausgeführt, wenn eine neue Position eröffnet wird; das heißt, sie wird innerhalb der OnTradeTransactionEvent-Funktion der Basisklasse aufgerufen. Im Hauptteil der .Add-Methode müssen wir den Breakeven-Preis und den Trigger-Preis bestimmen, der den Breakeven-Mechanismus aktiviert.

Um die erforderlichen Preise zu berechnen, wie z. B. breakeven_price und price_to_beat (den Trigger-Preis, der den Breakeven aktiviert und den Stop-Loss der Position modifiziert), verwenden wir eine Formel, die derjenigen ähnelt, die in der Fixed-Points-Breakeven-Klasse verwendet wird.

  • Kaufpositionen:

break_even_price = open_price + (atr_buff[0] * atr_multiplier_extra_be)

  • Verkaufspositionen:

break_even_price = open_price - (atr_buff[0]* atr_multiplier_extra_be)

In dieser Formel verwenden wir atr_multiplier_extra_be anstelle von extra_points_be, um den Breakeven-Preis zu berechnen. Für den Trigger-Preis,  price_to_beat , ist die Formel dieselbe, außer dass wir anstelle von atr_multiplier_extra_be die Variable atr_multiplier_be verwenden.

Nachdem die zuzuweisenden Werte bestimmt wurden, definieren wir die Funktion .Add. Zuerst kopieren wir ATR-Daten beginnend bei this.atr_idx und kopieren nur ein Element. Wir prüfen, ob der von CopyBuffer zurückgegebene Wert nicht kleiner als 1 ist; andernfalls geben wir false zurück, was darauf hinweist, dass das Ticket nicht zum Breakeven-Array hinzugefügt werden konnte.

  ResetLastError();

  if(CopyBuffer(this.atr_handle, 0, this.atr_idx, 1, this.atr_buff) < 1)
   {
    printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError());
    return false;
   }

Dann erstellen wir die Struktur new_pos, die wichtige Informationen für die Funktion BreakEven() enthält. Wir weisen ihren Mitgliedern die erforderlichen Werte zu: den Breakeven-Preis, den Trigger-Preis (unter Verwendung der obigen Formeln), das Ticket und den Positionstyp (Kauf oder Verkauf).

  position_be new_pos;
  new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_extra_be) : open_price - (atr_buff[0] * atr_multiplier_extra_be);
  new_pos.type =  position_type;
  new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_be) : open_price - (atr_buff[0] * atr_multiplier_be);
  new_pos.ticket = post_ticket;
  ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos);

Vollständiger Code der Funktion .Add

//+------------------------------------------------------------------+
//| Function to add an element to the positions array                |
//+------------------------------------------------------------------+
bool CBreakEvenAtr::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type)
 {
  ResetLastError();

  if(CopyBuffer(this.atr_handle, 0, this.atr_idx, 1, this.atr_buff) < 1)
   {
    printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError());
    return false;
   }

  position_be new_pos;
  new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_extra_be) : open_price - (atr_buff[0] * atr_multiplier_extra_be);
  new_pos.type =  position_type;
  new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_be) : open_price - (atr_buff[0] * atr_multiplier_be);
  new_pos.ticket = post_ticket;
  ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos);
  return true;
 }


Grundlagen und Design der RRR-basierten Klasse CBreakEvenRR

Der Breakeven-Modus auf Basis von RRR (Chance-Risiko-Verhältnis) ist neben ATR eine weitere dynamische Variante des Breakevens. Kurz gesagt besteht er darin, den Stop-Loss in einen bestimmten Abstand zum Eröffnungspreis zu verschieben, sobald die Position ein festgelegtes Niveau über dem Stop-Loss erreicht. Ein Verhältniswert von 1 bedeutet beispielsweise, dass der Breakeven aktiviert wird, wenn der aktuelle Preis größer oder gleich dem Eröffnungspreis zuzüglich des Stop-Loss-Abstands in Punkten wird.

Zuerst werde ich einige Änderungen vornehmen. Im vorherigen Artikel wurde der Stop-Loss um eine feste Anzahl von Punkten vom Eröffnungspreis verschoben. In diesem neuen Teil werde ich die Möglichkeit hinzufügen, diese Bewegung auf Basis von ATR durchzuführen, um den RRR-basierten Breakeven noch dynamischer zu gestalten.

Fangen wir jetzt mit dem Programmieren an.

Wie ich bereits angemerkt habe, erfordert das Hinzufügen von ATR als Option zur Berechnung des Breakeven-Preises die Erstellung einer Enumeration, die es uns ermöglicht, zu wählen, wie der Breakeven-Preis berechnet wird: entweder durch feste Punkte oder durch den ATR-Wert multipliziert mit dem entsprechenden Multiplikator.

enum ENUM_TYPE_EXTRA_BE_BY_RRR
 {
  EXTRA_BE_RRR_BY_ATR,         //By Atr
  EXTRA_BE_RRR_BY_FIXED_POINTS //By Fixed Points
 };

Die Klasse wird öffentlich von der Basisklasse CBreakEvenBase abgeleitet.

class CBreakEvenRR : public CBreakEvenBase
 {
private:

Beginnen wir mit der Definition der Hauptvariablen. Wir benötigen eine Variable, die den Koeffizienten speichert, bei dem der Breakeven-Mechanismus aktiviert wird.

double             coefficient_rr; //Coefficient of rr

Wir müssen zudem den Berechnungstyp für den Breakeven-Preis speichern.

ENUM_TYPE_EXTRA_BE_BY_RRR type;

Als Nächstes folgt eine Variable, die je nach gewähltem Modus entweder den ATR-Multiplikator oder den bereits mit point_value multiplizierten Wert speichert:

  double             extra_value_be; //Extra value that will be added to the opening price of the position to obtain the breakeven price
  //Note: if the type is atr this will contain the atr multiplier, if not it will contain the value already multiplied by the point value

Um ATR zu integrieren, benötigen wir drei erforderliche Variablen: ein Handle, ein Array zum Speichern von Werten und eine Ganzzahlvariable zum Speichern des Index, ab dem das Kopieren der Daten beginnt.

  int                handle_atr;
  int                idx_atr;
  double             atr_buff[];

Konstruktor

Der Klassenkonstruktor nimmt dieselben drei Parameter entgegen wie die Basisklasse.

CBreakEvenRR(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_);

Innerhalb des Konstruktorrumpfs initialisieren wir die internen Klassenvariablen und rufen zudem den Basisklassenkonstruktor auf.

//+------------------------------------------------------------------+
//| Constructor                                                       |
//+------------------------------------------------------------------+
void CBreakEvenRR::CBreakEvenRR(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
  : CBreakEvenBase(symbol_, magic_, mode_),
    handle_atr(INVALID_HANDLE),
    idx_atr(0),
    coefficient_rr(1.0),
    extra_value_be(100.0),
    type(EXTRA_BE_RRR_BY_FIXED_POINTS)
 {
  ArraySetAsSeries(atr_buff, true);
  this.num_params = 6;
 }

Destructor

Im Destruktor werden das Array, das die ATR-Daten speichert, sowie das Handle des ATR-Indikators freigegeben.

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CBreakEvenRR::~CBreakEvenRR(void)
 {
  ArrayFree(this.atr_buff);
  if(handle_atr != INVALID_HANDLE)
    IndicatorRelease(this.handle_atr);
 }

Funktionen zur Einstellung von Parametern

In den Funktionen zum Festlegen der Klassenparameter werden wir zwei weitere Funktionen mit einer Logik erstellen, die der der Klasse für den ATR-basierten Breakeven ähnelt. Eine davon nimmt ein Handle entgegen, die andere nimmt Parameter wie Zeitrahmen und ATR-Periode entgegen, um das Handle direkt innerhalb der Funktion zu erstellen.

Definieren wir zuerst die Funktion, die ein Handle entgegennimmt.

Zu Beginn prüfen wir, ob keine ungültigen Variablenwerte vorliegen und ob der im Typ type_extra gespeicherte Breakeven-Typ gültig ist.

//+------------------------------------------------------------------+
//| Function to set break even values ​​by rr without MqlParams        |
//+------------------------------------------------------------------+
void CBreakEvenRR::SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_)
 {
  if(coefficient_rr <= 0.00)
   {
    printf("%s: Critical Error | The %+f coefficient of 'reward' is invalid", __FUNCTION__, coefficient_rr);
    ExpertRemove();
    return;
   }

  if(atr_multiplier_or_extra_points <= 0.00)
   {
    printf("%s: Error | The ATR multiplier or extra points value %f is less than or equal to 0", __FUNCTION__, atr_multiplier_or_extra_points);
    ExpertRemove();
    return;
   }

  if(type_extra != EXTRA_BE_RRR_BY_ATR && type_extra != EXTRA_BE_RRR_BY_FIXED_POINTS)
   {
    printf("%s: Critical error | The extra value type %s is invalid", __FUNCTION__, EnumToString(type_extra));
    ExpertRemove();
    return;
   }

Falls eine Prüfung fehlschlägt, entfernen wir den Expert Advisor vom aktuellen Chart und geben eine Fehlermeldung aus.

Dann weisen wir den internen Variablen die entsprechenden Werte zu:

 this.type = type_extra;
 this.coefficient_rr = rr_a_put_the_break_even;

Wenn die Variable type_extra den Wert EXTRA_BE_RRR_BY_ATR hat, prüfen wir den ATR-Index.

  if(type_extra == EXTRA_BE_RRR_BY_ATR)
   {
    if(idx_atr_ < 0)
     {
      printf("%s: Error | The ATR index %I32d is invalid", __FUNCTION__, idx_atr_);
      ExpertRemove();
      return;
     }
    else
      this.idx_atr = idx_atr_;

Wenn die Prüfung erfolgreich ist, weisen wir der internen Variable idx_atr den Wert des Parameters idx_atr_ zu.

Als Nächstes prüfen wir, ob das Handle gültig ist; falls nicht, entfernen wir den Expert Advisor. Wenn die Prüfung erfolgreich ist, weisen wir den Parameterwert atr_handle_ der internen Variablen handle_atr zu. Zusätzlich wird der Variablen extra_value_be der Wert atr_multiplier_or_extra_points zugewiesen.

    if(atr_handle_  == INVALID_HANDLE)
     {
      printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError());
      ExpertRemove();
      return;
     }
    else
      this.handle_atr = atr_handle_;

    this.extra_value_be = atr_multiplier_or_extra_points;
  }
  else
    this.extra_value_be =  atr_multiplier_or_extra_points  * this.point_value;

Nun gehen wir zur zweiten Funktion über, die Parameter für die Erstellung des ATR-Handles entgegennimmt.

  void               SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_);

Zuerst prüfen wir, ob die Parameterwerte type_extra und atr_period_ gültig sind.

//+------------------------------------------------------------------+
//| Function to set break even values ​​by rr without MqlParams        |
//+------------------------------------------------------------------+
void CBreakEvenRR::SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_)
 {
  if(type_extra != EXTRA_BE_RRR_BY_ATR && type_extra != EXTRA_BE_RRR_BY_FIXED_POINTS)
   {
    printf("%s: Critical error | The extra value type %s is invalid", __FUNCTION__, EnumToString(type_extra));
    ExpertRemove();
    return;
   }

  if(atr_period_ < 0)
   {
    printf("%s: Error | The ATR period %I32d is invalid", __FUNCTION__, atr_period_);
    ExpertRemove();
    return;
   }

Wenn einer der Werte ungültig ist, entfernen wir den Expert Advisor vom aktuellen Chart.

Wenn type_extra den Wert EXTRA_BE_RRR_BY_ATR hat, erstellen wir das ATR-Indikator-Handle unter Verwendung der Parameter tf_atr und atr_period_.

  if(type_extra == EXTRA_BE_RRR_BY_ATR)
   {
    TesterHideIndicators(true);
    ResetLastError();
    this.handle_atr = iATR(this.symbol, tf_atr, atr_period_);
    TesterHideIndicators(false);
   }

Abschließend rufen wir die erste SetSimple-Funktion auf.

  SetSimple(rr_a_put_the_break_even, type_extra, atr_multiplier_or_extra_points, idx_atr_, this.handle_atr);
 }

Nun überschreiben wir die .Set()-Funktion, die zum Festlegen der Klassenparameter verwendet wird und von der Breakeven-Basisklasse geerbt wurde.

  void               Set(MqlParam &params[]) override;

Wie bei der Set-Funktion prüfen wir zuerst, ob die Größe des params-Arrays nicht kleiner ist als die Anzahl der von dieser Klasse benötigten Parameter.

//+------------------------------------------------------------------+
//| Function to set break even values ​​by rr with MqlParams           |
//+------------------------------------------------------------------+
void CBreakEvenRR::Set(MqlParam &params[])
 {
  if((int)params.Size() < num_params)
   {
    printf("%s: Error | The size of the MqlParams array %I32u to set the be by rr is less than 2", __FUNCTION__, params.Size());
    ExpertRemove();
    return;
   }

Wenn die Prüfung fehlschlägt, entfernen wir den Expert Advisor.

Um das params-Array zu verwenden und die internen Werte der Klasse festzulegen, wird das Define HANDLE_INSTEAD_OF_PERIOD angewendet. Dieses Define gibt an, ob die Parameter im params-Array bereits ein fertiges Handle enthalten. Wenn dies der Fall ist, wird die erste SetSimple-Funktion verwendet; andernfalls wird die zweite SetSimple-Funktion aufgerufen, die das Handle erstellt.

  if(params[5].integer_value == HANDLE_INSTEAD_OF_PERIOD)
   {
    //->  (0)double rr_a_put_the_break_even,(1) ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, (2)double atr_multiplier_or_extra_points, (3)int idx_atr_, (4)int atr_handle_
    SetSimple(params[0].double_value, (ENUM_TYPE_EXTRA_BE_BY_RRR)params[1].integer_value, params[2].double_value, (int)params[3].integer_value, (int)params[4].integer_value);
   }
  else
   {
    //->  (0)double rr_a_put_the_break_even,(1) ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, (2)double atr_multiplier_or_extra_points, (3)int idx_atr_, (4)ENUM_TIMEFRAMES tf_atr, (5)int atr_period_
    SetSimple(params[0].double_value, (ENUM_TYPE_EXTRA_BE_BY_RRR)params[1].integer_value, params[2].double_value, (int)params[3].integer_value, (ENUM_TIMEFRAMES)params[4].integer_value, (int)params[5].integer_value);
   }
 }

Die .Add-Funktion

Innerhalb der Add-Funktion beginnen wir mit der Einstellung der Werte, die für die CBreakEvenBase-Klasse für die korrekte Ausführung der CBreakEven-Funktion erforderlich sind, welche für die Anwendung des Breakeven-Mechanismus auf offene Positionen zuständig ist.

Zuerst prüfen wir, ob für die Position ein Stop Loss festgelegt ist:

//+------------------------------------------------------------------+
//| Function to add an element to the positions array                |
//+------------------------------------------------------------------+
bool CBreakEvenRR::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type)
 {
  if(sl_price <= 0.00)
   {
    printf("%s: Error | Position %I64u with stop loss %+f has sl less than 0", __FUNCTION__, post_ticket, sl_price);
    return false;
   }

Wenn kein Stop Loss vorhanden ist, wird false zurückgegeben und die Position wird nicht zum PositionsBe-Array hinzugefügt.

Als Nächstes erstellen wir eine Variable vom Typ double namens val, die den Abstand vom Eröffnungspreis speichert, bei dem das Stop-Loss gesetzt wird. Wenn die Variable type den Wert EXTRA_BE_RRR_BY_ATR hat, kopieren wir ATR-Daten beginnend bei der internen Variablen idx_atr, wobei nur ein Element kopiert wird. Wenn weniger als ein Element kopiert wird, geben wir false zurück und geben eine Fehlermeldung aus. Andernfalls multiplizieren wir val mit dem Wert an Index 0 des atr_buff-Arrays.

 double val = this.extra_value_be;

  if(type == EXTRA_BE_RRR_BY_ATR)
   {
    ResetLastError();
    if(CopyBuffer(this.handle_atr, 0, this.idx_atr, 1, this.atr_buff) < 1)
     {
      printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError());
      return false;
     }
    else
      val *= this.atr_buff[0];
   }

Als Nächstes prüfen wir, ob der Abstand, bei dem der Stop Loss gesetzt wird, gültig ist, das heißt, kleiner als der Abstand, bei dem der Breakeven-Mechanismus aktiviert wird, um Fehler durch ungültige Stops zu vermeiden.

  double diff = MathAbs(open_price - sl_price);

  if((diff * coefficient_rr) <= val)
   {
    printf("%s: Error | The distance from the opening price %f where the stoploss is located is greater than or equal to the price to trigger the breakeven", __FUNCTION__, this.extra_value_be);
    return false;
   }

Wenn schließlich alle Prüfungen erfolgreich sind, berechnen wir den neuen Stop-Loss-Preiswert, breakeven_price, sowie den Trigger-Preis, der den Breakeven-Modus aktiviert, price_to_beat, unter Verwendung der folgenden Formeln:

Für den neuen Stop Loss (breakeven_price):

  • Kaufpositionen:

break_even_price = open_price + val

  • Verkaufspositionen:

break_even_price = open_price - val

Für den Breakeven-Aktivierungspreis, price_to_beat:

  • Kaufpositionen:

price_to_beat = open_price + (coefficient_rr * diff)

  • Verkaufspositionen:

price_to_beat = open_price - (coefficient_rr * diff)

Wir erstellen die Struktur position_be und weisen ihr die erforderlichen Werte zu: Positionstyp, Ticket und die beiden Preisniveaus (breakeven_price und price_to_beat).

Danach fügen wir diese Struktur dem PositionsBe-Array hinzu.

  position_be new_pos;
  new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + val : open_price - val;
  new_pos.type =  position_type;
  new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (coefficient_rr * diff) : open_price - (coefficient_rr * diff);
  new_pos.ticket = post_ticket;

  ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos);
  return true;
 }


Die Klasse CBreakEven: Auswahl und dynamische Konfiguration des Breakeven-Modus

Um den zu verwendenden Breakeven-Modus effizient auszuwählen, erstellen wir eine Auswahlklasse. Sie ist verantwortlich für die Ausführung der Breakeven-Logik und dafür, den internen Zeiger je nach ausgewähltem Breakeven-Typ auf die passende abgeleitete Klasse zu setzen bzw. entsprechend zu casten.

Beginnen wir mit der Definition der Struktur BreakEvenParams, die ein MqlParam-Array enthalten wird, in dem die Parameter eines spezifischen Breakeven-Typs gespeichert werden.

struct BreakEvenParams
 {
  MqlParam           params[];
 };

 Deklarieren wir nun die Klasse selbst und ihren privaten Bereich.

//---
class CBreakEven
 {
private:
  ulong              magic;
  string             symbol;
  ENUM_BREAKEVEN_MODE breakeven_mode;

  BreakEvenParams    parameters[];
  CBreakEvenBase*    CreateBreakEven(ENUM_BREAKEVEN_TYPE type);

Der private Bereich enthält die Magic Number, das Symbol und den Breakeven-Modus. Er enthält außerdem ein Array vom Typ BreakEvenParams, um die Parameter der drei Breakeven-Typen zu speichern, die berücksichtigt werden. Eine Funktion, die einen CBreakEvenBase-Zeiger zurückgibt, wird ebenfalls deklariert; sie wird in einer der folgenden Funktionen aufgerufen, um den internen Zeiger zu setzen.

Schließlich wird eine Variable vom Typ CBreakEvenBase* erstellt; sie wird konfiguriert und in der Funktion SetInternalPointer in den erforderlichen Typ umgewandelt, abhängig vom Breakeven-Modus, der vom Benutzer ausgewählt wurde.

CBreakEvenBase*    obj;

Konstruktor

Der Konstruktor hat dieselben Parameter wie der Konstruktor der Breakeven-Basisklasse.

CBreakEven(ulong magic_, string symbol_, ENUM_BREAKEVEN_MODE mode);

Innerhalb des Konstruktors initialisieren wir die internen Variablenwerte und setzen die Größe des Parameter-Arrays auf 3, entsprechend den drei unterstützten Breakeven-Typen. 

//+------------------------------------------------------------------+
//| Constructor                                                       |
//+------------------------------------------------------------------+
CBreakEven::CBreakEven(ulong magic_, string symbol_, ENUM_BREAKEVEN_MODE mode)
 {
  this.magic = magic_;
  this.symbol = symbol_;
  this.breakeven_mode = mode;
  ArrayResize(parameters, 3);
 }

Destructor

Innerhalb des Destruktors geben wir zuerst das Parameter-Array frei.

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CBreakEven::~CBreakEven()
 {
  ArrayFree(this.parameters);

Bevor wir den Breakeven-Zeiger obj löschen, prüfen wir, ob er dynamisch ist, und falls ja, löschen wir ihn.

  if(CheckPointer(this.obj) == POINTER_DYNAMIC)
   {
    delete obj;
    obj = NULL;
   }
 }

Funktionen zur Einstellung von Parametern

Um Werte innerhalb des Parameter-Arrays zu setzen, erstellen wir fünf Funktionen, mit denen wir die Parameter der drei Breakeven-Modi konfigurieren können: zwei für ATR, zwei für RRR und eine für den Breakeven mit festen Punkten.

Funktionen zum Einstellen der RRR-Breakeven-Parameter

Beginnen wir mit den RRR-Funktionen. Wie oben erwähnt, wird es zwei davon geben. Eine erfordert die Übergabe des ATR-Handles:

void               SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_);

Und die andere erfordert allgemeine Parameter sowie Parameter für die Erstellung des ATR-Handles:

void               SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_);

Beginnen wir mit der Definition der ersten Funktion, die die Übergabe des ATR-Handles erfordert. In dieser Funktion setzen wir, wie auch in den anderen, zunächst die Größe des params-Arrays auf 6.

ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);

Dann weisen wir jedem Index des params-Arrays den entsprechenden Wert zu.

  parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even;
  parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra;
  parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points;
  parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_;
  parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = atr_handle_;

Der letzte Index, 5, erhält den Wert HANDLE_INSTEAD_OF_PERIOD, damit die Parametereinstellungsfunktion der Klasse CBreakEvenRR bestimmen kann, welche Funktion aufgerufen werden soll; in diesem Fall diejenige, die die Übergabe eines Handles erfordert.

parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = HANDLE_INSTEAD_OF_PERIOD;

Bei der zweiten Funktion sind die Schritte ähnlich, aber jetzt enthält Index 4, der zuvor den ATR-Handle-Wert enthielt, den ATR-Zeitrahmen und Index 5 den ATR-Zeitraum.

//+------------------------------------------------------------------+
//| Set rr without handle                                            |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);
  parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even;
  parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra;
  parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points;
  parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_;
  parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = (int)tf_atr;
  parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = atr_period_;
 }

Vollständiger Code

//+------------------------------------------------------------------+
//| Set rr without handle                                            |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);
  parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even;
  parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra;
  parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points;
  parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_;
  parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = (int)tf_atr;
  parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = atr_period_;
 }

//+------------------------------------------------------------------+
//| Set rr with handle                                               |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);
  parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even;
  parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra;
  parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points;
  parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_;
  parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = atr_handle_;
  parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = HANDLE_INSTEAD_OF_PERIOD;
 }

Funktionen zum Einstellen der ATR-Breakeven-Parameter

Um die Werte des params-Arrays festzulegen, verwenden wir ebenfalls zwei Funktionen, wie im Fall des RRR-Breakevens, jedoch mit weniger Parametern.

Beide Funktionen zum Festlegen von Parametern haben genau fünf Parameter. Die erste, die ein Handle erfordert, wird wie folgt definiert:

void CBreakEven::SetBeByAtr(int atr_idx, double atr_multiplier_be, double atr_multiplier_extra_be, int atr_handle)

Bei der Beschreibung dieser Funktion besteht der erste Schritt darin, die Größe des Parameter-Arrays innerhalb des params-Arrays festzulegen:

ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5);

Anschließend werden allen fünf Elementen des params-Arrays Werte zugewiesen:

  parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx;
  parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be;
  parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be;
  parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = atr_handle;

Dabei ist zu beachten, dass das letzte Element, Index 4, den Wert HANDLE_INSTEAD_OF_PERIOD haben muss, damit die Mitgliedsfunktion der CBreakEvenAtr-Klasse, die die Übergabe eines Handles erfordert, ausgewählt werden kann:

parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = HANDLE_INSTEAD_OF_PERIOD;

Für die zweite Funktion ändern sich nur die Werte der Indizes 3 und 4.

Index 3 enthält den ATR-Zeitrahmenwert:

parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = int(timeframe);

Und Index 4 enthält den ATR-Periodenwert:

parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = atr_period;

Vollständiger Code

//+------------------------------------------------------------------+
//| Set atr (without handle)                                         |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByAtr(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5);
  parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx_;
  parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be_;
  parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be_;
  parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = int(timeframe);
  parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = atr_period;
 }

//+------------------------------------------------------------------+
//| Set atr (with handle)                                            |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByAtr(int atr_idx, double atr_multiplier_be, double atr_multiplier_extra_be, int atr_handle)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5);
  parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx;
  parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be;
  parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be;
  parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = atr_handle;
  parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = HANDLE_INSTEAD_OF_PERIOD;
 }

Funktion zum Festlegen der Parameter für ein Breakeven mit festen Punkten.

Um die Konfiguration abzuschließen, verbleibt der punktbasierte Breakeven; er erfordert nur zwei Parameter. Innerhalb der CBreakEven-Klasse deklarieren wir ihn wie folgt:

void               SetBeByFixedPoints(int points_be_, int extra_points_be_);

Um es zu definieren, legen wir zunächst die Größe des entsprechenden params-Arrays auf 2 fest:

ArrayResize(parameters[int(BREAKEVEN_TYPE_FIXED_POINTS)].params, 2);

Dann weisen wir Index 0 die Anzahl der Punkte für die Breakeven-Aktivierung und Index 1 die Anzahl der zusätzlichen Punkte zu.

  parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[0].integer_value = points_be_;
  parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[1].integer_value = extra_points_be_;

Vollständiger Code

//+------------------------------------------------------------------+
//| Set Fixed Point be                                               |
//+------------------------------------------------------------------+
void CBreakEven::SetBeByFixedPoints(int points_be_, int extra_points_be_)
 {
  ArrayResize(parameters[int(BREAKEVEN_TYPE_FIXED_POINTS)].params, 2);
  parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[0].integer_value = points_be_;
  parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[1].integer_value = extra_points_be_;
 }

Funktion zum Abrufen eines Zeigers basierend auf dem Breakeven-Typ

Um den erforderlichen Breakeven-Modus in Abhängigkeit von dem durch den Benutzer festgelegten Typ zu erhalten, erstellen wir eine Funktion, die einen CBreakEvenBase-Zeiger zurückgibt.

CBreakEvenBase *CBreakEven::CreateBreakEven(ENUM_BREAKEVEN_TYPE type)

Beginnen wir mit der Erstellung einer switch-Anweisung, in der jeder Fall einem Breakeven-Typ entspricht. Wenn die Typ-Variable mit keinem davon übereinstimmt, wird standardmäßig NULL zurückgegeben. Wenn eine Übereinstimmung gefunden wird, gibt die Funktion einen Zeiger auf die entsprechende abgeleitete Klasse zurück.

  switch(type)
   {
    case BREAKEVEN_TYPE_ATR:
      return new CBreakEvenAtr(this.symbol, this.magic, this.breakeven_mode);

    case BREAKEVEN_TYPE_RR:
      return new CBreakEvenRR(this.symbol, this.magic, this.breakeven_mode);

    case BREAKEVEN_TYPE_FIXED_POINTS:
      return new CBreakEvenSimple(this.symbol, this.magic, this.breakeven_mode);

    default:
      return NULL;
   }

Vollständiger Code

//+------------------------------------------------------------------+
//| Dynamically create the correct BreakEven                         |
//+------------------------------------------------------------------+
CBreakEvenBase *CBreakEven::CreateBreakEven(ENUM_BREAKEVEN_TYPE type)
 {
  switch(type)
   {
    case BREAKEVEN_TYPE_ATR:
      return new CBreakEvenAtr(this.symbol, this.magic, this.breakeven_mode);

    case BREAKEVEN_TYPE_RR:
      return new CBreakEvenRR(this.symbol, this.magic, this.breakeven_mode);

    case BREAKEVEN_TYPE_FIXED_POINTS:
      return new CBreakEvenSimple(this.symbol, this.magic, this.breakeven_mode);

    default:
      return NULL;
   }
  return NULL;
 }

Funktion zum Festlegen des internen Werts des obj-Zeigers

Um schließlich dem internen Zeiger einen Wert zuzuweisen, erstellen wir eine Funktion namens SetInternalPointer, die den Breakeven-Typ als Parameter entgegennimmt:

void SetInternalPointer(ENUM_BREAKEVEN_TYPE type)

Wir beginnen die Funktionsdefinition mit der Überprüfung des Zustands des obj-Zeigers.

  if(CheckPointer(this.obj) == POINTER_DYNAMIC)
   {
    delete this.obj;
    this.obj = NULL;
   }

Wenn der Zeiger dynamisch ist, löschen wir ihn.

Anschließend initialisieren wir den Zeiger obj über die Funktion CreateBreakEven, die je nach gewähltem Breakeven-Typ einen Zeiger zurückgibt. Nach der Zuweisung prüfen wir, ob er NULL ist. In diesem Fall geben wir eine Fehlermeldung aus und entfernen den Expert Advisor vom aktuellen Chart.

  this.obj = CreateBreakEven(type);

  if(this.obj == NULL)
   {
    printf("%s: Critical error | The type %d is invalid.", __FUNCTION__, type);
    ExpertRemove();
    return;
   }

Wir prüfen, ob die Größe des params-Arrays ausreicht, um die erforderlichen Parameter zu speichern. Wenn diese Prüfung fehlschlägt, löschen wir das Objekt, setzen den Zeiger auf NULL und entfernen den Expert Advisor vom Chart.

  if((int)parameters[type].params.Size() < obj.GetNumParams())
   {
    printf("%s: Error | The parameter array for %s is too small (%I32u elements)",
           __FUNCTION__, EnumToString(type), parameters[type].params.Size());
    delete obj;
    obj = NULL;
    ExpertRemove();
    return;
   }

Abschließend setzen wir die erforderlichen Werte mithilfe der Set-Funktion der Basisklasse:

obj.Set(parameters[type].params);

Vollständiger Code

//+------------------------------------------------------------------+
//| Set Pointer                                                      |
//+------------------------------------------------------------------+
void CBreakEven::SetInternalPointer(ENUM_BREAKEVEN_TYPE type)
 {
  if(CheckPointer(this.obj) == POINTER_DYNAMIC)
   {
    delete this.obj;
    this.obj = NULL;
   }

  this.obj = CreateBreakEven(type);

  if(this.obj == NULL)
   {
    printf("%s: Critical error | The type %d is invalid.", __FUNCTION__, type);
    ExpertRemove();
    return;
   }

  if((int)parameters[type].params.Size() < obj.GetNumParams())
   {
    printf("%s: Error | The parameter array for %s is too small (%I32u elements)",
           __FUNCTION__, EnumToString(type), parameters[type].params.Size());
    delete obj;
    obj = NULL;
    ExpertRemove();
    return;
   }

  obj.Set(parameters[type].params);
 }
//+------------------------------------------------------------------+


Testen des Order Blocks Expert Advisors mit verschiedenen Breakeven-Typen

In diesem letzten Abschnitt konzentrieren wir uns auf das Testen der drei Breakeven-Modi im Strategietester.

Beginnen wir mit der Konfiguration der Parameter. Wir verwenden ATR-basierten Stop Loss und Take Profit. Der ATR-Multiplikator für den Stop Loss wird auf 3,0 und für den Take Profit auf 6,0 eingestellt, also mit einem Verhältnis von 1:2. Das Handelsintervall wird von 03:00 bis 14:00 GMT-3 eingestellt.

Einstellungen

 Einstellungen

Abbildung 1: Einstellungen für den Backtest des Order Blocks Expert Advisors

Wie im Bild zu sehen, wird der Roboter auf Gold (XAUUSD) getestet, und wir wählen den M5-Zeitrahmen (5 Minuten). Das Modell basiert auf echten Ticks, mit einem Hebel von 1:30 und einer Anfangseinlage von 10.000 USD.

Erster Backtest ohne Breakeven-Aktivierung

Für einen klareren Vergleich wird der erste Test ohne Aktivierung des Breakeven-Modus durchgeführt.

 ohne Breakeven

Abbildung 2: Backtest-Chart des Order Blocks Expert Advisors ohne Breakeven-Aktivierung

Vor Beginn der Backtests ist es wichtig, zu beachten, dass die erzielten Ergebnisse für die spezifischen Parameter gültig sind, sodass die vorgestellten Schlussfolgerungen nicht auf jede Strategie oder einen beliebigen Parametersatz verallgemeinert werden können. Eine ungefähre Einschätzung kann jedoch für ähnliche Werte vorgenommen werden.

Zum Beispiel werden wir im unten vorgestellten Backtest den punktbasierten Breakeven testen. Die Parameter werden auf 100 zusätzliche Punkte für den Breakeven-Preis und 200 Punkte für dessen Aktivierung eingestellt. Daher gelten die aus diesem Backtest gezogenen Schlussfolgerungen für Werte nahe 200 bzw. 100 Punkten für die Breakeven-Aktivierung und den zusätzlichen Breakeven-Offset.

Backtest für punktbasierten Breakeven

Für den Backtest mit festen Punkten wählen wir 200 Punkte für die Breakeven-Aktivierung und 100 zusätzliche Punkte. Die erzielten Ergebnisse sind unten dargestellt.

Breakeven nach Punkten

Abbildung 3: Handelsergebnis-Chart für den Order Blocks Expert Advisor mit punktbasiertem Breakeven

Wie in der Abbildung zu sehen ist, gab es viele Serien von Positionen mit kleinen Gewinnen. Wie erwartet, wurden sie vom Breakeven-Mechanismus beeinflusst. Darüber hinaus wurden in diesem Backtest mit punktbasiertem Breakeven Positionen, die im Zeitraum vom 16. April bis zum 29. April 2025 einen positiven schwebenden Gewinn aufwiesen, ohne Verluste geschlossen. Im Gegensatz dazu wurden im ersten Backtest in diesem Zeitraum Verluste verzeichnet.

Dies könnte darauf hindeuten, dass ein punktbasierter Breakeven ein konservativerer Ansatz ist, aber er kann auch zukünftige Gewinne begrenzen. Infolgedessen betrug der Endsaldo in diesem Backtest nur 12.000 USD, während der Endsaldo im ersten Backtest bei etwa 15.800 USD lag. Dies deutet darauf hin, dass ein punktbasierter Breakeven zwar Verluste reduziert, aber gleichzeitig den Gewinn erheblich begrenzt.

Backtest mit ATR-basiertem Breakeven

Breakeven nach ATR

Abbildung 4: Backtest-Chart des Order Blocks Expert Advisor mit ATR-basiertem Breakeven

Im zweiten Backtest mit ATR-basiertem Breakeven zeigen die Ergebnisse eine Verbesserung. Zunächst analysieren wir die Auswirkung dieser Breakeven-Art in positiven und negativen Handelsserien.

In profitablen Phasen agiert dieser Breakeven nicht so aggressiv wie der Breakeven mit festen Punkten. In Verlustphasen begrenzt er Verluste moderat. Dies ist deutlich in der ersten großen Drawdown-Sequenz sichtbar, die vom Anfang des Jahres 2024 bis März dauerte. Die Bilanzkurve dieses Backtests ähnelt weitgehend der Kurve des ersten Backtests.

Somit können wir schlussfolgern, dass dieser Breakeven-Modus moderater ist, vielleicht nicht so aggressiv wie der Breakeven mit festen Punkten, da sein Aktivierungspreis je nach Volatilität und ATR-Wert höher oder niedriger sein kann.

Backtest mit RRR-basiertem Breakeven

RR-basierter Breakeven

Abbildung 5: Handelsergebnis-Diagramm für den Order Blocks Expert Advisor mit RRR-basiertem Breakeven und einem Stop-Loss-Take-Profit-Verhältnis von 1:2

Basierend auf diesem Backtest können wir feststellen, dass der Unterschied zwischen dem Backtest ohne Breakeven und der Version mit RRR-basiertem Breakeven in Verlustphasen, insbesondere im Januar und Februar 2024, gering ist. Dies deutet darauf hin, dass diese Verluste real waren, das heißt, es gab keinen positiven schwebenden Gewinn: Nachdem eine Position eröffnet wurde, erreichte der Preis sofort den Stop-Loss, sodass der RRR-basierte Breakeven keine Gelegenheit zur Aktivierung hatte.

In profitablen Phasen ist das Gewinnwachstum moderat begrenzt. Zum Beispiel kam diese Aufwärtssequenz im Juli 2024, als im ersten Backtest fast alle Positionen durch Take-Profit geschlossen wurden, bei Verwendung des RRR-basierten Breakevens weitgehend zum Stillstand. Dies könnte darauf zurückzuführen sein, dass der Expert Advisor in Konsolidierungsphasen handelt, in denen der Preis keinen klaren Trend bildete, was es dem Breakeven-Mechanismus ermöglichte, sich zu aktivieren und den Trade zu schließen, bevor sich eine klare Richtung entwickelte.

Wir können daraus schließen, dass der RRR-basierte Breakeven konservativer ist, da der Preis eine größere Distanz zurücklegen muss, damit der Mechanismus aktiviert wird. Daher ist er nicht die optimale Option für aggressive Strategien, kann aber für Swing-Trading-Strategien nützlich sein, die auf ein hohes Chance-Risiko-Verhältnis ausgerichtet sind, wie etwa 1:3 oder 1:5. Um dies zu überprüfen, führen wir als Nächstes einen Backtest mit einem Chance-Risiko-Verhältnis von 1:3 durch.

RRR-basierter Breakeven 2

Abbildung 6: Handelsergebnis-Diagramm für den Order Blocks Expert Advisor mit RRR-basiertem Breakeven und einem Stop-Loss-Take-Profit-Verhältnis von 1:3.

Der neue Backtest zeigte bessere Ergebnisse als der vorherige, mit einem Verhältnis von 1:2. Die wichtigsten Änderungen bestehen darin, dass der Mindestkontostand bei etwa 9.500 USD lag, was fast dem Ergebnis des ATR-basierten Breakeven-Backtests entspricht. Darüber hinaus waren die Verluste während der ersten negativen Serie von Januar bis Februar nicht so signifikant wie im ursprünglichen Backtest.

Im April ist jedoch die längste negative Serie unter allen drei Breakeven-Typen zu beobachten: fast fünf Monate mit Verlusten. Dies fällt mit einer etwa fünfmonatigen Seitwärtsbewegung bei Gold zusammen. Im Vergleich zum ersten RRR-Breakeven-Backtest könnte dies darauf hindeuten, dass Positionen nicht sofort geschlossen wurden, sondern wahrscheinlich zwischen schwebenden Gewinnen und Verlusten schwankten, sodass die Größen von Stop Loss und Take Profit signifikant gewesen sein müssen.

Das Problem ist, dass in diesem neuen Backtest die Größe des Stop Loss auf ein Drittel des ursprünglichen Wertes reduziert wurde, sodass die Wahrscheinlichkeit, dass Positionen mit einem Verlust geschlossen werden, gestiegen ist. Dies könnte darauf hindeuten, dass die Haltezeit der Positionen während der Seitwärtsphase länger war und dass vor dem Schließen Schwankungen zwischen positiven und negativen Werten auftraten.


Schlussfolgerung

In diesem Artikel haben wir die Implementierung von ATR- und RRR-basiertem Breakeven in MQL5 abgeschlossen. Anschließend haben wir eine Klasse erstellt, die es ermöglicht, verschiedene Breakeven-Typen innerhalb einer einzigen Klasse zu verwalten, was den Wechsel zwischen ihnen vereinfacht, ohne dass für jeden neuen Modus Parameter neu eingegeben werden müssen.

Abschließend haben wir die verschiedenen Breakeven-Typen getestet und analysiert. Wir haben gesehen, dass der Breakeven mit festen Punkten bei den angegebenen Parametern aggressiver ist. Im Gegensatz dazu ist der RRR-basierte Breakeven passiver, schränkt den Gewinn jedoch stärker ein. Der ATR-basierte Breakeven nimmt eine Zwischenposition zwischen diesen beiden Optionen ein.

Zusammenfassend lässt sich sagen, dass es unmöglich ist, mit Sicherheit zu sagen, welcher Breakeven-Typ für jede konkrete Strategie am besten geeignet ist, da dies von einer Reihe von Faktoren abhängt, die berücksichtigt werden müssen. Dies sollte individuell bewertet werden. Dennoch können als Empfehlung verschiedene Breakeven-Typen getestet und derjenige ausgewählt werden, der am besten zur verwendeten Strategie passt.

Basierend auf den durchgeführten Backtests können wir also davon ausgehen, dass der RRR-basierte Breakeven besser für Strategien mit einem vordefinierten Chance-Risiko-Verhältnis von größer oder gleich 1:3 geeignet ist. Dies wurde im fünften Backtest bestätigt, bei dem der Nettogewinn fast dreimal so hoch war wie im vierten.

In diesem Artikel verwendete/aktualisierte Dateien:

Dateiname Typ Beschreibung 
 Risk_Management.mqh  .mqh (Header-Datei) Enthält die Risikomanagementklasse, die im letzten Artikel der Risikomanagementreihe entwickelt wurde.
 Order_Block_Indicador_New_Part_2.mq5 .mq5 (Indikator) Enthält den Code für den Order Block Indikator.
 Order Block EA MT5.mq5  .mq5 (Expert Advisor) Code des Order-Block-Expert-Advisors mit integriertem Breakeven-Mechanismus.
 OB_SET_WITHOUT_BREAKEVEN.set .set (Konfigurationsdatei) Einstellungen für den ersten Backtest, ohne den Breakeven-Mechanismus.
 OB_SET_BREAKEVEN_POINTS.set .set (Konfigurationsdatei) Einstellungen für den zweiten Backtest mit Breakeven bei festen Punkten.
 OB_SET_BREAKEVEN_ATR.set .set (Konfigurationsdatei) Einstellungen für den dritten Backtest mit ATR-basiertem Breakeven.
 OB_SET_BREAKEVEN_RR_1_2.set .set (Konfigurationsdatei) Einstellungen für den vierten Backtest mit RRR-basiertem Breakeven im Verhältnis 1:2.
 OB_SET_BREAKEVEN_RR_1_3.set .set (Konfigurationsdatei) Einstellungen für den fünften Backtest mit Breakeven, basierend auf dem RR im Verhältnis 1:3.
 PositionManagement.mqh .mqh (Header-Datei)  MQH-Datei, die den Code für den Breakeven-Mechanismus enthält.


Übersetzt aus dem Spanischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/es/articles/18111

Beigefügte Dateien |
MQL5.zip (242.42 KB)
Gauß-Prozesse im maschinellen Lernen (Teil 1): Klassifikationsmodell in MQL5 Gauß-Prozesse im maschinellen Lernen (Teil 1): Klassifikationsmodell in MQL5
Der Artikel befasst sich mit der Klassifikation mittels Gauß-Prozessen. Wir beginnen mit dem Studium der theoretischen Grundlagen und gehen dann zur praktischen Entwicklung der GP-Bibliothek in MQL5 über.
Marktsimulation: Positionsansicht (V) Marktsimulation: Positionsansicht (V)
Trotz dessen, was im vorherigen Artikel gezeigt wurde, mag all dies zunächst einfach erscheinen. In Wirklichkeit gibt es jedoch noch einige Probleme, und viele Aufgaben sind noch offen. Sie, lieber Leser, Sie denken vielleicht, dass alles einfach und unkompliziert ist. Aus Unerfahrenheit akzeptieren Sie vielleicht einfach alles, was Ihnen präsentiert wird. Und das ist ein Fehler, den Sie zu vermeiden versuchen sollten. Noch schlimmer ist es, etwas zu verwenden, ohne wirklich zu verstehen, was genau Sie da verwenden. Anfänger durchlaufen oft eine Copy-and-Paste-Phase. Wenn Sie nicht für immer in dieser Phase stecken bleiben wollen, sollten Sie lernen, wie man bestimmte Werkzeuge verwendet. Eines der am häufigsten von Programmierern verwendeten Werkzeuge ist die Dokumentation. Das zweite ist das Testen, unterstützt durch Protokolldateien. Hier werden wir uns ansehen, wie man dabei vorgeht.
Neuronale Netze im Trading: Effektive Merkmalsextraktion zur präzisen Klassifizierung (letzter Teil) Neuronale Netze im Trading: Effektive Merkmalsextraktion zur präzisen Klassifizierung (letzter Teil)
Das Mantis-Framework transformiert komplexe Zeitreihen in informative Token und dient als zuverlässige Grundlage für einen intelligenten Handelsagenten, der in Echtzeit agieren kann.
Neuronale Netze im Trading: Effektive Merkmalsextraktion für präzise Klassifizierung (Mantis) Neuronale Netze im Trading: Effektive Merkmalsextraktion für präzise Klassifizierung (Mantis)
Lernen Sie Mantis kennen – ein leichtgewichtiges Foundation-Model für die Zeitreihenklassifizierung auf Basis einer Transformer-Architektur mit kontrastivem Vortraining und einem hybriden Attention-Mechanismus, der eine rekordverdächtige Genauigkeit und hohe Skalierbarkeit ermöglicht.