Anwendung des L1-Trendfilters in MetaTrader 5
- die langfristige Dynamik der Daten erhalten bleibt;
- kurzfristige Schwankungen und Rauschen unterdrückt werden;
- strukturelle Bruchpunkte (Änderungen der Trendsteigung) automatisch erkannt werden.

Inhalt
- Einführung
- 1. Problemformulierung der Trendfilterung
1.1. Hodrick-Prescott-Filter
1.2. Methode der L1-Trendfilterung
1.3. Die Rolle des Regularisierungsparameters λ
1.4. Geometrische Interpretation
1.5. Algorithmus zur Berechnung von λmax - 2. MQL5-Methoden zur Berechnung des L1-Trends
2.1. L1TrendFilterLambdaMax
2.2. L1TrendFilter - 3. Beispiele für L1-Trendberechnungen
3.1. L1-Trend bei synthetischen Daten (Random Walk)
3.2. L1-Trend für S&P 500-Kursreihen
3.3. Skalierungseigenschaften von λmax
3.3.1. Numerisches Experiment zur Brownschen Bewegung
3.3.2. Skalierung für Finanzzeitreihen
3.3.3. Praktische Implikationen der Skalierung
3.4. L1-Trendindikatoren
3.4.1. L1TrendFilter.mq5 – L1-Trendindikator
3.4.2. L1TrendFilterSlope.mq5 – L1-Indikator der Trendsteigung
3.4.3. L1TrendFilterSlopeSign.mq5 – Indikator der Trendrichtung - 3.4.4. Volatilitätsindikatoren auf der Grundlage des L1-Trends
3.4.4.1. L1Volatility.mq5 – Indikator für Restvolatilität
3.4.4.2. L1VolatilitySmoothed.mq5 – Indikator für geglättete Restvolatilität
3.4.4.3. L1VolatilityAbsolute.mq5 – Indikator für absolute Volatilität
3.4.4.4. L1VolatilityNormalized.mq5 – Indikator für normalisierte Volatilität
3.4.4.5. L1VolatilityNormalizedSmoothed.mq5 – Indikator für geglättete normalisierte Volatilität
3.4.4.6. L1VolatilityRegime.mq5 – Erkennung von Marktregimen anhand der Volatilität -
3.5. Anwendung des L1-Trends in Handelsstrategien
3.5.1. Strategie des gleitenden Durchschnitts
3.5.1.1. Allgemeine Methodik zur Bewertung der Effizienz der L1-Trendfilter
3.5.1.2. Ergebnisse für die Strategie des gleitenden Durchschnitts
3.5.2. MACD-Strategie
3.5.2.1. Ergebnisse für die MACD-Strategie
3.5.3. ADX-Strategie
3.5.3.1. Ergebnisse für die ADX-Strategie
3.5.4. EMA-Strategie
3.5.4.1. Ergebnisse für die EMA-Strategie
3.5.5. Zusammenfassung über die Verwendung des L1-Filters in MovingAverage-, MACD-, ADX- und EMA-Handelsstrategien - Schlussfolgerung
Einführung
Finanzzeitreihen sind durch ein hohes Maß an Rauschen, häufige Ausreißer und wechselnde Marktregimes gekennzeichnet. In praktischen Handelssystemen zeigt sich dies auf einfache und messbare Weise: Klassische „glatte“ Filter (gleitende Durchschnitte, HP) hinken hinterher, verwischen die Momente von Steigungsveränderungen und interpretieren lokale Korrekturen oft als Umkehrungen – in der Folge steigt die Zahl der falschen Ein- und Ausstiege, der Profitfaktor sinkt und der Drawdown wächst. Darüber hinaus ist die Auswahl des Regularisierungsparameters λ in der Regel auf eine manuelle Abstimmung beschränkt und lässt sich nicht gut auf andere Instrumente, Zeitrahmen und Historienlängen übertragen.Dieser Beitrag schlägt eine praktische Lösung für diese Probleme vor, die auf dem L1-Trendfilter basiert: Die Optimierung mit L1-Regularisierung der zweiten Differenzen ergibt automatisch eine stückweise lineare Approximation mit expliziten Bruchpunkten. Die wichtigsten Vorteile sind eine klare Interpretation der Bruchpunkte als Regimewechsel, die Möglichkeit, die Skala der Regularisierung über die Berechnung von λmax und den Übergang zu einem relativen Parameter λ = coef · λmax festzulegen, sowie eine lineare Berechnungskomplexität, die für die Implementierung in MQL5 geeignet ist.
Wir präsentieren nicht nur die Theorie, sondern auch einen vollständigen praxisorientierten Leitfaden: Methoden zur Berechnung von λmax und des L1-Trends, drei Indikatoren (Trend, Steigung, Steigungsvorzeichen), sieben L1-Trend-Volatilitätsindikatoren, Integration in Expert Advisors und ein reproduzierbares Testprotokoll (vier Filtermodi, Balance/Equity-Export und Visualisierung).
1. Formulierung des Problems der Trendfilterung
Wir betrachten eine skalare Zeitreihe, die als Summe von zwei Komponenten dargestellt wird:
,
wobei
die Trendkomponente und
ein Rauschen oder eine unregelmäßige Komponente ist.
Ziel ist es, den Trend
aus den beobachteten Daten
zu schätzen.
Das Problem kann als Kompromiss zwischen der Genauigkeit der Originaldaten und der Glättung des geschätzten Trends ausgedrückt werden.
1.1. Hodrick-Prescott-Filter
Der Hodrick-Prescott-Filter definiert den Trend als die Lösung des Minimierungsproblems:
,
wobei der Parameter λ den Grad der Glättung steuert.
Die wichtigsten Eigenschaften des HP-Filters:
- Linearität in Bezug auf die Daten;
- Berechnungskomplexität O(n);
- Für kleine λ nähert sich der Trend den ursprünglichen Daten an;
- Für große λ tendiert der Trend zur besten linearen Annäherung.
Der HP-Filter erzeugt jedoch immer einen glatten Trend und erkennt starke Änderungen der Steigung nur schlecht.
1.2. L1-Trendfilterung
Die Grundidee der L1-Trendfilterung besteht darin, einen Trend zu finden, der den Originaldaten nahekommt, dabei aber möglichst wenige Änderungen der Steigung aufweist. Im Gegensatz zu klassischen Glättungsmethoden, die die quadratische Krümmung minimieren, minimiert der L1-Ansatz die Summe der Absolutwerte der zweiten Differenzen.
Dies führt zu einem grundlegend anderen Ergebnis:
- die meisten zweiten Differenzen werden zu Null,
- der Trend wird automatisch in lineare Segmente aufgeteilt.
Der L1-Filter versucht also nicht, den Trend zu glätten, sondern findet stattdessen die minimale Anzahl von Strukturveränderungen, die die beobachteten Daten erklären. Dadurch eignet sich die Methode besonders für Finanzzeitreihen, deren Dynamik häufig aus einer Abfolge von quasi-linearen Wachstums- und Abschwungphasen besteht.
Beim L1-Trendfilter wird der quadratische Strafterm für die zweiten Differenzen durch die L1-Norm ersetzt, und der Trend wird als Lösung eines konvexen Optimierungsproblems definiert:

In Matrixform:

wobei:
- y – Eingangszeitreihe;
- x – der geschätzte Trend;
- D – Zweite-Differenz-Matrix;
- λ>=0 – Regularisierungsparameter.
Die Verwendung der L1-Norm führt zu einem grundlegend anderen Ergebnis: Viele zweite Differenzen werden zu Null, was bedeutet, dass der Trend stückweise linear ist.
Die zweite Differenz ist wie folgt definiert:

Wenn
, dann liegen die Punkte
auf einer Geraden.
Folglich bedeutet eine zweite Differenz von Null einen linearen Trendabschnitt, während eine zweite Differenz ungleich Null einem Bruchpunkt entspricht. Die L1-Norm fördert die Spärlichkeit des Vektors Dx, was bedeutet, dass die meisten zweiten Differenzen zu Null werden. Dies bedeutet, dass der Trend in den entsprechenden Intervallen linear ist. Punkte, bei denen die zweite Differenz nicht Null ist, werden als Bruchpunkte des Trends interpretiert.
So konstruiert die L1-Trendfiltermethode den Trend automatisch als eine Reihe von linearen Segmenten, die an Punkten struktureller Veränderungen verbunden sind.
Die wichtigsten Eigenschaften des L1-Trendfilters:
- Der Trend besteht aus linearen Segmenten;
- Bruchpunkte werden als strukturelle Veränderungen in den Zeitreihen interpretiert;
- Bei λ = 0 stimmt der Trend mit den ursprünglichen Daten überein;
- Für ausreichend große λ wird der Trend genau die beste lineare Annäherung;
- Die Rechenkomplexität bleibt linear in der Anzahl der Beobachtungen.
1.3. Die Rolle des Regularisierungsparameters λ
Der Parameter λ steuert den Kompromiss zwischen Approximationsgenauigkeit und Trendkomplexität:
| Wert von λ | Art der Lösung |
|---|---|
| λ=0 | x=y, keine Glättung |
| Kleines λ | Schwache Glättung, viele Bruchpunkte |
| Mittelgroßes λ | Stückweise linearer Trend |
| Großes λ | Nahezu linearer Trend |
| λ≥λmax | Streng linearer Trend |
Tabelle 1. Abhängigkeit des L1-Trends von dem Regularisierungsparameter λ
Somit steuert λ die Anzahl und die Lage der Bruchpunkte eines Trend.
1.4. Geometrische Interpretation des Problems
Der gewünschte Trend x kann als ein Punkt in einem n-dimensionalen Raum betrachtet werden. Der erste Term der Zielfunktion, der für die Genauigkeit der Annäherung verantwortlich ist, definiert eine euklidische Kugel, deren Mittelpunkt der Beobachtungspunkt y ist: Je näher x an y liegt, desto kleiner ist der Fehler.Der Regularisierungsterm mit der L1-Norm der zweiten Differenzen definiert eine konvexe polyedrische Menge (Polyeder). Im Gegensatz zu glatten Ellipsoiden, die bei der L2-Regularisierung entstehen, hat dieses Polyeder scharfe Scheitelpunkte. Diese Scheitelpunkte entsprechen Situationen, in denen einige zweite Differenzen des Trends gleich Null sind.
Gerade die scharfen Ecken in der L1-Norm führen zu spärlichen Lösungen: Die optimale Lösung liegt tendenziell an einem Eckpunkt des Polyeders, an dem nur einige Nebenbedingungen aktiv sind. Dies bedeutet, dass die meisten zweiten Differenzen zu Null werden, und der Trend nimmt automatisch eine stückweise lineare Form an.
Die optimale Lösung entspricht dem ersten Berührungspunkt zwischen der euklidischen Kugel und dem L1-Polyeder. An diesem Punkt besteht der Trend aus linearen Segmenten, die an einer begrenzten Anzahl von Bruchpunkten miteinander verbunden sind.
Der Parameter λmax entspricht der Situation, in der die euklidische Kugel das L1-Polyeder nicht an einem Scheitelpunkt, sondern entlang des Unterraums der linearen Funktionen berührt. In diesem Fall sind alle zweiten Differenzen gleich Null, und der Trend ist streng linear.
Für λ ≥ λmax wird keine der L1-Beschränkungen aktiv, sodass weitere Erhöhungen der Regularisierung die Lösung nicht verändern und der Trend linear bleibt.
1.5. Algorithmus zur Berechnung von λmax
Im Folgenden wird die Berechnung des maximalen Regularisierungsparameters λmax für einen Eingangsvektor y der Länge N beschrieben.
1. Es wird die Zweitdifferenzmatrix D der Größe (N-2)×N konstruiert:

2. Der Krümmungsvektor Dy wird berechnet.
3. Das lineare Gleichungssystem wird gelöst:
![]()
4. Anschließend wird das betragsmäßig größte Element des Vektors v bestimmt.

Für Finanzzeitreihen ist der Parameter λmax von großer praktischer Bedeutung:
- Er ermöglicht die Normalisierung des Regularisierungsparameters;
- Er ermöglicht die Wahl von λ unabhängig von der Datenskala;
- Er vereinfacht den Vergleich zwischen verschiedenen Zeitreihen;
- Er erlaubt es, λ als einen Bruchteil der maximalen Regularisierung zu interpretieren.
Die Verwendung eines relativen Parameters der Form: λ=coef_lambda_max⋅λmax, wobei coef_lambda_max ∈ (0,1), vereinfacht die praktische Anwendung erheblich.
In den folgenden Beispielen für Indikatoren und Expert Advisors wird λ in Einheiten von λmax verwendet, während in den Parametereinstellungen der Multiplikator coef_lambda_max angegeben wird.
2. MQL5-Methoden zur Berechnung des L1-Trends
Für die praktische Anwendung des L1-Trendfilters werden zwei Methoden für Vektoren vom Typ double und float implementiert.
- L1TrendFilterLambdaMax berechnet den maximalen Regularisierungsparameter;
- L1TrendFilter berechnet den L1-Trend für einen bestimmten Wert des Regularisierungsparameters λ, der auch in Einheiten von λmax angegeben werden kann.
2.1. L1TrendFilterLambdaMax
Methode zur Berechnung des maximalen Regularisierungsparameters λmax für einen Datenvektor.
Berechnung für vector<double>:
bool vector::L1TrendFilterLambdaMax( double &lambda_max // the maximum value of the regularization parameter lambda )Berechnung für vector<float>:
bool vectorf::L1TrendFilterLambdaMax( float &lambda_max // the maximum value of the regularization parameter lambda );
Parameter
lambda
[out] Der maximale Wert des Regularisierungsparameters λmax oder -1 im Falle eines Fehlers.
Rückgabewert
Gibt bei Erfolg true zurück.
Hinweis
Der Speicherbedarf wächst linear mit der Vektorgröße.
2.2. L1TrendFilter
Methode zur Berechnung des L1-Trends für einen Datenvektor.
Berechnung für vector<double>:
bool vector::L1TrendFilter( double lambda, // regularization parameter bool relative, // flag indicating lambda is in λmax units vector& result // output vector with L1 filtering result );
Berechnung für vector<float>:
bool vectorf::L1TrendFilter( float lambda, // regularization parameter bool relative, // flag indicating lambda is in λmax units vectorf& result // output vector with L1 filtering result );
Parameter
lambda
[in] Wert des Regularisierungsparameters lambda (bei relative = true wird lambda im Bereich [0, 1] als Bruchteil von λmax definiert).
relative
[in] Flag, das angibt, wie λ angegeben wird. Ist dies der Fall, wird λ in Einheiten von λmax angegeben; andernfalls wird der absolute Wert verwendet.
result
[out] Vektor, der das Ergebnis des L1-Filters enthält.
Rückgabewert
Gibt bei Erfolg true zurück.
Hinweis
Der Speicherbedarf wächst linear mit der Vektorgröße.
Empfohlene Bereiche für λ (relativer Modus).
| λ-Multiplikator | Ergebnis |
|---|---|
| 0.005 – 0.015 | fast L2, verrauscht |
| 0.02 – 0.04 | Mikro-Segmente |
| 0.04 – 0.07 | optimal für Signale |
| 0.07 – 0.12 | mittelfristige Trends |
| 0.12 – 0.25 | Marktregime |
| > 0.3 | wenige Segmente |
Tabelle 2. Arbeitsbereiche von λ in Einheiten von λmax
Für praktische Anwendungen wird empfohlen, Multiplikatoren im Bereich von 0,04 – 0,25 zu verwenden.
3. Beispiele für die Anwendung
In diesem Abschnitt befassen wir uns mit L1-Trendberechnungen für simulierte Daten einer Brownschen Bewegung und für S&P-500-Kursdaten sowie mit den Skalierungseigenschaften von λmax sowohl für Brownian-Motion- als auch für FOREX-Marktdaten.
Außerdem stellen wir drei Indikatorvarianten vor, die helfen, optimale Regularisierungsparameter (Multiplikatoren von λmax) zu bestimmen, um die beste L1-Trendzerlegung für bestimmte Symbole und Zeiträume zu erhalten.
Darüber hinaus werden die Ergebnisse der Filterung von Handelssignalen (Ausrichtung auf den L1-Trend) für die Strategien MovingAverage, MACD, ADX und EMA vorgestellt.
3.1. L1 Trendberechnung auf simulierten Daten (Random Walk)
Ein Beispiel ist die Berechnung des L1-Trends mit verschiedenen Werten des Regularisierungsparameters λ auf Daten mit simulierter Brownscher Bewegung.
Skript-Code:
//+------------------------------------------------------------------+ //| TestL1Trend.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| Generate Brown movement data | //+------------------------------------------------------------------+ void BMData(vector<double> &data,int &data_count) { data.Resize(data_count); data[0] = 0.0; for(int i=1; i<data_count; i++) data[i] = data[i-1] + (MathRand()/32767.0 - 0.5); } //+------------------------------------------------------------------+ //| CopyValues | //+------------------------------------------------------------------+ bool CopyValues(vector<double> &data_v,double &data[]) { int data_count=(int)data.Size(); if(data_count==0) return(false); ArrayResize(data,data.Size()); for(int i=0; i<data_count; i++) data[i]=data_v[i]; return(true); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { MathSrand(1); int data_count=1000; vector<double> data_test; BMData(data_test,data_count); //--- prepare arrays for chart double x[],y[]; ArrayResize(x,data_count); ArrayResize(y,data_count); for(int i=0; i<data_count; i++) x[i]=i; //--- CGraphic graphic; long chart=0; string name="test"; if(ObjectFind(chart,name)<0) graphic.Create(chart,name,0,0,0,1000,600); else graphic.Attach(chart,name); graphic.BackgroundMain("L1 Trend filtering (random walk) with different lambda"); graphic.BackgroundMainSize(16); graphic.HistoryNameWidth(60); graphic.HistoryColor(ColorToARGB(clrGray,255)); graphic.XAxis().AutoScale(false); graphic.XAxis().Min(0); graphic.XAxis().Max(data_count); //--- CopyValues(data_test,y); graphic.CurveAdd(x,y,CURVE_LINES,"Data").LinesWidth(1); //--- L1TrendFilterLambdaMax double lambda_max=0.0; if(data_test.L1TrendFilterLambdaMax(lambda_max)) PrintFormat("lambda_max=%f",lambda_max); //--- vector<double> data_l1; const double lambda_factors[]= {1.0,0.9,0.8,0.5,0.25,0.1,0.01,0.05,0.001,0.0005}; for(int i=0; i<ArraySize(lambda_factors); i++) { double lambda=lambda_max*lambda_factors[i]; PrintFormat("%d. lambda=%f",i+1,lambda); bool res=data_test.L1TrendFilter(lambda_factors[i],true,data_l1); if(res) { CopyValues(data_l1,y); graphic.CurveAdd(x,y,CURVE_LINES,"lambda="+DoubleToString(lambda,0)).LinesWidth(3); } } //--- graphic.CurvePlotAll(); graphic.Update(); DebugBreak(); } //+------------------------------------------------------------------+Ausgabe:
TestL1Trend (EURUSD,H1) lambda_max=51703.353749 TestL1Trend (EURUSD,H1) 1. lambda=51703.353749 TestL1Trend (EURUSD,H1) 2. lambda=46533.018374 TestL1Trend (EURUSD,H1) 3. lambda=41362.682999 TestL1Trend (EURUSD,H1) 4. lambda=25851.676874 TestL1Trend (EURUSD,H1) 5. lambda=12925.838437 TestL1Trend (EURUSD,H1) 6. lambda=5170.335375 TestL1Trend (EURUSD,H1) 7. lambda=517.033537 TestL1Trend (EURUSD,H1) 8. lambda=2585.167687 TestL1Trend (EURUSD,H1) 9. lambda=51.703354 TestL1Trend (EURUSD,H1) 10. lambda=25.851677
In diesem Beispiel ist zu erkennen, dass eine Verringerung des Regularisierungsparameters λ eine detailliertere Zerlegung in Trendsegmente ermöglicht (Abb.1).
Wenn λ ≥ λmax ist, wird die Lösung zu einer Geraden, die der linearen Regression entspricht (der globale Trend).

Abb. 1. Beispiel für die Berechnung des L1-Filters mit verschiedenen Werten von λ bei Daten mit Brownscher Bewegung
Funktionen zur Berechnung des L1-Trends sind sowohl für Double- als auch für Float-Vektoren verfügbar.
Das Testskript für den Vergleich der Berechnungen wird im Folgenden vorgestellt.
//+------------------------------------------------------------------+ //| TestL1TrendFloatDouble.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Graphics\Graphic.mqh> uint32_t ExtSeed=1; //+------------------------------------------------------------------+ //| Generate Brown movement data | //+------------------------------------------------------------------+ template<typename T> void BMData(vector<T> &data,uint64_t data_count) { MathSrand(ExtSeed); data.Resize(data_count); data[0] = 0.0; for(uint64_t i=1; i<data_count; i++) data[i] = data[i-1] + T(MathRand()/32767.0 - 0.5); } //+------------------------------------------------------------------+ //| CopyValues | //+------------------------------------------------------------------+ template<typename T> bool CopyValues(double &data[],const vector<T> &data_v) { if(ArrayResize(data,data.Size())!=data.Size()) return(false); for(uint64_t i=0; i<data.Size(); i++) data[i]=data_v[i]; return(true); } //+------------------------------------------------------------------+ //| L1TrendCalculate | //+------------------------------------------------------------------+ template<typename T> bool L1TrendCalculate(double &result[],uint64_t data_count,double lambda,bool lambda_is_relative) { vector<T> data_test; BMData(data_test,data_count); vector<T> vres; if(!data_test.L1TrendFilter((T)lambda,lambda_is_relative,vres)) return(false); if(ArrayResize(result,(uint32_t)vres.Size())!=vres.Size()) return(false); for(uint64_t n=0; n<result.Size(); n++) result[n]=vres[n]; return(true); } //+------------------------------------------------------------------+ //| TestRun | //+------------------------------------------------------------------+ bool TestRun(uint32_t data_count,uint32_t mode) { //--- create graph CGraphic graphic; long chart=0; string name="L1TrendTest"; if(ObjectFind(chart,name)<0) graphic.Create(chart,name,0,0,0,1280,600); else graphic.Attach(chart,name); string mode_name="("; if((mode&1)==1) mode_name+="DOUBLE"; if((mode&3)==3) mode_name+=" & "; if((mode&2)==2) mode_name+="FLOAT"; mode_name+=")"; graphic.BackgroundMain("L1Trend filtering (random walk) with different lambda "+mode_name); graphic.BackgroundMainSize(16); graphic.HistoryNameWidth(60); graphic.HistoryColor(ColorToARGB(clrGray,255)); graphic.XAxis().AutoScale(false); graphic.XAxis().Min(0); graphic.XAxis().Max(data_count); //--- prepare arrays double x[]; double y[]; if(ArrayResize(x,data_count)!=data_count) return(false); for(uint32_t i=0; i<data_count; i++) x[i]=i; vector<double> v; BMData(v,data_count); v.Swap(y); graphic.CurveAdd(x,y,CURVE_LINES,"Data").LinesWidth(1); //--- calculate const double lambda_factors[]= {1.0,0.9,0.8,0.5,0.25,0.1,0.01,0.05,0.001,0.0005}; //--- double if((mode&1)==1) { for(uint64_t i=0; i<lambda_factors.Size(); i++) { if(L1TrendCalculate<double>(y,data_count,lambda_factors[i],true)) graphic.CurveAdd(x,y,CURVE_LINES,"DBL="+DoubleToString(lambda_factors[i],4)).LinesWidth(4); } } //--- float if((mode&2)==2) { for(uint64_t i=0; i<lambda_factors.Size(); i++) { if(L1TrendCalculate<float>(y,data_count,(float)lambda_factors[i],true)) graphic.CurveAdd(x,y,CURVE_LINES,"FLT="+DoubleToString(lambda_factors[i],4)).LinesWidth(2); } } //--- update graphic.CurvePlotAll(); graphic.Update(); return(true); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { for(uint32_t n=0; !IsStopped(); n++,Sleep(1000)) { TestRun(1000,1+n%3); if((n%3)==2) ExtSeed++; } } //+------------------------------------------------------------------+
Ausgabe:

3.2. L1 Trendberechnung für die S&P 500 Kursreihe
Im Folgenden betrachten wir die Berechnung von log(S&P 500) aus dem Originalbeitrag l_1 Trend Filtering, S.J. Kim, K. Koh, S. Boyd, and D. Gorinevsky, SIAM Review, problems and techniques section, 51(2):339–360, May 2009.
Zur Ausführung des Skripts werden die Daten aus der Datei „snp500.txt“ verwendet. Sie muss in den Ordner terminal_data_folder\MQL5\Files abgelegt werden.
//+------------------------------------------------------------------+ //| TestL1TrendFilterSP500.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| LoadData | //+------------------------------------------------------------------+ void LoadData(string filename,vector<double> &data,int &data_count) { data_count=0; ResetLastError(); int file_handle=FileOpen(filename,FILE_READ|FILE_TXT|FILE_ANSI); if(file_handle!=INVALID_HANDLE) { while(!FileIsEnding(file_handle)) { string str=FileReadString(file_handle); if(data.Size()<=(ulong)data_count) data.Resize(data_count+1); data[data_count]=StringToDouble(str); data_count++; } FileClose(file_handle); } else PrintFormat("Failed to open %s file, Error code = %d",filename,GetLastError()); //--- data.Resize(data_count); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { long chart=0; string name="log SP500"; int data_count=0; vector<double> data_sp500; LoadData("snp500.txt",data_sp500,data_count); vector<double> data_l1_sp500; data_l1_sp500.Resize(data_count); //--- L1TrendFilterLambdaMax double lambda_max=0.0; if(data_sp500.L1TrendFilterLambdaMax(lambda_max)) PrintFormat("Lambda_max=%f",lambda_max); double lambda=50; //--- L1TrendFilter if(data_sp500.L1TrendFilter(lambda,false,data_l1_sp500)) { //--- prepare arrays for chart double x[],y[],y2[]; ArrayResize(x,data_count); ArrayResize(y,data_count); ArrayResize(y2,data_count); for(int i=0; i<data_count; i++) { x[i]=i; y[i]=data_sp500[i]; y2[i]=data_l1_sp500[i]; } //--- CGraphic graphic; if(ObjectFind(chart,name)<0) graphic.Create(chart,name,0,0,0,1000,600); else graphic.Attach(chart,name); graphic.BackgroundMain("log SP500 L1 trend filtering"); graphic.BackgroundMainSize(16); graphic.HistoryNameWidth(60); graphic.HistoryColor(ColorToARGB(clrGray,255)); graphic.XAxis().AutoScale(false); graphic.XAxis().Min(0); graphic.XAxis().Max(data_count); graphic.XAxis().DefaultStep(100); graphic.CurveAdd(x,y,CURVE_LINES,"SP500").LinesWidth(1); graphic.CurveAdd(x,y2,CURVE_LINES,"L1 trend").LinesWidth(3); graphic.CurvePlotAll(); graphic.Update(); DebugBreak(); } } //+------------------------------------------------------------------+
Das Ergebnis der Skriptausführung ist in Abb. 2. dargestellt.

Abb. 2. Beispiel einer L1-Trendschätzung für logarithmische Kursreihen des S&P 500 Index
Auf der Registerkarte Experten wird der Wert von λmax für die jeweilige Zeitreihe angezeigt:
TestL1TrendFilterSP500 (EURUSD,H1) Lambda_max=37394.835512
Dieses Skript demonstriert die Anwendung der Methoden L1TrendFilterLambdaMax und L1TrendFilter mit einem festen Wert λ = 50, wie in der Originalarbeit der Autoren der Methode.
In den folgenden Beispielen werden anstelle von absoluten Werten des Regularisierungsparameters λ relative Werte (in Einheiten von λmax) mit dem Flag relative = true verwendet.
3.3. Skalierungseigenschaften von λmax
Der Parameter λmax spielt beim L1-Filter eine Schlüsselrolle, da er die Obergrenze der Regularisierung festlegt, bei der die Lösung zu einer globalen linearen Approximation degeneriert. Eine interessante Eigenschaft dieser Größe ist ihre Skalierungsabhängigkeit von der Länge der Zeitreihe.
Numerische Experimente zeigen, dass λmax nach einem Potenzgesetz mit der Anzahl der Beobachtungen wächst:
![]()
wobei: T – Länge der Zeitreihe, α – Skalierungsexponent.
Für einen Random-Walk (Brownsche Bewegung) kann gezeigt werden, dass der Exponent nahe bei α ≈ 2,5 liegen sollte. Die Amplitude der Brownschen Bewegung wächst mit
Daraus ergibt sich folgende Skalierungsbeziehung:
![]()
was einem Exponenten α ≈ 2,5 entspricht.
Mit zunehmender Länge der Zeitreihe wächst der Wert von λmax also deutlich schneller als linear.
3.3.1. Numerisches Experiment für Brownsche Bewegung
Um das Skalierungsgesetz zu überprüfen, wurde ein numerisches Experiment durchgeführt.
Für verschiedene Zeitreihenlängen T wurden Realisierungen der Brownschen Bewegung erzeugt, woraufhin der Durchschnittswert von λmax berechnet wurde.
Es wurde eine logarithmische Näherung verwendet:
![]()
was die Schätzung des Exponenten α mittels linearer Regression ermöglicht.
Der Programmcode für das Experiment ist unten angegeben.
//+------------------------------------------------------------------+ //| TestScalingLambdaMaxBrownMovement.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| Generate Brownian motion | //+------------------------------------------------------------------+ void GenerateBrownian(int N,vector<double> &data) { data.Resize(N); data[0] = 0.0; for(int i=1; i<N; i++) data[i] = data[i-1] + (MathRand()/32767.0 - 0.5); } //+------------------------------------------------------------------+ //| LinearRegression | //+------------------------------------------------------------------+ void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b) { double sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0; for(int i = 0; i < n; i++) { sx += x[i]; sy += y[i]; sxx += x[i] * x[i]; sxy += x[i] * y[i]; } double denom = n * sxx - sx * sx; a = (n * sxy - sx * sy) / denom; b = (sy - a * sx) / n; } //+------------------------------------------------------------------+ //| TestScaling with statistics | //+------------------------------------------------------------------+ void TestScalingStatistics() { MathSrand(42); int RUNS = 10; // int MC = 10; // Monte Carlo double alpha_values[]; ArrayResize(alpha_values, RUNS); // --- geometric grid of T int nT = 8; int Tvals[]; ArrayResize(Tvals, nT); int T0 = 64; for(int i = 0; i < nT; i++) Tvals[i] = T0 << i; Print("Scaling test with statistics"); //--- double logT[]; double logLambda[]; vector<double> bm; vector<double> l1_trend; for(int run = 0; run < RUNS; run++) { ArrayResize(logT, nT); ArrayResize(logLambda, nT); //--- for(int i = 0; i < nT; i++) { int T = Tvals[i]; double lambda_sum = 0.0; l1_trend.Resize(T); for(int k = 0; k < MC; k++) { GenerateBrownian(T, bm); double lambda_max=0.0; if (bm.L1TrendFilterLambdaMax(lambda_max)) lambda_sum += lambda_max; bm.L1TrendFilter(0.2,true,l1_trend); } double lambda_avg = lambda_sum / MC; logT[i] = MathLog((double)T); logLambda[i] = MathLog(lambda_avg); } // --- regression double alpha, c; LinearRegression(logT, logLambda, nT, alpha, c); alpha_values[run] = alpha; PrintFormat("run %d -> alpha = %.6f", run+1, alpha); } //--- statistics double mean = 0.0; for(int i=0;i<RUNS;i++) mean += alpha_values[i]; mean /= RUNS; // --- standard deviation double var = 0.0; for(int i=0;i<RUNS;i++) var += (alpha_values[i]-mean)*(alpha_values[i]-mean); var /= (RUNS - 1); double stddev = MathSqrt(var); // --- standard error of mean double sem = stddev / MathSqrt((double)RUNS); // --- theoretical comparison double alpha_theory=2.5; double percent_error=MathAbs(mean-alpha_theory)/alpha_theory*100.0; //--- results PrintFormat("mean alpha = %.6f", mean); PrintFormat("std deviation = %.6f", stddev); PrintFormat("standard error = %.6f", sem); PrintFormat("theory = %.4f", alpha_theory); PrintFormat("percent error from theory = %.4f %%", percent_error); } //+------------------------------------------------------------------+ //| TestScaling | //+------------------------------------------------------------------+ void TestScaling() { MathSrand(1); // --- geometric grid of T int nT = 8; int Tvals[]; ArrayResize(Tvals,nT); //--- int T0 = 64; for(int i=0; i<nT; i++) Tvals[i]=T0<<i; // 64 * 2^i //--- double logT[], logLambda[]; ArrayResize(logT,nT); ArrayResize(logLambda,nT); //--- Print("scaling test for lambda_max"); for(int i=0; i<nT; i++) { int T = Tvals[i]; //--- Monte-Carlo simulations int MC=1000; double lambda_sum = 0.0; for(int k=0; k<MC; k++) { vector<double> bm; GenerateBrownian(T, bm); double lambda_max=0.0; if(bm.L1TrendFilterLambdaMax(lambda_max)) lambda_sum += lambda_max; } double lambda_avg=lambda_sum/MC; logT[i]= MathLog((double)T); logLambda[i]=MathLog(lambda_avg); PrintFormat("T=%5d <lambda_max>=%.6f",T,lambda_avg); } // --- linear regression in log-log double alpha, c; LinearRegression(logT,logLambda,nT,alpha,c); //--- PrintFormat("estimated scaling exponent alpha = %.4f",alpha); double alpha_theory=2.5; PrintFormat("theoretical value = %.4f",alpha_theory); //--- plot scaling law CGraphic g; g.Create(0, "ScalingLaw",0,0,0,1000,600); g.BackgroundMain("Scaling law of lambda_max (Brownian motion)"); g.BackgroundMainSize(16); g.CurveAdd(logT, logLambda, CURVE_POINTS, "Simulation"); //--- double xfit[2], yfit[2]; xfit[0] = logT[0]; xfit[1] = logT[nT-1]; //--- yfit[0] = alpha*xfit[0] + c; yfit[1] = alpha*xfit[1] + c; //---least squares fit g.CurveAdd(xfit, yfit, CURVE_LINES, "LS fit"); g.CurvePlotAll(); g.Update(); DebugBreak(); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- calculate scaling with statistics TestScalingStatistics(); //--- show sample results TestScaling(); } //+------------------------------------------------------------------+
Ausgabe:
TestScalingLambdaMaxBrownMovement (EURUSD,H1) Scaling test with statistics TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 1 -> alpha = 2.480774 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 2 -> alpha = 2.530977 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 3 -> alpha = 2.435511 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 4 -> alpha = 2.461984 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 5 -> alpha = 2.467093 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 6 -> alpha = 2.487965 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 7 -> alpha = 2.532371 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 8 -> alpha = 2.455831 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 9 -> alpha = 2.483485 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 10 -> alpha = 2.420283 TestScalingLambdaMaxBrownMovement (EURUSD,H1) mean alpha = 2.475627 TestScalingLambdaMaxBrownMovement (EURUSD,H1) std deviation = 0.036281 TestScalingLambdaMaxBrownMovement (EURUSD,H1) standard error = 0.011473 TestScalingLambdaMaxBrownMovement (EURUSD,H1) theory = 2.5000 TestScalingLambdaMaxBrownMovement (EURUSD,H1) percent error from theory = 0.9749 % TestScalingLambdaMaxBrownMovement (EURUSD,H1) scaling test for lambda_max TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 64 <lambda_max>=97.302362 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 128 <lambda_max>=566.626861 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 256 <lambda_max>=3162.076116 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 512 <lambda_max>=18271.204936 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 1024 <lambda_max>=100057.796790 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 2048 <lambda_max>=578620.887399 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 4096 <lambda_max>=3192555.936035 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 8192 <lambda_max>=17895314.647170 TestScalingLambdaMaxBrownMovement (EURUSD,H1) estimated scaling exponent alpha = 2.4967 TestScalingLambdaMaxBrownMovement (EURUSD,H1) theoretical value = 2.5000
Die doppellogarithmische Darstellung (in doppellogarithmischer Skala) zeigt das Vorhandensein einer Potenzgesetzabhängigkeit der Funktion λmax von der Anzahl der Datenpunkte für die Brownsche Bewegung.
Abb.3. Potenzgesetz-Abhängigkeit von LambdaMax für Brownsche Bewegungalt
Abb. 3 Potenzgesetz-Abhängigkeit von LambdaMax für Brownsche Bewegung
Die Simulationsergebnisse zeigen:
mean alpha = 2.4756 std deviation = 0.036 theory = 2.5 percent error ≈ 1%
Das Experiment bestätigt also den theoretischen Zusammenhang:
![]()
Die doppellogarithmische Darstellung zeigt eine lineare Beziehung zwischen log(λmax) und log(T).
3.3.2. Skalierung für Finanzzeitreihen
Ein ähnliches Experiment wurde für die Kursreihen des FOREX-Marktes durchgeführt. Für verschiedene Währungspaare und Zeitrahmen wurde der Exponent α geschätzt.
Die Ergebnisse zeigen, dass der Wert von α für reale Finanzdaten ebenfalls im Bereich von α ≈ 2,45-2,60 liegt, was dem theoretischen Wert für die Brownsche Bewegung sehr nahe kommt. Dies bedeutet, dass das Skalierungsverhalten von λmax nahezu universell ist und für verschiedene Märkte und Zeiträume gilt.
Das Skript TestScalingLambdaMaxSymbol.mq5 berechnet den Exponenten von λmax für ein bestimmtes Symbol über die Standardzeitrahmen M1-H1.
//+------------------------------------------------------------------+ //| TestScalingLambdaMaxSymbol.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //--- input parameters input string WorkSymbol = "EURUSD"; // Symbol input int YearStart = 2024; input int YearEnd = 2025; #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| GetHistoricalData | //+------------------------------------------------------------------+ bool GetHistoricalData(double &data[], string symbol, ENUM_TIMEFRAMES tf, int year_start, int year_end) { datetime from = StringToTime(IntegerToString(year_start) + ".01.01 00:00"); datetime to = StringToTime(IntegerToString(year_end) + ".12.31 23:59"); int copied = CopyClose(symbol, tf, from, to, data); if(copied <= 0) { Print("Error in CopyClose: ", GetLastError()); ArrayResize(data, 0); return false; } //PrintFormat("Loaded bars: %d (%s %s)", ArraySize(data), symbol, EnumToString(tf)); return true; } //+------------------------------------------------------------------+ //| LinearRegression | //+------------------------------------------------------------------+ void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b) { double sx = 0, sy = 0, sxx = 0, sxy = 0; for(int i = 0; i < n; i++) { sx += x[i]; sy += y[i]; sxx += x[i] * x[i]; sxy += x[i] * y[i]; } double denom = n*sxx - sx*sx; if(denom!=0) { a = (n*sxy-sx*sy)/denom; b = (sy-a*sx)/n; } } //+------------------------------------------------------------------+ //| Scaling test for one timeframe | //+------------------------------------------------------------------+ bool TestScalingLambaMaxTF(string symbol, ENUM_TIMEFRAMES tf, double &logT_out[], double &logLambda_out[], double &alpha_out) { MathSrand(42); double prices[]; if(!GetHistoricalData(prices, symbol, tf, YearStart, YearEnd)) return false; int Tvals[]; int nT=8; int T0=64; ArrayResize(Tvals, nT); for(int i = 0; i < nT; i++) Tvals[i] = T0 << i; ArrayResize(logT_out, nT); ArrayResize(logLambda_out, nT); int data_size = ArraySize(prices); vector<double> data_prices; for(int i = 0; i < nT; i++) { int T = Tvals[i]; int MC = 1000; double lambda_sum = 0.0; for(int k = 0; k < MC; k++) { if(data_size < T) break; int start = MathRand() % (data_size - T); data_prices.Resize(T); for(int j=0; j<T; j++) data_prices[j]=prices[start+j]; double lambda_max=0.0; if(data_prices.L1TrendFilterLambdaMax(lambda_max)) lambda_sum += lambda_max; } double lambda_avg = lambda_sum / MC; logT_out[i]=MathLog((double)T); logLambda_out[i]=MathLog(lambda_avg); //PrintFormat("TF=%s T=%5d <lambda_max>=%.6f", EnumToString(tf), T, lambda_avg); } double c; LinearRegression(logT_out, logLambda_out, nT, alpha_out, c); PrintFormat("%s (%s) estimated scaling exponent = %.4f", symbol,EnumToString(tf), alpha_out); return true; } //+------------------------------------------------------------------+ //| TestScalingLambdaMaxSymbol | //+------------------------------------------------------------------+ void TestScalingLambdaMaxSymbol(string symbol) { ENUM_TIMEFRAMES timeframes[] = {PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6, PERIOD_M10, PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30, PERIOD_H1 }; uint colors[] = {clrRed,clrBlue,clrGreen,clrOrange,clrPurple,clrDarkGreen,clrCyan, clrNavy,clrOrangeRed,clrDodgerBlue,clrCrimson,clrDarkRed }; //--- CGraphic g; g.Create(0,"ScalingLawTest",0,0,0,1000,600); g.BackgroundMain("Scaling law of lambda_max ("+symbol+")"); g.BackgroundMainSize(16); PrintFormat("%s scaling test for standard timeframes",symbol); for(int i = 0; i < ArraySize(timeframes); i++) { double logT[], logLambda[], alpha; // Print("processing timeframe: ", EnumToString(timeframes[i]), " -----"); if(TestScalingLambaMaxTF(symbol,timeframes[i],logT,logLambda,alpha)) { g.CurveAdd(logT,logLambda,ColorToARGB(colors[i % ArraySize(colors)],255),CURVE_POINTS_AND_LINES,EnumToString(timeframes[i])); } } g.CurvePlotAll(); g.Update(); //--- DebugBreak(); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- estimate lambda_max scale exponent for price data TestScalingLambdaMaxSymbol(WorkSymbol); } //+------------------------------------------------------------------+
Ergebnisse für EURUSD:
TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD scaling test for standard timeframes TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M1) estimated scaling exponent = 2.5038 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M2) estimated scaling exponent = 2.5350 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M3) estimated scaling exponent = 2.5034 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M4) estimated scaling exponent = 2.5422 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M5) estimated scaling exponent = 2.5341 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M6) estimated scaling exponent = 2.5132 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M10) estimated scaling exponent = 2.5188 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M12) estimated scaling exponent = 2.5126 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M15) estimated scaling exponent = 2.5208 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M20) estimated scaling exponent = 2.4887 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M30) estimated scaling exponent = 2.5695 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_H1) estimated scaling exponent = 2.6118
Die Ergebnisse für EURUSD (Standardzeitrahmen M1-H1) sind in Abb. 4 dargestellt.

Abb. 4 Potenzgesetz-Abhängigkeit von λmax für die verschiedenen EURUSD-Zeitrahmen
In ähnlicher Weise können auch andere Währungspaare analysiert werden.
Für USDJPY:
TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY scaling test for standard timeframes TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M1) estimated scaling exponent = 2.5851 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M2) estimated scaling exponent = 2.5825 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M3) estimated scaling exponent = 2.4889 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M4) estimated scaling exponent = 2.5099 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M5) estimated scaling exponent = 2.5059 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M6) estimated scaling exponent = 2.4939 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M10) estimated scaling exponent = 2.5548 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M12) estimated scaling exponent = 2.5641 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M15) estimated scaling exponent = 2.5525 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M20) estimated scaling exponent = 2.5390 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M30) estimated scaling exponent = 2.5805 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_H1) estimated scaling exponent = 2.4645
Die Ergebnisse für den USDJPY werden ebenfalls durch eine Potenzgesetz-Beziehung gut angenähert.

Abb. 5 Potenzgesetz-Abhängigkeit von λmax für die verschiedenen USDJPY-Zeitraster
Für GBPUSD:
TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD scaling test for standard timeframes TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M1) estimated scaling exponent = 2.5235 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M2) estimated scaling exponent = 2.5449 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M3) estimated scaling exponent = 2.5439 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M4) estimated scaling exponent = 2.5427 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M5) estimated scaling exponent = 2.5248 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M6) estimated scaling exponent = 2.5308 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M10) estimated scaling exponent = 2.5293 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M12) estimated scaling exponent = 2.5235 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M15) estimated scaling exponent = 2.5069 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M20) estimated scaling exponent = 2.4977 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M30) estimated scaling exponent = 2.5659 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_H1) estimated scaling exponent = 2.5524
Ähnlich verhält es sich mit der GBPUSD-Kursreihe (Abb. 6).

Abb. 6. Potenzgesetz-Abhängigkeit von λmax für die verschiedenen GBPUSD-Zeitrahmen
Für die betrachteten EURUSD-, USDJPY- und GBPUSD-Serien liegen die geschätzten Exponentenwerte ebenfalls nahe bei 2,5.
Lineare Beziehungen im log-log-Maßstab für die Funktion λmax über mehrere Zeitrahmen und Währungspaare zeigen eine Potenzgesetz-Abhängigkeit von λmax von der Anzahl der Beobachtungen.
3.3.3. Praktische Implikationen der Skalierung
Das Vorhandensein einer Potenzgesetz-Abhängigkeit für λmax hat eine wichtige praktische Auswirkung.
Da λmax ∝ T^2,5 ist, hängt der absolute Wert von λ stark davon ab:
- die Länge des Datenfensters,
- den Zeitrahmen,
- die Skala der Zeitreihe.
Daher ist die Verwendung eines absoluten Wertes von λ in der Praxis unpraktisch.
Ein wesentlich robusterer Ansatz ist die Verwendung eines relativen Parameters λ=c⋅λmax, wobei 0<c<1.
Ein solcher Ansatz:
- macht den Regularisierungsparameter skaleninvariant,
- vereinfacht die Übertragung von Parametern zwischen verschiedenen Instrumenten,
- ermöglicht es, dieselben Einstellungen über verschiedene Zeiträume hinweg zu verwenden.
Aus diesem Grund wird in allen folgenden Beispielen der Parameter λ in Einheiten von λmax angegeben.
3.4. L1-Trendindikatoren
In diesem Abschnitt werden drei Arten von Indikatoren betrachtet:
- Berechnung des L1-Trends auf der Grundlage der Schlusskurse;
- Berechnung der linearen Wachstumskoeffizienten (Steigung) des L1-Trends;
- Berechnung des Vorzeichens der L1-Trendsteigung;
3.4.1. L1TrendFilter.mq5 – L1-Trendindikator
In diesem Beispiel wird der L1-Filter anhand der Schlusskurse für eine bestimmte Anzahl von Bars (im Beispiel BarsToShow = 1000) berechnet, wobei der Lambda-Koeffizient in Einheiten von λmax angegeben wird.
Die Berechnung erfolgt mit dem Methodenaufruf L1TrendFilter(relative = true), wobei der Parameter λ in Einheiten von λmax definiert ist. Die Indikatorwerte werden direkt im Chartfenster angezeigt.
Der Code des L1TrendFilter.mq5-Indikators ist unten aufgeführt.
//+------------------------------------------------------------------+ //| L1TrendFilter.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1TrendFilter" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Trend[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Trend,INDICATOR_DATA); ArrayInitialize(Trend,EMPTY_VALUE); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check bars static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(Trend,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- range int start=rates_total-BarsToShow; //--- hide old bars for(int i=0; i<start; i++) Trend[i]=EMPTY_VALUE; //--- int data_count=BarsToShow; //--- copy Close vector<double> DataClose; DataClose.Resize(data_count); for(int i=0; i<data_count; i++) DataClose[i]=close[start+i]; //--- lambda max double lambda_max=0.0; bool res=DataClose.L1TrendFilterLambdaMax(lambda_max); if(res) { PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f", lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda); } //--- L1 trend filtering vector<double> filtered_data; filtered_data.Resize(data_count); if(DataClose.L1TrendFilter(CoefLambda,true,filtered_data)) { for(int i=0; i<data_count; i++) Trend[start+i]=filtered_data[i]; } //--- return(rates_total); } //+------------------------------------------------------------------+
In Abb. 7 ist ein Beispiel für die Berechnung des Indikators L1TrendFilter.mq5 mit CoefLambda = 0,015 dargestellt.

Abb. 7. Beispiel für die Berechnung des Indikators L1TrendFilter.mq5 mit CoefLambda = 0,015
Zum Vergleich kann man mehrere Varianten mit unterschiedlichen Regularisierungsparametern berechnen.
Abb. 8 zeigt Berechnungen mit den Parametern CoefLambda = 0,015, CoefLambda = 0,025 und CoefLambda = 0,055.

Abb. 8 Beispiele für die Berechnung des Indikators L1TrendFilter.mq5 mit den verschiedenen CoefLambda-Werten
3.4.2. L1TrendFilterSlope.mq5 – Indikator der L1-Trenddynamik
Um die Trendsteigung anzuzeigen, kann man die Inkrementierung der L1TrendFilter-Indikatorwerte verwenden.
Ein Beispiel hierfür ist der Indikator L1TrendFilterSlope, der die Werte in einem separaten Fenster anzeigt.
Der Code des Indikators ist unten angegeben.
//+------------------------------------------------------------------+ //| L1TrendFilterSlope.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1TrendFilterSlope" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Trend[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Trend,INDICATOR_DATA); ArrayInitialize(Trend,EMPTY_VALUE); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check bars static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(Trend,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc= (prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int data_count=BarsToShow; //--- hide old bars for(int i=0;i<start;i++) Trend[i]=EMPTY_VALUE; //--- copy Close vector<double> DataClose; DataClose.Resize(data_count); for(int i=0;i<data_count;i++) DataClose[i]=close[start+i]; //--- lambda max double lambda_max=0.0; if(DataClose.L1TrendFilterLambdaMax(lambda_max)) { PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f", lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda); } //--- L1 filtering vector<double> filtered_data; filtered_data.Resize(data_count); bool res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data); if(res) { //--- slope (first difference) for(int i=1; i<data_count; i++) { double delta=filtered_data[i]-filtered_data[i-1]; Trend[start+i]=delta; } //--- copy first element Trend[start]=Trend[start+1]; } return(rates_total); } //+------------------------------------------------------------------+
Das Ergebnis der gemeinsamen Indikatoren L1TrendFilter.mq5 und L1TrendFilterSlope.mq5 ist in Abb. 9 dargestellt.

Abb. 9 Beispiel für die Berechnung der Indikatoren L1TrendFilter.mq5 und L1TrendFilterSlope.mq5 mit CoefLambda = 0,015
3.4.3. L1TrendFilterSlopeSign.mq5 – Indikator der L1-Trendrichtung
In ähnlicher Weise kann man einen Indikator berechnen, der das Vorzeichen des Anstiegs des Indikators L1TrendFilterSlope.mq5 anzeigt.
Code des Indikators L1TrendFilterSlopeSign.mq5:
//+------------------------------------------------------------------+ //| L1TrendFilterSlopeSign.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1TrendFilterSlope" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Trend[]; //+------------------------------------------------------------------+ //| Signum | //+------------------------------------------------------------------+ double Signum(const double value) { return((value>0)-(value<0)); } //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Trend,INDICATOR_DATA); ArrayInitialize(Trend,EMPTY_VALUE); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check bars static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(Trend,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int data_count=BarsToShow; //--- hide old bars for(int i=0; i<start; i++) Trend[i]=EMPTY_VALUE; //--- copy Close vector<double> DataClose; DataClose.Resize(data_count); for(int i=0; i<data_count; i++) DataClose[i]=close[start+i]; //--- lambda max double lambda_max=0.0; bool res=DataClose.L1TrendFilterLambdaMax(lambda_max); if(res) { PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f", lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda); } //--- L1 filtering vector<double> filtered_data; filtered_data.Resize(data_count); res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data); if(res) { Trend[start]=0; for(int i=1; i<data_count; i++) { double delta=filtered_data[i]-filtered_data[i-1]; Trend[start+i]=Signum(delta); } } return(rates_total); } //+------------------------------------------------------------------+
Ein Beispiel für die gemeinsame Darstellung aller drei Indikatoren ist in Abb. 10 dargestellt (es wurde der gleiche Koeffizientenwert CoefLambda = 0,015 verwendet).

Abb. 10. Beispiel für die Berechnung der Indikatoren L1TrendFilter.mq5, L1TrendFilterSlope.mq5 und L1TrendFilterSlopeSign.mq5 mit CoefLambda = 0,015
3.4.4. Volatilitätsindikatoren auf der Grundlage des L1-Trends
In diesem Abschnitt werden Indikatoren zur Bewertung der Volatilität auf Basis des L1-Trends vorgestellt
Diese Instrumente ermöglichen es, Phasen der Marktinstabilität und -stabilität zu erkennen, die aktuelle Marktdynamik zu analysieren und fundiertere Handelsentscheidungen zu treffen.
Die in diesem Abschnitt betrachteten Indikatoren sind:
- L1Volatility.mq5 – Restvolatilität relativ zum L1-Trend;
- L1VolatilitySmoothed.mq5 – geglättete Restvolatilität;
- L1VolatilityAbsolute.mq5 – absolute Volatilität;
- L1VolatilityNormalized.mq5 – normalisierte Volatilität;
- L1VolatilityNormalizedSmoothed.mq5 – geglättete normalisierte Volatilität;
- L1VolatilityRegime.mq5 – Erkennung von Marktregimen anhand der Volatilität.
Alle Indikatoren basieren auf einem einheitlichen L1-Trend-Rahmen, der die analytische Konsistenz gewährleistet und die Interpretation der erzielten Ergebnisse vereinfacht.
Die Verwendung dieser Indikatoren ermöglicht die visuelle Identifizierung von Perioden mit hoher und niedriger Volatilität sowie die Bestimmung des aktuellen Marktregimes – Seitwärtsmarkt, Trend, Expansion oder Panik.
Dadurch kann ein Händler seine Handelsstrategien an die aktuellen Marktbedingungen anpassen, etwa durch konservativere Ansätze in Phasen geringer Volatilität oder durch aktivere Strategien bei starken Marktbewegungen.
3.4.4.1. L1Volatility.mq5 – L1-Volatilitätsindikator
Der Indikator berechnet die Restvolatilität als Differenz zwischen den Schlusskursen und dem entsprechenden Wert des L1-Trends.
Mit diesem Ansatz lassen sich instabile Marktphasen und genaue Ein- und Ausstiegszeitpunkte ermitteln.
Optisch wird der Indikator in einem separaten Chartfenster als orangefarbene Linie dargestellt.
Der Indikator hilft dabei:
- Kursabweichungen vom L1-Trend zu bewerten und die Stärke der Marktbewegung zu messen;
- lokale Volatilitätsspitzen für ein präziseres Risikomanagement zu erkennen;
- die Dynamik verschiedener Instrumente innerhalb desselben Zeitrahmens zu vergleichen.
Der Indikator ist besonders nützlich in Systemen, in denen kurzfristige Volatilitätsänderungen überwacht werden müssen, ohne den breiteren Trendkontext zu verlieren.
Der Code des Indikators L1Volatility.mq5 ist unten angegeben.
//+------------------------------------------------------------------+ //| L1Volatility.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1Volatility" #property indicator_type1 DRAW_LINE #property indicator_color1 clrOrangeRed #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Volatility[]; //--- //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Volatility,INDICATOR_DATA); ArrayInitialize(Volatility,EMPTY_VALUE); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check bars static bool warned=false; if(rates_total<BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(Volatility,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int data_count=BarsToShow; //--- hide old bars for(int i=0;i<start;i++) Volatility[i]=EMPTY_VALUE; //--- copy Close vector<double> DataClose; DataClose.Resize(data_count); for(int i=0; i<data_count; i++) DataClose[i]=close[start+i]; //--- lambda max double lambda_max=0.0; bool res=DataClose.L1TrendFilterLambdaMax(lambda_max); if(res) { PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f", lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda); } //--- L1 filter vector<double> filtered_data; filtered_data.Resize(data_count); res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data); if(res) { for(int i=0; i<data_count; i++) { double residual=close[start+i]-filtered_data[i]; Volatility[start+i]=residual; } } //--- return(rates_total); } //+------------------------------------------------------------------+
Das Berechnungsergebnis ist in Abb. 11 dargestellt.

Abb. 11. L1Volatility.mq5 Indikator
3.4.4.2. L1VolatilitySmoothed.mq5 – Geglätteter Restvolatilitätsindikator
Dieser Indikator stellt eine geglättete Version von L1Volatility dar, bei der ein einfacher gleitender Durchschnitt (SMA) angewendet wird.
Durch die Glättung lassen sich:
- Reduzierung kurzfristiger Schwankungen und Ausschläge;
- die Darstellung klarer und übersichtlicher gestalten;
- nachhaltige Veränderungen der Volatilität in den Blick nehmen.
Der Indikator eignet sich für Strategien, die eine Bewertung längerfristiger Volatilitätstrends erfordern, z.B. in adaptiven Handelssystemen oder beim Herausfiltern von Fehlsignalen in Trend- und Schwankungsphasen.
Der Code des L1VolatilitySmoothed.mq5-Indikators ist unten aufgeführt.
//+------------------------------------------------------------------+ //| L1VolatilitySmoothed.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_label1 "L1VolatilitySmoothed" #property indicator_type1 DRAW_LINE #property indicator_color1 clrMediumVioletRed #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units input int SmoothPeriod = 10; // Smooth period //--- double VolSmoothed[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, VolSmoothed, INDICATOR_DATA); ArrayInitialize(VolSmoothed, EMPTY_VALUE); PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS, _Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total<BarsToShow) { ArrayInitialize(VolSmoothed,EMPTY_VALUE); return(0); } //--- recalc only on new bar static datetime last_bar_time = 0; if(time[0] == last_bar_time && prev_calculated > 0) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; for(int i=0; i<start; i++) VolSmoothed[i]=EMPTY_VALUE; //--- copy close prices vector<double> price(BarsToShow); for(int i=0; i<BarsToShow; i++) price[i] = close[start+i]; vector<double> l1(BarsToShow); bool res=price.L1TrendFilter(CoefLambda,true,l1); if(res) { //--- calculate raw volatility vector<double> rawVol(BarsToShow); for(int i=0; i<BarsToShow; i++) rawVol[i]=close[start+i]-l1[i]; //--- apply simple moving average smoothing for(int i=0; i<BarsToShow; i++) { double sum = 0.0; int count = 0; for(int j=MathMax(0,i-SmoothPeriod+1); j<=i; j++) { sum+=rawVol[j]; count++; } VolSmoothed[start+i]=sum/count; } } //--- return(rates_total); } //+------------------------------------------------------------------+
Abbildung 12 zeigt die beiden Indikatoren L1Volatility.mq5 und L1VolatilitySmoothed.mq5.

Abb. 12. L1Volatility.mq5 und L1VolatilitySmoothed.mq5 Indikatoren
3.4.4.3. L1VolatilityAbsolute.mq5 – Absoluter Volatilitätsindikator
Der Indikator berechnet den absoluten Wert der Differenz zwischen den Schlusskursen und dem L1-Trend.
Merkmale und Anwendungen:
- Ignoriert die Bewegungsrichtung und bewertet nur die Größe der Schwankung;
- Praktisch für die Analyse der Amplitude von Kursschwankungen unabhängig von der Trendrichtung;
- Nützlich für Systeme, die auf Extremwertstatistiken und Risikoanalysen basieren.
Die absolute Volatilität spiegelt das wahre Ausmaß der Kursabweichungen wider und ermöglicht es dem Händler, die Stärke der Marktbewegung zu beobachten, ohne sich von ihrer Richtung ablenken zu lassen.
Der Code des Indikators L1VolatilityAbsolute.mq5 ist nachstehend aufgeführt.
//+------------------------------------------------------------------+ //| L1VolatilityAbsolute.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1VolatilityAbsolute" #property indicator_type1 DRAW_LINE #property indicator_color1 clrOrange #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Vol[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Vol,INDICATOR_DATA); ArrayInitialize(Vol,EMPTY_VALUE); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting bars ",BarsToShow); warned=true; } ArrayInitialize(Vol,EMPTY_VALUE); return(0); } static datetime last_bar=0; bool new_bar=(time[0]!=last_bar); //--- if(!(prev_calculated==0 || new_bar || rates_total!=prev_calculated)) return(prev_calculated); //--- last_bar=time[0]; int start=rates_total-BarsToShow; int N=BarsToShow; for(int i=0; i<start; i++) Vol[i]=EMPTY_VALUE; //--- vector<double> price; price.Resize(N); for(int i=0; i<N; i++) price[i]=close[start+i]; vector<double> l1; l1.Resize(N); bool res=price.L1TrendFilter(CoefLambda,true,l1); if(res) { for(int i=0; i<N; i++) Vol[start+i]=MathAbs(close[start+i]-l1[i]); } //--- return(rates_total); } //+------------------------------------------------------------------+
Ein Beispiel für die Berechnung des Indikators ist in Abb.13 dargestellt.

Abb. 13. L1VolatilityAbsolute.mq5 Indikator
3.4.4.4. L1VolatilityNormalized.mq5 – Normalisierter Volatilitätsindikator
Der Indikator normalisiert die Volatilität anhand der ATR (Average True Range) zusammen mit dem L1-Trend.
Sie berechnet das Verhältnis zwischen der absoluten Kursabweichung vom Trend und der durchschnittlichen Kursspanne im ATR-Zeitraum. Die Normalisierung beseitigt die Abhängigkeit von der Kursskala und ermöglicht einen Vergleich zwischen verschiedenen Instrumenten und Zeitrahmen.
Die Anwendungen umfassen:
- Identifizierung von relativ starken und schwachen Marktbewegungen;
- Vergleich der Volatilität zwischen verschiedenen Vermögenswerten;
- Bewertung der Marktbedingungen unabhängig vom Kursniveau.
Der Code des Indikators L1VolatilityNormalized.mq5 ist unten angegeben.
//+------------------------------------------------------------------+ //| L1VolatilityNormalized.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_label1 "L1VolatilityNormalized" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double VolNormalized[]; //--- //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- prepare SetIndexBuffer(0, VolNormalized,INDICATOR_DATA); ArrayInitialize(VolNormalized,EMPTY_VALUE); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check bars static bool warned=false; if(rates_total<BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(VolNormalized,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; int start=rates_total-BarsToShow; //--- for(int i=0; i<start; i++) VolNormalized[i]=EMPTY_VALUE; //--- copy close prices vector<double> price(BarsToShow); for(int i=0; i<BarsToShow; i++) price[i]=close[start+i]; //--- vector<double> l1(BarsToShow); bool res=price.L1TrendFilter(CoefLambda,true,l1); if(res) { //--- compute normalized volatility double mean=0.0; double stddev=0.0; for(int i=0; i<BarsToShow; i++) mean+=close[start+i]-l1[i]; mean/=BarsToShow; //--- for(int i=0; i<BarsToShow; i++) stddev+=MathPow(close[start+i]-l1[i]-mean,2); stddev=MathSqrt(stddev/BarsToShow); //--- for(int i=0; i<BarsToShow; i++) VolNormalized[start+i]=stddev>0?(close[start+i]-l1[i])/stddev:0; } //--- return(rates_total); } //+------------------------------------------------------------------+
Das Ergebnis der Berechnung ist in Abb. 14. dargestellt.

Abb.14. L1VolatilityNormalized.mq5 Indikator
3.4.4.5. L1VolatilityNormalizedSmoothed.mq5 – Geglätteter normalisierter Volatilitätsindikator
Dieser Indikator erweitert den Normalisierungsansatz um die Glättung des exponentiellen gleitenden Durchschnitts (EMA).
Vorteile:
- Reduziert den Einfluss von kurzzeitigem Rauschen und scharfen Ausreißern;
- Ergibt ein klareres und besser interpretierbares Volatilitätsprofil;
- Hilft bei der Bewertung der anhaltenden Volatilität und des aktuellen Marktregimes.
Der Indikator ist besonders nützlich für adaptive Strategien, die eine stabile Volatilitätsschätzung erfordern, z. B. bei der automatischen Auswahl von Handelsmodi.
Der Code des L1VolatilityNormalizedSmoothed.mq5-Indikators ist unten angegeben.
//+------------------------------------------------------------------+ //| L1VolatilityNormalizedSmoothed.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_label1 "L1VolatilityNormalizedSmoothed" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDeepSkyBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units input int SmoothPeriod = 10; // EMA smoothing period (1=no smoothing) //--- double NormVolSmooth[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- prepare SetIndexBuffer(0,NormVolSmooth,INDICATOR_DATA); ArrayInitialize(NormVolSmooth,EMPTY_VALUE); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check bars static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(NormVolSmooth,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc= (prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; int start=rates_total-BarsToShow; //--- for(int i=0; i<start; i++) NormVolSmooth[i]=EMPTY_VALUE; //--- copy close prices vector<double> price(BarsToShow); for(int i=0; i<BarsToShow; i++) price[i]=close[start+i]; //--- vector<double> l1(BarsToShow); bool res=price.L1TrendFilter(CoefLambda,true,l1); if(res) { //--- compute normalized volatility vector<double> VolNormalized(BarsToShow); double mean = 0, stddev = 0; for(int i=0; i<BarsToShow; i++) mean += close[start+i]-l1[i]; mean /= BarsToShow; //--- for(int i=0; i<BarsToShow; i++) stddev += MathPow(close[start+i]-l1[i]-mean,2); stddev = MathSqrt(stddev/BarsToShow); //--- for(int i=0; i<BarsToShow; i++) VolNormalized[i]=stddev>0 ? (close[start+i]-l1[i])/stddev: 0; //--- EMA smoothing vector<double> Smooth(BarsToShow); double alpha=(SmoothPeriod<=1) ? 1.0: 2.0/(SmoothPeriod+1.0); //--- Smooth[0] = VolNormalized[0]; for(int i=1; i<BarsToShow; i++) Smooth[i]=alpha*VolNormalized[i]+(1.0-alpha)*Smooth[i-1]; //--- copy to indicator buffer for(int i=0; i<BarsToShow; i++) NormVolSmooth[start+i]=Smooth[i]; } //--- return(rates_total); } //+------------------------------------------------------------------+
Das Ergebnis der Berechnung ist in Abb. 15. dargestellt.

Abb.15. L1VolatilityNormalized.mq5 und L1VolatilityNormalizedSmoothed.mq5 Indikatoren
3.4.4.6. L1VolatilityRegime.mq5 – Indikator zur Erkennung von Marktregimen
Der Indikator klassifiziert das aktuelle Marktregime anhand der normalisierten und geglätteten Volatilität und identifiziert vier Marktzustände.
Merkmale des Indikators:
- Völlig autonom und nicht auf externe Daten angewiesen;
- Bietet eine klare Visualisierung der Marktdynamik für adaptive Strategien;
- Die Schwellenwertparameter LowVolThresh und HighVolThresh können für verschiedene Instrumente und Zeitrahmen angepasst werden.
| Wert | Regime | Beschreibung |
|---|---|---|
| 0 | Range | Geringe Volatilität, Seitwärtsmarkt |
| 1 | Trend | Mäßige Volatilität, Vorhandensein eines Trends |
| 2 | Expansion | Starke Bewegung, Marktexpansion |
| 3 | Panik | Extreme Volatilität, starke Bewegungen |
Tabelle 3. Regime des L1VolatilityRegime.mq5 Indikators
- Schnelle Ermittlung der aktuellen Marktlage;
- Anpassung der Handelsstrategien an die vorherrschenden Bedingungen;
- Reduzierung des Risikos bei extremen Bewegungen und verbessern der Handelseffizienz.
Der Code des Indikators L1VolatilityRegime.mq5 ist nachstehend aufgeführt.
//+------------------------------------------------------------------+ //| L1VolatilityRegime.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1 Volatility Regime" #property indicator_type1 DRAW_LINE #property indicator_color1 clrRoyalBlue #property indicator_width1 2 //--- input parameters input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units input int ATRPeriod = 14; // ATR period input int SmoothPeriod = 10; // Smooth period input double L1MoveThresh = 0.0; // Move volatility input double LowVolThresh = 0.5; // Low volatility input double HighVolThresh = 1.5; // High volatility input double PanicMult = 2.0; // Panic volatility //--- double Regime[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, Regime, INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE, EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS, 0); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check bars if(rates_total<BarsToShow+ATRPeriod) { ArrayInitialize(Regime,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int count=BarsToShow; //--- for(int i=0; i<start; i++) Regime[i]=EMPTY_VALUE; //--- vector<double> DataClose(count),DataHigh(count),DataLow(count); for(int i=0; i<count; i++) { DataClose[i]=close[start+i]; DataHigh[i]=high[start+i]; DataLow[i]=low[start+i]; } //--- vector<double> L1(count); bool res=DataClose.L1TrendFilter(CoefLambda,true,L1); if(!res) return(prev_calculated); //--- vector<double> TR(count),ATR(count); for(int i=0; i<count; i++) { if(i==0) TR[i]=DataHigh[i]-DataLow[i]; else { double h_l=DataHigh[i]-DataLow[i]; double h_pc=MathAbs(DataHigh[i]-DataClose[i-1]); double l_pc=MathAbs(DataLow[i]-DataClose[i-1]); TR[i]=MathMax(h_l,MathMax(h_pc,l_pc)); } int from=MathMax(0,i-ATRPeriod+1); double sumTR=0.0; int n = i-from+1; for(int j=from; j<=i;j++) sumTR += TR[j]; ATR[i]=sumTR/n; } //--- vector<double> NormVol(count), SmoothVol(count); for(int i=0; i<count; i++) NormVol[i]=(ATR[i]>0) ? MathAbs(DataClose[i]-L1[i])/ATR[i] : 0; double alpha=2.0/(SmoothPeriod+1.0); SmoothVol[0]=NormVol[0]; for(int i=1; i<count; i++) SmoothVol[i]=alpha*NormVol[i]+(1.0-alpha)*SmoothVol[i-1]; //--- for(int i=0; i<count; i++) { double vol=SmoothVol[i]; double deltaL1=(i>0) ? (L1[i]-L1[i-1]): 0.0; if(vol<LowVolThresh) Regime[start+i]=0; // Range else if(vol>=LowVolThresh && vol<HighVolThresh) Regime[start+i]=(MathAbs(deltaL1)>L1MoveThresh) ? 1:0; // Trend/Range else if(vol>=HighVolThresh && vol<HighVolThresh*PanicMult) Regime[start+i]=2; // Expansion else Regime[start+i]=3; // Panic } //--- return(rates_total); } //+------------------------------------------------------------------+
Das Ergebnis der Berechnung ist in Abb. 16. dargestellt.

Abb.16. L1VolatilityRegime.mq5 Indikator
Der Einfachheit halber kann auch eine Version mit farbkodierter Visualisierung des Regimes verwendet werden.
Der Code des Indikators L1VolatilityRegimeColor.mq5 ist unten aufgeführt.
//+------------------------------------------------------------------+ //| L1VolatilityRegimeColor.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 4 #property indicator_plots 4 //--- #property indicator_label1 "Range" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- #property indicator_label2 "Trend" #property indicator_type2 DRAW_LINE #property indicator_color2 clrLime #property indicator_width2 2 //--- #property indicator_label3 "Expansion" #property indicator_type3 DRAW_LINE #property indicator_color3 clrOrange #property indicator_width3 2 //--- #property indicator_label4 "Panic" #property indicator_type4 DRAW_LINE #property indicator_color4 clrRed #property indicator_width4 2 //--- input parameters input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units input int ATRPeriod = 14; // ATR period input int SmoothPeriod = 10; // Smooth period input double L1MoveThresh = 0.0; // Move volatility input double LowVolThresh = 0.5; // Low volatility input double HighVolThresh = 1.5; // High volatility input double PanicMult = 2.0; // Panic volatility //--- buffers double Regime[]; double BufRange[], BufTrend[], BufExpansion[], BufPanic[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,BufRange,INDICATOR_DATA); SetIndexBuffer(1,BufTrend,INDICATOR_DATA); SetIndexBuffer(2,BufExpansion,INDICATOR_DATA); SetIndexBuffer(3,BufPanic,INDICATOR_DATA); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,EMPTY_VALUE); //--- IndicatorSetInteger(INDICATOR_DIGITS,0); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total<BarsToShow+ATRPeriod) { ArrayInitialize(Regime,EMPTY_VALUE); ArrayInitialize(BufRange,EMPTY_VALUE); ArrayInitialize(BufTrend,EMPTY_VALUE); ArrayInitialize(BufExpansion,EMPTY_VALUE); ArrayInitialize(BufPanic,EMPTY_VALUE); return(0); } //--- new bars static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int count=BarsToShow; //--- ArrayResize(Regime,rates_total); for(int i=0;i<start;i++) Regime[i]=EMPTY_VALUE; //--- vector<double> DataClose(count),DataHigh(count),DataLow(count); for(int i=0; i<count; i++) { DataClose[i]=close[start+i]; DataHigh[i]=high[start+i]; DataLow[i]=low[start+i]; } //--- vector<double> L1(count); bool res=DataClose.L1TrendFilter(CoefLambda,true,L1); if(!res) return(prev_calculated); //--- vector<double> TR(count),ATR(count); for(int i=0; i<count; i++) { if(i==0) TR[i]=DataHigh[i]-DataLow[i]; else { double h_l = DataHigh[i]-DataLow[i]; double h_pc = MathAbs(DataHigh[i]-DataClose[i-1]); double l_pc = MathAbs(DataLow[i]-DataClose[i-1]); TR[i] = MathMax(h_l, MathMax(h_pc, l_pc)); } int from=MathMax(0,i-ATRPeriod+1); double sumTR=0; int n=i-from+1; for(int j=from; j<=i; j++) sumTR+=TR[j]; ATR[i]=sumTR/n; } //--- vector<double> NormVol(count), SmoothVol(count); for(int i=0;i<count;i++) NormVol[i]=(ATR[i]>0) ? MathAbs(DataClose[i]-L1[i])/ATR[i] : 0; //--- double alpha=2.0/(SmoothPeriod+1.0); SmoothVol[0]=NormVol[0]; for(int i=1; i<count; i++) SmoothVol[i]=alpha*NormVol[i]+(1.0-alpha)*SmoothVol[i-1]; //--- calc Regime[] for(int i=0; i<count; i++) { double vol=SmoothVol[i]; double deltaL1=(i>0) ? (L1[i]-L1[i-1]):0.0; if(vol<LowVolThresh) Regime[start+i]=0; else if(vol<HighVolThresh) Regime[start+i]=(MathAbs(deltaL1)>L1MoveThresh) ? 1:0; else if(vol<HighVolThresh*PanicMult) Regime[start+i]=2; else Regime[start+i]=3; } //--- buffers for(int i=0; i<rates_total; i++) { BufRange[i] = (Regime[i]==0) ? Regime[i]: EMPTY_VALUE; BufTrend[i] = (Regime[i]==1) ? Regime[i]: EMPTY_VALUE; BufExpansion[i] = (Regime[i]==2) ? Regime[i]: EMPTY_VALUE; BufPanic[i] = (Regime[i]==3) ? Regime[i]: EMPTY_VALUE; } //--- return(rates_total); } //+------------------------------------------------------------------+
Das kombinierte Berechnungsergebnis ist in Abb. 17. dargestellt.

Abb.17. L1VolatilityRegime.mq5 und L1VolatilityRegimeColor.mq5 Indikatoren
Die Abbildungen 18-20 zeigen Beispiele für gemeinsame Volatilitätsindikatoren für EURGBP, AUDCAD und CHFJPY.

Abb.18. Volatilitätsindikatoren für EURGBP

Abb.19. Volatilitätsindikatoren für AUDCAD

Abb.20. Volatilitätsindikatoren für CHFJPY
3.5. Verwendung des L1-Trends in Handelsstrategien
In diesem Abschnitt betrachten wir MovingAverage-, MACD-, ADX- und EMA-Handelsstrategien mit verschiedenen Optionen für die Anwendung von Handelssignalfiltern.
Durch das Hinzufügen von Filtern zu Handelssignalen können die Eigenschaften von Handelssystemen verbessert werden. Um die Effektivität des Filtereinsatzes in den vorgestellten Expert Advisors zu analysieren, werden wir Balance- und Equity-Daten (bei jeder neuen Bar) in separaten Dateien speichern und ein Python-Skript verwenden, um die Ergebnisse der Handelssysteme in verschiedenen Modi zu visualisieren.
Alle vorgestellten Expert Advisors haben die gleiche Architektur.
Allgemeines Prinzip für die Ausrichtung des L1-Trends auf Handelssignale
- Der Eröffnungsfilter (L1FilterOpen = true) erlaubt die Eröffnung von Trades nur in Richtung des dominanten Trends.
- Der Ausstiegsfilter (L1FilterClose = true) hilft, Positionen während starker Trends zu halten und reduziert vorzeitige Ausstiege während lokaler Korrekturen.
Eingabeparameter (gemeinsam für alle Expert Advisors):
//--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file
Speichern von Balance- und Equity-Daten in einer Datei
Der Eingabeparameter SaveStatistics ermöglicht das Speichern der aktuellen Werte von Zeit, Schlusskurs, Balance, Equity usw. bei jeder neuen Bar in eine Datei in: terminal_data_folder\Tester\Agent-127.0.0.1-3000\MQL5\Files.
Die Funktion zum Speichern der Daten wird innerhalb von OnTick() aufgerufen und hängt vom Wert des Eingabeparameters bool SaveStatistics ab.
//+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); }
Das Präfix im gespeicherten Dateinamen hängt von der Kombination von L1FilterOpen und L1FilterClose ab.
Der Dateiname hängt von der Strategie und dem Symbol ab und wird in der Initialisierungsfunktion des Expert Advisors gebildet:
//+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return filename; } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return INIT_SUCCEEDED; }
Anwendung von Handelssignalfiltern in verschiedenen Modi:
//+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); }
Anwendung von Handelssignalfiltern in verschiedenen Modi
Sequentielle Ausführung des Expert Advisors im Strategietester mit allen 4 Kombinationen:
- L1FilterOpen = false, L1FilterClose = false (Handel ohne Filter);
- L1FilterOpen = true, L1FilterClose = false (Filter für die Eröffnung);
- L1FilterOpen = false, L1FilterClose = true (Filter für das Schließen);
- L1FilterOpen = true, L1FilterClose = true (Filter für die Eröffnung und das Schließen).
erzeugt Dateien wie z.B.: 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt, 3_MA_EURUSD.txt.
Diese Dateien enthalten Balance-/Equity-Daten und ermöglichen einen Vergleich der Wirksamkeit der Handelssignalfilter mithilfe der L1-Trendausrichtung.
Visualisierung von Daten
Um kombinierte Charts zu erstellen, kopieren wir die 4 Dateien in einen separaten Ordner und führen das Python-Skript aus (z. B. „C:\data\“).
import pandas as pd import matplotlib.pyplot as plt import os # --- folder for charts output_dir = "C:\\data\\charts\\" os.makedirs(output_dir, exist_ok=True) symbol = "EURUSD" name_strategy = "MA" file_strategy = name_strategy+"_"+symbol title_strategy = " ("+symbol+" "+name_strategy+" strategy+filters)" file_prefix = symbol+"_"+name_strategy+"_" # --- files files = [ "C:\\data\\0_"+file_strategy+".txt", "C:\\data\\1_"+file_strategy+".txt", "C:\\data\\2_"+file_strategy+".txt", "C:\\data\\3_"+file_strategy+".txt" ] # --- labels labels = [ "No filters", "Open L1 filter", "Close L1 filter", "Open+Close L1 filter" ] # --- load data def load_file(filename): df = pd.read_csv( filename, sep=";", header=None, names=[ "time", "balance", "equity", "margin", "free_margin", "margin_level", "volume", "close" ] ) df["time"] = pd.to_datetime(df["time"]) return df # --- close price chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["close"], color='gray') plt.title(symbol+" Close Price") plt.xlabel("Time") plt.ylabel("closing price") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"close_price.png", dpi=100) plt.show() # --- balance chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["balance"], label=label) plt.title("Balance" + title_strategy) plt.xlabel("Time") plt.ylabel("Balance") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"balance.png", dpi=100) plt.show() plt.close() # --- equity chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["equity"], label=label) plt.title("Equity" + title_strategy) plt.xlabel("Time") plt.ylabel("Equity") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"equity.png", dpi=100) plt.show() plt.close() #--- balance + equity chart plt.figure(figsize=(10,6), dpi=100) for i, (file, label) in enumerate(zip(files, labels)): df = load_file(file) # --- get matplotlib color color = plt.rcParams["axes.prop_cycle"].by_key()["color"][i % 10] #--- equity — solid line plt.plot( df["time"], df["equity"], color=color, linestyle="-", label=f"{label} equity" ) #--- balance — dashed line plt.plot( df["time"], df["balance"], color=color, linestyle="--", label=f"{label} balance" ) plt.title("Balance + Equity" + title_strategy) plt.xlabel("Time") plt.ylabel("Value") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir+file_prefix+"balance_equity.png", dpi=100) plt.show() plt.close()
Implementierung von Handelssignalfiltern
Die Parameter L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda definieren die Einstellungen für die L1-Trendberechnung und ihre Verwendung beim Filtern von Handelssignalen.
L1-Trendberechnung und Abgleich mit Handelssignalen
In allen Expert Advisors wird eine zusätzliche Filterung durch die L1-Trendanalyse vorgenommen:
- Eine geglättete Kursreihe wird unter Verwendung der letzten L1TotalBars erstellt;
- Der Wachstumskoeffizient Delta des Trends wird als Differenz zwischen den letzten beiden gefilterten Werten berechnet;
Wenn delta > 0 – Aufwärtstrend; wenn delta < 0 – Abwärtstrend.
//+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1]-data_filtered[data_count-2]; //--- return(dp); }
Der Parameter L1CoefLambda wird in Einheiten von λmax angegeben, wodurch die Filter robust gegenüber der Volatilität und der Anzahl der Bars wird.
Filter für Positionseröffnungen
Wenn L1FilterOpen = true:
- wird das BUY-Signal bei einem negativen L1-Trend ignoriert;
- wird das SELL-Signal bei einem positiven L1-Trend ignoriert.
Der Handel wird nur in Richtung des vorherrschenden Trends eröffnet.
//+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; if(signal == WRONG_VALUE) return; //--- L1 filter if(L1FilterOpen) { double delta = CheckTrendL1(); if(signal == ORDER_TYPE_BUY && delta < 0) { signal = WRONG_VALUE; PrintFormat("Open BUY signal cancelled by L1 trend delta=%.5f", delta); } if(signal == ORDER_TYPE_SELL && delta > 0) { signal = WRONG_VALUE; PrintFormat("Open SELL signal cancelled by L1 trend delta=%.5f", delta); } } //--- if(signal == WRONG_VALUE) return; //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars) return; //--- double price = (signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK): SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0); }
Handelsausstiegsfilter
Wenn L1FilterClose = true:
- BUY-Positionen werden nicht durch Umkehrsignale geschlossen, solange der L1-Trend aufwärts gerichtet ist;
- SELL-Positionen werden nicht geschlossen, solange der L1-Trend abwärts gerichtet ist.
Dies trägt dazu bei, Positionen in starken Trends zu halten, und verringert den vorzeitigen Ausstieg bei lokalen Korrekturen.
//+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=MA_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type = PositionGetInteger(POSITION_TYPE); bool close_signal = false; //--- if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL) close_signal = true; if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY) close_signal = true; //--- check L1 filter if(L1FilterClose) { double delta = CheckTrendL1(); if(type == POSITION_TYPE_BUY && delta > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend delta=%.5f", delta); } if(type == POSITION_TYPE_SELL && delta < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend delta=%.5f", delta); } } //--- if(close_signal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>=L1TotalBars) ExtTrade.PositionClose(_Symbol,3); }
3.5.1. Handelsstrategie des gleitenden Durchschnitts
Als erstes Beispiel betrachten wir einen Expert Advisor, der auf der Trendfolgestrategie mit einem klassischen gleitenden Durchschnitt basiert.
Handelssignale werden auf der Grundlage der Kreuzung des Schlusskurses und des gleitenden Durchschnitts erzeugt:
- BUY – wenn Close den gleitenden Durchschnitt von unten nach oben kreuzt;
- SELL – wenn Close den gleitenden Durchschnitt von oben nach unten kreuzt.
Die Signale werden anhand der letzten beiden geschlossenen Bars ausgewertet, wodurch der Einfluss des aktuellen, noch nicht beendeten Bars eliminiert und Fehlsignale reduziert werden.
Zusätzlich werden Handelseinstiegs- und -ausstiegsfilter gemäß den L1-Trend-Einstellungen (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda) angewendet.
Code des MovingAverageFilteredL1.mq5 Expert Advisor:
//+------------------------------------------------------------------+ //| MovingAverageFilteredL1.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- best MovingAverage parameters for EURUSD,H1,2025 input int MovingPeriod = 61; // MA period input int MovingShift = 0; // MA shift //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file //--- #define MA_MAGIC 1234501 #include <Trade\Trade.mqh> CTrade ExtTrade; int ExtHandle = INVALID_HANDLE; bool ExtHedging = false; string ExtStrategyName="MA"; string ExtStrategyFileName=""; //+------------------------------------------------------------------+ //| Check new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time=0; datetime t[1]; //--- if(CopyTime(_Symbol,_Period,0,1,t)>0) { if(t[0]!=last_time) { last_time=t[0]; return(true); } } return(false); } //+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1]-data_filtered[data_count-2]; //--- return(dp); } //+------------------------------------------------------------------+ //| GetTradeSignal | //+------------------------------------------------------------------+ bool GetTradeSignal(ENUM_ORDER_TYPE &signal) { signal = WRONG_VALUE; MqlRates bars[]; double ma[]; ArraySetAsSeries(bars,true); ArraySetAsSeries(ma,true); ArrayResize(bars,2); ArrayResize(ma,2); //-- two last closed bars if(CopyRates(_Symbol,_Period,2,2,bars) != 2) { Print("CopyRates failed"); return(false); } if(CopyBuffer(ExtHandle,0,2,2,ma) != 2) { Print("CopyBuffer failed"); return(false); } double close_prev = bars[1].close; double close_last = bars[0].close; double ma_prev = ma[1]; double ma_last = ma[0]; //--- check MA crossover if(close_prev < ma_prev && close_last > ma_last) signal = ORDER_TYPE_BUY; else if(close_prev > ma_prev && close_last < ma_last) signal = ORDER_TYPE_SELL; //--- log // PrintFormat("PrevBar: time=%s close=%.5f ma=%.5f | LastBar: time=%s close=%.5f ma=%.5f | Signal=%s", // TimeToString(bars[0].time,TIME_DATE|TIME_MINUTES), close_prev, ma_prev, // TimeToString(bars[1].time,TIME_DATE|TIME_MINUTES), close_last, ma_last, // (signal==ORDER_TYPE_BUY?"BUY":signal==ORDER_TYPE_SELL?"SELL":"NONE")); //--- return(true); } //+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; if(signal == WRONG_VALUE) return; //--- L1 filter if(L1FilterOpen) { double delta = CheckTrendL1(); if(signal == ORDER_TYPE_BUY && delta < 0) { signal = WRONG_VALUE; PrintFormat("Open BUY signal cancelled by L1 trend delta=%.5f", delta); } if(signal == ORDER_TYPE_SELL && delta > 0) { signal = WRONG_VALUE; PrintFormat("Open SELL signal cancelled by L1 trend delta=%.5f", delta); } } //--- if(signal == WRONG_VALUE) return; //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars) return; //--- double price = (signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK): SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0); } //+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=MA_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type = PositionGetInteger(POSITION_TYPE); bool close_signal = false; //--- if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL) close_signal = true; if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY) close_signal = true; //--- check L1 filter if(L1FilterClose) { double delta = CheckTrendL1(); if(type == POSITION_TYPE_BUY && delta > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend delta=%.5f", delta); } if(type == POSITION_TYPE_SELL && delta < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend delta=%.5f", delta); } } //--- if(close_signal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>=L1TotalBars) ExtTrade.PositionClose(_Symbol,3); } //+------------------------------------------------------------------+ //| SelectPosition | //+------------------------------------------------------------------+ bool SelectPosition() { bool res = false; if(ExtHedging) { uint total = PositionsTotal(); for(uint i=0; i<total; i++) { string sym = PositionGetSymbol(i); if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==MA_MAGIC) { res = true; break; } } } else { if(PositionSelect(_Symbol)) res = (PositionGetInteger(POSITION_MAGIC)==MA_MAGIC); } return(res); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- check parameters if(MovingPeriod<=0) { Print("Error: MovingPeriod parameter must be positive"); return(INIT_PARAMETERS_INCORRECT); } ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MA_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(_Symbol); //--- prepare indicator ExtHandle = iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { Print("Failed to create MA handle"); return(INIT_FAILED); } //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return(filename); } //+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); } //+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); //--- IndicatorRelease(ExtHandle); } //+------------------------------------------------------------------+
3.5.1.1. Allgemeine Methodik zur Bewertung der Effizienz der L1-Trendfilter
Um die Wirksamkeit des L1-Filters zu bewerten, ist Folgendes erforderlich:
- Finde den besten Parametersatz der Handelsstrategie, der den maximalen Gewinn abwirft.
Es ist ratsam, die Handelssignale der leistungsstärksten Strategien zu verbessern. - Betrachte die Testergebnisse für 4 Varianten der Filterverwendung (durch Einstellung der Parameter L1FilterOpen / L1FilterClose):
- Handel ohne Filter;
- Handel mit Filter beim Einstieg;
- Handel mit Filter beim Ausstieg;
- Handel mit Filter beim Einstieg und Ausstieg;
- Anschließend wird ein Python-Skript zur Erstellung kombinierter Diagramme ausgeführt.
Im Folgenden werden diese Schritte am Beispiel des Expert Advisors MovingAverageFilteredL1.mq5, Handel mit EURUSD, Zeitrahmen H1, Testperiode Jahr 2025 besprochen.
Für den Filtervorgang wird eine feste Anzahl von Bars für die Trendberechnung verwendet: L1TotalBars = 1000. Der Regularisierungsparameter λ wird in Einheiten von λmax angegeben, wobei ein fester Wert L1CoefLambda = 0,2 verwendet wird.
Eingabeparameter:
//--- best MovingAverage parameters for EURUSD,H1,2025 input int MovingPeriod = 64; // MA period input int MovingShift = 0; // MA shift //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file
Geben wir in den Optimierungseinstellungen das Symbol EURUSD, den Zeitrahmen H1 und den Testzeitraum von 2025.01.01 bis 2025.12.31 an.

Abb.21. Optimierungseinstellungen des Expert Advisors MovingAverageFilteredL1.mq5
Für eine schnelle Optimierung verwenden wir den Modus „1 minute OHLC“ (diese Strategie arbeitet nur mit neuen Bars, daher ist diese Annäherung akzeptabel) und wählen den Optimierungsalgorithmus „Fast genetic based algorithm“ mit der Einstellung „Balance max“.

Abb.22. Optimierungsparameter des Expert Advisors MovingAverageFilteredL1.mq5
Da unser Ziel in dieser Phase darin besteht, die besten Parameter der Handelsstrategie zu finden, deaktivieren wir alle Filter und das Speichern von Dateien in den Optimierungsparametern.
Der Einfachheit halber optimieren wir nur einen Parameter, „MA Period“, im Bereich von 1 bis 800, mit Schritt 1.

Abb.23. Optimierungsergebnisse des Expert Advisors MovingAverageFilteredL1.mq5
Die Optimierung des Parameters „MA-Periode“ dauerte weniger als 1 Minute; die Ergebnisse und die Liste der besten Werte sind in Abb.23 dargestellt.
Für genauere Tests wählen wir in den Testeinstellungen „Jeder Tick basiert auf echten Ticks“:

Abb.24. Testen der Einstellungen für den Expert Advisor MovingAverageFilteredL1.mq5
Anschließend wird ein Einzeltest mit dem besten Wert „MA Periode“ = 64 durchgeführt:

Abb.25. Beste Optimierungsparameter des Expert Advisors MovingAverageFilteredL1.mq5

Abb.26. Testergebnisse mit den besten Parametern für den Expert Advisor MovingAverageFilteredL1.mq5
Jetzt ist es notwendig, Tests mit dem Speichern von Daten in Dateien durchzuführen.
Dazu setzen wir SaveStatistics = true und führen Tests mit 4 Kombinationen von Handelssignalfiltern durch.

Abb.27. Testparameter für das Speichern von Ergebnissen in Dateien für den Expert Advisor MovingAverageFilteredL1.mq5
Nachdem die Tests mit allen 4 Kombinationen durchgeführt wurden, enthält das Testerverzeichnis Dateien: 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt und 3_MA_EURUSD.txt.
Sie enthalten die Zeit, den Schlusskurs sowie Balance und Equity für jede Bar des Testintervalls.
Erstellen Sie ein separates Verzeichnis und kopieren Sie sie dorthin (in diesem Beispiel C:\Data).

Abb.28. Dateien mit Testergebnissen für alle 4 Filtermodi
Die Datenanalyse wird mit einem Python-Skript durchgeführt:
import pandas as pd import matplotlib.pyplot as plt import os # --- folder for charts output_dir = "C:\\data\\charts\\" os.makedirs(output_dir, exist_ok=True) symbol = "EURUSD" name_strategy = "MA" file_strategy = name_strategy+"_"+symbol title_strategy = " ("+symbol+" "+name_strategy+" strategy+filters)" file_prefix = symbol+"_"+name_strategy+"_" # --- files files = [ "C:\\data\\0_"+file_strategy+".txt", "C:\\data\\1_"+file_strategy+".txt", "C:\\data\\2_"+file_strategy+".txt", "C:\\data\\3_"+file_strategy+".txt" ] # --- labels labels = [ "No filters", "Open L1 filter", "Close L1 filter", "Open+Close L1 filter" ] # --- load data def load_file(filename): df = pd.read_csv( filename, sep=";", header=None, names=[ "time", "balance", "equity", "margin", "free_margin", "margin_level", "volume", "close" ] ) df["time"] = pd.to_datetime(df["time"]) return df # --- close price chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["close"], color='gray') plt.title(symbol+" Close Price") plt.xlabel("Time") plt.ylabel("closing price") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"close_price.png", dpi=100) plt.show() # --- balance chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["balance"], label=label) plt.title("Balance" + title_strategy) plt.xlabel("Time") plt.ylabel("Balance") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"balance.png", dpi=100) plt.show() plt.close() # --- equity chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["equity"], label=label) plt.title("Equity" + title_strategy) plt.xlabel("Time") plt.ylabel("Equity") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"equity.png", dpi=100) plt.show() plt.close() #--- balance + equity chart plt.figure(figsize=(10,6), dpi=100) for i, (file, label) in enumerate(zip(files, labels)): df = load_file(file) # --- get matplotlib color color = plt.rcParams["axes.prop_cycle"].by_key()["color"][i % 10] #--- equity — solid line plt.plot( df["time"], df["equity"], color=color, linestyle="-", label=f"{label} equity" ) #--- balance — dashed line plt.plot( df["time"], df["balance"], color=color, linestyle="--", label=f"{label} balance" ) plt.title("Balance + Equity" + title_strategy) plt.xlabel("Time") plt.ylabel("Value") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir+file_prefix+"balance_equity.png", dpi=100) plt.show() plt.close()
Um das Python-Skript in MetaEditor auszuführen, drücken Sie auf „Kompilieren“ (Abb.29).

Abb.29. Skript „PlotData.py“ in MetaEditor
Nach der Ausführung von PlotData.py werden die folgenden Charts angezeigt:
- EURUSD-Kursreihe (Schlusskurse pro Bar);
- Balance-Kurven für alle Filtermodi;
- Equity-Kurven für alle Filtermodi;
- Kombinierte Kurven von Balance + Equity (zur Bewertung der Reduzierung des Drawdowns).
Die Charts werden ebenfalls im PNG-Format unter C:\Data\Charts\ gespeichert.

Abb.30. EURUSD-Kurschart für den Testzeitraum

Abb.31. Balance-Kurven für den Expert Advisor MovingAverageFilteredL1.mq5 für die verschiedenen Filtermodi

Abb.32. Equity-Kurven für den Expert Advisor MovingAverageFilteredL1.mq5 für verschiedene Filtermodi

Abb.33. Kombinierte Balance- und Equity-Kurven für den Expert Advisor MovingAverageFilteredL1.mq5 für die verschiedenen Filtermodi
Um Python-Skripte in MetaEditor auszuführen, installieren Sie Python (im Beispiel Version 3.14) und geben Sie den Pfad in den Einstellungen an.

Abb.34. Python-Einstellungen in MetaEditor
Das Skript verwendet die Bibliotheken pandas und matplotlib. Wenn sie nicht installiert sind:
pip install pandas pip install matplotlib
3.5.1.2. Ergebnisse der Anwendung von L1-Filtern (MovingAverage-Strategie)
Die Ergebnisse (Balance-, Equity und kombinierte Kurven) sind in den Abbildungen 35-37 dargestellt.
Farben:
- Blau (Strategie ohne Filter);
- Grün (L1-Ausstiegsfilter der Position);
- Rot (L1-Filter sowohl beim Öffnen als auch beim Schließen);
- Orange (L1-Filter beim Öffnen);

Abb.35. Balance-Kurven für den Expert Advisor MovingAverageFilteredL1.mq5 für die verschiedenen Filtermodi

Abb.36. Equity-Kurven für den Expert Advisor MovingAverageFilteredL1.mq5 für die verschiedenen Filtermodi

Abb.37. Kombinierte Balance- und Equity-Kurven für den Expert Advisor MovingAverageFilteredL1.mq5 für verschiedene Filtermodi
3.5.2. MACD-Handelsstrategie
Ein weiteres Beispiel ist ein Expert Advisor, der auf der Grundlage von Signalen einer Handelsstrategie handelt, die auf dem Indikator MACD (Moving Average Convergence/Divergence) basiert.
Handelssignale
Signale werden erzeugt, wenn die Hauptlinie des MACD die Signallinie kreuzt:
- BUY – die Hauptlinie des MACD kreuzt die Signallinie von unten nach oben.
- SELL – die Hauptlinie des MACD kreuzt die Signallinie von oben nach unten.
Die Signale werden nur zum Zeitpunkt des Schließens der Bar analysiert, was die Anzahl der falschen Auslöser innerhalb der Bars reduziert.
Zusätzlich werden Handelseinstiegs- und -ausstiegsfilter gemäß den L1-Trendfiltereinstellungen (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda) verwendet.
Code des Expert Advisors MACDFilteredL1.mq5:
//+------------------------------------------------------------------+ //| MACDFilteredL1.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- best MACD parameters for USDCHF,H1,2025 input int FastEMA = 43; // Fast EMA input int SlowEMA = 59; // Slow EMA input int SignalEMA = 37; // SignalEMA //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file //--- #define MACD_MAGIC 1234502 #include <Trade\Trade.mqh> int ExtHandle = INVALID_HANDLE; bool ExtHedging = false; CTrade ExtTrade; string ExtStrategyName="MACD"; string ExtStrategyFileName=""; //+------------------------------------------------------------------+ //| Check new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time=0; datetime t[1]; //--- if(CopyTime(_Symbol,_Period,0,1,t)>0) { if(t[0]!=last_time) { last_time=t[0]; return(true); } } return(false); } //+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1]-data_filtered[data_count-2]; //--- return(dp); } //+------------------------------------------------------------------+ //| GetTradeSignal(MACD) | //+------------------------------------------------------------------+ bool GetTradeSignal(ENUM_ORDER_TYPE &signal) { signal = WRONG_VALUE; double macd_main[]; double macd_signal[]; //--- ArrayResize(macd_main,2); ArrayResize(macd_signal,2); //--- ArraySetAsSeries(macd_main, true); ArraySetAsSeries(macd_signal, true); //--- buffer 0 = MACD main, buffer 1 = signal line if(CopyBuffer(ExtHandle,0,1,2,macd_main)!=2) return(false); if(CopyBuffer(ExtHandle,1,1,2,macd_signal)!=2) return(false); //--- double main_prev = macd_main[1]; double main_last = macd_main[0]; double signal_prev = macd_signal[1]; double signal_last = macd_signal[0]; //--- MACD crossover if(main_prev < signal_prev && main_last > signal_last) signal = ORDER_TYPE_BUY; else if(main_prev > signal_prev && main_last < signal_last) signal = ORDER_TYPE_SELL; //--- return(true); } //+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal) || signal == WRONG_VALUE) return; //--- if(L1FilterOpen) { double dp = CheckTrendL1(); if(signal == ORDER_TYPE_BUY && dp < 0) return; if(signal == ORDER_TYPE_SELL && dp > 0) return; } //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol, _Period) < L1TotalBars) return; //--- double price = (signal == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0); } //+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=MACD_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type = PositionGetInteger(POSITION_TYPE); bool close_signal = false; //--- if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL) close_signal = true; if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY) close_signal = true; //--- check L1 filter if(L1FilterClose) { double dp = CheckTrendL1(); if(type == POSITION_TYPE_BUY && dp > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp); } if(type == POSITION_TYPE_SELL && dp < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp); } } //--- if(close_signal) ExtTrade.PositionClose(_Symbol, 3); } //+------------------------------------------------------------------+ //| SelectPosition | //+------------------------------------------------------------------+ bool SelectPosition() { bool res = false; if(ExtHedging) { uint total = PositionsTotal(); for(uint i=0; i<total; i++) { string sym = PositionGetSymbol(i); if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==MACD_MAGIC) { res = true; break; } } } else { if(PositionSelect(_Symbol)) res = (PositionGetInteger(POSITION_MAGIC)==MACD_MAGIC); } return(res); } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { //--- check parameters if(FastEMA <= 0 || SlowEMA <= 0 || SignalEMA <= 0) { Print("Error: MACD parameters must be positive"); return(INIT_PARAMETERS_INCORRECT); } if(FastEMA >= SlowEMA) { Print("FastEMA must be less than SlowEMA"); return(INIT_PARAMETERS_INCORRECT); } //--- ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MACD_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(_Symbol); //--- prepare indicator ExtHandle=iMACD(_Symbol,_Period,FastEMA,SlowEMA,SignalEMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { Print("Failed to create MACD handle"); return(INIT_FAILED); } //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return(filename); } //+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); } //+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); //--- if(ExtHandle != INVALID_HANDLE) IndicatorRelease(ExtHandle); } //+------------------------------------------------------------------+
Die Testeinstellungen sind in Abb.38 dargestellt.

Abb.38. Einstellungen des Strategy Testers für den Expert Advisor MACDFilteredL1.mq5

Abb.39. Testparameter des Expert Advisors MACDFilteredL1.mq5

Abb.40. Testergebnisse des Expert Advisors MACDFilteredL1.mq5

Abb.41. Testparameter für das Speichern von Ergebnissen in Dateien für den Expert Advisor MACDFilteredL1.mq5
Nach der Durchführung von Tests im Strategietester mit verschiedenen Filterkonfigurationen erscheinen im Testerverzeichnis die Dateien x_MACD_EURUSD.txt.
Sie sollten in den Ordner C:\Data\ kopiert werden und das Skript PlotData.py sollte ausgeführt werden.

Abb.42. Dateien mit Testergebnissen für den Expert Advisor MACDFiltered.mq5 für die verschiedenen Filtermodi
3.5.2.1. Ergebnisse der Anwendung von L1-Filtern (MACD-Strategie)
Die Ergebnisse sind in den Abbildungen 43-45 dargestellt.

Abb.43. Balance-Kurven des Expert Advisors MACDFilteredL1.mq5 für die verschiedenen Filtermodi

Abb.44. Equity-Kurve des Expert Advisors MACDFilteredL1.mq5 für die verschiedenen Filtermodi

Abb.45. Kombinierte Balance- und Equity-Kurven des Expert Advisors MACDFilteredL1.mq5 für die verschiedenen Filtermodi
3.5.3. ADX-Handelsstrategie
Ein weiteres Beispiel ist der Expert Advisor ADXFilteredL1.mq5, der eine Trendfolgestrategie auf der Grundlage des Average Directional Movement Index (ADX) implementiert.
Die wichtigsten Handelssignale beruhen auf der Analyse der +DI- und -DI-Linien sowie des ADX-Niveaus, das die Trendstärke charakterisiert.
Signale sind wie folgt definiert:
- BUY: +DI kreuzt -DI von unten nach oben;
- SELL: +DI kreuzt -DI von oben nach unten.
Zusätzlich wird der ADX-Wert berücksichtigt. Liegt der ADX unter dem Schwellenwert ADXTrendLevel, wird der Markt als schwach tendierend oder in einer Handelsspanne befindlich eingestuft, und das Signal wird ignoriert.
Es werden die Indikatorwerte der letzten beiden geschlossenen Bars verwendet, wodurch der Einfluss des aktuellen, noch nicht beendeten Bars ausgeschlossen und Fehlsignale reduziert werden.
Ein- und Ausstiegsfilter werden auch gemäß den L1-Trend-Einstellungen (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda) angewendet.
Der Code des Expert Advisors ADXFilteredL1.mq5 wird im Folgenden vorgestellt.
//+------------------------------------------------------------------+ //| ADXFilteredL1.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- best ADX parameters for EURUSD,H1,2025 input int ADXPeriod = 65; // ADX Period input double ADXTrendLevel = 7; // ADX Trend Level //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file //--- #define ADX_MAGIC 1234503 #include <Trade\Trade.mqh> CTrade ExtTrade; int ExtHandle = INVALID_HANDLE; bool ExtHedging = false; string ExtStrategyName="ADX"; string ExtStrategyFileName=""; //+------------------------------------------------------------------+ //| Check new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time=0; datetime t[1]; //--- if(CopyTime(_Symbol,_Period,0,1,t)>0) { if(t[0]!=last_time) { last_time=t[0]; return(true); } } return(false); } //+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1] - data_filtered[data_count-2]; //--- return(dp); } //+------------------------------------------------------------------+ //| GetTradeSignal (ADX) | //+------------------------------------------------------------------+ bool GetTradeSignal(ENUM_ORDER_TYPE &signal) { signal=WRONG_VALUE; double adx[],plusdi[],minusdi[]; ArrayResize(adx,2); ArrayResize(plusdi,2); ArrayResize(minusdi,2); //--- ArraySetAsSeries(adx,true); ArraySetAsSeries(plusdi,true); ArraySetAsSeries(minusdi,true); //--- buffer0 = ADX if(CopyBuffer(ExtHandle,0,1,2,adx)!=2) return(false); //--- buffer1 = +DI if(CopyBuffer(ExtHandle,1,1,2,plusdi)!=2) return(false); //--- buffer2 = -DI if(CopyBuffer(ExtHandle,2,1,2,minusdi)!=2) return(false); double adx_last=adx[0]; double plus_prev=plusdi[1]; double plus_last=plusdi[0]; double minus_prev=minusdi[1]; double minus_last=minusdi[0]; //--- strong trend required if(adx_last<ADXTrendLevel) return(true); //--- +DI cross -DI if(plus_prev<minus_prev && plus_last>minus_last) signal=ORDER_TYPE_BUY; else if(plus_prev>minus_prev && plus_last<minus_last) signal=ORDER_TYPE_SELL; //--- return(true); } //+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; //--- if(!GetTradeSignal(signal) || signal==WRONG_VALUE) return; //--- if(L1FilterOpen) { double dp=CheckTrendL1(); if(signal==ORDER_TYPE_BUY && dp<0) return; if(signal==ORDER_TYPE_SELL && dp>0) return; } //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars) return; //--- double price=(signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK) : SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol,signal,TradeLot,price,0,0); } //+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=ADX_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type=PositionGetInteger(POSITION_TYPE); bool close_signal=false; //--- if(type==POSITION_TYPE_BUY && signal==ORDER_TYPE_SELL) close_signal=true; //--- if(type==POSITION_TYPE_SELL && signal==ORDER_TYPE_BUY) close_signal=true; //--- check L1 filter //--- check L1 filter if(L1FilterClose) { double dp = CheckTrendL1(); if(type == POSITION_TYPE_BUY && dp > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp); } if(type == POSITION_TYPE_SELL && dp < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp); } } //--- if(close_signal) ExtTrade.PositionClose(_Symbol,3); } //+------------------------------------------------------------------+ //| SelectPosition | //+------------------------------------------------------------------+ bool SelectPosition() { bool res = false; if(ExtHedging) { uint total = PositionsTotal(); for(uint i=0; i<total; i++) { string sym = PositionGetSymbol(i); if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==ADX_MAGIC) { res = true; break; } } } else { if(PositionSelect(_Symbol)) res = (PositionGetInteger(POSITION_MAGIC)==ADX_MAGIC); } return(res); } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(ADX_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(_Symbol); //--- prepare indicator ExtHandle=iADX(_Symbol,_Period,ADXPeriod); if(ExtHandle==INVALID_HANDLE) { Print("ADX handle error"); return(INIT_FAILED); } //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return(filename); } //+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); } //+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); //--- release indicator handle if(ExtHandle!=INVALID_HANDLE) IndicatorRelease(ExtHandle); } //+------------------------------------------------------------------+
Die Prüfeinstellungen, Parameter und Ergebnisse sind in den Abbildungen 46-48 dargestellt.

Abb.46. Testereinstellung für den Expert Advisor ADXFilteredL1.mq5

Abb.47. Testparameter für den Expert Advisor ADXFilteredL1.mq5

Abb.48. Testergebnisse für den Expert Advisor ADXFilteredL1.mq5
Zum Testen müssen wir 4 Läufe mit den verschiedenen Filtereinstellungen durchführen:

Abb.49. Testparameter für das Speichern der Ergebnisse in Dateien für den Expert Advisor ADXFilteredL1.mq5
Danach erscheinen die Dateien im Testerverzeichnis; sie sollten nach C:\Data kopiert und mit PlotData.py verarbeitet werden.

Abb.50. Dateien mit den Testergebnissen für den Expert Advisor ADXFiltered.mq5 mit den verschiedenen Filtereinstellungen
3.5.3.1. Ergebnisse der Anwendung von L1-Filtern für die ADX-Strategie
Die Ergebnisse sind in den Abb. 51-53 dargestellt.

Abb.51. Balance-Kurve des Expert Advisors ADXFilteredL1.mq5 mit den verschiedenen Filtermodi

Abb.52. Equity-Kurven des Expert Advisors ADXFilteredL1.mq5 mit den verschiedenen Filtermodi

Abb.53. Kombinierte Balance- und Equity-Kurven des Expert Advisors ADXFilteredL1.mq5 mit den verschiedenen Filtermodi
3.5.4. Handelsstrategie basierend auf dem Kreuzen von EMAs
Der Expert Advisor EMAFilteredL1.mq5 implementiert eine trendfolgende Handelsstrategie, die auf dem Kreuzen zweier gleitender Durchschnitte (EMA) basiert.
Die Strategie verwendet zwei gleitende Durchschnitte:
- FastEMA – schneller exponentieller gleitender Durchschnitt;
- SlowEMA – langsamer exponentieller gleitender Durchschnitt.
Handelssignale werden wie folgt gebildet:
- BUY: wenn der schnelle EMA den langsamen EMA von unten nach oben kreuzt;
- SELL: wenn der schnelle EMA den langsamen EMA von oben nach unten kreuzt.
Für die Analyse werden die Werte der Indikatoren der letzten beiden geschlossenen Bars verwendet, wodurch der Einfluss des aktuellen, noch nicht beendeten Bars eliminiert und die Anzahl der Fehlsignale reduziert wird.
Zusätzlich werden Handelseinstiegs- und -ausstiegsfilter gemäß den L1-Trendfiltereinstellungen (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda) angewendet.
Der Code des Expert Advisors EMAFilteredL1.mq5 wird im Folgenden vorgestellt.
//+------------------------------------------------------------------+ //| EMAFilteredL1.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- best EMA parameters for EURUSD,H1,2025 input int FastEMA = 29; // Fast EMA input int SlowEMA = 101; // Slow EMA //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file //--- #define EMA_MAGIC 1234503 #include <Trade\Trade.mqh> CTrade ExtTrade; int ExtHandle = INVALID_HANDLE; bool ExtHedging = false; int FastHandle, SlowHandle; string ExtStrategyName="EMA"; string ExtStrategyFileName=""; //+------------------------------------------------------------------+ //| Check new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time=0; datetime t[1]; //--- if(CopyTime(_Symbol,_Period,0,1,t)>0) { if(t[0]!=last_time) { last_time=t[0]; return(true); } } return(false); } //+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1] - data_filtered[data_count-2]; //--- return(dp); } //+------------------------------------------------------------------+ //| GetTradeSignal (2EMA crossover) | //+------------------------------------------------------------------+ bool GetTradeSignal(ENUM_ORDER_TYPE &signal) { signal=WRONG_VALUE; //--- double fast[],slow[]; ArrayResize(fast,2); ArrayResize(slow,2); //--- ArraySetAsSeries(fast,true); ArraySetAsSeries(slow,true); //--- if(CopyBuffer(FastHandle,0,1,2,fast)!=2) return(false); //--- if(CopyBuffer(SlowHandle,0,1,2,slow)!=2) return(false); //--- if(fast[1]<slow[1] && fast[0]>slow[0]) signal=ORDER_TYPE_BUY; if(fast[1]>slow[1] && fast[0]<slow[0]) signal=ORDER_TYPE_SELL; //--- return(true); } //+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; //--- if(!GetTradeSignal(signal) || signal==WRONG_VALUE) return; //--- if(L1FilterOpen) { double dp=CheckTrendL1(); //--- if(signal==ORDER_TYPE_BUY && dp<0) return; if(signal==ORDER_TYPE_SELL && dp>0) return; } //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars) return; //--- double price=(signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK) : SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol,signal,TradeLot,price,0,0); } //+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=EMA_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type=PositionGetInteger(POSITION_TYPE); bool close_signal=false; //--- if(type==POSITION_TYPE_BUY && signal==ORDER_TYPE_SELL) close_signal=true; //--- if(type==POSITION_TYPE_SELL && signal==ORDER_TYPE_BUY) close_signal=true; //--- check L1 filter if(L1FilterClose) { double dp = CheckTrendL1(); if(type == POSITION_TYPE_BUY && dp > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp); } if(type == POSITION_TYPE_SELL && dp < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp); } } //--- if(close_signal) ExtTrade.PositionClose(_Symbol,3); } //+------------------------------------------------------------------+ //| SelectPosition | //+------------------------------------------------------------------+ bool SelectPosition() { bool res = false; if(ExtHedging) { uint total = PositionsTotal(); for(uint i=0; i<total; i++) { string sym = PositionGetSymbol(i); if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==EMA_MAGIC) { res = true; break; } } } else { if(PositionSelect(_Symbol)) res = (PositionGetInteger(POSITION_MAGIC)==EMA_MAGIC); } return(res); } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(EMA_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(_Symbol); //--- prepare indicators FastHandle=iMA(_Symbol,_Period,FastEMA,0,MODE_EMA,PRICE_CLOSE); SlowHandle=iMA(_Symbol,_Period,SlowEMA,0,MODE_EMA,PRICE_CLOSE); if(FastHandle==INVALID_HANDLE||SlowHandle==INVALID_HANDLE) return(INIT_FAILED); //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return(filename); } //+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); } //+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); //--- release indicator handles if(FastHandle!=INVALID_HANDLE) IndicatorRelease(FastHandle); //--- if(SlowHandle!=INVALID_HANDLE) IndicatorRelease(SlowHandle); } //+------------------------------------------------------------------+
Die Einstellungen, Parameter und Testergebnisse des Expert Advisors EMAFilteredL1.mq5 sind in Abb. 54-56 dargestellt.

Abb.54. Testereinstellungen für den Expert Advisor EMAFilteredL1.mq5

Abb.55. Testparameter für den Expert Advisor EMAFilteredL1.mq5

Abb.56. Testergebnisse für den Expert Advisor EMAFilteredL1.mq5
Um die Wirksamkeit der L1-Filter zu analysieren, ist es notwendig, den Expert Advisor im Tester mit verschiedenen Filtermodi nacheinander laufen zu lassen:

Abb.57. Testparameter für das Speichern von Ergebnissen in Dateien für den Expert Advisor EMAFilteredL1.mq5
Als Ergebnis werden Dateien im Tester-Ordner erstellt, die in das im Python-Skript PlotData.py angegebene Verzeichnis kopiert und mit den Einstellungen: symbol = „EURUSD“, name_strategy = „EMA“ ausgeführt werden sollten.

Abb.58. Dateien mit Testergebnissen des Expert Advisors EMAFilteredL1.mq5 unter verschiedenen Modi der Handelssignalfilter
3.5.4.1. Ergebnisse der Anwendung von L1-Filtern auf Handelssignale der EMA-Strategie
Die Ergebnisse sind in Abb. 59-61 dargestellt.

Abb.59. Balance-Kurven des Expert Advisors EMAFilteredL1.mq5 für die verschiedenen Filtermodi

Abb.60. Equity-Kurven des Expert Advisors EMAFilteredL1.mq5 für verschiedene Filtermodi

Abb.61. Kombinierte Balance- und Equity-Kurven des Expert Advisors EMAFilteredL1.mq5 für die verschiedenen Filtermodi
3.5.5. Zusammenfassung über die Verwendung des L1-Filters in MovingAverage-, MACD-, ADX- und EMA-Handelsstrategien
In den betrachteten Beispielen wurden die Handelsstrategien für das Währungspaar EURUSD, Zeitrahmen H1, für das Jahr 2025 getestet (Abb. 62).

Abb.62. EURUSD-Kurschart über den Testzeitraum (2025, H1, Schlusskurse)

Abb.63. Balance-Kurven von gleitendem Durchschnitt, MACD, ADX und EMA-Strategien unter verschiedenen Filtermodi
Die Analyse der Strategien Gleitender Durchschnitt, MACD, ADX und EMA hat gezeigt, dass die besten Ergebnisse erzielt werden, wenn die L1-Filter in der Phase der Positionsschließung angewendet wird (in den Charts grün hervorgehoben). Die Verwendung des Filters beim Ausstieg unterdrückt effektiv störende Umkehrungen und falsche Signale, sodass Positionen in Richtung eines stabilen Trends gehalten werden können. Dies führt zu einer Erhöhung des Gewinns und des Gewinnfaktors sowie zu einem geringeren maximalen Drawdown.
Die Anwendung des L1-Filters beim Öffnen von Positionen (orange hervorgehoben) erwies sich als weniger effektiv, da der zusätzliche Filter die Anzahl der Einstiege begrenzt und dazu führt, dass ein Teil der profitablen Bewegungen verpasst wird, ohne dass sich die Handelsqualität proportional verbessert.

Abb.64. Balance- und Equity-Diagramme von Strategien mit gleitendem Durchschnitt, MACD, ADX und EMA unter verschiedenen Filtermodi
So erhöht der L1-Filter von Handelssignalen beim Schließen von Positionen die Stabilität des Handelssystems, verringert die Empfindlichkeit gegenüber kurzfristigen Kursschwankungen und verbessert das Gewinn-Risiko-Verhältnis. Im Vergleich zu klassischen gleitenden Durchschnitten unterscheidet der L1-Filter besser zwischen vorübergehenden Korrekturen und echten Trendumkehrungen, was einen effizienteren Einsatz von Trendfolgestrategien ermöglicht.
Beim Handel mit Signalen, die am L1-Trendfilter ausgerichtet sind, ist das Verhalten der Balance- und Equity-Kurven zu beachten (Abb.64). Beim Handel in Trendrichtung liegt der Equity-Wert oft über dem der Balance, was die Risikokennzahlen deutlich verbessert und den Drawdown reduziert. Daher verbessert die Ausrichtung am Trend auch die Eigenschaften der Handelssysteme (verringert Drawdown und Risiken).
Darüber hinaus sinkt bei der Ausrichtung der Handelssignale am L1-Trend die Anzahl der Trades, während ihre Qualität steigt, was sich ebenfalls positiv auf die statistischen Eigenschaften der Handelsstrategien insgesamt auswirkt.
| № | Strategie | Nettogewinn insgesamt, USD | % Buy-and-Hold |
|---|---|---|---|
| 1 | Buy-and-Hold | 1363.8 | 100 % |
| 2 | GleitenderDurchschnitt (ohne Filter) | 1001.03 | 73.4 % |
| 3 | GleitenderDurchschnitt (L1-Einstiegsfilter) | 107.65 | 7.89 % |
| 4 | GleitenderDurchschnitt (L1-Ausstiegsfilter) | 1342.5 | 98.43 % |
| 5 | GleitenderDurchschnitt (L1 Einstieg + Ausstieg) | 986.16 | 72.31 % |
| 6 | MACD (ohne Filter) | 997.79 | 73.16 % |
| 7 | MACD (L1-Einstiegsfilter) | 140.13 | 10.27 % |
| 8 | MACD (L1-Ausstiegsfilter) | 1359.52 | 99.69 % |
| 9 | MACD (L1 Einstieg + Ausstieg) | 697.54 | 51.15 % |
| 10 | ADX (ohne Filter) | 791.99 | 58.07 % |
| 11 | ADX (Einstiegsfilter L1) | -50.9 | -3.73 % |
| 12 | ADX (L1-Ausstiegsfilter) | 940.39 | 68.95 % |
| 13 | ADX (L1 Einstieg + Ausstieg) | 430.05 | 31.53 % |
| 14 | EMA (ohne Filter) | 957.3 | 70.19 % |
| 15 | EMA (L1-Einstiegsfilter) | -173.35 | -12.71 % |
| 16 | EMA (L1-Ausstiegsfilter) | 1258.99 | 92.31 % |
| 17 | EMA (L1 Eingang + Ausgang) | -131.41 | -9.64% |
Tabelle 4. Gesamtgewinn bei Verwendung des L1-Filters in den Strategien MovingAverage, MACD, ADX und EMA im Vergleich zu Buy-and-Hold
Tabelle 4 zeigt, dass die Verwendung des L1-Filters beim Schließen von Positionen die Rentabilität aller Strategien verbesserte.
Wenn das Ergebnis der Strategie Buy-and-Hold ($1363,8) als 100% der gesamten Trendbewegung betrachtet wird, ergibt sich folgendes Bild:
- Der gleitende Durchschnittsgewinn stieg von 73,4% auf 98,43%;
- Der MACD-Gewinn stieg von 73,16 % auf 99,69 %;
- Der ADX-Gewinn stieg von 58,07 % auf 68,5 %;
- Der EMA-Gewinn stieg von 70,19% auf 92,31%.
Wie wir sehen, ermöglichte die Verwendung des L1-Filters den Strategien Gleitender Durchschnitt, MACD und EMA eine Gewinnsteigerung von 22-26%, wobei der größte Teil der Trendbewegung (98,43%, 99,69% und 92,31%) erfasst werden konnte und sich dem Ergebnis von Buy-and-Hold annäherte. Der Gewinn der ADX-Strategie stieg um 10 %.
In den Beispielen wurden Strategien mit Parametern berücksichtigt, die die höchsten Balancewerte ergaben (d. h. zu den bestmöglichen Lösungen gehörten). Sie sind blau hervorgehoben. Die Ergebnisse zeigen, dass selbst diese profitabelsten Lösungen durch zusätzliche Filter der Handelssignale durch Ausrichtung am L1-Trend weiter verbessert werden können. Einige Strategien haben sich nur geringfügig verbessert (beim ADX liegen die grünen Kurven nahe an den blauen, was auf eine Annäherung an die optimale Balancelösung hinweist). Die Qualität der Handelssignale einer Strategie (Optimalität der ausgewählten Parameter) lässt sich daran messen, wie sehr sie sich durch eine solche L1-Filter verbessert.
Insbesondere wurde in diesem Fall ein stark tendierender EURUSD-Markt berücksichtigt (Abb. 62). Für andere Marktregime und Instrumente werden die Ergebnisse anders ausfallen. Darüber hinaus wurde der L1-Trend auf dem H1-Zeitrahmen mit einem Regularisierungsparameter λ = 0,2 · λmax konstruiert. Für andere Instrumente und Zeiträume können geeignete Werte für diesen Koeffizienten anhand von L1-Trendindikatoren geschätzt werden.
Schlussfolgerung
Der L1-Trendfilter hat seine praktische Nützlichkeit als Instrument zur Trennung von lokalem Rauschen und echten Trendänderungen bewiesen.
Die Methode erzeugt einen stückweise linearen Trend mit automatischen Bruchpunkten und einer bequemen Abstimmungsskala über λmax, wodurch das Problem der manuellen Parameteranpassung entfällt.
Auf der Ebene der praktischen Integration wurde ein komplettes Toolkit bereitgestellt: Funktionen zur Berechnung von λmax und des L1-Filters, drei Indikatoren (L1Trend, L1TrendSlope, L1TrendSlopeSign), sieben L1-Trend-Volatilitätsindikatoren (L1Volatility, L1VolatilitySmoothed, L1VolatilityAbsolute, L1VolatilityNormalized, L1VolatilityNormalizedSmoothed, L1VolatilityRegime, L1VolatilityRegimeColor), Expert Advisor Vorlagen und ein reproduzierbares Testprotokoll (vier Modi: kein Filter, Einstiegsfilter, Ausstiegsfilter, beide Filter; Speichern der Ergebnisse und Python-Visualisierungsskript).
Es sei darauf hingewiesen, dass der L1-Trendfilter auch für die Datenbeschriftung beim maschinellen Lernen verwendet werden kann. Insbesondere in dem Artikel „Developing Trend Trading Strategies Using Machine Learning“ wird die Trendbestimmung mithilfe von Ableitungen der durch den Savitzky-Golay-Filter geglätteten Kurse durchgeführt. Ein ähnlicher Ansatz kann mit dem L1-Filter umgesetzt werden, bei der der Trend durch stückweise lineare Funktionen angenähert wird und die Stärke des Trends auf jedem Segment natürlich mit der Steigung des entsprechenden Segments zusammenhängt.
Praktische Empfehlungen:
- Relative Regularisierung verwenden: λ = coef_lambda_max · λmax. Für die meisten Aufgaben sollte der Koeffizient im Bereich 0,04-0,25 liegen; für feinere Details ≈0,02-0,04; für grobe Annäherung und Regime-Erkennung ≈0,12-0,25.
- In den meisten Fällen ist der L1-Filter am effektivsten, wenn er beim Schließen von Positionen eingesetzt wird (um profitable Trends zu halten und vorzeitige Ausstiege zu vermeiden). Die Anwendung auf den Einstieg reduziert oft die Anzahl der Abschlüsse, ohne dass die Qualität proportional verbessert wird.
- Für die Analyse des aktuellen Trends verwenden wir eine einfache Regel: delta = x_filtered[last] – x_filtered[last-1]. Das Vorzeichen von delta gibt die Richtung des dominanten L1-Trends an.
Einschränkung: Die Wirkung hängt vom Instrument, dem Zeitrahmen und dem Marktregime ab; eine Validierung anhand historischer Daten mit ausgewählten Metriken ist erforderlich.
Die vorgeschlagenen MQL5-Module und das Testprotokoll ermöglichen einen schnellen Hypothesentest und die Auswahl von Arbeitsparametern für ein bestimmtes Handelssystem.
Alle Codes aus dem Artikel sind auch im öffentlichen Projekt „MQL5\Shared Projects\L1Trend“ verfügbar.
Beispiele
| Typ | Datei | Beschreibung |
|---|---|---|
| Skript | MQL5\Scripts\TestL1Trend.mq5 | Testskript für die Berechnung des L1-Trends auf Modelldaten (Random Walk) |
| Skript | MQL5\Scripts\TestL1TrendFloatDouble.mq5 | Testskript zur Berechnung des L1-Trends auf Modelldaten (Random Walk) für Double- und Float-Vektoren |
| Skript | MQL5\Scripts\TestL1TrendFilterSP500.mq5 | Testskript für die Berechnung des L1-Trends auf SP500-Indexkursdaten |
| Datei | MQL5\Files\snp500.txt | Datendatei für das Testskript (Protokoll der Kursreihe des SP500-Index) |
| Skript | MQL5\Scripts\TestScalingBrownianMotion.mq5 | Skript zur Berechnung der Potenzgesetz-Abhängigkeit von λmax für die Brownsche Bewegung |
| Skript | MQL5\Scripts\TestScalingSymbol.mq5 | Skript zur Berechnung der Potenzgesetz-Abhängigkeit von λmax für Kursreihen mit einem bestimmten Symbol |
| Indikator | MQL5\Indicators\L1TrendFilter.mq5 | Indikator für die Berechnung des L1-Trends |
| Indikator | MQL5\Indicators\L1TrendFilter_Slope.mq5 | Indikator für die Berechnung der Änderungsrate des L1-Trends |
| Indikator | MQL5\Indicators\L1TrendFilter_SlopeSign.mq5 | Indikator zur Berechnung des Vorzeichenwechsels des L1-Trends |
| Indikator | MQL5\Indicators\L1Volatility.mq5 | Indikator zur Berechnung der Restvolatilität (Differenz zwischen den Schlusskursen und dem L1-Trendwert) |
| Indikator | MQL5\Indicators\L1VolatilitySmoothed.mq5 | Indikator für die Berechnung der geglätteten Restvolatilität |
| Indikator | MQL5\Indicators\L1VolatilityAbsolute.mq5 | Indikator für die Berechnung der absoluten Volatilität |
| Indikator | MQL5\Indicators\L1VolatilityNormalized.mq5 | Indikator zur Berechnung der normalisierten Volatilität unter Verwendung der ATR (Average True Range) und des L1-Trends |
| Indikator | MQL5\Indicators\L1VolatilityNormalizedSmoothed.mq5 | Indikator für die Berechnung der geglätteten normalisierten Volatilität |
| Indikator | MQL5\Indicators\L1VolatilityRegime.mq5 | Indikator für die Erkennung von Marktregimen |
| Indikator | MQL5\Indicators\L1VolatilityRegimeColor.mq5 | Farbversion des Indikators zur Erkennung von Marktregimen |
| Expert Advisor | MQL5\Experts\MovingAverageFilteredL1.mq5 | Expert Advisor Handel auf Basis der Strategie des gleitenden Durchschnitts mit L1-Filter |
| Expert Advisor | MQL5\Experts\MACDFilteredL1.mq5 | Expert Advisor Handel auf Basis der MACD-Strategie mit L1-Filter |
| Expert Advisor | MQL5\Experts\ADXFilteredL1.mq5 | Expert Advisor Handel auf Basis der ADX-Strategie mit L1-Filter |
| Expert Advisor | MQL5\Experts\EMAFilteredL1.mq5 | Expert Advisor Handel basierend auf dem Kreuzen von zwei EMAs mit L1-Filter |
| Python-Skript | MQL5\Scripts\PlotData.py | Python-Skript zur Analyse der Wirksamkeit der Anwendung des L1-Filters auf Handelssignale |
Tabelle 5. Beschreibung der im Artikel verwendeten Programmcodes
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/21142
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Neuronale Netze im Trading: Anomalieerkennung im Frequenzbereich (CATCH)
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Eindimensionale Singularspektralanalyse
- 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.
Es geht ungefähr so:
Es geht ungefähr so:
Die Aufspaltung in Trends hängt sehr stark vom Regularisierungsparameter lambda ab - je kleiner lambda, desto kürzere Trends können erfasst werden.
In den betrachteten Beispielen wurden feste Werte von lambda in der Einheit lambda=0,2*lambda_max verwendet. Die Berechnung in Einheiten von lambda_max ermöglicht teilweise eine Anpassung an die Daten. Der lambda_max-Wert selbst hängt von der Geometrie der Reihe (relative Streuung), d. h. der Volatilität, ab.
Es ist zu bedenken, dass ein Trend verschiedene Phasen und einen eigenen Lebenszyklus hat. Daher brauchen wir einen Mechanismus, um uns an den aktuellen Trend anzupassen, d.h. Lambda irgendwie zu verwalten und die optimale Trendaufteilung zu finden - diese Aufgabe ist noch nicht gelöst.
Wenn die Strategie selbst in dem Intervall keinen Gewinn abwirft, wird auch der Filter nicht helfen.
Die besten Ergebnisse sollten auf einem idealen Trendmarkt erzielt werden, das Beispiel war wie folgt: EURUSD, 2025, H1 (die besten Parameter MovingAverage period=61).
L1-Schluss-Filter
Hier können wir sehen, dass der Ausstiegsfilter dazu beigetragen hat, den Gewinn im Trendbereich zu erhöhen.
Eine Variante der gleichen Strategie mit zusätzlichen Positionen bei Korrekturen:
Ohne Hinzufügungen:
Mit Hinzufügungen:
Intervalle mit flachem Markt enthalten lokale kleine Trends, und um diese korrekt zu berücksichtigen, sollten wir kleinere Werte des Lambda-Parameters verwenden (wenn er als Ausstiegsfilter verwendet wird).
Außerdem sollten die besten Werte der MovingAverage-Parameter im Intervall mit flachem Markt anders sein. D.h. die optimalen Perioden der Durchschnitte auf dem zweiten Intervall haben sich geändert (aber bei der Optimierung im Tester geben die gefundenen Parameter den höchsten Gewinn unter allen anderen auf dem gesamten Intervall der Optimierung).
Prüfen wir die Ergebnisse für flache Intervalle mit verschiedenen Lambdas.
Ohne Filter:
Mit Ausgangsfilter lambda=0,2*lambda_max
Mit Filter lambda=0,001 lambda_max (kleinere Trends).
Auf der flachen Strecke bei lambda=0,001 lambda_max können wir also das Ergebnis ohne Filter verbessern und lokale kleine Trends berücksichtigen.
Allerdings zeigte die Variante mit dem Filter lambda=0,2*lambda_max hier eine geringere Rentabilität als die Strategie ohne Filter.
Variante mit Hinzufügung von Positionen (unterschiedliches Lambda) zu lokalen Trends innerhalb der Ebenheit
Ohne Filter:
C-Filter lambda=0.2*lambda_max und Hinzufügen von Korrekturen:
C-Filter mit lambda=0,001*lambda_max und Hinzufügen von Korrekturen:
Die Variante mit Filter mit lambda=0,2*lambda_max und Hinzunahme von Korrekturen zeigte ein besseres Ergebnis als die Variante ohne Filter.
Das Hinzufügen lokaler kleiner Trends (lambda=0,001*lambda_max) innerhalb des flachen Intervalls auf Korrekturen ermöglichte es, den Gewinn der ursprünglichen Strategie ohne Filter zu erhöhen (und die Variante mit lambda=0,2*lambda_max in Bezug auf den Gewinn zu verbessern).
Variante mit Hinzufügung von Positionen (unterschiedliches Lambda) zu lokalen Trends innerhalb der Ebenheit
Ohne Filter:
C-Filter lambda=0.2*lambda_max und Hinzufügen von Korrekturen:
C-Filter lambda=0,001*lambda_max und Zusatzkorrekturen:
Die Variante mit Filter mit lambda=0,2*lambda_max und Hinzunahme von Korrekturen zeigte ein besseres Ergebnis als die Variante ohne Filter.
Das Hinzufügen lokaler kleiner Trends (lambda=0,001*lambda_max) innerhalb des flachen Intervalls auf Korrekturen ermöglichte es, den Gewinn der ursprünglichen Strategie ohne Filter zu erhöhen (und die Variante mit lambda=0,2*lambda_max in Bezug auf den Gewinn zu verbessern).
Handel, zumindest auf Demo
Das Verständnis wird mit der Erfahrung und nach dem Warten von 10 Monaten nutzloser Arbeit kommen.