English Русский
preview
Anwendung des L1-Trendfilters in MetaTrader 5

Anwendung des L1-Trendfilters in MetaTrader 5

MetaTrader 5Beispiele |
29 8
MetaQuotes
MetaQuotes
Das Hauptziel des L1-Trendfilters besteht darin, den zugrunde liegenden Trend aus einer Zeitreihe so zu extrahieren, dass:
  • die langfristige Dynamik der Daten erhalten bleibt;
  • kurzfristige Schwankungen und Rauschen unterdrückt werden;
  • strukturelle Bruchpunkte (Änderungen der Trendsteigung) automatisch erkannt werden.
Im Gegensatz zu klassischen Glättungsverfahren erzwingt dieser Ansatz keinen glatten Trend, sondern liefert eine stückweise lineare Approximation, was besonders für die Analyse von Finanzzeitreihen wichtig ist.

L1-Trendfilter



Inhalt


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

    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 die logarithmische Kursreihe des S&P 500-Index

    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 , während der zweite Differenzenoperator mit skaliert.  Bei der Berechnung von λmax wird das Maximum einer Größe ermittelt, die mit dem Integral der Krümmung der Reihe zusammenhängt.


    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. Potenzgesetzabhängigkeit von λmax für die verschiedenen EURUSD-Zeitrahmen

    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. Potenzgesetzabhängigkeit von λmax für die verschiedenen USDJPY-Zeitrahmen

    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. Potenzgesetzliche Abhängigkeit von LambdaMax für die verschiedenen GBPUSD-Zeitrahmen

    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:

    1. die Länge des Datenfensters,
    2. den Zeitrahmen,
    3. 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;
    Diese Indikatoren können für die visuelle Analyse der Trendzerlegung verwendet werden und helfen, geeignete Werte des Regularisierungsparameters λ für die Verwendung in Handelsstrategien zu ermitteln.

    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

    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 L1TrendFilter.mq5-Indikators mit den verschiedenen CoefLambda-Werten

    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

    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

    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. L1Volatilität.mq5 Indikator

    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

    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

    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

    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

      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

      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

        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.18. Volatilitätsindikatoren für EURGBP


        Abb.19. Volatilitätsindikatoren für AUDCAD

        Abb.19. Volatilitätsindikatoren für AUDCAD


        Abb.20. Volatilitätsindikatoren für CHFJPY

        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:

        1. L1FilterOpen = false, L1FilterClose = false (Handel ohne Filter);
        2. L1FilterOpen = true, L1FilterClose = false (Filter für die Eröffnung);
        3. L1FilterOpen = false, L1FilterClose = true (Filter für das Schließen);
        4. 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:

          1. Eine geglättete Kursreihe wird unter Verwendung der letzten L1TotalBars erstellt;
          2. 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:

            1. Finde den besten Parametersatz der Handelsstrategie, der den maximalen Gewinn abwirft.
              Es ist ratsam, die Handelssignale der leistungsstärksten Strategien zu verbessern.

            2. 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;
              Speicher die Ergebnisse in einer Datei (Eingabeparameter SaveStatistics=true). Die resultierenden Dateien sollten in einen separaten Ordner kopiert werden (in den Beispielen: C:\Data\).

            3. 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

            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

            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

            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. Testeinstellungen für den Expert Advisor MovingAverageFilteredL1.mq5

            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.25. Beste Optimierungsparameter des Expert Advisors MovingAverageFilteredL1.mq5


            Abb.26. Testergebnisse mit den besten Parametern für den Expert Advisor 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 die Speicherung der Ergebnisse in Dateien für den Expert Advisor MovingAverageFilteredL1.mq5

            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 den Testergebnissen des Expert Advisors MovingAverageFilteredL1.mq5 für alle 4 Filtermodi

            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

            Abb.29. Skript „PlotData.py“ in MetaEditor


            Nach der Ausführung von PlotData.py werden die folgenden Charts angezeigt:

            1. EURUSD-Kursreihe (Schlusskurse pro Bar);
            2. Balance-Kurven für alle Filtermodi;
            3. Equity-Kurven für alle Filtermodi;
            4. 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.30. EURUSD-Kurschart für den Testzeitraum


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

            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

            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

            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

            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:

            1. Blau (Strategie ohne Filter);
            2. Grün (L1-Ausstiegsfilter der Position);
            3. Rot (L1-Filter sowohl beim Öffnen als auch beim Schließen);
            4. Orange (L1-Filter beim Öffnen);

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

            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.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

            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:

            1. BUY – die Hauptlinie des MACD kreuzt die Signallinie von unten nach oben.
            2. 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 Strategietesters für den Expert Advisor MACDFilteredL1.mq5

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


            Abb.39. Testparameter des Expert Advisors MACDFilteredL1.mq5

            Abb.39. Testparameter des Expert Advisors MACDFilteredL1.mq5


            Abb.40. Testergebnisse des Expert Advisors MACDFilteredL1.mq5

            Abb.40. Testergebnisse des Expert Advisors MACDFilteredL1.mq5


            Abb.41. Testparameter für die Speicherung der Ergebnisse in Dateien für den Expert Advisor 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

            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.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.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

            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.46. Testereinstellung für den Expert Advisor ADXFilteredL1.mq5


            Abb.47. Testparameter 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

            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 die Speicherung der Ergebnisse in Dateien für den Expert Advisor ADXFilteredL1.mq5

            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

            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-Kurven des Expert Advisors ADXFilteredL1.mq5 mit den verschiedenen Filtermodi

            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.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

            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:

            1. FastEMA – schneller exponentieller gleitender Durchschnitt;
            2. 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. Testeinstellungen für den Expert Advisor EMAFilteredL1.mq5

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


            Abb.55. Testparameter 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

            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 die Speicherung der Ergebnisse in Dateien für den Expert Advisor EMAFilteredL1.mq5

            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 den Testergebnissen des Expert Advisors EMAFilteredL1.mq5 unter verschiedenen Modi der Handelssignalfilter

            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.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.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

            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.62. EURUSD-Kurschart über den Testzeitraum (2025, H1, Schlusskurse)


            Abb.63. Balance-Kurven der Strategien Gleitender Durchschnitt, MACD, ADX und EMA unter verschiedenen Filtermodi

            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-Kurven der Strategien Gleitender Durchschnitt, MACD, ADX und EMA unter verschiedenen Filtermodi

            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:

            1. Der gleitende Durchschnittsgewinn stieg von 73,4% auf 98,43%;
            2. Der MACD-Gewinn stieg von 73,16 % auf 99,69 %;
            3. Der ADX-Gewinn stieg von 58,07 % auf 68,5 %;
            4. 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

            Beigefügte Dateien |
            Letzte Kommentare | Zur Diskussion im Händlerforum (8)
            Renat Akhtyamov
            Renat Akhtyamov | 20 Apr. 2026 in 19:09

            Es geht ungefähr so:


            Quantum
            Quantum | 20 Apr. 2026 in 20:12
            Renat Akhtyamov #:

            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).

            EURUSD

            Optionen für Tester

            Parameter des Prüfgeräts

            Testergebnisse ohne Filter

            L1-Schluss-Filter

            L1 Filter schließen

            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:

            Testeroptionen mit Zusatz

            Testeroptionen mit Positionen hinzufügen

            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).

            Quantum
            Quantum | 20 Apr. 2026 in 21:08

            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.

            Quantum
            Quantum | 20 Apr. 2026 in 21:30

            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).

            Renat Akhtyamov
            Renat Akhtyamov | 21 Apr. 2026 in 08:06
            Quantum #:

            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.

            Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
            In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
            Neuronale Netze im Trading: Anomalieerkennung im Frequenzbereich (CATCH) Neuronale Netze im Trading: Anomalieerkennung im Frequenzbereich (CATCH)
            Das CATCH-Framework kombiniert Fourier-Transformation und Frequenz-Patching, um Marktanomalien genau zu erkennen, die mit herkömmlichen Methoden kaum oder gar nicht erkannt werden können. Im Folgenden untersuchen wir, wie dieser Ansatz verborgene Muster in Finanzdaten aufdeckt.
            Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
            In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
            Eindimensionale Singularspektralanalyse Eindimensionale Singularspektralanalyse
            Der Artikel untersucht die theoretischen und praktischen Aspekte der Methode der singulären Spektralanalyse (SSA), einer effizienten Methode der Zeitreihenanalyse, die es ermöglicht, die komplexe Struktur einer Reihe als Zerlegung in einfache Komponenten, wie Trend, saisonale (periodische) Schwankungen und Rauschen, darzustellen.