English
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 25): Trendlinien-Händler mit der Anpassung der kleinsten Quadrate und dynamischer Signalgenerierung

Automatisieren von Handelsstrategien in MQL5 (Teil 25): Trendlinien-Händler mit der Anpassung der kleinsten Quadrate und dynamischer Signalgenerierung

MetaTrader 5Handel |
33 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem letzten Artikel (Teil 24) haben wir das System des London Session Breakout in MetaQuotes Language 5 (MQL5) entwickelt, das Pre-London-Ranges verwendet, um schwebende Aufträge mit Risikomanagement und Trailing Stops zu platzieren und so einen effektiven Session-basierten Handel zu ermöglichen. In Teil 25 erstellen wir ein Trendlinien-Handelsprogramm, das eine Anpassung nach der Methode der kleinsten Quadrate verwendet, um Unterstützungs- und Widerstandstrendlinien zu erkennen und automatische Kauf- und Verkaufssignale zu generieren, wenn die Kurse diese Linien berühren, ergänzt durch visuelle Indikatoren wie Pfeile und anpassbare Handelsparameter. Wir werden die folgenden Themen behandeln:

  1. Entwurf des Trendlinien-Handelssystems
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende werden Sie eine leistungsstarke MQL5-Strategie für den trendbasierten Handel haben, die Sie anpassen können – legen wir los!


Entwurf des Trendlinien-Handelssystems

Das Trendlinien-Handelssystem ist eine Handelsstrategie, bei der diagonale Linien auf Preisdiagrammen gezeichnet werden, um hohe (Widerstände) oder tiefe (Unterstützung) Umkehrpunkte zu verbinden, was Händlern hilft, den vorherrschenden Trend zu erkennen. Händler kaufen in der Nähe von steigenden Trendlinien (Unterstützung) in einem Aufwärtstrend oder verkaufen in der Nähe von fallenden Trendlinien (Widerstand) in einem Abwärtstrend, in der Erwartung, dass der Kurs abprallt. Ein Bruch der Trendlinie signalisiert häufig eine potenzielle Trendumkehr oder eine Trendabschwächung, was Händler dazu veranlasst, ihre Positionen aufzugeben oder umzukehren. Hier ist eine Illustration einer fallenden Trendlinie.

ABWÄRTSTRENDLINIE

Wir werden nun ein Trendlinien-Handelsprogramm entwickeln, um den Handel zu automatisieren, indem wir Unterstützungs- und Widerstandstrendlinien mit Hilfe einer Methode der kleinsten Quadrate erkennen, die präzise Kauf- und Verkaufssignale ermöglicht, wenn die Kurse diese Linien berühren.

Falls Sie es noch nicht wissen: Die Methode der kleinsten Quadrate ist ein statistisches Verfahren zur Bestimmung einer Linie (oder Kurve), die am besten zu einer Reihe von Datenpunkten passt, indem die Summe der Quadrate der vertikalen Abweichungen (Fehler) zwischen den Datenpunkten und der angepassten Linie minimiert wird. Sie ist für uns wichtig, weil sie die genaueste lineare Annäherung an die Beziehung zwischen den Schwingungspunkten liefert, was für die Vorhersage, Trendanalyse und Datenmodellierung in unserem Fachgebiet unerlässlich ist. Sehen Sie sich unten die statistische Logik an.

METHODE DER KLEINSTEN QUADRATE ZUR ANPASSUNG

Wir planen, die mathematische Erkennung von Trendlinien mit visuellem Feedback und konfigurierbaren Handelsparametern zu kombinieren, um Trendumschwünge in dynamischen Märkten effizient nutzen zu können. Wir beabsichtigen, Umkehrpunkte zu identifizieren, Trendlinien mit ausreichenden Berührungen (mindestens 3 Berührungen) zu versehen, ihre Integrität zu überprüfen und Trades mit Risikomanagement auszulösen, während wir gleichzeitig Trendlinien und Berührungspunkte zur besseren Übersichtlichkeit auf dem Chart anzeigen. 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.
//+------------------------------------------------------------------+
//|                                       a. Trendline 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 description "Trendline Trader using mean Least Squares Fit"
#property version     "1.00"
#property strict

#include <Trade\Trade.mqh>                         //--- Include Trade library for trading operations
CTrade obj_Trade;                                  //--- Instantiate trade object

//+------------------------------------------------------------------+
//| 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
void LeastSquaresFit(const datetime &times[], const double &prices[], int n, double &slope, double &intercept); //--- Declare least squares fit function

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
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 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 Einrichtung der Kernkomponenten für das Programm zur Automatisierung des Handels auf der Grundlage von Trendlinienberührungen. Zunächst binden wir die Bibliothek „<Trade.mqh>“ ein und instanziieren das Objekt „obj_Trade“ als „CTrade“, um Handelsoperationen wie die Ausführung von Kauf- und Verkaufsaufträgen zu verwalten. Anschließend werden wir drei Strukturen definieren: „Swing“ mit „time“ (datetime) und „price“ (double) zur Erfassung von Umkehrpunkten; „StartingPoint“ mit „time“ (datetime), „price“ (double), und „is_support“ (bool), um verwendete Startpunkte für Unterstützung oder Widerstand zu erfassen; und „TrendlineInfo“ mit „name“ (string), „start_time“ und „end_time“ (datetimes), „start_price“ und „end_price“, „slope“ (double), „is_support“ (bool), „touch_count“ (int), „creation_time“ (datetime), „touch_indices“ (int array) und „is_signaled“ (bool) zum Speichern von Trendliniendetails.

Als Nächstes werden Funktionen zur Bewältigung wichtiger Aufgaben deklariert: „DetectSwings“ zum Erkennen von Umkehrpunkten, „SortSwings“ zum Ordnen der Umkehrpunkten, „CalculateAngle“ zum Berechnen der Trendliniensteigung, „ValidateTrendline“ zum Sicherstellen der Gültigkeit von Trendlinien, „FindAndDrawTrendlines“ zum Erstellen und Zeichnen von Trendlinien, „UpdateTrendlines“ zu deren Pflege, „RemoveTrendlineFromStorage“ zur Bereinigung, „IsStartingPointUsed“ zur Überprüfung der Punktverwendung und „LeastSquaresFit“ zur Berechnung von Steigung und Achsenabschnitt unter Verwendung der Methode der kleinsten Quadrate.

Zuletzt konfigurieren wir die Eingabeparameter und globale Variablen: Eingaben wie „LookbackBars“ (200) für den Bereich der Schwungerkennung, „TouchTolerance“ (10.0 Punkte) für die Berührungsgenauigkeit, „MinTouches“ (3) für die Gültigkeit, und der Rest ist selbsterklärend; und globale Variablen wie „swingLows“ und „swingHighs“ Arrays mit „numLows“ und „numHighs“ (0) für Umkehrpunkte, und „trendlines“ und „startingPoints“ Arrays mit „numTrendlines“ und „numStartingPoints“ (0) für die Speicherung von Trendlinien und Punkten. Dieses strukturierte Setup bildet die Grundlage des EA, um Trendlinien zu erkennen und effektiv zu handeln. 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 sicherzustellen, bereiten wir den EA in OnInit vor, indem wir die Größe des Arrays „trendlines“ mit ArrayResize auf 0 ändern und „numTrendlines“ auf 0 setzen, um alle vorhandenen Trendliniendaten zu löschen. Anschließend wird die Größe des Arrays „startingPoints“ auf 0 geändert und „numStartingPoints“ auf 0 gesetzt, um die Startpunktdatensätze zurückzusetzen. Schließlich wird „INIT_SUCCEEDED“ zurückgegeben, um die erfolgreiche Initialisierung zu bestätigen.

Dann, in der Funktion OnDeinit, tun wir das Gleiche und stellen sicher, dass kein Speicherleck entsteht, wenn das Programm entfernt wird, und schaffen einen Neuanfang für den Betrieb des EA und eine angemessene Ressourcenverwaltung. 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
}

Hier implementieren wir Schlüsselfunktionen zur Erkennung von Balken und Umkehrpunkten und legen damit den Grundstein für die Trendlinienanalyse. Zunächst erstellen wir die Funktion „IsNewBar“, die auf einen neuen Balken prüft, indem sie „lastTime“ statisch als 0 speichert, sie mit „currentTime“ aus iTime für das aktuelle Symbol und die Periode bei Shift 0 für den aktuellen Balken vergleicht, „lastTime“ aktualisiert, wenn sie unterschiedlich ist, und „true“ für einen neuen Balken oder andernfalls „false“ zurückgibt. Anschließend implementieren wir die Funktion „SortSwings“, die das Array „Swings“ nach „Zeit“ in aufsteigender Reihenfolge (die älteste zuerst) mit einem Bubble-Sort-Algorithmus sortiert, indem sie durch „count – 1“ Elemente iteriert und benachbarte „Swing“ -Strukturen mit einer temporären „temp“-Struktur vertauscht, wenn ihre Zeiten nicht in der richtigen Reihenfolge sind.

Zuletzt implementieren wir die Funktion „DetectSwings“, indem wir „numLows“ und „numHighs“ auf 0 zurücksetzen und die Arrays „swingLows“ und „swingHighs“ auf 0 verkleinern, und berechnen „effectiveLookback“ als das Minimum von „LookbackBars“ und der Gesamtzahl der Balken aus iBars. Wir beenden mit einem Print, wenn weniger als 5 Balken verfügbar sind, und Iteration durch die Balken von 2 bis „effectiveLookback – 2“ um hohe und tiefe Umkehrpunkte zu identifizieren, indem die „iLow“- und „iHigh“-Werte mit zwei vorhergehenden und nachfolgenden Bars verglichen werden, wobei die „Swing“-Strukturen mit „time“ aus „iTime“ und „price“ aus „iLow“ oder iHigh zu „swingLows“ oder „swingHighs“ mit ArrayResize hinzugefügt werden, wobei der Zähler inkrementiert wird und die Arrays mit „SortSwings“ sortiert werden, wenn diese nicht leer sind. Dadurch wird die rechtzeitige Erkennung von Umkehrpunkten für eine genaue Trendlinienkonstruktion gewährleistet. 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
}

Wir implementieren wichtige Funktionen zur Berechnung von Trendlinienwinkeln und zur Überprüfung ihrer Integrität, um eine zuverlässige Erkennung von Trendlinien zu gewährleisten. Zunächst erstellen wir die Funktion „CalculateAngle“, die zwei Punkte („time1“, „price1“ und „time2“, „price2“) mit Hilfe der Funktion ChartTimePriceToXY in die Chartkoordinaten „x1“, „y1“, „x2“, „y2“ konvertiert und 0 zurückgibt.0 zurück, wenn die Konvertierung fehlschlägt, berechnet dann die x-Differenz „dx“ und die y-Differenz „dy“, behandelt vertikale Linien, indem es -90.0 oder 90.0 zurückgibt, wenn „dx“ Null ist, und berechnet den Winkel in Grad mit „MathArctan(-dy / dx) * 180.0 / M_PI“ für die Visualisierung der Steigung.

Anschließend implementieren wir die Funktion „ValidateTrendline“, die eine Trendlinie validiert, indem sie den Index des Startbalkens mit iBarShift für „start_time“ ermittelt, false zurückgibt, wenn er ungültig ist, und von „bar_start“ bis 0 iteriert und den Trendlinienpreis zu jeder „bar_time“ mit „ref_price + slope * dk“ berechnet, wobei „dk“ die Zeitdifferenz zur Referenzzeit ist. Bei Unterstützungs-Trendlinien („isSupport“ = true) prüfen wir, ob der iLow des Balkens unter „line_price – tolerance_pen“ fällt und geben false zurück, wenn er durchbrochen wurde; bei Widerstand prüfen wir, ob iHigh „line_price + tolerance_pen“ übersteigt und geben false zurück, wenn er durchbrochen wurde, und geben true zurück, wenn die Trendlinie hält. Wir können uns nun auf die Funktion für die Berechnungslogik der kleinste Quadrate konzentrieren. Wir werden es einfach halten.

//+------------------------------------------------------------------+
//| Perform least-squares fit for slope and intercept                |
//+------------------------------------------------------------------+
void LeastSquaresFit(const datetime &times[], const double &prices[], int n, double &slope, double &intercept) {
   double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0; //--- Initialize sums
   for (int k = 0; k < n; k++) {                        //--- Iterate through points
      double x = (double)times[k];                      //--- Convert time to x
      double y = prices[k];                             //--- Set price as y
      sum_x += x;                                       //--- Accumulate x
      sum_y += y;                                       //--- Accumulate y
      sum_xy += x * y;                                  //--- Accumulate x*y
      sum_x2 += x * x;                                  //--- Accumulate x^2
   }
   slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x); //--- Calculate slope
   intercept = (sum_y - slope * sum_x) / n;             //--- Calculate intercept
}

Wir implementieren die Funktion „LeastSquaresFit“, um die optimale Steigung und den optimalen Achsenabschnitt für Trendlinien zu berechnen, was eine präzise Anpassung der Trendlinien ermöglicht. Zunächst werden die Variablen „sum_x“, „sum_y“, „sum_xy“ und „sum_x2“ auf 0 gesetzt, um die Werte für die Berechnung der kleinsten Quadrate zu akkumulieren. Dann wird durch „n“ Punkte in den Arrays „times“ und „prices“ iteriert, wobei jeder „times[k]“ als „x“ in einen Double konvertiert wird und „Preise[k]“ als „y“, addieren „x“ zu „sum_x“, „y“ zu „sum_y“, „x * y“ zu „sum_xy“ und „x * x“ zu „sum_x2“. Zuletzt berechnen wir die „Steigung“ mit der Formel „(n * sum_xy – sum_x * sum_y) / (n * sum_x2 – sum_x * sum_x)“ und den „Achsenabschnitt“ als „(sum_y – Steigung * sum_x) / n“, was die beste Anpassungslinie für die Trendlinie auf der Grundlage der Eingabepunkte ergibt. Falls Sie sich über die Formel wundern, sehen Sie unten nach.

METHODE DER KLEINSTEN QUADRATE ZUR ANPASSUNG

Dies gewährleistet eine mathematisch korrekte Platzierung der Trendlinien für zuverlässige Handelssignale. 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
}

Wir fahren fort mit der Implementierung von Dienstfunktionen zur Verwaltung von Trendlinien-Startpunkten und Bereinigungen, um eine effiziente Trendlinienverfolgung und Diagrammverwaltung zu gewährleisten. Zunächst erstellen wir die Funktion „IsStartingPointUsed“, die durch „numStartingPoints“ im Array „startingPoints“ iteriert und prüft, ob ein gegebener „time“, „price“ und „is_support“ mit einem vorhandenen Startpunkt übereinstimmt, indem sie „time“ genau, „price“ innerhalb von „TouchTolerance * _Point“ mit MathAbs und „is_support“ vergleicht und true zurückgibt, wenn sie gefunden wurde, oder false, wenn nicht. Anschließend wird die Funktion „RemoveTrendlineFromStorage“ implementiert, die die Eingabe „index“ mit „numTrendlines“ vergleicht und bei Ungültigkeit den Vorgang abbricht und die Entfernung protokolliert.

Wenn „DeleteExpiredObjects“ wahr ist, löschen wir das Trendlinien-Objekt mit ObjectDelete für „trendlines[index].name“, durchlaufen die Schleife „touch_count“, um Berührungspfeile und Beschriftungen mit Namen wie „trendlines[index].name + '_touch' + IntegerToString(m)“ und „trendlines[index].name + '_point_label' + IntegerToString(m)“, und entfernen die Trendlinienbeschriftung, den Signalpfeil und den Signaltext mit „label_name“, „signal_arrow“ und „signal_text“. Zuletzt verschieben wir das Array „trendlines“ von „index“ auf „numTrendlines – 1“, um den Eintrag zu entfernen, die Größe von „trendlines“ mit ArrayResize zu ändern und ihre Anzahl zu verringern, um doppelte Trendlinien zu vermeiden und abgelaufene oder unterbrochene Trendlinien effektiv zu bereinigen. 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
   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, intercept;                //--- Declare slope and intercept
               LeastSquaresFit(touch_times, touch_prices, touches, slope, intercept); //--- Perform least squares fit
               int adjusted_touch_indices[];           //--- Initialize adjusted indices
               ArrayResize(adjusted_touch_indices, 0); //--- Resize adjusted indices
               int adjusted_touches = 0;               //--- Initialize adjusted touches count
               for (int k = 0; k < numSwings; k++) {   //--- Iterate through swings
                  double expected = intercept + slope * (double)swings[k].time; //--- Calculate expected price
                  double actual = swings[k].price;     //--- Get actual price
                  if (MathAbs(expected - actual) <= touch_tolerance) { //--- Check touch
                     ArrayResize(adjusted_touch_indices, adjusted_touches + 1); //--- Add index
                     adjusted_touch_indices[adjusted_touches] = k; //--- Set index
                     adjusted_touches++;               //--- Increment 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 && j > best_j)) { //--- Check better trendline
                           max_touches = adjusted_touches; //--- Update max touches
                           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 = swings[touch_indices[0]].price; //--- Set start price check
   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 Funktion „FindAndDrawTrendlines“, um Trendlinien zu identifizieren und zu zeichnen, wobei nur eine aktive Trendlinie pro Typ mit optimalen Berührungspunkten gewährleistet wird. Zunächst prüfen wir, ob eine Trendlinie vorhanden ist, indem wir durch „numTrendlines“ in „trendlines“ iterieren, „has_active“ auf true setzen, wenn „is_support“ mit der Eingabe übereinstimmt, und den Vorgang beenden, wenn sie gefunden wurde. Dann fahren wir damit fort, uns auf der Grundlage von „isSupport“ auf Unterstützung oder Widerstand einzustellen: Für Unterstützung kopieren wir „numLows“ zu „numSwings“, füllen „swings“ aus „swingLows“ aus, setzen „lineColor“ auf „SupportLineColor“ und „prefix“ auf „Trendline_Support_“; für den Widerstand verwenden wir „numHighs“, „swingHighs“, „ResistanceLineColor“ und „Trendline_Resistance_“, wobei wir aufhören, wenn „numSwings“ kleiner als 2 ist. Als Nächstes berechnen wir „touch_tolerance“ und „pen_tolerance“ unter Verwendung von „TouchTolerance“ und „PenetrationTolerance“ mit _Point und iterieren durch „numSwings“-Paare, um eine anfängliche „initial_slope“ zu berechnen, wobei wir „touch_indices“ für Punkte innerhalb der Berührungstoleranz sammeln.

Wenn die Berührungen „MinTouches“ erfüllen und „MinBarSpacing“ über iBarShift passieren, verwenden wir „LeastSquaresFit“, um „slope“ (Steigung) und „intercept“ (Y-Achsenabschnitt) zu erhalten, überprüfen erneut die Berührungen und validieren mit „ValidateTrendline“ und „CalculateAngle“ gegen „MinAngle“ und „MaxAngle“ und aktualisieren „best_j“, „max_touches“, „best_slope“, „best_intercept“, „best_min_time“ und „best_touch_indices“ für die beste Trendlinie. Zuletzt, wenn „max_touches“ mit „MinTouches“ übereinstimmt und der Startpunkt über „IsStartingPointUsed“ ungenutzt ist, erstellen wir eine Trendlinie mit der Funktion ObjectCreate als OBJ_TREND mit „unique_name“, zeichnen Berührungspfeile und Beschriftungen, wenn „DrawTouchArrows“ und „DrawLabels“ wahr sind, speichern Details in „trendlines“, fügen den Startpunkt zu den Startpunkten hinzu und protokollieren, um eine präzise Trendlinienerstellung zu gewährleisten. 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_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 (trendlines[i].is_support && prev_low < line_price - pen_tolerance) { //--- Check support break
         PrintFormat("%s trendline %s is no longer valid (broken by price). Line price: %.5f, Prev low: %.5f, Penetration: %.5f points.", type, name, line_price, prev_low, PenetrationTolerance); //--- Log break
         RemoveTrendlineFromStorage(i);           //--- Remove trendline
         broken = true;                           //--- Set broken flag
      } else if (!trendlines[i].is_support && prev_high > line_price + pen_tolerance) { //--- Check resistance break
         PrintFormat("%s trendline %s is no longer valid (broken by price). Line price: %.5f, Prev high: %.5f, Penetration: %.5f points.", type, name, line_price, prev_high, PenetrationTolerance); //--- Log break
         RemoveTrendlineFromStorage(i);           //--- Remove trendline
         broken = true;                           //--- Set broken flag
      }
      if (!broken && !trendlines[i].is_signaled && EnableTradingSignals) { //--- Check for trading signal
         bool touched = false;                    //--- Initialize touched 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 && MathAbs(prev_low - line_price) <= touch_tolerance) { //--- Check support touch
            touched = true;                       //--- Set touched flag
            signal_type = "BUY";                  //--- Set buy signal
            signal_color = clrBlue;               //--- Set blue color
            arrow_code = 217;                     //--- Set up arrow for support (BUY)
            anchor = ANCHOR_TOP;                  //--- Set top anchor
            text_angle = -90.0;                   //--- Set vertical upward for BUY
            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(Ask - inpSLPoints * _Point, _Digits); //--- Calculate stop loss
            double TP = NormalizeDouble(Ask + (inpSLPoints * inpRRRatio) * _Point, _Digits); //--- Calculate take profit
            obj_Trade.Buy(inpLot, _Symbol, Ask, SL, TP); //--- Execute buy trade
         } else if (!trendlines[i].is_support && MathAbs(prev_high - line_price) <= touch_tolerance) { //--- Check resistance touch
            touched = true;                       //--- Set touched flag
            signal_type = "SELL";                 //--- Set sell signal
            signal_color = clrRed;                //--- Set red color
            arrow_code = 218;                     //--- Set down arrow for resistance (SELL)
            anchor = ANCHOR_BOTTOM;               //--- Set bottom anchor
            text_angle = 90.0;                    //--- Set vertical downward for SELL
            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(Bid + inpSLPoints * _Point, _Digits); //--- Calculate stop loss
            double TP = NormalizeDouble(Bid - (inpSLPoints * inpRRRatio) * _Point, _Digits); //--- Calculate take profit
            obj_Trade.Sell(inpLot, _Symbol, Bid, SL, TP); //--- Execute sell trade
         }
         if (touched) {                           //--- Check if touched
            PrintFormat("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
         }
      }
   }
}

Um sicherzustellen, dass aktive Trendlinien überwacht werden und auf sie reagiert wird, erstellen wir die Funktion „UpdateTrendlines“, die leer ist, da wir nichts zurückgeben müssen. Zunächst wird „current_time“ mit Hilfe von iTime für den aktuellen Balken ermittelt und „pointValue“ als _Point, „pen_tolerance“ als „PenetrationTolerance * pointValue“ und „touch_tolerance“ als „TouchTolerance * pointValue“ berechnet. Dann wird rückwärts durch „numTrendlines“ im Array „trendlines“ iteriert, wobei der „type“ der Trendlinie als „Support“ (Unterstützung) oder „Resistance“ (Widerstand) anhand von „is_support“, und prüfen, ob „current_time“ die „end_time“ überschreitet, protokollieren den Ablauf mit PrintFormat und entfernen die Trendlinie mit „RemoveTrendlineFromStorage“, wenn sie abgelaufen ist.

Als Nächstes berechnen wir für nicht abgelaufene Trendlinien den Trendlinienpreis zur „prev_bar_time“ (aus iTime bei shift 1) mit „start_price + slope * dk“, prüfen, ob die Trendlinie gebrochen ist, indem wir „prev_low“ oder „prev_high“ gegen „line_price“ mit „pen_tolerance“, protokollieren Brüche mit „PrintFormat“ und entfernen sie mit „RemoveTrendlineFromStorage“, wenn sie gebrochen sind.

Schließlich, wenn nicht gebrochen und „is_signaled“ ist falsch mit „EnableTradingSignals“ true, wir prüfen auf Berührungen: für die Unterstützung, wenn „prev_low“ ist innerhalb „touch_tolerance“ von „line_price“, wir setzen ein Kaufsignal, kaufen mit „obj_Trade.Buy“ unter Verwendung von „inpLot“, „Ask“, „SL“ und „TP“, berechnet mit „inpSLPoints“ und „inpRRRatio“und zeichnen einen blauen Aufwärtspfeil (Code 217) und einen Text; für den Widerstand, wenn „prev_high“ innerhalb der Toleranz liegt, setzen wir ein Verkaufssignal, verkaufen mit „obj_Trade.Sell“ und zeichnen einen roten Abwärtspfeil (Code 218) und Text, protokollieren mit „PrintFormat“, erstellen Objekte mit ObjectCreate, setzen Eigenschaften mit ObjectSetInteger und ObjectSetString und markieren „is_signaled“ true, um sicherzustellen, dass Trendlinien aktualisiert werden und genaue Handelssignale erzeugen. Die Wahl der zu verwendenden Pfeilcodes ist Ihnen überlassen. Hier ist eine Liste von Codes, die Sie aus den von MQL5 definierten Wingdings-Codes verwenden können.

MQL5 WINGDINGS

Wir können diese Funktionen nun in 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 OnTick werden die Erkennung von Trendlinien und der Handel mit jeder neuen Balkenlogik orchestriert. Zunächst prüfen wir, ob sich ein neuer Balken gebildet hat, indem wir „IsNewBar“ aufrufen und bei „false“ sofort abbrechen, um eine redundante Verarbeitung zu vermeiden. Dann rufen wir „DetectSwings“ auf, um die in „swingHighs“ und „swingLows“ gespeicherten Höchst- und Tiefststände zu ermitteln und zu aktualisieren. Als Nächstes rufen wir „UpdateTrendlines“ auf, um bestehende Trendlinien zu validieren, abgelaufene oder durchbrochene Trendlinien zu entfernen und Handelssignale zu generieren, wenn Kursberührungen innerhalb einer definierten Berührungstoleranz erkannt werden. Zuletzt rufen wir die Funktion „FindAndDrawTrendlines“ mit dem Parameter true auf, um Unterstützungstrendlinien zu erstellen und sicherzustellen, dass neue Trendlinien nur dann gezeichnet werden, wenn keine aktive Trendlinie des Unterstützungstyps existiert. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

BESTÄTIGTE UNTERSTÜTZUNGSTRENDLINIE

Aus dem Bild können wir ersehen, dass wir die Trendlinie bei Berührung 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.

WIDERSTANDSTRENDLINIE

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 berührt, 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:

GRAFIK

Bericht des Backtest:

BERICHT


Schlussfolgerung

Abschließend haben wir ein Programm für Handelsstrategie von Trendlinien in MQL5 entwickelt, das die Methode der kleinsten Quadrate verwendet, um robuste Unterstützungs- und Widerstandstrendlinien zu erkennen und automatische Kauf- und Verkaufssignale mit visuellen Hilfsmitteln wie Pfeilen und Kennzeichnungen zu erzeugen. Durch modulare Komponenten wie die Struktur „TrendlineInfo“ und Funktionen wie „FindAndDrawTrendlines“ bietet es einen disziplinierten Ansatz für den trendbasierten Handel, den Sie durch Anpassung seiner Parameter verfeinern können.

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.

Indem Sie die vorgestellten Konzepte und Implementierungen nutzen, können Sie dieses Trendliniensystem an Ihren Handelsstil anpassen und Ihre algorithmischen Strategien verbessern. Viel Spaß beim Handeln!

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

Beigefügte Dateien |
Entwicklung des Price Action Analysis Toolkit (Teil 36): Direkter Python-Zugang zu MetaTrader 5 Market Streams freischalten Entwicklung des Price Action Analysis Toolkit (Teil 36): Direkter Python-Zugang zu MetaTrader 5 Market Streams freischalten
Schöpfen Sie das volle Potenzial Ihres MetaTrader 5 Terminals aus, indem Sie das datenwissenschaftliche Ökosystem von Python und die offizielle MetaTrader 5 Client-Bibliothek nutzen. Dieser Artikel zeigt, wie man Live-Tick- und Minutenbalken-Daten direkt in den Parquet-Speicher authentifiziert und streamt, mit Ta und Prophet ein ausgefeiltes Feature-Engineering durchführt und ein zeitabhängiges Gradient-Boosting-Modell trainiert. Anschließend setzen wir einen leichtgewichtigen Flask-Dienst ein, um Handelssignale in Echtzeit zu liefern. Egal, ob Sie ein hybrides Quant-Framework aufbauen oder Ihren EA mit maschinellem Lernen erweitern, Sie erhalten eine robuste Ende-zu-Ende-Pipeline für den datengesteuerten algorithmischen Handel an die Hand.
CRUD-Operationen in Firebase mit MQL CRUD-Operationen in Firebase mit MQL
Dieser Artikel bietet eine Schritt-für-Schritt-Anleitung zur Beherrschung von CRUD-Operationen (Create, Read, Update, Delete) in Firebase, wobei der Schwerpunkt auf der Echtzeitdatenbank und dem Firestore liegt. Entdecken Sie, wie Sie die SDK-Methoden von Firebase nutzen können, um Daten in Web- und Mobilanwendungen effizient zu verwalten, vom Hinzufügen neuer Datensätze bis zum Abfragen, Ändern und Löschen von Einträgen. Lernen Sie praktische Code-Beispiele und Best Practices für die Strukturierung und Verarbeitung von Daten in Echtzeit kennen, die es Entwicklern ermöglichen, dynamische, skalierbare Anwendungen mit der flexiblen NoSQL-Architektur von Firebase zu erstellen.
MetaTrader Tick-Info-Zugang von MQL5-Diensten zur Python-Anwendung über Sockets MetaTrader Tick-Info-Zugang von MQL5-Diensten zur Python-Anwendung über Sockets
Manchmal ist nicht alles in der MQL5-Sprache programmierbar. Und selbst wenn es möglich wäre, bestehende fortgeschrittene Bibliotheken in MQL5 zu konvertieren, wäre dies sehr zeitaufwändig. Dieser Artikel versucht zu zeigen, dass wir die Abhängigkeit vom Windows-Betriebssystem umgehen können, indem wir Tick-Informationen wie Bid, Ask und Time mit MetaTrader-Diensten über Sockets an eine Python-Anwendung übertragen.
Einführung in MQL5 (Teil 20): Einführung in „Harmonic Patterns“ Einführung in MQL5 (Teil 20): Einführung in „Harmonic Patterns“
In diesem Artikel befassen wir uns mit den Grundlagen der harmonischen Muster, ihren Strukturen und ihrer Anwendung im Handel. Sie lernen etwas über Fibonacci-Retracements, Extensions und wie man die Erkennung harmonischer Muster in MQL5 implementiert, was die Grundlage für den Aufbau fortgeschrittener Handelswerkzeuge und Expert Advisors bildet.