English 日本語
preview
Aufbau eines professionellen Handelssystems mit Heikin Ashi (Teil 2): Entwicklung eines EA

Aufbau eines professionellen Handelssystems mit Heikin Ashi (Teil 2): Entwicklung eines EA

MetaTrader 5Handelssysteme |
43 0
Chacha Ian Maroa
Chacha Ian Maroa

Einführung

Dieser Artikel ist der zweite Teil der Serie „Entwickeln eines professionellen Handelssystems mit Heikin Ashi“. Im ersten Teil haben wir einen nutzerdefinierten Heikin Ashi-Indikator mit MetaQuotes Language 5 (MQL5) erstellt und dabei bewährte Verfahren für die Entwicklung nutzerdefinierter Indikatoren befolgt. In diesem nächsten Teil gehen wir einen Schritt weiter und entwickeln einen Expert Advisor namens Zen Breakout, der unseren nutzerdefinierten Heikin Ashi-Indikator und den Indikator Fractals verwendet, um zuverlässige Ausbruchssignale zu generieren.

Das Konzept ist einfach:

  • Wenn eine starke Heikin Ashi-Aufwärtskerze über dem letzten hohen Umkehrpunkt schließt (vom Fractals-Indikator erkannt), eröffnet der EA eine Kaufposition.
  • Wenn eine starke Heikin Ashi-Abwärtskerze unter einem aktuellen Tiefpunkt schließt, eröffnet der EA eine Verkaufsposition.

Jeder von unserem EA eröffnete Handel verfügt über klar definierte Stop-Loss- und Take-Profit-Levels mit einem konfigurierbaren Risiko-Ertrags-Verhältnis. Nach der Lektüre dieses Artikels werden Sie wissen, wie man:

  • Hängen Sie einen nutzerdefinierten Indikator und einen integrierten Indikator an einen EA.
  • Implementierung einer Ausbruchslogik mit Heikin Ashi und Fraktalen.
  • Wenden Sie eine flexible Positionsgrößenbestimmung an (manuell oder auf der Grundlage des prozentualen Risikos Ihres Kontos).
  • Verpacken Sie einen EA mit seinem Indikator in einer einzigen Datei, um ihn leichter verteilen zu können.



Strategiekonzept

Die Strategie Zen-Breakout kombiniert Momentum-Erkennung mit Ausbruchsbestätigung.

  • Kaufsituation

Das Setup für Kaufpositionen entsteht, wenn eine starke Heikin Ashi-Aufwärtskerze über dem letzten hohen Umkehrpunkt schließt.

Kaufsituation

  • Verkaufssituation

Sie tritt auf, wenn eine starke Heikin-Ashi-Kerze unterhalb des letzten tiefen Umkehrpunktes schließt.

Verkaufssituation

  • Stop-Loss-Platzierung

Bei Kaufpositionen wird der Stop-Loss beim Tief der Ausbruchskerze gesetzt.

Stop-Loss-Platzierung

Bei Verkaufspositionen wird der Stop-Loss beim Hoch der Ausbruchskerze gesetzt.

Stop-Loss-Platzierung Abwärtsszenario

  • Platzierung von Take-Profit

Take-Profit wird über ein konfigurierbares Risiko-Ertrags-Verhältnis definiert. Wenn zum Beispiel das Risiko pro Handel 100 Punkte beträgt und das Risiko-Ertrags-Verhältnis 1:2 ist, liegt der Take-Profit 200 Punkte vom Einstieg entfernt.



Vorbereiten des EA

Um Handelssignale zu generieren, liest Zen Breakout direkt Daten von zwei Indikatoren:

  • Fraktal-Indikator

Der Fractals-Indikator ist auf dem MetaTrader 5-Terminal vorinstalliert und wird häufig verwendet, um die jüngsten Höchst- und Tiefststände des Marktes zu ermitteln. In unserem EA werden wir die MQL5-Funktion iFractals() verwenden, um den Indikator zu initialisieren und sein Handle für die weitere Verwendung in unserem Code zu erhalten.

  • Ein nutzerdefinierter Heikin Ashi-Indikator

Wir werden den nutzerdefinierten Heikin Ashi-Indikator verwenden, den wir im ersten Teil erstellt haben, um Ausbrüche mit starkem Momentum zu erkennen. Um programmatisch darauf zuzugreifen, verwenden wir die MQL5-Funktion iCustom(), um sie zu initialisieren und ihr Handle zu erhalten. Außerdem werden wir den Indikator als Ressource innerhalb des EA verpacken, sodass er als eine einzige, in sich geschlossene Datei verteilt werden kann.

Wir werden unserem Zen Breakout EA die folgenden konfigurierbaren Eingabeparameter hinzufügen, um ihn flexibler zu machen. 

  • magicNumber

Die magische Zahl, magicNumber, ist eine eindeutige Kennung, die der EA jedem Handel zuweist, den er eröffnet. Dadurch kann der EA seine Handelsgeschäfte von denen unterscheiden, die manuell oder von anderen EAs eröffnet wurden, und sicherstellen, dass er nur seine eigenen Positionen ändert oder schließt.

  • timeFrame

Dieser Parameter gibt den Chart-Zeitrahmen an, in dem der EA arbeiten soll. Die Nutzer können aus den 21 verfügbaren Zeitrahmen des MetaTrader 5 wählen, die von M1 (1 Minute) bis MN1 (monatlich) reichen.

  • lotSizeMode

Legt fest, wie der EA die Losgröße für neue Positionen berechnet:

    • Manuell – Der Nutzer gibt eine feste Losgröße im Parameter „lotSize“ an.
    • Auto – Der EA berechnet die Losgröße dynamisch auf der Grundlage des Kontostands und des Parameters „riskPerTradePercent“.
  • riskPerTradePercent

Gibt den Prozentsatz des Kontoguthabens an, der pro Handel riskiert werden soll (wird nur verwendet, wenn der Parameter „lotSizeMode“ auf auto mode eingestellt ist). Wenn der Kontostand beispielsweise $10000 beträgt und dieser Parameter auf 1,0 gesetzt ist, wird der EA die Positionen so dimensionieren, dass ein Stop-Loss-Treffer zu einem Verlust von $100 (1 % von $10000) führt.

  • lotSize

Legt eine feste Losgröße für alle neuen Abschlüsse fest (wird nur verwendet, wenn der Parameter „lotSizeMode“ auf den manuellen Modus eingestellt ist). Wenn zum Beispiel der Parameter „lotSize“ auf 0,5 gesetzt ist, wird jede neue Position mit einem Volumen von 0,5 Lots eröffnet.

  • RRr (Risiko-Ertrags-Verhältnis)

Legt das Risiko-Ertrags-Verhältnis für jeden Handel fest. Die Nutzer können aus sieben vordefinierten Verhältnissen wählen, sodass die potenziellen Gewinne die potenziellen Verluste überwiegen, wenn das Take-Profit-Niveau erreicht ist.



Schritt-für-Schritt-Anleitung zum Schreiben des Expert Advisors

In diesem Artikel wird davon ausgegangen, dass Sie bereits mit grundlegenden Programmierkonzepten vertraut sind und über solide Erfahrungen mit der Sprache MQL5 in MetaTrader 5 und MetaEditor verfügen. Wir werden diese Themen nicht behandeln, also fangen wir gleich an, unseren EA zu schreiben. Bereiten Sie eine leere Quelldatei in MetaEditor vor. Wir sind bereit, mit der Codierung zu beginnen. Beginnen wir mit dem ursprünglichen Standardcode. Wir werden dies als Grundlage für den Aufbau des EA verwenden.

//+------------------------------------------------------------------+
//|                                                  zenBreakout.mq5 |
//|          Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian |
//|                          https://www.mql5.com/en/users/chachaian |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/en/users/chachaian"
#property version   "1.10"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
  
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

}

//--- Utility functions
//+------------------------------------------------------------------+

Der nächste Schritt besteht darin, die nutzerdefinierten Funktionen für unseren EA hinzuzufügen. Fügen Sie die folgenden Funktionen direkt unter der Funktion OnTick() hinzu. Wir werden diese Funktionen eine nach der anderen in unserem Quellcode aufrufen, während wir unseren EA entwickeln. 

...

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

}

//--- Utility functions
//+------------------------------------------------------------------+
//| This function configures the chart's appearance.                 |                                   |
//+------------------------------------------------------------------+
bool ConfigureChartAppearance()
{
   if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){
      Print("Error while setting chart background, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){
      Print("Error while setting chart grid, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_MODE, CHART_LINE)){
      Print("Error while setting chart mode, ", GetLastError());
      return false;
   }

   if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){
      Print("Error while setting chart foreground, ", GetLastError());
      return false;
   }
   
   return true; 
}

//+-------------------------------------------------------------------------+
//| Function to generate a unique graphical object name with a given prefix |                                   |
//+-------------------------------------------------------------------------+
string GenerateUniqueName(string prefix){
   int attempt = 0;
   string uniqueName;
   while(true)
   {
      uniqueName = prefix + IntegerToString(MathRand() + attempt);
      if(ObjectFind(0, uniqueName) < 0)
         break;
      attempt++;
   }
   return uniqueName;
}
//+-------------------------------------------------------------------------+
//| Returns true if Heikin Ashi candle is bullish and has no lower wick     |                                   |
//+-------------------------------------------------------------------------+
bool IsBullishBreakoutCandle(int index)
{
   if(index < 0 || index >= ArraySize(heikinAshiOpen)) return false;

   double open  = heikinAshiOpen[index];
   double close = heikinAshiClose[index];
   double low   = heikinAshiLow[index];

   //--- Candle must be bullish and have no lower wick
   return (close > open && low >= MathMin(open, close));
}

//+-------------------------------------------------------------------------+
//| Returns true if Heikin Ashi candle is bearish and has no upper wick     |                                   |
//+-------------------------------------------------------------------------+
bool IsBearishBreakoutCandle(int index)
{
   if(index < 0 || index >= ArraySize(heikinAshiOpen)) return false;

   double open  = heikinAshiOpen[index];
   double close = heikinAshiClose[index];
   double high  = heikinAshiHigh[index];

   //--- Candle must be bearish and have no upper wick
   return (close < open && high <= MathMax(open, close));
}

//+----------------------------------------------------------------------------------------------+
//| Returns the index of the most recent swing high before 'fromIndex'. Returns -1 if not found  |                                   |
//+----------------------------------------------------------------------------------------------+
int FindMostRecentSwingHighIndex(int fromIndex)
{
   if(fromIndex <= 0 || fromIndex >= ArraySize(swingHighs))
      fromIndex = 1;

   for(int i = fromIndex; i < ArraySize(swingHighs); i++)
   {
      if(swingHighs[i] != EMPTY_VALUE)
         return i;
   }

   return -1; //--- No swing high found
}

//+----------------------------------------------------------------------------------------------+
//| Returns the index of the most recent swing low before 'fromIndex'. Returns -1 if not found   |                                   |
//+----------------------------------------------------------------------------------------------+
int FindMostRecentSwingLowIndex(int fromIndex)
{
   if(fromIndex <= 0 || fromIndex >= ArraySize(swingLows))
      fromIndex = 1;

   for(int i = fromIndex; i < ArraySize(swingLows); i++)
   {
      if(swingLows[i] != EMPTY_VALUE)
         return i;
   }

   return -1; // No swing low found
}

//+------------------------------------------------------------------+
//| This function detects a bullish signal                           |
//+------------------------------------------------------------------+
bool IsBullishSignal(datetime &timeStart, int &indexStart, datetime &timeEnd, int &indexEnd)
{
   indexStart = FindMostRecentSwingHighIndex(1);
   double recentSwingHigh               = iHigh(_Symbol, timeframe, indexStart);
   double previousHeikinAshiCandleClose = heikinAshiClose[1];
   double previousHeikinAshiCandleOpen  = heikinAshiOpen[1];
   
   if(IsBullishBreakoutCandle(1)){
      if(previousHeikinAshiCandleClose > recentSwingHigh && previousHeikinAshiCandleOpen < recentSwingHigh){
         timeStart = iTime(_Symbol, timeframe, indexStart);
         indexEnd  = 0;
         timeEnd   = iTime(_Symbol, timeframe, indexEnd);
         return true;
      }
   }
   return false;
}

//+------------------------------------------------------------------+
//| This function detects a bearish signal                           |
//+------------------------------------------------------------------+
bool IsBearishSignal(datetime &timeStart, int &indexStart, datetime &timeEnd, int &indexEnd)
{
   indexStart = FindMostRecentSwingLowIndex(1);
   double recentSwingLow                = iLow(_Symbol, timeframe, indexStart);
   double previousHeikinAshiCandleClose = heikinAshiClose[1];
   double previousHeikinAshiCandleOpen  = heikinAshiOpen[1];
   
   if(IsBearishBreakoutCandle(1)){
      if(previousHeikinAshiCandleClose < recentSwingLow && previousHeikinAshiCandleOpen > recentSwingLow){
         timeStart = iTime(_Symbol, timeframe, indexStart);
         indexEnd  = 0;
         timeEnd   = iTime(_Symbol, timeframe, indexEnd);
         return true;
      }
   }
   return false;
}

//+-------------------------------------------------------------------+
//| Function to check if there's a new bar on a given chart timeframe |                           |
//+-------------------------------------------------------------------+
bool IsNewBar(string symbol, ENUM_TIMEFRAMES tf, datetime &lastTm)
{

   datetime currentTime = iTime(symbol, tf, 0);
   if(currentTime != lastTm){
      lastTm       = currentTime;
      return true;
   }  
   return false;
   
}

//+------------------------------------------------------------------+
//| To check if there is an active buy position opened by this EA    |                                 |
//+------------------------------------------------------------------+
bool IsThereAnActiveBuyPosition(ulong magicNm){
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0){
         Print("Error while fetching position ticket ", _LastError);
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magicNm && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
            return true;
         }
      }
   }
   return false;
}

//+------------------------------------------------------------------+
//| To check if there is an active sell position opened by this EA   |                                 |
//+------------------------------------------------------------------+
bool IsThereAnActiveSellPosition(ulong mgcNumber){
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0){
         Print("Error while fetching position ticket ", _LastError);
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == mgcNumber && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
            return true;
         }
      }
   }
   return false;
}

//+------------------------------------------------------------------+
//| To open a buy position                                           |
//+------------------------------------------------------------------+
bool OpenBuy(){
   double rewardValue = 1.0;
   switch(RRr){
      case ONE_TO_ONE: 
         rewardValue = 1.0;
         break;
      case ONE_TO_ONEandHALF:
         rewardValue = 1.5;
         break;
      case ONE_TO_TWO: 
         rewardValue = 2.0;
         break;
      case ONE_TO_THREE: 
         rewardValue = 3.0;
         break;
      case ONE_TO_FOUR: 
         rewardValue = 4.0;
         break;
      case ONE_TO_FIVE: 
         rewardValue = 5.0;
         break;
      case ONE_TO_SIX: 
         rewardValue = 6.0;
         break;
      default:
         rewardValue = 1.0;
         break;
   }
   ENUM_POSITION_TYPE positionType    = POSITION_TYPE_BUY;
   ENUM_ORDER_TYPE   action           = ORDER_TYPE_BUY;
   double stopLevel                   = iLow(_Symbol, timeframe, 1);
   double askPrice                    = AppData.askPrice;
   double bidPrice                    = AppData.bidPrice;
   double stopDistance                = askPrice - stopLevel;
   double targetLevel                 = askPrice + (stopDistance * rewardValue);  
   double lotSz                       = AppData.amountAtRisk / (AppData.contractSize * stopDistance);
   
   if(lotSizeMode == MODE_AUTO){
      lotSz                              = NormalizeDouble(lotSz, 2);
   }else{
      lotSz                              = NormalizeDouble(lotSize, 2);
   }
      
   if(!Trade.Buy(lotSz, _Symbol, askPrice, stopLevel, targetLevel)){
      Print("Error while opening a long position, ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }else{
      MqlTradeResult result = {};
      Trade.Result(result);
      AppData.tradeInfo.orderTicket                 = result.order;
      AppData.tradeInfo.type                        = action;
      AppData.tradeInfo.posType                     = positionType;
      AppData.tradeInfo.entryPrice                  = result.price;
      AppData.tradeInfo.takeProfitLevel             = targetLevel;
      AppData.tradeInfo.stopLossLevel               = stopLevel;
      AppData.tradeInfo.openTime                    = AppData.currentGmtTime;
      AppData.tradeInfo.lotSize                     = lotSz;
      return true;
   }
   
   return false; 
}

//+------------------------------------------------------------------+
//| To open a sell position                                          |
//+------------------------------------------------------------------+
bool OpenSel(){
   double rewardValue = 1.0;
   switch(RRr){
      case ONE_TO_ONE: 
         rewardValue = 1.0;
         break;
      case ONE_TO_ONEandHALF:
         rewardValue = 1.5;
         break;
      case ONE_TO_TWO: 
         rewardValue = 2.0;
         break;
      case ONE_TO_THREE: 
         rewardValue = 3.0;
         break;
      case ONE_TO_FOUR: 
         rewardValue = 4.0;
         break;
      case ONE_TO_FIVE: 
         rewardValue = 5.0;
         break;
      case ONE_TO_SIX: 
         rewardValue = 6.0;
         break;
      default:
         rewardValue = 1.0;
         break;
   }
   ENUM_POSITION_TYPE positionType    = POSITION_TYPE_SELL;
   ENUM_ORDER_TYPE   action           = ORDER_TYPE_SELL;
   double stopLevel                   = iHigh(_Symbol, timeframe, 1);
   double bidPrice                    = AppData.bidPrice;
   double askPrice                    = AppData.askPrice;
   double stopDistance                = stopLevel - bidPrice;
   double targetLevel                 = bidPrice - (stopDistance * rewardValue);
   double lotSz                       = AppData.amountAtRisk / (AppData.contractSize * stopDistance);
   
   if(lotSizeMode == MODE_AUTO){
      lotSz                              = NormalizeDouble(lotSz, 2);
   }else{
      lotSz                              = NormalizeDouble(lotSize, 2);
   }
   
   if(!Trade.Sell(lotSz, _Symbol, bidPrice, stopLevel, targetLevel)){
      Print("Error while opening a short position, ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }else{ 
      MqlTradeResult result = {};
      Trade.Result(result);
      AppData.tradeInfo.orderTicket                 = result.order;
      AppData.tradeInfo.type                        = action;
      AppData.tradeInfo.posType                     = positionType;
      AppData.tradeInfo.entryPrice                  = result.price;
      AppData.tradeInfo.takeProfitLevel             = targetLevel;
      AppData.tradeInfo.stopLossLevel               = stopLevel;
      AppData.tradeInfo.openTime                    = AppData.currentGmtTime;
      AppData.tradeInfo.lotSize                     = lotSz;
      return true;
   }
   return false; 
}
//+------------------------------------------------------------------+

Wenn Sie jetzt versuchen, den EA zu kompilieren, werden Sie eine Reihe von Kompilierfehlern sehen. Das liegt daran, dass viele der Funktionen, die Sie hinzugefügt haben, sich auf Variablen beziehen, die noch nicht definiert wurden. Wir werden diese Variablen später in den globalen Bereich aufnehmen. Lassen Sie uns zunächst die Funktionen der einzelnen Funktionen durchgehen und erklären, was sie bewirken.

  • ConfigureChartAppearance

Diese Funktion richtet das visuelle Erscheinungsbild des Charts ein, bevor der EA ausgeführt wird. Es sorgt für eine saubere, minimalistische Ansicht, indem es den Chart-Hintergrund auf weiß ändert, das Raster für ein übersichtliches Aussehen ausblendet, den Chart-Modus auf ein Linienchart umstellt und die Vordergrundfarbe auf schwarz setzt, um einen guten Kontrast zu erzielen.

  • GenerateUniqueName

Es wird verwendet, um einen eindeutigen Namen für jedes neu erstellte grafische Objekt auf dem Chart zu generieren, auf dem unser EA läuft. Dadurch wird sichergestellt, dass jedes vom EA gezeichnete Objekt einen eindeutigen Bezeichner hat, sodass ein versehentliches Überschreiben von zuvor gezeichneten Objekten verhindert wird. Die Funktion nimmt eine Zeichenkette als Eingabe, wendet darauf einen Algorithmus an und erzeugt einen eindeutigen Objektbezeichner.

  • IsBullishBreakoutCandle

Diese Funktion prüft, ob eine Heikin Ashi-Kerze, die durch ihren Index spezifiziert ist, die Kriterien für einen Ausbruch nach oben erfüllt, indem sie die folgenden spezifischen Bedingungen überprüft.

    • Der Schlusskurs der Kerze muss größer sein als der Eröffnungskurs der Kerze.
    • Die Kerze darf keinen unteren Docht haben.

Wenn beide Bedingungen erfüllt sind, gibt die Funktion den Wert „true“ zurück, was bedeutet, dass die Heikin Ashi-Kerze als steigende Ausbruchskerze eingestuft werden kann.

  • IsBearishBreakoutCandle

Diese Funktion prüft, ob eine durch ihren Index spezifizierte Heikin Ashi-Kerze die Kriterien für einen Ausbruch nach unten erfüllt.

  • FindMostRecentSwingHighIndex

Der Hauptzweck dieser Funktion besteht darin, den Index des letzten hohen Umkehrpunkts zu ermitteln. Dies geschieht durch das Durchsuchen eines Arrays von Fraktalwerten, die direkt aus dem Puffer des Indikators Fraktale mit der Nummer Null abgerufen werden. Die Funktion sucht speziell nach dem letzten hohen Umkehrpunkt, das vor einem bestimmten Index auftrat, der als Eingabe angegeben wird.

  • FindMostRecentSwingLowIndex

Der Zweck dieser Funktion ist es, den Index des letzten tiefen Umkehrpunkts zu finden.

  • IsBullishSignal

Diese Funktion prüft, ob sich ein gültiges Ausbruchssignal nach oben gebildet hat. Zunächst wird der jüngste hohe Umkehrpunkt gefunden und dessen Kurs abgerufen. Dann werden die Eröffnungs- und Schlusswerte der vorherigen Heikin Ashi-Kerze ermittelt. Wenn die letzte eine Aufwärtskerze ist und keinen unteren Docht aufweist und der Schlusskurs über dem hohen Umkehrpunkt liegt, während der Eröffnungskurs darunter liegt, zeichnet die Funktion die Start- und Endzeit als Referenz auf und gibt true zurück. Andernfalls wird false zurückgegeben.

  • IsBearishSignal

Diese Funktion prüft, ob sich ein gültiges Ausbruchssignal nach unten gebildet hat. 

  • IsNewBar

Diese Funktion prüft, ob sich ein neuer Balken auf dem angegebenen Symbol und Zeitrahmen gebildet hat. Sie vergleicht die Öffnungszeit des aktuellen Balkens mit der zuvor gespeicherten Zeit. Wenn sie sich unterscheiden, wird die gespeicherte Zeit aktualisiert und true zurückgegeben, andernfalls false.

  • IsThereAnActiveBuyPosition

Diese Funktion prüft, ob es eine aktive Kaufposition gibt, die speziell von diesem EA eröffnet wurde. Sie akzeptiert eine magische Zahl als Eingabe, die eine eindeutige Kennung ist, die den Handelsgeschäften des EA zugewiesen wird. Die Funktion durchläuft alle offenen Positionen, und wenn sie eine Kaufposition findet, deren magische Zahl mit der angegebenen übereinstimmt, gibt sie true zurück, andernfalls gibt sie false zurück.

  • IsThereAnActiveSellPosition

Diese Funktion prüft, ob es eine aktive Verkaufsposition gibt, die speziell von diesem EA eröffnet wurde.

  • OpenBuy

Diese Funktion ist für die Eröffnung einer Kaufposition gemäß den Risiko-Rendite-Einstellungen und Risikomanagementregeln des EA verantwortlich. Zunächst wird der Rendite-Multiplikator auf der Grundlage des nutzerdefinierten Risiko-/Ertragsverhältnisses (1:1, 1:1,5, 1:2 usw.) ausgewählt. Anschließend wird das Stop-Loss-Niveau beim Tiefststand der vorherigen Kerze berechnet und der Stop-Abstand zum aktuellen Briefkurs (Ask) gemessen. Anhand dieser Stopp-Distanz wird der Take-Profit-Level berechnet, indem die Distanz mit dem gewählten Gewinnverhältnis multipliziert wird, um sicherzustellen, dass der Handel das festgelegte Risiko-Ertrags-Profil respektiert.

Anschließend ermittelt die Funktion die Losgröße. Wenn der Modus für die Losgröße auf automatisch eingestellt ist, wird die Losgröße auf der Grundlage des Risikobetrags, der Kontraktgröße und des Stoppabstands berechnet und dann auf zwei Dezimalstellen normalisiert. Ist der manuelle Modus gewählt, wird stattdessen die vom Nutzer festgelegte Losgröße verwendet.
Schließlich versucht die Funktion, einen Kaufauftrag unter Verwendung der berechneten Parameter zu senden. Wenn der Handel erfolgreich platziert wurde, werden detaillierte Informationen über den Auftrag (Ticketnummer, Typ, Einstiegskurs, Stop-Loss, Take-Profit, Losgröße und Zeit) in einer strukturierten Variablen, AppData.tradeInfo, gespeichert, um später darauf zurückgreifen zu können. Wenn der Auftrag fehlschlägt, werden detaillierte Fehlermeldungen ausgegeben, um die Fehlersuche zu erleichtern, und es wird false zurückgegeben.

Diese Funktion bindet das Risikomanagement, die Renditeberechnung und die Handelsausführung des EA in einem einzigen, gut strukturierten Prozess zusammen und ist damit einer der Kernbausteine des Zen Breakout EA.

  • OpenSel

Diese Funktion verhält sich genauso wie die Funktion „OpenBuy“, nur dass sie eine Verkaufsposition statt einer Kaufposition eröffnet. 

Nachdem die Struktur des Expert Advisors (EA) eingerichtet ist, müssen nun die Eingabeparameter definiert werden. Sie können dies tun, indem Sie sie direkt unter den #property-Direktiven ganz oben in Ihrer Programmdatei deklarieren. Fügen Sie einfach den folgenden Codeblock an dieser Stelle ein:

...

#property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/en/users/chachaian"
#property version   "1.10"

//--- Input parameters
input group "Information"
input ulong magicNumber         = 254700680002;
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

input group "Risk Management"
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode = MODE_AUTO;
input double riskPerTradePercent           = 1.0;
input double lotSize                       = 0.1;
input ENUM_RISK_REWARD_RATIO RRr           = ONE_TO_ONEandHALF;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   return(INIT_SUCCEEDED);
}

...

Da wir diese Parameter bereits im Abschnitt Vorbereitung des EA definiert und erläutert haben, werden wir ihre Beschreibungen hier nicht wiederholen. Als Nächstes werden wir nutzerdefinierte Enumerationen für einige unserer Eingabeparameter erstellen. Enumerationen bieten den Nutzern eine Dropdown-Liste mit vordefinierten Optionen, wenn sie den EA konfigurieren. Wir werden unsere nutzerdefinierten Enumerationen direkt unter den #property-Direktiven und über den Eingabeparametern definieren.

...

#property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/en/users/chachaian"
#property version   "1.10"

//--- Custom enumerations
enum ENUM_RISK_REWARD_RATIO   { ONE_TO_ONE, ONE_TO_ONEandHALF, ONE_TO_TWO, ONE_TO_THREE, ONE_TO_FOUR, ONE_TO_FIVE, ONE_TO_SIX };
enum ENUM_LOT_SIZE_INPUT_MODE { MODE_MANUAL, MODE_AUTO };

//--- Input parameters
input group "Information"
input ulong magicNumber         = 254700680002;
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

...

Unser Code enthält zwei nutzerdefinierte Enumerationen, die die Konfiguration des EAs intuitiver machen:

  • ENUM_RISK_REWARD_RATIO

Diese Enumeration definiert sieben voreingestellte Optionen für das Risiko-Ertrags-Verhältnis (1:1 bis 1:6). Dies ermöglicht es den Händlern, ihr bevorzugtes Verhältnis einfach aus einer Dropdown-Liste auszuwählen, anstatt die Werte manuell einzugeben.

  • ENUM_LOT_SIZE_INPUT_MODE

Legt fest, wie der EA die Losgröße berechnet. Mit MODE_MANUAL kann der Nutzer eine feste Losgröße festlegen, während MODE_AUTO die Losgröße dynamisch auf der Grundlage von Kontostand und Risikoprozentsatz berechnet.

Als Nächstes definieren wir ein Makro namens zenBreakout, das den Namen des EA als String speichert. Dieses Makro wird später in unserer eigenen Funktion GenerateUniqueName() verwendet, um eindeutige Namen für neue grafische Objekte zu erstellen. Wir platzieren die Makrodefinition nun direkt unter den bestehenden #property-Anweisungen.

...

#property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/en/users/chachaian"
#property version   "1.10"

//--- Macros
#define zenBreakout "zenBreakout"

//--- Custom enumerations
enum ENUM_RISK_REWARD_RATIO   { ONE_TO_ONE, ONE_TO_ONEandHALF, ONE_TO_TWO, ONE_TO_THREE, ONE_TO_FOUR, ONE_TO_FIVE, ONE_TO_SIX };
enum ENUM_LOT_SIZE_INPUT_MODE { MODE_MANUAL, MODE_AUTO };

...

Als Nächstes fügen wir die erforderlichen Bibliotheken direkt unter der Definition unserer nutzerdefinierten Enumeration ein.

...

//--- Custom enumerations
enum ENUM_RISK_REWARD_RATIO   { ONE_TO_ONE, ONE_TO_ONEandHALF, ONE_TO_TWO, ONE_TO_THREE, ONE_TO_FOUR, ONE_TO_FIVE, ONE_TO_SIX };
enum ENUM_LOT_SIZE_INPUT_MODE { MODE_MANUAL, MODE_AUTO };

//--- Libraries
#include <Trade\Trade.mqh>
#include <ChartObjects\ChartObjectsLines.mqh>

//--- Input parameters
input group "Information"
input ulong magicNumber         = 254700680002;
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

...

  • Trade.mqh
Ermöglicht den Zugriff auf die Klasse CTrade, die Handelsoperationen wie das Öffnen, Schließen und Ändern von Positionen vereinfacht.

  • ChartObjectsLines.mqh

Ermöglicht den Zugriff auf Klassen zum Erstellen und Verwalten von Linienobjekten im Chart, die wir später verwenden werden, um bestätigte Ausbrüche über und unter den letzten Schwungpunkten anzuzeigen.

Als Nächstes definieren wir zwei Datenstrukturen direkt unter unseren Eingabeparametern, um die vom EA verwendeten Schlüsselinformationen zu organisieren und zu speichern.

...

//--- Input parameters
input group "Information"
input ulong magicNumber         = 254700680002;
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

input group "Risk Management"
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode = MODE_AUTO;
input double riskPerTradePercent           = 1.0;
input double lotSize                       = 0.1;
input ENUM_RISK_REWARD_RATIO RRr           = ONE_TO_ONEandHALF;

//--- Data Structures
struct MqlTradeInfo
{
   ulong orderTicket;                 
   ENUM_ORDER_TYPE type;
   ENUM_POSITION_TYPE posType;
   double entryPrice;
   double takeProfitLevel;
   double stopLossLevel;
   datetime openTime;
   double lotSize;   
};

struct MqlAppData 
{
   double bidPrice;
   double askPrice;
   double currentBalance;
   double currentEquity;
   datetime currentGmtTime;
   datetime lastDailyCheckTime;
   datetime lastBarOpenTime;
   double contractSize;
   long digitValue;
   double amountAtRisk;
   MqlTradeInfo tradeInfo;
};

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   return(INIT_SUCCEEDED);
}

...
  • MqlTradeInfo

Diese Datenstruktur enthält alle Details über die aktive Position, einschließlich Ticketnummer, Auftragsart, Positionstyp, Einstiegskurs, Stop Loss, Take Profit, Losgröße und Eröffnungszeit. 

  • MqlAppData

Diese Datenstruktur dient als Container für Daten auf Anwendungsebene, z. B. Geld- und Briefkurse, Kontostand, Kapital, GMT-Zeit, Öffnungszeit des letzten Balkens, Kontraktgröße, Symbolziffern und Risikobetrag pro Handel. Sie enthält auch eine Instanz von MqlTradeInfo, die es uns ermöglicht, sowohl Konto- als auch Handelsinformationen an einem Ort zusammenzufassen.

Als Nächstes deklarieren wir globale Variablen, auf die im gesamten EA zugegriffen werden kann. Wir werden sie direkt unter unseren Datenstrukturen deklarieren. 

...

//--- Data Structures
struct MqlTradeInfo
{
   ulong orderTicket;                 
   ENUM_ORDER_TYPE type;
   ENUM_POSITION_TYPE posType;
   double entryPrice;
   double takeProfitLevel;
   double stopLossLevel;
   datetime openTime;
   double lotSize;   
};

struct MqlAppData 
{
   double bidPrice;
   double askPrice;
   double currentBalance;
   double currentEquity;
   datetime currentGmtTime;
   datetime lastDailyCheckTime;
   datetime lastBarOpenTime;
   double contractSize;
   long digitValue;
   double amountAtRisk;
   MqlTradeInfo tradeInfo;
};

//--- Global variables
CTrade Trade;
CChartObjectTrend TrendLine;
MqlAppData AppData;

int    heikinAshiIndicatorHandle;
double heikinAshiOpen     [];
double heikinAshiHigh     [];
double heikinAshiLow      [];
double heikinAshiClose    [];

int    fractalsIndicatorHandle;
double swingHighs [];
double swingLows  [];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   return(INIT_SUCCEEDED);
}

...
  • CTrade Trade

Eine Instanz der Klasse CTrade, mit der Handelsoperationen wie das Eröffnen, Schließen und Ändern von Aufträgen und Positionen abgewickelt werden können.

  • CChartObjectTrend Trendlinie

Stellt ein Trendlinienobjekt dar, das im Chart gezeichnet und bearbeitet werden kann.

  • MqlAppData AppData

Eine Instanz unserer MqlAppData-Struktur, die es uns ermöglicht, alle Daten auf Anwendungsebene zu speichern und von überall im Code darauf zuzugreifen. 

Wir deklarieren auch Indikator-Handles und einige Arrays, um Werte zu speichern, die von unserem nutzerdefinierten Heikin Ashi und dem integrierten Fractals-Indikator gelesen werden. Diese enthalten Echtzeit-Indikatorwerte, sodass unser EA die Preisbewegung analysieren und gültige Ausbrüche erkennen kann.

In unserer Funktion OnTick() aktualisieren wir zunächst unsere globalen Variablen, damit sie bei jedem Tick die aktuellsten Markt- und Kontodaten widerspiegeln. Fügen wir den folgenden Codeblock direkt am Anfang der Funktion OnTick() ein.

...

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Scope variables
   AppData.bidPrice           = SymbolInfoDouble (_Symbol, SYMBOL_BID);
   AppData.askPrice           = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   AppData.currentBalance     = AccountInfoDouble(ACCOUNT_BALANCE);
   AppData.currentEquity      = AccountInfoDouble(ACCOUNT_EQUITY);
   AppData.amountAtRisk       = (riskPerTradePercent/100.0) * AppData.currentBalance;
}

...
  • AppData.bidPrice

Ruft den aktuellen Geldkurs (Bid) für ein bestimmtes Finanzpapier ab, auf dem der Chart läuft, und speichert ihn.

  • AppData.askPreis

Ermittelt und speichert den aktuellen Briefkurs (Ask) für ein bestimmtes Finanzpapier, für das der Chart gerade läuft.

  • AppData.currentBalance

Ruft den aktuellen Kontostand bei jeder Preisänderung ab und zeichnet ihn auf.

  • AppData.currentEquity

Ruft den Echtzeit-Kapitalwert ab und speichert ihn.

  • AppData.amountAtRisk

Berechnet den tatsächlichen Risikobetrag für den nächsten Handel basierend auf dem Parameter riskPerTradePercent und dem aktuellen Kontostand.

Nachdem wir unsere Datenstrukturen und globalen Variablen eingerichtet haben, können wir im nächsten Schritt die Indikatoren einführen, die unsere Strategie steuern. Wir beginnen mit der Initialisierung von Indikator-Handles sowohl für den nutzerdefinierten Heikin Ashi-Indikator, den wir im ersten Teil erstellt haben, als auch für den Standard-Fraktal-Indikator, der in MetaTrader 5 verfügbar ist. Sobald die Handles initialisiert sind, werden wir Echtzeitdaten aus ihren Puffern lesen, sodass der EA sie nutzen kann, um gültige Ausbruchssignale zu erkennen. Um eine effiziente Speichernutzung zu gewährleisten, werden wir auch die Indikator-Handles freigeben, wenn sie nicht mehr benötigt werden. Darüber hinaus werden wir unseren nutzerdefinierten Heikin Ashi-Indikator als Ressource innerhalb der EA-Datei verpacken, sodass er als eine einzige, in sich geschlossene Datei verteilt werden kann, ohne dass die Nutzer den Indikator manuell separat installieren müssen. 

Bevor wir unseren nutzerdefinierten Heikin Ashi-Indikator als Ressource verpacken können, müssen wir ihn zunächst selbst erstellen. Bereiten Sie eine neue leere Indikator-Quelldatei in MetaEditor vor und nennen Sie sie „heikinAshiindicator.mq5“. Kopieren Sie dann den beigefügten Indikator-Quellcode, fügen Sie ihn in diese Datei ein und kompilieren Sie ihn. Nach erfolgreicher Kompilierung erzeugt MetaTrader die Datei „heikinAshiindicator.ex5“. Wir werden diese kompilierte Datei dann als Ressource verpacken, damit sie Teil unseres EA wird.

...

#property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/en/users/chachaian"
#property version   "1.10"
#resource "\\Indicators\\heikinAshiIndicator.ex5"

...

Damit wird der Compiler angewiesen, die kompilierte Indikator-Datei (heikinAshiIndicator.ex5) in den EA einzubetten. Auf diese Weise müssen die Nutzer den Indikator nicht manuell in ihrem Indikatorenverzeichnis installieren. Der EA wird immer Zugriff darauf haben, solange die Datei zur Kompilierzeit vorhanden ist. Dies erleichtert die Verteilung und gewährleistet eine nahtlose Installation für die Endnutzer.

Als Nächstes initialisieren wir die Indikator-Handles in der Funktion OnTick(), damit unser Expert Advisor auf Echtzeitdaten sowohl des nutzerdefinierten Heikin Ashi-Indikators als auch des integrierten Fractals-Indikators zugreifen kann.

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...
   
   //--- Initialize global variables
   AppData.currentBalance        = AccountInfoDouble(ACCOUNT_BALANCE);
   AppData.currentEquity         = AccountInfoDouble(ACCOUNT_EQUITY);
   AppData.lastDailyCheckTime    = iTime(_Symbol, PERIOD_D1, 0);
   AppData.lastBarOpenTime       = 0;
   AppData.digitValue            = SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   AppData.contractSize          = SymbolInfoDouble (_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   
   //--- Initialize the Heikin Ashi indicator
   heikinAshiIndicatorHandle     = iCustom(_Symbol, timeframe, "::Indicators\\heikinAshiIndicator.ex5");
   if(heikinAshiIndicatorHandle  == INVALID_HANDLE){
      Print("Error while initializing The Heikin Ashi Indicator: ", GetLastError());
      return INIT_FAILED;
   }
   
   //--- Initialize the Fractals indicator
   fractalsIndicatorHandle = iFractals(_Symbol, timeframe);
   if(fractalsIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing The Fractals Indicator: ", GetLastError());
      return INIT_FAILED;
   }
}

...

Wir haben gerade den Code hinzugefügt, der notwendig ist, um die beiden Indikatoren zu initialisieren, die wir in unserem EA verwenden werden.

  • Heikin Ashi Indikator

Wir verwenden iCustom(), um unseren Heikin Ashi-Indikator zu laden. Wenn das Handle nicht erfolgreich erstellt wird, gibt der EA eine Fehlermeldung im Expertenjournal aus und beendet sich. Dies ist nützlich für die Fehlersuche und stellt außerdem sicher, dass der EA nicht ohne seine primäre Signalquelle läuft.

  • Fraktal-Indikator
Wir verwenden iFractals(), um den eingebauten Fractals-Indikator zu initialisieren. Auch hier prüfen wir, ob ein gültiges Handle vorliegt, und wenn die Initialisierung fehlschlägt, geben wir den entsprechenden Fehler aus und stoppen die weitere Ausführung des EA.

Sobald die Indikatoren initialisiert sind, besteht der nächste Schritt innerhalb der Funktion OnTick() darin, Daten aus ihren Puffern zu lesen. Fügen Sie nun den folgenden Codeblock in die Funktion OnTick() ein und platzieren Sie ihn direkt unter den Anweisungen zur Zuweisung der globalen Variablen.

...

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Scope variables
   AppData.bidPrice           = SymbolInfoDouble (_Symbol, SYMBOL_BID);
   AppData.askPrice           = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   AppData.currentBalance     = AccountInfoDouble(ACCOUNT_BALANCE);
   AppData.currentEquity      = AccountInfoDouble(ACCOUNT_EQUITY);
   AppData.amountAtRisk       = (riskPerTradePercent/100.0) * AppData.currentBalance;  

   //--- Get a few Heikin Ashi values
   int copiedHeikinAshiOpen = CopyBuffer(heikinAshiIndicatorHandle, 0, 0, 10, heikinAshiOpen);
   if(copiedHeikinAshiOpen  == -1){
      Print("Error while copying Heikin Ashi Open prices: ", GetLastError());
      return;
   }
   
   int copiedHeikinAshiHigh = CopyBuffer(heikinAshiIndicatorHandle, 1, 0, 10, heikinAshiHigh);
   if(copiedHeikinAshiHigh  == -1){
      Print("Error while copying Heikin Ashi High prices: ", GetLastError());
      return;
   }
   
   int copiedHeikinAshiLow = CopyBuffer(heikinAshiIndicatorHandle, 2, 0, 10, heikinAshiLow);
   if(copiedHeikinAshiLow  == -1){
      Print("Error while copying Heikin Ashi Low prices: ", GetLastError());
      return;
   }
   
   int copiedHeikinAshiClose = CopyBuffer(heikinAshiIndicatorHandle, 3, 0, 10, heikinAshiClose);
   if(copiedHeikinAshiClose  == -1){
      Print("Error while copying Heikin Ashi Close prices: ", GetLastError());
      return;
   }
   
   //--- Get the latest Fractals indicator values
   int copiedSwingHighs = CopyBuffer(fractalsIndicatorHandle, 0, 0, 200, swingHighs);
   if(copiedSwingHighs == -1){
      Print("Error while copying fractal's indicator swing highs: ", GetLastError());
   }
   
   int copiedSwingLows = CopyBuffer(fractalsIndicatorHandle, 1, 0, 200, swingLows);
   if(copiedSwingLows == -1){
      Print("Error while copying fractal's indicator swing lows: ", GetLastError());
   }
}

...

Wir verwenden CopyBuffer(), um die letzten 10 Werte der Eröffnungs-, Höchst-, Tiefst- und Schlusskurse aus dem Heikin Ashi-Indikator abzurufen. In ähnlicher Weise kopieren wir die letzten 200 Werte der hohen und tiefen Umkehrpunkte des Fractals-Indikators. Wenn das Kopieren fehlschlägt, wird in jedem Fall ein Fehler protokolliert, aber die Programmausführung nicht abgebrochen.

Der nächste Schritt besteht darin, unsere Daten-Arrays mit ArraySetAsSeries() als Serien zu definieren. Diese Funktion kehrt die Indizierungsrichtung unserer Arrays um, sodass das Element 0 den Datenpunkt des jüngsten Balkens darstellt und höhere Indizes ältere Balken repräsentieren. Wir fügen den folgenden Codeblock direkt unter dem Abschnitt ein, in dem wir unsere Indikator-Handles initialisieren.

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   ...
   
   //--- Initialize the Heikin Ashi indicator
   heikinAshiIndicatorHandle     = iCustom(_Symbol, timeframe, "::Indicators\\heikinAshiIndicator.ex5");
   if(heikinAshiIndicatorHandle  == INVALID_HANDLE){
      Print("Error while initializing The Heikin Ashi Indicator: ", GetLastError());
      return INIT_FAILED;
   }
   
   //--- Initialize the Fractals indicator
   fractalsIndicatorHandle = iFractals(_Symbol, timeframe);
   if(fractalsIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing The Fractals Indicator: ", GetLastError());
      return INIT_FAILED;
   }
   
   //--- Set Arrays as series
   ArraySetAsSeries(heikinAshiOpen,  true);
   ArraySetAsSeries(heikinAshiHigh,  true);
   ArraySetAsSeries(heikinAshiLow,   true);
   ArraySetAsSeries(heikinAshiClose, true);
   ArraySetAsSeries(swingHighs,      true);
   ArraySetAsSeries(swingLows,       true); 
   
   return(INIT_SUCCEEDED);
}

...

Dies ist äußerst hilfreich, denn so kann der EA einfach und konsistent auf die neuesten Werte zugreifen.

Der letzte Schritt bei der Arbeit mit Indikatoren besteht darin, sicherzustellen, dass ihre Ressourcen ordnungsgemäß freigegeben werden, sobald der Expert Advisor vom Chart getrennt wird. Dies geschieht in der Funktion OnDeinit(), die automatisch ausgeführt wird, wenn ein EA vom Chart abgetrennt wird. Indem wir IndicatorRelease() für unsere Indikator-Handles aufrufen, geben wir den von ihnen belegten Speicher frei. 

...

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

   //--- Free up memory used by indicators
   if(heikinAshiIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(heikinAshiIndicatorHandle);
   }
   
   if(fractalsIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(fractalsIndicatorHandle);
   }
   
}

...

Es ist immer eine gute Praxis, diesen Bereinigungsschritt in jeden EA aufzunehmen, der entweder nutzerdefinierte oder integrierte Indikatoren verwendet.

Bevor Sie die Indikator-Handles freigeben, ist es ratsam, die Charts von allen grafischen Objekten zu befreien, die unser EA möglicherweise erstellt hat. Dadurch wird sichergestellt, dass keine verirrten Trendlinien auf dem Chart verbleiben, sobald der EA vom Chart getrennt wird. Dies wird durch den Aufruf von ObjectsDeleteAll(0) innerhalb der Funktion OnDeinit() erreicht. Lassen Sie uns nun fortfahren und dies kurz vor dem Abschnitt zur Speicherfreigabe tun.

...

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

      //--- Delete all graphical objects
      ObjectsDeleteAll(0);

   //--- Free up memory used by indicators
   if(heikinAshiIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(heikinAshiIndicatorHandle);
   }  
 
   ...
   
}

...

In diesem Stadium sollte das Kompilieren des Quellcodes keine Fehler mehr erzeugen, da alle erforderlichen globalen Variablen korrekt definiert und initialisiert wurden. 

Schließlich haben wir das Herzstück unseres Expert Advisors erreicht – die zentrale Handelslogik. Dieser Codeblock wird nur ausgeführt, wenn ein neuer Balken geöffnet wird, um sicherzustellen, dass die Signale nur einmal am Ende einer Kerze ausgewertet werden. Fügen wir unsere Haupt-Handelslogik direkt unter dem bestehenden Code innerhalb der Funktion OnTick() ein.

...

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   ...
   
   //--- Get the latest Fractals indicator values
   int copiedSwingHighs = CopyBuffer(fractalsIndicatorHandle, 0, 0, 200, swingHighs);
   if(copiedSwingHighs == -1){
      Print("Error while copying fractal's indicator swing highs: ", GetLastError());
   }
   
   int copiedSwingLows = CopyBuffer(fractalsIndicatorHandle, 1, 0, 200, swingLows);
   if(copiedSwingLows == -1){
      Print("Error while copying fractal's indicator swing lows: ", GetLastError());
   }
   
   //--- Run this block on new bar open
   if(IsNewBar(_Symbol, timeframe, AppData.lastBarOpenTime)){
      
      datetime timeStart  = 0;
      int      indexStart = 0;
      datetime timeEnd    = 0;
      int      indexEnd   = 0;
      
      //--- Handle Bullish Signals
      if(IsBullishSignal(timeStart, indexStart, timeEnd, indexEnd)){
         if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
            OpenBuy();
         }
         double high = iHigh(_Symbol, timeframe, indexStart);
         TrendLine.Create(0, GenerateUniqueName(zenBreakout), 0, timeStart, high, timeEnd, high);
      }
      
      //--- Handle Bearish Signals
      if(IsBearishSignal(timeStart, indexStart, timeEnd, indexEnd)){
         if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
            OpenSel();
         }
         double low  = iLow (_Symbol, timeframe, indexStart);
         TrendLine.Create(0, GenerateUniqueName(zenBreakout), 0, timeStart, low, timeEnd, low);
      }
   }

}

...

In diesem Abschnitt deklarieren wir zunächst einige Hilfsvariablen (timeStart, indexStart, timeEnd, indexEnd), die gefüllt werden, wenn ein gültiges Signal gefunden wird.

Anschließend rufen wir unsere eigene Funktion IsBullishSignal() auf. Wenn ein Aufwärts-Setup erkannt wird und keine aktiven Kauf- oder Verkaufspositionen vorhanden sind, ruft der EA OpenBuy() auf, um eine neue Kaufposition zu eröffnen. Unmittelbar danach wird mit TrendLine.Create() eine Trendlinie über den identifizierten Balkenbereich gezeichnet, die visuell markiert, wo das Signal auftritt.

Die gleiche Logik gilt für Abwärtssignale, aber in diesem Fall ruft der EA OpenSel() auf, um eine Verkaufsorder zu eröffnen, und zieht eine Trendlinie am Tiefpunkt des Schwungs, an dem das Baisse-Setup erkannt wurde.

Dieser gesamte Block ist von entscheidender Bedeutung, da er die früheren Einstellungen – Eingabeparameter, Indikatoren und globale Variablen – miteinander verknüpft, um schließlich umsetzbare Handelsentscheidungen zu treffen.

Herzlichen Glückwunsch, dass Sie es so weit geschafft haben! Zu diesem Zeitpunkt ist unser Expert Advisor vollständig entwickelt und sollte fehlerfrei kompiliert werden. Ich empfehle Ihnen, die beigefügte Quelldatei herunterzuladen und sie mit Ihrer eigenen Implementierung zu vergleichen. So können Sie übersehene Schritte oder Tippfehler erkennen und sicherstellen, dass Ihr Code mit dem übereinstimmt, den wir gemeinsam erstellt haben. Wenn Sie sich die Zeit nehmen, Ihre Arbeit zu überprüfen und zu vergleichen, ist das eine gute Möglichkeit, Ihr Verständnis zu vertiefen und Vertrauen aufzubauen, bevor Sie den EA in einem Chart testen.



Tests

Ich habe einen Backtest mit Gold als Finanzinstrument durchgeführt, der den Zeitraum vom 1. Januar 2025 bis zum 31. August 2025 abdeckt. Die Eingabeparameter wurden wie folgt konfiguriert:

  • magicNumber: 254700680002
  • timeFrame: H1
  • lotSizeMode: MODE_AUTO
  • riskPerTradePercent: 1.0
  • lotSize: 0.1
  • RRr: ONE_TO_ONEandHALF

Ausgehend von einem Kontostand von 100.000 $ zeigt der Backtest einen Kapitalzuwachs von etwas mehr als 12 % über den 8-Monats-Zeitraum.

Strategie-Testbericht

Nachfolgend ist die Wachstumskurve des Kapitals dargestellt:

Wachstumskurve des Kapitals

Die Kapitalkurve zeigt, dass die derzeitige Strategie in etwa kostendeckend ist, was ein positives Zeichen ist, da es zeigt, dass der Ansatz nicht völlig unrentabel ist. Ich glaube, dass die Leistung durch die Optimierung der Parameter und die Einbeziehung fortgeschrittener Signalfilter wie Handelssitzungen weiter verbessert werden kann.


Schlussfolgerung

Wir haben die Entwicklung unseres Expert Advisors erfolgreich abgeschlossen. Gemeinsam gingen wir jeden Schritt durch, von der Einrichtung von Eingabeparametern, Enumerationen und globalen Variablen bis hin zur Initialisierung von Indikatoren, dem Lesen von Daten aus ihren Puffern und der Implementierung der zentralen Handelslogik.

Wir haben jetzt einen voll funktionsfähigen Expert Advisor, der automatisch nach unserer Heikin Ashi-Logik handelt. Der Backtest mit Gold zeigte eine relativ stabile Kapitalkurve, was bestätigt, dass die Logik wie beabsichtigt funktioniert und der EA ohne Fehler kompiliert wird. Dies ist ein wichtiger Meilenstein.

Versuchen Sie von hier aus, die Parameter zu optimieren, um zu sehen, ob Sie die Rentabilität verbessern können, oder fügen Sie zusätzliche Filter wie Session Timing oder Volatilitätsprüfungen hinzu. Die Arbeit, die wir geleistet haben, bildet eine solide Grundlage für den Aufbau anspruchsvollerer automatisierter Systeme, die mit realen Marktbedingungen umgehen können.

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

Beigefügte Dateien |
zenBreakout.mq5 (20.43 KB)
Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (XI) – Korrelation im Nachrichtenhandel Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (XI) – Korrelation im Nachrichtenhandel
In diesem Beitrag werden wir untersuchen, wie das Konzept der Finanzkorrelation angewendet werden kann, um die Entscheidungseffizienz beim Handel mit mehreren Symbolen während der Ankündigung wichtiger wirtschaftlicher Ereignisse zu verbessern. Der Schwerpunkt liegt dabei auf der Bewältigung des erhöhten Risikos, das durch die erhöhte Volatilität bei der Veröffentlichung von Nachrichten entsteht.
Entwicklung eines nutzerdefinierten Indikators für die Kontoperformance-Matrix Entwicklung eines nutzerdefinierten Indikators für die Kontoperformance-Matrix
Dieser Indikator fungiert als Disziplinierungsinstrument, indem er Kontokapital, Gewinn/Verlust und Drawdown in Echtzeit verfolgt und ein Performance-Dashboard anzeigt. Es kann den Händlern helfen, konsistent zu bleiben, übermäßiges Handeln zu vermeiden und die Regeln für die Anfechtung von Prop-Firmen einzuhalten.
Aufbau von KI-gesteuerten Handelssystemen in MQL5 (Teil 2): Entwicklung eines ChatGPT-integrierten Programms mit Nutzeroberfläche Aufbau von KI-gesteuerten Handelssystemen in MQL5 (Teil 2): Entwicklung eines ChatGPT-integrierten Programms mit Nutzeroberfläche
In diesem Artikel entwickeln wir ein in ChatGPT integriertes Programm in MQL5 mit einer Nutzeroberfläche, das das JSON-Parsing-Framework aus Teil 1 nutzt, um Prompts an die API von OpenAI zu senden und die Antworten auf einem MetaTrader 5-Chart anzuzeigen. Wir implementieren ein Dashboard mit einem Eingabefeld, einer Übermittlungsschaltfläche und einer Antwortanzeige, wobei wir die API-Kommunikation und den Textumbruch für die Nutzerinteraktion übernehmen.
Entwicklung von Handelsstrategien mit den Oszillatoren Parafrac und Parafrac V2: Einzeleintritt Performance Insights Entwicklung von Handelsstrategien mit den Oszillatoren Parafrac und Parafrac V2: Einzeleintritt Performance Insights
In diesem Artikel werden der ParaFrac Oscillator und sein V2-Modell als Handelsinstrumente vorgestellt. Es werden drei Handelsstrategien vorgestellt, die mit Hilfe dieser Indikatoren entwickelt wurden. Jede Strategie wurde getestet und optimiert, um ihre Stärken und Schwächen zu ermitteln. Die vergleichende Analyse zeigte die Leistungsunterschiede zwischen dem Original und dem V2-Modell auf.