Automatisieren von Handelsstrategien in MQL5 (Teil 34): Trendline Breakout System mit R-Squared Goodness of Fit
Einführung
In unserem System der letzten Artikel (Teil 33) haben wir ein Shark-Muster in MetaQuotes Language 5 (MQL5) entwickelt, das steigende und fallende harmonische Shark-Muster mit Hilfe von Fibonacci-Verhältnisse erkennt und Handelsgeschäfte mit anpassbaren Take-Profit- und Stop-Loss-Levels automatisiert, die durch Chartobjekte wie Dreiecke und Trendlinien visualisiert werden. In Teil 34 erstellen wir ein Trendlinen-Ausbruchssystem, das Unterstützungs- und Widerstandstrendlinien mit Hilfe von Umkehrpunkte identifiziert, die durch die Nebenbedingungen von R-Quadrat Goodness of Fit und der Winkel validiert werden, um Handelsgeschäfte bei einem Ausbruch mit dynamischen Chartvisualisierungen auszuführen. Wir werden die folgenden Themen behandeln:
- Das Verständnis der Strategie des Trendlinien-Ausbruchs
- Implementation in MQL5
- Backtests
- Schlussfolgerung
Am Ende haben Sie eine robuste MQL5-Strategie für den Handel mit Trendlinienausbrüchen, die Sie nur noch anpassen müssen – legen wir los!
Das Verständnis der Strategie des Trendlinien-Ausbruchs
Bei der Strategie des Trendlinien-Ausbruchs werden diagonale Linien auf Preischarts gezeichnet, um hohe (Widerstand) oder tiefe Umkehrpunkte (Unterstützung) zu verbinden und so wichtige Preisniveaus zu identifizieren, auf denen der Markt wahrscheinlich umkehren oder sich fortsetzen wird. Wenn der Kurs diese Trendlinien durchbricht – entweder schließt er oberhalb einer Widerstandslinie oder unterhalb einer Unterstützungslinie – signalisiert dies eine potenzielle Verschiebung der Marktdynamik, was Händler dazu veranlasst, mit bestimmten Risiko- und Ertragsparametern in Richtung des Ausbruchs zu handeln. Dieser Ansatz nutzt starke Kursbewegungen nach dem Ausbruch und zielt darauf ab, signifikante Trends zu erfassen und gleichzeitig das Risiko durch Stop-Loss- und Take-Profit-Niveaus zu steuern. Hier ist eine Illustration eines Ausbruchs aus der Abwärtstrendlinie.

Unser Plan ist es, innerhalb eines bestimmten Rückblickzeitraums hohe und tiefe Umkehrpunkte zu erkennen, Trendlinien mit einer minimalen Anzahl von Berührungspunkten zu konstruieren und sie mithilfe von R-Quadrat Metriken und Winkelbeschränkungen zu validieren, um Zuverlässigkeit zu gewährleisten. R-Quadrat, auch Bestimmtheitsmaß genannt, ist ein statistisches Maß, das angibt, wie gut ein Regressionsmodell die Variabilität der abhängigen Variable mit Hilfe der unabhängigen Variablen erklärt. Er stellt den Anteil der Gesamtvariation des Ergebnisses dar, der durch das Modell erklärt wird, wobei die Werte von 0 bis 1 reichen. Hier ist eine kurze Visualisierung des Modells.

Wir werden eine Handelsausführungslogik für Ausbrüche implementieren, die durch das Schließen von Kerzen oder das Überschreiten der Trendlinie durch ganze Kerzen ausgelöst wird, mit visuellem Feedback durch Trendlinien, Pfeile und Kennzeichnungen, und wir werden den Lebenszyklus der Trendlinien verwalten, indem wir abgelaufene oder durchbrochene Trendlinien entfernen und ein Handelssystem für Ausbrüche schaffen. Schauen Sie sich das angestrebte Ergebnis an, und dann können wir mit der Umsetzung beginnen.

Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner der Indikatoren, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald das Programm erstellt ist, werden wir in der Programmierumgebung damit beginnen, einige Eingaben und Strukturen zu deklarieren, die das Programm dynamischer machen werden.
//+------------------------------------------------------------------+ //| Trendline Breakout Trader EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #include <Trade\Trade.mqh> //--- Include Trade library for trading operations CTrade obj_Trade; //--- Instantiate trade object //+------------------------------------------------------------------+ //| Breakout definition enumeration | //+------------------------------------------------------------------+ enum ENUM_BREAKOUT_TYPE { BREAKOUT_CLOSE = 0, // Breakout on close above/below line BREAKOUT_CANDLE = 1 // Breakout on entire candle above/below line }; //+------------------------------------------------------------------+ //| Swing point structure | //+------------------------------------------------------------------+ struct Swing { //--- Define swing point structure datetime time; //--- Store swing time double price; //--- Store swing price }; //+------------------------------------------------------------------+ //| Starting point structure | //+------------------------------------------------------------------+ struct StartingPoint { //--- Define starting point structure datetime time; //--- Store starting point time double price; //--- Store starting point price bool is_support; //--- Indicate support/resistance flag }; //+------------------------------------------------------------------+ //| Trendline storage structure | //+------------------------------------------------------------------+ struct TrendlineInfo { //--- Define trendline info structure string name; //--- Store trendline name datetime start_time; //--- Store start time datetime end_time; //--- Store end time double start_price; //--- Store start price double end_price; //--- Store end price double slope; //--- Store slope bool is_support; //--- Indicate support/resistance flag int touch_count; //--- Store number of touches datetime creation_time; //--- Store creation time int touch_indices[]; //--- Store touch indices array bool is_signaled; //--- Indicate signal flag }; //+------------------------------------------------------------------+ //| Forward declarations | //+------------------------------------------------------------------+ void DetectSwings(); //--- Declare swing detection function void SortSwings(Swing &swings[], int count); //--- Declare swing sorting function double CalculateAngle(datetime time1, double price1, datetime time2, double price2); //--- Declare angle calculation function bool ValidateTrendline(bool isSupport, datetime start_time, datetime ref_time, double ref_price, double slope, double tolerance_pen); //--- Declare trendline validation function void FindAndDrawTrendlines(bool isSupport); //--- Declare trendline finding/drawing function void UpdateTrendlines(); //--- Declare trendline update function void RemoveTrendlineFromStorage(int index); //--- Declare trendline removal function bool IsStartingPointUsed(datetime time, double price, bool is_support); //--- Declare starting point usage check function double CalculateRSquared(const datetime ×[], const double &prices[], int n, double slope, double intercept); //--- Declare R-squared calculation function //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input ENUM_BREAKOUT_TYPE BreakoutType = BREAKOUT_CLOSE; // Breakout Definition input int LookbackBars = 200; // Set bars for swing detection lookback input double TouchTolerance = 10.0; // Set tolerance for touch points (points) input int MinTouches = 3; // Set minimum touch points for valid trendline input double PenetrationTolerance = 5.0; // Set allowance for bar penetration (points) input int ExtensionBars = 100; // Set bars to extend trendline right input int MinBarSpacing = 10; // Set minimum bar spacing between touches input double inpLot = 0.01; // Set lot size input double inpSLPoints = 100.0; // Set stop loss (points) input double inpRRRatio = 1.1; // Set risk:reward ratio input double MinAngle = 1.0; // Set minimum inclination angle (degrees) input double MaxAngle = 89.0; // Set maximum inclination angle (degrees) input double MinRSquared = 0.8; // Minimum R-squared for trendline acceptance input bool DeleteExpiredObjects = false; // Enable deletion of expired/broken objects input bool EnableTradingSignals = true; // Enable buy/sell signals and trades input bool DrawTouchArrows = true; // Enable drawing arrows at touch points input bool DrawLabels = true; // Enable drawing trendline/point labels input color SupportLineColor = clrGreen; // Set color for support trendlines input color ResistanceLineColor = clrRed; // Set color for resistance trendlines //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ Swing swingLows[]; //--- Store swing lows int numLows = 0; //--- Track number of swing lows Swing swingHighs[]; //--- Store swing highs int numHighs = 0; //--- Track number of swing highs TrendlineInfo trendlines[]; //--- Store trendlines int numTrendlines = 0; //--- Track number of trendlines StartingPoint startingPoints[]; //--- Store used starting points int numStartingPoints = 0; //--- Track number of starting points
Wir beginnen mit der Implementierung unseres Trendlinien-Ausbruchssystems, indem wir die grundlegenden Komponenten für die Erkennung und den Handel mit Trendlinien-Ausbrüchen einrichten. Zunächst binden wir die Bibliothek „Trade.mqh“ ein und instanziieren ein CTrade-Objekt namens „obj_Trade“ für Handelsoperationen. Dann definieren wir die Enumeration „ENUM_BREAKOUT_TYPE“ mit den Optionen „BREAKOUT_CLOSE“ (Ausbruch des Kerzenschlusskurses) und „BREAKOUT_CANDLE“ (Ausbruch der gesamten Kerze), was eine flexible Ausbruchserkennung ermöglicht. Als Nächstes erstellen wir die Struktur „Swing“, um die Zeit und den Preis des Umkehrpunkts zu speichern, die Struktur „StartingPoint“, um die verwendeten Startpunkte der Trendlinie mit einem Unterstützungs-/Widerstandsflag zu verfolgen, und die Struktur „TrendlineInfo“, um Trendliniendetails wie Name, Start-/Endzeiten und Preise, Steigung, Anzahl der Berührungen, Erstellungszeit, Berührungsindizes und Signalstatus zu speichern.
Wir deklarieren Vorwärtsfunktionen wie „DetectSwings“, „SortSwings“ und „CalculateAngle“ für die Kernlogik. Dann legen wir die Eingabeparameter fest: „BreakoutType“ als „BREAKOUT_CLOSE“, „LookbackBars“ bei 200, und der Rest ist selbsterklärend. Schließlich initialisieren wir globale Arrays „swingLows“, „swingHighs“, „trendlines“ und „startingPoints“ mit Zählern „numLows“, „numHighs“, „numTrendlines“ und „numStartingPoints“, um Umkehrpunkte und Trendlinien zu verwalten, die das Rückgrat für die Erkennung und Validierung von Trendlinien für den Handel von Ausbrüchen bilden. Da wir nun alles vorbereitet haben, können wir die Speicherfelder in der Initialisierung initialisieren.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { ArrayResize(trendlines, 0); //--- Resize trendlines array numTrendlines = 0; //--- Reset trendlines count ArrayResize(startingPoints, 0); //--- Resize starting points array numStartingPoints = 0; //--- Reset starting points count return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ArrayResize(trendlines, 0); //--- Resize trendlines array numTrendlines = 0; //--- Reset trendlines count ArrayResize(startingPoints, 0); //--- Resize starting points array numStartingPoints = 0; //--- Reset starting points count }
Um eine ordnungsgemäße Einrichtung und Bereinigung der Ressourcen zu gewährleisten, implementieren wir die Funktion OnInit, indem wir ArrayResize aufrufen, um das Array „trendlines“ auf Null zu setzen, „numTrendlines“ auf 0 zurückzusetzen, die Größe des Arrays „startingPoints“ auf Null zu ändern und „numStartingPoints“ auf 0 zurückzusetzen und dann INIT_SUCCEEDED zurückzugeben, um die erfolgreiche Initialisierung zu bestätigen. In der Funktion OnDeinit führen wir dann eine identische Bereinigung durch, um sicherzustellen, dass bei der Beendigung des Programms kein Speicherplatz verloren geht. Nachdem die Initialisierung abgeschlossen ist, können wir nun mit der Definition der Strategielogik fortfahren. Um die Logik zu modularisieren, verwenden wir Funktionen. Die erste Logik, die wir definieren, ist die Erkennung von Umkehrpunkten, damit wir Basis-Trendlinienpunkte erhalten.
//+------------------------------------------------------------------+ //| Check for new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime lastTime = 0; //--- Store last bar time datetime currentTime = iTime(_Symbol, _Period, 0); //--- Get current bar time if (lastTime != currentTime) { //--- Check for new bar lastTime = currentTime; //--- Update last time return true; //--- Indicate new bar } return false; //--- Indicate no new bar } //+------------------------------------------------------------------+ //| Sort swings by time (ascending, oldest first) | //+------------------------------------------------------------------+ void SortSwings(Swing &swings[], int count) { for (int i = 0; i < count - 1; i++) { //--- Iterate through swings for (int j = 0; j < count - i - 1; j++) { //--- Compare adjacent swings if (swings[j].time > swings[j + 1].time) { //--- Check time order Swing temp = swings[j]; //--- Store temporary swing swings[j] = swings[j + 1]; //--- Swap swings swings[j + 1] = temp; //--- Complete swap } } } } //+------------------------------------------------------------------+ //| Detect swing highs and lows | //+------------------------------------------------------------------+ void DetectSwings() { numLows = 0; //--- Reset lows count ArrayResize(swingLows, 0); //--- Resize lows array numHighs = 0; //--- Reset highs count ArrayResize(swingHighs, 0); //--- Resize highs array int totalBars = iBars(_Symbol, _Period); //--- Get total bars int effectiveLookback = MathMin(LookbackBars, totalBars); //--- Calculate effective lookback if (effectiveLookback < 5) { //--- Check sufficient bars Print("Not enough bars for swing detection."); //--- Log insufficient bars return; //--- Exit function } for (int i = 2; i < effectiveLookback - 2; i++) { //--- Iterate through bars double low_i = iLow(_Symbol, _Period, i); //--- Get current low double low_im1 = iLow(_Symbol, _Period, i - 1); //--- Get previous low double low_im2 = iLow(_Symbol, _Period, i - 2); //--- Get two bars prior low double low_ip1 = iLow(_Symbol, _Period, i + 1); //--- Get next low double low_ip2 = iLow(_Symbol, _Period, i + 2); //--- Get two bars next low if (low_i < low_im1 && low_i < low_im2 && low_i < low_ip1 && low_i < low_ip2) { //--- Check for swing low Swing s; //--- Create swing struct s.time = iTime(_Symbol, _Period, i); //--- Set swing time s.price = low_i; //--- Set swing price ArrayResize(swingLows, numLows + 1); //--- Resize lows array swingLows[numLows] = s; //--- Add swing low numLows++; //--- Increment lows count } double high_i = iHigh(_Symbol, _Period, i); //--- Get current high double high_im1 = iHigh(_Symbol, _Period, i - 1); //--- Get previous high double high_im2 = iHigh(_Symbol, _Period, i - 2); //--- Get two bars prior high double high_ip1 = iHigh(_Symbol, _Period, i + 1); //--- Get next high double high_ip2 = iHigh(_Symbol, _Period, i + 2); //--- Get two bars next high if (high_i > high_im1 && high_i > high_im2 && high_i > high_ip1 && high_i > high_ip2) { //--- Check for swing high Swing s; //--- Create swing struct s.time = iTime(_Symbol, _Period, i); //--- Set swing time s.price = high_i; //--- Set swing price ArrayResize(swingHighs, numHighs + 1); //--- Resize highs array swingHighs[numHighs] = s; //--- Add swing high numHighs++; //--- Increment highs count } } if (numLows > 0) SortSwings(swingLows, numLows); //--- Sort swing lows if (numHighs > 0) SortSwings(swingHighs, numHighs); //--- Sort swing highs }
Nachdem die grundlegende Einrichtung abgeschlossen ist, implementieren wir nun den Kern er Logik zur Erkennung von Umkehrpunkten und zur Verwaltung von Balkenaktualisierungen. Zunächst entwickeln wir die Funktion „IsNewBar“, die eine statische Variable „lastTime“ verwendet, um die Zeit des vorherigen Balkens zu speichern, sie mit der Zeit des aktuellen Balkens vergleicht, die über iTime für das Symbol und die Periode bei Shift 0 ermittelt wurde, „lastTime“ aktualisiert, wenn es einen Unterschied gibt, und „true“ zurückgibt, um einen neuen Balken anzuzeigen, oder „false“, wenn nicht. Dann implementieren wir die Funktion „SortSwings“, die ein „Swing“-Array nach der Zeit in aufsteigender Reihenfolge mit einem Bubble-Sort-Algorithmus sortiert, indem sie das Array mit verschachtelten Schleifen durchläuft, die „Zeit“-Felder benachbarter Elemente vergleicht und sie mit einer temporären Struktur „Swing“ vertauscht, wenn sie nicht in der richtigen Reihenfolge sind.
Als Nächstes erstellen wir die Funktion „DetectSwings“, wobei wir „numLows“ und „numHighs“ auf 0 zurücksetzen und die Größe der Arrays „swingLows“ und „swingHighs“ auf Null setzen, einen effektiven Lookback mit MathMin von „LookbackBars“ und die Gesamtzahl der Bars von iBars berechnen und mit einem Print beenden, wenn weniger als 5 Bars verfügbar sind. Anschließend durchlaufen wir die Balken von Index 2 bis „effectiveLookback – 2“ und suchen nach einem tiefem Umkehrpunkt, indem wir das Tief des aktuellen Balkens („iLow“) mit zwei vorherigen und zwei nachfolgenden Balken vergleichen, und nach hohen Umkehrpunkt, indem wir auf ähnliche Weise iHigh verwenden. Wenn ein Umkehrpunkt erkannt wird, erstellen wir die Struktur „Swing”, setzen deren „time” mit „iTime” und „price” mit dem Tiefst- oder Höchstwert, hängen sie mit ArrayResize an „swingLows” oder „swingHighs” an und erhöhen den entsprechenden Zähler. Schließlich rufen wir „SortSwings“ auf „swingLows“ und „swingHighs“ auf, wenn sie Elemente enthalten, um eine chronologische Reihenfolge für die Konstruktion von Trendlinien zu gewährleisten. Definieren wir nun Funktionen zur Berechnung der Trendlinienneigung für die Einschränkung auf der Grundlage der Neigung und ihrer Validierung.
//+------------------------------------------------------------------+ //| Calculate visual inclination angle | //+------------------------------------------------------------------+ double CalculateAngle(datetime time1, double price1, datetime time2, double price2) { int x1, y1, x2, y2; //--- Declare coordinate variables if (!ChartTimePriceToXY(0, 0, time1, price1, x1, y1)) return 0.0; //--- Convert time1/price1 to XY if (!ChartTimePriceToXY(0, 0, time2, price2, x2, y2)) return 0.0; //--- Convert time2/price2 to XY double dx = (double)(x2 - x1); //--- Calculate x difference double dy = (double)(y2 - y1); //--- Calculate y difference if (dx == 0.0) return (dy > 0.0 ? -90.0 : 90.0); //--- Handle vertical line case double angle = MathArctan(-dy / dx) * 180.0 / M_PI; //--- Calculate angle in degrees return angle; //--- Return angle } //+------------------------------------------------------------------+ //| Validate trendline | //+------------------------------------------------------------------+ bool ValidateTrendline(bool isSupport, datetime start_time, datetime ref_time, double ref_price, double slope, double tolerance_pen) { int bar_start = iBarShift(_Symbol, _Period, start_time); //--- Get start bar index if (bar_start < 0) return false; //--- Check invalid bar index for (int bar = bar_start; bar >= 0; bar--) { //--- Iterate through bars datetime bar_time = iTime(_Symbol, _Period, bar); //--- Get bar time double dk = (double)(bar_time - ref_time); //--- Calculate time difference double line_price = ref_price + slope * dk; //--- Calculate line price if (isSupport) { //--- Check support case double low = iLow(_Symbol, _Period, bar); //--- Get bar low if (low < line_price - tolerance_pen) return false;//--- Check if broken } else { //--- Handle resistance case double high = iHigh(_Symbol, _Period, bar); //--- Get bar high if (high > line_price + tolerance_pen) return false;//--- Check if broken } } return true; //--- Return valid }
Hier implementieren wir Funktionen zur Berechnung von Trendlinienwinkeln und zur Validierung ihrer Integrität. Zunächst entwickeln wir die Funktion „CalculateAngle“, die zwei Zeit-Kurspunkte („time1“, „price1“ und „time2“, „price2“) mit ChartTimePriceToXY in Chartkoordinaten („x1“, „y1“ und „x2“, „y2“) umwandelt und 0 zurückgibt.0 zurück, wenn eine der beiden Konvertierungen fehlschlägt. Wir berechnen die Differenzen „dx“ und „dy“, behandeln vertikale Linien, indem wir -90.0 oder 90.0 zurückgeben, wenn „dx“ auf der Grundlage von „dy“ Null ist, und berechnen den Winkel in Grad mit MathArctan von „-dy / dx“ multipliziert mit 180/M_PI für die visuelle Steigung.
Dann implementieren wir die Funktion „ValidateTrendline“, die eine Trendlinie validiert, indem sie den Bar-Index von „start_time“ mit iBarShift ermittelt und false zurückgibt, wenn er ungültig ist; wir iterieren von diesem Index bis zur Gegenwart und berechnen den Preis der Trendlinie zu jedem Bar-Zeitpunkt („iTime“) mit der Formel „ref_price + slope * (bar_time – ref_time)“; für Support-Trendlinien („isSupport“ true) wird geprüft, ob das Tief des Balkens (iLow) unter „line_price – tolerance_pen“ fällt und bei einem Ausbruch false zurückgegeben; bei Widerstand wird geprüft, ob das Hoch des Balkens (iHigh) „line_price + tolerance_pen“ übersteigt und bei einem Ausbruch false zurückgegeben, andernfalls true, um sicherzustellen, dass die Trendlinien die Winkelbeschränkungen einhalten und für eine zuverlässige Ausbruchserkennung nicht durchbrochen werden. Wir können nun die Funktion für das R-Quadrat der Anpassungsgüte definieren.
//+------------------------------------------------------------------+ //| Calculate R-squared for goodness of fit | //+------------------------------------------------------------------+ double CalculateRSquared(const datetime ×[], const double &prices[], int n, double slope, double intercept) { double sum_y = 0.0; //--- Initialize sum of y for (int k = 0; k < n; k++) { //--- Iterate through points sum_y += prices[k]; //--- Accumulate y } double mean_y = sum_y / n; //--- Calculate mean y double ss_tot = 0.0, ss_res = 0.0; //--- Initialize sums of squares for (int k = 0; k < n; k++) { //--- Iterate through points double x = (double)times[k]; //--- Get x (time) double y_pred = intercept + slope * x; //--- Calculate predicted y double y = prices[k]; //--- Get actual y ss_res += (y - y_pred) * (y - y_pred); //--- Accumulate residual sum ss_tot += (y - mean_y) * (y - mean_y); //--- Accumulate total sum } if (ss_tot == 0.0) return 1.0; //--- Handle constant y case return 1.0 - ss_res / ss_tot; //--- Calculate and return R-squared }
Wir entwickeln die Funktion „CalculateRSquared“, die Arrays von Zeiten und Preisen, die Anzahl der Punkte „n“ und die „Steigung“ und den „Achsenabschnitt“ der Trendlinie als Eingaben erhält; wir initialisieren „sum_y“ auf 0 und iterieren durch „prices“, um die Summe zu berechnen, und berechnen dann den Mittelwert „mean_y“, indem wir „sum_y“ durch „n“ dividieren. Dann initialisieren wir „ss_tot“ und „ss_res“ für Gesamt- und den Residuen der Quadratsummen, iterieren erneut, um die vorhergesagten Preise („y_pred“) mit der Formel „intercept + slope * time“ zu berechnen, die Residuen (Quadrate von „y – y_pred“) in „ss_res“ und die Abweichungen vom Mittelwert (Quadrate von „y – mean_y“) in „ss_tot“ zu akkumulieren und 1 zurückzugeben.0, wenn „ss_tot“ Null ist (konstante Preise), oder Berechnung von R-Quadrat als „1.0 – ss_res / ss_tot“. Für die Berechnung der Gültigkeit der Trendlinien verwenden wir einfach die Formel R-Quadrat. Definieren wir nun eine Funktion zur Verwaltung der Trendlinien.
//+------------------------------------------------------------------+ //| Check if starting point is already used | //+------------------------------------------------------------------+ bool IsStartingPointUsed(datetime time, double price, bool is_support) { for (int i = 0; i < numStartingPoints; i++) { //--- Iterate through starting points if (startingPoints[i].time == time && MathAbs(startingPoints[i].price - price) < TouchTolerance * _Point && startingPoints[i].is_support == is_support) { //--- Check match return true; //--- Return used } } return false; //--- Return not used } //+------------------------------------------------------------------+ //| Remove trendline from storage and optionally chart objects | //+------------------------------------------------------------------+ void RemoveTrendlineFromStorage(int index) { if (index < 0 || index >= numTrendlines) return; //--- Check valid index Print("Removing trendline from storage: ", trendlines[index].name); //--- Log removal if (DeleteExpiredObjects) { //--- Check deletion flag ObjectDelete(0, trendlines[index].name); //--- Delete trendline object for (int m = 0; m < trendlines[index].touch_count; m++) { //--- Iterate touches string arrow_name = trendlines[index].name + "_touch" + IntegerToString(m); //--- Generate arrow name ObjectDelete(0, arrow_name); //--- Delete touch arrow string text_name = trendlines[index].name + "_point_label" + IntegerToString(m); //--- Generate text name ObjectDelete(0, text_name); //--- Delete point label } string label_name = trendlines[index].name + "_label"; //--- Generate label name ObjectDelete(0, label_name); //--- Delete trendline label string signal_arrow = trendlines[index].name + "_signal_arrow"; //--- Generate signal arrow name ObjectDelete(0, signal_arrow); //--- Delete signal arrow string signal_text = trendlines[index].name + "_signal_text"; //--- Generate signal text name ObjectDelete(0, signal_text); //--- Delete signal text } for (int i = index; i < numTrendlines - 1; i++) { //--- Shift array trendlines[i] = trendlines[i + 1]; //--- Copy next trendline } ArrayResize(trendlines, numTrendlines - 1); //--- Resize trendlines array numTrendlines--; //--- Decrement trendlines count }
Hier implementieren wir Funktionen zur Verwaltung von Trendlinien-Startpunkten und deren Bereinigung. Zunächst entwickeln wir die Funktion „IsStartingPointUsed“, die das Array „startingPoints“ durchläuft und prüft, ob ein gegebener „time“, „price“ und „is_support“ mit einem vorhandenen Startpunkt innerhalb von „TouchTolerance * _Point“ übereinstimmt, indem sie MathAbs verwendet und true zurückgibt, wenn sie gefunden wird, oder false, wenn nicht. Dadurch wird sichergestellt, dass nicht mehr als 1 Trendlinie von einem Punkt ausgeht.
Dann erstellen wir die Funktion „RemoveTrendlineFromStorage“, die die Eingabe „index“ gegen „numTrendlines“ validiert, das Entfernen des „Namens“ der Trendlinie mit „Print“ protokolliert und, falls „DeleteExpiredObjects“ wahr ist, die Chart-Objekte mit ObjectDelete für die Trendlinie („name“), Berührungspfeile („name + '_touch' + index“), Punktbeschriftungen („name + '_point_label' + index“), Trendlinienbeschriftungen („name + '_label'“), Signalpfeil („name + '_signal_arrow'“), und Signaltext („name + '_signal_text'“). Als Nächstes werden die Elemente im Array „trendlines“ in einer Schleife von „index“ nach links verschoben, die Größe des Arrays mit ArrayResize um eins verringert und „numTrendlines“ dekrementiert, um eindeutige Trendlinien-Startpunkte und die ordnungsgemäße Bereinigung ungültiger Trendlinien und ihrer Charts sicherzustellen. Definieren wir nun eine Funktion, um die Trendlinien zu finden und zu zeichnen, indem wir die von uns definierten Hilfsfunktionen verwenden.
//+------------------------------------------------------------------+ //| Find and draw trendlines if no active one exists | //+------------------------------------------------------------------+ void FindAndDrawTrendlines(bool isSupport) { bool has_active = false; //--- Initialize active flag for (int i = 0; i < numTrendlines; i++) { //--- Iterate through trendlines if (trendlines[i].is_support == isSupport) { //--- Check type match has_active = true; //--- Set active flag break; //--- Exit loop } } if (has_active) return; //--- Exit if active trendline exists Swing swings[]; //--- Initialize swings array int numSwings; //--- Initialize swings count color lineColor; //--- Initialize line color string prefix; //--- Initialize prefix if (isSupport) { //--- Handle support case numSwings = numLows; //--- Set number of lows ArrayResize(swings, numSwings); //--- Resize swings array for (int i = 0; i < numSwings; i++) { //--- Iterate through lows swings[i].time = swingLows[i].time; //--- Copy low time swings[i].price = swingLows[i].price; //--- Copy low price } lineColor = SupportLineColor; //--- Set support line color prefix = "Trendline_Support_"; //--- Set support prefix } else { //--- Handle resistance case numSwings = numHighs; //--- Set number of highs ArrayResize(swings, numSwings); //--- Resize swings array for (int i = 0; i < numSwings; i++) { //--- Iterate through highs swings[i].time = swingHighs[i].time; //--- Copy high time swings[i].price = swingHighs[i].price; //--- Copy high price } lineColor = ResistanceLineColor; //--- Set resistance line color prefix = "Trendline_Resistance_"; //--- Set resistance prefix } if (numSwings < 2) return; //--- Exit if insufficient swings double pointValue = _Point; //--- Get point value double touch_tolerance = TouchTolerance * pointValue; //--- Calculate touch tolerance double pen_tolerance = PenetrationTolerance * pointValue; //--- Calculate penetration tolerance int best_j = -1; //--- Initialize best j index int max_touches = 0; //--- Initialize max touches double best_rsquared = -1.0; //--- Initialize best R-squared int best_touch_indices[]; //--- Initialize best touch indices double best_slope = 0.0; //--- Initialize best slope double best_intercept = 0.0; //--- Initialize best intercept datetime best_min_time = 0; //--- Initialize best min time for (int i = 0; i < numSwings - 1; i++) { //--- Iterate through first points for (int j = i + 1; j < numSwings; j++) { //--- Iterate through second points datetime time1 = swings[i].time; //--- Get first time double price1 = swings[i].price; //--- Get first price datetime time2 = swings[j].time; //--- Get second time double price2 = swings[j].price; //--- Get second price double dt = (double)(time2 - time1); //--- Calculate time difference if (dt <= 0) continue; //--- Skip invalid time difference double initial_slope = (price2 - price1) / dt; //--- Calculate initial slope int touch_indices[]; //--- Initialize touch indices ArrayResize(touch_indices, 0); //--- Resize touch indices int touches = 0; //--- Initialize touches count ArrayResize(touch_indices, touches + 1); //--- Add first index touch_indices[touches] = i; //--- Set first index touches++; //--- Increment touches ArrayResize(touch_indices, touches + 1); //--- Add second index touch_indices[touches] = j; //--- Set second index touches++; //--- Increment touches for (int k = 0; k < numSwings; k++) { //--- Iterate through swings if (k == i || k == j) continue; //--- Skip used indices datetime tk = swings[k].time; //--- Get swing time double dk = (double)(tk - time1); //--- Calculate time difference double expected = price1 + initial_slope * dk; //--- Calculate expected price double actual = swings[k].price; //--- Get actual price if (MathAbs(expected - actual) <= touch_tolerance) { //--- Check touch within tolerance ArrayResize(touch_indices, touches + 1); //--- Add index touch_indices[touches] = k; //--- Set index touches++; //--- Increment touches } } if (touches >= MinTouches) { //--- Check minimum touches ArraySort(touch_indices); //--- Sort touch indices bool valid_spacing = true; //--- Initialize spacing flag for (int m = 0; m < touches - 1; m++) { //--- Iterate through touches int idx1 = touch_indices[m]; //--- Get first index int idx2 = touch_indices[m + 1]; //--- Get second index int bar1 = iBarShift(_Symbol, _Period, swings[idx1].time); //--- Get first bar int bar2 = iBarShift(_Symbol, _Period, swings[idx2].time); //--- Get second bar int diff = MathAbs(bar1 - bar2); //--- Calculate bar difference if (diff < MinBarSpacing) { //--- Check minimum spacing valid_spacing = false; //--- Mark invalid spacing break; //--- Exit loop } } if (valid_spacing) { //--- Check valid spacing datetime touch_times[]; //--- Initialize touch times double touch_prices[]; //--- Initialize touch prices ArrayResize(touch_times, touches); //--- Resize times array ArrayResize(touch_prices, touches); //--- Resize prices array for (int m = 0; m < touches; m++) { //--- Iterate through touches int idx = touch_indices[m]; //--- Get index touch_times[m] = swings[idx].time; //--- Set time touch_prices[m] = swings[idx].price; //--- Set price } double slope = initial_slope; //--- Use initial slope from two points double intercept = price1 - slope * (double)time1; //--- Calculate intercept double rsquared = CalculateRSquared(touch_times, touch_prices, touches, slope, intercept); //--- Calculate R-squared if (rsquared >= MinRSquared) { //--- Check minimum R-squared int adjusted_touch_indices[]; //--- Initialize adjusted indices ArrayResize(adjusted_touch_indices, touches); //--- Resize to current touches ArrayCopy(adjusted_touch_indices, touch_indices); //--- Copy indices int adjusted_touches = touches; //--- Set adjusted touches if (adjusted_touches >= MinTouches) { //--- Check minimum adjusted touches datetime temp_min_time = swings[adjusted_touch_indices[0]].time; //--- Get min time double temp_ref_price = intercept + slope * (double)temp_min_time; //--- Calculate ref price if (ValidateTrendline(isSupport, temp_min_time, temp_min_time, temp_ref_price, slope, pen_tolerance)) { //--- Validate trendline datetime temp_max_time = swings[adjusted_touch_indices[adjusted_touches - 1]].time; //--- Get max time double temp_max_price = intercept + slope * (double)temp_max_time; //--- Calculate max price double angle = CalculateAngle(temp_min_time, temp_ref_price, temp_max_time, temp_max_price); //--- Calculate angle double abs_angle = MathAbs(angle); //--- Get absolute angle if (abs_angle >= MinAngle && abs_angle <= MaxAngle) { //--- Check angle range if (adjusted_touches > max_touches || (adjusted_touches == max_touches && rsquared > best_rsquared)) { //--- Check better trendline max_touches = adjusted_touches; //--- Update max touches best_rsquared = rsquared; //--- Update best R-squared best_j = j; //--- Update best j best_slope = slope; //--- Update best slope best_intercept = intercept; //--- Update best intercept best_min_time = temp_min_time; //--- Update best min time ArrayResize(best_touch_indices, adjusted_touches); //--- Resize best indices ArrayCopy(best_touch_indices, adjusted_touch_indices); //--- Copy indices } } } } } } } } } if (max_touches < MinTouches) { //--- Check insufficient touches string type = isSupport ? "Support" : "Resistance"; //--- Set type string return; //--- Exit function } int touch_indices[]; //--- Initialize touch indices ArrayResize(touch_indices, max_touches); //--- Resize touch indices ArrayCopy(touch_indices, best_touch_indices); //--- Copy best indices int touches = max_touches; //--- Set touches count datetime min_time = best_min_time; //--- Set min time double price_min = best_intercept + best_slope * (double)min_time; //--- Calculate min price datetime max_time = swings[touch_indices[touches - 1]].time; //--- Set max time double price_max = best_intercept + best_slope * (double)max_time; //--- Calculate max price datetime start_time_check = min_time; //--- Set start time check double start_price_check = price_min; //--- Set start price check (approximate if not exact) if (IsStartingPointUsed(start_time_check, start_price_check, isSupport)) { //--- Check used starting point return; //--- Skip if used } datetime time_end = iTime(_Symbol, _Period, 0) + PeriodSeconds(_Period) * ExtensionBars; //--- Calculate end time double dk_end = (double)(time_end - min_time); //--- Calculate end time difference double price_end = price_min + best_slope * dk_end; //--- Calculate end price string unique_name = prefix + TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS); //--- Generate unique name if (ObjectFind(0, unique_name) < 0) { //--- Check if trendline exists ObjectCreate(0, unique_name, OBJ_TREND, 0, min_time, price_min, time_end, price_end); //--- Create trendline ObjectSetInteger(0, unique_name, OBJPROP_COLOR, lineColor); //--- Set color ObjectSetInteger(0, unique_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style ObjectSetInteger(0, unique_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, unique_name, OBJPROP_RAY_RIGHT, false); //--- Disable right ray ObjectSetInteger(0, unique_name, OBJPROP_RAY_LEFT, false); //--- Disable left ray ObjectSetInteger(0, unique_name, OBJPROP_BACK, false); //--- Set to foreground } ArrayResize(trendlines, numTrendlines + 1); //--- Resize trendlines array trendlines[numTrendlines].name = unique_name; //--- Set trendline name trendlines[numTrendlines].start_time = min_time; //--- Set start time trendlines[numTrendlines].end_time = time_end; //--- Set end time trendlines[numTrendlines].start_price = price_min; //--- Set start price trendlines[numTrendlines].end_price = price_end; //--- Set end price trendlines[numTrendlines].slope = best_slope; //--- Set slope trendlines[numTrendlines].is_support = isSupport; //--- Set type trendlines[numTrendlines].touch_count = touches; //--- Set touch count trendlines[numTrendlines].creation_time = TimeCurrent(); //--- Set creation time trendlines[numTrendlines].is_signaled = false; //--- Set signaled flag ArrayResize(trendlines[numTrendlines].touch_indices, touches); //--- Resize touch indices ArrayCopy(trendlines[numTrendlines].touch_indices, touch_indices); //--- Copy touch indices numTrendlines++; //--- Increment trendlines count ArrayResize(startingPoints, numStartingPoints + 1); //--- Resize starting points array startingPoints[numStartingPoints].time = start_time_check;//--- Set starting point time startingPoints[numStartingPoints].price = start_price_check; //--- Set starting point price startingPoints[numStartingPoints].is_support = isSupport; //--- Set starting point type numStartingPoints++; //--- Increment starting points count if (DrawTouchArrows) { //--- Check draw arrows for (int m = 0; m < touches; m++) { //--- Iterate through touches int idx = touch_indices[m]; //--- Get touch index datetime tk_time = swings[idx].time; //--- Get touch time double tk_price = swings[idx].price; //--- Get touch price string arrow_name = unique_name + "_touch" + IntegerToString(m); //--- Generate arrow name if (ObjectFind(0, arrow_name) < 0) { //--- Check if arrow exists ObjectCreate(0, arrow_name, OBJ_ARROW, 0, tk_time, tk_price); //--- Create touch arrow ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, 159); //--- Set arrow code ObjectSetInteger(0, arrow_name, OBJPROP_ANCHOR, isSupport ? ANCHOR_TOP : ANCHOR_BOTTOM); //--- Set anchor ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, lineColor); //--- Set color ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, arrow_name, OBJPROP_BACK, false); //--- Set to foreground } } } double angle = CalculateAngle(min_time, price_min, max_time, price_max); //--- Calculate angle string type = isSupport ? "Support" : "Resistance"; //--- Set type string Print(type + " Trendline " + unique_name + " drawn with " + IntegerToString(touches) + " touches. Inclination angle: " + DoubleToString(angle, 2) + " degrees."); //--- Log trendline if (DrawLabels) { //--- Check draw labels datetime mid_time = min_time + (max_time - min_time) / 2; //--- Calculate mid time double dk_mid = (double)(mid_time - min_time); //--- Calculate mid time difference double mid_price = price_min + best_slope * dk_mid; //--- Calculate mid price double label_offset = 20 * _Point * (isSupport ? -1 : 1); //--- Calculate label offset double label_price = mid_price + label_offset; //--- Calculate label price int label_anchor = isSupport ? ANCHOR_TOP : ANCHOR_BOTTOM;//--- Set label anchor string label_text = type + " Trendline"; //--- Set label text string label_name = unique_name + "_label"; //--- Generate label name if (ObjectFind(0, label_name) < 0) { //--- Check if label exists ObjectCreate(0, label_name, OBJ_TEXT, 0, mid_time, label_price); //--- Create label ObjectSetString(0, label_name, OBJPROP_TEXT, label_text); //--- Set text ObjectSetInteger(0, label_name, OBJPROP_COLOR, clrBlack); //--- Set color ObjectSetInteger(0, label_name, OBJPROP_FONTSIZE, 8); //--- Set font size ObjectSetInteger(0, label_name, OBJPROP_ANCHOR, label_anchor); //--- Set anchor ObjectSetDouble(0, label_name, OBJPROP_ANGLE, angle); //--- Set angle ObjectSetInteger(0, label_name, OBJPROP_BACK, false); //--- Set to foreground } color point_label_color = isSupport ? clrSaddleBrown : clrDarkGoldenrod; //--- Set point label color double point_text_offset = 20.0 * _Point; //--- Set point text offset for (int m = 0; m < touches; m++) { //--- Iterate through touches int idx = touch_indices[m]; //--- Get touch index datetime tk_time = swings[idx].time; //--- Get touch time double tk_price = swings[idx].price; //--- Get touch price double text_price; //--- Initialize text price int point_text_anchor; //--- Initialize text anchor if (isSupport) { //--- Handle support text_price = tk_price - point_text_offset; //--- Set text price below point_text_anchor = ANCHOR_LEFT; //--- Set left anchor } else { //--- Handle resistance text_price = tk_price + point_text_offset; //--- Set text price above point_text_anchor = ANCHOR_BOTTOM; //--- Set bottom anchor } string text_name = unique_name + "_point_label" + IntegerToString(m); //--- Generate text name string point_text = "Pt " + IntegerToString(m + 1); //--- Set point text if (ObjectFind(0, text_name) < 0) { //--- Check if text exists ObjectCreate(0, text_name, OBJ_TEXT, 0, tk_time, text_price); //--- Create text ObjectSetString(0, text_name, OBJPROP_TEXT, point_text); //--- Set text ObjectSetInteger(0, text_name, OBJPROP_COLOR, point_label_color); //--- Set color ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 8); //--- Set font size ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, point_text_anchor); //--- Set anchor ObjectSetDouble(0, text_name, OBJPROP_ANGLE, 0); //--- Set angle ObjectSetInteger(0, text_name, OBJPROP_BACK, false); //--- Set to foreground } } } }
Hier implementieren wir die Logik der Trendlinienerkennung und -visualisierung. Zunächst wird in der Funktion „FindAndDrawTrendlines“ nach vorhandenen Trendlinien des Typs „isSupport“ in „trendlines“ gesucht, wobei „has_active“ auf true gesetzt und die Funktion beendet wird, wenn sie gefunden wurde. Dann initialisieren wir ein Array „swings“, kopieren „swingLows“ oder „swingHighs“ basierend auf „isSupport“, setzen „lineColor“ auf „SupportLineColor“ oder „ResistanceLineColor“ und „prefix“ auf „Trendline_Support_“ oder „Trendline_Resistance_“, und beenden, wenn weniger als zwei Umkehrpunkte vorhanden sind.
Als Nächstes berechnen wir die Toleranzen („TouchTolerance“ und „PenetrationTolerance“ skaliert durch _Point) und iterieren durch Paare von Umkehrpunkten, um „initial_slope“ zu berechnen, wobei wir Berührungspunkte innerhalb der „touch_tolerance“ in „touch_indices“ sammeln. Wir validieren Berührungen mit „MinTouches“ und „MinBarSpacing“ unter Verwendung von iBarShift und ArraySort, berechnen „slope“ und „intercept“und werten „CalculateRSquared“ und „ValidateTrendline“ aus, um die beste Trendlinie auf der Grundlage von „max_touches“ und „best_rsquared“ auszuwählen. Wenn sie gültig ist, zeichnen wir die Trendlinie mit „ObjectCreate“ (OBJ_TREND) mit „unique_name“, setzen Eigenschaften wie OBJPROP_COLOR, „OBJPROP_STYLE“und deaktivieren Strahlen und speichern sie dann in „trendlines“ mit Details wie „start_time“, „end_time“ (erweitert durch „ExtensionBars“) und „touch_indices“. Wir aktualisieren „startingPoints“ mit „IsStartingPointUsed“, um Duplikate zu vermeiden, und wenn „DrawTouchArrows“ wahr ist, zeichnen wir Pfeile (OBJ_ARROW) an Berührungspunkten mit „lineColor“ und entsprechenden Ankern.
Wenn „DrawLabels“ wahr ist, fügen wir eine Trendlinienbeschriftung (OBJ_TEXT) mit „type + ' Trendline'“ am Mittelpunkt hinzu, gewinkelt über „CalculateAngle“, und Punktbeschriftungen („Pt 1“, etc.) mit den Farben „clrSaddleBrown“ oder „clrDarkGoldenrod“ und protokollieren die Details der Trendlinie. Was nun bleibt, ist die Verwaltung der bestehenden Trendlinien durch kontinuierliche Aktualisierungen und die Überprüfung auf Kreuzungen für Signale. Der Einfachheit halber werden wir die gesamte Logik in einer einzigen Funktion zusammenfassen.
//+------------------------------------------------------------------+ //| Update trendlines and check for signals | //+------------------------------------------------------------------+ void UpdateTrendlines() { datetime current_time = iTime(_Symbol, _Period, 0); //--- Get current time double pointValue = _Point; //--- Get point value double pen_tolerance = PenetrationTolerance * pointValue; //--- Calculate penetration tolerance double touch_tolerance = TouchTolerance * pointValue; //--- Calculate touch tolerance for (int i = numTrendlines - 1; i >= 0; i--) { //--- Iterate trendlines backward string type = trendlines[i].is_support ? "Support" : "Resistance"; //--- Determine trendline type string name = trendlines[i].name; //--- Get trendline name if (current_time > trendlines[i].end_time) { //--- Check if expired PrintFormat("%s trendline %s is no longer valid (expired). End time: %s, Current time: %s.", type, name, TimeToString(trendlines[i].end_time), TimeToString(current_time)); //--- Log expiration RemoveTrendlineFromStorage(i); //--- Remove trendline continue; //--- Skip to next } datetime prev_bar_time = iTime(_Symbol, _Period, 1); //--- Get previous bar time double dk = (double)(prev_bar_time - trendlines[i].start_time); //--- Calculate time difference double line_price = trendlines[i].start_price + trendlines[i].slope * dk; //--- Calculate line price double prev_close = iClose(_Symbol, _Period, 1); //--- Get previous bar close double prev_low = iLow(_Symbol, _Period, 1); //--- Get previous bar low double prev_high = iHigh(_Symbol, _Period, 1); //--- Get previous bar high bool broken = false; //--- Initialize broken flag if (BreakoutType == BREAKOUT_CLOSE) { //--- Check breakout on close if (trendlines[i].is_support && prev_close < line_price) { //--- Support break by close PrintFormat("%s trendline %s is no longer valid (broken by close). Line price: %.5f, Prev close: %.5f.", type, name, line_price, prev_close); //--- Log break broken = true; //--- Set broken flag } else if (!trendlines[i].is_support && prev_close > line_price) { //--- Resistance break by close PrintFormat("%s trendline %s is no longer valid (broken by close). Line price: %.5f, Prev close: %.5f.", type, name, line_price, prev_close); //--- Log break broken = true; //--- Set broken flag } } else if (BreakoutType == BREAKOUT_CANDLE) { //--- Check breakout on entire candle if (trendlines[i].is_support && prev_high < line_price) { //--- Entire candle below support PrintFormat("%s trendline %s is no longer valid (entire candle below). Line price: %.5f, Prev high: %.5f.", type, name, line_price, prev_high); //--- Log break broken = true; //--- Set broken flag } else if (!trendlines[i].is_support && prev_low > line_price) { //--- Entire candle above resistance PrintFormat("%s trendline %s is no longer valid (entire candle above). Line price: %.5f, Prev low: %.5f.", type, name, line_price, prev_low); //--- Log break broken = true; //--- Set broken flag } } if (broken && EnableTradingSignals && !trendlines[i].is_signaled) { //--- Check for breakout signal bool signaled = false; //--- Initialize signaled flag string signal_type = ""; //--- Initialize signal type color signal_color = clrNONE; //--- Initialize signal color int arrow_code = 0; //--- Initialize arrow code int anchor = 0; //--- Initialize anchor double text_angle = 0.0; //--- Initialize text angle double text_offset = 0.0; //--- Initialize text offset double text_price = 0.0; //--- Initialize text price int text_anchor = 0; //--- Initialize text anchor if (trendlines[i].is_support) { //--- Support break: SELL signaled = true; //--- Set signaled flag signal_type = "SELL BREAK"; //--- Set sell break signal signal_color = clrRed; //--- Set red color arrow_code = 218; //--- Set down arrow anchor = ANCHOR_BOTTOM; //--- Set bottom anchor text_angle = 90.0; //--- Set vertical downward text_offset = 20 * pointValue; //--- Set text offset text_price = line_price + text_offset; //--- Calculate text price text_anchor = ANCHOR_BOTTOM; //--- Set bottom anchor double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Get bid price double SL = NormalizeDouble(line_price + inpSLPoints * _Point, _Digits); //--- SL above the line double risk = SL - Bid; //--- Calculate risk double TP = NormalizeDouble(Bid - risk * inpRRRatio, _Digits); //--- Calculate take profit obj_Trade.Sell(inpLot, _Symbol, Bid, SL, TP); //--- Execute sell trade } else { //--- Resistance break: BUY signaled = true; //--- Set signaled flag signal_type = "BUY BREAK"; //--- Set buy break signal signal_color = clrBlue; //--- Set blue color arrow_code = 217; //--- Set up arrow anchor = ANCHOR_TOP; //--- Set top anchor text_angle = -90.0; //--- Set vertical upward text_offset = -20 * pointValue; //--- Set text offset text_price = line_price + text_offset; //--- Calculate text price text_anchor = ANCHOR_LEFT; //--- Set left anchor double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Get ask price double SL = NormalizeDouble(line_price - inpSLPoints * _Point, _Digits); //--- SL below the line double risk = Ask - SL; //--- Calculate risk double TP = NormalizeDouble(Ask + risk * inpRRRatio, _Digits); //--- Calculate take profit obj_Trade.Buy(inpLot, _Symbol, Ask, SL, TP); //--- Execute buy trade } if (signaled) { //--- Check if signaled PrintFormat("Breakout signal generated for %s trendline %s: %s at price %.5f, time %s.", type, name, signal_type, line_price, TimeToString(current_time)); //--- Log signal string arrow_name = name + "_signal_arrow"; //--- Generate signal arrow name if (ObjectFind(0, arrow_name) < 0) { //--- Check if arrow exists ObjectCreate(0, arrow_name, OBJ_ARROW, 0, prev_bar_time, line_price); //--- Create signal arrow ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, arrow_code); //--- Set arrow code ObjectSetInteger(0, arrow_name, OBJPROP_ANCHOR, anchor); //--- Set anchor ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, signal_color); //--- Set color ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, arrow_name, OBJPROP_BACK, false); //--- Set to foreground } string text_name = name + "_signal_text"; //--- Generate signal text name if (ObjectFind(0, text_name) < 0) { //--- Check if text exists ObjectCreate(0, text_name, OBJ_TEXT, 0, prev_bar_time, text_price); //--- Create signal text ObjectSetString(0, text_name, OBJPROP_TEXT, " " + signal_type); //--- Set text content ObjectSetInteger(0, text_name, OBJPROP_COLOR, signal_color); //--- Set color ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 10); //--- Set font size ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, text_anchor); //--- Set anchor ObjectSetDouble(0, text_name, OBJPROP_ANGLE, text_angle); //--- Set angle ObjectSetInteger(0, text_name, OBJPROP_BACK, false); //--- Set to foreground } trendlines[i].is_signaled = true; //--- Set signaled flag } } if (broken) { //--- Remove if broken RemoveTrendlineFromStorage(i); //--- Remove trendline } } }
Um die Logik der Trendlinienaktualisierung und den Handel von Ausbrüchen zu implementieren, wird in der Funktion „UpdateTrendlines“ die Zeit des aktuellen Balkens mit iTime abgerufen und „pointValue“, „pen_tolerance“ („PenetrationTolerance * pointValue“) und „touch_tolerance“ („TouchTolerance * pointValue“) berechnet. Dann wird rückwärts durch die „trendlines[]“ iteriert, der „type“ (Unterstützung oder Widerstand) und der „name“ bestimmt und geprüft, ob die Trendlinie mit „current_time > end_time“ abgelaufen ist, mit PrintFormat protokolliert und mit „RemoveTrendlineFromStorage“ entfernt, wenn sie abgelaufen ist.
Als Nächstes berechnen wir den Preis der Trendlinie am vorherigen Balken („prev_bar_time“ aus „iTime“) unter Verwendung von „start_price + slope * (prev_bar_time – start_time)“ und prüfen auf Ausbrüche: für „BreakoutType“ als „BREAKOUT_CLOSE“ prüfen wir, ob der „prev_close“ (iClose) der Unterstützungstrendlinie unter „line_price“ oder der Widerstand darüber liegt, indem wir mit „PrintFormat“ protokollieren und „BREAKOUT_CANDLE“ prüft, ob das „prev_high“ (iHigh) der Unterstützung unter oder das „prev_low“ (iLow) des Widerstands über der „line_price“ liegt, protokolliert und als gebrochen gesetzt.
Wenn ein Ausbruch vorliegt und „EnableTradingSignals“ wahr und „is_signaled“ falsch ist, werden die Handelsparameter festgelegt: für Unterstützung (Verkauf), verwenden wir „signal_type“ als „SELL BREAK“, rote Farbe, Pfeil nach unten (218), und berechnen Bid (SymbolInfoDouble), Stop Loss („line_price + inpSLPoints * _Point“), Risiko und Take Profit mit „inpRRRatio“, Ausführung mit „obj_Trade.Sell“; für den Widerstand (Buy) verwenden wir „BUY BREAK“, blaue Farbe, Pfeil nach oben (217), und berechnen Ask, Stop Loss und Take Profit, Ausführung mit „obj_Trade.Buy“. Wir zeichnen dann einen Signalpfeil („OBJ_ARROW“) und einen Text („OBJ_TEXT“) mit „ObjectCreate“ und setzen Eigenschaften wie „OBJPROP_ARROWCODE“, „OBJPROP_ANCHOR“ und „OBJPROP_COLOR“, protokollieren das Signal mit „PrintFormat“, setzen „is_signaled“ auf true und entfernen durchbrochene Trendlinien aus dem Speicher. Die Wahl der zu verwendenden Pfeilcodes ist Ihnen überlassen. Hier ist eine Liste von Codes, die Sie aus den Codes der MQL5 definierten Wingdings verwenden können.

Wir können diese Funktionen nun in der Ereignishandhabung von OnTick aufrufen, damit das System tickbasiertes Feedback gibt.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (!IsNewBar()) return; //--- Exit if not new bar DetectSwings(); //--- Detect swings UpdateTrendlines(); //--- Update trendlines FindAndDrawTrendlines(true); //--- Find/draw support trendlines }
In der Funktion OnTick rufen wir zunächst „IsNewBar“ auf, um zu prüfen, ob ein neuer Balken vorhanden ist, und brechen ab, wenn dies nicht der Fall ist, um die Leistung zu optimieren. Wenn ein neuer Balken erkannt wird, rufen wir „DetectSwings“ auf, um hohe und tiefe Umkehrpunkte der Schwankungen zu ermitteln, gefolgt von „UpdateTrendlines“, um auf Ausbrüche oder abgelaufene Trendlinien zu prüfen und gegebenenfalls Handelsgeschäfte durchzuführen. Dann rufen wir „FindAndDrawTrendlines“ mit „true“ auf, um Unterstützungstrendlinien zu erkennen und zu zeichnen und sicherzustellen, dass nur gültige Trendlinien visualisiert werden. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Aus dem Bild können wir ersehen, dass wir die Trendlinie beim Ausbruch finden, analysieren, einzeichnen und handeln. Abgelaufene Linien werden ebenfalls erfolgreich aus dem Speicherarray entfernt. Das Gleiche können wir auch für Widerstandstrendlinien erreichen, indem wir die gleiche Funktion wie für Unterstützung aufrufen, aber den Eingabeparameter auf false setzen.
//--- other ontick functions FindAndDrawTrendlines(false); //--- Find/draw resistance trendlines //---
Nach Übergabe der Funktion und Kompilierung erhalten wir das folgende Ergebnis.

Aus dem Bild geht hervor, dass wir auch die Widerstandstrendlinien erkennen und handeln. Wenn wir alles testen und kombinieren, erhalten wir das folgende Ergebnis.

Aus dem Bild können wir ersehen, dass wir die Trendlinien erkennen, sie visualisieren und auf sie reagieren, wenn der Preis sie durchbricht, und somit unsere Ziele erreichen. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.
Backtests
Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.
Backtest-Grafik:

Bericht des Backtest:

Schlussfolgerung
Zusammenfassend haben wir eine Strategie des Trendlinien-Ausbruchs in MQL5 entwickelt, das Umkehrpunkte verwendet, um Support- und Resistance-Trendlinien mit einer guten R-Quadrat-Anpassung zu identifizieren und zu validieren und Ausbruchshandel mit anpassbaren Risikoparametern auszuführen. Das System verbessert Handelsentscheidungen mit dynamischen Visualisierungen, einschließlich Trendlinien, Berührungspunktpfeilen und Kennzeichnungen, die eine klare Marktanalyse gewährleisten.
Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Volatilität der Märkte kann zu Verlusten führen. Gründliche Backtests und sorgfältiges Risikomanagement sind entscheidend, bevor Sie dieses Programm auf den Live-Märkten einsetzen.
Durch die Implementierung dieser Trendlinien-Ausbruchsstrategie sind Sie für die Erfassung von Marktbewegungen gerüstet und können Ihre Handelsreise weiter anpassen. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19625
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.
Entwicklung des Price Action Analysis Toolkit (Teil 43): Wahrscheinlichkeit und Ausbrüche von Kerzen
Aufbau eines Handelssystems (Teil 5): Verwaltung von Gewinnen durch strukturierte Handelsausstiege
Automatisieren von Handelsstrategien in MQL5 (Teil 35): Erstellung eines Blockausbruch-Handelssystems
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 81): Verwendung von Ichimoku-Mustern und des ADX-Wilder mit Beta-VAE-Inferenzlernen
- 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.
Great danke für die gemeinsame Nutzung Ich schätze Sie wirklich teilen (alle Ihre Codes), Robust und gut markiert Code, Ausgezeichnete Vorlage zu bauen aus! Etwas, das ich versucht habe, selbst zu erstellen, aber auf jeden Fall nicht so gut zusammengesetzt wie diese