Entwicklung des Price Action Analysis Toolkit (Teil 43): Wahrscheinlichkeit und Ausbrüche von Kerzen
Inhalt
Einführung
In diesem Artikel führen wir eine Wahrscheinlichkeitsanalyse dreier gängiger Kerzen-Typen, Doji, Engulfing und Pinbar, durch, um zu ermitteln, wie sie die Preisbewegung bei verschiedenen Instrumentenpaaren beeinflussen. Diese Kerzen signalisieren oft entweder eine Trendfortsetzung oder eine Trendumkehr. So kann beispielsweise in einem Abwärtstrend eine Aufwärts-Pinbar oder ein „Bullisches Engulfing“ auf eine mögliche Umkehr nach oben hinweisen, und umgekehrt kann in einem Aufwärtstrend eine Abwärts-Pinbar oder ein „Bearisches Engulfing“ einer Umkehr nach unten vorausgehen. Ein Doji steht oft für Unentschlossenheit: Innerhalb eines starken Trends kann es eine kurze Pause mit anschließender Fortsetzung signalisieren, während es an wichtigen Unterstützungen oder Widerständen oder nach einer langen Bewegung auf eine mögliche Umkehr hinweisen kann. Ein Doji, der in einem Seitwärtsmarkt oder ohne Volumen-Kontext gebildet wird, ist häufig neutral.
Ein häufiges Problem, mit dem Händler konfrontiert werden, sind verfrühte Einstiege, z. B. wenn sie eine Aufwärts-Pinbar sehen und sofort kaufen, nur um dann eine Verlustposition einzunehmen, wenn der Markt nicht umkehrt. Verschiedene Instrumente und Zeitrahmen reagieren unterschiedlich auf dieselbe Formation; einige Paare zeigen nach bestimmten Mustern ein stärkeres Follow-Through. Dieser EA ist als Wahrscheinlichkeitsanalytiker aufgebaut; er scannt historische Balken und quantifiziert, wie oft jedes Muster zu einer Fortsetzung, Umkehrung oder einem erfolgreichen Ausbruch bei einem bestimmten Paar und Zeitrahmen führt. Diese empirische Sichtweise hilft Händlern, die mit Kursen handeln, zu wissen, welche Muster für Umkehr- oder Fortsetzungssignale auf den von ihnen gehandelten Märkten zuverlässiger sind.
Wenn Sie sich das obige Chart genau ansehen, ist Muster A ein „Bullish Engulfing“, das einem Kaufsignal ähnelt, und wir sehen, dass sich der Markt nach der Formation nach oben bewegt, was ein positives Ergebnis bestätigt. In B beobachten wir einen Doji, gefolgt von einer Abwärts-Pinbar, und der Markt bewegt sich anschließend nach unten, was das Abwärtssignal bestätigt. Das Gleiche gilt für C, das ein „Bearish Engulfing“ ist, das einen Verkauf signalisiert und den erwarteten Rückgang bewirkt.
Bei D hat sich ein Doji auf einem Unterstützungsniveau gebildet, was auf eine potenzielle Aufwärtsumkehr hindeutet; die anschließende Pinbar sollte anhand ihres Endes und ihrer Platzierung interpretiert werden, da die Kerzenfarbe allein irreführend sein kann. Schließlich zeigt E ein „Bearish Engulfing“, das fehlschlug, wobei der Kurs sich nach oben statt nach unten bewegte, was zeigt, dass diese Muster probabilistisch sind und falsche Signale erzeugen können. Die Darstellung unseres Tools als Wahrscheinlichkeitsanalytiker hilft bei der Quantifizierung der Häufigkeit, mit der jede Formation bei einem bestimmten Paar und einem bestimmten Zeitrahmen zu einer Umkehr, einer Fortsetzung oder einem Misserfolg führt.
Überblick über die Strategie
Ich möchte, dass Sie die Idee hinter diesem Instrument verstehen. Ich werde zunächst das Konzept erläutern und ein Flussdiagramm bereitstellen, damit Sie die Logik nachvollziehen können, und dann im Abschnitt über die Implementierung die Codierungsdetails zeigen.
Zunächst sammelt das Tool die jüngsten Kursbalken, die Sie für das gewählte Symbol und den gewählten Zeitrahmen analysieren lassen. Das bedeutet, dass die Eröffnungs-, Höchst-, Tiefst- und Schlussstände jeder Kerze zusammen mit den Handelsaktivitäten auf diesem Balken geladen werden. Außerdem wird die Kartenanzeige vorbereitet und alle Zähler werden zurückgesetzt, sodass jeder Lauf neu beginnt. Ziel ist es, eine saubere, aktuelle Historie zu verwenden, damit die Prozentsätze die Daten widerspiegeln, die Sie für den Handel erwarten.
Als Nächstes untersucht das Tool die historischen Balken einzeln und prüft, ob ein Balken einer der drei von uns verfolgten Kerzen-Formen entspricht: Pinbar, Engulfing, oder Doji. Im Klartext: Eine Pinbar hat einen kleinen Körper und einen langen Schweif, ein Engulfing liegt vor, wenn der Körper einer Kerze den Körper der vorherigen Kerze vollständig bedeckt, und ein Doji hat einen kleinen Körper. Wenn das Tool eine Übereinstimmung findet, wird dieser Moment als ein Auftreten des Musters aufgezeichnet.
Für jedes Auftreten eines Musters zeichnet das Tool ein paar Dinge auf: dass das Muster auftrat, wann es auftrat und wie viel Handelsaktivität auf diesem Balken stattfand (das „Volumen“). Anschließend werden drei Ergebnisse für dieses Ereignis überprüft: Hat sich der nächste geschlossene Balken in die vom Muster vorgeschlagene Richtung bewegt, hat sich der Kurs zu irgendeinem Zeitpunkt innerhalb der nächsten N Balken, die Sie festgelegt haben, in diese Richtung bewegt und hat der Kurs den Höchst- oder Tiefstwert des Musters um den voreingestellten Pip-Betrag durchbrochen und wurde dann durch den folgenden Balken bestätigt. Jede Überprüfung wird als Ja oder Nein aufgezeichnet, und die Mengen für diese spezifischen Vorkommnisse werden ebenfalls gespeichert.

In der einfachsten Ansicht werden diese Zählungen in leicht lesbare Prozentsätze umgewandelt. Die Formeln lauten:
- Next-bar % = (Erfolgreiche Ereignisse des nächsten Balkens ÷ Gesamtereignisse) × 100
- Within-N % = (Erfolgreiche Within-N-Ereignisse ÷ Gesamtereignisse) × 100
- Breakout success % = (Bestätigte Ausbrüche ÷ Ausbrüche insgesamt) × 100
- Next-bar % (volumengewichtet) = (Volumen der erfolgreichen Next-bar-Muster ÷ Gesamtvolumen aller Muster) × 100
- Within-N % (volumengewichtet) = (Volumen der erfolgreichen Within-N-Muster ÷ Gesamtvolumen aller Muster) × 100
- Breakout % (volumengewichtet) = (Volumen der bestätigten Breakouts ÷ Gesamtvolumen der Ausbrüche) × 100
Beispiel: Wenn die 50 Pinbars zusammen ein Gesamtvolumen von 10.000 und die 15 erfolgreichen Pinbars ein Volumen von 3.000 haben, dann sind 3 .000 ÷ 10.000 = 0,30 → 0,30 × 100 = 30%igen Erfolg von Next-bar (volumengewichtet).
Schließlich zeigt das Tool die Zahlen für jedes Muster im Dashboard an und schreibt sie optional in eine CSV-Datei. Für jedes Muster werden in der Regel die Gesamthäufigkeit, der Anteil des nächsten Balkens, der Anteil innerhalb von N, die Anzahl der Ausbrüche und der Anteil der erfolgreichen Ausbrüche sowie die volumengewichteten Versionen angezeigt, wenn Sie diese aktiviert haben. Wenn ein Muster nie aufgetreten ist, zeigt das Tool 0% oder „N/A“ für nicht berechenbare Prozentsätze an. Der praktische Tipp ist einfach: Lesen Sie beide Ansichten. Die einfachen Prozentsätze geben an, wie oft etwas passiert ist, und die volumengewichteten Prozentsätze geben an, wie sehr der Handel diese Ergebnisse unterstützt hat.
Umsetzung
Dieser Expert Advisor, Kerzen Probability EA, ist ein kompaktes, aber funktionsreiches Tool zur Identifizierung wichtiger Kerzen-Muster (Pinbar, Engulfing, Doji), zur Quantifizierung ihres historischen Verhaltens und zur Darstellung dieser Ergebnisse direkt auf dem Chart und in einer Datei. Der EA wurde für die praktische Forschung und die schnelle visuelle Überprüfung entwickelt: Er hilft Händlern und Systementwicklern, die Mustererkennung in messbare, wiederholbare Statistiken umzuwandeln, die als Grundlage für das Strategiedesign oder manuelle Handelsentscheidungen dienen können.
Im Folgenden stelle ich eine strukturierte MQL5-Implementierung vor, die die Systemarchitektur, die Eingaben, das statistische Modell, die Visualisierung und die Nutzeroberfläche umfasst.
Konfiguration und Eingänge
Ein kompakter, gut dokumentierter Satz von Eingabeparametern befindet sich am Anfang der Datei, sodass die Nutzer den EA ohne Bearbeitung des Codes konfigurieren können. Diese Parameter umfassen den Analysehorizont (LookbackBars), das Follow-up-Fenster (LookAheadBars), Ausbruchs-Schwellenwerte (BreakoutPips, BreakoutConfirmBars), den Zeitrahmen (TF), Visualisierungs- und UI-Flags wie ShowPatternMarkers und ShowControlButtons sowie Export-/Gewichtungsoptionen wie ExportCSV und UseVolumeWeighting. Die Offenlegung dieser Steuerelemente ermöglicht ein schnelles Experimentieren und dokumentiert, welche Schaltflächen für die Abstimmung oder den Vergleich von Ergebnissen wichtig sind.
//--- Inputs, user-configurable parameters input int LookbackBars = 2000; // how many bars to scan input int LookAheadBars = 5; // how many bars forward to look for follow-up input int BreakoutPips = 5; // breakout threshold in pips input int BreakoutConfirmBars = 1; // bars after breakout used for confirmation input ENUM_TIMEFRAMES TF = PERIOD_CURRENT; input bool ExportCSV = false; input string CSVFileName = "PatternStats.csv"; input bool ShowPatternMarkers = true; input int MaxMarkers = 500; input int MarkerFontSize = 12; input int MarkerOffsetPips = 3; input int MarkerStackWindow = 2; input bool UseVolumeWeighting = true; input bool ShowControlButtons = true;
Datenmodell der Musterstatistik
Die Metriken für jedes Muster sind in einer einzigen Struktur, PatternStats, gekapselt, die die Anzahl des Auftretens, Next-Bar- und Within-Lookahead-Übereinstimmungen, Ausbrüche und bestätigte Ausbruchserfolge, eine kumulative Volumensumme und volumengewichtete Gegenwerte erfasst. Durch diese Art der Organisation von Metriken bleibt die Aggregation modular und das Hinzufügen neuer Statistiken oder Muster ist einfach, ohne dass der Kern der Analyseschleife verändert wird.
struct PatternStats { string name; int total; int nextSameDir; int withinLookAheadSame; int breakouts; int breakoutSuccess; double volTotal; double weightedNextSameDir; double weightedWithin; double weightedBreakouts; double weightedBreakoutSuccess; }; // Example declarations PatternStats pinbar, engulfing, dojiStats;
Initialisierung, OnInit
Startup-Aufgaben laufen deterministisch in OnInit() ab; der Code setzt alle Statistiken mittels ResetStats() auf Null, weist jeder Instanz von PatternStats lesbare Namen zu, bereitet Zähler für die einzelnen Balkenmarkierungen vor und erstellt optional Schaltflächen zur Steuerung, wenn Interaktivität aktiviert ist. Eine kurze Ergebnissbehandlung wird mit EventSetTimer() gestartet, und Analyze() wird einmal sofort aufgerufen, sodass das Chart die Ergebnisse beim Anhängen anzeigt, was ein wiederholbares Verhalten bei Neustarts und einen sicheren Betrieb während Live-Sitzungen gewährleistet.
int OnInit() { // reset stats and name patterns ResetStats(); pinbar.name = "Pinbar"; engulfing.name = "Engulfing"; dojiStats.name = "Doji"; // prepare marker counts buffer ArrayResize(markerCounts, 64); for(int i=0;i<ArraySize(markerCounts);i++) markerCounts[i]=0; // create UI if requested if(ShowControlButtons) CreateControlButtons(); // run periodically to detect new bars EventSetTimer(5); // initial analysis Analyze(); return(INIT_SUCCEEDED); }
Sauberes Ende, OnDeinit
Ein sauberes Ende ist in OnDeinit() implementiert, das den Timer beendet, jedes von EA erstellte Chartobjekt, einschließlich Markierungen, Schaltflächen und Dashboard-Elemente, löscht und einen Neuaufbau des Charts erzwingt, um Artefakte zu entfernen. Diese sorgfältige Bereinigung vermeidet Unordnung und garantiert, dass andere Werkzeuge oder Vorlagen nicht beeinträchtigt werden, wenn der EA entfernt wird.
void OnDeinit(const int reason) { EventKillTimer(); DeleteAllOurObjects(); // removes only objects with EA prefixes ChartRedraw(); }
Reaktive Aktualisierungen, OnTimer und OnChartEvent
Die Reaktionsfähigkeit des Charts wird durch einen leichtgewichtigen Timer und die Behandlung von Chartereignissen gesteuert. OnTimer() erkennt das Eintreffen neuer Balken und löst Analyze() nur bei Bedarf aus, um redundante Arbeit zu vermeiden, während OnChartEvent() auf Schaltflächenklicks wartet und die Erkennungsströme umschaltet. Wenn Sie auf ein Steuerelement klicken, wird das Aktivierungskennzeichen aktualisiert, die Schaltflächenbeschriftung wird mit Live-Metriken aktualisiert und die Analyse wird erneut ausgeführt, sodass die Nutzerinteraktionen sofort die Bildschirmstatistiken widerspiegeln.
void OnTimer() { static datetime lastTime = 0; datetime t = iTime(_Symbol, TF, 1); // previous completed bar if(t == lastTime) return; lastTime = t; Analyze(); } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK && StringFind(sparam, "Btn_") == 0) { if(StringCompare(sparam, "Btn_Pinbar") == 0) { enabledPinbar = !enabledPinbar; UpdateButtonLabel("Btn_Pinbar", pinbar, enabledPinbar); Analyze(); } if(StringCompare(sparam, "Btn_Engulfing") == 0) { enabledEngulfing = !enabledEngulfing; UpdateButtonLabel("Btn_Engulfing", engulfing, enabledEngulfing); Analyze(); } if(StringCompare(sparam, "Btn_Doji") == 0) { enabledDoji = !enabledDoji; UpdateButtonLabel("Btn_Doji", dojiStats, enabledDoji); Analyze(); } } }
Funktionen zur Mustererkennung
Jede Musterregel ist als knappe, parametrisierte Hilfsfunktion implementiert, z. B. IsPinbar(shift), IsEngulfing(shift) und IsDoji(shift). Diese Funktionen lesen nur die erforderlichen Felder der Balken und wenden konfigurierbare Schwellenwerte wie PinbarBodyPct, PinbarTailPct und DojiBodyPct an, wobei sie Richtungswerte (+1, -1, 0) zurückgeben, was die nachgelagerte Aggregation vereinfacht und eine priorisierte oder kombinierte Musterlogik ermöglicht.
int IsPinbar(int shift) { if(IsDoji(shift)) return 0; double o = iOpen(_Symbol, TF, shift), c = iClose(_Symbol, TF, shift); double h = iHigh(_Symbol, TF, shift), l = iLow(_Symbol, TF, shift); double range = h - l; if(range <= 0) return 0; double body = MathAbs(c - o); if(body > PinbarBodyPct * range) return 0; double upperTail = h - MathMax(o, c); double lowerTail = MathMin(o, c) - l; if(lowerTail >= PinbarTailPct * range && upperTail <= (1.0 - PinbarTailPct) * range) return +1; if(upperTail >= PinbarTailPct * range && lowerTail <= (1.0 - PinbarTailPct) * range) return -1; return 0; } int IsEngulfing(int shift) { int totalBars = iBars(_Symbol, TF); if(totalBars <= shift + 1) return 0; double o1 = iOpen(_Symbol, TF, shift), c1 = iClose(_Symbol, TF, shift); double o2 = iOpen(_Symbol, TF, shift + 1), c2 = iClose(_Symbol, TF, shift + 1); double body1 = c1 - o1, body2 = c2 - o2; if(body1 > 0 && body2 < 0 && o1 <= c2 && c1 >= o2) return +1; if(body1 < 0 && body2 > 0 && o1 >= c2 && c1 <= o2) return -1; return 0; } bool IsDoji(int shift) { double o = iOpen(_Symbol, TF, shift), c = iClose(_Symbol, TF, shift); double h = iHigh(_Symbol, TF, shift), l = iLow(_Symbol, TF, shift); double range = h - l; if(range <= 0) return false; return (MathAbs(c - o) <= DojiBodyPct * range); }
Zentrale Analyseschleife: Analysieren Sie
Ein einziger linearer Durchlauf durch das konfigurierte Lookback-Fenster bildet das Herzstück des EA; die symbolspezifische Skalierung für Punkte und Pips wird einmal im Voraus berechnet, Mustererkennungsfunktionen werden für jede Verschiebung aufgerufen, übereinstimmende Muster werden über UpdateStatsOnPattern() aggregiert, und optionale gestapelte Marker werden gezeichnet. Die Beibehaltung der Schleife mit einem Durchlauf gewährleistet deterministische Ergebnisse und vorhersehbare CPU-Aufwand.
void Analyze() { ResetStats(); int totalBars = iBars(_Symbol, TF); if(totalBars < 3) return; int maxIndex = MathMin(totalBars - 1, LookbackBars); if(maxIndex < 2) return; // pip scaling double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); double factor = (digits == 5 || digits == 3) ? 10.0 : 1.0; double breakoutPoints = BreakoutPips * point * factor; ArrayResize(markerCounts, MathMax(3, maxIndex + 3)); for(int i=0;i<ArraySize(markerCounts);i++) markerCounts[i]=0; if(ShowPatternMarkers) DeletePatternObjects(); int drawn=0; for(int shift=2; shift<=maxIndex; shift++) { int eng = IsEngulfing(shift); int pin = IsPinbar(shift); bool doj = IsDoji(shift); bool matched = false; if(eng != 0 && enabledEngulfing) { UpdateStatsOnPattern(engulfing, shift, eng, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Engulfing", eng, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } matched = true; } if(!matched && pin != 0 && enabledPinbar) { UpdateStatsOnPattern(pinbar, shift, pin, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Pinbar", pin, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } matched = true; } if(!matched && doj && enabledDoji) { UpdateStatsOnPattern(dojiStats, shift, 0, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Doji", 0, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } } } if(ExportCSV) SaveToCSV(); DrawDashboard(); DrawSummaryHeaderAndLabel(); if(ShowControlButtons) { UpdateButtonLabel("Btn_Pinbar", pinbar, enabledPinbar); UpdateButtonLabel("Btn_Engulfing", engulfing, enabledEngulfing); UpdateButtonLabel("Btn_Doji", dojiStats, enabledDoji); } ChartRedraw(); }
Aktualisierung der Statistiken, UpdateStatsOnPattern
Die Erkennung eines Musters aktiviert eine umfassende Messroutine, die die Ereigniszähler erhöht, das Volumen akkumuliert, den unmittelbar folgenden Balken und das konfigurierte Vorschaufenster auf gleichgerichtete Bewegungen untersucht, Ausbrüche um die Musterextreme herum mit BreakoutPips erkennt und die Bestätigung nach BreakoutConfirmBars auswertet. Es werden sowohl reine Zählungen als auch volumengewichtete Zähler beibehalten, sodass die Ergebnisse entweder nach einfacher Häufigkeit oder nach Marktbeteiligung interpretiert werden können.
void UpdateStatsOnPattern(PatternStats &s, int shift, int direction, int lookAhead, double breakoutPoints) { s.total++; double vol = (double)iVolume(_Symbol, TF, shift); if(vol <= 0) vol = 1.0; s.volTotal += vol; // next-bar direction if(shift - 1 >= 0) { double nO = iOpen(_Symbol, TF, shift - 1), nC = iClose(_Symbol, TF, shift - 1); int nDir = (nC > nO) ? +1 : (nC < nO) ? -1 : 0; if(direction == 0) { if(nDir != 0) { s.nextSameDir++; s.weightedNextSameDir += vol; } } else { if(nDir == direction) { s.nextSameDir++; s.weightedNextSameDir += vol; } } } // within lookahead bool anySame = false; double weightedAny = 0.0; int totalBars = iBars(_Symbol, TF); for(int k=1; k<=lookAhead; k++) { int idx = shift - k; if(idx < 1 || idx > totalBars - 1) break; double o = iOpen(_Symbol, TF, idx), c = iClose(_Symbol, TF, idx); double v = (double)iVolume(_Symbol, TF, idx); if(v <= 0) v = 1.0; int d = (c > o) ? +1 : (c < o) ? -1 : 0; if(direction == 0) { if(d != 0) { anySame = true; weightedAny += v; break; } } else { if(d == direction) { anySame = true; weightedAny += v; break; } } } if(anySame) { s.withinLookAheadSame++; s.weightedWithin += (weightedAny > 0.0 ? weightedAny : vol); } // breakout detection and confirmation double highP = iHigh(_Symbol, TF, shift), lowP = iLow(_Symbol, TF, shift); bool breakout = false; if(shift - 1 >= 0) { double nextHigh = iHigh(_Symbol, TF, shift - 1), nextLow = iLow(_Symbol, TF, shift - 1); if(direction == +1 && nextHigh >= highP + breakoutPoints) breakout = true; else if(direction == -1 && nextLow <= lowP - breakoutPoints) breakout = true; else if(direction == 0 && (nextHigh >= highP + breakoutPoints || nextLow <= lowP - breakoutPoints)) breakout = true; } if(breakout) { s.breakouts++; s.weightedBreakouts += vol; int confirmShift = shift - 1 - BreakoutConfirmBars; if(confirmShift >= 0 && confirmShift <= totalBars - 1) { double cO = iOpen(_Symbol, TF, confirmShift), cC = iClose(_Symbol, TF, confirmShift); double cVol = (double)iVolume(_Symbol, TF, confirmShift); if(cVol <= 0) cVol = 1.0; int cDir = (cC > cO) ? +1 : (cC < cO) ? -1 : 0; bool confirmed = false; if(direction == +1 && cC >= highP + breakoutPoints) confirmed = true; else if(direction == -1 && cC <= lowP - breakoutPoints) confirmed = true; else if(direction == 0 && (cC >= highP + breakoutPoints || cC <= lowP - breakoutPoints)) confirmed = true; if(confirmed && (direction == 0 ? cDir != 0 : cDir == direction)) { s.breakoutSuccess++; s.weightedBreakoutSuccess += cVol; } } } }
Prozentsatzberechnung und CSV-Export
Die Umwandlung der bloßen Zählungen oder gewichteten Summen in lesbare Prozentsätze wird in ComputePct() zentralisiert; volumengewichtete Prozentsätze werden zurückgegeben, wenn UseVolumeWeighting aktiviert ist; andernfalls werden einfache Häufigkeitsprozentsätze verwendet. Wenn ExportCSV true ist, schreibt SaveToCSV() eine tabulatorgetrennte Datei mit Kopfzeilen und einer Zeile pro Muster, sodass die Offline-Analyse in Python, R oder Tabellenkalkulationsprogrammen einfach und reproduzierbar ist.
double ComputePct(double weightedValue, PatternStats &s, int simpleCount, int total) { if(UseVolumeWeighting) { if(s.volTotal <= 0.0) return 0.0; return 100.0 * weightedValue / s.volTotal; } else { if(total <= 0) return 0.0; return 100.0 * simpleCount / total; } } void SaveToCSV() { int handle = FileOpen(CSVFileName, FILE_WRITE|FILE_CSV|FILE_ANSI, '\t'); if(handle == INVALID_HANDLE) { Print("Failed to open CSV: ", CSVFileName); return; } FileWrite(handle, "Pattern","Total","Next%","Within%","Breakouts","Success%","VolTotal","W_Next%","W_Within%","W_Breakouts%","W_Success%"); WriteStatsLine(handle, pinbar); WriteStatsLine(handle, engulfing); WriteStatsLine(handle, dojiStats); FileClose(handle); } void WriteStatsLine(int handle, PatternStats &s) { double nextPct = ComputePct(s.weightedNextSameDir, s, s.nextSameDir, s.total); double withinPct = ComputePct(s.weightedWithin, s, s.withinLookAheadSame, s.total); double successPct = s.breakouts > 0 ? 100.0 * s.breakoutSuccess / s.breakouts : 0.0; double wBreakoutsPct = s.volTotal > 0.0 ? 100.0 * s.weightedBreakouts / s.volTotal : 0.0; double wSuccessPct = s.weightedBreakouts > 0.0 ? 100.0 * s.weightedBreakoutSuccess / s.weightedBreakouts : 0.0; FileWrite(handle, s.name, s.total, DoubleToString(nextPct,1), DoubleToString(withinPct,1), s.breakouts, DoubleToString(successPct,1), DoubleToString(s.volTotal,0), DoubleToString(nextPct,1), DoubleToString(withinPct,1), DoubleToString(wBreakoutsPct,1), DoubleToString(wSuccessPct,1)); }
Chartdarstellung, Markierungen und Stapelung
Visuelle Indikatoren werden mit der Funktion DrawPatternOnChartWithStacking() gezeichnet, die kompakte Textpfeile oder Symbole an der Musterleiste platziert und sie vertikal stapelt, um Kollisionen zu vermeiden. Die Stapelung verwendet ein Array markerCounts[] pro Balken und den Parameter MarkerStackWindow zur Steuerung der Stapelung. Die Marker sind farblich gekennzeichnet und absichtlich unauffällig, damit sie einen unmittelbaren Kontext liefern, ohne das Kursgeschehen zu verdecken.
void DrawPatternOnChartWithStacking(int shift, string patternName, int direction, int fontSize, int offsetPips, double factor) { string name = "Pattern_" + patternName + "_" + IntegerToString(shift); if(ObjectFind(0, name) != -1) ObjectDelete(0, name); datetime t = iTime(_Symbol, TF, shift); double highP = iHigh(_Symbol, TF, shift), lowP = iLow(_Symbol, TF, shift); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double offset = offsetPips * point * factor; double priceBase = (highP + lowP) / 2.0; if(direction > 0) priceBase = lowP - offset; else if(direction < 0) priceBase = highP + offset; int idx = shift; if(idx >= ArraySize(markerCounts)) ArrayResize(markerCounts, idx + 5); int stack = markerCounts[idx]++; double stackOffset = (stack / MarkerStackWindow) * offset * 1.2; if(direction > 0) priceBase -= stackOffset; else priceBase += stackOffset; string arrow = (direction>0 ? "▲" : direction<0 ? "▼" : "◆"); string text = arrow + " " + StringSubstr(patternName, 0, 3); ObjectCreate(0, name, OBJ_TEXT, 0, t, priceBase); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, 0); ObjectSetInteger(0, name, OBJPROP_BACK, 1); ObjectSetInteger(0, name, OBJPROP_COLOR, (direction>0 ? clrLime : direction<0 ? clrRed : clrLightBlue)); }
Dashboard- und Zusammenfassungsbeschriftungen
Ein kompaktes On-Chart-Dashboard, das von DrawDashboard() erzeugt wird, präsentiert kanonische Spalten wie Pattern, Total, Next%, Within%, Breakouts und Success%, während DrawSummaryHeaderAndLabel() eine Zusammenfassung oben links zur schnellen Überprüfung erstellt. Durch die Verwendung konsistenter Objektnamenspräfixe können diese Oberflächenelemente vom EA zuverlässig bereinigt werden, wenn sie nicht mehr benötigt werden.
void DrawDashboard() { string rectName = "Dash_Background"; if(ObjectFind(0, rectName) != -1) ObjectDelete(0, rectName); ObjectCreate(0, rectName, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, rectName, OBJPROP_CORNER, 0); ObjectSetInteger(0, rectName, OBJPROP_XDISTANCE, 8); ObjectSetInteger(0, rectName, OBJPROP_YDISTANCE, 70); ObjectSetInteger(0, rectName, OBJPROP_XSIZE, 660); ObjectSetInteger(0, rectName, OBJPROP_YSIZE, 80); ObjectSetInteger(0, rectName, OBJPROP_BACK, 1); ObjectSetInteger(0, rectName, OBJPROP_COLOR, clrDarkSlateGray); // header and rows omitted for brevity, see DrawDashboard in main code for full cell logic } void DrawSummaryHeaderAndLabel() { string header = "SummaryHeader"; if(ObjectFind(0, header) != -1) ObjectDelete(0, header); ObjectCreate(0, header, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, header, OBJPROP_CORNER, 0); ObjectSetInteger(0, header, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, header, OBJPROP_YDISTANCE, 12); ObjectSetInteger(0, header, OBJPROP_FONTSIZE, 11); ObjectSetString(0, header, OBJPROP_TEXT, "Candlestick Pattern Stats Summary"); ObjectSetInteger(0, header, OBJPROP_COLOR, clrBlue); string sum = "SummaryLabel"; if(ObjectFind(0, sum) != -1) ObjectDelete(0, sum); ObjectCreate(0, sum, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, sum, OBJPROP_CORNER, 0); ObjectSetInteger(0, sum, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, sum, OBJPROP_YDISTANCE, 30); ObjectSetInteger(0, sum, OBJPROP_FONTSIZE, 9); ObjectSetInteger(0, sum, OBJPROP_BACK, 1); ObjectSetString(0, sum, OBJPROP_TEXT, FormatStatsForLabel(pinbar) + "\n" + FormatStatsForLabel(engulfing) + "\n" + FormatStatsForLabel(dojiStats)); }
Steuertasten und dynamische Beschriftungen
Schaltflächen für Steuerelemente mit automatischer Größe werden mit CreateAutoSizedButton() erstellt und mit UpdateButtonLabel() live aktualisiert. Jede Schaltfläche zeigt einen kurzen Identifikator, einen EIN/AUS-Status sowie die aktuellen Metriken Next% und Succ%. EstimateButtonWidth() verhindert das Abschneiden, und die Farben der Schaltflächen ändern sich, um den aktivierten oder deaktivierten Zustand widerzuspiegeln, sodass die Nutzer interaktiv einstellen können, welche Muster zur Statistik beitragen.
void CreateAutoSizedButton(string name, int x, int y, string text) { if(ObjectFind(0, name) != -1) ObjectDelete(0, name); ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_CORNER, 1); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetString(0, name, OBJPROP_FONT, ButtonFont); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, ButtonFontSize); ObjectSetString(0, name, OBJPROP_TEXT, text); int w = EstimateButtonWidth(text, ButtonFontSize); int h = ButtonFontSize + ButtonPaddingY * 2; ObjectSetInteger(0, name, OBJPROP_XSIZE, w); ObjectSetInteger(0, name, OBJPROP_YSIZE, h); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite); } void UpdateButtonLabel(string name, PatternStats &s, bool enabled) { if(ObjectFind(0, name) == -1) return; string onoff = enabled ? "ON" : "OFF"; double nextPct = ComputePct(s.weightedNextSameDir, s, s.nextSameDir, s.total); double successPct = s.breakouts > 0 ? 100.0 * s.breakoutSuccess / s.breakouts : 0.0; string shortName = (StringCompare(s.name, "Pinbar") == 0) ? "Pin" : (StringCompare(s.name,"Engulfing")==0) ? "Eng" : "Doj"; string txt = shortName + ": " + onoff + " Next% " + DoubleToString(nextPct,1) + " Succ% " + DoubleToString(successPct,1); ObjectSetString(0, name, OBJPROP_TEXT, txt); ObjectSetInteger(0, name, OBJPROP_XSIZE, EstimateButtonWidth(txt, ButtonFontSize)); if(enabled) { ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite); ObjectSetInteger(0, name, OBJPROP_COLOR, clrBlack); } else { ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrLightGray); ObjectSetInteger(0, name, OBJPROP_COLOR, clrDimGray); } } int EstimateButtonWidth(const string txt, const int fontSize) { int len = StringLen(txt); double charW = fontSize * 0.75; int base = (int)MathCeil(len * charW) + ButtonPaddingX * 2 + 10; if(base < 100) base = 100; return base; }
Hausverwaltung und Objektmanagement
Jedes vom EA erstellte Chartobjekt trägt ein deterministisches Präfix wie OBJ_PREFIX_PATTERN, OBJ_PREFIX_DASH oder OBJ_PREFIX_BTN. Die zugehörigen Löschfunktionen entfernen nur Objekte, die diesen Präfixen entsprechen, wodurch das Risiko des versehentlichen Löschens von nicht verwandten Nutzerobjekten minimiert wird und der Fußabdruck des EA berechenbar und nicht invasiv bleibt.
void DeleteAllOurObjects() { int total = ObjectsTotal(0); for(int i = total - 1; i >= 0; i--) { string nm = ObjectName(0, i); if(StringFind(nm, "Pattern_") == 0 || StringFind(nm, "Dash_") == 0 || StringFind(nm, "Btn_") == 0 || StringCompare(nm, "SummaryLabel") == 0 || StringCompare(nm, "SummaryHeader") == 0) ObjectDelete(0, nm); } }
Tests
In diesem Abschnitt evaluieren wir den EA anhand des Step-Index und des Boom-300-Index, die ausgewählt wurden, um die Robustheit des Tools bei synthetischen Instrumenten mit unterschiedlichen Volatilitätsprofilen zu testen.
- Step Index

Das obige Chart veranschaulicht die Leistung des Tools beim Stufenindex. Wenn der EA an das Chart angehängt ist, erstellt er ein kompaktes Dashboard in der oberen linken Ecke, das die Statistiken der Muster zusammenfasst, sowie eine Reihe von Schaltflächen in der unteren linken Ecke, die die Visualisierung im Chart steuern. Wenn Sie auf eine Schaltfläche klicken, wird der entsprechende Musterstrom deaktiviert oder aktiviert, und die Kennzeichnungen der Kerzen werden entfernt oder wiederhergestellt, sodass Sie durch das Chart schreiten und die Mustererkennung visuell überprüfen können. Umschaltbare Steuerelemente sind nützlich, wenn Sie sich auf einen einzigen Mustertyp konzentrieren möchten.
Im gezeigten Beispiel meldet das Dashboard Erfolgsquoten von 40,2 % für Pinbar und 31,7 % für Engulfing. Der Doji zeigt 65,1 % an; wir behandeln den Doji in dieser Analyse jedoch als neutrales Signal, da seine offensichtliche Stärke in erster Linie auf die Filterung des Ausbruchs und nicht auf eine Richtungsbestimmung zurückzuführen ist. Daher ist das Doji-Ergebnis am besten als Hinweis auf eine Tendenz zur Auflösung der Fortsetzung bzw. des Ausbruchs bei diesem Instrument zu interpretieren und nicht als einfaches Aufwärts- oder Abwärtssignal.
Die Ergebnisse werden zur unabhängigen Überprüfung auch in CSV exportiert. Um die Dateien zu erzeugen, öffnen Sie die EA-Eingaben und setzen ExportCSV = true; der Name der Zusammenfassungsdatei kann über CSVFileName geändert werden. Der EA schreibt die CSV-Zusammenfassung zur Offline-Analyse in den Datei-Ordner Ihres Terminals.
input bool ExportCSV = true; input string CSVFileName = "PatternStats.csv";
- Boom 300 Index

Beim Boom 300 Index liegen die gemessenen Erfolgsquoten bei 40,0% für Engulfing, 42,1% für Pinbar und 78,6% für Doji. Diese Zahlen zeigen, dass Doji-Ereignisse bei diesem Instrument nach den Regeln des EA viel wahrscheinlicher von Fortsetzungs- oder Ausbruchsbewegungen gefolgt werden, während Engulfing und Pinbar eine bescheidenere, direktionale Performance aufweisen.
Vergleicht man den Boom 300 mit dem Step Index, so ist die Pinbar-Performance bei beiden Instrumenten praktisch gleich, was auf eine gewisse Robustheit dieses Musters hindeutet, während Engulfing beim Boom 300 deutlich besser abschneidet als beim Step Index; dies impliziert, dass Engulfing-Signale beim Boom 300 innerhalb des EA-Parametersatzes und des Stichprobenzeitraums eine positivere Folgetätigkeit ergeben.
Schlussfolgerung
Dieser Artikel führte durch den Aufbau eines MQL5 Expert Advisors, der die Wahrscheinlichkeiten von Kerzen-Mustern über mehrere Instrumente hinweg misst. Er zeigt, wie man Pinbar-, Engulfing- und Doji-Formationen erkennt, Statistiken zu den einzelnen Mustern sammelt und Erfolgsquoten meldet, damit Sie beurteilen können, welche Muster einen positiven Follow-Through für jedes Paar erzeugen. Ich stelle den EA als experimentelles Analysewerkzeug zur Visualisierung von Preis-Aktions-Mustern und deren empirischen Wahrscheinlichkeiten vor. Es ist als Recherche- und Überprüfungshilfe gedacht und nicht als schlüsselfertiger Handelsroboter, und die Nutzer sollten ihre eigenen Eingabepräferenzen und Validierungsroutinen anwenden, um ihre Zeitrahmen und Instrumente zu berücksichtigen.
Wenn sich dieser Ansatz bei ausreichend großen Stichproben als robust erweist, hilft er dabei, realistische Erwartungen für das typische Verhalten eines bestimmten Musters im Live-Handel festzulegen, und unterstützt außerdem reproduzierbare Tests über die CSV-Audit-Dateien. Ich plane, das Tool in künftigen Episoden weiter zu verbessern, und ich freue mich über Verbesserungsvorschläge und Anfragen für zusätzliche Analysefunktionen.
Lesen Sie meine anderen Artikel.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19738
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Automatisieren von Handelsstrategien in MQL5 (Teil 35): Erstellung eines Blockausbruch-Handelssystems
Automatisieren von Handelsstrategien in MQL5 (Teil 34): Trendline Breakout System mit R-Squared Goodness of Fit
Entwicklung des Price Action Analysis Toolkit (Teil 45): Erstellen eines dynamischen Level-Analyse-Panels in MQL5
Aufbau eines Handelssystems (Teil 5): Verwaltung von Gewinnen durch strukturierte Handelsausstiege
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.