English 日本語
preview
Entwicklung des Price Action Analysis Toolkit (Teil 39): Automatisierung der BOS- und ChoCH-Erkennung in MQL5

Entwicklung des Price Action Analysis Toolkit (Teil 39): Automatisierung der BOS- und ChoCH-Erkennung in MQL5

MetaTrader 5Beispiele |
81 2
Christian Benjamin
Christian Benjamin

Einführung

Willkommen zu Teil 39 der Serie Price Action Analysis Toolkit Development. In dieser Folge bauen wir ein praktisches MQL5-System auf, das Händlern hilft, das Chart mit Klarheit zu lesen, anstatt auf Kerzen zu starren und Strukturverschiebungen zu verpassen. Das Ziel ist einfach: Fraktale Umkehrpunkt (pivots) und Strukturregeln in zuverlässige, nicht nachmalende Signale zu verwandeln, auf die Sie sich sowohl im Live-Handel als auch bei historischen Tests verlassen können.

Wir verwenden fraktale Umkehrpunkte als zuverlässige lokale Ankerpunkte und erkennen zwei komplementäre Signale: ChoCH (Change of Character), das anzeigt, wenn der Markt seine bisherigen Trend verliert – zum Beispiel, wenn ein Aufwärtstrend kein höheres Hoch oder ein Abwärtstrend kein niedrigeres Tief erreicht – und BOS (Break of Structure), das bestätigt, dass sich der Trend verschoben hat, wenn der Preis entscheidend über einem vorherigen Hoch oder Tief schließt. Betrachten Sie ChoCH als eine Frühwarnung und BOS als Bestätigung.

CHoCH und BOS

Die Kombination von Fraktalen mit ChoCH/BOS bietet sauberere, nicht übermalende Anker für die Analyse, eine frühere Warnung vor potenziellen Umschwüngen und eine Klarheit über mehrere Zeitrahmen, die hilft, das Rauschen auf niedrigeren Charts zu filtern. Dieselben Regeln lassen sich problemlos automatisieren, protokollieren und Backtests unterziehen, was sie ideal für einen EA macht.

In diesem Artikel werden das Design des Algorithmus und die vollständige MQL5-Implementierung erläutert: Scannen von Fraktalen mit geschlossenem Balken, speichersichere Fraktalspeicherung, sicheres Zeichnen von persistenten Objekten und Ereignisbehandlung, die jedes bestätigte BOS/ChoCH (Desktop, Mobile und Sound) protokolliert und meldet. Am Ende haben Sie einen funktionierenden Detektor, den Sie kompilieren, testen und einsetzen können.

Wir beginnen mit der Strategielogik, gehen dann zur MQL5-Implementierung, zu Warnungen, Protokollierungs- und Benachrichtigungsoptionen, Tests und Ergebnissen über und schließen mit einem Fazit. Siehe das nachstehende Inhaltsverzeichnis.


Strategische Logik

In diesem Abschnitt werden wir die grundlegende strategische Logik hinter dem System, das wir aufbauen, erläutern. Im Mittelpunkt dieser Technik stehen der fraktale Indikator und die daraus abgeleiteten Struktursignale. Falls Sie es verpasst haben, haben wir in einem früheren Artikel über eine Fraktal-Breakout-Trendstrategie berichtet – Fraktale sind vielseitig: Über Breakouts hinaus bieten sie zuverlässige Ankerpunkte, die viele Arten von Preisaktionsanalysen unterstützen. Hier verwenden wir sie, um Change of Character (ChoCH) und Break of Structure (BOS) zu erkennen.

Ein fraktaler Pivot ist ein lokaler Umkehrpunkt, der entsteht, wenn das Hoch (oder Tief) eines mittleren Balkens höher (oder niedriger) ist als eine symmetrische Anzahl von benachbarten Balken. Bei einer Fensterlänge, die als g_length = 2*p + 1 definiert ist, ist der zentrale Balken ein hohes Fraktal, wenn sein Hoch größer oder gleich jedem Hoch im Fenster ist, und ein tiefes Fraktal, wenn sein Tief kleiner oder gleich jedem Tief im Fenster ist. Fraktale erzeugen konsistente, nicht übermalende Anker, da die Erkennung das gesamte Fenster der bestätigenden Balken erfordert.

Ein Break of Structure (BOS) ist eine entscheidende Verletzung der aktuellen Marktstruktur: Der Kurs schließt über einem vorherigen hohen Umkehrpunkt (ein Aufwärts-BOS) oder unter einem vorherigen tiefen Umkehrpunkt (ein Abwärts-BOS). Ein BOS signalisiert, dass sich das Momentum und die kurz- bis mittelfristige Markttendenz in Richtung des Durchbruchs verschoben haben – es ist die Bestätigung, die Händler in der Regel nutzen, um sich auf die neue Richtung festzulegen.

Ein Change of Character (ChoCH) ist ein früheres, schwächeres Signal dafür, dass sich die Ausrichtung des Marktes ändert. Typische Beispiele sind das Ausbleiben eines höheren Hochs während eines Aufwärtstrends oder das Ausbleiben eines niedrigeren Tiefs während eines Abwärtstrends. ChoCH sollte als Warnung betrachtet werden: Sie geht oft einem BOS voraus, wenn die nachfolgende Kursbewegung an Überzeugungskraft gewinnt, und gibt Ihnen die Möglichkeit, sich vorzubereiten (Stopps nachziehen, das Engagement reduzieren oder nach Umkehreinstiegen suchen).

Entscheidend ist, dass alle Signale nur mit geschlossenen Balken ausgewertet werden, um ein sich wiederholendes Neuzeichnen zu vermeiden. In der Praxis bedeutet das, dass wir den Schlusskurs des abgeschlossene Balken (z.B. prevClose vs. curClose) mit gespeicherten Fraktal-Ebenen vergleichen; wir deklarieren niemals einen BOS oder ChoCH auf einem unbestätigten, sich noch bildenden Balken. Die Verwendung der Logik der Schlusskurse abgeschlossener Balken gewährleistet, dass die Ereignisse reproduzierbar und in Backtests verwendet werden können.

Der Algorithmus ist absichtlich einfach und deterministisch: Er erkennt zuverlässige, fraktale Anker auf geschlossenen Balken, achtet auf Kreuzungen dieser Anker, markiert und zeichnet jeden Bruch nur einmal und gibt ein einziges Protokoll/eine einzige Meldung pro bestätigtes Ereignis aus. Dieser Ansatz mit geschlossenem Balken vermeidet ein Repaiting und erzeugt reproduzierbare, backtestbare Signale.

  • Fluss auf hoher Ebene

Logik

  • Warten auf einen neuen geschlossenen Balken (verwenden Sie den Zeitstempel des geschlossenen Balkens als Auslöser).
  • Erkenne ein Fraktal in der Mitte eines symmetrischen Fensters der Länge g_length = 2*p + 1.
  • Wenn gefunden, speicher das Fraktal (Zeit, Preis) und setze marked = false.
  • Vergleiche bei jedem neuen geschlossenen Balken prevClose und curClose mit jedem gespeicherten Fraktalpreis, um Kreuzungen zu erkennen.
  • Wenn eine Kreuzung auftritt (Bruch eines geschlossenen Balkens), markiere dieses Fraktal, zeichnen eine horizontale Linie/Trendlinie und ein verankertes Kennzeichen und gib ein Protokoll/eine Warnung aus.
  • Entferne regelmäßig alte Fraktale, damit die Felder begrenzt bleiben.

Pseudocode des Kern (kompakt):

if new_closed_bar():
  ScanForFractals()            // detect & append new fractal anchors
  PruneFractals(maxKeepBars)   // remove very old anchors
  for each fractal in stored_fractals:
    if not fractal.marked and crossed(prevClose, curClose, fractal.price):
      DrawBreak(fractal)
      LabelAndLog(fractal)
      fractal.marked = true


MQL5-Implementierung

Header und Metadaten

Am Anfang der Datei werden in kurzen Kommentaren wichtige Metadaten wie Dateiname, Autor, Copyright und Verweislinks angegeben. Diese Angaben sind wichtig für die Versionskontrolle und für die spätere Bezugnahme bei der Weitergabe oder Wiederverwendung der Datei. Die #property-Direktiven bestimmen das Kompilierungsverhalten; insbesondere #property strict erzwingt strengere Typ- und API-Prüfungen, was dazu beiträgt, subtile Fehler in der Entwicklung frühzeitig zu erkennen. Die Direktive #include <stdlib.mqh> bindet die Standard-Hilfsbibliothek ein, die häufige Programmieraufgaben vereinfacht und die Hauptcodebasis sauberer und besser wartbar hält. Wenn Leser diese Bibliothek nicht zur Verfügung haben, sollten sie sie entweder bereitstellen oder das Include entfernen, um Kompilierungsfehler zu vermeiden.

//+------------------------------------------------------------------+
//|                                       Fractal Reaction System.mq5|
//|                               Copyright 2025, Christian Benjamin.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Christian Benjamin."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

#include <stdlib.mqh>

Nutzereingaben

Der Eingabeblock stellt Konfigurationsparameter zur Verfügung, die das Verhalten des EAs steuern, ohne dass eine Neukompilierung erforderlich ist. AutoDetectLength ermöglicht die automatische Auswahl einer geeigneten fraktalen Länge entsprechend dem Zeitrahmen des Charts, während LengthInput die manuelle Eingabe ermöglicht. Beachten Sie, dass die Erkennung von Fraktalen eine ungerade Fenstergröße (z. B. 3, 5 oder 7) erfordert, um einen einzelnen mittleren Balken mit symmetrischen Nachbarn zu gewährleisten. Anzeigeoptionen wie ShowBull und ShowBear sowie Farbeinstellungen (BullColor, BearColor) verbessern die visuelle Klarheit und beschleunigen die Interpretation über mehrere Charts hinweg.

Die Parameter HorizontalRightBars und HorizontalLeftExtend legen fest, wie die Trendlinien visuell nach rechts oder links erweitert werden, um die Interpretation der Relevanz der Pegel zu erleichtern. DebugMode ermöglicht die Diagnoseprotokollierung für Entwicklung und Tests, und MaxFractalHistoryBars begrenzt, wie viele historische Fraktale aufbewahrt werden, um ein unbegrenztes Speicherwachstum bei längerem Betrieb zu verhindern.

// User-configurable inputs
input bool   AutoDetectLength = false;
input int    LengthInput      = 5;
input bool   ShowBull         = true;
input color  BullColor        = clrLime;
input bool   ShowBear         = true;
input color  BearColor        = clrRed;
input int    HorizontalRightBars = 0;
input int    HorizontalLeftExtend = 3;
input bool   DebugMode        = false;
input int    MaxFractalHistoryBars = 2000;

Globale Variablen und Datenstrukturen

Die globalen Variablen verwalten den Laufzeitstatus und die dauerhafte Speicherung. g_chart_id speichert den Chart-Identifikator, sodass alle grafischen Objekte explizit mit dem richtigen Chart verknüpft sind. g_length und p_half stehen für die Länge des Fraktalfensters bzw. seine halbe Größe; diese Werte werden einmal bei der Initialisierung berechnet und dann wiederverwendet. ea_digits, ea_point und ea_point_pips normalisieren die Preispräzision bei verschiedenen Brokern und Symbolen und sorgen für konsistente Offsets und die Platzierung von Kennzeichnungen. Das System speichert hohe und tiefe Fraktale in äquivalenten Arrays (*_time[], *_price[], *_marked[]), wobei Zeitstempel und Preis jedes Fraktal identifizieren und die Markierung eine doppelte Verarbeitung verhindert. os_state schließlich kapselt die Marktneigung des Systems: 0 für neutral, 1 für steigend und -1 für fallend.

// Internal globals
long   g_chart_id;
int    g_length;
int    p_half;
int    ea_digits;
double ea_point, ea_point_pips;

datetime bull_time[];
double bull_price[];
bool   bull_marked[];

datetime bear_time[];
double bear_price[];
bool   bear_marked[];

int    os_state = 0; // 0: none, 1: bullish, -1: bearish

Initialisierung (OnInit)

OnInit() führt die Initialisierung und Eingabesanitisierung durch. Die Routine erhält die Chart-ID, bestimmt die fraktale Länge (entweder automatisch erkannt oder vom Nutzer angegeben), erzwingt einen Mindestwert und stellt sicher, dass die Länge ungerade ist. Sie berechnet p_half und liest die Symbolgenauigkeit, um die Punkt- und Pip-Größen für eine genaue Darstellung zu berechnen. Die fraktalen Arrays werden auf einen leeren Zustand zurückgesetzt. Wenn der DebugMode aktiviert ist, gibt die Funktion eine Initialisierungsnachricht aus, die Schlüsselwerte enthält, um die korrekte Konfiguration zu bestätigen, bevor die Ausführung fortgesetzt wird.

int OnInit()
{
    g_chart_id = ChartID();

    // Determine fractal length
    if(AutoDetectLength)
    {
        if(_Period <= PERIOD_H1)
            g_length = 5;
        else if(_Period <= PERIOD_H4)
            g_length = 7;
        else if(_Period <= PERIOD_D1)
            g_length = 9;
        else
            g_length = 11;
    }
    else
    {
        g_length = LengthInput;
    }

    // Ensure odd length >=3
    if(g_length < 3)
        g_length = 5;
    if((g_length % 2) == 0)
        g_length++;

    p_half = g_length / 2;

    // Get symbol info
    ea_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    ea_point = Point();
    ea_point_pips = ea_point;
    if(ea_digits == 3 || ea_digits == 5)
        ea_point_pips = Point() * 10.0;

    // Clear fractal arrays
    ArrayResize(bull_time,0);
    ArrayResize(bull_price,0);
    ArrayResize(bull_marked,0);
    ArrayResize(bear_time,0);
    ArrayResize(bear_price,0);
    ArrayResize(bear_marked,0);

    if(DebugMode)
        PrintFormat("EA INIT: AutoDetect=%s LengthInput=%d g_length=%d p_half=%d chart=%d",
                    AutoDetectLength ? "true" : "false", LengthInput, g_length, p_half, g_chart_id);
    return(INIT_SUCCEEDED);
}

Aufräumen (OnDeinit)

OnDeinit() übernimmt die Bereinigung, wenn der EA aus dem Chart entfernt wird. Er ruft CleanupObjectsByPrefix() mit einem konsistenten Präfix auf, um alle vom EA erstellten grafischen Objekte (wie Trendlinien und Beschriftungen) zu entfernen. Auf diese Weise wird verhindert, dass verwaiste Objekte das Chart unübersichtlich machen und nachfolgende Analysen oder Werkzeuge beeinträchtigen – ein wichtiger Aspekt für professionelle Einsätze und Demonstrationen.

void OnDeinit(const int reason)
{
    CleanupObjectsByPrefix("CHB_");
}

Hauptschleife (OnTick)

OnTick() implementiert ein Modell „einmal pro geschlossenem Balken“ für deterministisches Verhalten. Die Funktion prüft den Zeitstempel des letzten geschlossenen Balkens und führt keine weitere Arbeit aus, wenn sich der Balken seit dem letzten Aufruf nicht verändert hat. Wenn ein neuer geschlossener Balken erkannt wird, löst OnTick() drei primäre Funktionen aus: ScanForFractals(), um neu validierte Fraktale zu erkennen, PruneFractals(), um Einträge zu entfernen, die älter sind als das konfigurierte Historienlimit, und ProcessFractalCrosses(), um zu ermitteln, ob der Preis gespeicherte Fraktalebenen überschritten hat. Diese Sequenzierung bewahrt die Effizienz und stellt sicher, dass alle Erkennungen und Verarbeitungen an abgeschlossenen Balken stattfinden, was für reproduzierbare Backtests und ein zuverlässiges Live-Verhalten wichtig ist.

void OnTick()
{
    static datetime last_checked = 0;
    datetime t = iTime(_Symbol, _Period, 1);
    if(t == last_checked)
        return;
    last_checked = t;

    ScanForFractals();
    PruneFractals(MaxFractalHistoryBars);
    ProcessFractalCrosses();
}

Fraktalerkennung (ScanForFractals)

ScanForFractals() prüft, ob genügend historische Daten vorhanden sind, und untersucht dann den mittleren Balken bei der Verschiebung p_half, der die Mitte des Fraktalfensters darstellt. Es ruft IsFractalHighAtShift() und IsFractalLowAtShift() auf, um festzustellen, ob der mittlere Balken als hohes oder niedriges Fraktal eingestuft werden kann. Diese Hilfsmittel führen strenge Vergleiche zwischen dem mittleren Balken und seinen Nachbarn durch. Wenn ein gültiges Fraktal identifiziert wird und noch nicht aufgezeichnet ist (Vermeidung von Duplikaten durch Zeitstempel), fügt die Routine den Zeitstempel und den Preis an die entsprechenden Felder an und markiert es als unverarbeitet, damit es später an der Erkennung von Kreuzungen teilnehmen kann.

void ScanForFractals()
{
    int bars = iBars(_Symbol, _Period);
    if(bars <= g_length)
        return;

    int centerShift = p_half;
    if(centerShift >= bars)
        return;

    // High fractal detection
    if(IsFractalHighAtShift(centerShift))
    {
        datetime t_fr = (datetime)iTime(_Symbol, _Period, centerShift);
        double p_fr = iHigh(_Symbol, _Period, centerShift);
        // Store if new
        bool exists = false;
        for(int i=0;i<ArraySize(bull_time);i++)
            if(bull_time[i]==t_fr)
                exists = true;
        if(!exists)
        {
            int n = ArraySize(bull_time);
            ArrayResize(bull_time, n+1);
            ArrayResize(bull_price, n+1);
            ArrayResize(bull_marked, n+1);
            bull_time[n] = t_fr;
            bull_price[n] = p_fr;
            bull_marked[n] = false;
            if(DebugMode)
                PrintFormat("FRAC_BULL DETECTED: t=%s price=%G", TimeToString(t_fr, TIME_DATE|TIME_SECONDS), p_fr);
        }
    }

    // Low fractal detection
    if(IsFractalLowAtShift(centerShift))
    {
        datetime t_fr = (datetime)iTime(_Symbol, _Period, centerShift);
        double p_fr = iLow(_Symbol, _Period, centerShift);
        // Store if new
        bool exists = false;
        for(int i=0;i<ArraySize(bear_time);i++)
            if(bear_time[i]==t_fr)
                exists = true;
        if(!exists)
        {
            int n = ArraySize(bear_time);
            ArrayResize(bear_time, n+1);
            ArrayResize(bear_price, n+1);
            ArrayResize(bear_marked, n+1);
            bear_time[n] = t_fr;
            bear_price[n] = p_fr;
            bear_marked[n] = false;
            if(DebugMode)
                PrintFormat("FRAC_BEAR DETECTED: t=%s price=%G", TimeToString(t_fr, TIME_DATE|TIME_SECONDS), p_fr);
        }
    }
}

Hilfsmittel zur fraktalen Validierung

IsFractalHighAtShift() und IsFractalLowAtShift() erzwingen die Fraktaldefinition durch Iteration durch das symmetrische Fenster um den mittleren Balken. Sie geben false zurück, wenn ein Nachbar die Dominanz des Zentrums ungültig macht oder wenn das gesamte Fenster nicht verfügbar ist. Diese strengen Prüfungen verhindern verfrühte oder falsche Fraktalansprüche und schützen vor Indexfehlern zu Beginn der historischen Daten oder nach Zeitrahmenänderungen. Bei größeren Datensätzen sollten Sie das massenhafte Kopieren historischer Zeitreihen mit CopyHigh/CopyLow in Betracht ziehen, um wiederholte API-Aufrufe pro Balken zu reduzieren und die Leistung zu verbessern.

bool IsFractalHighAtShift(int shift)
{
    int bars = iBars(_Symbol,_Period);
    int p = p_half;
    if(shift < 0 || shift >= bars)
        return false;

    double center = iHigh(_Symbol,_Period,shift);
    for(int k=-p; k<=p; k++)
    {
        if(k == 0)
            continue;
        int s = shift + k;
        if(s < 0 || s >= bars)
            return false; // incomplete window
        if(iHigh(_Symbol,_Period,s) > center)
            return false;
    }
    return true;
}

Hilfsmittel: Prüfung auf tiefe Fraktalwerte

bool IsFractalLowAtShift(int shift)
{
    int bars = iBars(_Symbol,_Period);
    int p = p_half;
    if(shift < 0 || shift >= bars)
        return false;

    double center = iLow(_Symbol,_Period,shift);
    for(int k=-p; k<=p; k++)
    {
        if(k == 0)
            continue;
        int s = shift + k;
        if(s < 0 || s >= bars)
            return false;
        if(iLow(_Symbol,_Period,s) < center)
            return false;
    }
    return true;
}

Verarbeitung von Kreuzungen (ProcessFractalCrosses)

ProcessFractalCrosses() wandelt gespeicherte Fraktal-Levels in umsetzbare Signale um, indem sie prüft, ob der letzte bestätigte Schlusskurs einen Fraktalpreis überschritten hat. Die Funktion verwendet einen konservativen Ansatz für geschlossene Balken: Sie vergleicht prevClose (den vorhergehenden abgeschlossenen Balken) und curClose (den letzten abgeschlossenen Balken) und wendet CrossedOver() oder CrossedUnder() an, um Kreuzungen zu bestimmen. Wenn bei einem unverarbeiteten Fraktal eine Kreuzung erkannt wird, weist der EA ein eindeutiges, kennzeichnendes Objekt zu, klassifiziert das Ereignis auf der Grundlage von os_state als Break of Structure (BOS) oder Change of Character (ChoCH), zeichnet optional eine Bruchlinie und ein Etikett, aktualisiert os_state, markiert das Fraktal als verarbeitet und gibt Warnmeldungen aus. Diese einmalige Verarbeitung pro Fraktal macht das Verhalten deterministisch und eignet sich gut für Backtests

void ProcessFractalCrosses()
{
    double prevClose = iClose(_Symbol, _Period, 2);
    double curClose  = iClose(_Symbol, _Period, 1);
    datetime curTime = (datetime)iTime(_Symbol, _Period, 1);

    // Process bullish fractals
    for(int i=0; i<ArraySize(bull_time); i++)
    {
        if(bull_marked[i])
            continue;
        double level = bull_price[i];

        if(CrossedOver(prevClose, curClose, level))
        {
            datetime fr_time = bull_time[i];
            string tag = "CHB_BULL_" + IntegerToString((int)fr_time);
            bool isChoCH = (os_state == -1);
            string niceName = isChoCH ? "Bull ChoCH" : "Bull BOS";

            if(ShowBull)
            {
                DrawBreak(tag, fr_time, level, curTime, true);
                CreateAnchoredLabel(tag + "_lbl", niceName, fr_time, level + 3*ea_point, BullColor);
            }

            os_state = 1;
            bull_marked[i] = true;
            string msg = StringFormat("%s detected: %s %s at %s price=%s",
                niceName, _Symbol, TimeframeToString(_Period), TimeToString(curTime, TIME_DATE|TIME_MINUTES), DoubleToString(level, ea_digits));
            EmitLogAlert(msg);
        }
    }

    // Process bearish fractals
    for(int i=0; i<ArraySize(bear_time); i++)
    {
        if(bear_marked[i])
            continue;
        double level = bear_price[i];

        if(CrossedUnder(prevClose, curClose, level))
        {
            datetime fr_time = bear_time[i];
            string tag = "CHB_BEAR_" + IntegerToString((int)fr_time);
            bool isChoCH = (os_state == 1);
            string niceName = isChoCH ? "Bear ChoCH" : "Bear BOS";

            if(ShowBear)
            {
                DrawBreak(tag, fr_time, level, curTime, false);
                CreateAnchoredLabel(tag + "_lbl", niceName, fr_time, level - 3*ea_point, BearColor);
            }

            os_state = -1;
            bear_marked[i] = true;
            string msg = StringFormat("%s detected: %s %s at %s price=%s",
                niceName, _Symbol, TimeframeToString(_Period), TimeToString(curTime, TIME_DATE|TIME_MINUTES), DoubleToString(level, ea_digits));
            EmitLogAlert(msg);
        }
    }
}

Hinweisgebung ((EmitLogAlert)

EmitLogAlert() zentralisiert die Benachrichtigungslogik. Die Meldung wird zu Prüf- und Kontrollzwecken immer in das Expertenprotokoll gedruckt. Optional wird ein Popup-Alarm erzeugt, eine Push-Benachrichtigung an einen konfigurierten MetaTrader-Mobilclient gesendet und eine Tondatei abgespielt. Diese Multi-Channel-Alarmierung stellt sicher, dass die Händler unabhängig von ihrem aktuellen Kontext zeitnah benachrichtigt werden. Wenn Push-Benachrichtigungen erforderlich sind, stellen Sie sicher, dass die MetaQuotes-ID im Terminal konfiguriert ist.

void EmitLogAlert(const string msg)
{
    Print(msg);

    if(EnableAlerts)
        Alert(msg);

    if(EnableNotifications)
        SendNotification(msg);

    if(EnableSound && StringLen(AlertSoundFile) > 0)
        PlaySound(AlertSoundFile);
}

Funktionen zur Visualisierung

DrawBreak(), CreateTrendLine() und CreateAnchoredLabel() verwalten Chartgrafiken. DrawBreak() berechnet die Balkenindizes für das Fraktal und die Unterbrechungszeiten mit iBarShift(), erweitert die ältere Seite um HorizontalLeftExtend und verschiebt optional die neuere Kante in Richtung Gegenwart gemäß HorizontalRightBars. CreateTrendLine() erstellt ein verankertes Trendobjekt zwischen zwei Zeitpunkten und wendet ein visuelles Styling an. CreateAnchoredLabel() platziert einen beschreibenden Text an einem Zeitstempel und einem Preis, zur besseren Lesbarkeit um einige Punkte versetzt. Jede Erstellungsroutine verwendet SafeDelete(), um kollidierende Objekte mit demselben Namen zu entfernen, bevor neue erstellt werden; dadurch wird sichergestellt, dass das Chart übersichtlich und konsistent bleibt.

void DrawBreak(const string tag, datetime fract_time, double fract_price, datetime break_time, bool bullish)
{
    int barFr = iBarShift(_Symbol, _Period, fract_time, false);
    int barBreak = iBarShift(_Symbol, _Period, break_time, false);
    int bars = iBars(_Symbol, _Period);
    if(barFr == -1 || barBreak == -1)
        return;

    int older_shift = MathMax(barFr, barBreak);
    int newer_shift = MathMin(barFr, barBreak);

    // Extend left
    older_shift = MathMin(older_shift + HorizontalLeftExtend, bars - 1);

    // Extend right towards current bar
    if(HorizontalRightBars > 0)
        newer_shift = MathMax(newer_shift - HorizontalRightBars, 0);

    // Swap if necessary
    if(older_shift < newer_shift)
    {
        int tmp = older_shift;
        older_shift = newer_shift;
        newer_shift = tmp;
    }

    datetime tLeft = (datetime)iTime(_Symbol, _Period, older_shift);
    datetime tRight = (datetime)iTime(_Symbol, _Period, newer_shift);

    string lineName = tag + "_line";
    CreateTrendLine(lineName, tLeft, fract_price, tRight, (bullish ? BullColor : BearColor), false);
}

void CreateAnchoredLabel(const string name, const string txt, datetime when, double price, color col)
{
    SafeDelete(name);
    if(ObjectCreate(g_chart_id, name, OBJ_TEXT, 0, when, price))
    {
        ObjectSetString(g_chart_id, name, OBJPROP_TEXT, txt);
        ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col);
        ObjectSetInteger(g_chart_id, name, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(g_chart_id, name, OBJPROP_BACK, false);
        ObjectMove(g_chart_id, name, 0, when, price);
    }
}

void CreateTrendLine(const string name, datetime tLeft, double price, datetime tRight, color col, bool dashed=false)
{
    SafeDelete(name);
    if(ObjectCreate(g_chart_id, name, OBJ_TREND, 0, tLeft, price, tRight, price))
    {
        ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col);
        ObjectSetInteger(g_chart_id, name, OBJPROP_WIDTH, 2);
        ObjectSetInteger(g_chart_id, name, OBJPROP_STYLE, dashed ? STYLE_DASH : STYLE_SOLID);
        ObjectSetInteger(g_chart_id, name, OBJPROP_BACK, false);
        ObjectSetInteger(g_chart_id, name, OBJPROP_SELECTABLE, false);
    }
}

void SafeDelete(const string name)
{
    if(ObjectFind(g_chart_id, name) >= 0)
        ObjectDelete(g_chart_id, name);
}

Hilfsfunktionen

CrossedOver() und CrossedUnder() kapseln die Kreuzungs-Bedingungen für geschlossene Balken(prevClose <= level && curClose > level, und die Umkehrung).

bool CrossedOver(double prevClose, double curClose, double level)
{
    return (prevClose <= level && curClose > level);
}

bool CrossedUnder(double prevClose, double curClose, double level)
{
    return (prevClose >= level && curClose < level);
}

TimeframeToString() konvertiert Zeitrahmenkonstanten in lesbare Zeichenketten für eine klarere Protokollausgabe.

string TimeframeToString(int period)
{
    switch(period)
    {
        case PERIOD_M1: return "M1";
        case PERIOD_M5: return "M5";
        case PERIOD_M15: return "M15";
        case PERIOD_M30: return "M30";
        case PERIOD_H1: return "H1";
        case PERIOD_H4: return "H4";
        case PERIOD_D1: return "D1";
        case PERIOD_W1: return "W1";
        case PERIOD_MN1: return "MN1";
        default: return IntegerToString(period);
    }
}

CleanupObjectsByPrefix() entfernt alle Objekte, die ein Präfix gemeinsam haben; es geht rückwärts durch die Objektliste, um Indexverschiebungsfehler beim Löschen zu vermeiden.

void CleanupObjectsByPrefix(const string prefix)
{
    long total = ObjectsTotal(g_chart_id);
    for(int i=total-1; i>=0; i--)
    {
        string name = ObjectName(g_chart_id, i);
        if(StringLen(name) >= StringLen(prefix) && StringSubstr(name, 0, StringLen(prefix)) == prefix)
            ObjectDelete(g_chart_id, name);
    }
}

Entfernen (PruneFractals)

PruneFractals() erhält die Leistung und Speicherstabilität, indem gespeicherte Fraktale, die älter als der Schwellenwert MaxFractalHistoryBars sind, entfernt werden. Die Routine komprimiert Arrays an Ort und Stelle mit einem Schreibzeiger und ändert dann ihre Größe, wodurch unnötige Zuweisungen vermieden werden. Beachten Sie, dass iBarShift() beim Pruning für jedes gespeicherte Fraktal aufgerufen wird, sodass massive Speicherbegrenzungen die Verarbeitungszeit erhöhen können. Durch die Wahl eines angemessenen Standardwerts (z. B. 2000 Balken) wird ein Gleichgewicht zwischen historischer Abdeckung und Laufzeiteffizienz hergestellt.

void PruneFractals(int keepBars)
{
    if(keepBars <=0) return;

    // Prune bullish fractals
    int nB = ArraySize(bull_time);
    if(nB > 0)
    {
        int write = 0;
        for(int i=0; i<nB; i++)
        {
            int sh = iBarShift(_Symbol, _Period, bull_time[i], false);
            if(sh != -1 && sh <= keepBars)
            {
                bull_time[write] = bull_time[i];
                bull_price[write] = bull_price[i];
                bull_marked[write] = bull_marked[i];
                write++;
            }
        }
        if(write != nB)
        {
            ArrayResize(bull_time, write);
            ArrayResize(bull_price, write);
            ArrayResize(bull_marked, write);
        }
    }

    // Prune bearish fractals
    int nS = ArraySize(bear_time);
    if(nS > 0)
    {
        int write = 0;
        for(int i=0; i<nS; i++)
        {
            int sh = iBarShift(_Symbol, _Period, bear_time[i], false);
            if(sh != -1 && sh <= keepBars)
            {
                bear_time[write] = bear_time[i];
                bear_price[write] = bear_price[i];
                bear_marked[write] = bear_marked[i];
                write++;
            }
        }
        if(write != nS)
        {
            ArrayResize(bear_time, write);
            ArrayResize(bear_price, write);
            ArrayResize(bear_marked, write);
        }
    }
}

Dieser EA ist ein modulares und professionelles System, das validierte Fraktale auf geschlossenen Balken identifiziert, sie effizient speichert, bestätigte Kreuzungen erkennt, um BOS/ChoCH-Signale zu generieren, klare Chart-Anmerkungen wiedergibt und Multi-Channel-Warnungen ausgibt. Jedes Modul ist auf Übersichtlichkeit, Reproduzierbarkeit und Wartbarkeit ausgelegt. In einer Produktions- oder Schulungsumgebung sollten die Leser in einer Demo-Umgebung testen und während der Validierungsschritte den DebugMode aktivieren.


Optionen für Warnungen, Protokollierung und Benachrichtigung

Der EA bietet drei konfigurierbare Ausgabekanäle, damit Sie kein Ereignis verpassen: Desktop-Popups, Mobile Push und Sound. Verwenden Sie sie zusammen für eine maximale Abdeckung oder einzeln, wenn Sie einen leiseren Betrieb wünschen.

Einstellungen:

  • EnableAlerts – Desktop-Popups über Alert() (sofort, sichtbar, während das Terminal läuft). Nützlich für die aktive Überwachung.
  • EnableNotifications – mobile Push-Nachricht über SendNotification() (erfordert die Eingabe ihrer Metaquotes-ID unter in Extras – Optionen – Benachrichtigungen und die Aktivierung von Benachrichtigungen in der Mobil-App von MetaTrader.
  • EnableSound – einen lokalen Terminalsound über PlaySound(Dateiname) abspielen; die Datei muss sich im Ordner Sounds des Terminals befinden und die Lautstärke des Terminalsounds darf nicht stumm gestellt sein.

Empfohlenes Format der Warnmeldung (klar, analysierbar):

Bull BOS detected: EURUSD H1 at 2025.08.01 14:00 price=1.12345 fr_time=2025.08.01 12:00

Geben Sie Symbol, Zeitrahmen, Ereignistyp, Bestätigungszeit, Preis und optional einen Fraktal-Zeitstempel an, um den Abgleich zu erleichtern.

Um Benachrichtigungen zu aktivieren, stellen Sie Ihre MetaQuotes-ID in den Terminaloptionen ein und stellen Sie sicher, dass die Benachrichtigungen auf Ihrem mobilen Gerät aktiviert sind.

Kopieren Sie Ihre WAV- oder MP3-Dateien in den Sounds-Ordner des Terminals und überprüfen Sie, ob die PlaySound-Funktion über Optionen – Ereignisse korrekt funktioniert. Um Spamming zu verhindern, sollten Sie die *_marked[]-Arrays verwenden, um doppelte Alarme für dasselbe Fraktal zu vermeiden, und bei volatilen Marktbedingungen eine kurze Abkühlungsphase (z. B. 30-120 Sekunden) pro Symbol einführen. Außerdem können Sie zu Prüfungs- und Fehlerbehebungszwecken Ereignisse protokollieren, indem Sie sie an eine CSV-Datei anhängen – mit FILE_COMMON oder indem Sie die Datei im MQL5/Files-Verzeichnis des Terminals ablegen – während des Testens, und stellen Sie sicher, dass Sie die Datei nach dem Schreiben sofort schließen, um Dateisperren zu vermeiden.


Tests und Ergebnisse

In diesem Abschnitt stelle ich die Leistung des Systems vor – sowohl historische Backtests als auch Live-Ergebnisse – die meines Erachtens den Zielvorgaben entsprechen und sich wie erwartet verhalten. Ich zeige die Backtest-Konfiguration und -Metriken, die Kapitalkurve und Handelsbeispiele, die das Verhalten des Detektors in der Praxis veranschaulichen, sowie einen kurzen Überblick über die Live-Performance mit Echtzeit-Screenshots und den Abgleich mit dem Backtest. Abschließend fasse ich die Einschränkungen zusammen und schlage die nächsten Schritte zur weiteren Validierung vor.

Ich habe Backtests für EURUSD und Step Index durchgeführt, beide im H1-Zeitrahmen, und zeige die Ergebnisse unten. Die folgende GIF-Animation stellt die ChoCH- und BOS-Ereignisse klar und deutlich dar – die Anmerkungen sind präzise und machen die Abfolge von Warnung und Bestätigung leicht nachvollziehbar.

  • Step Index H1: kommentierter fraktaler Pivot (Anker), ChoCH (gescheitertes HH) und Bull BOS (bestätigt auf Schlusskurs).

Schritt Index Backtest

  • EURUSD H1: kommentierter fraktaler Pivot (Anker), ChoCH (gescheitertes HH) und Bull BOS (bestätigt auf Schlusskurs).

EURUSD Backtest


Ich habe auch Live-Tests durchgeführt, um die Echtzeitleistung zu bestätigen, und das Tool hat sich genau wie erwartet verhalten. Die folgenden Screenshots zeigen, wie der EA die Ereignisse ChoCH und BOS in Echtzeit erkennt, kennzeichnet und protokolliert, wobei die Desktop-Warnungen und Chart-Objekte sofort nach dem bestätigten geschlossenen Balken erscheinen. Das Live-Verhalten stimmte mit den Backtest-Signalen überein, wobei in den Ausführungsprotokollen nur die üblichen Live-Market-Effekte (Spread und Slippage) sichtbar waren.

Live-Demo-Volatilität 75 (1s) Index M1: Echtzeit ChoCH-Warnungen und Bull BOS-Bestätigungen.

Live Volatilität 75 (1s) Index

Dieser Live-Screenshot zeigt, wie das System fraktale Umkehrpunkte verwendet, um frühe ChoCH-Warnungen zu erkennen und dann Strukturverschiebungen mit nicht übermalenden BOS-Markern zu bestätigen – eine visuelle Bestätigung des Echtzeitverhaltens des Detektors.

Live-Demo-Step Index M1: ChoCH-Warnungen und Bull BOS-Bestätigungen in Echtzeit

Der EA markiert zunächst eine Abwärtsbewegung mit einem fallenden ChoCH und aufeinanderfolgenden fallenden BOS-Levels, markiert dann ein steigenden ChoCH, wenn die Abwärtsbewegung scheitert, und bestätigt eine Aufwärts-Umkehr mit mehreren Aufwärts-BOS-Signalen – ein Beweis für das Frühwarnsystem (ChoCH) und die nicht nachmalende Bestätigung (BOS) in Echtzeit.

Nachfolgend finden Sie die Protokolle auf der Registerkarte MetaTrader 5 experts.

2025.09.03 
10 :
20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1)   Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.40
2025.09.03 
10 :
20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1)   Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.73
2025.09.03 
10 :
20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1)   Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.73
2025.09.03 
10 :
20 :58.894 Fractal Reaction System (Step Index,M1) Bear BOS detected: Step Index M1 at 2025.09.03 13:44 price=8233.6
2025.09.03 
10 :
20 :58.894 Fractal Reaction System (Step Index,M1) Alert: Bear BOS detected: Step Index M1 at 2025.09.03 13:44 price=8233.6
2025.09.03 
10 :
20 :58.896 Fractal Reaction System (Volatility 75 (1s) Index,M1)   Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3447.86

Sowohl bei historischen Backtests (EURUSD und Step Index, H1) als auch bei Live-Tests verhielt sich der fraktalbasierte Detektor genau so, wie er konzipiert war: Fraktale Umkehrpunkte lieferten stabile, Ankerpunkte ohne Repainting. Die ChoCH-Warnungen zeigten frühzeitig den Verlust der Verzerrung an, und BOS-Bestätigungen signalisierten zuverlässig die strukturellen Verschiebungen. Backtests ergaben konsistente Handelsereignisse, die mit den kommentierten GIFs übereinstimmen (Warn- und Bestätigungssequenzen), und Live-Screenshots zeigen dasselbe Muster in Echtzeit, nur mit den erwarteten Live-Market-Effekten (Spread und Slippage).


Schlussfolgerung

Das Fractal Reaction System wandelt einfache fraktale Umkehrpunkte in zuverlässige, nicht nachmalende Marktstruktursignale um: ChoCH (Change of Character) als Frühwarnung und BOS (Break of Structure) als Bestätigung. Der hier vorgestellte EA ist speichersicher, wertet aus Gründen der Reproduzierbarkeit nur geschlossene Balken aus, zeichnet persistente Strukturobjekte auf dem Chart und protokolliert und warnt bei jedem bestätigten Ereignis – ein Verhalten, das sowohl in historischen Backtests als auch in Live-Tests konsistent ist. Seine Hauptstärken sind Transparenz (überprüfbare Ereignisse), Reproduzierbarkeit (geschlossene Logik) und Praktikabilität (Desktop-, mobile und akustische Benachrichtigungen sowie einfache Abstimmung über Protokolle).

Dieses Tool ist ein Signaldetektor, kein vollständiger Handelsmanager: Faktoren der Live-Ausführung wie Spread, Slippage und Fills beeinflussen die realisierte Leistung, und die ChoCH-Warnungen sind eher informativ als vorschreibend. Bevor Sie Kapital einsetzen, sollten Sie den EA mit Ihren eigenen Instrumenten und Broker-Einstellungen validieren, das mitgelieferte Einstellungen für Backtests und die Ereignisprotokolle prüfen und das Hinzufügen von Positionsgrößenregeln, Filtern für höhere Zeitrahmen oder eines Cooldown-Mechanismus in Betracht ziehen, um Ihrem Risikoprofil zu entsprechen.

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (2)
linfo2
linfo2 | 15 Sept. 2025 in 17:31
Noch einmal vielen Dank für Ihre gut erklärt und innovative Ideen, was haben Sie in stdlib_mq5? der Bot funktioniert gut, wenn das auskommentiert ist nur in Ihrem zwickt interessiert. danke für den Upload
Chris
Chris | 25 Okt. 2025 in 11:44

Hallo,


Datei 'C:\Benutzer\Administrator\AppData\Roaming\MetaQuotes\Terminal\24F345EB9F291441AFE537834F9D8A19\MQL5\Include\stdlib_mq5.mqh' nicht gefunden Fractal_Reaction_System.mq5

Wo kann ich die Datei bekommen?


Chris


Die Grenzen des maschinellen Lernens überwinden (Teil 3): Eine neue Perspektive auf irreduzible Fehler Die Grenzen des maschinellen Lernens überwinden (Teil 3): Eine neue Perspektive auf irreduzible Fehler
Dieser Artikel wirft einen neuen Blick auf eine verborgene, geometrische Fehlerquelle, die im Stillen jede Vorhersage Ihrer Modelle beeinflusst. Indem wir die Messung und Anwendung von Prognosen des maschinellen Lernens im Handel überdenken, zeigen wir, wie diese übersehene Perspektive schärfere Entscheidungen, höhere Renditen und einen intelligenteren Umgang mit Modellen, die wir bereits zu verstehen glaubten, ermöglichen kann.
Aufbau eines professionellen Handelssystems mit Heikin Ashi (Teil 1): Entwickeln eines nutzerdefinierten Indikators Aufbau eines professionellen Handelssystems mit Heikin Ashi (Teil 1): Entwickeln eines nutzerdefinierten Indikators
Dieser Artikel ist der erste Teil einer zweiteiligen Serie, die praktische Fähigkeiten und Best Practices für das Schreiben von nutzerdefinierten Indikatoren in MQL5 vermitteln soll. Anhand des Heikin Ashi als Arbeitsbeispiel untersucht der Artikel die Theorie hinter den Heikin Ashi-Charts, erklärt, wie Heikin Ashi-Kerzen berechnet werden, und demonstriert ihre Anwendung in der technischen Analyse. Das Herzstück ist eine schrittweise Anleitung zur Entwicklung eines voll funktionsfähigen Heikin Ashi-Indikators von Grund auf, mit klaren Erklärungen, die dem Leser helfen zu verstehen, was zu programmieren ist und warum. Dieses Grundwissen bildet die Grundlage für den zweiten Teil, in dem wir einen Expert Advisor erstellen werden, der auf der Grundlage der Heikin Ashi-Logik handelt.
Vereinfachung von Datenbanken in MQL5 (Teil 1): Einführung in Datenbanken und SQL Vereinfachung von Datenbanken in MQL5 (Teil 1): Einführung in Datenbanken und SQL
Wir erforschen, wie man Datenbanken in MQL5 mit den systemeigenen Funktionen der Sprache manipuliert. Wir decken alles ab, vom Erstellen, Einfügen, Aktualisieren und Löschen von Tabellen bis zum Import und Export von Daten, alles mit Beispielcode. Der Inhalt dient als solide Grundlage für das Verständnis der internen Mechanismen des Datenzugriffs und ebnet den Weg für die Diskussion von ORM, die wir in MQL5 aufbauen werden.
Automatisieren von Handelsstrategien in MQL5 (Teil 31): Erstellung eines Price Action 3 Drives Harmonic Pattern Systems Automatisieren von Handelsstrategien in MQL5 (Teil 31): Erstellung eines Price Action 3 Drives Harmonic Pattern Systems
In diesem Artikel entwickeln wir ein 3 Drives Pattern System in MQL5, das steigende und fallende harmonische Muster der 3 Drives mit Umkehrpunkten und Fibonacci-Verhältnissen identifiziert und Handelsgeschäfte mit anpassbaren Einstiegs-, Stop-Loss- und Take-Profit-Levels basierend auf vom Nutzer ausgewählten Optionen ausführt. Wir verbessern den Einblick des Händlers mit visuellem Feedback durch Chart-Objekte.