English Русский 中文 Español 日本語 Português
preview
Entwicklung fortschrittlicher ICT-Handelssysteme: Implementierung von Orderblöcken in einem Indikator

Entwicklung fortschrittlicher ICT-Handelssysteme: Implementierung von Orderblöcken in einem Indikator

MetaTrader 5Beispiele |
305 18
Niquel Mendoza
Niquel Mendoza


1.0: Einführung 

Herzlich willkommen und vielen Dank, dass Sie diesen Artikel lesen. Heute werden Sie lernen, wie man einen Indikator entwickelt, der auf der Orderblock-Theorie von Smart Money Concepts und Inner Circle Trader basiert.  

1.1: Grundlagen der Erstellung von Orderblöcken 

Orderblöcke sind Zonen auf dem Chart, in denen schwebende Aufträge wahrscheinlich darauf warten, ausgeführt zu werden.

Dies ist typischerweise der Fall, wenn ein großer Marktteilnehmer, z. B. ein Finanzinstitut, eine bedeutende Position eingehen möchte, aber nicht über genügend Liquidität verfügt, um den gesamten Auftrag auf einmal auszuführen, ohne den Markt zu beeinträchtigen. Nach den grundlegenden Gesetzen von Angebot und Nachfrage treibt die Ausführung eines Teils des Auftrags den Preis in die Höhe (im Falle eines Kaufs), da aggressiv nach Verkäufern gesucht wird, die die für den Abschluss des Geschäfts erforderliche Liquidität bereitstellen können.

Da der institutionelle Teilnehmer den gesamten Auftrag nicht auf einmal ausführen kann, ohne eine erhebliche Preisverschiebung zu verursachen, teilt er den Auftrag in kleinere Teile auf. So können sie den Handel abschließen, ohne dass sich der Kurs erheblich bewegt, bevor sie ihre Position vollständig eingegangen sind.

Auf der Grundlage dieses Konzepts können wir diese Zonen auf einem Preis-Chart als Bereiche mit starkem Ungleichgewicht zwischen Angebot und Nachfrage (sowohl beim Kauf als auch beim Verkauf) identifizieren. Im Folgenden werden wir drei Möglichkeiten zur Identifizierung dieser Zonen und zu ihrer Implementierung in den Code untersuchen.

1.1.1: Auf Preisaktionen basierende Logik (Grundstufe) 


Bevor wir uns mit der Logik hinter den Orderblöcken befassen, sollten wir uns kurz die Bestandteile eines Kerzen ansehen, die für das Verständnis der Mechanik unerlässlich sind.

Eine Kerze besteht aus vier Preispunkten:                    

Preis Beschreibung
  High Das Hoch, das während des Zeitrahmens der Kerze erreicht wurde
  Low Der während des Zeitrahmens der Kerze erreichte Mindestpreis
  Open Der Preis, zu dem die Kerze eröffnet wird
  Close Der Preis, zu dem die Kerze schließt   

Schauen wir uns zum besseren Verständnis ein Beispiel auf dem Schaubild an:

          OHCL

Nach der Theorie des Orderblocks ist das erste wichtige Element die Feststellung eines Marktungleichgewichts. Dieses Ungleichgewicht ist auf dem Chart oft als eine Abfolge von mehreren aufeinanderfolgenden Kerzen in dieselbe Richtung sichtbar, was auf einen klaren Trend hindeutet.

Beispiel: Aufwärtstrend mit 4 aufeinanderfolgenden Kerzen

In diesem Fall konzentrieren wir uns auf einen Aufwärtstrend, der durch vier aufeinanderfolgende aufwärts gerichtete Kerzen gebildet wird, und befolgen diese Regeln:      

Kerze Beschreibung
Vorherige Kerze Diese Kerze geht der Aufwärtsbewegung von 4 aufeinanderfolgenden Kerzen voraus. Er schließt in der Regel unter dem Ausgangsniveau der Aufwärtsbewegung.
Erste Kerze Markiert den Beginn der Aufwärtsbewegung. Der Schlusskurs liegt über dem Eröffnungskurs der vorherigen Kerze.
Zweite, dritte und vierte Kerze
Diese Kerzen setzen die Aufwärtsdynamik fort und schließen jeweils über dem Schlusskurs der vorherigen Kerze.

Regeln:
  • Bedingung der Aufwärtsbewegung: Um als gültige Bewegung zu gelten, müssen die vier Kerzen nacheinander aufwärts gerichtet sein. Mit der ersten Kerze beginnt das Ungleichgewicht, und die folgenden Kerzen bestätigen es.
  • Identifizierung des Orderblocks: Der Orderblock wird in der Zone identifiziert, die die vorherige Kerze und die erste Aufwärtskerze umfasst und den Bereich darstellt, in dem die Käufer die Kontrolle übernommen haben.

Unten sehen Sie ein grafisches Beispiel mit vier aufeinanderfolgenden Aufwärtskerzen, die eindeutig auf ein Kursungleichgewicht hinweisen.

Einfaches OB-Beispiel

Regeln für die Identifizierung von Orderblöcken auf der Grundlage aufeinanderfolgender Kerzen:

Aspekt Steigender Orderblock Fallender Orderblock
Kerzenzustand Die Kerze 1, 2, 3 und 4 müssen alle aufwärts sein. Jede schließt über ihren Eröffnungskurs. Die Kerze 1, 2, 3 und 4 müssen alle abwärts sein. Jede schließt unter ihrem Eröffnungskurs.
Validierung des Extremums von Kerze 2 Das Tief von Kerze 2 muss über dem Mittelpunkt des Körpers von Kerze 1 liegen.
(Ausnahme: Hammer)
Das Hoch von Kerze 2 muss unter dem Mittelpunkt des Körpers von Kerze 1 liegen.
(Ausnahme: Hammer)
Körperzustand der Kerze 2 Mindestens 40 % des Körpers der Kerze 2 müssen über dem Hoch von Kerze 1 liegen. Mindestens 40% des Körpers der Kerze 2 müssen unter dem Tief von Kerze 1 liegen.
Validierung des Extremums von Kerze 3  Das Tief der Kerze 3 muss über 25% des Körpers der Kerze 2 liegen. Das Hoch der Kerze 3 muss unter 25% des Körpers von Kerze 2 liegen.
Kerze 3 Körperzustand Die Hälfte des Körpers der 3. Kerze muss über dem Hoch der Kerze 2 liegen. Die Hälfte des Körpers der 3. Kerze muss unter dem Tief von Kerze 2 liegen.

Zweck der Regeln:

Diese Kriterien sollen sicherstellen, dass das Muster der 4 Kerzen stark genug ist, um einen Orderblock zu validieren und zu bestätigen, dass die schwebenden Aufträge innerhalb der Zone noch nicht ausgeführt wurden.

1.1.2: Auf Preisaktionen basierende Logik und Indikatoren (Mittelstufe)

Bei diesem fortschrittlicheren Ansatz stützen wir uns nicht nur auf die Preisbewegung, sondern wenden auch Indikatoren an, um die Stärke der Bewegung zu validieren, insbesondere den Indikator für das Volumen.

Grundsätze der Strategie

Wie bereits erwähnt, beginnen bedeutende Marktbewegungen oft mit einem relativ geringen Volumen, gefolgt von einem starken Anstieg des Volumens, sobald größere Aufträge ausgeführt werden. Dieser Volumenanstieg erstreckt sich in der Regel über 2 oder 3 Kerzen und signalisiert die Bildung eines Orderblocks.

Wir können diese Logik in zwei Hauptszenarien unterteilen:

Fall 1: Orderblock mit steigendem Volumen

In diesem Szenario wird der Orderblock gebildet, wenn das Volumen deutlich zu steigen beginnt. Die Bedingungen sind wie folgt:

  1. Beginn der Bewegung: Sie beginnt mit einer Kerze mit geringem Volumen, die den Beginn der Akkumulation markiert.
  2. Erhöhung des Volumens: Bei der nächsten Kerze steigt das Volumen deutlich an, was die Ausführung des Auftrags signalisiert. Dieser Anstieg kann sich über 2 bis 3 aufeinander folgende Kerzen fortsetzen.
  3. Bestätigung des Orderblock: Der Orderblock wird in der Zone identifiziert, in der das Volumen zu steigen beginnt und die schwebende Aufträge als erfüllt gelten.

Aufwärts-Beispiel: 

Aufwärts mit steigendem Volumen

Abwärts-Beispiel:         

Abwärts mit steigendem Volumen

Fall 2: Orderblock mit einer einzigen Volumen-Spitze

In diesem Fall wird der Orderblock identifiziert, wenn eine signifikante Volumenspitze in einer Schlüsselkerze, der so genannten ersten Kerze, beobachtet wird. Das Orderblock-Muster wird sowohl durch die Preis- als auch durch die Volumenanalyse bestätigt und besteht aus 3 aufeinanderfolgenden Kerzen, die entweder auf- oder abwärts sind.

Regeln:

Aspekt Steigender Orderblock Aufwärts-Orderblock
Volumenspitze der Kerze 1
Kerze 1 muss das höchste Volumen unter den drei Kerzen haben und ihr Volumen muss sowohl die vorherige Kerze als auch Kerze 2 übersteigen.
Kerze 1 muss das höchste Volumen unter den drei Kerzen haben und ihr Volumen muss sowohl die vorherige Kerze als auch Kerze 2 übersteigen.
Überprüfen des Extremwerts von Kerze 2
Das Tief von Kerze 2 muss über dem Mittelpunkt von Kerze 1 liegen, was bedeutet, dass die Akkumulationszone des Orderblocks unberührt bleibt.
(Ausnahme: wenn Kerze 1 ein Hammer ist)
Das Hoch von Kerze 2 muss unter dem Mittelpunkt von Kerze 1 liegen, was bedeutet, dass die Akkumulationszone des Orderblocks unberührt bleibt.
(Ausnahme: wenn Kerze 1 ein umgekehrter Hammer ist)
Zustand 60% Kerzenkörper 2 60 % des Körpers der 2. Kerze müssen über dem Hoch der 1. Kerze liegen, was auf eine Fortsetzung des Aufwärtsmomentums hindeutet. 60 % des Körpers der 2. Kerze müssen unter dem Tief von Kerze 1 liegen, was auf eine Fortsetzung des Abwärtsmomentums hindeutet.
Überprüfen des Extremwerts von Kerze 3 Das Hoch von Kerze 3 muss über dem Eröffnungskurs von Kerze 2 liegen, was ein anhaltendes Abwärtsmomentum bestätigt. Das Tief von Kerze 3 muss unter dem Eröffnungskurs von Kerze 2 liegen, was ein anhaltendes Aufwärtsmomentum bestätigt.

Aufwärts-Beispiel: 

Aufwärts mit Volumenspitze

 Abwärts-Beispiel:

Abwärts mit Volumenspitze


2.0: Entwicklung des Indikators der Orderblöcken

2.1: Konfigurieren von Eingängen und Indikatorparametern


Nachdem wir nun einen großen Teil der Theorie behandelt haben, kommen wir endlich zu dem Teil, auf den viele von Ihnen wahrscheinlich gewartet haben - die Codierung all dessen, was wir bis jetzt gelernt haben. 

1. Wir beginnen mit der Erstellung eines neuen Programms vom Typ „Benutzerdefinierter Indikator“:

2. Als Nächstes schreiben wir den Namen des Indikators und den Namen des Autors.

3. Dann wählen wir „OnCalculate()“ für nachfolgende Berechnungen.

 4. Zum Abschluss muss auf „Fertig stellen“ gedrückt werden.

In diesem Stadium werden wir noch keine Zeichenobjekte definieren.

indicator_buffers 
indicator_plots 

Um den Fehler zu vermeiden: „kein Indikatorplot für Indikator00 definiert“

fügen wir oben den folgenden Platzhaltercode ein:

#property  indicator_buffers 1
#property  indicator_plots 1

Dadurch wird die Warnung entfernt, sodass wir mit der Konfiguration und der Implementierung der Logik fortfahren können.

Konfigurieren wir zunächst unsere Eingänge:

  • Farben für steigende und fallende Orderblöcke:

    Diese Einstellungen ermöglichen es dem Nutzer, die Farben auszuwählen, die steigende und fallende Orderblöcke visuell darstellen, sodass sie auf einen Blick zu erkennen sind.

  • Optionen zur Anpassung des Rechtecks:

    Diese Optionen steuern, wie die Rechtecke zur Markierung von Orderblöcken angezeigt werden:

    • Breite der Umrandung: Legt die Dicke des Rechteckrandes fest.
    • Vor dem Hintergrund: Legt fest, ob das Rechteck hinter oder über den Kerzen erscheint.
    • Wählbar: Ermöglicht es den Nutzern, die Möglichkeit zu aktivieren oder zu deaktivieren, die Rechtecke im Chart anzuklicken und zu verschieben.
  • Suchbereich der Orderblocks:

    Dieser Parameter legt fest, wie viele Kerzen vor der aktuellen Kerze der Indikator nach gültigen Orderblöcken suchen soll. Durch diese Einstellung kann der Indikator an verschiedene Strategien oder Zeitrahmen angepasst werden.

  • Organisieren von Eingaben mit Gruppierung:

    Eingaben sind einstellbare Parameter, die der Nutzer außerhalb des Programms ändern kann. Um die Lesbarkeit und Organisation zu verbessern, verwenden wir das folgende Konzept:     

    sinput

    Mit dem obigen Schlüsselwort können wir die Parameter besser organisieren, indem wir sie mit Hilfe der Eigenschaft in Kategorien gruppieren:

    group
  • Dies verbessert nicht nur die Struktur des Codes, sondern erleichtert dem Nutzer auch die Erkennung der verschiedenen Parametersätze, die sich auf bestimmte Aspekte des Indikators beziehen, wie z. B. die Einrichtung von Rechtecken oder die Analyse von Orderblöcken.

    sinput group "--- Order Block Indicator settings ---"
    input          int  Rango_universal_busqueda = 500; // Universal range for searching order blocks
    input          int  Witdth_order_block = 1;         // Width of the order block lines
    
    input          bool Back_order_block = true; // Enable object to be drawn in the background
    input          bool Fill_order_block = true; // Enable fill for the order block rectangle
    
    input          color Color_Order_Block_Bajista = clrRed;   // Assign red color for bearish order block
    input          color Color_Order_Block_Alcista = clrGreen; // Assign green color for bullish order block
    

    2.2: Schlüsselstrukturen und Funktionen anlegen

    In diesem Abschnitt definieren wir die wichtigsten Strukturen und Funktionen zur Verwaltung von Orderblöcken in unserem Indikator. So können wir die wichtigsten Informationen für jeden Orderblock speichern und organisieren und die Daten mithilfe dynamischer Arrays effizient verwalten.

    1. Variable zum Speichern des Zeitpunkts des letzten Kerzen

    Zunächst erstellen wir eine Variable, die den Zeitpunkt der letzten verarbeiteten Kerze speichert. Dies ist notwendig, um doppelte Orderblöcke auf derselben Kerze zu vermeiden und eine korrekte Verfolgung im Zeitverlauf zu gewährleisten.

    datetime tiempo_ultima_vela;
    

    2. Handle für den ATR-Indikator:

    Der zweite Schritt besteht darin, einen Indikator-Handle zu erstellen:

    ATR (Average True Range)
    

    Dies wird uns helfen, die Marktvolatilität zu messen und die Logik des Indikators zu ergänzen. Dieses Handle wird gleich zu Beginn initialisiert, damit er bei der Berechnung von Orderblöcken verwendet werden kann.

    int atr_i;
    

    3. Anlegen einer Struktur zur Datenspeicherung der Orderblöcke

    Nun erstellen wir eine Struktur, in der die relevanten Daten für jeden Orderblock gespeichert werden. Diese Struktur ist sehr wichtig, da sie Informationen über den Zeitpunkt, die Preise, den Namen des Blocks und darüber, ob er gemildert wurde oder nicht, enthält. Außerdem werden wir ein dynamisches Array erstellen, in dem alle erkannten Orderblöcke gespeichert werden.

    struct OrderBlock
    {
       datetime time1;      // Time of the candle prior to the first candle of the order block
       double price1;       // Upper price level of the order block (level 1)
       double price2;       // Lower price level of the order block (level 2)
       string name;         // Name of the order block
       bool mitigated;      // Status of the order block (true if mitigated, false if not)
    };
    

    Feldbeschreibung der Struktur

    Die Struktur von „OrderBlock“ besteht aus den folgenden Feldern:

    • time1: In diesem Feld wird die Zeit der Kerze gespeichert, die der ersten Kerze des Orderblocks vorausgeht. Es ist nützlich zu wissen, wann ein Block gebildet wurde und die Zeit zu vergleichen.

    • price1: Stellt das Hoch oder den ersten Schlüsselpreis des Orderblocks dar. Dies ist das Hoch im Falle eines steigenden Orderblocks.

    • price2: Stellt das Tief oder den zweiten Schlüsselpreis des Orderblocks dar. Dies ist das Tief im Falle eines steigenden Orderblocks.

    • name: Speichert einen eindeutigen Namen zur Identifizierung von Orderblöcken im Chart. Dieser Name wird verwendet, um den Block eindeutig zu kennzeichnen und ihn visuell erkennbar zu machen.

    • mitigated: Zeigt an, ob der Orderblock abgeschwächt wurde oder nicht. Wenn sich der Orderblock abgeschwächt hat (d.h. der Preis hat die Blockstufen berührt oder überschritten), ist dieser Wert true, andernfalls false.

    4. Dynamisches Array zur Speicherung von Orderblöcken

    Schließlich wird ein dynamisches Array erstellt, das alle identifizierten Orderblöcke enthält. Diese Arrays ermöglichen es uns, mehrere Blöcke im Speicher zu speichern und sie dynamisch zu verwalten, indem wir Orderblöcke je nach Bedarf im Laufe der Zeit ein- oder ausschalten.

    OrderBlocks ob_bajistas[];
    OrderBlocks ob_alcistas[];
    

    Die Funktion OnInit():

    Die Funktion OnInit() ist für die Initialisierung aller Elemente des Indikators verantwortlich und führt Prüfungen durch, um sicherzustellen, dass alles in Ordnung ist, bevor der Indikator zu arbeiten beginnt. Im Folgenden wird Schritt für Schritt erklärt, was im Code geschieht.

    1. Variablen initialisieren

    Zu Beginn wird der Anfangswert der Variablen festgelegt:

    "tiempo_ultima_vela"

    A 0. Diese Variable ist wichtig, da sie den Zeitpunkt der letzten verarbeiteten Kerze speichert, wodurch Verdoppelungen vermieden und der Indikatorfluss ordnungsgemäß verwaltet wird.

    tiempo_ultima_vela = 0;

    Dann wird das Indikator-Handle gestartet:

    "ATR" (Average True Range)

    Die ATR-Periode beträgt 14 Kerzen. Die ATR wird dann zur Messung der Marktvolatilität verwendet und trägt zur Logik der Orderblöcke bei.

    atr_i = iATR(_Symbol, PERIOD_CURRENT, 14);
    
    

    2. Überprüfung der Eingabeparameter

    Nach der Initialisierung prüft der Code, ob sich der Wert der Variablen geändert hat:

    Rango_universal_busqueda

    Es sind weniger als 40. Diese Variable definiert den Bereich, in dem der Indikator nach Orderblöcken sucht. Wenn dieser Bereich zu klein ist, kann dies die Genauigkeit und Wirksamkeit des Indikators beeinträchtigen, sodass eine Warnmeldung angezeigt wird und der Indikator anhält und den Wert INIT_PARAMETERS_INCORRECT zurückgibt.

    if (Rango_universal_busqueda < 40)
    {
       Print("Search range too small");
       return (INIT_PARAMETERS_INCORRECT);
    }
    

    Mit dieser Prüfung können Sie sicherstellen, dass der Suchbereich die richtige Größe hat, damit der Indikator ordnungsgemäß funktioniert, und um falsche Einstellungen zu vermeiden, die seine Funktion beeinträchtigen könnten.

    3. Überprüfung der Initialisierung des ATR-Indikators

    Der nächstes Schritt besteht darin, zu überprüfen, ob die Initialisierung des Handles des ATR-Indikators korrekt ist.

    Wenn: 

    "atr_i"

    heißt das:

    INVALID_HANDLE

    Dies bedeutet, dass bei dem Versuch, den Indikator zu erstellen, ein Fehler aufgetreten ist, sodass eine Fehlermeldung angezeigt und zurückgegeben wird:

    INIT_FAILED

    Dadurch wird der Indikator beendet.

    if (atr_i == INVALID_HANDLE)
    {
       Print("Error copying data for indicators");
       return (INIT_FAILED);
    }
    

    4. Größenänderung von dynamischen Arrays

    Die dynamischen Arrays, in denen die steigenden und fallendenOrderblöcke gespeichert werden, werden bei Bedarf in ihrer Größe verändert, während ihre Anfangsgröße auf 0 gesetzt wird. Dadurch wird sichergestellt, dass beide Arrays zu Beginn des Programms leer sind, um neue Orderblöcke zu speichern.

    ArrayResize(ob_bajistas, 0);
    ArrayResize(ob_alcistas, 0);
    

    Die Verwendung dynamischer Arrays ist sehr wichtig, um die Anzahl der erkannten Orderblöcke zu kontrollieren, da diese Arrays je nach Bedarf wachsen oder schrumpfen können.

    Initialisierung erfolgreich abgeschlossen

    Wenn alle Prüfungen und Initialisierungen ohne Fehler abgeschlossen sind, dann gibt die Funktion:

    OnInit()

    zurück:

    INIT_SUCCEEDED

    Dies bedeutet, dass der Indikator ordnungsgemäß initialisiert und bereit ist, auf dem Chart zu laufen.

    return (INIT_SUCCEEDED);

    Hier der vollständiger Code:

    int OnInit()
      {
    //--- indicator buffers mapping
       tiempo_ultima_vela = 0;
       atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
    
       if(Rango_universal_busqueda < 40)
         {
          Print("Search range too small");
          return (INIT_PARAMETERS_INCORRECT);
         }
    
       if(atr_i== INVALID_HANDLE)
         {
          Print("Error copying data of indicators");
          return(INIT_FAILED);
         }
    
       ArrayResize(ob_bajistas,0);
       ArrayResize(ob_alcistas,0);
    
    
    //---
       return(INIT_SUCCEEDED);
      }
    

    Jetzt fügen wir Code zum Deinitialisierungsereignis des Indikators hinzu, um Speicher freizugeben:

    void OnDeinit(const int reason)
      {
    //---
      Eliminar_Objetos();
    
      ArrayFree(ob_bajistas);
      ArrayFree(ob_alcistas);
       
      }
    
    Eliminar_Objetos();

    Dies ist die Funktion, mit der wir später alle erstellten Rechtecke entfernen werden.

    Prüfung, ob eine neue Kerze erschienen ist

    Der Zweck dieses Codes besteht darin, den Indikator zu optimieren, indem sichergestellt wird, dass er nur ausgeführt wird, wenn eine neue Kerze geöffnet wird, und nicht bei jeder Kursänderung. Die Ausführung eines Indikators bei jeder Preisänderung würde Computerressourcen verschwenden, insbesondere wenn wir mehrere Vermögenswerte analysieren oder mehrere Indikatoren verwenden.

    Dazu wird der Zeitpunkt des letzten verarbeiteten Kerzen überprüft. Wenn eine neue Kerze erkannt wird, wird die Indikatorverarbeitung aktiviert. Nachstehend finden Sie eine Beschreibung der einzelnen Teile des Codes.

    1. Initialisierung 

    Wir haben eine boolsche Variable: 

    "new_vela"

    Sie wird als Auslöser verwendet. Der Standardwert ist false, was bedeutet, dass keine neue Kerze geöffnet wurde.

    bool new_vela = false; // We assign the trigger that tells us whether a new candle has opened to false

    2. Prüfung auf das Auftauchen einer neuen Kerze

    Der nächstes Schritt besteht darin, den Zeitpunkt der letzten verarbeiteten Kerze zu überprüfen:

    tiempo_ultima_vela

    Prüfen wir, ob er sich von der Zeit der aktuellen Kerze im Chart unterscheidet. Funktion:

    iTime()

    Sie gibt die Eröffnungszeit einer bestimmten Kerze zurück, in diesem Fall die jüngste Kerze (die als 0 indiziert ist). Wenn die Zeit nicht übereinstimmt, bedeutet dies, dass eine neue Kerze erschienen ist.

    if(tiempo_ultima_vela != iTime(_Symbol, PERIOD_CURRENT, 0)) // Check if the current time is different from the stored time
    {
        new_vela = true; // If it doesn't match, set the new candle indicator to true
        tiempo_ultima_vela = iTime(_Symbol, PERIOD_CURRENT, 0); // Update the last processed candle time
    }
    

    Dieser Codeblock führt zwei Aufgaben aus:

    1. Prüfung, ob sich eine neue Kerze gebildet hat.
    2. Aktualisierung der Variablen last_sail_time mit der Zeit des neuen Kerzen zur späteren Verwendung.

    Ausführen des Hauptcodes

    Wenn diese Variable 

    new_vela

    wahr ist, bedeutet dies, dass eine neue Kerze geöffnet wurde. In diesem Fall können wir den Hauptcode des Indikators ausführen, der die Orderblöcke verarbeitet, oder jede andere geeignete Logik. Durch diese Prüfung vermeiden wir, dass der Code bei jedem Kurs-Tick ausgeführt wird, sondern nur, wenn eine neue Kerze im Chart erscheint.

    if(new_vela == true)
    {
       // Here we will place the main code of the indicator that will run only when a new candlestick opens
    }
    

    Erstellen von Arrays zum Speichern von Kerzen-, Volumen- und ATR-Daten

    Dieser Block enthält Arrays, die wichtige Informationen über Kerzen, Tickvolumen und ATR speichern. Diese Daten sind für die Analyse des Preisverhaltens und die Identifizierung von Orderblöcken erforderlich.

    1. Deklaration der Arrays

    Wir deklarieren Arrays der Typen double, datetime und long, um die entsprechenden Werte zu speichern:

    double openArray[];  // Stores Open price of candlesticks
    double closeArray[]; // Stores Close price of candlesticks
    double highArray[];  // Stores High price of candlesticks
    double lowArray[];   // Stores Low price of candlesticks
    datetime Time[];     // Stores the time of each candlestick
    double atr[];        // Stores ATR values, which indicator market volatility
    long Volumen[];      // Stores tick volume, representing the number of transactions in each candlestick
    

    2. Einrichten von Arrays als Zeitreihen

    Wir verwenden die Funktion ArraySetAsSeries(), um Arrays als Zeitreihen zu verwenden. Dies bedeutet, dass der Index 0 die jüngste Kerze darstellt, was den Zugriff auf die neuesten Kerzendaten erleichtert:

    ArraySetAsSeries(openArray, true);
    ArraySetAsSeries(closeArray, true);
    ArraySetAsSeries(highArray, true);
    ArraySetAsSeries(lowArray, true);
    ArraySetAsSeries(Time, true);
    ArraySetAsSeries(Volumen, true);
    ArraySetAsSeries(atr, true);
    

    3. Kopieren von Kerzen- und ATR-Daten

    Dann verwenden wir die Funktionen CopyOpen, CopyClose, CopyHigh, CopyLow, CopyTime und CopyTickVolume, um die Kerzen- und Tickvolumen-Daten in die gewünschten Arrays zu kopieren. CopyBuffer wird auch verwendet, um ATR-Werte zu erhalten:

    int copiedBars = CopyOpen(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), openArray);
    if(copiedBars < 0)
    {
        Print("Error copying data from Open: ", GetLastError());
    }
    CopyClose(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), closeArray);
    CopyHigh(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), highArray);
    CopyLow(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), lowArray);
    CopyTime(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), Time);
    CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), Volumen);
    CopyBuffer(atr_i, 0, 0, (Rango_universal_busqueda * 2), atr);
    

    4. Fehlerbehandlung

    Beim Kopieren offener Daten wird geprüft, ob die Anzahl der kopierten Balken negativ ist, was auf einen Fehler hinweist. In diesem Fall wird zur Unterstützung der Fehlersuche eine Fehlermeldung mit Hilfe der Funktion GetLastError() angezeigt.

    Vorbereitung des Codes für die Logik der Orderblockerkennung

    Vor der Implementierung der Logik zur Erkennung von Orderblöcken werden wir eine Reihe von notwendigen Vorbereitungen treffen:

    1. Erkennen von vorherigen Aufwärtskerzen: Wir werden eine Funktion erstellen, die feststellt, ob es vor der ersten Kerze des Musters eine Aufwärtskerze gibt. Falls gefunden, wird der Wert dieser ersten Kerze der nächstgelegenen zugewiesen, sodass wir den Orderblock vom Beginn der Bewegung an zeichnen können.

    2. Zeichnen von Rechtecken: Wir implementieren eine spezielle Funktion zum Zeichnen von Rechtecken, um Orderblöcke auf dem Chart visuell darzustellen.

    3. Verwaltung der Arrays: Wir werden Funktionen entwickeln, um die erkannten Orderblöcke in die entsprechenden Arrays einzufügen. Dazu gehören:

      • Überprüfung der Vervielfältigung: Eine Funktion, mit der wir uns vergewissern können, dass der Orderblock, den wir hinzufügen wollen, noch nicht erfasst wurde. Es werden also nur neue Orderblöcke hinzugefügt.
    4. Abschwächende Orderblöcke: Wir werden eine Funktion erstellen, die prüft, ob ein Orderblock sich abschwächt.

    5. Entfernung von Orderblöcken: Wir werden eine Funktion hinzufügen, mit der Orderblöcke als entfernt markiert werden können, um unsere Benachrichtigungen ordentlich und sauber zu halten.

    Mit diesen Funktionen können wir anfangen, Orderblöcke in Arrays hinzuzufügen und sicherstellen, dass nur neue Blöcke registriert werden. Aufgrund des Umfangs des Codes werden wir ab jetzt nicht mehr Zeile für Zeile erklären, sondern stattdessen eine kurze Beschreibung jedes relevanten Abschnitts geben.

    1. Funktion

    //+------------------------------------------------------------------+
    //|     Functions to Manage and Add Values to the Arrays             |
    //+------------------------------------------------------------------+
    void AddIndexToArray_alcistas(OrderBlocks &newVela_Order_block_alcista)
      {
       if(!IsDuplicateOrderBlock_alcista(newVela_Order_block_alcista))  // Here we check if the structure we are about to add already exists in the array
         {
          int num_orderblocks_alcista = ArraySize(ob_alcistas);  // We assign the variable "num_orderblocks_alcista" the current size of the ob_alcistas array
          ArrayResize(ob_alcistas, num_orderblocks_alcista + 1); // Resize the array by increasing its size by 1 to make space for a new order block
          ob_alcistas[num_orderblocks_alcista] = newVela_Order_block_alcista; // Assign the new order block to the new index (last position) in the array
         }
      }
    
    bool IsDuplicateOrderBlock_alcista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_alcistas); i++) //Start a loop to go through all positions of the ob_alcistas array
         {
          if(ob_alcistas[i].time1 == newBlock.time1 &&
             ob_alcistas[i].name == newBlock.name
            ) // Check if both time1 and name of the order block already exist in the array
            {
             return true; // If they do, return true (i.e., it is a duplicate)
             break; // Exit the loop
            }
         }
       return false; // If no duplicate is found, return false
      }
    
    // This would be the same logic but for bearish order blocks
    void AddIndexToArray_bajistas(OrderBlocks &newVela_Order_block_bajista)
      {
       if(!IsDuplicateOrderBlock_bajista(newVela_Order_block_bajista))
         {
          int num_orderblocks_bajistas = ArraySize(ob_bajistas);
          ArrayResize(ob_bajistas, num_orderblocks_bajistas + 1);
          ob_bajistas[num_orderblocks_bajistas] = newVela_Order_block_bajista;
         }
      }
    
    bool IsDuplicateOrderBlock_bajista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_bajistas); i++)
         {
          if(ob_bajistas[i].time1 == newBlock.time1 &&
             ob_bajistas[i].name == newBlock.name
            )
            {
             return true; // Duplicate found
             break;
            }
         }
       return false; // No duplicate found
      }
    

    Nachdem wir nun die Funktionen implementiert haben, mit deren Hilfe wir prüfen können, ob ein Orderblock kein Duplikat ist, und ihn dem dynamischen Array hinzufügen können, ist es an der Zeit zu erklären, wie wir diese Orderblöcke im Chart zeichnen werden.

    Dazu werden wir zwei wichtige Preispunkte verwenden:

    • Price 1: Im Falle eines steigenden Orderblocks stellt dieser Preis die untere Kante des Rechtecks dar. Bei einem fallenden Orderblock ist dies die obere Kante.

    • Price 2: Bei einem steigenden Orderblock entspricht dieser Preis der oberen Kante des Rechtecks. Im fallenden Orderblock wird dies die untere Kante des Rechtecks sein.

    Aufwärts-Beispiel:

         steigender OB

     Abwärts-Beispiel:

          Fallender Order Block

    Gehen wir nun zur Demonstration von Funktionen für die Abschwächung in Orderblöcken über.

    //+------------------------------------------------------------------+
    //|             Functions for Order Blocks                           |
    //+------------------------------------------------------------------+
    datetime  mitigados_alcsitas(double price, double &openArray[], double &closeArray[], double &highArray[], double &lowArray[], datetime &Time[], datetime start, datetime end)
      {
       int startIndex = iBarShift(_Symbol,PERIOD_CURRENT,start); // Using iBarShift we find the index of the candle by passing the "start" time
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);   // Using iBarShift we find the index of the candle by passing the "end" time
       NormalizeDouble(price,_Digits); // Normalize the price we will work with
    
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--) // Start a loop from start (time1 of the order block) to end (time[1])
         {
          //terminated by endIndex which will be time[0] + 1 = time[1]  --> We are searching for mitigation from past to present (backward)
          NormalizeDouble(lowArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          NormalizeDouble(highArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          //Normalizamos todas laas variable
    
          if(price > lowArray[i] || price > openArray[i] || price > closeArray[i] || price > highArray[i]) // Check if OHLC closed below price
            {
             return Time[i]; //If mitigation is found, return the time of the candle where it happened
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
            }
         }
    
       return 0; //If no mitigation was found, return 0
      }
    
    // the same in the bearish case but changing something
    // instead of the price closing below the price
    datetime  mitigado_bajista(double price, double &openArray[], double &closeArray[], double &highArray[], double &lowArray[], datetime &Time[], datetime start, datetime end)
      {
    
       int startIndex = iBarShift(_Symbol,PERIOD_CURRENT,start);
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(price,_Digits);
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--)
         {
          NormalizeDouble(lowArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          NormalizeDouble(highArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          if(highArray[i] > price || closeArray[i] > price || openArray[i] > price || lowArray[i] > price)
            {
    
             return Time[i]; // returns the time of the candle found
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
    
            }
         }
    
       return 0; // not mitigated so far
      }
    
    datetime esOb_mitigado_array_alcista(OrderBlocks &newblock, datetime end)
      {
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(newblock.price2,_Digits);
    
       for(int i = 0 ; i <  endIndex -2  ; i++)
         {
    
          double low = NormalizeDouble(iLow(_Symbol,PERIOD_CURRENT,i),_Digits);
          double close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits);
          double open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);
    
          if((newblock.price2 >= low || newblock.price2 >= open) || newblock.price2 >= close)
            {
             newblock.mitigated = true;
    
    
             return iTime(_Symbol,PERIOD_CURRENT,i); // returns the time of the found candle
            }
         }
    
       return 0; // not mitigated so far
      }
    
    datetime esOb_mitigado_array_bajista(OrderBlocks &newblock, datetime end)
      {
    
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(newblock.price2,_Digits);
    
       for(int i = 0 ; i<  endIndex -2  ; i++)
         {
    
          double high = NormalizeDouble(iHigh(_Symbol,PERIOD_CURRENT,i),_Digits);
          double close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits);
          double open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);
    
          if((high >= newblock.price2 || close >= newblock.price2) || open >= newblock.price2)
            {
             newblock.mitigated = true;
             // returns the time of the found candlestick
             return iTime(_Symbol,PERIOD_CURRENT,i);
            }
         }
    
       return 0; // not mitigated so far
      }
    
    

    Diese Funktionen sind für die Überprüfung des Zustands von Orderblöcken zuständig:

    • Funktion zur Prüfung einer Abschwächung: Diese Funktion prüft das Abschwächen von Orderblöcken und dient der Auswertung der hinzugefügten Struktur.

    • Funktion zur Aktivierung einer Abschwächung: Die zweite Funktion, die das Schlüsselwort „array“ enthält, aktiviert den Status einer Abschwächung für Orderblöcke.

    Als Nächstes werden wir uns Funktionen zum Zeichnen von Rechtecken und zum Auffinden der nächstgelegenen Auf- oder Abwärtskerze ansehen.

    Die Bestimmung der nächstgelegenen Auf- oder Abwärtskerze ist sehr wichtig. Sie soll die korrekte Erkennung der entsprechenden Kerze bei der Erkennung von Orderblöcken sicherstellen, insbesondere in Situationen, in denen sich der Orderblock zu Beginn einer starken Bewegung bildet. Dadurch wird eine falsche Erkennung in der Mitte oder am Ende einer Bewegung vermieden, die die Wirksamkeit der Analyse beeinträchtigen könnte.

    Aufwärts-Beispiel:

    Beispiel Funktion OB+

    Hier sind die Funktionen:

    //+------------------------------------------------------------------+
    //|  Functions to find the nearest bullish or bearish candle         |
    //+------------------------------------------------------------------+
    int FindFurthestAlcista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Function to find the furthest bullish candle in a consecutive sequence
    // Initialize variables
       int furthestVela = 0;
       int counter_seguidas = 0;
    
       for(int i = startVela  + 1; i <= startVela + numVelas ; i++)  // Since the candle at "start" is already known to be bullish, we skip it (+1)
         {
          //then it is obvious that the candle at time 1 is bullish (in this function), that's why we increase +1, then we check that i is less than or equal to the index of startVela + num candles
          //here num candles would be the number of candles to search for
          double Close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits); //we get the open by index
          double Open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);  //we get the close by index
    
          if(Close > Open || Close == Open)  // we check if it's a bullish candle (close > open), that is, the close must be greater than the open
            {
             counter_seguidas++; // if this is true, we increase a variable by 1, which we will use later
            }
          else
             if(Open > Close)
               {
                furthestVela = startVela  + 1 + counter_seguidas; //if the found candle is not bullish, it is obviously bearish, therefore we assign the index of the previous candle to the one that started the bullish move
                // startVela: is the candle we passed, then we add 1 because as we said before, we will draw the order block one candle before the bullish move (i.e., bearish candle)
                // to this we add the counter of consecutive candles
    
                break; // exit the loop
               }
         }
    
    //we check if the body of the candle before the move is more than 30% larger than the candle that starts the move; if it is, we revert to the normal value
       double body1 = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,(furthestVela -1)),_Digits) - NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,(furthestVela -1)),_Digits);;
       double body_furtles =  NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,furthestVela),_Digits) -  NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,furthestVela),_Digits);
    
       if(body_furtles > (1.3 * body1))
          furthestVela--;
    
       return furthestVela; // return the index of the found candle
      }
    
    // Function to search for the furthest bearish candle with consecutive bearish candles
    int FindFurthestBajista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Index of the initial candle
       int furthestVela = 0; // Initialize variable
       int counter_seguidas = 0; // Counter of consecutive bearish candles
    
       for(int i = startVela + 1; i <= startVela + numVelas; i++)
         {
          double Close = NormalizeDouble(iClose(_Symbol, PERIOD_CURRENT, i), _Digits);
          double Open = NormalizeDouble(iOpen(_Symbol, PERIOD_CURRENT, i), _Digits);
    
          // If the candle is bearish
          if(Close < Open || Close == Open)
            {
             counter_seguidas++; // Increase the counter of consecutive bearish candles
            }
          // If the candle is bullish, we stop
          else
             if(Close > Open)
               {
                // Return the candle where the bearish sequence is interrupted by a bullish one
                furthestVela = startVela + 1 + counter_seguidas;
                break;
               }
         }
    
       return furthestVela;
      }
    

    Jetzt müssen wir nur noch eine Funktion zum Zeichnen von Rechtecken erstellen:

     void RectangleCreate(long chart_ID, string name, const int sub_window, datetime time1, double price1, datetime time2, double price2, color clr, int width, bool fill, bool back , ENUM_LINE_STYLE style , bool select = false)
      {
       ResetLastError(); // reset the error
    
     // check and create rectangles
       if(!ObjectCreate(chart_ID, name, OBJ_RECTANGLE, sub_window, time1, price1, time2, price2))
         {
          Print(__FUNCTION__, ": Falo al crear el rectangulo ! Error code = ", GetLastError()  , "El nombre del rectangulo es  : " , name); //if creation fails, print the function + error code and rectangle name
         }
    
      // set the properties of the rectangles
       ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);
       ObjectSetInteger(chart_ID, name, OBJPROP_STYLE, STYLE_SOLID);
       ObjectSetInteger(chart_ID, name, OBJPROP_WIDTH, width);
       ObjectSetInteger(chart_ID, name, OBJPROP_FILL, fill);
       ObjectSetInteger(chart_ID, name, OBJPROP_BACK, back);
       ObjectSetInteger(chart_ID, name, OBJPROP_SELECTABLE, select);
       ObjectSetInteger(chart_ID, name, OBJPROP_SELECTED, select);
       ObjectSetInteger(Chart_ID, name, OBJPROP_STYLE ,style);  
      }
    

    Wenn alle diese Funktionen fertig sind, können wir zum nächsten Teil übergehen.

    2.3: Logik der Orderblockerkennung


    Die Logik, die wir für dieses System entwickelt haben, lautet wie folgt:

    1. Erkennen von Orderblöcken mit der oben beschriebenen Logik.
    2. Zuweisen der Werte der Strukturen. 
    3. Hinzufügen des Arrays zur Struktur, die Orderblöcke speichern wird.
    4. Prüfen der Abschwächung von Orderblöcken.
    5. Zeichnen der Orderblöcke.
    6. Alarm.
    Auf dieser Grundlage können wir mit der Programmierung des Indikators beginnen.

    Zunächst legen wir Strukturen an, um die Werte des Orderblocks zu speichern:

    • Wir erstellen 4 Variablen, die die Form der OrderBlock-Struktur haben werden,
    • 2, um steigende Orderblöcke (Indikator- und Price Action-Orderblöcke) zu speichern und
    • 2, um fallende Orderblöcke (Indikator- und Price-Action-Orderblöcke) zu speichern.

    OrderBlocks  newVela_Order_block_alcista;
    OrderBlocks  newVela_Order_block_volumen;
    OrderBlocks newVela_Order_Block_bajista;
    OrderBlocks newVela_Order_Block_bajista_2;
    

    Nachdem wir diese Strukturen hinzugefügt haben, verfügen wir bereits über Variablen, in denen die Werte des Orderblocks gespeichert werden.

    Jetzt verwenden wir einfach die Logik, um Bestellblöcke zu erkennen. Also, fangen wir an.

    Wir beginnen mit der Logik:

    • Wir müssen nach Orderblöcken im Kerzen-Bereich suchen und ihnen einen Index zuweisen, was der Suche nach einem Kerzen-Muster mit den von uns in der Logik festgelegten Bedingungen ähnelt.
    • Hierfür verwenden wir eine for-Schleife.

    Hier ist der Code:    

    for(int i = Rango_universal_busqueda  ; i  > 5  ; i--)
       {
    
    //checking errors
    if(i + 3> ArraySize(highArray)  || i + 3 > ArraySize(atr))
    continue;
    if(i < 0)
    continue;
    
    //--------Variable Declaration--------------------------------------------//
    
    // Update candle indices
    int one_vela = i ; // central candlestick
    int  vela_atras_two = i +2;
    int vela_atras_one = one_vela +1;
    int two_vela = one_vela - 1;
    int tree_vela = one_vela - 2;
    int four_vela = one_vela -3;
    
    NormalizeDouble(highArray[vela_atras_one],_Digits);
    NormalizeDouble(lowArray[vela_atras_one ], _Digits);
    NormalizeDouble(closeArray[vela_atras_one ],_Digits);
    NormalizeDouble(openArray[vela_atras_one ],_Digits);
    
    NormalizeDouble(highArray[two_vela],_Digits);
    NormalizeDouble(lowArray[two_vela], _Digits);
    NormalizeDouble(closeArray[two_vela],_Digits);
    NormalizeDouble(openArray[two_vela],_Digits);
    
    NormalizeDouble(highArray[tree_vela],_Digits);
    NormalizeDouble(lowArray[tree_vela], _Digits);
    NormalizeDouble(closeArray[tree_vela],_Digits);
    NormalizeDouble(openArray[tree_vela],_Digits);
    
    NormalizeDouble(highArray[one_vela],_Digits);
    NormalizeDouble(lowArray[one_vela], _Digits);
    NormalizeDouble(closeArray[one_vela],_Digits);
    NormalizeDouble(openArray[one_vela],_Digits);
    
    // Calculate average body size of previous candles
    double body1 = closeArray[one_vela] - openArray[one_vela];
    double body2 = closeArray[two_vela] - openArray[two_vela];
    double body3 = closeArray[tree_vela] - openArray[two_vela];
    
    // Volume condition
    long Volumen_one_vela = Volumen[one_vela];
    long Volumen_two_vela = Volumen[two_vela];
    long volumen_vela_atras_one = Volumen[vela_atras_one];
    

    • Grundsätzlich erstellen wir in diesem Code eine Schleife, die sich vom Hoch der Kerze aus bewegt, der sein wird:

    (Rango_universal_busqueda)

    und endet bei Index 6:

    i  > 5

    In diesem Fall i > 5 

    Wir subtrahieren 1 von dem Wert von i.

    • Wir normalisieren den OHCL der Kerzen, mit denen wir arbeiten werden, massiv.
    • Wir ordnen den Kerzenkörper wie folgt zu: Schließen - Öffnen.
    • Wir erhalten den Tick-Wert, allerdings nur für den Spitzenfall:

    //Volume 
    long Volumen_one_vela = Volumen[one_vela];
    long Volumen_two_vela = Volumen[two_vela];
    long volumen_vela_atras_one = Volumen[vela_atras_one];
    

    Jetzt fügen wir noch einen weiteren Fall hinzu, nämlich den ATR, den wir grundsätzlich analysieren, um starke Kursbewegungen in eine Richtung zu erkennen.  

    • In dem unten abgebildeten Codefragment sehen Sie die grundlegenden logischen Bedingungen und den ATR-Indikator:
      //Boolean variables to detect if the case is met (only Price Action)
      bool esVelaCorrecta_case_normal =false;
      bool  esVela_Martillo = false;
      
      //Here we check that 4 consecutive bullish candles have formed with close > open
      if(
         closeArray[one_vela] > openArray[one_vela] &&
         closeArray[two_vela] > openArray[two_vela] &&
         closeArray[tree_vela] > openArray[tree_vela] &&
         closeArray[four_vela] > openArray[four_vela]
      )
        {
      
         esVelaCorrecta_case_normal =true; // if true, assign true to "esVelaCorrecta_case_normal"
        }
      else
         esVelaCorrecta_case_normal =false; // otherwise assign false
      
      bool fuerte_movimiento_alcista =false; // create a variable that activates only if a strong bullish movement occurs
      
      // Check if a movement of 6 consecutive bullish candles was created
      if(
         closeArray[one_vela + 2] > openArray[one_vela + 2] &&
         closeArray[one_vela + 1] > openArray[one_vela +1] &&
         closeArray[one_vela] > openArray[one_vela] &&
         closeArray[two_vela] > openArray[two_vela] &&
         closeArray[tree_vela] > openArray[tree_vela] &&
         closeArray[four_vela] > openArray[four_vela]
      )
        {
         fuerte_movimiento_alcista = true; // if true assign true to "fuerte_movimiento_alcista"
        }
      
      //verificamos si es vela martillo
      if(openArray[one_vela] - lowArray[one_vela] > closeArray[one_vela] - openArray[one_vela]) // check if lower wick is larger than the candle body
        {
         esVela_Martillo = true; // if so set "esVela_Martillo" to true
        }
      
      bool atr_case = false;
      
      if(atr[vela_atras_two] > atr[one_vela] && atr[two_vela] > atr[one_vela] && atr[two_vela] > atr[vela_atras_two] && closeArray[one_vela] > openArray[one_vela]
         && closeArray[four_vela] > openArray[four_vela] && closeArray[tree_vela] > openArray[tree_vela])
         atr_case = true;  // in this code we look for ATR to first fall in one candle
      //then rise, and candles 1, 3, 4 must be bullish; second candle not necessary for this case
      
      //Verification for normal case
      if((esVelaCorrecta_case_normal == true && ((lowArray[two_vela] > ((body1 *0.5)+openArray[one_vela]) && ((body2 * 0.4)+openArray[two_vela]) > highArray[one_vela]) || esVela_Martillo == true)
          && lowArray[tree_vela] > ((body2 * 0.25) +openArray[two_vela])) || fuerte_movimiento_alcista == true || atr_case == true)  
        {
         int furthestAlcista = FindFurthestAlcista(Time[one_vela],20); // call function to find previous bullish candles before "one_vela"
         if(furthestAlcista > 0) // whether or not found, will be > 0 since it returns previous candle index if none found
           {
      
            datetime time1 = Time[furthestAlcista];     //assign time of furthestAlcista candle to time1
            double price2 = openArray[furthestAlcista]; //assign open of furthestAlcista as price2 (usually drawn on a bearish candle)
            double price1 = lowArray[furthestAlcista];  //assign low of furthestAlcista as price1
      
            //assign mentioned variables to the structure
            newVela_Order_block_alcista.price1 = price1;
            newVela_Order_block_alcista.time1 = time1;
            newVela_Order_block_alcista.price2 = price2;
      
            case_OrderBlockAlcista_normal = true; //if all true, activate normal bullish case
           }
         else
            case_OrderBlockAlcista_normal =false;
      
        }
      //versión bajista
      
      bool case_OrderBlockBajista_normal = false;
      bool case_OrderBlockBajista_volumen = false;
      
      //---------------Conditions for Order Blocks--------------------//
      //+------------------------------------------------------------------+
      //| Conditions For Bearish Order Block case_normal                   |
      //+------------------------------------------------------------------+
      if(closeArray[one_vela] < openArray[one_vela]  &&
         closeArray[two_vela] < openArray[two_vela]  &&
         closeArray[tree_vela] < openArray[tree_vela]  &&
         closeArray[one_vela-3]< openArray[one_vela-3]
        )
        {
         esVelaCorrecta_case_normal =true;
        }
      else
         esVelaCorrecta_case_normal =false;
      
      bool a = false;
      
      if(atr[vela_atras_two] > atr[one_vela] && atr[two_vela] > atr[one_vela] && atr[two_vela] > atr[vela_atras_two] && esVelaCorrecta_case_normal)
         a= true;
      
      bool fuerte_movimiento_bajista =false;
      
      if(
         closeArray[one_vela + 2] < openArray[one_vela + 2] &&
         closeArray[one_vela + 1] < openArray[one_vela +1] &&
         closeArray[one_vela] < openArray[one_vela] &&
         closeArray[two_vela] < openArray[two_vela] &&
         closeArray[tree_vela] < openArray[tree_vela] &&
         closeArray[one_vela - 3] <= openArray[one_vela - 3]
      )
        {
         fuerte_movimiento_bajista = true;
        }
      
      // Verification for normal bearish case
      if((esVelaCorrecta_case_normal == true && highArray[two_vela] < ((body1 *0.70)+closeArray[one_vela]) && ((body2 * 0.4)+closeArray[two_vela]) < lowArray[one_vela] && highArray[tree_vela] < highArray[two_vela])
         || a == true || fuerte_movimiento_bajista == true
        )
        {
         int furthestBajista = FindFurthestBajista(Time[one_vela], 20);
         if(furthestBajista != -1)
           {
      
            datetime time1 = Time[furthestBajista];
            double price1 = closeArray[furthestBajista];
            double price2 = lowArray[furthestBajista];
      
            newVela_Order_Block_bajista.price1 = price1;
            newVela_Order_Block_bajista.time1 = time1;
            newVela_Order_Block_bajista.price2 = price2 ;
      
           }
         else
            case_OrderBlockBajista_normal =false;
        }
      //+------------------------------------------------------------------+
      

      Lassen Sie uns die einzelnen Funktionen mit entsprechenden Kommentaren erläutern. Wir suchen also nach bestimmten Mustern, und wenn wir sie finden, aktivieren wir die booleschen Variablen.

      Als Nächstes prüfen wir, ob es vor der Kerze one_candle, also der Kerze, mit der die Bewegung beginnt, Aufwärtskerzen gibt.

      Schließlich weisen wir den Orderblöcken Preis- und Zeitwerte zu.

      Nun gehen wir zum Volumenfall über, wo wir sowohl Volumenspitzen als auch steigende Volumina betrachten werden.

      //condition orderblock volume --------------------------------//
      if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
        {
         VolumenCorrecto = true; //here we check the volume peak
        }
      else
         VolumenCorrecto = false;
      
      //so that the bullish candle behind is bearish and 2 bullish
      if(closeArray[one_vela] > openArray[one_vela]  &&
         closeArray[two_vela] > openArray[two_vela])
        {
         VelaCorrecta_casevolumen = true;
        }
      
      //consecutive case
      bool case_vol_2 = false;
      if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] < closeArray[tree_vela] && openArray[four_vela] < closeArray[four_vela])
         case_vol_2 = true;
      
      //here we verify that the highlights do not mitigate the order block
      if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true
          && ((lowArray[two_vela] > ((body1 * 0.5)+openArray[one_vela]) && ((body2 *0.6)+openArray[two_vela]) > highArray[one_vela]) || esVela_Martillo == true)
          && highArray[tree_vela] > openArray[two_vela]) || case_vol_2 == true)
        {
      //I already explained all this above, it is literally the same, we look for the closest bullish trend and assign a value to the one before it
         int furthestAlcista = FindFurthestAlcista(Time[one_vela],20);
         if(furthestAlcista > 0)
           {
      
            datetime time1 = Time[furthestAlcista];
            double price2 = openArray[furthestAlcista];
            double price1 = lowArray[furthestAlcista];
      
            newVela_Order_block_volumen.price1 = price1;
            newVela_Order_block_volumen.time1 = time1;
            newVela_Order_block_volumen.price2 = price2;
      
            case_orderblock_vol= true;
           }
         else
            case_orderblock_vol =false;
      
        }
      //Bearish version
      //+------------------------------------------------------------------+
      
      //+------------------------------------------------------------------+
      //| Condition for Bullish Order Block Case case_Volumen              |
      //+------------------------------------------------------------------+
      
      bool VelaCorrecta_casevolumen = false;
      bool VolumenCorrecto;
      //condition orderblock volume --------------------------------//
      //by peak volume
      if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
        {
         VolumenCorrecto = true;
        }
      else
         VolumenCorrecto = false;
      //we look here for 2 consecutive bearish candles
      if(closeArray[one_vela] < openArray[one_vela]  &&
         closeArray[two_vela] < openArray[two_vela])
        {
      
         VelaCorrecta_casevolumen = true; //we set the variable "VelaCorrecta_casevolumen" to true
        }
      //we look for an increasing volume in addition to the 3rd candle and 4th candle being bearish
      bool case_vol_2 = false;
      if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] > closeArray[tree_vela] && openArray[four_vela] > closeArray[four_vela])
         case_vol_2 = true;
      
      if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true && highArray[two_vela] < ((body1 * 0.5)+closeArray[one_vela]) && ((body2 *0.5)+closeArray[two_vela]) < lowArray[one_vela]) || case_vol_2 == true)   // verificamos si se cumple
        {
      // the peak volume case or increasing volume case
         int furthestBajista = FindFurthestBajista(Time[one_vela],20); //we look for the bearish candle closest to the 1st candle
      
         if(furthestBajista > 0)
           {
            //if this is true, which as I said before it will always be, we assign the candle values
            //to the structure variables to draw the rectangles
            datetime time1 = Time[furthestBajista];
            double price1 = closeArray[furthestBajista];
            double price2 = lowArray[furthestBajista];
      
            newVela_Order_Block_bajista_2.price1 = price1;
            newVela_Order_Block_bajista_2.time1 = time1;
            newVela_Order_Block_bajista_2.price2 = price2 ;
      
            case_OrderBlockBajista_volumen = true;
           }
         else
            case_OrderBlockBajista_volumen = false;
        }
      //+------------------------------------------------------------------+
      
      

      Nachdem wir nun die Erkennung von Orderblöcken implementiert haben, müssen wir sie dem Array hinzufügen, damit sie später im Chart angezeigt werden können.

      In diesem Code werden wir die folgenden Aktionen durchführen:

      1. Wir initialisieren die Variable „mitigated“ auf false.
      2. Wir legen einen Namen für einen Orderblock auf der Grundlage seines Typs fest und stellen die Zeit gemäß one_sail ein.
      3. Schließlich fügen wir den Orderblock in ein dynamisches Array ein.
      if(case_OrderBlockAlcista_normal == true
         && mitigados_alcsitas(newVela_Order_block_alcista.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_alcista.time1,Time[0]) == 0)  //we verify that the order block has not been mitigated
        {
         newVela_Order_block_alcista.mitigated = false; //we activate the order block status as unmitigated = false
         newVela_Order_block_alcista.name =  "Order Block Alcista normal" + TimeToString(newVela_Order_block_alcista.time1) ;  //we assign the name "Normal Bullish Order Block" + the time of one_Vela
         AddIndexToArray_alcistas(newVela_Order_block_alcista); //we add to the array to then check if they are being mitigated and draw them
      
        }
      //the same would be for the volume case
      if(case_orderblock_vol == true
         && mitigados_alcsitas(newVela_Order_block_volumen.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_volumen.time1,Time[0]) == 0)
        {
         newVela_Order_block_volumen.mitigated = false;
         newVela_Order_block_volumen.name =  "Order Block Alcista vol" + TimeToString(newVela_Order_block_volumen.time1) ;
         AddIndexToArray_alcistas(newVela_Order_block_volumen);
      
        }
      
        } 
      
      //--- Bearish version
      
      if(case_OrderBlockBajista_normal == true  && mitigado_bajista(newVela_Order_Block_bajista.price2,openArray, closeArray, highArray, lowArray, Time, Time[0],newVela_Order_Block_bajista.time1) == 0
        ) //we check if the bearish order block was not mitigated and the normal case is true
        {
         newVela_Order_Block_bajista.mitigated = false; //we initialize the state of the order block as unmitigated = false
         newVela_Order_Block_bajista.name = ("Order Block Bajista ")+ TimeToString(newVela_Order_Block_bajista.time1) ; //we assign the name as "Bearish Block Order" + the time of the 1st candle
         AddIndexToArray_bajistas(newVela_Order_Block_bajista); //we add the structure to the array
        }
      
      if(case_OrderBlockBajista_volumen == true   && mitigado_bajista(newVela_Order_Block_bajista_2.price2, openArray,closeArray,highArray,lowArray,Time,Time[0],newVela_Order_Block_bajista_2.time1)== 0
        )//we check if the bearish order block was not mitigated and the volume case is true
        {
         newVela_Order_Block_bajista_2.mitigated = false; //we initialize the state of the order block as unmitigated = false
         newVela_Order_Block_bajista_2.name = ("Order Block Bajista ") + TimeToString(newVela_Order_Block_bajista_2.time1) ; //we assign the name as "Bearish Block Order" + the time of the 1st candle
         AddIndexToArray_bajistas(newVela_Order_Block_bajista_2); //we add the structure to the array
      
        }
        } 
      //+------------------------------------------------------------------+
      

      Jetzt können wir mit dem Zeichnen und Prüfen der Milderung fortfahren.

      2.4: Visualisierung: Farben und Prüfen der Abschwächung von Orderblocks


      In diesem Abschnitt werden wir uns ansehen, wie man Orderblöcke aktualisiert, sie zeichnet und ihren abgeschwächten Zustand aktiviert.

      •  Wir werden sie zeichnen, indem wir über die Arrays „ob_bullish“ und „ob_bearish“ iterieren, die, wie wir bereits gesagt haben, Informationen über Bestellblöcke speichern.
      •  Wir werden die Objekte mit „ObjectMove“ verschieben, weil wir nicht alles neu zeichnen wollen, da dies die Effizienz des Programms verringert und auch mehr Computerressourcen verbraucht.

      Nachdem wir all dies getan haben, sehen wir uns nun den Code an, den ich vorbereitet habe, um diese Anforderungen zu erfüllen:

      for(int i = 0; i < ArraySize(ob_alcistas); i++) //We iterate through all the indexes of the array where the order blocks information is stored
        {
         datetime mitigadoTime = esOb_mitigado_array_alcista(ob_alcistas[i],ob_alcistas[i].time1); //we call the function that will tell us if index i has been mitigated or not. If it is, we activate its state to true
      
         if(ob_alcistas[i].mitigated == false)  //we verify that it has not been mitigated
           {
            if(mitigadoTime == 0) //We condition that the order block has not been touched by the price
              {
      
               if(ObjectFind(ChartID(),ob_alcistas[i].name) < 0) //we check if the object exists in the graph with ObjectFind
                 {
                  RectangleCreate(ChartID(), ob_alcistas[i].name, 0, ob_alcistas[i].time1, ob_alcistas[i].price1,
                                  Time[0], ob_alcistas[i].price2,Color_Order_Block_Alcista, Witdth_order_block, Fill_order_block, Back_order_block,STYLE_SOLID); //  we create the rectangle with the data
      
                 }
               else
                  ObjectMove(ChartID(),ob_alcistas[i].name,1,Time[0],ob_alcistas[i].price2);     //on the contrary, if the object exists, the only thing we will do is update it to the current time using anchor point 1
              }
           }
        }
      
      // Draw all order blocks from the orderBlocks array
      for(int i = 0; i < ArraySize(ob_bajistas); i++)
        {
         datetime mitigadoTime = esOb_mitigado_array_bajista(ob_bajistas[i],ob_bajistas[i].time1);
      
         if(ob_bajistas[i].mitigated == false)
           {
      
            if(mitigadoTime == 0)
              {
      
               if(ObjectFind(ChartID(),ob_bajistas[i].name) < 0)
                 {
                  RectangleCreate(ChartID(), ob_bajistas[i].name,0, ob_bajistas[i].time1, ob_bajistas[i].price1,
                                  Time[0], ob_bajistas[i].price2,Color_Order_Block_Bajista,Witdth_order_block,Fill_order_block,Back_order_block,STYLE_SOLID);
      
                 }
               else
                  ObjectMove(ChartID(),ob_bajistas[i].name,1,Time[0],ob_bajistas[i].price2);
      
              }
           }
      
        }
      //+------------------------------------------------------------------+
      
      • Die Erkennung des Orderblocks erfolgt innerhalb der for-Schleife, die ihrerseits mit der folgenden Bedingung ausgeführt wird:
      new_vela == true
      • Ein Rechteck wird außerhalb der Schleife gezeichnet, aber auch mit der Bedingung:
      new_vela == true

      2.5: Implementierung von Warnmeldungen für die Entschärfung von Orderblöcken und die Beseitigung von Objekten


      In diesem Abschnitt werden wir uns ansehen, wie man Warnungen implementiert, wenn Orderblöcken aufgelöst werden, und die eingangs erwähnte Funktion erstellen:

      Eliminar_Objetos()

      Beginnen wir mit der Definition von Logik:

      1. Wir müssen die folgenden Funktionen aufrufen:

      esOb_mitigado_array_bajista  

      esOb_mitigado_array_alcista

      Wenn wir feststellen, dass der Orderblock abgeschwächt wurde, geben wir den Zeitpunkt des Abschwächungs-Kerzen zurück und setzen den Status des Orderblocks auf true, was bedeutet, dass der Orderblock abgeschwächt wurde.

      Um herauszufinden, ob ein Orderblock sich abschwächt oder nicht, verwenden wir daher dessen Zustand:

      mitigated

      Betrachtet man nun die Struktur des Orderblocks, so sieht man, dass er einen Preis, eine Zeit, einen Zustand und einen Namen hat:

      struct OrderBlocks
      {
       datetime time1;
       double price1;
       double price2;
       string name;
       bool mitigated;
       
      };
      

       Von dieser Struktur sind wir besonders an 2 Variablen für Warnungen interessiert:

       string name;
       bool mitigated;
      

      • mitigated: Diese boolesche Variable gibt Aufschluss darüber, ob sich der Orderblock abgeschwächt hat.
      • name: Mit diesem Parameter wird geprüft, ob der abgeschwächte Orderblock sich bereits zuvor abgeschwächt hat.
      Denken Sie daran, dass der Orderblock, sobald „mitigated“ aktiviert ist, immer „mitigated“, also abgeschwächt, sein wird. Wir brauchen also einen Filter, um zu verhindern, dass der Indikator von Zeit zu Zeit Alarme anzeigt: Dieser Filter wird der Name des Orderblocks sein.

      Wir brauchen noch:

      • Zwei Arrays. Diese Arrays sind vom Typ String und ebenfalls dynamisch, da wir ihre Größe aktualisieren werden, wenn mehr Orderblöcke entschärft werden.
      • Eine Funktion zum Hinzufügen von Einträgen zu den String-Arrays.
      • Eine Funktion, die prüft, ob die Zeichenkette, die wir als Name des Orderblocks übergeben, bereits im Array vorhanden ist.

      Also gut, ich habe die fehlenden Funktionen und Arrays integriert:

      • Wir gehen in den globalen Abschnitt des Programms, wo wir schreiben:

      string pricetwo_eliminados_oba[];
      string pricetwo_eliminados_obb[];
      

      Dies sind die Felder, die wir benötigen.

      Dann werden wir die folgenden Funktionen erstellen:

        bool Es_Eliminado_PriceTwo(string pName_ob , string &pArray_price_two_eliminados[])
        {
         bool a = false; // we create the variable "a" and initialize it to false
           for(int i = 0 ; i < ArraySize(pArray_price_two_eliminados) ; i++) // we traverse all the indices of the array passed as a reference
           {
            if(pName_ob == pArray_price_two_eliminados[i]) // we will compare all the positions in the array with the variable "pName_ob"
            { // if the comparison is identical the variable "a" becomes true
             a = true; 
              break; // we exit the loop
            } 
           } 
        return a; //we return the value of "a"
       }   
       
       //function to add values and assign a new size to the array passed by reference 
       void Agregar_Index_Array_1(string &array[], string pValor_Aagregar) {
          int num_array = ArraySize(array);
          if (ArrayResize(array, num_array + 1) == num_array + 1) {
              array[num_array] = pValor_Aagregar;
          } else {
            Print("Error resizing array");
          }
      }
      
      • Mit diesen Funktionen können wir sicherstellen, dass ein Orderblock sich nicht schon vorher abgeschwächt hat, um ständige Warnungen zu vermeiden.

      Nun gehen wir zu dem Teil innerhalb von OnCalculate() über, um die Programmierung der Warnmeldungen abzuschließen:

      • Wir beginnen damit, eine Schleife über alle Indizes des Arrays der Orderblöcke zu erstellen.
      • Wir überprüfen mit einem if() den Status des Orderblocks und stellen außerdem sicher, dass der Name des Orderblocks NICHT in dem String-Array enthalten ist, in dem wir die Namen der gemilderten Orderblöcke speichern.
      • Wenn all dies zutrifft, benachrichtigen wir den Nutzer mit einer Meldung, dass sich ein Orderblock abgeschwächt hat.
      • Wir fügen den Namen des Orderblocks in das String-Array ein, um Wiederholungen zu vermeiden.
      • Wir beenden die Schleife mit einer Pause.
      // Loop through the order blocks
      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) == false)
           {
            Alert("El order block alcista esta siendo mitigado: ", TimeToString(ob_alcistas[i].time1));
      
            Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
      
            break;
           }
        }
      
      // Loop through the order blocks
      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) == false)
           {
      
            Alert("El order block bajista esta siendo mitigado: ", TimeToString(ob_bajistas[i].time1));
      
            Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
      
            break;
           }
        }
      //+------------------------------------------------------------------+
      

      Nachdem wir uns nun mit den Warnungen befasst haben, können wir uns nun dem Löschen von Objekten zuwenden:

      bool  ObjectDelete(
         long    chart_id,     // chart identifier
         string  name          // object name
         );
      

      Als Nächstes benötigen wir die aktuelle Chart-ID:

      ChartID()

      Name des Orderblocks:

      name

      Mit all dem im Hinterkopf müssen wir eine Schleife durch alle unsere Orderblockpositionen ziehen und dann aufrufen:

      ObjectDelete() 

      Um alle von uns erstellten Objekte zu löschen:

        void Eliminar_Objetos()
        {
        
        for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // we iterate through the array of bullish order blocks 
        {
         ObjectDelete(ChartID(),ob_alcistas[i].name); // we delete the object using the name of the order block
        }
        for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // we iterate through the array of bearish order blocks 
        {
        ObjectDelete(ChartID(),ob_bajistas[n].name);  // we delete the object using the name of the order block
        }
        
       }
      

      An diesem Punkt haben wir die Arbeit am Indikator abgeschlossen, aber wir müssen noch die Funktionen ändern:

      OnInit() y OnDeinit()

      Um die neu hinzugefügten Variablen und Arrays richtig zu behandeln.

      Zu: 

      OnDeinit()

      Wir sorgen auch dafür, dass die vom Indikator verwendeten Ressourcen freigegeben werden, indem wir die grafischen Objekte löschen und den Speicher der dynamischen Arrays freigeben, in denen die Daten des Orderblocks gespeichert sind.

      Es ist auch wichtig, sicherzustellen, dass der ATR-Handler ordnungsgemäß freigegeben wird, wenn wir ihn korrekt initialisiert haben, um Speicherlecks oder Fehler beim Schließen des Indikators zu vermeiden. Wir gehen dabei wie folgt vor:

      if(atr_i != INVALID_HANDLE) 
          IndicatorRelease(atr_i);
      

      Endgültige Umsetzung von

      OnDeinit()

      ist wie folgt:

       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 );
      
        }
      
      //---
      
      int OnInit()
        {
      //--- indicator buffers mapping
        tiempo_ultima_vela = 0;
           atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
           
           if(Rango_universal_busqueda < 40)
           {
            Print("Search range too small");
            return (INIT_PARAMETERS_INCORRECT);
           }  
           
            if( atr_i== INVALID_HANDLE)
           {
            Print("Error copying data for indicators");  
            return(INIT_FAILED);
           }
        
        ArrayResize(ob_bajistas,0);
        ArrayResize(ob_alcistas,0);
        ArrayResize(pricetwo_eliminados_oba,0);
        ArrayResize(pricetwo_eliminados_obb,0);
         
      //---
         return(INIT_SUCCEEDED);
        }
      


      3.0: Schlussfolgerung 

      In diesem Artikel haben Sie gelernt, wie man:

      • einen Indikator erstellt, der auf den Konzepten von Smart Money und Inner Circle Trader basiert,
      • Warnmeldungen konfiguriert,
      • Rechtecke auf dem Chart zeichnet.

      Unser Endergebnis:

                 Orderblock-Beispiel-GIF

      Wenn Sie es bis hierher geschafft haben, danke ich Ihnen aufrichtig für Ihren Enthusiasmus und Ihre Geduld beim Erlernen fortgeschrittener Handelskonzepte. Die Programmierung bietet eine breite Palette von Möglichkeiten, von grundlegenden Konzepten wie Hochs und Tiefs eines Zeitraums bis hin zur Erstellung intelligenter Handelsroboter. Ich lade Sie ein, weitere Artikel zu lesen, um in dieser faszinierenden Welt der Programmierung voranzukommen.

      Ich würde mich sehr freuen, wenn Sie diesen Artikel mit jemandem teilen, der ihn brauchen könnte.

      Als Dankeschön für Ihre Lektüre habe ich eine Datei vorbereitet, die den gesamten Code für den Indikator enthält, den wir hier besprochen haben. Außerdem möchte ich erwähnen, dass diese Serie hier nicht zu Ende ist und ich plane, weitere Teile zu entwickeln. Im Folgenden finden Sie eine mögliche Übersicht über künftige Artikel:

      Zweiter Teil:

      • Integration von Puffern und Zeichnungen für diesen Indikator (Kauf- und Verkaufssignalpuffer).
      • Implementieren von Take-Profit (TP) und Stop-Loss (SL), sobald ein Signal ausgelöst wird (zwei Linien für TP und zwei für SL).
      • Einführung einer neuen fortschrittlichen Methode zur Erkennung von Orderblöcken (auf der Grundlage des Orderbooks).

      Wenn ich genügend Unterstützung erhalte, plane ich, einen dritten Teil zu erstellen, in dem wir einen Expert Advisor (EA) mit den von uns entwickelten Indikatorpuffern erstellen werden.

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

      Beigefügte Dateien |
      Letzte Kommentare | Zur Diskussion im Händlerforum (18)
      CapeCoddah
      CapeCoddah | 14 Juli 2025 in 10:56

      Hallo,

      ich habe gerade meinen EA im Strategy Tester getestet, EURUSD H4 1/1/2025-2//1/2025, und am Ende des Laufs festgestellt, dass es zwei Probleme mit dem Block Order Indikator gab.

      Erstens wählte er eine Block Order am 2/3/2025 aus, was außerhalb des Testfensters liegt und zweitens platzierte er den Block Text im Chart Shift Bereich.


      Viel Spaß mit


      CapeCoddah

      CapeCoddah
      CapeCoddah | 16 Juli 2025 in 10:34

      Hier ist eine ins Englische übersetzte Version Ihres ersten Indikators. Ich habe beschlossen, dass ich Ihre vielen Code-Kommentare auf Englisch verstehen muss und habe Google Translate erneut in Betracht gezogen, da DeepL mich nicht beeindruckt hat. Ich habe zunächst alle //-Kommentare in #/# geändert, damit Google die //-Zeilenkommentare übersetzen kann, und dann die Textdatei in ein MS Word-Dokument für die Eingabe in Translate konvertiert.Nach der Übersetzung öffnete ich das neue Dokument und speicherte es als Textdatei, benannte es um und begann mit der Syntaxierung des neuen Quelltextes. Ich schätze, dass Translate 90 % der Arbeit erledigt hat, aber es fügte Leerzeichen und Zeichen hinzu, die eine manuelle Konvertierung erforderten. Nach einem Tag Arbeit wurde es ohne Fehler kompiliert. Erstaunlicherweise funktionierte es beim ersten Versuch! Ich habe ihn mit Ihrem ursprünglichen Indikator für 1000 Balken verglichen, und sie waren identisch.

      Niquel Mendoza
      Niquel Mendoza | 16 Juli 2025 in 15:07
      CapeCoddah # :

      Hier ist eine ins Englische übersetzte Version Ihres ersten Indikators. Ich habe beschlossen, dass ich Ihre vielen Code-Kommentare auf Englisch verstehen muss und habe Google Translate erneut in Betracht gezogen, da DeepL mich nicht beeindruckt hat. Ich habe zunächst alle //-Kommentare in #/# geändert, damit Google die //-Zeilenkommentare übersetzen kann, und dann die Textdatei in ein MS Word-Dokument für die Eingabe in Translate konvertiert.Nach der Übersetzung öffnete ich das neue Dokument und speicherte es als Textdatei, benannte es um und begann mit der Syntaxierung des neuen Quelltextes. Ich schätze, dass Translate 90 % der Arbeit erledigt hat, aber es fügte Leerzeichen und Zeichen hinzu, die eine manuelle Konvertierung erforderten. Nach einem Tag Arbeit wurde es ohne Fehler kompiliert. Erstaunlicherweise funktionierte es beim ersten Versuch! Ich habe ihn mit Ihrem ursprünglichen Indikator für 1000 Balken verglichen, und sie waren identisch.

      Hallo CapeCoddah, ich fand den Code, den Sie erstellt haben, ausgezeichnet. Es tut mir leid, dass ich nicht früher geantwortet habe; ich bin in mehrere Projekte involviert, was meine Verfügbarkeit, Ihnen zu helfen, eingeschränkt hat. Heute kann ich jedoch etwas Zeit aufwenden, um an einer verbesserten Version des Indikators zu arbeiten. Ich stelle den Code unten zur Verfügung.
      CapeCoddah
      CapeCoddah | 18 Juli 2025 in 12:48

      Leider hat es den Anschein, dass Ihr Indikator strukturell fehlerhaft und für den Handel unbrauchbar ist, da er seine Berechnungen auf Basis zukünftiger Variablen durchführt, die zum Zeitpunkt der Berechnung unbekannt sind, wie im nachstehenden Code fett hervorgehoben.

      for( int i = Universal_search_range ; i > 5 ; i--) {
      //Fehlerprüfung
      if( i + 3 > ArraySize(highArray) || i + 3 > ArraySize(atr))
      continue ;
      if( i < 0) continue;

      // Aktualisierung der Candlestick-Indizes
      one_candle = i ; //Zentralkerze
      candle_behind_two = i +2;
      candle_behind_one = one_candle +1;
      zwei_Kerze = eine_Kerze - 1;
      drei_Kerze = eine_Kerze - 2;
      four_candle = one_candle -3
      ;

      // Berechne das durchschnittliche Volumen der vorherigen Kerzen
      body1 = MathAbs(closeArray[one_candle] - openArray[one_candle]);
      body2 = MathAbs(closeArray[two_candle] - openArray[two_candle]);
      body3 = MathAbs(closeArray[three_candle] - openArray[three_candle]);

      Niquel Mendoza
      Niquel Mendoza | 27 Juli 2025 in 13:23
      CapeCoddah #:

      Leider hat es den Anschein, dass Ihr Indikator strukturell fehlerhaft und für den Handel unbrauchbar ist, da er seine Berechnungen auf Basis zukünftiger Variablen durchführt, die zum Zeitpunkt der Berechnung nicht bekannt sind, wie im Code unten fett hervorgehoben.

      for( int i = Universal_search_range ; i > 5 ; i--) {
      //Fehlerprüfung
      if( i + 3 > ArraySize(highArray) || i + 3 > ArraySize(atr))
      continue ;
      if( i < 0) continue;

      // Aktualisierung der Candlestick-Indizes
      one_candle = i ; //Zentralkerze
      candle_behind_two = i +2;
      candle_behind_one = one_candle +1;
      zwei_Kerze = eine_Kerze - 1;
      drei_Kerze = eine_Kerze - 2;
      four_candle = one_candle -3
      ;

      // Berechne das durchschnittliche Volumen der vorherigen Kerzen
      body1 = MathAbs(closeArray[one_candle] - openArray[one_candle]);
      body2 = MathAbs(closeArray[two_candle] - openArray[two_candle]);
      body3 = MathAbs(closeArray[three_candle] - openArray[three_candle]);

      Hallo CapeCoddah, ich denke, das ist nicht der Fall, da der Indikator zum Beispiel alle Berechnungen mit Arrays in Serie durchführt (obwohl es nicht üblich ist, werden sie normalerweise ohne Serie durchgeführt) im Rahmen der ersten Schleife gezeigt, dass Schleife verwendet wird, um um Blöcke zu erkennen, was getan wird, ist von der "Universal_search_range" Kerze gehen (Denken Sie daran, dass in der Serie Kerze 0 ist die jüngste) bis Kerze 6 so zu keiner Zeit sehe ich zukünftige Kerzen verwendet werden und wenn das der Fall wäre dann two_candle oder ein anderer Index würde in einem Wert kleiner als 0 verursacht außerhalb des Bereichs führen. Die Kerze four_candle = one_candle - 3 wäre also der Wert, der 0 am nächsten kommt, wenn die Schleife mit i = 6 und four_candle = 3 endet, so dass ich sagen kann, dass die aktuelle Kerze 0 ist und ich zu keinem Zeitpunkt zukünftige Kerzen verwende. Der Name mag verwirrend klingen, ich weiß, aber ich habe es so gemacht, weil es für mich einfacher zu verstehen war, denn wenn es darum ging, die Orderblöcke zu erhalten, war one_vela wie die zentrale Kerze. Wenn ich also nach einer starken Bewegung suchte, wertete ich die Kerzen aus, die darauf folgten (in der Serie wäre das eine Subtraktion).

      Neuronale Netze im Handel: Direktionale Diffusionsmodelle (DDM) Neuronale Netze im Handel: Direktionale Diffusionsmodelle (DDM)
      In diesem Artikel werden gerichtete Diffusionsmodelle diskutiert, die datenabhängiges anisotropes und gerichtetes Rauschen in einem Vorwärtsdiffusionsprozess ausnutzen, um aussagekräftige Graphendarstellungen zu erfassen.
      Optimierungsmethoden der ALGLIB-Bibliothek (Teil II) Optimierungsmethoden der ALGLIB-Bibliothek (Teil II)
      In diesem Artikel werden wir die verbleibenden Optimierungsmethoden aus der ALGLIB-Bibliothek weiter untersuchen, mit besonderem Augenmerk auf deren Prüfung auf komplexe mehrdimensionale Funktionen. So können wir nicht nur die Effizienz der einzelnen Algorithmen bewerten, sondern auch ihre Stärken und Schwächen unter verschiedenen Bedingungen ermitteln.
      Analyse der Auswirkungen des Wetters auf die Währungen der Agrarländer mit Python Analyse der Auswirkungen des Wetters auf die Währungen der Agrarländer mit Python
      Welcher Zusammenhang besteht zwischen Wetter und Devisen? In der klassischen Wirtschaftstheorie wurde der Einfluss von Faktoren wie dem Wetter auf das Marktverhalten lange Zeit ignoriert. Aber alles hat sich geändert. Versuchen wir, Zusammenhänge zwischen den Witterungsbedingungen und der Stellung der Agrarwährungen auf dem Markt zu finden.
      SQLite-Fähigkeiten in MQL5: Beispiel für ein Dashboard mit Handelsstatistiken nach Symbolen und magischen Zahlen SQLite-Fähigkeiten in MQL5: Beispiel für ein Dashboard mit Handelsstatistiken nach Symbolen und magischen Zahlen
      In diesem Artikel werden wir einen Indikator erstellen, der Handelsstatistiken auf einem Dashboard nach Konto, Symbolen und Handelsstrategien anzeigt. Wir werden den Code anhand von Beispielen aus der Dokumentation und dem Artikel über die Arbeit mit Datenbanken implementieren.