Automatisieren von Handelsstrategien in MQL5 (Teil 25): Trendlinien-Händler mit der Anpassung der kleinsten Quadrate und dynamischer Signalgenerierung
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:
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.

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.

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.

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

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.

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:

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.

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

Aus dem Bild können wir ersehen, dass wir die Trendlinien erkennen, sie visualisieren und auf sie reagieren, wenn der Preis sie 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:

Bericht des Backtest:

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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Entwicklung des Price Action Analysis Toolkit (Teil 36): Direkter Python-Zugang zu MetaTrader 5 Market Streams freischalten
CRUD-Operationen in Firebase mit MQL
MetaTrader Tick-Info-Zugang von MQL5-Diensten zur Python-Anwendung über Sockets
Einführung in MQL5 (Teil 20): Einführung in „Harmonic Patterns“
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.