
Datenwissenschaft und ML (Teil 37): Mit Kerzenmustern und AI den Markt schlagen
Inhalt
- Einführung
- Grundlagen einer Kerze
- Kerzenmuster
- Indikator zur Erkennung von Kerzenmustern
- Sammeln von Kerzenmustern für maschinelles Lernen
- Training eines KI-Modells zur Erstellung von Vorhersagen auf der Grundlage von Kerzenmustern
- Finalisierung in einem Handelsroboter
- Schlussfolgerung
Einführung
Die erste Strategie, die ich jemals beim Handel verwendet habe, basierte auf Kerzen. Ich würde es jetzt nicht als Strategie bezeichnen, aber mein erstes Mal, als ich einen Handel eröffnete, war auf einige Kerzenmuster zurückzuführen, die ich durch die Lektüre eines Buches namens „Candlestick Trading Bible“ von Honma Munehisa gelernt hatte, das mir von einem Freund empfohlen wurde.
Kerzenmuster in Finanzcharts werden von Händlern verwendet, um mögliche Preisbewegungen auf der Grundlage vergangener Muster zu bestimmen. Sie werden durch Auf- und Abwärtsbewegungen des Preises erzeugt, die zwar zufällig erscheinen, aber von Händlern verwendet werden, um die kurzfristige Richtung des Preises vorherzusagen.
Dieses Konzept wurde in den 1700er Jahren von einem Händler namens Honma Munehisa entwickelt, der als der erfolgreichste Händler der Geschichte gilt. Zu seiner Zeit war er als Gott der Märkte bekannt, und seine Entdeckungen brachten ihm heute mehr als 10 Milliarden Dollar ein.
Munehisa entdeckte, dass die Kosten für Angebot und Nachfrage zwar den Preis für Reis bestimmen, die Märkte aber auch von menschlichen Emotionen beeinflusst werden.
Diese menschlichen Emotionen können in einer Kerze widergespiegelt werden, wo sich die Preisbewegung in verschiedenen Farben darstellt. Oft wird eine schwarze Kerze für eine Abwärtsbewegung verwendet und eine weiße Kerze repräsentiert oft eine Aufwärtsbewegung. Diese Farben spielen heutzutage keine Rolle, weil sie in jeder Handelsplattform geändert werden können.
Die Grundlagen einer Kerze
Die Schatten oder Dochte der Kerzen zeigen die Tageshöchst- und -tiefstkurse und deren Vergleich mit den Eröffnungs- und Schlusskursen. Die Form der Kerze variiert je nach dem Verhältnis zwischen den OHLC-Kursen (Open, High, Low und Close) der Kerze. Es gibt eine ganze Reihe von Kerzenmustern, die Munehisa seinerzeit eingeführt hat, und einige, die in letzter Zeit von Händlern entwickelt wurden.
Basierend auf diesen Kerzenmustern werden wir diese Kerzenmuster identifizieren, sammeln und auf maschinelle Lernmodelle anwenden und beobachten, wie diese Kerzenmuster unseren KI-basierten Handelsmodellen einen Mehrwert verleihen und uns dabei helfen können, die Finanzmärkte zu schlagen.
Kerzenmuster
Da es viele Kerzenmuster gibt, werden wir nur 10 Muster besprechen. Der Hauptgrund für diese 10 ist, dass sie einfach zu verstehen sind und sich nur auf einen einzigen Balken beziehen, d.h. sie können auf dem aktuellen Balken erkannt werden.
Wir verwerfen alle Kerzenmuster, bei denen zur Erkennung mehrere Kerzen verarbeitet werden müssen.
Das Kodierungsformat für die CTALib-Klasse wurde von TALib (Technical Analysis Library) inspiriert, einer C#-, C++- und Python-basierten Bibliothek für die technische Analyse, die mehrere Funktionen zur Erkennung von Kerzenmustern enthält.
Eine weiße Kerze
Dies ist eine Aufwärtskerze, bei der der Schlusskurs höher ist als der Eröffnungskurs. Diese einfache Aufwärtskerze zeigt einen Aufwärtsimpuls an, sie bietet keine Vorhersage, sondern ist nur ein Hinweis auf die Richtung der Kerze.
Wir können es wie folgt kodieren.
bool CTALib::CDLWHITECANDLE(double open, double close) { return (close > open); }
Eine schwarze Kerze
Im Gegensatz zur weißen Kerze handelt es sich hier um eine Abwärtskerze, bei der der Eröffnungskurs höher ist als der Schlusskurs. Diese einfache Abwärtskerze deutet auf ein Abwärtsmomentum hin.
Ähnlich wie die weiße Kerze bietet sie keine Vorhersage, sondern ist nur ein Hinweis auf die Richtung der Kerze.
bool CTALib::CDLBLACKCANDLE(double open,double close) { return (open > close); }
Eine Doji-Kerze
Eine Doji-Kerze ist eine Kerze, bei der sowohl das Eröffnungs- als auch das Schlusskursniveau praktisch gleich sind. Sie sieht aus wie ein Kreuz, manchmal ein umgekehrtes Kreuz, oder ein Pluszeichen. Dieses Kerzenmuster ist selten, da es oft innerhalb von mehreren Kerzen auftritt. Es handelt sich in der Regel um ein Trendumkehrsignal, das aber auch Unentschlossenheit über die zukünftigen Preise am Markt signalisieren kann.
Es gibt einige Variationen, wie in der Abbildung unten gezeigt.
Die Kodierung einer Funktion zur Erkennung einer Doji-Kerzen kann schwierig sein, einfach aufgrund der Definition einer Doji-Kerzen. Damit eine Kerze als Doji betrachtet werden kann, müssen sowohl der Eröffnungs- als auch der Schlusskurs gleich sein. Angesichts des Volumens und der Volatilität auf dem heutigen Markt ist es äußerst selten, dass der Eröffnungs- und der Schlusskurs gleich sind. Selbst wenn der Kurs zum Beispiel zu nahe beieinander liegt - Eröffnungskurs = 1,0000 und Schlusskurs = 1,0001 - sind diese beiden für einen Computer nicht gleich.
Wenn die Differenz zwischen Eröffnungs- und Schlusskurs innerhalb dieses Wertes liegt, können wir davon ausgehen, dass die Kurse zu nahe beieinander liegen und daraus schließen, dass es sich um ein Doji-Kerzenmuster handelt.
bool CTALib::CDLDOJI(double open,double close, double torrelance=3) { return (fabs(open-close)<torrelance*_Point); //A torrelance in market points }
Dragonfly Doji
Das Kerzenmuster eines Dragonfly-Doji signalisiert eine mögliche Kursumkehr bei einem Handelsinstruments. Wenn ein Dragonfly-Doji in einem Abwärtstrend auftaucht, ist das ein guter Hinweis darauf, dass die Kurse bald steigen oder der Abwärtstrend zu Ende sein könnte.
Da es sich um eine Doji-Kerze handelt, deren unterer Schatten länger ist als der obere, können wir im Code zunächst sicherstellen, dass es sich um eine Doji-Kerze handelt, und müssen dann explizit sicherstellen, dass der untere Schatten doppelt so lang ist wie der obere Schatten.
bool CTALib::CDLDRAGONFLYDOJI(double open, double high, double low, double close, double body_torrelance = 3, double shadow_ratio = 2.0) { double body_size = MathAbs(open - close); double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); //--- Body is very small (like a Doji) if (CDLDOJI(open, close, body_torrelance)) { //--- Lower shadow is significantly longer than upper shadow if (lower_shadow > upper_shadow * shadow_ratio) return true; } return false; }
Gravestone doji
Dies ist eine Doji-Kerze mit einem langen oberen Schatten (Docht). Es handelt sich um die Strategie einer Abwärts-Umkehr, die auf eine Änderung der Marktrichtung hinweist.
Wenn diese Kerze während eines Aufwärtstrends erscheint, deutet dies darauf hin, dass der aktuelle Trend zu Ende sein könnte und ein Abwärtstrend bevorsteht.
Da es sich um eine Doji-Kerze handelt, deren oberer Schatten länger ist als der untere, können wir im Code zunächst sicherstellen, dass es sich um eine Doji-Kerze handelt, und müssen dann explizit sicherstellen, dass der obere Schatten zweimal länger ist als der untere.
bool CTALib::CDLGRAVESTONEDOJI(double open, double high, double low, double close, double body_torrelance = 3, double shadow_ratio = 2.0) { double body_size = MathAbs(open - close); double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); //--- Body is very small (like a Doji) if (CDLDOJI(open, close, body_torrelance)) { //--- Lower shadow is significantly longer than upper shadow if (upper_shadow > lower_shadow * shadow_ratio) return true; } return false; }
Hammer
Dies ist eine Kerze mit einem kleinen Körper an der Spitze der Kerze und einem langen unteren Schatten. Es handelt sich um ein Aufwärts-Umkehrsignal, das, wenn es bei einem Abwärtstrend auftritt, bedeutet, dass eine potenzielle Aufwärtsbewegung bevorsteht.
Kerzenmuster sind abstrakt und verwirrend. Manchmal kann ein Muster variieren, je nachdem, wie man es betrachtet und wonach man zu einem bestimmten Zeitpunkt sucht. Das Muster eines Hammers kann mit einem Dragonfly-Doji verwechselt werden, denn was diese beiden unterscheidet, ist nur die Größe ihres Körpers, aber sie haben die gleichen Merkmale.
Dies ist eines der Probleme, auf die wir bei der Arbeit mit Kerzenmustern häufig stoßen.
Dieses Problem kann durch die Festlegung expliziter Regeln und Schwellenwerte gelöst werden, die je nach Instrument (Symbol) und anderen Anforderungen angepasst werden können.
bool CTALib::CDLHAMMER(double open, double high, double low, double close, double min_body_percentage = 0.2, // To avoid being a doji double lower_shadow_ratio = 2.0, // Lower shadow at least 2x the body double upper_shadow_max_ratio = 0.3) // Upper shadow must be small { double body_size = MathAbs(open - close); double total_range = high - low + DBL_EPSILON; double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); double body_percentage = body_size / total_range; return ( body_percentage >= min_body_percentage && lower_shadow >= lower_shadow_ratio * body_size && upper_shadow <= upper_shadow_max_ratio * body_size ); }
Da sowohl der Dragonfly-Doji als auch der Hammer einen längeren unteren Schatten, einen kleinen Körper und einen kurzen oberen Schatten haben, mussten wir den min_body_percentage einführen, um zu prüfen, wie klein ein Körper im Verhältnis zu seinem Gesamtbereich (High-Low) sein muss, damit eine Kerze ein Hammer ist, während wir auch prüfen, ob ihr unterer Schatten standardmäßig doppelt so lang wie ihr oberer Schatten ist.
Inverted Hammer
Er ähnelt dem Hammer, hat aber unten einen kleinen Körper, einen langen oberen Schatten und einen kleinen unteren Schatten.
Dieses Muster ist in Abwärtstrends zu finden, da es auf eine bevorstehende Aufwärtsbewegung des Marktes hinweist.
Wir können ihn ähnlich wie den Hammer kodieren, nur mit kleinen Änderungen bei den Schatten.
bool CTALib::CDLINVERTEDHAMMER(double open, double high, double low, double close, double min_body_percentage = 0.2, // Avoid doji double upper_shadow_ratio = 2.0, // Upper shadow must be long double lower_shadow_max_ratio = 0.3) // Lower shadow should be small { double body_size = MathAbs(open - close); double total_range = high - low + DBL_EPSILON; double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); double body_percentage = body_size / total_range; return ( body_percentage >= min_body_percentage && upper_shadow >= upper_shadow_ratio * body_size && lower_shadow <= lower_shadow_max_ratio * body_size ); }
Spinning Top
Dies ist eine Kerze mit einem kleinen Körper in der Mitte und langen Schatten auf beiden Seiten.
Wenn diese Kerze erscheint, bedeutet dies Unentschlossenheit auf dem Markt, was oft die Fortsetzung des aktuellen Trends bedeutet, da weder Käufer noch Verkäufer die Oberhand haben.
Da diese Kerze einer Doji-Kerze ähnelt (sie hat nur einen größeren Körper), müssen wir explizit sicherstellen, dass der Körper nicht so klein ist wie bei einem Doji und dass er sich in der Mitte der langen Schatten im Verhältnis zum Körper befindet.
bool CTALib::CDLSPINNINGTOP(double open, double high, double low, double close, double body_percentage_threshold = 0.3, double shadow_ratio = 2.0, double shadow_symmetry_tolerance = 0.3) { double body_size = MathAbs(open - close); double total_range = high - low + DBL_EPSILON; double upper_shadow = upperShadowCalc(open, close, high); double lower_shadow = lowerShadowCalc(open, close, low); double body_percentage = body_size / total_range; //--- Calculate shadow symmetry ratio double shadow_diff = MathAbs(upper_shadow - lower_shadow); double shadow_sum = upper_shadow + lower_shadow + DBL_EPSILON; double symmetry_ratio = shadow_diff / shadow_sum; // Closer to 0 = more balanced return ( body_percentage < body_percentage_threshold && // Body is small compared to candle size upper_shadow > body_size * shadow_ratio && // Both shadows are significantly larger than the body lower_shadow > body_size * shadow_ratio && symmetry_ratio <= shadow_symmetry_tolerance //Shadows are roughly equal (symmetrical) ); }
Aufwärts-Marubozu
Der Name „Marubozu“ stammt von dem japanischen Wort für „eng geschnitten“, was auf eine Kerze ohne Schatten hinweist.
Ein Marubozu ist dann eine Aufwärtskerze mit einem kleinen oder gar keinem unteren und oberen Schatten (Docht), das ist ein starkes Aufwärtssignal, das auf ein Momentum hinweist.
Wir können eine Toleranz in Punkten hinzufügen, um zu prüfen, ob die Eröffnungs- und Schlusskurse sehr nahe an ihren Höchst- und Tiefstkursen liegen.
bool CTALib::CDLBULLISHMARUBOZU(double open, double high, double low, double close, double tolerance = 2) { return (MathAbs(open - low) <= (tolerance*_Point) && MathAbs(close - high) <= (tolerance*_Point) && close > open); }
Abwärts-Marubozu
Ein Abwärts-Marubozu ist eine Abwärtskerze mit kleinen oder gar keinen unteren und oberen Schatten (Dochten). Es ist ein starkes Abwärtssignal, das auf ein Momentum hinweist.
Ähnlich wie bei der Aufwärts-Marubozu haben wir eine Toleranz in Punkten, um zu prüfen, ob die Eröffnungs- und Schlusskurse sehr nahe an ihren Höchst- und Tiefstkursen liegen.
bool CTALib::CDLBEARISHMARUBOZU(double open, double high, double low, double close, double tolerance = 2) { return (MathAbs(open - high) <= (tolerance*_Point) && MathAbs(close - low) <= (tolerance*_Point) && close < open); }
Im Moment betrachten wir nur die Erkennung von Kerzenmustern und deren Signale auf der Grundlage ihres Aussehens, aber die richtige Art und Weise, die Signale aus einem Kerzen zu extrahieren, muss meinen Quellen zufolge auch die Trenderkennung beinhalten, z. B. muss ein Hammer in einem Abwärtstrend erscheinen, um als Aufwärtssignal zu gelten.
Der Trend ist ein entscheidender Teil der Gleichung, den Sie berücksichtigen sollten, wenn Sie dieses Projekt weiterführen wollen.
Indikator zur Erkennung von Kerzenmustern
Lassen Sie uns die Kerzenmuster mit Hilfe des Codes, den wir für ihre Ableitung verwendet haben, visualisieren. Ganz einfach, weil ich diese Muster oft abstrakt und verwirrend finde und weil wir beabsichtigen, diese Daten für maschinelles Lernen zu verwenden, wobei wir wissen, dass die Qualität der Daten am wichtigsten ist. Durch die Visualisierung dieser Muster stellen wir sicher, dass Sie darüber informiert sind, wie diese Muster berechnet wurden und welches Ergebnis sie zeigen.
Zögern Sie nicht, einige Parameter zu verändern und den Code nach Ihrem Ermessen zu modifizieren.
Lassen Sie uns sicherstellen, dass zumindest der Code, den wir gerade geschrieben haben, diese Muster erkennen kann, die wir auch als Menschen auf dem Markt erkennen können.
Dieser auf Kerzen basierende Indikator wird 5 Puffer und eine Darstellung im Hauptchart haben.
Dateiname: Candlestick Identifier.mq5
#property indicator_chart_window #property indicator_buffers 5 #property indicator_plots 1 #property indicator_type1 DRAW_COLOR_CANDLES #property indicator_color1 clrDodgerBlue, clrOrange, clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 double OpenBuff[]; double HighBuff[]; double LowBuff[]; double CloseBuff[]; double ColorBuff[]; #include <ta-lib.mqh> //!important for candlestick patterns
Da es sich bei der Bibliothek ta-lib.mqh um eine statische Klasse handelt, müssen ihre Klassen nicht initialisiert werden. Wir können die Funktionen zur Erkennung von Kerzenmustern sofort innerhalb der Funktion OnCalculate aufrufen.
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- if (rates_total<1) return rates_total; for(int i = prev_calculated; i < rates_total; i++) { OpenBuff[i] = open[i]; HighBuff[i] = high[i]; LowBuff[i] = low[i]; CloseBuff[i] = close[i]; //--- if (close[i]>open[i]) ColorBuff[i] = 1.0; else ColorBuff[i] = 0.0; //--- double padding = MathAbs(high[i] - low[i]) * 0.2; // 20% padding if (CTALib::CDLDOJI(open[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding, "Doji", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLDRAGONFLYDOJI(open[i], high[i], low[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"DragonFly Doji", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLGRAVESTONEDOJI(open[i], high[i], low[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"GraveStone Doji", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLHAMMER(open[i], high[i], low[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Hammer", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLINVERTEDHAMMER(open[i], high[i], low[i], close[i])) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Inverted Hammer", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLSPINNINGTOP(open[i], high[i], low[i], close[i], 0.3, 2.0)) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Spinning Top", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLBULLISHMARUBOZU(open[i], high[i], low[i], close[i], 2)) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Bullish Marubozu", clrBlack, 90.0); ColorBuff[i] = 2.0; } if (CTALib::CDLBEARISHMARUBOZU(open[i], high[i], low[i], close[i], 2)) { TextCreate(string(i)+(string)time[i], time[i]-PeriodSeconds(), high[i]+padding,"Bearish Marubozu", clrBlack, 90.0); ColorBuff[i] = 2.0; } } //--- return value of prev_calculated for next call return(rates_total); }
Standardmäßig sind die Aufwärtskerzen orange und die Abwärtskerzen blau. Jede Kerze, die mit den oben in diesem Artikel besprochenen Mustern erkannt wird, wird rot markiert und folgt einem Text, der in einem 90-Grad-Winkel ausgerichtet ist und die Art des Kerzenmusters angibt, zu dem die rot markierte Kerze gehört.
Wie Sie auf dem Bild oben sehen können, leistet unser Kerzen-Erkennungsindikator gute Arbeit bei der Erkennung dieser Kerzenmuster. Wir können uns jetzt auf die Logik verlassen, die wir in unserem Code angewendet haben.
Nun wollen wir diese Muster sammeln und mit Hilfe eines Skripts in einer CSV-Datei speichern, um diese Informationen für das Training von Modellen für maschinelles Lernen zu verwenden.
Sammeln von Kerzenmustern für maschinelles Lernen
Da einige Kerzenmuster auf dem Markt nicht häufig auftreten, insbesondere in höheren Zeitrahmen, in denen es nur sehr wenige Balken in der Geschichte gibt, sammeln wir unsere Daten vom 01. Januar 2005 bis zum 01. Januar 2023.
Dieser Zeitraum von 18 Jahren sollte uns eine Vielzahl von Balken aus dem täglichen Zeitrahmen und damit zahlreiche Muster liefern, die unsere maschinellen Lernmodelle beobachten können.
#include <ta-lib.mqh> //Contains CTALib class for candlestick patterns detection #include <MALE5\Pandas\pandas.mqh> //https://www.mql5.com/de/articles/17030 input datetime start_date = D'2005.01.01'; input datetime end_date = D'2023.01.01'; input string symbol = "XAUUSD"; input ENUM_TIMEFRAMES timeframe = PERIOD_D1; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- vector open, high, low, close; open.CopyRates(symbol, timeframe, COPY_RATES_OPEN, start_date, end_date); high.CopyRates(symbol, timeframe, COPY_RATES_HIGH, start_date, end_date); low.CopyRates(symbol, timeframe, COPY_RATES_LOW, start_date, end_date); close.CopyRates(symbol, timeframe, COPY_RATES_CLOSE, start_date, end_date); CDataFrame df; vector cdl_patterns = {}; cdl_patterns = CTALib::CDLWHITECANDLE(open, close); df.insert("White Candle", cdl_patterns); cdl_patterns = CTALib::CDLBLACKCANDLE(open, close); df.insert("Black Candle", cdl_patterns); cdl_patterns = CTALib::CDLDOJI(open, close); df.insert("Doji Candle", cdl_patterns); cdl_patterns = CTALib::CDLDRAGONFLYDOJI(open, high, low, close); df.insert("Dragonflydoji Candle", cdl_patterns); cdl_patterns = CTALib::CDLGRAVESTONEDOJI(open, high, low, close); df.insert("Gravestonedoji Candle", cdl_patterns); cdl_patterns = CTALib::CDLHAMMER(open, high, low, close); df.insert("Hammer Candle", cdl_patterns); cdl_patterns = CTALib::CDLINVERTEDHAMMER(open, high, low, close); df.insert("Invertedhammer Candle", cdl_patterns); cdl_patterns = CTALib::CDLSPINNINGTOP(open, high, low, close); df.insert("Spinningtop Candle", cdl_patterns); cdl_patterns = CTALib::CDLBULLISHMARUBOZU(open, high, low, close); df.insert("BullishMarubozu Candle", cdl_patterns); cdl_patterns = CTALib::CDLBEARISHMARUBOZU(open, high, low, close); df.insert("BearishMarubozu Candle", cdl_patterns); df.insert("Open", open); df.insert("High", high); df.insert("Low", low); df.insert("Close", close); df.to_csv(StringFormat("CandlestickPatterns.%s.%s.csv",symbol,EnumToString(timeframe)), true); }
Wir sammeln auch die OHLC-Werte (Open, High, Low und Close), um die Zielvariable vorzubereiten und für den Fall, dass etwas passiert und wir sie brauchen.
Training eines KI-Modells für Vorhersagen auf der Grundlage von Kerzenmustern
Da wir nun einen Datensatz haben, können wir diese Daten in ein Python-Skript (Jupyter Notebook) laden.
import pandas as pd symbol = "XAUUSD" df = pd.read_csv(f"/kaggle/input/forex-candlestick-patterns/CandlestickPatterns.{symbol}.PERIOD_D1.csv") df
Ausgabe:
Weiße Kerze | Schwarze Kerze | Doji-Kerze | Dragonfly-Doji Kerze | Gravestone-Doji Kerze | Hammer-Kerze | Inverted-Hammer Kerze | Gravestonedoji-Kerze | Aufwärts-Marubozu | Abwärts-Marubozu | Open | High | Low | Close | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 438.45 | 438.71 | 426.72 | 429.55 |
1 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 429.52 | 430.18 | 423.71 | 427.51 |
2 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 427.50 | 428.77 | 425.10 | 426.58 |
3 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 426.31 | 427.85 | 420.17 | 421.37 |
4 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 421.39 | 425.48 | 416.57 | 419.02 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
Die größte Herausforderung bei der Arbeit mit diesen Kerzen-Daten ist die Festlegung der Zielvariablen.
Bei den meisten Klassifizierungsproblemen im Zusammenhang mit den Finanzmärkten wird die Zielvariable häufig auf der Grundlage der künftigen Preisentwicklung definiert, wobei ein Parameter verwendet wird, den wir häufig als „Lookahead“ bezeichnen.
Dieser Parameter gibt an, wie viele Balken (oder Zeitschritte) wir in den Daten vorausschauen. Wenn zum Beispiel der Lookahead auf 1 gesetzt ist, wird der Schlusskurs des aktuellen Balkens mit dem Schlusskurs des nächsten Balkens verglichen:
Wenn Close[nächster Balken] > Close[aktueller Balken] ist, deutet dies auf eine Aufwärtsbewegung hin, sodass wir eine Zielmarke von 1 zuweisen.
Andernfalls, wenn diese Bedingung nicht erfüllt ist, deutet dies auf eine Abwärtsbewegung hin, sodass wir den Wert 0 vergeben.
Wir können hier dasselbe tun, um sicherzustellen, dass wir unsere Merkmale nutzen, um die Informationen im Voraus zu prognostizieren, aber wie in der obigen Tabelle dargestellt, haben wir viele Nullen in diesen Daten, da wir oft keine besonderen Balken in jeder Zeile erkannt haben, abgesehen von den weißen und schwarzen Kerzen, die keine besonderen Muster sind und wir sie nicht als Kerzenmuster betrachten.
Das bedeutet, dass wir unseren maschinellen Lernmodellen häufiger Nullwerte geben und die Modelle dazu zwingen, die Beziehung zu verstehen und die Zielvariable vorauszusagen, wenn sie keinen Wert hat, was dazu führt, dass sich unsere Modelle zu sehr auf weiße und schwarze Kerzen verlassen, was wir nicht wollen.
Wir können dieses Problem angehen, indem wir alle Zeilen mit Nullwerten für alle Kerzenmuster löschen und das Modell mit reinen Kerzen-Daten trainieren. Dies würde auch erfordern, dass wir die gleiche Situation zur Laufzeit des Modells vermeiden.
Eine andere Möglichkeit, dieses Problem zu lösen, ist die Einführung des Klassensignals Halten, das in allen Zeilen, in denen alle Kerzenmuster 0 (falsch) waren, mit Ausnahme der Spalten „Weiße“ und „Schwarze Kerzen“, durch -1 angezeigt wird, aber dies führt zu einem enormen Problem des Klassenungleichgewichts, das wir im vorherigen Artikel behandelt haben.
Fahren wir fort, die Zielvariable unabhängig davon vorzubereiten.
lookahead = 1 new_df = df.copy() new_df["future_close"] = new_df["Close"].shift(-lookahead) new_df.dropna(inplace=True) # Drop NaNs caused by the shift operation signal = [] for i in range(len(new_df)): # Iterate over rows, not columns if new_df["future_close"].iloc[i] > new_df["Close"].iloc[i]: signal.append(1) else: signal.append(0) new_df["Signal"] = signal
Anschließend werden die Prädiktoren in ein 2D-Array mit dem Namen X aufgeteilt, wobei unerwünschte Merkmale wie die OHLC-Werte, die Spalte, die wir vorhersagen wollen (Ziel), und das Merkmal future_close close, das wir zur Ableitung der Zielspalte verwendet haben, weggelassen werden. Wir weisen dem Array y auch die Zielspalte mit dem Namen Signal zu.
X = new_df.drop(columns=[ "Signal", "Open", "High", "Low", "Close", "future_close" ]) y = new_df["Signal"] # Split data into train and test sets X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, shuffle=False)
Ich habe mich bei diesem Problem für ein Catboost-Modell entschieden, da wir viele kategorische Spalten haben, die theoretisch gut mit dem Catboost-Klassifikator funktionieren sollten.
from catboost import CatBoostClassifier from sklearn.utils.class_weight import compute_class_weight # Automatically calculate class weights classes = np.unique(y) weights = compute_class_weight(class_weight='balanced', classes=classes, y=y) class_weights = dict(zip(classes, weights)) # Define the base model model = CatBoostClassifier( iterations=1000, learning_rate=0.01, depth=5, loss_function='Logloss', class_weights=class_weights, verbose=100 ) model.fit(X_train, y_train) # Training the classifier
Ausgabe:
0: learn: 0.6930586 total: 3.64ms remaining: 3.64s 100: learn: 0.6897625 total: 136ms remaining: 1.21s 200: learn: 0.6888030 total: 269ms remaining: 1.07s 300: learn: 0.6883559 total: 401ms remaining: 931ms 400: learn: 0.6881469 total: 532ms remaining: 795ms 500: learn: 0.6879966 total: 661ms remaining: 658ms 600: learn: 0.6879013 total: 789ms remaining: 524ms 700: learn: 0.6878311 total: 916ms remaining: 391ms 800: learn: 0.6877729 total: 1.04s remaining: 260ms 900: learn: 0.6877273 total: 1.17s remaining: 129ms 999: learn: 0.6876900 total: 1.3s remaining: 0us <catboost.core.CatBoostClassifier at 0x798cc6d08dd0>
Bewerten wir dieses Modell anhand unbekannter Daten (der Testzeitraum).
y_pred = model.predict(X_test)
print("\nClassification Report:\n", classification_report(y_test, y_pred))
Ausgabe:
Classification Report: precision recall f1-score support 0 0.49 0.55 0.52 429 1 0.58 0.52 0.55 511 accuracy 0.53 940 macro avg 0.53 0.53 0.53 940 weighted avg 0.54 0.53 0.53 940
Die Ergebnisse zeigen ein durchschnittliches Modell mit einer Genauigkeit von 0,58 bzw. 0,49 für die Klassen 1 und 0. Während wir uns bei der Vorhersage der Klasse 1 auf dieses Modell verlassen können, was es mit 58%iger Sicherheit tut, können wir uns bei der Vorhersage der Klasse 0 nicht darauf verlassen; in diesem Fall sind wir besser dran, wenn wir raten.
Eine Gesamtgenauigkeit von 53 % von 100 %, die in diesem Handelsbereich realistisch ist, ist besser als das Werfen einer Münze oder zufälliges Raten, das eine 50/50-Gewinnquote garantieren kann.
Schauen wir uns das Diagramm der Merkmalsbedeutung an, um zu sehen, welche Merkmale für dieses Modell am wichtigsten waren.
import matplotlib.pyplot as plt # Get feature importances importances = model.get_feature_importance() feature_names = X_train.columns if hasattr(X_train, 'columns') else [f'feature_{i}' for i in range(X_train.shape[1])] # Create DataFrame for plotting feat_imp_df = pd.DataFrame({ 'Feature': feature_names, 'Importance': importances }).sort_values(by='Importance', ascending=False) # Plot plt.figure(figsize=(7, 3)) plt.barh(feat_imp_df['Feature'], feat_imp_df['Importance']) plt.gca().invert_yaxis() # Highest importance on top plt.title('Feature Importances') plt.xlabel('Importance') plt.ylabel('Feature') plt.tight_layout() plt.show()
Ausgabe:
Das Kerzenmuster des Spinning-Top war das wirkungsvollste Merkmal dieses Modells, gefolgt von der Doji-Kerze, während die Abwärts-Marubozu die geringste Wirkung hatte.
Nach dem, was ich über diese Kerzenmuster gelesen habe, nämlich, dass einige Muster dazu bestimmt sind, den Trend oder was im Markt über einen längeren Zeitraum oder Horizont passieren wird, wie zum Beispiel ein Doji-Kerze, die zu zu oberst einer Aufwärtsbewegung erscheint, könnte bedeuten, dass ein Abwärtstrend dabei ist sich zu bilden.
Wir haben die Zielvariable auf der Grundlage des Lookahead-Wertes von 1 erstellt, d. h. wir verwenden diese Kerzenmuster, um einen statt mehrerer Balken für das Signal zu verwenden.
Sie können also gerne verschiedene Lookahead-Werte größer als 1 untersuchen, um die Auswirkungen dieser Kerzenmuster auf einen längeren Zeitraum oder über verschiedene Horizonte hinweg zu beobachten. Bei meinen bisherigen Untersuchungen habe ich herausgefunden, dass das Modell, das auf dem Lookahead-Wert von 1 trainiert wurde, das genaueste Modell erzeugt.
Wir bleiben vorerst bei dem Vorausschauwert von 1, da ich glaube, dass wir dieses Problem des Vorhersagehorizonts in unserem endgültigen Handelsroboter durch Stop-Loss- und Take-Profit-Werte oder durch das Schließen unserer Handelsgeschäfte nach einer bestimmten Anzahl von Bars lösen können.
Finalisierung in einem Handelsroboter
Nachdem wir nun ein Modell auf der Grundlage von Kerzenmustern trainiert haben, wollen wir es in der tatsächlichen Handelsumgebung testen und sehen, ob Kerzenmuster im Bereich der künstlichen Intelligenz (KI) nützlich sein könnten.
Zunächst müssen wir unser Modell im ONNX-Format speichern, das mit MQL5 und MetaTrader 5 kompatibel ist.
model_onnx = convert_sklearn( model, "catboost", [("input", FloatTensorType([None, X_train.shape[1]]))], target_opset={"": 12, "ai.onnx.ml": 2}, ) # And save. with open(f"CatBoost.CDLPatterns.{symbol}.onnx", "wb") as f: f.write(model_onnx.SerializeToString())
Weitere Informationen zum Speichern dieses Catboost-Modells finden Sie hier.
Unser Expert Advisor (EA) ist ziemlich einfach.
#include <Trade\Trade.mqh> //The trading module #include <Trade\PositionInfo.mqh> //Position handling module #include <ta-lib.mqh> //For candlestick patterns #include <Catboost.mqh> //Has a class for deploying a catboost model CTrade m_trade; CPositionInfo m_position; CCatboostClassifier catboost; input int magic_number = 21042025; input int slippage = 100; input string symbol_ = "XAUUSD"; input ENUM_TIMEFRAMES timeframe_ = PERIOD_D1; input int lookahead = 1; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if (!MQLInfoInteger(MQL_TESTER)) if (!ChartSetSymbolPeriod(0, symbol_, timeframe_)) { printf("%s failed to set symbol %s and timeframe %s, Check these values. Err = %d",__FUNCTION__,symbol_,EnumToString(timeframe_),GetLastError()); return INIT_FAILED; } //--- if (!catboost.Init(StringFormat("CatBoost.CDLPatterns.%s.onnx",symbol_), ONNX_COMMON_FOLDER)) //Initialize the catboost model return INIT_FAILED; //--- m_trade.SetExpertMagicNumber(magic_number); m_trade.SetDeviationInPoints(slippage); m_trade.SetMarginMode(); m_trade.SetTypeFillingBySymbol(Symbol()); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- double open = iOpen(Symbol(), Period(), 1), high = iHigh(Symbol(), Period(), 1), low = iLow(Symbol(), Period(), 1), close = iClose(Symbol(), Period(), 1); vector x = { CTALib::CDLWHITECANDLE(open, close), CTALib::CDLBLACKCANDLE(open, close), CTALib::CDLDOJI(open, close), CTALib::CDLDRAGONFLYDOJI(open, high, low, close), CTALib::CDLGRAVESTONEDOJI(open, high, low, close), CTALib::CDLHAMMER(open, high, low, close), CTALib::CDLINVERTEDHAMMER(open, high, low, close), CTALib::CDLSPINNINGTOP(open, high, low, close), CTALib::CDLBULLISHMARUBOZU(open, high, low, close), CTALib::CDLBEARISHMARUBOZU(open, high, low, close) }; long signal = catboost.predict(x).cls; //Predicted class MqlTick ticks; if (!SymbolInfoTick(Symbol(), ticks)) { printf("Failed to obtain ticks information, Error = %d",GetLastError()); return; } double volume_ = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); if (signal == 1) { if (!PosExists(POSITION_TYPE_BUY) && !PosExists(POSITION_TYPE_SELL)) m_trade.Buy(volume_, Symbol(), ticks.ask,0,0); } if (signal == 0) { if (!PosExists(POSITION_TYPE_SELL) && !PosExists(POSITION_TYPE_BUY)) m_trade.Sell(volume_, Symbol(), ticks.bid,0,0); } CloseTradeAfterTime((Timeframe2Minutes(Period())*lookahead)*60); //Close the trade after a certain lookahead and according the the trained timeframe }
Nach der Initialisierung des Catboost-Modells im ONNX-Format, das im Verzeichnis Common gespeichert wurde.
In der Funktion OnTick werden die Werte der zuvor geschlossenen Leiste Open, High, Low und Close abgerufen und mit den Funktionen von CTALib zur Erkennung von Kerzenmustern geparst, dann werden die Ergebnisse zur Erstellung von Prognosen in einem Vektor namens x verwendet.
Wir müssen auf die Merkmale und ihre Reihenfolge achten, da sie beim Training des endgültigen Catboost-Modells im Python-Skript verwendet wurden.
X_train.columns
In unserem endgültigen Modell hatten wir.
Index(['White Candle', 'Black Candle', 'Doji Candle', 'Dragonflydoji Candle', 'Gravestonedoji Candle', 'Hammer Candle', 'Invertedhammer Candle', 'Spinningtop Candle', 'BullishMarubozu Candle', 'BearishMarubozu Candle'], dtype='object')
Diese Reihenfolge wurde innerhalb des Expert Advisors beibehalten.
Zurzeit haben wir keinen Stop-Loss und keinen entsprechenden Take-Profit-Wert, sodass wir die offenen Handelsgeschäfte (Positionen) nach einer bestimmten Anzahl von Bars, die im gegebenen Zeitrahmen vergangen sind, schließen.
Tester-Konfigurationen.
Ergebnisse.
Was mich fasziniert, ist die Ähnlichkeit der Ergebnisse zwischen den Handelsgeschäften von Kauf und Verkauf: Die Zahl der in diesen zwei Jahren eröffneten Verkäufe lag bei 257, 2 Handelsgeschäfte weniger als Käufe mit 259.
Dies ist unangemessen, und wir können sagen, dass trotz des Modells, das alle Kerzenmuster berücksichtigt, die weißen und schwarzen Kerzen am aussagekräftigsten sind, da sie auf jedem Balken erscheinen. Wir schließen den Handel auch nach einem Balken (Lookahead-Wert = 1), dieses Problem geht darauf zurück, wie wir die Zielvariable vorbereitet und das Modell mit den Daten trainiert haben, die in vielen Merkmalen Null (falsche) Werte enthalten.
Um sicherzustellen, dass die einzigartigen Kerzenmuster beachtet werden, müssen wir prüfen, wann alle speziellen Kerzenmuster 0 (falsch) waren - also vom Modell nicht erkannt wurden - und verhindern, dass ein Handel eröffnet wird, wenn dies geschieht.
Wir wollen nur dann einen Handel eröffnen, wenn ein einzigartiges Kerzenmuster erkannt wird, das sich vom weißen und schwarzen Kerzen unterscheidet.
void OnTick() { //--- double open = iOpen(Symbol(), Period(), 1), high = iHigh(Symbol(), Period(), 1), low = iLow(Symbol(), Period(), 1), close = iClose(Symbol(), Period(), 1); vector x = { CTALib::CDLWHITECANDLE(open, close), CTALib::CDLBLACKCANDLE(open, close), CTALib::CDLDOJI(open, close), CTALib::CDLDRAGONFLYDOJI(open, high, low, close), CTALib::CDLGRAVESTONEDOJI(open, high, low, close), CTALib::CDLHAMMER(open, high, low, close), CTALib::CDLINVERTEDHAMMER(open, high, low, close), CTALib::CDLSPINNINGTOP(open, high, low, close), CTALib::CDLBULLISHMARUBOZU(open, high, low, close), CTALib::CDLBEARISHMARUBOZU(open, high, low, close) }; vector patterns = { CTALib::CDLDOJI(open, close), CTALib::CDLDRAGONFLYDOJI(open, high, low, close), CTALib::CDLGRAVESTONEDOJI(open, high, low, close), CTALib::CDLHAMMER(open, high, low, close), CTALib::CDLINVERTEDHAMMER(open, high, low, close), CTALib::CDLSPINNINGTOP(open, high, low, close), CTALib::CDLBULLISHMARUBOZU(open, high, low, close), CTALib::CDLBEARISHMARUBOZU(open, high, low, close) }; //Store all the special patterns long signal = catboost.predict(x).cls; //Predicted class MqlTick ticks; if (!SymbolInfoTick(Symbol(), ticks)) { printf("Failed to obtain ticks information, Error = %d",GetLastError()); return; } double volume_ = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); if (signal == 1 && patterns.Sum()>0) //Check if there are is atleast a special pattern before opening a trade { if (!PosExists(POSITION_TYPE_BUY) && !PosExists(POSITION_TYPE_SELL)) m_trade.Buy(volume_, Symbol(), ticks.ask,0,0); } if (signal == 0 && patterns.Sum()>0) //Check if there are is atleast a special pattern before opening a trade { if (!PosExists(POSITION_TYPE_SELL) && !PosExists(POSITION_TYPE_BUY)) m_trade.Sell(volume_, Symbol(), ticks.bid,0,0); } CloseTradeAfterTime((Timeframe2Minutes(Period())*lookahead)*60); //Close the trade after a certain lookahead and according the the trained timeframe }
Testergebnisse.
Es sieht jetzt viel besser aus. Ein paar Handelsgeschäfte wurden eröffnet, was der Seltenheit dieser Kerzenmuster im höheren Zeitrahmen ähnelt, die unser Modell auf dem täglichen Zeitrahmen trainiert hat.
Die prozentuale Anzahl profitabler Handelsgeschäfte liegt bei 54,55 %, was der im Klassifizierungsbericht ermittelten Gesamtgenauigkeit von 0,53 (53 %) sehr nahe kommt; diese Ähnlichkeit zeigt, dass wir auf dem richtigen Weg sind.
Schlussfolgerung
Es ist also möglich, Kerzenmuster bei der Arbeit mit Modellen der Künstlichen Intelligenz (KI) zu verwenden und das Endergebnis für Vorhersagen auf dem Markt zu nutzen. Im Gegensatz zur Verwendung von Indikatoren und mathematischen Berechnungen, wie bei allen typischen Daten, die wir oft für die Vorhersage der Märkte verwenden, erfordern Kerzenmuster jedoch viele Überlegungen und eine große Aufmerksamkeit für winzige Details bei der Sammlung der Daten und der Erstellung von Merkmalen, die von den beobachtbaren Kerzen auf dem Markt abgeleitet werden, denn eine kleine Fehlinterpretation einer Kerze könnte zu einem ganz anderen Ergebnis führen.
Es heißt, dass unsere Wünsche und Überzeugungen Einfluss darauf haben, wie wir Informationen wahrnehmen und interpretieren. Mit anderen Worten: Wir sehen, was wir sehen wollen.
Ich glaube, das passiert meistens, wenn man mit Kerzenmustern arbeitet. Wenn man nach einem Hammer sucht, dann könnte ein Dragonfly-Doji wie ein Hammer aussehen und umgekehrt.
Ich glaube, dass bei der Aufbereitung der Kerzen-basierten Daten viele Versuche und Irrtümer erforderlich sind, um eine optimale Leistung bei der Verwendung dieser Daten im maschinellen Lernen zu erzielen.
Mit freundlichen Grüßen.
Quellen und Referenzen
- What Is a Doji Candle Pattern, and What Does It Tell You?
- Hammer Candlestick: What It Is and How Investors Use It
- Shooting Star: What It Means in Stock Trading, With an Example
- What is a Spinning top candlestick pattern?
- Marubozu: What it Means, How it Works, Why it's Used
- The candlestick trading bible
Tabelle der Anhänge
Dateiname | Beschreibung/Verwendung |
---|---|
Experts\CandlestickPatterns AI-EA.mq5 | Ein Expert Advisor (EA), der das Catboost-Modell einsetzt, das Vorhersagen auf der Grundlage von Kerzenmustern macht. |
Indicators\Candlestick Identifier.mq5 | Ein Indikator zur Anzeige von Kerzenmustern auf dem Chart. |
Scripts\Candlestick Patterns Collect.mq5 | Ein Skript zum Sammeln von Kerzenmustern und Speichern dieser Informationen in einer CSV-Datei. |
Include\Catboost.mqh | Eine Bibliothek, die Klassen zum Laden, Initialisieren und Einsetzen des Catboost-Klassifikators für Vorhersagen auf dem Markt enthält. |
Include\pandas.mqh | Python-ähnliches Pandas-Modul zur Datenspeicherung und -manipulation. |
Include\ta-lib.mqh | Bibliothek für technische Analysen, die eine Klasse zur Erkennung von Kerzenmustern enthält. |
Common\Files\*.csv | CSV-Dateien, die Kerzen-Daten für die Verwendung beim maschinellen Lernen enthalten. |
Common\Files\*.onnx | Modelle für maschinelles Lernen im ONNX-Format. |
CandlestickMarket Prediction.ipynb | Ein Python-Skript (Jupyter noteboot) für das Training des Catboost-Modells. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17832





- 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.
Ich habe dies bereits in diesem Artikel erklärt.
Im Moment betrachten wir nur die Erkennung von Candlestick-Mustern und deren Signale auf der Grundlage ihres Aussehens, aber die richtige Art und Weise, die Signale aus einem Candlestick zu extrahieren, muss meinen Quellen zufolge auch die Trenderkennung einschließen, z. B. muss ein Hammer in einem Abwärtstrend erscheinen, um als bullisches Signal zu gelten.
Der Trend ist ein wichtiger Teil der Gleichung, den Sie berücksichtigen sollten, wenn Sie dieses Projekt weiterverfolgen wollen.
Preisaktionen sind wichtig, das ist unbestritten.
Ich habe dies bereits in dem Artikel erläutert.
Im Moment betrachten wir nur die Erkennung von Candlestick-Mustern und deren Signale auf der Grundlage ihres Aussehens, aber der richtige Weg, die Signale aus einem Candlestick zu extrahieren, muss meinen Quellen zufolge auch die Trenderkennung beinhalten.
Der Trend ist ein wichtiger Teil der Gleichung, den Sie berücksichtigen sollten, wenn Sie dieses Projekt weiterverfolgen wollen.
Preisaktionen sind wichtig, das ist unbestritten.
Ihr Zitat hat nichts mit dem Hauptgedanken meiner Aussage zu tun.