English 日本語
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 34): Trendline Breakout System mit R-Squared Goodness of Fit

Automatisieren von Handelsstrategien in MQL5 (Teil 34): Trendline Breakout System mit R-Squared Goodness of Fit

MetaTrader 5Handel |
58 2
Allan Munene Mutiiria
Allan Munene Mutiiria

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:

  1. Das Verständnis der Strategie des Trendlinien-Ausbruchs
  2. Implementation in MQL5
  3. Backtests
  4. 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.

MUSTER FÜR EINEN TRENDLINIENAUSBRUCH

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.

R-QUADRAT-MODELL

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.

TRENDLINIEN-SYSTEM


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 &times[], 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 &times[], 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.

MQL5 WINGDINGS

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:

BESTÄTIGTES SIGNAL FÜR DEN AUSBRUCH AUS DER UNTERSTÜTZUNG

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.

BESTÄTIGTES SIGNAL FÜR DEN DURCHBRUCH DES WIDERSTANDS

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

KOMBINIERTES 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:

GRAPH

Bericht des Backtest:

BERICHT


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

Letzte Kommentare | Zur Diskussion im Händlerforum (2)
linfo2
linfo2 | 1 Okt. 2025 in 16:27
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 definitiv nicht so gut zusammengesetzt wie diese
Allan Munene Mutiiria
Allan Munene Mutiiria | 1 Okt. 2025 in 22:05
linfo2 #:
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
Danke, dass Sie es hilfreich finden. Sehr willkommen.
Entwicklung des Price Action Analysis Toolkit (Teil 43): Wahrscheinlichkeit und Ausbrüche von Kerzen Entwicklung des Price Action Analysis Toolkit (Teil 43): Wahrscheinlichkeit und Ausbrüche von Kerzen
Verbessern Sie Ihre Marktanalyse mit dem Candlestick Probability EA in MQL5, einem leichtgewichtigen Tool, das rohe Preisbalken in Echtzeit in instrumentenspezifische Wahrscheinlichkeiten umwandelt. Es klassifiziert Pinbars, Engulfing und Doji-Muster, wenn der Balken schließt, verwendet ATR-fähige Filterung und optionale Ausbruchsbestätigung. Der EA berechnet rohe und volumengewichtete Follow-Through-Prozentsätze, die Ihnen helfen, das typische Ergebnis jedes Musters für bestimmte Symbole und Zeitrahmen zu verstehen. Markierungen auf dem Chart, ein kompaktes Dashboard und interaktive Kippschalter ermöglichen eine einfache Validierung und Fokussierung. Exportieren Sie detaillierte CSV-Protokolle für Offline-Tests. Nutzen Sie es, um Wahrscheinlichkeitsprofile zu entwickeln, Strategien zu optimieren und Mustererkennung in einen messbaren Vorteil zu verwandeln.
Aufbau eines Handelssystems (Teil 5): Verwaltung von Gewinnen durch strukturierte Handelsausstiege Aufbau eines Handelssystems (Teil 5): Verwaltung von Gewinnen durch strukturierte Handelsausstiege
Für viele Händler ist es ein vertrauter Schmerzpunkt: zu sehen, wie ein Handel bis auf einen Hauch an Ihr Gewinnziel herankommt, nur um dann umzukehren und ihren Stop-Loss zu treffen. Oder noch schlimmer: Sie sehen, dass ein Trailing-Stop Sie an der Gewinnschwelle stoppt, bevor der Markt auf Ihr ursprüngliches Ziel zusteuert. Dieser Artikel befasst sich mit dem Einsatz mehrerer Einstiege zu unterschiedlichen Rendite-Risiko-Verhältnissen, um systematisch Gewinne zu sichern und das Gesamtrisiko zu reduzieren.
Automatisieren von Handelsstrategien in MQL5 (Teil 35): Erstellung eines Blockausbruch-Handelssystems Automatisieren von Handelsstrategien in MQL5 (Teil 35): Erstellung eines Blockausbruch-Handelssystems
In diesem Artikel erstellen wir ein Block-Ausbruchssytems in MQL5, das Konsolidierungsbereiche identifiziert, Ausbrüche erkennt und Ausbruchsblöcke mit Umkehrpunkten validiert, um Retests mit definierten Risikoparametern zu handeln. Das System visualisiert Auftrags- und Ausbruchsblöcke mit dynamischen Kennzeichnungen und Pfeilen und unterstützt den automatisierten Handel und Trailing Stops.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 81):  Verwendung von Ichimoku-Mustern und des ADX-Wilder mit Beta-VAE-Inferenzlernen MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 81): Verwendung von Ichimoku-Mustern und des ADX-Wilder mit Beta-VAE-Inferenzlernen
Dieser Beitrag schließt an Teil 80 an, in dem wir die Paarung von Ichimoku und ADX im Rahmen eines Reinforcement Learning untersucht haben. Wir wenden uns nun dem Inferenzlernen zu. Ichimoku und ADX ergänzen sich, wie bereits erwähnt, jedoch werden wir die Schlussfolgerungen des letzten Artikels in Bezug auf die Verwendung von Pipelines wieder aufgreifen. Für unser Inferenzlernen verwenden wir den Beta-Algorithmus eines Variational Auto Encoders. Wir bleiben auch bei der Implementierung einer nutzerdefinierten Signalklasse für die Integration mit dem MQL5-Assistenten.