Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 6): Weiterentwicklung der RSI-Berechnungen mit Glättung, Farbwechsel und Multi-Timeframe-Unterstützung
Einführung
In unserem vorherigen Artikel (Teil 5) haben wir einen WaveTrend Crossover-Indikator in MetaQuotes Language 5 (MQL5) entwickelt, der eine Zeichenfläche (Canvas) für Nebelgradienten verwendet, Signalblasen anzeigt, um wichtige Crossover-Ereignisse hervorzuheben, und Risikomanagementfunktionen, einschließlich Stop-Loss- und Take-Profit-Berechnungen, integriert, um Handelsentscheidungen zu verbessern. In Teil 6 erstellen wir mit dem Relative Strength Index (RSI) einen fortschrittlichen Indikator, der mehrere RSI-Berechnungsmethoden – einschließlich klassischer, geglätteter und adaptiver Varianten –, verschiedene Datenglättungsverfahren, dynamische Farbwechsel (hue shifts) für ein sofortiges visuelles Feedback und Datenverarbeitung mit mehreren Zeitrahmen und Interpolation unterstützt, um RSI-Messwerte über verschiedene Zeiträume hinweg anzugleichen. Wir werden die folgenden Themen behandeln:
- Erforschung von RSI-Varianten, Glättungsmethoden und dynamischen Merkmalen
- Implementierung in MQL5
- Backtests
- Schlussfolgerung
Am Ende haben Sie einen voll funktionsfähigen und anpassbaren MQL5-Indikator für die vielseitige RSI-Analyse, der mehrere Optionen der Berechnung, Glättung, Visualisierung und Zeitrahmen unterstützt – beginnen wir!
Erforschung von RSI-Varianten, Glättungsmethoden und dynamischen Merkmalen
Der normale Relative Strength Index (RSI) misst die Geschwindigkeit und Veränderung von Kursbewegungen, um überkaufte oder überverkaufte Bedingungen zu identifizieren. Er schwankt normalerweise zwischen 0 und 100 mit Grenzen bei 70 und 30. Wir werden den RSI weiterentwickeln, indem wir Varianten wie den Cuttler-, den Ehlers-, den Harris-, den Quick-, den Basic-, den RSX- und den Gradual-Stil hinzufügen, um seine Berechnungen anzupassen und verschiedene Aspekte zu betonen, einschließlich der Glättung und der Reaktion auf die Marktdynamik. Bei der Datenglättung werden Mittelwertbildungstechniken – einfache, wachstumsbasierte, ausgeglichene oder gewichtete lineare Mittelwerte – zur Vorverarbeitung von Kursdaten wie Schluss-, Eröffnungs-, Höchst- und Tiefstkursen oder abgeleiteten Durchschnittswerten eingesetzt, um das Rauschen zu reduzieren und klarere Signale zu liefern.
Farbwechsel ändern die Farbe des Indikators auf der Grundlage von Bedingungen wie Richtungsänderungen, Überschreiten des Mittelpunkts oder Überschreiten von Grenzen und liefern visuelle Hinweise auf Trendumkehr oder Stärke. Dynamische Grenzen aktualisieren die überkauften und überverkauften Niveaus auf der Grundlage der jüngsten RSI-Extreme über einen bestimmten Zeitraum, während statische Grenzen feste Prozentsätze verwenden, und die Unterstützung mehrerer Zeitrahmen ermöglicht die Analyse über verschiedene Zeiträume mit optionaler Interpolation für eine glattere Darstellung.
Unser Plan ist es, Nutzereingaben für die Auswahl von RSI-Varianten, Datenquellen, Glättungsansätzen, Farbtonbedingungen und Begrenzungseinstellungen zu konfigurieren, dann die RSI-Kurve mit diesen Optionen zu berechnen, Begrenzungen und Füllungen zu zeichnen, Daten mit mehreren Zeitrahmen zu verarbeiten und Benachrichtigungen bei Farbtonänderungen auszulösen. Kurz gesagt, entsteht so ein anpassbares RSI-Werkzeug, das sich an unterschiedliche Marktbedingungen und Nutzerpräferenzen für die technische Analyse anpassen lässt und einen fortschrittlichen RSI-Indikator im Vergleich zum traditionellen Indikator erzeugt, wie unten dargestellt.

Wir können sehen, dass wir am Ende eine adaptive RSI-Engine erhalten, die das Momentum aus jeder Kursdarstellung berechnen kann, nicht nur aus dem Schlusskurs. Sie glättet dynamisch sowohl die Eingabedaten als auch den RSI selbst, sodass wir zwischen schnellem, langsamem oder zyklusabhängigem Verhalten wechseln können, ohne die Kernlogik zu ändern. Darüber hinaus passt er seine Grenzen und visuellen Zustände selbst an, sodass sich überkaufte/überverkaufte Signale den Marktbedingungen anpassen, anstatt starr zu bleiben. Fangen wir an, das umzusetzen!
Implementierung in MQL5
Um den Indikator in MQL5 zu erstellen, öffnen Sie einfach den MetaEditor, gehen Sie zum Navigator, suchen Sie den Ordner Indikatoren, klicken Sie auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald der Indikator erstellt ist, werden wir in der Programmierumgebung die Eigenschaften und Einstellungen des Indikators festlegen, z. B. die Anzahl der Puffer, die Darstellungen und die Eigenschaften der einzelnen Linien, wie Farbe, Breite und Beschriftung.
//+------------------------------------------------------------------+ //| Multi-Method RSI with Smoothing.mq5 | //| Copyright 2026, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property indicator_separate_window #property indicator_buffers 8 #property indicator_plots 5 #property indicator_label1 "RSI High/Low Area" #property indicator_type1 DRAW_FILLING #property indicator_color1 C'209,243,209',C'255,230,183' #property indicator_label2 "RSI Top Boundary" #property indicator_type2 DRAW_LINE #property indicator_color2 clrLimeGreen #property indicator_style2 STYLE_DOT #property indicator_label3 "RSI Center Line" #property indicator_type3 DRAW_LINE #property indicator_color3 clrSilver #property indicator_style3 STYLE_DOT #property indicator_label4 "RSI Bottom Boundary" #property indicator_type4 DRAW_LINE #property indicator_color4 clrOrange #property indicator_style4 STYLE_DOT #property indicator_label5 "RSI Curve" #property indicator_type5 DRAW_COLOR_LINE #property indicator_color5 clrSilver,clrLimeGreen,clrOrange #property indicator_width5 2
Wir beginnen die Implementierung, indem wir den Indikator mit „#property indicator_separate_window“ so einstellen, dass er in einem separaten Unterfenster angezeigt wird, das sich zur besseren Übersichtlichkeit vom Hauptchart unterscheidet. Als Nächstes weisen wir acht Puffer für interne Berechnungen mit „#property indicator_buffers 8“ zu und definieren fünf Plots für visuelle Elemente mit „#property indicator_plots 5“. Für die Plots erstellen wir einen gefüllten Bereich für die RSI-Hoch/Tief-Regionen in hellgrünen und orangefarbenen Tönen, eine gepunktete limonengrüne Linie für die obere Begrenzung, eine gepunktete silberne Mittellinie, eine gepunktete orangefarbene untere Begrenzung und eine farbige RSI-Kurvenlinie, die zwischen Silber, Limonengrün und Orange mit einer Breite von 2 zur besseren Sichtbarkeit wechselt. Als Nächstes werden wir einige Eingabeparameter zur Steuerung des Indikators definieren.
//+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum DataSourceType { Data_ClosePrice, // Use closing price Data_OpenPrice, // Use opening price Data_HighPrice, // Use highest price Data_LowPrice, // Use lowest price Data_MidPoint, // Use midpoint price Data_StandardPrice, // Use standard price Data_BalancedPrice, // Use balanced price Data_OverallAverage, // Use overall average price Data_MidBodyAverage, // Use mid-body average Data_DirectionAdjusted, // Use direction-adjusted price Data_ExtremeAdjusted, // Use extreme-adjusted price Data_SmoothedClose, // Use smoothed close Data_SmoothedOpen, // Use smoothed open Data_SmoothedHigh, // Use smoothed high Data_SmoothedLow, // Use smoothed low Data_SmoothedMid, // Use smoothed midpoint Data_SmoothedStandard,// Use smoothed standard Data_SmoothedBalanced,// Use smoothed balanced Data_SmoothedOverall, // Use smoothed overall Data_SmoothedMidBody, // Use smoothed mid-body Data_SmoothedAdjusted,// Use smoothed adjusted Data_SmoothedExtreme // Use smoothed extreme }; enum RsiVariant { Variant_CuttlerStyle, // Cuttler-style RSI Variant_EhlersStyle, // Ehlers-style smoothed RSI Variant_HarrisStyle, // Harris-style RSI Variant_QuickStyle, // Quick RSI Variant_BasicStyle, // Basic RSI Variant_RsxStyle, // RSX-style Variant_GradualStyle // Gradual RSI }; enum HueShiftCondition { Hue_OnDirectionShift, // Shift hue on direction change Hue_OnCenterCrossing, // Shift hue on center crossing Hue_OnBoundaryCrossing // Shift hue on boundary crossing }; enum AveragingApproach { Avg_Basic, // Basic averaging Avg_GrowthBased, // Growth-based averaging Avg_EvenedOut, // Evened-out averaging Avg_WeightedLinear // Weighted linear averaging }; //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "Chart and Calculation Settings"; input ENUM_TIMEFRAMES AnalysisTimeframe = PERIOD_CURRENT; // Choose timeframe for data analysis input int RsiLength = 14; // Length for RSI computation input group "Data Source and Variant Options"; input DataSourceType SourceData = Data_ClosePrice; // Select data source for calculations input RsiVariant ChosenRsiVariant = Variant_BasicStyle; // Select RSI computation variant input int DataSmoothingLength = 0; // Smoothing length for data (0 or 1 disables) input AveragingApproach DataSmoothingApproach = Avg_GrowthBased; // Approach for data smoothing input group "Hue Adjustment Settings"; input HueShiftCondition HueAdjustmentOn = Hue_OnBoundaryCrossing; // Condition for hue adjustment input group "Boundary Configuration"; input int DynamicBoundaryLength = 50; // Length for dynamic boundaries (1 or less for static) input double TopBoundaryPercent = 80.0; // Top boundary percentage input double BottomBoundaryPercent = 20.0; // Bottom boundary percentage input group "Notification Preferences"; input bool ActivateNotifications = false; // Activate notifications? input bool NotifyOnActiveBar = true; // Notify on active bar? input bool InterpolateMultiFrame = true; // Smooth multi-frame data?
Wir fahren fort mit der Definition von Enumerationen, um die Nutzeroptionen zu kategorisieren und die Konfigurierbarkeit zu verbessern. Die Enumeration „DataSourceType“ listet verschiedene Preisdatenquellen auf, von grundlegenden wie Schluss- oder Eröffnungskursen bis hin zu abgeleiteten Durchschnittswerten wie „midpoint“ oder „balanced price“, und enthält geglättete Versionen zur Rauschunterdrückung. Als Nächstes bietet die Enumeration „RsiVariant“ verschiedene RSI-Berechnungsstile, darunter Basic, RSX, Cuttler, Ehlers, Harris, Quick und Gradual, die die Auswahl der gewünschten Berechnungsmethode ermöglichen.
Wir erstellen auch die Enumeration „HueShiftCondition“, um zu bestimmen, wann die RSI-Kurve ihre Farbe ändert, mit Optionen für Richtungsverschiebungen, Überschreitungen der Mitte oder Überschreitungen der Grenzen. Darüber hinaus legt die Enumeration „AveragingApproach“ Glättungsverfahren wie „basic“, „growth-based“, „evened-out“ oder „weighted linear“ für die Vorverarbeitung der Daten fest. Die Nutzereingaben sind in Abschnitte unterteilt, beginnend mit den Chart- und Berechnungseinstellungen, wobei „AnalysisTimeframe“ den Zeitrahmen auswählt und „RsiLength“ die RSI-Periode festlegt. Wir haben Kommentare hinzugefügt, um sie leichter verständlich zu machen. Daraufhin wird das folgende Fenster angezeigt.

Als Nächstes werden wir den Indikator initialisieren, aber lassen Sie uns zunächst einige globale Variablen definieren, die wir im gesamten Programm verwenden werden.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ double rsiCurveValues[], rsiHueValues[], areaFillTop[], areaFillBottom[], topBoundaryValues[], centerLineValues[], bottomBoundaryValues[], processedBarCounts[]; //--- Declare buffers int multiFrameDataHandle = INVALID_HANDLE; //--- Initialize multi-frame handle ENUM_TIMEFRAMES chosenTimeframe; //--- Declare chosen timeframe #define MULTI_FRAME_ACCESS iCustom(_Symbol, chosenTimeframe, __FILE__, PERIOD_CURRENT, RsiLength, SourceData, ChosenRsiVariant, DataSmoothingLength, DataSmoothingApproach, HueAdjustmentOn, DynamicBoundaryLength, TopBoundaryPercent, BottomBoundaryPercent, ActivateNotifications, NotifyOnActiveBar, InterpolateMultiFrame) //--- Define multi-frame access int timeframeCodes[] = {PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6, PERIOD_M10, PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30, PERIOD_H1, PERIOD_H2, PERIOD_H3, PERIOD_H4, PERIOD_H6, PERIOD_H8, PERIOD_H12, PERIOD_D1, PERIOD_W1, PERIOD_MN1}; //--- Define timeframe codes string timeframeLabels[] = {"1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "6 minutes", "10 minutes", "12 minutes", "15 minutes", "20 minutes", "30 minutes", "1 hour", "2 hours", "3 hours", "4 hours", "6 hours", "8 hours", "12 hours", "daily", "weekly", "monthly"}; //--- Define timeframe labels //+------------------------------------------------------------------+ //| Convert timeframe to text | //+------------------------------------------------------------------+ string convertTimeframeToText(int code) { if (code == PERIOD_CURRENT) //--- Check current code = _Period; //--- Set period int pos; //--- Declare pos for (pos = 0; pos < ArraySize(timeframeCodes); pos++) //--- Loop codes if (code == timeframeCodes[pos]) break; //--- Break on match return(timeframeLabels[pos]); //--- Return label } //+------------------------------------------------------------------+ //| Describe RSI variant | //+------------------------------------------------------------------+ string describeRsiVariant(int variant) { switch (variant) { //--- Switch variant case Variant_BasicStyle: //--- Handle basic return("RSI"); //--- Return RSI case Variant_RsxStyle: //--- Handle RSX return("RSX"); //--- Return RSX case Variant_CuttlerStyle: //--- Handle Cuttler return("Cuttler-style RSI"); //--- Return Cuttler case Variant_HarrisStyle: //--- Handle Harris return("Harris-style RSI"); //--- Return Harris case Variant_QuickStyle: //--- Handle Quick return("Quick RSI"); //--- Return Quick case Variant_GradualStyle: //--- Handle Gradual return("Gradual RSI"); //--- Return Gradual case Variant_EhlersStyle: //--- Handle Ehlers return("Ehlers-style smoothed RSI"); //--- Return Ehlers default: //--- Handle default return(""); //--- Return empty } }
Hier werden globale Arrays für die Indikatorpuffer deklariert, darunter „rsiCurveValues“ für die Haupt-RSI-Linie, „rsiHueValues“ für Farbindizes, „areaFillTop“ und „areaFillBottom“ für die gefüllten Bereiche, „topBoundaryValues“, „centerLineValues“ und „bottomBoundaryValues“ für die Begrenzungslinien sowie „processedBarCounts“ für die Verfolgung der berechneten Bars. Außerdem initialisieren wir „multiFrameDataHandle“ auf INVALID_HANDLE für die Verwaltung von Multi-Zeitrahmen-Daten und deklarieren „chosenTimeframe“, um den ausgewählten Zeitrahmen zu speichern. Mithilfe einer Präprozessoranweisung definieren wir „MULTI_FRAME_ACCESS“ als einen Aufruf an iCustom mit dem Symbol, dem gewählten Zeitrahmen, dem Dateinamen und allen Eingabeparametern, um den Zugriff auf Daten aus verschiedenen Zeitrahmen zu erleichtern.
Als Nächstes richten wir Arrays „timeframeCodes“ mit vordefinierten Periodenkonstanten wie PERIOD_M1 bis „PERIOD_MN1“ und „timeframeLabels“ mit entsprechenden Stringbeschreibungen für menschenlesbare Zeitrahmennamen ein. Die Funktion „convertTimeframeToText“ übersetzt einen Zeitrahmencode in seine Bezeichnung, indem sie zunächst „PERIOD_CURRENT“ in die tatsächliche Periode auflöst, dann in einer Schleife „timeframeCodes“ durchläuft, um eine Übereinstimmung zu finden und die entsprechende Bezeichnung aus „timeframeLabels“ zurückzugeben. Schließlich verwendet die Funktion „describeRsiVariant“ eine Switch-Anweisung für die Varianteneingabe, um eine beschreibende Zeichenkette für jeden RSI-Stil zurückzugeben, z. B. „RSI“ für die Basisvariante oder „Ehlers-style smoothed RSI“ für die Ehlers-Variante, wobei bei Nichtübereinstimmung eine leere Zeichenkette vorgegeben wird. Wir können nun diese Hilfsmittel verwenden, um den Indikator zu initialisieren.
//+------------------------------------------------------------------+ //| Initialize indicator | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, areaFillTop, INDICATOR_DATA); //--- Set top fill buffer SetIndexBuffer(1, areaFillBottom, INDICATOR_DATA); //--- Set bottom fill buffer SetIndexBuffer(2, topBoundaryValues, INDICATOR_DATA); //--- Set top boundary buffer SetIndexBuffer(3, centerLineValues, INDICATOR_DATA); //--- Set center line buffer SetIndexBuffer(4, bottomBoundaryValues, INDICATOR_DATA); //--- Set bottom boundary buffer SetIndexBuffer(5, rsiCurveValues, INDICATOR_DATA); //--- Set RSI curve buffer SetIndexBuffer(6, rsiHueValues, INDICATOR_COLOR_INDEX); //--- Set hue buffer SetIndexBuffer(7, processedBarCounts, INDICATOR_CALCULATIONS); //--- Set processed counts buffer PlotIndexSetInteger(0, PLOT_SHOW_DATA, false); //--- Hide filling data PlotIndexSetInteger(1, PLOT_SHOW_DATA, false); //--- Hide bottom data PlotIndexSetInteger(2, PLOT_SHOW_DATA, true); //--- Show top boundary PlotIndexSetInteger(3, PLOT_SHOW_DATA, true); //--- Show center line PlotIndexSetInteger(4, PLOT_SHOW_DATA, true); //--- Show bottom boundary chosenTimeframe = MathMax(_Period, AnalysisTimeframe); //--- Set chosen timeframe IndicatorSetString(INDICATOR_SHORTNAME, convertTimeframeToText(chosenTimeframe) + " " + describeRsiVariant(ChosenRsiVariant) + " with Adjustment (" + (string)RsiLength + "," + (string)DataSmoothingLength + "," + (string)DynamicBoundaryLength + ")"); //--- Set short name return(INIT_SUCCEEDED); //--- Return success }
Im OnInit-Ereignis-Handler werden die zugewiesenen Puffer mit bestimmten Plot-Indizes und Datentypen verknüpft. Wir verknüpfen Puffer 0 mit „areaFillTop“ als „INDICATOR_DATA“ für den oberen Füllstand, Puffer 1 mit „areaFillBottom“ ebenfalls für den unteren Füllstand, Puffer 2 mit „topBoundaryValues“ für die oberen Begrenzungsdaten, Puffer 3 mit „centerLineValues“ für die Mittellinie, Puffer 4 mit „bottomBoundaryValues“ für die untere Begrenzung, Puffer 5 an „rsiCurveValues“ für die RSI-Hauptwerte, Puffer 6 an „rsiHueValues“ als INDICATOR_COLOR_INDEX für die Farbindexierung und Puffer 7 an „processedBarCounts“ als „INDICATOR_CALCULATIONS“ für die Verfolgung der verarbeiteten Bars. Anschließend konfigurieren wir die Sichtbarkeit der Plots mithilfe von PlotIndexSetInteger, wobei die Daten für die Plots 0 und 1 ausgeblendet werden, um die Anzeige der rohen Füllwerte zu vermeiden, während die Plots 2, 3 und 4 für die Grenzen und die Mittellinie angezeigt werden.
Als Nächstes bestimmen wir den „chosenTimeframe“, indem wir das Maximum zwischen der aktuellen Chart-Periode und dem vom Nutzer eingegebenen „AnalysisTimeframe“ nehmen, um Multi-Timeframe-Operationen zu unterstützen. Wir legen den Kurznamen des Indikators mit IndicatorSetString fest, indem wir den Zeitrahmentext aus „convertTimeframeToText“, die Beschreibung der RSI-Variante aus „describeRsiVariant“ und einen String mit Schlüsselparametern wie „RsiLength“, „DataSmoothingLength“ und „DynamicBoundaryLength“ zur einfachen Identifizierung im Chart kombinieren. Schließlich geben wir INIT_SUCCEEDED zurück, um die erfolgreiche Initialisierung anzuzeigen. Wir können mit der Berechnung unserer Indikatoren fortfahren. Wir beginnen mit der Festlegung des Zeitrahmens für die Berechnungen wie folgt.
//+------------------------------------------------------------------+ //| Check timeframe validity | //+------------------------------------------------------------------+ bool checkTimeframeValidity(ENUM_TIMEFRAMES frame, const datetime& timeStamps[]) { static bool alerted = false; //--- Set alerted flag if (timeStamps[0] < SeriesInfoInteger(_Symbol, frame, SERIES_FIRSTDATE)) { //--- Check first date datetime startDate, checkDate[]; //--- Declare dates if (SeriesInfoInteger(_Symbol, PERIOD_M1, SERIES_TERMINAL_FIRSTDATE, startDate)) //--- Get terminal date if (startDate > 0) { //--- Check date CopyTime(_Symbol, frame, timeStamps[0], 1, checkDate); //--- Copy time SeriesInfoInteger(_Symbol, frame, SERIES_FIRSTDATE, startDate); //--- Get series date } if (startDate <= 0 || startDate > timeStamps[0]) { //--- Check invalid alerted = true; //--- Set alerted return(false); //--- Return false } } if (alerted) { //--- Check alerted alerted = false; //--- Reset alerted } return(true); //--- Return true } //+------------------------------------------------------------------+ //| Calculate indicator | //+------------------------------------------------------------------+ int OnCalculate(const int barTotal, const int prevProcessed, const datetime& timeStamps[], const double& opens[], const double& highs[], const double& lows[], const double& closes[], const long& volumeTicks[], const long& actualVolumes[], const int& spreadValues[]) { if (Bars(_Symbol, _Period) < barTotal) return(-1); //--- Check insufficient bars if (chosenTimeframe != _Period) { //--- Check multi-frame double interimData[]; //--- Declare interim data datetime activeTimeStamp[], followingTimeStamp[]; //--- Declare timestamps if (!checkTimeframeValidity(chosenTimeframe, timeStamps)) return(0); //--- Check validity if (multiFrameDataHandle == INVALID_HANDLE) multiFrameDataHandle = MULTI_FRAME_ACCESS; //--- Get handle if (multiFrameDataHandle == INVALID_HANDLE) return(0); //--- Check handle if (CopyBuffer(multiFrameDataHandle, 7, 0, 1, interimData) == -1) return(0); //--- Copy processed #define FRAME_RATIO PeriodSeconds(chosenTimeframe) / PeriodSeconds(_Period) //--- Define frame ratio int currentPos = MathMin(MathMax(prevProcessed - 1, 0), MathMax(barTotal - (int)interimData[0] * FRAME_RATIO - 1, 0)); //--- Compute pos for (; currentPos < barTotal && !_StopFlag; currentPos++) { //--- Loop positions #define TRANSFER_MULTI_FRAME(_array, _pos) if (CopyBuffer(multiFrameDataHandle, _pos, timeStamps[currentPos], 1, interimData) == -1) break; _array[currentPos] = interimData[0] //--- Define transfer TRANSFER_MULTI_FRAME(areaFillTop, 0); //--- Transfer top fill TRANSFER_MULTI_FRAME(areaFillBottom, 1); //--- Transfer bottom fill TRANSFER_MULTI_FRAME(topBoundaryValues, 2); //--- Transfer top boundary TRANSFER_MULTI_FRAME(centerLineValues, 3); //--- Transfer center line TRANSFER_MULTI_FRAME(bottomBoundaryValues, 4); //--- Transfer bottom boundary TRANSFER_MULTI_FRAME(rsiCurveValues, 5); //--- Transfer RSI curve TRANSFER_MULTI_FRAME(rsiHueValues, 6); //--- Transfer hue if (!InterpolateMultiFrame) continue; //--- Skip if no interpolate CopyTime(_Symbol, chosenTimeframe, timeStamps[currentPos], 1, activeTimeStamp); //--- Copy active time if (currentPos < (barTotal - 1)) { //--- Check not last CopyTime(_Symbol, chosenTimeframe, timeStamps[currentPos + 1], 1, followingTimeStamp); //--- Copy following time if (activeTimeStamp[0] == followingTimeStamp[0]) continue; //--- Skip same time } int stepsBack = 1; //--- Initialize steps back while ((currentPos - stepsBack) > 0 && timeStamps[currentPos - stepsBack] >= activeTimeStamp[0]) stepsBack++; //--- Count back for (int stepsForward = 1; (currentPos - stepsForward) >= 0 && stepsForward < stepsBack; stepsForward++) { //--- Loop forward #define SMOOTH_MULTI_FRAME(_array) _array[currentPos - stepsForward] = _array[currentPos] + (_array[currentPos - stepsBack] - _array[currentPos]) * stepsForward / stepsBack //--- Define smooth SMOOTH_MULTI_FRAME(areaFillTop); //--- Smooth top fill SMOOTH_MULTI_FRAME(areaFillBottom); //--- Smooth bottom fill SMOOTH_MULTI_FRAME(topBoundaryValues); //--- Smooth top boundary SMOOTH_MULTI_FRAME(bottomBoundaryValues); //--- Smooth bottom boundary SMOOTH_MULTI_FRAME(centerLineValues); //--- Smooth center line SMOOTH_MULTI_FRAME(rsiCurveValues); //--- Smooth RSI curve } } return(currentPos); //--- Return pos } }
Zunächst erstellen wir die Funktion „checkTimeframeValidity“, um zu überprüfen, ob für den ausgewählten Zeitrahmen genügend historische Daten für den aktuellen Zeitstempel verfügbar sind. Wir verwenden das statische Flag „alerted“, um festzustellen, ob ein ungültiger Zustand festgestellt wurde. Liegt der erste Zeitstempel vor dem Startdatum der Serie, das über SeriesInfoInteger mit SERIES_FIRSTDATE ermittelt wurde, holen wir das erste Datum des Terminals mit „SERIES_TERMINAL_FIRSTDATE“ und kopieren die Uhrzeit des Zeitrahmens mit CopyTime, um die Daten zu vergleichen. Wenn das Startdatum ungültig oder später als der Zeitstempel ist, wird das Flag „alerted“ gesetzt und „false“ zurückgegeben; andernfalls wird das Flag zurückgesetzt, falls es zuvor gesetzt wurde, und „true“ zurückgegeben.
Im OnCalculate-Ereignishandler, der die Indikatorwerte bei jeder neuen Bar oder jeder Datenaktualisierung verarbeitet, wird zunächst geprüft, ob die Anzahl der verfügbaren Bars unter der mit Bars angeforderten Gesamtzahl liegt, und bei unzureichender Anzahl wird -1 zurückgegeben, um die erforderliche Neuberechnung zu signalisieren. Weicht der gewählte Zeitrahmen von der aktuellen Periode ab, was auf den Multi-Zeitrahmen-Modus hindeutet, werden temporäre Arrays für Daten und Zeitstempel deklariert und dann „checkTimeframeValidity“ aufgerufen, um die Datenverfügbarkeit sicherzustellen, wobei bei Ungültigkeit 0 zurückgegeben wird.
Wir erhalten das Handle für mehrere Zeitrahmen, wenn es ungültig ist, mit dem vordefinierten Makro „MULTI_FRAME_ACCESS“, und geben 0 zurück, wenn es fehlschlägt. Wir kopieren die verarbeitete Zahl der Bars aus Puffer 7 in „interimData“ mit CopyBuffer, wobei bei einem Fehler ebenfalls 0 zurückgegeben wird. Wir definieren „FRAME_RATIO“ als das Verhältnis der Sekunden zwischen dem gewählten und dem aktuellen Zeitraum, um die Positionen der Bar zu skalieren. Wir berechnen den Ausgangswert „currentPos“ mithilfe der Minimum- und Maximum-Funktionen auf der Grundlage der zuvor verarbeiteten Bars und der angepassten Summe.
Wir führen eine Schleife von „currentPos“ bis „barTotal“ durch, überprüfen das Stop-Flag und definieren das Makro „TRANSFER_MULTI_FRAME“, um den Wert jedes Puffers vom Handle für mehrere Zeitrahmen in die entsprechenden Arrays wie „areaFillTop“, „areaFillBottom“, Grenzen, Mittellinie, „rsiCurveValues“ und „rsiHueValues“ an der aktuellen Position über den zeitbasierten „CopyBuffer“ zu kopieren und bei Fehlschlag abzubrechen.
Wenn die Multi-Timeframe-Interpolation aktiviert ist, kopieren wir den aktiven und den nachfolgenden Zeitstempel. Diesen Schritt überspringen wir, wenn es die letzte Bar ist oder die Zeitstempel übereinstimmen, und zählen dann „stepsBack“, um die vorherige Bar mit einem früheren Zeitstempel zu finden. In einer Vorwärtsschleife von 1 bis kleiner als „stepsBack“ wird das Makro „SMOOTH_MULTI_FRAME“ definiert, um die Werte in den Arrays zwischen der aktuellen und der letzten Position linear zu interpolieren und die Füllbereiche, Begrenzungen, die Mittellinie und die RSI-Kurve für eine nahtlose Multitimeframe-Anzeige zu glätten.
Schließlich geben wir die aktualisierte „currentPos“ zurück, um die verarbeiteten Bars anzuzeigen. Nachdem der Zeitrahmen festgelegt wurde, können wir mit den Berechnungen des Indikators fortfahren. Wir werden Hilfsfunktionen definieren, um den Code übersichtlich und modular zu gestalten. Beginnen wir mit der Funktion zur Berechnung der angepassten Datendurchschnitte für Glättungsmethoden.
#define AVG_VARIANTS 1 //--- Define avg variants #define AVG_ARRAY_X1 1 * AVG_VARIANTS //--- Define array x1 #define AVG_ARRAY_X2 2 * AVG_VARIANTS //--- Define array x2 //+------------------------------------------------------------------+ //| Compute custom average | //+------------------------------------------------------------------+ double computeCustomAverage(int avgApproach, double inputVal, double avgLen, int pos, int barCnt, int varIndex = 0) { switch (avgApproach) { //--- Switch approach case Avg_Basic: //--- Handle basic return(computeBasicAvg(inputVal, (int)avgLen, pos, barCnt, varIndex)); //--- Return basic avg case Avg_GrowthBased: //--- Handle growth return(computeGrowthAvg(inputVal, avgLen, pos, barCnt, varIndex)); //--- Return growth avg case Avg_EvenedOut: //--- Handle evened return(computeEvenedAvg(inputVal, avgLen, pos, barCnt, varIndex)); //--- Return evened avg case Avg_WeightedLinear: //--- Handle weighted return(computeLinearAvg(inputVal, avgLen, pos, barCnt, varIndex)); //--- Return linear avg default: //--- Handle default return(inputVal); //--- Return input } } double basicAvgArray[][AVG_ARRAY_X2]; //--- Declare basic avg array //+------------------------------------------------------------------+ //| Compute basic average | //+------------------------------------------------------------------+ double computeBasicAvg(double inputVal, int avgLen, int pos, int barCnt, int varIndex = 0) { if (ArrayRange(basicAvgArray, 0) != barCnt) ArrayResize(basicAvgArray, barCnt); //--- Resize array varIndex *= 2; //--- Adjust index int offset; //--- Declare offset basicAvgArray[pos][varIndex + 0] = inputVal; //--- Set value basicAvgArray[pos][varIndex + 1] = inputVal; //--- Set avg for (offset = 1; offset < avgLen && (pos - offset) >= 0; offset++) //--- Loop offsets basicAvgArray[pos][varIndex + 1] += basicAvgArray[pos - offset][varIndex + 0]; //--- Accumulate avg basicAvgArray[pos][varIndex + 1] /= 1.0 * offset; //--- Average return(basicAvgArray[pos][varIndex + 1]); //--- Return avg } double growthAvgArray[][AVG_ARRAY_X1]; //--- Declare growth avg array //+------------------------------------------------------------------+ //| Compute growth average | //+------------------------------------------------------------------+ double computeGrowthAvg(double inputVal, double avgLen, int pos, int barCnt, int varIndex = 0) { if (ArrayRange(growthAvgArray, 0) != barCnt) ArrayResize(growthAvgArray, barCnt); //--- Resize array growthAvgArray[pos][varIndex] = inputVal; //--- Set value if (pos > 0 && avgLen > 1) //--- Check pos and len growthAvgArray[pos][varIndex] = growthAvgArray[pos - 1][varIndex] + (2.0 / (1.0 + avgLen)) * (inputVal - growthAvgArray[pos - 1][varIndex]); //--- Compute growth return(growthAvgArray[pos][varIndex]); //--- Return avg } double evenedAvgArray[][AVG_ARRAY_X1]; //--- Declare evened avg array //+------------------------------------------------------------------+ //| Compute evened average | //+------------------------------------------------------------------+ double computeEvenedAvg(double inputVal, double avgLen, int pos, int barCnt, int varIndex = 0) { if (ArrayRange(evenedAvgArray, 0) != barCnt) ArrayResize(evenedAvgArray, barCnt); //--- Resize array evenedAvgArray[pos][varIndex] = inputVal; //--- Set value if (pos > 1 && avgLen > 1) //--- Check pos and len evenedAvgArray[pos][varIndex] = evenedAvgArray[pos - 1][varIndex] + (inputVal - evenedAvgArray[pos - 1][varIndex]) / avgLen; //--- Compute evened return(evenedAvgArray[pos][varIndex]); //--- Return avg } double linearAvgArray[][AVG_ARRAY_X1]; //--- Declare linear avg array //+------------------------------------------------------------------+ //| Compute linear average | //+------------------------------------------------------------------+ double computeLinearAvg(double inputVal, double avgLen, int pos, int barCnt, int varIndex = 0) { if (ArrayRange(linearAvgArray, 0) != barCnt) ArrayResize(linearAvgArray, barCnt); //--- Resize array linearAvgArray[pos][varIndex] = inputVal; //--- Set value if (avgLen <= 1) return(inputVal); //--- Return if no avg double totalWeights = avgLen; //--- Set total weights double totalValues = avgLen * inputVal; //--- Set total values for (int offset = 1; offset < avgLen && (pos - offset) >= 0; offset++) { //--- Loop offsets double currWeight = avgLen - offset; //--- Compute weight totalWeights += currWeight; //--- Accumulate weights totalValues += currWeight * linearAvgArray[pos - offset][varIndex]; //--- Accumulate values } return(totalValues / totalWeights); //--- Return avg }
Wir definieren Konstanten mit Präprozessoranweisungen: „AVG_VARIANTS“ wird auf 1 gesetzt, um die Anzahl der Mittelungsvarianten festzulegen, „AVG_ARRAY_X1“ auf das 1-fache der Varianten für einspaltige Arrays und „AVG_ARRAY_X2“ auf das 2-fache der Varianten für zweispaltige Arrays, um unterschiedliche Anforderungen an die Glättung zu unterstützen. In der Funktion „computeCustomAverage“ verwenden wir eine Switch-Anweisung für den Parameter „avgApproach“, um die entsprechende Mittelwertbildungsmethode zu wählen – einfach, wachstumsbasiert, ausgeglichen oder gewichtet linear – und den berechneten Mittelwert aus der jeweiligen Funktion oder einfach den „inputVal“ im Standardfall zurückzugeben, wenn keine Übereinstimmung vorliegt.
Wir deklarieren „basicAvgArray“ als ein mehrdimensionales Array mit der Größe „AVG_ARRAY_X2“. Die Funktion „computeBasicAvg“ prüft zunächst die Größe des Arrays und passt es gegebenenfalls an „barCnt“ an, multipliziert „varIndex“ mit 2 für den Spaltenversatz, speichert den „inputVal“ in der ersten Spalte, initialisiert den Durchschnitt in der zweiten Spalte, durchläuft dann Schleifen über frühere Positionen bis zu „avgLen“, um frühere Werte zu summieren, dividiert durch die tatsächliche Versatzzahl und gibt das Ergebnis zurück.
In ähnlicher Weise wird für die wachstumsbasierte Mittelwertbildung „growthAvgArray“ mit „AVG_ARRAY_X1“ deklariert, die Größe bei Bedarf in „computeGrowthAvg“ angepasst, der Wert direkt festgelegt und, wenn er über die ersten Bars hinausgeht und „avgLen“ 1 übersteigt, mit einer Formel aktualisiert, die eine gewichtete Differenz zum vorherigen Wert hinzufügt und eine exponentielle Glättung imitiert, bevor sie zurückkehrt. Für eine ausgeglichene Mittelwertbildung verwendet „evenedAvgArray“ „AVG_ARRAY_X1“, und „computeEvenedAvg“ ändert die Größe, legt den Wert fest und passt ihn dann an, wenn er über die zweite Bar hinausgeht und „avgLen“ über 1 liegt, indem es die Differenz zur vorherigen Bar dividiert durch die Länge für einen einfachen inkrementellen Mittelwert addiert und zurückgibt.
Schließlich verwendet „linearAvgArray“ auch „AVG_ARRAY_X1“, und „computeLinearAvg“ ändert die Größe, setzt den Wert, gibt ihn frühzeitig zurück, wenn „avgLen“ 1 oder weniger ist, initialisiert andernfalls die Gesamtwerte für Gewichte und gewichtete Werte mit dem aktuellen Wert, führt eine Schleife über die Prioritäten durch, um abnehmende Gewichte und entsprechende Werte zu akkumulieren, und gibt die Gesamtwerte geteilt durch die Gesamtgewichte für einen linear gewichteten Durchschnitt zurück. Als Nächstes müssen wir die Mittelwertbildungsfunktion mit einem Datensatz füttern. So definieren wir die Erzeugung des Datensatzes.
#define DATA_VARIANTS 1 //--- Define data variants #define DATA_VARIANT_SIZE 4 //--- Define variant size double smoothedDataArray[][DATA_VARIANTS * DATA_VARIANT_SIZE]; //--- Declare smoothed data array //+------------------------------------------------------------------+ //| Fetch chosen data | //+------------------------------------------------------------------+ double fetchChosenData(int dataType, const double& opens[], const double& closes[], const double& highs[], const double& lows[], int pos, int barCnt, int varIndex = 0) { if (dataType >= Data_SmoothedClose) { //--- Check smoothed if (ArrayRange(smoothedDataArray, 0) != barCnt) ArrayResize(smoothedDataArray, barCnt); //--- Resize array varIndex *= DATA_VARIANT_SIZE; //--- Adjust index double smoothedOpen; //--- Declare smoothed open if (pos > 0) //--- Check pos smoothedOpen = (smoothedDataArray[pos - 1][varIndex + 2] + smoothedDataArray[pos - 1][varIndex + 3]) / 2.0; //--- Compute smoothed open else //--- Handle initial smoothedOpen = (opens[pos] + closes[pos]) / 2; //--- Set initial open double smoothedClose = (opens[pos] + highs[pos] + lows[pos] + closes[pos]) / 4.0; //--- Compute smoothed close double smoothedHigh = MathMax(highs[pos], MathMax(smoothedOpen, smoothedClose)); //--- Compute smoothed high double smoothedLow = MathMin(lows[pos], MathMin(smoothedOpen, smoothedClose)); //--- Compute smoothed low smoothedDataArray[pos][varIndex + 2] = smoothedOpen; //--- Set smoothed open smoothedDataArray[pos][varIndex + 3] = smoothedClose; //--- Set smoothed close switch (dataType) { //--- Switch data type case Data_SmoothedClose: //--- Handle smoothed close return(smoothedClose); //--- Return close case Data_SmoothedOpen: //--- Handle smoothed open return(smoothedOpen); //--- Return open case Data_SmoothedHigh: //--- Handle smoothed high return(smoothedHigh); //--- Return high case Data_SmoothedLow: //--- Handle smoothed low return(smoothedLow); //--- Return low case Data_SmoothedMid: //--- Handle smoothed mid return((smoothedHigh + smoothedLow) / 2.0); //--- Return mid case Data_SmoothedMidBody: //--- Handle smoothed mid body return((smoothedOpen + smoothedClose) / 2.0); //--- Return mid body case Data_SmoothedStandard: //--- Handle smoothed standard return((smoothedHigh + smoothedLow + smoothedClose) / 3.0); //--- Return standard case Data_SmoothedBalanced: //--- Handle smoothed balanced return((smoothedHigh + smoothedLow + smoothedClose + smoothedClose) / 4.0); //--- Return balanced case Data_SmoothedOverall: //--- Handle smoothed overall return((smoothedHigh + smoothedLow + smoothedClose + smoothedOpen) / 4.0); //--- Return overall case Data_SmoothedAdjusted: //--- Handle smoothed adjusted if (smoothedClose > smoothedOpen) return((smoothedHigh + smoothedClose) / 2.0); //--- Return high close else return((smoothedLow + smoothedClose) / 2.0); //--- Return low close case Data_SmoothedExtreme: //--- Handle smoothed extreme if (smoothedClose > smoothedOpen) return(smoothedHigh); //--- Return high if (smoothedClose < smoothedOpen) return(smoothedLow); //--- Return low return(smoothedClose); //--- Return close } } switch (dataType) { //--- Switch data type case Data_ClosePrice: //--- Handle close return(closes[pos]); //--- Return close case Data_OpenPrice: //--- Handle open return(opens[pos]); //--- Return open case Data_HighPrice: //--- Handle high return(highs[pos]); //--- Return high case Data_LowPrice: //--- Handle low return(lows[pos]); //--- Return low case Data_MidPoint: //--- Handle mid point return((highs[pos] + lows[pos]) / 2.0); //--- Return mid case Data_MidBodyAverage: //--- Handle mid body return((opens[pos] + closes[pos]) / 2.0); //--- Return mid body case Data_StandardPrice: //--- Handle standard return((highs[pos] + lows[pos] + closes[pos]) / 3.0); //--- Return standard case Data_BalancedPrice: //--- Handle balanced return((highs[pos] + lows[pos] + closes[pos] + closes[pos]) / 4.0); //--- Return balanced case Data_OverallAverage: //--- Handle overall return((highs[pos] + lows[pos] + closes[pos] + opens[pos]) / 4.0); //--- Return overall case Data_DirectionAdjusted: //--- Handle direction adjusted if (closes[pos] > opens[pos]) return((highs[pos] + closes[pos]) / 2.0); //--- Return high close else return((lows[pos] + closes[pos]) / 2.0); //--- Return low close case Data_ExtremeAdjusted: //--- Handle extreme adjusted if (closes[pos] > opens[pos]) return(highs[pos]); //--- Return high if (closes[pos] < opens[pos]) return(lows[pos]); //--- Return low return(closes[pos]); //--- Return close } return(0); //--- Return zero }
Für die Logik der Datensatzerstellung definieren wir zunächst die Konstanten „DATA_VARIANTS“ als 1, um die Anzahl der Datenverarbeitungsvarianten anzugeben, und „DATA_VARIANT_SIZE“ als 4, um die Spaltenbreite pro Variante im Array festzulegen. Wir deklarieren „smoothedDataArray“ als ein mehrdimensionales Array, das so groß ist, dass es geglättete Preisdaten über Bars hinweg speichern kann. In der Funktion „fetchChosenData“ wird der entsprechende Kurswert auf der Grundlage des „dataType“ für die angegebene Barposition ausgewählt und zurückgegeben.
Wenn es sich um einen geglätteten Typ handelt, der mit „Data_SmoothedClose“ beginnt, passen wir die Größe des Arrays an „barCnt“ an, skalieren „varIndex“ um die Größe der Variante und berechnen „smoothedOpen“ als Durchschnitt der gespeicherten Open- und Close-Werte der vorherigen Bar, wenn diese über die erste Bar hinausgeht, oder andernfalls den aktuellen Open-Close-Mittelpunkt. Wir berechnen „smoothedClose“ als den Vier-Punkte-Durchschnitt von Open, High, Low und Close und leiten dann „smoothedHigh“ als das Maximum aus High, smoothedOpen und smoothedClose und „smoothedLow“ als das Minimum aus Low, smoothedOpen und smoothedClose ab. Wir speichern diese smoothedOpen und smoothedClose in dem Array an den Offsets +2 und +3.
Eine Switch-Anweisung gibt dann den spezifischen geglätteten Wert auf der Grundlage von „dataType“ zurück, z. B. den Mittelwert für „Data_SmoothedMid“, den Drei-Punkte-Durchschnitt für „Data_SmoothedStandard“ oder richtungsbasierte Anpassungen wie den High-Close-Durchschnitt, wenn der Close-Wert den Open-Wert übersteigt, für „Data_SmoothedAdjusted“ oder den extremen High/Low/Close-Wert für „Data_SmoothedExtreme“. Für nicht geglättete Typen gibt ein weiterer Schalter direkte Werte wie Close für „Data_ClosePrice“ oder berechnete Durchschnittswerte wie den High-Low Mittelwertpreis für „Data_MidPoint“, den typischen Preis für „Data_StandardPrice“ oder richtungsbereinigte Preise, ähnlich den geglätteten Fällen, aber unter Verwendung von Rohdaten, zurück. Wenn kein Typ übereinstimmt, wird 0 als Rückfall zurückgegeben. Im OnCalculate-Ereignishandler können wir nun die Funktionen aufrufen, um die ersten Datenberechnungen durchzuführen.
int beginPos = (int)MathMax(prevProcessed - 1, 0); //--- Set begin pos for (; beginPos < barTotal && !_StopFlag; beginPos++) { //--- Loop bars double adjustedData = computeCustomAverage(DataSmoothingApproach, fetchChosenData(SourceData, opens, closes, highs, lows, beginPos, barTotal), DataSmoothingLength, beginPos, barTotal); //--- Compute adjusted data } return(beginPos); //--- Return begin pos
Hier setzen wir den OnCalculate-Ereignishandler für den Einzelzeitrahmenmodus fort, indem wir „beginPos“ auf das Maximum von „prevProcessed“ minus 1 oder 0 setzen, um sicherzustellen, dass wir mit der letzten verarbeiteten Bar oder dem Anfang beginnen, wenn keiner verarbeitet wurde. Wir durchlaufen eine Schleife über die Bars von „beginPos“ bis kleiner als „barTotal“, überprüfen das _StopFlag, um eine Unterbrechung zu ermöglichen, und berechnen für jede Bar „adjustedData“, indem wir zunächst den ausgewählten Preis über „fetchChosenData“ abrufen unter Verwendung des „SourceData“-Typs und der OHLC-Arrays der aktuellen Bar, dann Anwendung der gewählten Glättung mit „computeCustomAverage“ auf der Grundlage von „DataSmoothingApproach“ und „DataSmoothingLength“. Nach der Schleife geben wir „beginPos“ zurück, um die Anzahl der verarbeiteten Bars zu melden. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

An der Abbildung ist zu erkennen, dass die wesentlichen Berechnungsschritte des Indikators bereits umgesetzt sind. Als Nächstes müssen wir die RSI-Werte unter Verwendung der jeweiligen Varianten berechnen, um den endgültigen Indikator für die Kurven zu erhalten.
#define RSI_VARIANTS 1 //--- Define RSI variants double rsiComputeArray[][RSI_VARIANTS * 13]; //--- Declare RSI compute array #define DATA_SHIFT_POS 0 //--- Define data shift pos #define DATA_SHIFTS_POS 3 //--- Define data shifts pos #define SHIFT_POS 1 //--- Define shift pos #define ABS_SHIFT_POS 2 //--- Define abs shift pos #define RSI_COMPUTE_POS 1 //--- Define RSI compute pos #define RS_COMPUTE_POS 1 //--- Define RS compute pos //+------------------------------------------------------------------+ //| Compute RSI value | //+------------------------------------------------------------------+ double computeRsiValue(int rsiVariant, double currData, double rsiLen, int pos, int barCnt, int varIndex = 0) { if (ArrayRange(rsiComputeArray, 0) != barCnt) ArrayResize(rsiComputeArray, barCnt); //--- Resize array int arrayOffset = varIndex * 13; //--- Compute offset rsiComputeArray[pos][arrayOffset + DATA_SHIFT_POS] = currData; //--- Set data shift switch (rsiVariant) { //--- Switch variant case Variant_BasicStyle: { //--- Handle basic double factorAlpha = 1.0 / MathMax(rsiLen, 1); //--- Compute alpha if (pos < rsiLen) { //--- Check initial int cnt; //--- Initialize count double totalAbsShifts = 0; //--- Initialize total abs for (cnt = 0; cnt < rsiLen && (pos - cnt - 1) >= 0; cnt++) //--- Loop shifts totalAbsShifts += MathAbs(rsiComputeArray[pos - cnt][arrayOffset + DATA_SHIFT_POS] - rsiComputeArray[pos - cnt - 1][arrayOffset + DATA_SHIFT_POS]); //--- Accumulate abs shifts rsiComputeArray[pos][arrayOffset + SHIFT_POS] = (rsiComputeArray[pos][arrayOffset + DATA_SHIFT_POS] - rsiComputeArray[0][arrayOffset + DATA_SHIFT_POS]) / MathMax(cnt, 1); //--- Set shift rsiComputeArray[pos][arrayOffset + ABS_SHIFT_POS] = totalAbsShifts / MathMax(cnt, 1); //--- Set abs shift } else { //--- Handle non-initial double dataShift = rsiComputeArray[pos][arrayOffset + DATA_SHIFT_POS] - rsiComputeArray[pos - 1][arrayOffset + DATA_SHIFT_POS]; //--- Compute shift rsiComputeArray[pos][arrayOffset + SHIFT_POS] = rsiComputeArray[pos - 1][arrayOffset + SHIFT_POS] + factorAlpha * (dataShift - rsiComputeArray[pos - 1][arrayOffset + SHIFT_POS]); //--- Update shift rsiComputeArray[pos][arrayOffset + ABS_SHIFT_POS] = rsiComputeArray[pos - 1][arrayOffset + ABS_SHIFT_POS] + factorAlpha * (MathAbs(dataShift) - rsiComputeArray[pos - 1][arrayOffset + ABS_SHIFT_POS]); //--- Update abs shift } return(50.0 * (rsiComputeArray[pos][arrayOffset + SHIFT_POS] / MathMax(rsiComputeArray[pos][arrayOffset + ABS_SHIFT_POS], DBL_MIN) + 1)); //--- Return RSI } case Variant_GradualStyle: { //--- Handle gradual double posSum = 0, negSum = 0; //--- Initialize sums for (int offset = 0; offset < (int)rsiLen && (pos - offset - 1) >= 0; offset++) { //--- Loop offsets double dataDiff = rsiComputeArray[pos - offset][arrayOffset + DATA_SHIFT_POS] - rsiComputeArray[pos - offset - 1][arrayOffset + DATA_SHIFT_POS]; //--- Compute diff if (dataDiff > 0) posSum += dataDiff; //--- Accumulate positive else negSum -= dataDiff; //--- Accumulate negative } if (pos < 1) rsiComputeArray[pos][arrayOffset + RSI_COMPUTE_POS] = 50; //--- Set initial else rsiComputeArray[pos][arrayOffset + RSI_COMPUTE_POS] = rsiComputeArray[pos - 1][arrayOffset + RSI_COMPUTE_POS] + (1 / MathMax(rsiLen, 1)) * (100 * posSum / MathMax(posSum + negSum, DBL_MIN) - rsiComputeArray[pos - 1][arrayOffset + RSI_COMPUTE_POS]); //--- Compute gradual return(rsiComputeArray[pos][arrayOffset + RSI_COMPUTE_POS]); //--- Return value } case Variant_QuickStyle: { //--- Handle quick double posSum = 0, negSum = 0; //--- Initialize sums for (int offset = 0; offset < (int)rsiLen && (pos - offset - 1) >= 0; offset++) { //--- Loop offsets double dataDiff = rsiComputeArray[pos - offset][arrayOffset + DATA_SHIFT_POS] - rsiComputeArray[pos - offset - 1][arrayOffset + DATA_SHIFT_POS]; //--- Compute diff if (dataDiff > 0) posSum += dataDiff; //--- Accumulate positive else negSum -= dataDiff; //--- Accumulate negative } return(100 * posSum / MathMax(posSum + negSum, DBL_MIN)); //--- Return quick RSI } case Variant_EhlersStyle: { //--- Handle Ehlers double posSum = 0, negSum = 0; //--- Initialize sums rsiComputeArray[pos][arrayOffset + DATA_SHIFTS_POS] = (pos > 2) ? (rsiComputeArray[pos][arrayOffset + DATA_SHIFT_POS] + 2.0 * rsiComputeArray[pos - 1][arrayOffset + DATA_SHIFT_POS] + rsiComputeArray[pos - 2][arrayOffset + DATA_SHIFT_POS]) / 4.0 : currData; //--- Compute shifts for (int offset = 0; offset < (int)rsiLen && (pos - offset - 1) >= 0; offset++) { //--- Loop offsets double dataDiff = rsiComputeArray[pos - offset][arrayOffset + DATA_SHIFTS_POS] - rsiComputeArray[pos - offset - 1][arrayOffset + DATA_SHIFTS_POS]; //--- Compute diff if (dataDiff > 0) posSum += dataDiff; //--- Accumulate positive else negSum -= dataDiff; //--- Accumulate negative } return(50 * (posSum - negSum) / MathMax(posSum + negSum, DBL_MIN) + 50); //--- Return Ehlers RSI } case Variant_CuttlerStyle: { //--- Handle Cuttler double posSum = 0; //--- Initialize positive sum double negSum = 0; //--- Initialize negative sum for (int offset = 0; offset < (int)rsiLen && (pos - offset - 1) >= 0; offset++) { //--- Loop offsets double dataDiff = rsiComputeArray[pos - offset][arrayOffset + DATA_SHIFT_POS] - rsiComputeArray[pos - offset - 1][arrayOffset + DATA_SHIFT_POS]; //--- Compute diff if (dataDiff > 0) posSum += dataDiff; //--- Accumulate positive else negSum -= dataDiff; //--- Accumulate negative } rsiComputeArray[pos][varIndex + RSI_COMPUTE_POS] = 100.0 - 100.0 / (1.0 + posSum / MathMax(negSum, DBL_MIN)); //--- Compute Cuttler return(rsiComputeArray[pos][varIndex + RSI_COMPUTE_POS]); //--- Return value } case Variant_HarrisStyle: { //--- Handle Harris double avgPos = 0, avgNeg = 0, posCnt = 0, negCnt = 0; //--- Initialize averages and counts for (int offset = 0; offset < (int)rsiLen && (pos - offset - 1) >= 0; offset++) { //--- Loop offsets double dataDiff = rsiComputeArray[pos - offset][varIndex + DATA_SHIFT_POS] - rsiComputeArray[pos - offset - 1][varIndex + DATA_SHIFT_POS]; //--- Compute diff if (dataDiff > 0) { //--- Handle positive avgPos += dataDiff; //--- Accumulate positive posCnt++; //--- Increment positive count } else { //--- Handle negative avgNeg -= dataDiff; //--- Accumulate negative negCnt++; //--- Increment negative count } } if (posCnt != 0) avgPos /= posCnt; //--- Average positive if (negCnt != 0) avgNeg /= negCnt; //--- Average negative rsiComputeArray[pos][varIndex + RSI_COMPUTE_POS] = 100 - 100 / (1.0 + (avgPos / MathMax(avgNeg, DBL_MIN))); //--- Compute Harris return(rsiComputeArray[pos][varIndex + RSI_COMPUTE_POS]); //--- Return value } case Variant_RsxStyle: { //--- Handle RSX double kgVal = 3.0 / (2.0 + rsiLen), hgVal = 1.0 - kgVal; //--- Compute kg and hg if (pos < rsiLen) { //--- Check initial for (int offset = 1; offset < 13; offset++) rsiComputeArray[pos][offset + arrayOffset] = 0; //--- Zero offsets return(50); //--- Return initial } double motion = rsiComputeArray[pos][DATA_SHIFT_POS + arrayOffset] - rsiComputeArray[pos - 1][DATA_SHIFT_POS + arrayOffset]; //--- Compute motion double absMotion = MathAbs(motion); //--- Compute abs motion for (int offset = 0; offset < 3; offset++) { //--- Loop offsets int subOffset = offset * 2; //--- Compute sub offset rsiComputeArray[pos][arrayOffset + subOffset + 1] = kgVal * motion + hgVal * rsiComputeArray[pos - 1][arrayOffset + subOffset + 1]; //--- Update 1 rsiComputeArray[pos][arrayOffset + subOffset + 2] = kgVal * rsiComputeArray[pos][arrayOffset + subOffset + 1] + hgVal * rsiComputeArray[pos - 1][arrayOffset + subOffset + 2]; //--- Update 2 motion = 1.5 * rsiComputeArray[pos][arrayOffset + subOffset + 1] - 0.5 * rsiComputeArray[pos][arrayOffset + subOffset + 2]; //--- Update motion rsiComputeArray[pos][arrayOffset + subOffset + 7] = kgVal * absMotion + hgVal * rsiComputeArray[pos - 1][arrayOffset + subOffset + 7]; //--- Update 7 rsiComputeArray[pos][arrayOffset + subOffset + 8] = kgVal * rsiComputeArray[pos][arrayOffset + subOffset + 7] + hgVal * rsiComputeArray[pos - 1][arrayOffset + subOffset + 8]; //--- Update 8 absMotion = 1.5 * rsiComputeArray[pos][arrayOffset + subOffset + 7] - 0.5 * rsiComputeArray[pos][arrayOffset + subOffset + 8]; //--- Update abs motion } return(MathMax(MathMin((motion / MathMax(absMotion, DBL_MIN) + 1.0) * 50.0, 100.00), 0.00)); //--- Return RSX } } return(0); //--- Return zero }
Hier definieren wir „RSI_VARIANTS“ als 1, um die Anzahl der RSI-Berechnungsvarianten anzugeben, und deklarieren „rsiComputeArray“ als mehrdimensionales Array mit 13 Spalten pro Variante, um Zwischenberechnungen über Bars hinweg zu speichern. Wir setzen Positionskonstanten wie „DATA_SHIFT_POS“ auf 0 für aktuelle Daten, „SHIFT_POS“ auf 1 für Nettoänderungen, „ABS_SHIFT_POS“ auf 2 für absolute Änderungen, „DATA_SHIFTS_POS“ auf 3 für geglättete Verschiebungen in einigen Varianten und „RSI_COMPUTE_POS“ (alias 1) für gespeicherte RSI-Werte.
In der Funktion „computeRsiValue“ wird die Größe von „rsiComputeArray“ bei Bedarf an „barCnt“ angepasst, ein „arrayOffset“ berechnet, indem „varIndex“ mit 13 multipliziert wird, und „currData“ an der Datenverschiebungsposition für die aktuelle Bar gespeichert. Wir verwenden einen Schalter auf „rsiVariant“, um den RSI anders zu berechnen. Für den Basisstil wird ein Alpha-Faktor von 1 über „rsiLen“ berechnet, und für anfängliche Bars, die kürzer als die Länge sind, werden die absoluten Verschiebungen summiert und die durchschnittlichen Netto- und Abs-Verschiebungen festgelegt; andernfalls werden sie exponentiell mit der aktuellen Datenverschiebung aktualisiert, wobei das 50-fache (Netto über Abs plus 1) zurückgegeben wird.
Für den graduellen Stil summieren wir die positiven und negativen Differenzen über den Zeitraum, setzen den Ausgangswert auf 50, aktualisieren dann schrittweise den vorherigen RSI mit einer gewichteten Anpassung auf der Grundlage der relativen Stärke und geben ihn zurück. Für den schnellen Stil summieren wir positive und negative Differenzen auf ähnliche Weise, wobei wir 100-mal positive Differenzen über die Gesamtdifferenz erhalten. Für Ehlers' Stil glätten wir die Daten mit einem Vier-Punkte-Durchschnitt, wenn sie über Bar 2 hinausgehen, summieren die Differenzen der geglätteten Werte und geben 50 plus 50 mal Netto über Gesamt.
Beim Cuttler-Stil werden positive und negative Differenzen addiert, 100 minus 100 über (1 plus positive/negative) berechnet, gespeichert und zurückgegeben. Bei der Harris-Methode werden die positiven und negativen Ergebnisse getrennt gesammelt und gezählt, der Durchschnitt gebildet, wenn es Zählungen gibt, und dann eine ähnliche Formel wie bei Cuttler berechnet, jedoch mit Durchschnittswerten, Speicherung und Rückgabe. Für den RSX-Stil berechnen wir die Verstärkungsfaktoren kg und hg, setzen die Array-Offsets für die ersten Bars unter der Länge auf Null und geben 50 zurück; andernfalls berechnen wir die Bewegung und die absolute Bewegung, wenden doppelte EMA-ähnliche Aktualisierungen in einer Schleife für drei Durchgänge auf beide an und geben 50-mal (Bewegung über abs plus 1) zwischen 0 und 100 geklammert zurück. Wenn keine Variante passt, wird 0 zurückgegeben. Wir können diese Funktion nun in unserer Schleife aufrufen, um die RSI-Daten zu berechnen.
for (; beginPos < barTotal && !_StopFlag; beginPos++) { //--- Loop bars double adjustedData = computeCustomAverage(DataSmoothingApproach, fetchChosenData(SourceData, opens, closes, highs, lows, beginPos, barTotal), DataSmoothingLength, beginPos, barTotal); //--- Compute adjusted data rsiCurveValues[beginPos] = computeRsiValue(ChosenRsiVariant, adjustedData, RsiLength, beginPos, barTotal); //--- Compute RSI value if (DynamicBoundaryLength <= 1) { //--- Check static boundary topBoundaryValues[beginPos] = TopBoundaryPercent; //--- Set top boundary bottomBoundaryValues[beginPos] = BottomBoundaryPercent; //--- Set bottom boundary centerLineValues[beginPos] = (topBoundaryValues[beginPos] + bottomBoundaryValues[beginPos]) / 2; //--- Set center line } else { //--- Handle dynamic double lowestVal = rsiCurveValues[beginPos]; //--- Set initial low double highestVal = rsiCurveValues[beginPos]; //--- Set initial high for (int offset = 1; offset < DynamicBoundaryLength && beginPos - offset >= 0; offset++) { //--- Loop offsets lowestVal = MathMin(rsiCurveValues[beginPos - offset], lowestVal); //--- Update low highestVal = MathMax(rsiCurveValues[beginPos - offset], highestVal); //--- Update high } double valRange = highestVal - lowestVal; //--- Compute range topBoundaryValues[beginPos] = lowestVal + TopBoundaryPercent * valRange / 100.0; //--- Set top boundary bottomBoundaryValues[beginPos] = lowestVal + BottomBoundaryPercent * valRange / 100.0; //--- Set bottom boundary centerLineValues[beginPos] = lowestVal + 0.5 * valRange; //--- Set center line } switch (HueAdjustmentOn) { //--- Switch hue condition case Hue_OnBoundaryCrossing: //--- Handle boundary crossing rsiHueValues[beginPos] = (rsiCurveValues[beginPos] > topBoundaryValues[beginPos]) ? 1 : (rsiCurveValues[beginPos] < bottomBoundaryValues[beginPos]) ? 2 : 0; //--- Set hue break; case Hue_OnCenterCrossing: //--- Handle center crossing rsiHueValues[beginPos] = (rsiCurveValues[beginPos] > centerLineValues[beginPos]) ? 1 : (rsiCurveValues[beginPos] < centerLineValues[beginPos]) ? 2 : 0; //--- Set hue break; default: //--- Handle default rsiHueValues[beginPos] = (beginPos > 0) ? (rsiCurveValues[beginPos] > rsiCurveValues[beginPos - 1]) ? 1 : (rsiCurveValues[beginPos] < rsiCurveValues[beginPos - 1]) ? 2 : 0 : 0; //--- Set hue } areaFillTop[beginPos] = rsiCurveValues[beginPos]; //--- Set top fill areaFillBottom[beginPos] = (rsiCurveValues[beginPos] > topBoundaryValues[beginPos]) ? topBoundaryValues[beginPos] : (rsiCurveValues[beginPos] < bottomBoundaryValues[beginPos]) ? bottomBoundaryValues[beginPos] : rsiCurveValues[beginPos]; //--- Set bottom fill } processedBarCounts[barTotal - 1] = MathMax(barTotal - prevProcessed + 1, 1); //--- Set processed counts
Wir fahren in der Schleife über die Bars im Einzelzeitrahmenmodus des „OnCalculate“-Ereignishandlers fort, indem wir den RSI-Wert für jede Bar berechnen. Nachdem wir die „adjustedData“ aus der Glättung erhalten haben, übergeben wir sie zusammen mit der ausgewählten „ChosenRsiVariant“, der „RsiLength“ und den Bardetails an „computeRsiValue“ und speichern das Ergebnis in „rsiCurveValues“ an der aktuellen Position. Als Nächstes bestimmen wir die Begrenzungsebenen: Wenn „DynamicBoundaryLength“ 1 oder weniger ist, was auf statische Begrenzungen hinweist, setzen wir „topBoundaryValues“ auf „TopBoundaryPercent“, „bottomBoundaryValues“ auf „BottomBoundaryPercent“ und „centerLineValues“ auf ihren Mittelwert.
Bei dynamischen Grenzen, wenn die Länge 1 übersteigt, werden der niedrigste und der höchste Wert mit dem aktuellen RSI initialisiert, dann wird eine Schleife über die vorherigen Bars bis zur Länge gezogen, um den Minimal- und Maximalwert mithilfe der Funktionen MathMin und MathMax zu aktualisieren. Wir berechnen den Bereich als höchsten minus niedrigsten Wert, setzen dann die obere Grenze als niedrigsten Wert plus den oberen Prozentsatz des Bereichs, den unteren Wert als niedrigsten Wert plus den unteren Prozentsatz und die Mitte als niedrigsten Wert plus die Hälfte des Bereichs. Anschließend wird der Farbindex in „rsiHueValues“ auf der Grundlage von „HueAdjustmentOn“ festgelegt: für Grenzübergänge wird 1 zugewiesen, wenn der RSI die Obergrenze überschreitet, 2, wenn er die Untergrenze unterschreitet, sonst 0; für das Kreuzen des Mittelwerts ähnlich, aber relativ zur Mittellinie; standardmäßig wird mit dem vorherigen RSI für die Richtung verglichen – 1 für steigende, 2 für fallende Kurse, sonst 0, mit 0 für die erste Bar. Schließlich konfigurieren wir die Füllbereiche, indem wir „areaFillTop“ auf den aktuellen RSI-Wert und „areaFillBottom“ auf die obere Begrenzung setzen, wenn der RSI überkauft ist, die untere Begrenzung, wenn er überverkauft ist, oder auf den RSI selbst setzen.
Nach der Schleife aktualisieren wir „processedBarCounts“ beim letzten Bartindex auf das Maximum der in diesem Aufruf verarbeiteten Bars, plus 1 oder nur 1, wobei wir den Berechnungsumfang verfolgen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Aus dem Bild geht hervor, dass der Indikator erfolgreich initialisiert, berechnet und gezeichnet wurde. Als nächster Schritt folgt die Verarbeitung der Warnmeldungen. Wir werden die Logik einfach in einer Funktion unterbringen und sie im Event-Handler zur Verarbeitung aufrufen.
//+------------------------------------------------------------------+ //| Process alert triggers | //+------------------------------------------------------------------+ void processAlertTriggers(const datetime& timeStamps[], double& hueTrends[], int barTotal) { if (!ActivateNotifications) return; //--- Check notifications int notifyIndex = barTotal - 1; //--- Set notify index if (!NotifyOnActiveBar) notifyIndex = barTotal - 2; //--- Adjust if not active datetime notifyStamp = timeStamps[notifyIndex]; //--- Get stamp if (hueTrends[notifyIndex] != hueTrends[notifyIndex - 1]) { //--- Check hue change if (hueTrends[notifyIndex] == 1) triggerNotification(notifyStamp, "rising"); //--- Trigger rising if (hueTrends[notifyIndex] == 2) triggerNotification(notifyStamp, "falling"); //--- Trigger falling } } //+------------------------------------------------------------------+ //| Trigger notification | //+------------------------------------------------------------------+ void triggerNotification(datetime stamp, string trend) { static string prevTrend = "none"; //--- Initialize previous trend static datetime prevStamp; //--- Initialize previous stamp if (prevTrend != trend || prevStamp != stamp) { //--- Check change prevTrend = trend; //--- Update trend prevStamp = stamp; //--- Update stamp string notifyText = convertTimeframeToText(_Period) + " " + _Symbol + " at " + TimeToString(TimeLocal(), TIME_SECONDS) + describeRsiVariant(ChosenRsiVariant) + " trend shifted to " + trend; //--- Format text Alert(notifyText); //--- Send alert } }
Hier fügen wir die Funktion „processAlertTriggers“ hinzu, um Benachrichtigungen aufgrund von Farbtonänderungen in der RSI-Kurve zu verarbeiten. Wenn „ActivateNotifications“ falsch ist, wird die Funktion sofort beendet. Wir setzen den „notifyIndex“ auf die letzte Bar oder den davor, wenn „NotifyOnActiveBar“ falsch ist, um eine Alarmierung bei unvollständigen Bars zu vermeiden, und rufen dann den Zeitstempel bei diesem Index ab. Wir prüfen, ob sich der Farbtonwert bei „notifyIndex“ vom vorherigen unterscheidet, und wenn ja, rufen wir „triggerNotification“ mit dem Zeitstempel und „rising“ für Farbton 1 oder „falling“ für Farbton 2 auf.
In der Funktion „triggerNotification“ verwenden wir statische Variablen, um den vorherigen Trend und den Zeitstempel zu verfolgen. Weicht der aktuelle Trend oder der Zeitstempel von den gespeicherten ab, aktualisieren wir diese, formatieren einen Benachrichtigungstext mit „convertTimeframeToText“ für die Periode, das Symbol, die lokale Zeit via TimeToString, die RSI-Variantenbeschreibung und die Trendverschiebungsmeldung und senden ihn dann mit der Alert-Funktion. Wenn wir es im Event-Handler aufrufen, erhalten wir folgendes Ergebnis.

Aus dem Bild ist ersichtlich, dass wir den Indikator berechnen, die Plots darstellen und das Warnsystem aktivieren, wenn es aktiviert ist, und somit unsere Ziele erreichen. Bleibt nur noch der Backtest der Programms, und das wird im nächsten Abschnitt behandelt.
Backtests
Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Bild im Bitmap-Format Graphics Interchange Format (GIF).

Schlussfolgerung
Abschließend haben wir eine verbesserte Version des Relative Strength Index (RSI) Indikators in MQL5 entwickelt, die mehrere Berechnungsvarianten, anpassbare Datenquellen mit Glättungsansätzen und dynamische Grenzen für überkaufte und überverkaufte Levels unterstützt. Wir haben die Farbwechsel für farbkodierte Visualisierungen, optionale Benachrichtigungen für Trendänderungen und die Verarbeitung von Daten mit mehreren Zeitrahmen mit einer Interpolation für breitere Analysen integriert. Mit diesem dynamischen RSI-Indikator sind Sie bestens gerüstet, um Ihre technische Analyse zu verbessern und Ihr Trading weiter zu individualisieren. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21083
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.
Vom Einsteiger zum Experten: Statistische Validierung von Angebots- und Nachfragezonen
Integration externer Anwendungen mit MQL5 Community OAuth
Entwicklung eines dynamischen Multi-Pair-EA (Teil 6): Adaptive Spread-Sensitivität für hochfrequente Symbolwechsel
Larry Williams‘ Marktgeheimnisse (Teil 9): Mit Mustern zum Gewinn
- 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.