English Русский 中文 Español 日本語 Português
preview
Entwicklung fortschrittlicher ICT-Handelssysteme: Implementierung von Signalen in den Indikator "Order Block"

Entwicklung fortschrittlicher ICT-Handelssysteme: Implementierung von Signalen in den Indikator "Order Block"

MetaTrader 5Beispiele |
139 1
Niquel Mendoza
Niquel Mendoza
  1. Einführung



Einführung

Willkommen zu unserem Artikel über MQL5! In diesem Teil werden wir uns auf das Hinzufügen von Puffern und Einstiegssignalen zu unserem Indikator konzentrieren und damit die wesentlichen Funktionen vervollständigen, die für den Einsatz in automatisierten Handelsstrategien erforderlich sind.

Wenn Sie neu in dieser Serie sind, empfehlen wir Ihnen, den ersten Teil zu lesen, in dem wir erklärt haben, wie man den Indikator von Grund auf erstellt und die grundlegenden Konzepte behandelt.


Erkennung von Auftragsblockaden anhand der Markttiefe

Unsere Logik zur Identifizierung von Auftragsblockaden anhand der Markttiefe ist wie folgt aufgebaut:

 Algorithmus            

  1. Array-Erstellung: Wir erstellen zwei Arrays, um das Volumen jeder Kerze in der Markttiefe zu speichern. So können wir Volumendaten effizient organisieren und analysieren.
  2. Erhebung von Markttiefedaten – für den Fall.                                
void  OnBookEvent( )       

In der Funktion OnBookEvent() erfassen wir jede Änderung der Markttiefe und zeichnen neue Volumendaten auf, um Aktualisierungen in Echtzeit zu gewährleisten.

      3. Regeln zur Validierung von Auftragsblockaden: Sobald die Volumina in den Arrays gespeichert sind, werden wir Preisaktionsregeln zusammen mit diesen Daten anwenden, um einen Auftragsblockade zu validieren.

Regeln zur Identifizierung von Auftragsblockaden mit Markttiefe

Bei der Entwicklung unseres Indikators haben wir zunächst nach Auftragsblockaden innerhalb einer bestimmten Kerzen-Spanne gesucht. Im Falle der Markttiefe werden wir jedoch keinen großen „x-Bereich“ verwenden. Stattdessen werden wir uns speziell auf die dritte Kerze konzentrieren (wobei Kerze 0 die aktuelle Kerze ist).

Regeln Steigender „Order Block“ Fallender „Order Block“ 
Volumenspitze bei Kerze 3: Das Kaufvolumen von Kerze 3 muss das kombinierte Kauf- und Verkaufsvolumen von Kerze 2 und 4 in einem bestimmten Verhältnis übersteigen. Das Verkaufsvolumen von Kerze 3 muss das kombinierte Kauf- und Verkaufsvolumen von Kerze 2 und 4 in einem bestimmten Verhältnis übersteigen.
3 aufeinanderfolgende Kerzen: Es müssen drei aufeinanderfolgende Aufwärtskerzen sein.
(Kerzen 1, 2 und 3)
Es müssen drei aufeinanderfolgende Abwärtskerzen sein.
(Kerzen 1, 2 und 3)
Körper der Kerze 3: Das Tief von Kerze 2 muss größer sein als die Hälfte des Körpers von Kerze 3. Das Hoch von Kerze 2 muss niedriger sein als die Hälfte des Körpers von Kerze 3.
Kerze 3 Hoch oder Tief: Das Hoch von Kerze 3 muss niedriger sein als der Schlusskurs von Kerze 2. Das Tief von Kerze 3 muss höher sein als der Schlusskurs von Kerze 2.

Mit diesen Regeln stellen wir Folgendes sicher:

  • Ungleichgewicht zwischen Kauf und Verkauf: Wir überprüfen ein signifikantes Ungleichgewicht bei Käufen oder Verkäufen innerhalb einer bestimmten Kerze, bei dem die Kauf- oder Verkaufsaufträge die der vorherigen und nächsten Kerze um ein bestimmtes Verhältnis übersteigen.
  • Kontrolle des Kerzenkörpers im Ungleichgewicht: Wir bestätigen, dass nicht ausgeführte Aufträge, die durch einen Nachfrage- oder Angebotsüberhang verursacht wurden, nicht von der nachfolgenden Kerze absorbiert werden, wodurch die Persistenz der Auftragsblockade bestätigt wird.
  • Stark steigende oder fallende Bewegung: Wir bestätigen, dass das Muster eine entscheidende Aufwärts- oder Abwärtsbewegung widerspiegelt und die Intensität des Ungleichgewichts im Kursgeschehen unterstreicht.

Mit all dem im Hinterkopf können wir nun die Logik in Code umsetzen.


Initialisierung und Abschluss des Ereignisses „Order Book“ und Erstellung von Arrays

Arrays erstellen

Bevor Sie das Orderbuch verwenden, müssen Sie dynamische Arrays erstellen, in denen Volumendaten gespeichert werden. Diese Arrays werden vom Typ sein:

long

Sie werden zur Speicherung von Kauf- bzw. Verkaufsmengen verwendet.

  1. Gehen wir zum globalen Abschnitt des Programms und deklarieren die dynamischen Arrays:

long buy_volume[];
long sell_volume[];

      2. Wir ändern im OnInit die Größe der Arrays, sodass sie eine Anfangsgröße von 1 haben. Dann weisen wir dem Index 0 eines jeden Arrays den Wert 0 zu:

  ArrayResize(buy_volume,1);
  ArrayResize(sell_volume,1);
  buy_volume[0] = 0.0;
  sell_volume[0] = 0.0;

Initialisierung und Abschluss des Ereignisses der Markttiefe

Bevor wir mit der Markttiefe beginnen, erstellen wir eine globale Variable, die anzeigt, ob diese Funktion verfügbar ist. Dadurch vermeiden wir die Verwendung von:

INIT_FAILED
Nicht alle Symbole bei jedem Broker bieten gehandelte Volumen in Markttiefe. Bei diesem Ansatz ist der Indikator nicht ausschließlich von einem Broker abhängig, der diese Funktion anbietet.

  • Um zu prüfen, ob das Symbol, mit dem Sie handeln möchten, die Markttiefe mit dem gehandelten Volumen unterstützt, gehen Sie wie folgt vor:

1. Klicken Sie auf die linke obere Ecke Ihres Charts in dem entsprechenden Feld: 

2. Überprüfen wir, ob für das Symbol ein Marktvolumen in der Tiefe verfügbar ist. Wenn dies der Fall ist, erhalten wir eine Bestätigung ähnlich der folgenden Abbildungen.

Symbol mit Markttiefe (Depth of Market):

Markttiefe 2

Symbol ohne Markttiefe (Depth of Market):

 ETHUSD Markttiefe

Wie bereits erwähnt, ist das Volumen der Markttiefe nicht für alle Instrumente verfügbar und hängt von Ihrem Broker ab.

Kommen wir nun zur Initialisierung und Vervollständigung der Markttiefe.

1. Globale Steuervariable

Wir definieren eine globale boolesche Variable zur Kennzeichnung der Verfügbarkeit von Markttiefe:

bool use_market_book = true; //true by default

Diese Variable beginnt mit true, wird aber geändert, wenn die Markttiefe nicht initialisiert werden kann.

2. Initialisierung der Markttiefe

Um die Markttiefe zu initialisieren, verwenden wir die Funktion: 

MarketBookAdd()

Dies öffnet die Markttiefe für ein bestimmtes Symbol. Die Funktion benötigt das aktuelle Symbol:

_Symbol

Innerhalb des OnInit-Ereignisses wird geprüft, ob die Initialisierung erfolgreich war:

 if(!MarketBookAdd(_Symbol)) //Verify initialization of the order book for the current symbol
     {
      Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Print error in case of failure
      use_market_book = false; //Mark use_market_book as false if initialization fails
     }

3. Abschluss der Markttiefe

In OnDeinit geben wir die Markttiefe frei mit:

 MarketBookRelease()

Wir prüfen dann das Schließen und drucken eine entsprechende Meldung:

//---
   if(MarketBookRelease(_Symbol)) //Verify if closure was successful
     Print("Order book successfully closed for: " , _Symbol); //Print success message if so
   else
     Print("Order book closed with errors for: " , _Symbol , "   Last error: " , GetLastError()); //Print error message with code if not


Erfassen der Daten der Markttiefe für die Volumendetektion in Arrays

Nachdem die Markttiefe initialisiert wurde, können wir mit der Erfassung relevanter Daten beginnen. Zu diesem Zweck erstellen wir das Ereignis OnBookEvent, das jedes Mal ausgelöst wird, wenn eine Änderung der Markttiefe eintritt.

  1. Erstellen von OnBookEvent:

void OnBookEvent(const string& symbol)
     2. Überprüfung des Symbols und der Verfügbarkeit der Markttiefe:
 if(symbol !=_Symbol || use_market_book == false)
      return; 
// Exit the event if conditions are not met

Mit dieser Prüfung kann die komplette Funktion OnBookEvent wie folgt aufgebaut werden:    

void OnBookEvent(const string& symbol)
  {
   if(symbol !=_Symbol || use_market_book == false)
      return;
// Define array to store Market Book data
   MqlBookInfo book_info[];

// Retrieve Market Book data
   bool book_count = MarketBookGet(_Symbol,book_info);

// Verify if data was successfully obtained
   if(book_count == true)
     {
      // Iterate through Market Book data
      for(int i = 0; i < ArraySize(book_info); i++)
        {
         // Check if the record is a buy order (BID)
         if(book_info[i].type == BOOK_TYPE_BUY  || book_info[i].type ==  BOOK_TYPE_BUY_MARKET)
           {
           
            buy_volume[0] += book_info[i].volume;
           }
         // Check if the record is a sell order (ASK)
         if(book_info[i].type == BOOK_TYPE_SELL || book_info[i].type == BOOK_TYPE_SELL_MARKET)
           {
            sell_volume[0] += book_info[i].volume;
           }
        }
     }
   else
     {
      Print("No Market Book data retrieved.");
     }
  }

Erläuterung des Kodex:

  • Abrufen des Volumens: Jedes Mal, wenn sich die Markttiefe ändert, sammelt OnBookEvent das Volumen der zuletzt registrierten Aufträge.
  • Aktualisierungen des Arrays: Es trägt Kauf- und Verkaufsvolumen in den Index 0 der buy_volume- bzw. sell_volume-Arrays ein.

Um sicherzustellen, dass die Arrays das Markttiefenvolumen für jede neue Kerze akkumulieren und eine fortlaufende Historie (z. B. 30 Elemente) führen, sind die folgenden Anpassungen erforderlich.

1. Neue Überprüfung der Kerze und Zählervalidierung (mehr als 1)

Um Fehlalarme beim Programmstart zu vermeiden und sicherzustellen, dass die Arrays nur dann aktualisiert werden, wenn ein neuer Kerze geöffnet wird (und nach mindestens einer vorherigen Öffnung), implementieren wir eine Prüfung, die die Zählervariable mit new_vela kombiniert. Auf diese Weise wird sichergestellt, dass das Array nur dann aktualisiert wird, wenn wirklich neue Informationen verfügbar sind.

Deklaration von statischen Variablen
Wir deklarieren den Zähler als statische Variable, damit er zwischen Aufrufen von OnCalculate bestehen bleibt. Die Variable new_vela sollte anzeigen, ob eine neue Kerze eröffnet wurde.
static int counter = 0;

Neue Bedingung für die Überprüfung von Kerzen und Zählern
Wir überprüfen, ob der Zähler größer als 1 ist, ob new_vela wahr ist und ob die Markttiefe verfügbar ist. Nur wenn alle diese Bedingungen erfüllt sind, werden wir die Größe der Arrays ändern und ihre Elemente verschieben. Dadurch wird eine vorzeitige Größenanpassung verhindert und sichergestellt, dass Aktualisierungen nur dann erfolgen, wenn gültige Daten verfügbar sind und das Marktbuch ein gehandeltes Volumen für das aktuelle Symbol liefert.

if(counter > 1 && new_vela == true && use_market_book == true)

Zähler-Update
Der Zähler wird jedes Mal um 1 erhöht, wenn eine neue Kerze erkannt wird.

counter++;

2. Steuerung der Array-Größe

Wir überprüfen, dass die Arrays eine maximale Größe von 30 Elementen nicht überschreiten. Ist dies der Fall, wird die Größe auf 30 zurückgesetzt und das älteste Element verworfen:

if(ArraySize(buy_volume) >= 30)
{
   ArrayResize(buy_volume, 30); // Keep buy_volume size at 30
   ArrayResize(sell_volume, 30); // Keep sell_volume size at 30
}

3. Größenanpassung für neue Werte

Wir fügen den Arrays einen zusätzlichen Slot hinzu, um den neuen Datenträger an der Anfangsposition zu speichern:

ArrayResize(buy_volume, ArraySize(buy_volume) + 1);
ArrayResize(sell_volume, ArraySize(sell_volume) + 1);

4. Sich verschiebende Elemente

Wir verschieben alle Array-Elemente um eine Position nach vorne. Dadurch wird sichergestellt, dass die neuesten Daten immer bei Index 0 gespeichert werden, während ältere Werte auf höhere Indizes verschoben werden.

for(int i = ArraySize(buy_volume) - 1; i > 0; i--)
{
   buy_volume[i] = buy_volume[i - 1];
   sell_volume[i] = sell_volume[i - 1];
}

5. Überprüfung des Volumens

Wir drucken die Kauf- und Verkaufsvolumina an Position 1 der Arrays, um das für die letzte Kerze aufgezeichnete Volumen zu überprüfen:

Print("Buy volume of the last candle: ", buy_volume[1]);
Print("Sell volume of the last candle: ", sell_volume[1]);

6. Volumen zurücksetzen

Wir setzen den Index 0 beider Arrays auf 0 zurück, damit sie mit der Akkumulation des Volumens für die neue Kerze beginnen:

buy_volume[0] = 0;
sell_volume[0] = 0;

7. Bedingung zur Vermeidung von Fehlern durch inkonsistente Marktbuchdaten

Eine zusätzliche Sicherheitsvorkehrung wird eingeführt, um use_market_book automatisch zu deaktivieren, wenn die Werte von buy_volume und sell_volume bei den jüngsten Positionen (Indizes 3, 2 und 1) alle null sind. Diese Anpassung ist notwendig, weil ein Symbol, auch wenn es im Live-Handel Marktbuchdaten zu haben scheint, bei der Ausführung im Strategietester auch aktiv erscheinen kann, die Arrays jedoch aufgrund fehlender Aktualisierungen der Markttiefe möglicherweise nicht korrekt gefüllt werden. Dies kann dazu führen, dass Nullen gespeichert werden, was dazu führen kann, dass der Indikator falsche Informationen verarbeitet.

Diese Überprüfung stellt sicher, dass der Indikator keine bedeutungslosen Daten verarbeitet und dass use_market_book nur angewendet wird, wenn das Marktbuch gültige Werte enthält.

if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4)
        {
         if(buy_volume[3] == 0 && sell_volume[3] == 0 &&  buy_volume[2] == 0 && sell_volume[2] == 0 &&  buy_volume[1] == 0 && sell_volume[1] == 0)  use_market_book = false;       
        }     

Integriertes Codefragment

if(counter > 1 && new_vela == true && use_market_book == true)
     {
      if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4)
        {
         if(buy_volume[3] == 0 && sell_volume[3] == 0 &&  buy_volume[2] == 0 && sell_volume[2] == 0 &&  buy_volume[1] == 0 && sell_volume[1] == 0)  use_market_book = false;       
        }
      
       // If array size is greater than or equal to 30, resize to maintain a fixed length
     if(ArraySize(buy_volume) >= 30)
      {
      ArrayResize(buy_volume, 30); // Ensure buy_volume does not exceed 30 elements
      ArrayResize(sell_volume, 30); // Ensure sell_volume does not exceed 30 elements
      }   
  
      ArrayResize(buy_volume,ArraySize(buy_volume)+1);
      ArrayResize(sell_volume,ArraySize(sell_volume)+1);

      for(int i = ArraySize(buy_volume) - 1; i > 0; i--)
        {
         buy_volume[i] = buy_volume[i - 1];
         sell_volume[i] = sell_volume[i - 1];
        }

      // Reset volumes at index 0 to begin accumulating for the new candlestick 
      buy_volume[0] = 0;
      sell_volume[0] = 0;
     }


Strategie zum Auffinden von Auftragsblockaden anhand der Markttiefe

Die Strategie folgt der gleichen Logik wie zuvor, mit einem entscheidenden Unterschied: Statt durch Schleifen zu iterieren, führen wir die Prüfungen direkt bei Kerze 3 durch. Die allgemeine Logik bleibt dieselbe: Wir überprüfen bestimmte Bedingungen, identifizieren die nächstgelegene relevante Kerze (je nach Art der Auftragsblockade), weisen der Struktur die entsprechenden Werte zu und fügen dann die Auftragsblockade zum Array hinzu. Hier wenden wir das gleiche Verfahren an, allerdings in vereinfachter Form.

Beginnen wir mit dem Erstellen der Strukturen, die die Informationen der Auftragsblockade speichern werden:

OrderBlocks newVela_Order_block_Book_bajista;
OrderBlocks newVela_Order_block_Book;

1. Ausgangsbedingungen

Zunächst wird überprüft, ob die Arrays buy_volume und sell_volume mindestens 5 Elemente enthalten. Dadurch wird sichergestellt, dass genügend historische Daten für die Analyse zur Verfügung stehen. Wir bestätigen auch, dass use_market_book aktiv ist, um die Markttiefe zu verarbeiten.

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)

2. Definition der Kontrollvariablen

Wir definieren die Variable case_book, um anzuzeigen, ob eine bestimmte Volumenbedingung erfüllt ist. Das Verhältnis wird auf 1,4 gesetzt, was als Vergleichsfaktor dient, um signifikante Steigerungen des Kaufvolumens zu erkennen.

bool case_book = false;
double ratio = 1.4;

3. Volumenbedingung für einen Kauf (Case Book)

Hier wird geprüft, ob das Kaufvolumen bei Index 3 signifikant größer ist als das Volumen bei den Indizes 2 und 4, sowohl auf der Kauf- als auch auf der Verkaufsseite, wobei das Verhältnis als Multiplikator verwendet wird. Wenn diese Bedingung erfüllt ist, wird case_book aktiviert.

Im Fall eines Anstiegs:

if(buy_volume[3] > buy_volume[4] * ratio && buy_volume[3] > buy_volume[2] * ratio &&
   buy_volume[3] > sell_volume[4] * ratio && buy_volume[3] > sell_volume[2] * ratio)
{
    case_book = true;
}
Im Fall einer Verminderung:
if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio &&
sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio)
{
case_book = true;
}

4. Berechnung des Kerzenkörpers

Wir berechnen die Größe des Kerzenkörpers (body_tree) bei Index 3, indem wir den Eröffnungskurs vom Schlusskurs subtrahieren.

double body_tree = closeArray[3] - openArray[3]; 
double body_tree = openArray[3] - closeArray[3];

5. Überprüfung der Preise für eine Aufwärtsbedingung

Wir bewerten die zuvor genannten Bedingungen (siehe Tabelle oben).

Im Fall eines Anstiegs:

if(lowArray[2] > ((body_tree * 0.5) + openArray[3]) && highArray[3] < closeArray[2] &&
   closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])

Im Fall einer Verminderung:

if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] &&
            closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])

6. Identifizierung früherer Aufwärtskerzen

Wir rufen die Funktion FindFurthestAlcista auf, die nach der am weitesten entfernten Aufwärtskerze innerhalb einer 20-Kerzen-Spanne ab Index 3 sucht. Dies hilft, eine Referenzkerze für ein starkes Aufwärtsszenario zu identifizieren. Wenn eine Aufwärtskerze gefunden wird, ist ihr Index größer als 0 und der Prozess kann fortgesetzt werden.

Im Fall eines Anstiegs:

int furthestAlcista = FindFurthestAlcista(Time[3], 20);
if(furthestAlcista > 0)

7. Zuweisung von Werten zu „Order Block“

Wenn alle Bedingungen erfüllt sind, definieren wir den „Order Block“ (newVela_Order_block_Book oder newVela_Order_block_Book_bearish) mit den Werten der identifizierten Kerze.

Im Fall eines Anstiegs:

Print("Case Book Found");
datetime time1 = Time[furthestAlcista]; 
double price2 = openArray[furthestAlcista];
double price1 = lowArray[furthestAlcista]; 

//Assign the above variables to the structure
newVela_Order_block_Book.price1 = price1;
newVela_Order_block_Book.time1 = time1;
newVela_Order_block_Book.price2 = price2;
newVela_Order_block_Book.mitigated = false;
newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);
AddIndexToArray_alcistas(newVela_Order_block_Book);

Im Fall einer Verminderung:

Print("Case Book Found");
datetime time1 = Time[furthestBajista];
double price1 = closeArray[furthestBajista];
double price2 = lowArray[furthestBajista];

//Assign the above variables to the structure
newVela_Order_block_Book_bajista.price1 = price1;
newVela_Order_block_Book_bajista.time1 = time1;
newVela_Order_block_Book_bajista.price2 = price2;
newVela_Order_block_Book_bajista.mitigated = false;
newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);
AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);

Hier der vollständiger Code:

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
  {

   bool case_book = false;
   double ratio = 1.4;

   if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio &&
      sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio)
     {
      case_book = true;
     }
   double body_tree =   openArray[3] - closeArray[3];

   if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] &&
      closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])
     {
      int furthestBajista = FindFurthestBajista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlesticks before "one candle"
      if(furthestBajista  > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle".
        {
         Print("Case Book Found");
         datetime time1 = Time[furthestBajista];
         double price1 = closeArray[furthestBajista];
         double price2 = lowArray[furthestBajista];

         //Assign the above variables to the structure
         newVela_Order_block_Book_bajista.price1 = price1;
         newVela_Order_block_Book_bajista.time1 = time1;
         newVela_Order_block_Book_bajista.price2 = price2;
         newVela_Order_block_Book_bajista.mitigated = false;
         newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);

         AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);

        }
     }
  }
//--------------------    Bullish   -------------------- 

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
  {

   bool case_book = false;
   double ratio = 1.4;


   if(buy_volume[3] > buy_volume[4]*ratio && buy_volume[3] > buy_volume[2]*ratio &&
      buy_volume[3] > sell_volume[4]*ratio && buy_volume[3] > sell_volume[2]*ratio)
     {
      case_book = true;
     }
   double body_tree =  closeArray[3] - openArray[3];

   if(lowArray[2] > ((body_tree * 0.5)+openArray[3]) && highArray[3] < closeArray[2] &&
      closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])
     {
      int furthestAlcista = FindFurthestAlcista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlessticks before "one candle"
      if(furthestAlcista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle".
        {
         Print("Case Book Found");
         datetime time1 = Time[furthestAlcista]; //let's assign the index time of furthestAlcista to the variable time1
         double price2 = openArray[furthestAlcista]; //let's assign the open of furthestAlcista as price 2 (remember that we draw it on a bearish candlestick most of the time)
         double price1 = lowArray[furthestAlcista]; //let's assign the low of furthestAlcista as price 1

         //Assign the above variables to the structure
         newVela_Order_block_Book.price1 = price1;
         newVela_Order_block_Book.time1 = time1;
         newVela_Order_block_Book.price2 = price2;
         newVela_Order_block_Book.mitigated = false;
         newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);

         AddIndexToArray_alcistas(newVela_Order_block_Book);

        }
     }
  }


Erstellen von Indikatorpuffern

Um die Puffer für unseren Indikator „Order Block“ in MQL5 zu erstellen und zu konfigurieren, beginnen wir mit der globalen Definition von zwei Puffern und zwei Plots, um die Preisniveaus von steigenden und fallenden Auftragsblockaden zu speichern und anzuzeigen.

1. Deklaration von Puffern und Flächen

Wir deklarieren zwei Puffer im globalen Teil des Programms, um die Preisdaten der Auftragsblockade zu speichern. Zusätzlich definieren wir zwei Flächen, um die Auftragsblockaden im Chart zu visualisieren.

#property  indicator_buffers 2
#property  indicator_plots 2
#property indicator_label1 "Bullish Order Block"
#property indicator_label2 "Bearish Order Block"

2. Dynamische Arrays für Puffer erstellen

Wir deklarieren zwei dynamische Arrays, buyOrderBlockBuffer und sellOrderBlockBuffer, um die Preise zu speichern, die den steigenden und fallenden Auftragsblockaden entsprechen. Diese Arrays sind mit den Indikatorpuffern verknüpft, sodass die Daten der Auftragsblockade auf dem Chart visualisiert werden können.

//--- Define the buffers
double buyOrderBlockBuffer[];   // Buffer for bullish order blocks
double sellOrderBlockBuffer[];  // Buffer for bearish order blocks

Beschreibung:

  • buyOrderBlockBuffer: Speichert die Kursniveaus der steigenden Auftragsblockaden und stellt Punkte dar, an denen der Kurs Unterstützung finden könnte.
  • sellOrderBlockBuffer: Speichert die Kursniveaus von fallenden Auftragsblockaden und stellt Punkte dar, an denen der Kurs auf Widerstand stoßen könnte.


Ändern der Funktion OnInit zum Konfigurieren von Puffern

In diesem Abschnitt passen wir die Funktion OnInit an, um die Indikatorpuffer zu konfigurieren, indem wir den Indikatorpuffern die Arrays der steigenden und fallenden Auftragsblockaden zuweisen. Dadurch wird sichergestellt, dass der Indikator die Daten korrekt speichert und im Chart anzeigt.

Schritte:

1. Datenpuffer mit SetIndexBuffer zuweisen

In OnInit weisen wir die Arrays buyOrderBlockBuffer und sellOrderBlockBuffer mit SetIndexBuffer den Indikatorpuffern zu. Dadurch wird sichergestellt, dass die Arrays Daten speichern und im Chart anzeigen können.

//--- Assign data buffers to the indicator
   SetIndexBuffer(0, buyOrderBlockBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, sellOrderBlockBuffer, INDICATOR_DATA)

2. Puffer als Serie konfigurieren und mit leeren Werten füllen

Um die Daten in umgekehrter chronologischer Reihenfolge (wie eine Zeitreihe) anzuzeigen, legen wir die Arrays als Serie fest. Außerdem werden beide Puffer mit EMPTY_VALUE initialisiert, um zu verhindern, dass falsche Daten angezeigt werden, bis die tatsächlichen Werte berechnet sind.

  ArraySetAsSeries(buyOrderBlockBuffer, true);
  ArraySetAsSeries(sellOrderBlockBuffer, true);
  ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
  ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE


Implementierung von Puffern im Indikator (2)

In diesem Abschnitt weisen wir den Indikatorpuffern die Preise der steigenden und fallenden Auftragsblockaden zu. Diese Puffer stellen die Daten am entsprechenden Index für den Zeitpunkt (time1) jeder Auftragsblockade zur Verfügung.

1. Preise von steigenden Auftragsblockaden zuweisen

Innerhalb der Schleife, in der jeder Aufwärtsblockade in ob_alcistas ausgewertet wird, weisen wir price2 dem buyOrderBlockBuffer zu. Wir verwenden iBarShift, um den genauen Index auf dem Chart zu erhalten, bei dem time1 mit der Zeit des „Order Blocks“ übereinstimmt.

buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2;

In diesem Fall wird der price2 der Aufwärtsblockade dem entsprechenden Index im BuyOrderBlockBuffer zugewiesen, sodass der Puffer das Preisniveau des Blocks auf dem Chart widerspiegelt.

2. Preise von fallenden Auftragsblockaden zuweisen

In ähnlicher Weise weisen wir price2 von jeder Abwärtsblockade dem sellOrderBlockBuffer zu, indem wir das Array ob_bajistas durchlaufen und den Preis am entsprechenden Index setzen.

sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2;

Zusammenfassung:
  • iBarShift findet den genauen Index, bei dem die Blockzeit mit der Chartposition übereinstimmt.
  • buyOrderBlockBuffer und sellOrderBlockBuffer erhalten die Werte price2, sodass die Preise zum richtigen Zeitpunkt für die Verwendung im Chart und weitere Indikatorberechnungen aufgezeichnet werden können.


Aktualisieren von Eingabeparametern (Eingaben)

In diesem Abschnitt konfigurieren wir die Eingabeparameter, um dem Nutzer die Möglichkeit zu geben, den Berechnungsstil für Take Profit (TP) und Stop Loss (SL) anzupassen. Wir erstellen eine Enumeration, um zwischen zwei Optionen zu wählen: ATR (Average True Range) oder POINT (Fixpunkte).

ENUM_TP_SL_STYLE

Die Enumeration ermöglicht es dem Nutzer, zwischen zwei TP- und SL-Berechnungsmodi zu wählen.

  • АТR: Legt TP und SL auf der Grundlage der durchschnittlichen Preisbewegungsspanne fest und passt sich automatisch an die aktuelle Marktvolatilität an.
  • POINT: Setzt TP und SL auf vom Nutzer definierte feste Punktanzahl.

enum ENUM_TP_SL_STYLE
  {
   ATR,
   POINT
  };

Erläuterung:

  • АТR: Der Nutzer legt einen Multiplikator fest, um den TP- und SL-Abstand relativ zur ATR zu bestimmen. Ein höherer Multiplikator vergrößert den TP- und SL-Abstand auf der Grundlage der Marktvolatilität.

  • POINT: Der Nutzer legt TP und SL manuell in festen Punkten fest, was statische Niveaus unabhängig von der Volatilität ermöglicht.

Wenn wir nun mit den Indikatorparametern weiterarbeiten, strukturieren wir die Eingabeparameter des Indikators mit sinput und gruppieren die Einstellungen in Abschnitte. Dies ermöglicht eine übersichtlichere Anzeige der Parameter in der Nutzeroberfläche und erleichtert dem Nutzer die Konfiguration.

1. Gruppe der Strategie 

Wir erstellen eine Strategiegruppe, die den TP- und SL-Berechnungsstil enthält:

sinput group "-- Strategy --"
input ENUM_TP_SL_STYLE tp_sl_style = POINT; // TP and SL style: ATR or fixed points

Hier erlaubt tp_sl_style dem Nutzer zu wählen, ob TP und SL auf Basis von ATR (Average True Range) oder in festen Punkten berechnet werden sollen.

2. TP- und SL-Konfiguration nach ausgewählter Methode

Um die spezifischen Einstellungen jeder Methode zu berücksichtigen, fügen wir zwei zusätzliche Gruppen hinzu: eine für die ATR-Methode und eine für Fixpunkte.

Gruppe des ATR: Hier gibt es zwei doppelte Eingabevariablen, die es dem Nutzer ermöglichen, ATR-Multiplikatoren festzulegen und so den TP- und SL-Bereich auf der Grundlage der Volatilität anzupassen.

sinput group " ATR "
input double Atr_Multiplier_1 = 1.5; // Multiplier for TP
input double Atr_Multiplier_2 = 2.0; // Multiplier for SL

Gruppe POINT: In dieser Gruppe fügen wir zwei Eingabevariablen vom Typ int hinzu, um die Fixpunkte TP und SL zu definieren, was uns erlaubt, diese Abstände manuell und präzise zu kontrollieren.

sinput group " POINT "
input int TP_POINT = 500; // Fixed points for TP
input int SL_POINT = 275; // Fixed points for SL

Durch diese Struktur sind die Parameter übersichtlich geordnet und kategorisiert, was die Verwendung erleichtert und die Übersichtlichkeit beim Einrichten des Indikators erhöht. Der Nutzer kann den TP- und SL-Stil intuitiv einstellen und zwischen automatischen Konfigurationen auf der Grundlage der ATR oder manuellen Einstellungen in Punkten wählen.

Der vollständige Code der Parameter:

sinput group "--- Order Block Indicator settings ---"
sinput group "-- Order Block --"
input          int  Rango_universal_busqueda = 500;
input          int  Witdth_order_block = 1;

input          bool Back_order_block = true;
input          bool Fill_order_block = true;

input          color Color_Order_Block_Bajista = clrRed;
input          color Color_Order_Block_Alcista = clrGreen;

sinput group "-- Strategy --"
input          ENUM_TP_SL_STYLE tp_sl_style = POINT;

sinput group " ATR "
input          double Atr_Multiplier_1 = 1.5;
input          double Atr_Multiplier_2 = 2.0;
sinput group " POINT "
input          int TP_POINT = 500;
input          int SL_POINT = 275;


Logik der Signalgenerierung des Indikators

Um Kauf- oder Verkaufssignale zu erzeugen, werden zwei statische Variablen verwendet:

Variabel Beschreibung
time_ und time_b Speichert den Zeitpunkt, zu dem der Auftragsblock abgeschwächt wird, und fügt eine Zeitspanne von 5 Kerzen (in Sekunden) für das Auslaufen hinzu.
buscar_oba und buscar_obb Steuert die Suche nach neu-schwächelnden Auftragsblockaden. Aktiviert oder deaktiviert je nach Bedingungen.

Prozess der Signalerzeugung

Erkennen einer schwächelnden Auftragsblockade:
  • Wenn ein Auftragsblock schwächelt, wird time_ auf die aktuelle Zeit plus der Zeitspanne von 5 Kerzen gesetzt.
  • Die Suchervariable wird auf „false“ gesetzt, um weitere Suchvorgänge während der Validierung der Signalbedingungen zu unterbrechen.
Bedingungen für Kauf- und Verkaufssignale:
  • Die Signale werden auf der Grundlage des gleitenden 30-Perioden-Exponentialdurchschnitts (EMA) und der Milderungszeit (time_) bewertet. 
In der folgenden Tabelle sind die spezifischen Bedingungen zusammengefasst:
Signalart   EMA-Bedingungen
Zeitliche Bedingungen
 Kauf  Der EMA mit eine Periodenlänge von 30 liegt unter dem Schlusskurs von Kerze 1   time_ muss größer sein als die aktuelle Zeit
 Eröffnen eines Verkaufs  Der EMA mit eine Periodenlänge von 30 liegt über dem Schlusskurs von Kerze 1   time_b muss größer sein als die aktuelle Zeit

Anmerkung: Diese Bedingungen stellen sicher, dass das Signal innerhalb einer Spanne von 5 Kerzen nach der Blockade des Auftrags erzeugt wird.

Maßnahmen bei Erfüllung oder Nichterfüllung der Bedingungen:

Status   Aktion
 Erfüllt Füllen Sie die TP- und SL-Puffer, um den entsprechenden Handel auszuführen.
 Nicht erfüllt Der Sucher wird auf true und time_ und time_b auf 0 zurückgesetzt, sodass die Suche nach neuen Auftragsblöcken wieder aufgenommen werden kann, wenn die maximale Zeit abgelaufen ist.

Blockschaltbild:

Kauf

 Logik zum Öffnen einer Kaufposition

Verkauf

Logik zum Öffnen einer Verkaufsposition


Umsetzung einer Handelsstrategie

Bevor wir beginnen, erstellen wir das Handle für eine exponentiellen gleitenden Durchschnitt.

Wir erstellen globale Variablen (Array und Handle):

int hanlde_ma;
double ma[]; 

In OnInit wird das Handle initialisiert und geprüft, ob ihm ein Wert zugewiesen wurde.

hanlde_ma = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE);

if(hanlde_ma == INVALID_HANDLE)
{
Print("The EMA indicator is not available. Failure: ", _LastError); 
return INIT_FAILED;
}
Wir deklarieren statische Variablen, um den Suchstatus und die OB-Aktivierungszeit zu kontrollieren, wobei wir zwischen Kauf- und Verkaufsszenarien unterscheiden.
//Variables for buy
static bool buscar_oba = true;
static datetime time_ = 0;

//Variables for sell
static bool buscar_obb = true;
static datetime time_b = 0;

Anschließend durchlaufen wir die weichen Auftragsblockaden in einer Schleife (ähnlich wie im vorangegangenen Artikel für Warnungen).

Wir beginnen mit dem Hinzufügen von Bedingungen:

//Bullish case
 if(buscar_oba == true)
//Bearish case
 if(buscar_obb == true)

Der nächste Schritt besteht darin festzustellen, ob der OB abgeschwächt wurde, d. h. ob der Preis auf ihn eingewirkt hat. Wird ein abgemilderter OB gefunden, wird seine Zeit erfasst und die Suche ausgesetzt. Dies gilt sowohl für Aufwärts- als auch für Abwärts-Szenarien.

// Bearish case
for(int i = 0; i < ArraySize(ob_bajistas); i++) {
    if(ob_bajistas[i].mitigated == true &&
       !Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) &&
       ObjectFind(ChartID(), ob_bajistas[i].name) >= 0) {
        
        Alert("The bearishorder block is being mitigated: ", TimeToString(ob_bajistas[i].time1));
        buscar_obb = false;  // Pause search
        time_b = iTime(_Symbol,_Period,1);  //  Record the mitigation time
        Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
        break;
    }
}

// Bullish case
for(int i = 0; i < ArraySize(ob_alcistas); i++) {
    if(ob_alcistas[i].mitigated == true &&
       !Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) &&
       ObjectFind(ChartID(), ob_alcistas[i].name) >= 0) {
        
        Alert("The bullish order block is mitigated: ", TimeToString(ob_alcistas[i].time1));
        time_ = iTime(_Symbol,_Period,0);
        Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
        buscar_oba = false;  // Pause search
        break;
    }
}

Dieser Abschnitt stellt sicher, dass das System die Suche einstellt, sobald ein Abschwächen festgestellt wird, um doppelte Signale zu vermeiden.

Ausgangsbedingung für die Ausführung eines Handelsgeschäfts

Die Strategie verwendet spezifische Bedingungen, um die Suche nach Kauf- oder Verkaufssignalen auszulösen, sobald ein OB entschärft wurde und die maximale Wartezeit nicht überschritten wurde.

// Buy
if(buscar_oba == false && time_ > 0 && new_vela) { /* Code for Buy */ }

// Sell
if(buscar_obb == false && time_b > 0 && new_vela) { /* Code for Sell */ }

Unter diesen Bedingungen:

  1. buscar_oba oder buscar_obb muss false sein (Bestätigung einer vorherigen Abschwächung).
  2. time_ oder time_b muss größer als 0 sein, was bedeutet, dass eine Zeit aufgezeichnet wurde.
  3. new_vela stellt sicher, dass die Logik nur auf eine neue Kerze angewendet wird, um wiederholte Aktionen zu vermeiden.

Validierung von Kauf- oder Verkaufsbedingungen

Um die notwendigen Voraussetzungen zu schaffen, benötigen wir zunächst eine Variable, in der die maximale Wartezeit gespeichert wird. Dann ist es wichtig, den Schlusskurs von Kerze 1 und seinen EMA (Exponential Moving Average) zu kennen. Um den Schlusskurs zu erhalten, verwenden wir die Funktion iClose, und wir speichern die EMA-Werte in einem Array, das die vollständige historische Reihe des gleitenden Durchschnitts enthält.

// Buy
double close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits);
datetime max_time_espera = time_ + (PeriodSeconds() * 5);

if(close_ > ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) {
    // Code for Buy...
}

// Sell
close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits);
max_time_espera = time_b + (PeriodSeconds() * 5);

if(close_ < ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) {
    // Code for Sell...
}

Zurücksetzen der Suche nach Auftragsblockaden

Wenn schließlich die maximale Wartezeit überschritten wird, ohne dass die Bedingungen erfüllt sind, setzt der Code die Suche zurück, um die Erkennung neuer OBs zu ermöglichen:

// Reset for Buy
if(iTime(_Symbol,_Period,0) > max_time_espera) {
    time_ = 0;
    buscar_oba = true;
}

// Reset for Sell
if(iTime(_Symbol,_Period,0) > max_time_espera) {
    time_b = 0;
    buscar_obb = true;
}

Jetzt fehlt noch eine Funktion zum Zeichnen von tp und sl, sowie zum Hinzufügen zu den Puffern. Wir erreichen dies durch den folgenden Code:

Fahren wir mit den neuen Abschnitten fort.


Festlegen von Take Profit (TP) und Stop Loss (SL) Levels

In diesem Abschnitt werden wir die Funktion GetTP_SL entwickeln, die die TP- und SL-Niveaus mit zwei Methoden berechnet: Entweder mit dem ATR (Average True Range) oder mit Fixpunkten, wie bereits in der Eingabekonfiguration erwähnt.

1: Funktion Definition

Die Funktion GetTP_SL erhält folgende Parameter: den Eröffnungskurs der Position, den Positionstyp (ENUM_POSITION_TYPE) und Referenzen für die TP- und SL-Ebenen (tp1, tp2, sl1 und sl2), auf denen die berechneten Werte gespeichert werden.

void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)

2: Abrufen des ATR

Um ATR-basierte Levels zu berechnen, benötigen wir zunächst ein Array, das den ATR-Wert der letzten Kerze speichert. Wir verwenden CopyBuffer, um das Array atr mit dem aktuellen Wert zu füllen.

double atr[];
ArraySetAsSeries(atr, true);
CopyBuffer(atr_i, 0, 0, 1, atr);

3: Berechnung von TP und SL auf Basis von ATR

Wenn tp_sl_style auf ATR eingestellt ist, berechnen wir die TP- und SL-Levels, indem wir den ATR-Wert mit den definierten Multiplikatoren (Atr_Multiplier_1 und Atr_Multiplier_2) multiplizieren. Diese Werte werden dann je nach Art der Position zum Eröffnungskurs addiert oder davon subtrahiert.

if (type == POSITION_TYPE_BUY) {
    sl1 = price_open_position - (atr[0] * Atr_Multiplier_1);
    sl2 = price_open_position - (atr[0] * Atr_Multiplier_2);
    tp1 = price_open_position + (atr[0] * Atr_Multiplier_1);
    tp2 = price_open_position + (atr[0] * Atr_Multiplier_2);
}

if (type == POSITION_TYPE_SELL) {
    sl1 = price_open_position + (atr[0] * Atr_Multiplier_1);
    sl2 = price_open_position + (atr[0] * Atr_Multiplier_2);
    tp1 = price_open_position - (atr[0] * Atr_Multiplier_1);
    tp2 = price_open_position - (atr[0] * Atr_Multiplier_2);
}

4: Berechnung von TP und SL auf Basis von Punkten

Wenn tp_sl_style auf POINT gesetzt ist, werden die angegebenen Punkte (TP_POINT und SL_POINT), multipliziert mit dem Punktwert des aktuellen Symbols (_Point), zum Eröffnungskurs addiert oder subtrahiert. Dies stellt eine einfachere Alternative zur ATR-basierten Berechnung dar.

if (type == POSITION_TYPE_BUY) {
    sl1 = price_open_position - (SL_POINT * _Point);
    sl2 = price_open_position - (SL_POINT * _Point * 2);
    tp1 = price_open_position + (TP_POINT * _Point);
    tp2 = price_open_position + (TP_POINT * _Point * 2);
}

if (type == POSITION_TYPE_SELL) {
    sl1 = price_open_position + (SL_POINT * _Point);
    sl2 = price_open_position + (SL_POINT * _Point * 2);
    tp1 = price_open_position - (TP_POINT * _Point);
    tp2 = price_open_position - (TP_POINT * _Point * 2);
}


Visualisierung von TP- und SL-Levels auf dem Chart

Wir werden eine Funktion erstellen, um die TP- und SL-Levels im Chart mithilfe von Linien und Textobjekten zu zeichnen.

Linien erstellen

bool TrendCreate(long            chart_ID,        // Chart ID
                 string          name,  // Line name
                 int             sub_window,      // Subwindow index
                 datetime              time1,           // Time of the first point
                 double                price1,          // Price of the first point
                 datetime              time2,           // Time of the second point
                 double                price2,          // Price of the second point
                 color           clr,        // Line color
                 ENUM_LINE_STYLE style, // Line style
                 int             width,           // Line width
                 bool            back,        // in the background
                 bool            selection    // Selectable form moving
                )
  {
   ResetLastError();
   if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2))
     {
      Print(__FUNCTION__,
            ": ¡Failed to create trend line! Error code = ",GetLastError());
      return(false);
     }

   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);
   ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width);
   ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
   ChartRedraw(chart_ID);
   return(true);
  }

Für Texte:

bool TextCreate(long              chart_ID,                // Chart ID
                string            name,              // Object name
                int               sub_window,             // Subwindow index
                datetime                time,                   // Anchor time
                double                  price,                  // Anchor price
                string            text,              // the text
                string            font,             // Font
                int               font_size,             // Font size
                color             clr,               // color
                double            angle,                // Text angle
                ENUM_ANCHOR_POINT anchor, // Anchor point
                bool              back=false,               // font
                bool              selection=false)          // Selectable for moving

  {

//--- reset error value
   ResetLastError();
//--- create "Text" object
   if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price))
     {
      Print(__FUNCTION__,
            ": ¡Failed to create object \"Text\"! Error code = ",GetLastError());
      return(false);
     }
   ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
   ObjectSetString(chart_ID,name,OBJPROP_FONT,font);
   ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size);
   ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle);
   ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor);
   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);

   ChartRedraw(chart_ID);
   return(true);
  }

Gehen wir nun zur Erstellung der Funktion über.

Schritt 1. Eingabe Details

Die Funktion erhält die folgenden Parameter:

  • tp1 und tp2 – Werte für die beiden Take-Profit-Ebenen.
  • sl1 und sl2 – Werte für die beiden Stop-Loss-Ebenen.
void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)

Schritt 2: Vorbereiten der Zeiten

Wir erstellen zunächst einen String curr_time, um das aktuelle Datum und die Uhrzeit der Kerze im Chart zu speichern. Dann berechnen wir extension_time, die vom aktuellen Zeitpunkt aus 15 Perioden nach vorne reicht, um die TP- und SL-Linien nach rechts zu projizieren. text_time wird verwendet, um die Positionen der Textlabel etwas über extension_time hinaus anzupassen.

string curr_time = TimeToString(iTime(_Symbol, _Period, 0));
datetime extension_time = iTime(_Symbol, _Period, 0) + (PeriodSeconds(PERIOD_CURRENT) * 15);
datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);

Schritt 3: Zeichnen von TP- und SL-Linien und Kennzeichnungen

  1. Gewinnmitnahme 1 (tp1):
  • Zeichnen einer grünen, gepunktete Linie (STYLE_DOT) bei tp1 mit TrendCreate.
  • Mit TextCreate ergänzen wir die Textbeschriftung „TP1“ an der Position tp1 ein.
TrendCreate(ChartID(), curr_time + " TP1", 0, iTime(_Symbol, _Period, 0), tp1, extension_time, tp1, clrGreen, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " TP1 - Text", 0, text_time, tp1, "TP1", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);

      2. Gewinnmitnahme 2 (tp2):
  • Wir zeichnen eine weitere grüne, gepunktete Linie bei tp2 und fügen Sie eine Textbeschriftung „TP2“ hinzu.
TrendCreate(ChartID(), curr_time + " TP2", 0, iTime(_Symbol, _Period, 0), tp2, extension_time, tp2, clrGreen, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " TP2 - Text", 0, text_time, tp2, "TP2", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);

      3. Stop Loss 1 (sl1):
  • Wir zeichnen eine rote gepunktete Linie bei sl1 und eine Textbeschriftung „SL1“.
TrendCreate(ChartID(), curr_time + " SL1", 0, iTime(_Symbol, _Period, 0), sl1, extension_time, sl1, clrRed, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " SL1 - Text", 0, text_time, sl1, "SL1", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);

       4. Stop Loss 2 (sl2):
  • Wir zeichnen ebenfalls eine rote Linie bei sl2 und eine Beschriftung „SL2“.
TrendCreate(ChartID(), curr_time + " SL2", 0, iTime(_Symbol, _Period, 0), sl2, extension_time, sl2, clrRed, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " SL2 - Text", 0, text_time, sl2, "SL2", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);

Hier der vollständiger Code:

void DrawTP_SL(double tp1, double tp2, double sl1, double sl2)
  {


   string  curr_time = TimeToString(iTime(_Symbol,_Period,0));
   datetime extension_time = iTime(_Symbol,_Period,0) + (PeriodSeconds(PERIOD_CURRENT) * 15);
   datetime   text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);


   TrendCreate(ChartID(),curr_time+" TP1",0,iTime(_Symbol,_Period,0),tp1,extension_time,tp1,clrGreen,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" TP1 - Text",0,text_time,tp1,"TP1","Arial",8,clrGreen,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" TP2",0,iTime(_Symbol,_Period,0),tp2,extension_time,tp2,clrGreen,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" TP2 - Text",0,text_time,tp2,"TP2","Arial",8,clrGreen,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" SL1",0,iTime(_Symbol,_Period,0),sl1,extension_time,sl1,clrRed,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" SL1 - Text",0,text_time,sl1,"SL1","Arial",8,clrRed,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" SL2",0,iTime(_Symbol,_Period,0),sl2,extension_time,sl2,clrRed,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" SL2 - Text",0,text_time,sl2,"SL2","Arial",8,clrRed,0.0,ANCHOR_CENTER);

  }


Hinzufügen von Puffern für TP- und SL-Ebenen (4)

Wie bei den beiden Puffern, die den price2 speichern, legen wir zusätzliche Puffer für TP und SL an:

#property indicator_label3 "Take Profit 1"
#property indicator_label4 "Take Profit 2"
#property indicator_label5 "Stop Loss 1"
#property indicator_label6 "Stop Loss 2"

Wir erhöhen die Anzahl des Dargestellten (plots) und die Puffer von 2 auf 6.

#property  indicator_buffers 6
#property  indicator_plots 6

Erstellen Sie ein Array von Puffern:

double tp1_buffer[];
double tp2_buffer[];
double sl1_buffer[];
double sl2_buffer[];

Wir initialisieren die Arrays und legen sie als Zeitreihe fest.

SetIndexBuffer(2, tp1_buffer, INDICATOR_DATA);
SetIndexBuffer(3, tp2_buffer, INDICATOR_DATA);

SetIndexBuffer(4, sl1_buffer, INDICATOR_DATA);
SetIndexBuffer(5, sl2_buffer, INDICATOR_DATA);


ArraySetAsSeries(buyOrderBlockBuffer, true);
ArraySetAsSeries(sellOrderBlockBuffer, true);
ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

ArraySetAsSeries(tp1_buffer, true);
ArraySetAsSeries(tp2_buffer, true);
ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

ArraySetAsSeries(sl1_buffer, true);
ArraySetAsSeries(sl2_buffer, true);
ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

Dadurch wird sichergestellt, dass die TP- und SL-Werte korrekt gespeichert und auf dem Chart angezeigt werden.


Fertigstellung des Hauptcodes und die Bereinigung

Um den Indikator zu vervollständigen, implementieren wir Bereinigungs- und Optimierungscode. Dies verbessert die Backtesting-Leistung und setzt Speicherressourcen für Arrays frei, die z. B. „Order Blocks“ speichern, sobald sie nicht mehr benötigt werden.

1. Arrays leeren

Im Rahmen von OnCalculate überwachen wir, ob eine neue Tageskerze erscheint. Wir verwenden eine globale Variable, um die letzte Kerzenzeit zu speichern.

datetime    tiempo_ultima_vela_1;
Jedes Mal, wenn eine neue Tageskerze geöffnet wird, geben wir Speicher aus den Arrays frei, um die Ansammlung alter Daten zu verhindern und die Leistung zu optimieren.
 if(tiempo_ultima_vela_1 != iTime(_Symbol,PERIOD_D1,  0))
     {
      Eliminar_Objetos();

      ArrayFree(ob_bajistas);
      ArrayFree(ob_alcistas);
      ArrayFree(pricetwo_eliminados_oba);
      ArrayFree(pricetwo_eliminados_obb);

      tiempo_ultima_vela_1 = iTime(_Symbol,PERIOD_D1,  0);
     }

2. Ändern von OnDeinit

In OnDeinit geben wir das Handle des EMA-Indikators frei und löschen alle Arrays. Dadurch wird sichergestellt, dass keine Speicherressourcen übrig bleiben, wenn der Indikator entfernt wird.

void OnDeinit(const int reason)
  {
   Eliminar_Objetos();

   ArrayFree(ob_bajistas);
   ArrayFree(ob_alcistas);
   ArrayFree(pricetwo_eliminados_oba);
   ArrayFree(pricetwo_eliminados_obb);

   if(atr_i  != INVALID_HANDLE)
      IndicatorRelease(atr_i);
   if(hanlde_ma != INVALID_HANDLE) //EMA
      IndicatorRelease(hanlde_ma);

   ResetLastError();

    if(MarketBookRelease(_Symbol)) //Verify if closure was successful
     Print("Order book successfully closed for: " , _Symbol); //Print success message if so
   else
     Print("Order book closed with errors for: " , _Symbol , "   Last error: " , GetLastError()); //Print error message with code if not
  }

3. Objekt-Löschfunktion

Die Funktion Eliminar_Objetos wurde optimiert, um TP- und SL-Linien zusammen mit den Rechtecken von „Order Block“ zu entfernen, sodass der Chart sauber bleibt.
void Eliminar_Objetos()
  {

   for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iterate through the array of bullish order blocks
     {
      ObjectDelete(ChartID(),ob_alcistas[i].name); // delete the object using the order block's name
     }
   for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iterate through the array of bearish order blocks
     {
      ObjectDelete(ChartID(),ob_bajistas[n].name);  // delete the object using the order block's name
     }
 //Delete all TP and SL lines
   ObjectsDeleteAll(0," TP",-1,-1);
   ObjectsDeleteAll(0," SL",-1,-1);
  }

4. Ersteinrichtung in OnInit

Wir konfigurieren den Kurznamen des Indikators und die Chartbeschriftung, um eine korrekte Beschriftung im Datenfenster zu gewährleisten.

   string short_name = "Order Block Indicator";
   IndicatorSetString(INDICATOR_SHORTNAME,short_name);

// Set data precision for digits

// Assign labels for each plot
   PlotIndexSetString(0, PLOT_LABEL, "Bullish Order Block");
   PlotIndexSetString(1, PLOT_LABEL, "Bearish Order Block");
   PlotIndexSetString(2, PLOT_LABEL, "Take Profit 1");
   PlotIndexSetString(3, PLOT_LABEL, "Take Profit 2");
   PlotIndexSetString(4, PLOT_LABEL, "Stop Loss 1");
   PlotIndexSetString(5, PLOT_LABEL, "Stop Loss 2");

5. Festlegen von TP- und SL-Levels bei der Eröffnung der Handelsgeschäfte

Schließlich legen wir Take-Profit- und Stop-Loss-Niveaus für Kauf- und Verkaufstransaktionen fest. Bei Kaufgeschäften verwenden wir den Briefkurs (Ask), bei Verkaufsgeschäften den Geldkurs (Bid). Dann zeichnen wir die TP- und SL-Linien zur Überwachung in den Chart ein.

//Buy
double ask= NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);

double tp1;
double tp2;
double sl1;
double sl2;
GetTP_SL(ask,POSITION_TYPE_BUY,tp1,tp2,sl1,sl2);

DrawTP_SL(tp1,tp2,sl1,sl2);

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

time_ = 0;
buscar_oba = true;

//Sell

double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
double tp1;
double tp2;
double sl1;
double sl2;
GetTP_SL(bid,POSITION_TYPE_SELL,tp1,tp2,sl1,sl2);

DrawTP_SL(tp1,tp2,sl1,sl2);

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

time_b = 0;
buscar_obb = true;
Schritt   Kauf
Verkauf
 Preis: Erhalten und Normalisieren von Ask.
Erhalten und Normalisieren von Bid.
Variablen: Wir initialisieren die Variablen, um Take Profit- und Stop Loss-Werte zu speichern.

(tp1, tp2, sl1 und sl2). 
Die gleichen Variablen werden für die Speicherung von Take Profit- und Stop Loss-Niveaus verwendet.

(tp1, tp2, sl1 und sl2). 
Berechnung: GetTP_SL berechnet TP- und SL-Levels basierend auf dem Ask-Preis für ein Kaufgeschäft. GetTP_SL berechnet TP- und SL-Levels basierend auf dem Bid-Preis für ein Verkaufsgeschäft.  
Zeichnung: DrawTP_SL zeigt auf dem Chart visuell TP- und SL-Levels für Kaufgeschäfte an. DrawTP_SL zeigt visuell auf dem Chart TP- und SL-Levels für Verkaufsgeschäfte an.
Puffer: Wir verwenden iBarShift, um den aktuellen Bar-Index zu finden und TP/SL in Puffern zu speichern.

 (tp1_puffer, tp2_puffer, sl1_puffer und sl2_puffer).    
um den aktuellen Barindex zu finden und TP/SL in denselben Puffern zu speichern.

 (tp1_puffer, tp2_puffer, sl1_puffer und sl2_puffer).   
Statische Variablen:  Wir setzen die statische Variablen zurück, um bei der nächsten Iteration nach neuen Blockaden von Kaufaufträgen zu suchen.

(Statische Variablen: „time_“ und „buscar_oba“).
Wir setzen die statische Variablen zurück, um bei der nächsten Iteration nach neuen Blockaden von Verkaufsaufträgen zu suchen.

(Statische Variablen: „time_b“ und „search_obb“).


Schlussfolgerung

In diesem Artikel haben wir untersucht, wie man einen Indikator „Order Block“ auf der Grundlage der Volumina der Markttiefe erstellt und seine Funktionalität durch Hinzufügen von zusätzlichen Puffern zum ursprünglichen Indikator optimiert.

Unser Endergebnis:

Letztes Beispiel-GIF

Mit diesem Abschnitt schließen wir die Entwicklung unseres Indikators „Order Block“ ab. In den kommenden Artikeln werden wir die Erstellung einer Risikomanagementklasse von Grund auf behandeln und einen Handelsbot entwickeln, der dieses Risikomanagement integriert und die Signalpuffer unseres Indikators nutzt, um präzisere und automatisierte Handelsentscheidungen zu treffen.

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

Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Vladislav Boyko
Vladislav Boyko | 4 Okt. 2025 in 19:24

https://www.mql5.com/de/articles/16268

5. Festlegen von TP- und SL-Levels bei der Eröffnung von Geschäften

Abschließend legen wir Take Profit- und Stop Loss-Levels für Kauf- und Verkaufstransaktionen fest. Bei Kaufgeschäften verwenden Sie den Briefkurs, bei Verkaufsgeschäften den Geldkurs. Zeichnen Sie dann die TP- und SL-Linien zur Überwachung in das Diagramm ein.

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

Das sieht so aus, als ob es ein wenig vereinfacht werden könnte.

Automatisieren von Handelsstrategien in MQL5 (Teil 24): London Session Breakout System mit Risikomanagement und Trailing Stops Automatisieren von Handelsstrategien in MQL5 (Teil 24): London Session Breakout System mit Risikomanagement und Trailing Stops
In diesem Artikel entwickeln wir ein London Session Breakout System, das Ausbrüche vor der Londoner Handelsspanne identifiziert und schwebende Aufträge mit anpassbaren Handelsarten und Risikoeinstellungen platziert. Wir integrieren Funktionen wie Trailing Stops, Risiko-Ertrags-Verhältnisse, maximale Drawdown-Grenzen und ein Kontrollpanel für die Überwachung und Verwaltung in Echtzeit.
Time Evolution Travel Algorithm (TETA) Time Evolution Travel Algorithm (TETA)
Dies ist mein eigener Algorithmus. Der Artikel stellt den Time Evolution Travel Algorithm (TETA) vor, der vom Konzept der Paralleluniversen und Zeitströme inspiriert ist. Der Grundgedanke des Algorithmus ist, dass wir, obwohl Zeitreisen im herkömmlichen Sinne unmöglich sind, eine Abfolge von Ereignissen wählen können, die zu unterschiedlichen Realitäten führen.
Entwicklung des Price Action Analysis Toolkit (Teil 33): Candle-Range Theory Tool Entwicklung des Price Action Analysis Toolkit (Teil 33): Candle-Range Theory Tool
Verbessern Sie Ihr Marktverständnis mit der Candle-Range Theory Suite für MetaTrader 5, einer vollständig MQL5-nativen Lösung, die rohe Preisbalken in Echtzeit-Volatilitätsinformationen umwandelt. Die leichtgewichtige Bibliothek CRangePattern vergleicht die „True Range“ jeder Kerze mit einer adaptiven ATR und klassifiziert sie in dem Moment, in dem sie schließt. Der CRT-Indikator projiziert diese Klassifizierungen dann als scharfe, farbkodierte Rechtecke und Pfeile auf Ihr Chart, die sich verengende Konsolidierungen, explosive Ausbrüche und Verengungen der gesamten Spanne in dem Moment anzeigen, in dem sie auftreten.
Analyse des Binärcodes der Börsenkurse (Teil II): Umwandlung in BIP39 und Schreiben des GPT-Modells Analyse des Binärcodes der Börsenkurse (Teil II): Umwandlung in BIP39 und Schreiben des GPT-Modells
Fortsetzung der Versuche, die Preisbewegungen zu entschlüsseln... Wie steht es mit der linguistischen Analyse des „Marktwörterbuchs“, das wir durch die Umwandlung des binären Preiscodes in BIP39 erhalten? In diesem Artikel befassen wir uns mit einem innovativen Ansatz für die Analyse von Börsendaten und untersuchen, wie moderne Techniken der natürlichen Sprachverarbeitung auf die Marktsprache angewendet werden können.